Visualizing data with D3.js
import * as d3 from 'd3' ;
/*
d3.select(selector: string)
μΈμλ‘ CSS μ νμ λ¬Έμμ΄μ μ λ¬, DOM μμλ₯Ό μ ννκ³ μ νν μμμ λν D3.js κΈ°λ₯μ μ μ©νλλ° μ¬μ©
*/
const element = d3 . select ( 'css selector' ) ;
/*
d3.selectAll(selector: string)
λ§€μΉλ λͺ¨λ μμλ₯Ό κ°λ Selection κ°μ²΄ λ°ν
*/
const elements = d3 . selectAll ( 'css selector' ) ;
/*
Selection κ°μ²΄μ ꡬ쑰λ μλμ κ°μ΅λλ€.
Selection κ°μ²΄λ₯Ό μ¬μ©νμ¬ μ νν μμμ μ€νμΌ, μμ±, λ°μ΄ν° λ°μΈλ©, μ΄λ²€νΈ νΈλ€λ§ λ±μ μ‘°μν μ μμ΅λλ€. λν Selection κ°μ²΄λ₯Ό μ΄μ©νμ¬ μ λλ©μ΄μ
κ³Ό μ ν ν¨κ³Όλ₯Ό μ μ©νμ¬ λμ μΈ ν¨κ³Όλ₯Ό ꡬνν μλ μμ΅λλ€. μ΄λ₯Ό ν΅ν΄ λ°μ΄ν° μκ°ν λ° DOM μ‘°μμ ν¨κ³Όμ μΌλ‘ μνν μ μμ΅λλ€.
Selection {
_groups: [Array of DOM elements], // μ νν μμμ κ·Έλ£Ή (μ¬λ¬ μμλ₯Ό ν¬ν¨ν μ μμ)
_parents: [Array of parent nodes], // μ νν μμμ λΆλͺ¨ λ
Έλ κ·Έλ£Ή (λ§μ°¬κ°μ§λ‘ μ¬λ¬ λΆλͺ¨ λ
Έλλ₯Ό ν¬ν¨ν μ μμ)
}
- _groups: μ νν DOM μμμ κ·Έλ£Ήμ
λλ€. μ΄λ λ°°μ΄ ννλ‘ μ¬λ¬ DOM μμλ₯Ό ν¬ν¨ν μ μμ΅λλ€.
μλ₯Ό λ€μ΄, d3.selectλ₯Ό μ¬μ©νμ¬ νλμ μμλ₯Ό μ νν κ²½μ°μλ _groups λ°°μ΄μ ν΄λΉ μμκ° ν¬ν¨λ©λλ€.
μ¬λ¬ κ°μ μμλ₯Ό μ νν κ²½μ°, κ° μμλ λ°°μ΄ λ΄μ κ°κ°μ μμλ‘ ννλ©λλ€.
- _parents: μ νν DOM μμμ λΆλͺ¨ λ
Έλ κ·Έλ£Ήμ
λλ€.
μ΄ μμ λ°°μ΄ ννλ‘ μ¬λ¬ λΆλͺ¨ λ
Έλλ₯Ό ν¬ν¨ν μ μμ΅λλ€.
_groupsμ μ μ¬νκ², κ° λΆλͺ¨ λ
Έλλ λ°°μ΄ λ΄μ κ°κ°μ μμλ‘ ννλ©λλ€.
*/
import * as d3 from 'd3' ;
/*
Selection.append(type: string)
μ νν μμ λ΄μ μλ‘μ΄ μμ μμλ₯Ό μΆκ°νλλ° μ¬μ©. μΆκ°λ μμλ μ νν μμ λ΄μμ ν΄λΉ μμλ€μ κ°μ₯ λ§μ§λ§μ μμΉ
λ°νκ°μ μλ‘κ² μΆκ°λ μμλ₯Ό κ°λ Selection κ°μ²΄
*/
d3 . select ( 'css selector' ) . append ( 'tag name' ) ;
/*
Selection.remove()
μ νλ μμλ₯Ό DOMμμ μ κ±°νλλ° μ¬μ©
*/
d3 . select ( 'css selector' ) . remove ( ) ;
/*
Selection.attr(key: string, value?: string)
νλ μμμ μμ±(HTML Attribute)μ μ€μ νκ±°λ κ°μ Έμ¬ λ μ¬μ©
λ°νκ°μ μμ λ μμλ₯Ό κ°λ Selection κ°μ²΄
*/
d3 . select ( 'css selector' ) . attr ( 'key' , 'value' ) ;
d3 . select ( 'css selector' ) . attr ( 'key' ) ; // 'value'
/*
Selection.style(key: string, value?: any, priority?: 'important')
μ νλ μμμ CSS μ€νμΌμ μ€μ νκ±°λ κ°μ Έμ¬ λ μ¬μ©
*/
d3 . select ( 'css selector' ) . style ( 'key' , 'value' ) ;
d3 . select ( 'css selector' ) . style ( 'key' ) ; // 'value'
import * as d3 from 'd3' ;
/*
selection.call(function: (element: Selection) => void)
μ νν μμμ μΈμλ‘ μ λ¬ν ν¨μλ₯Ό μ μ©. μ΄ ν¨μλ μ νν μμλ₯Ό λμμΌλ‘ μμ
μ μννκ±°λ μ€μ μ λ³κ²½
첫 λ²μ§Έ μΈμλ‘ μ νλ μμ μ λ¬
*/
d3 . select ( 'css selector' ) . call ( ( element : Selection ) => {
// element μ‘°μ,,,
} ) ;
import * as d3 from 'd3' ;
// μ£Όλ‘ λ§λ μ°¨νΈ, κ·Έλ£Ήνλ λ§λ μ°¨νΈ, μΆκ³Ό κ°μ μκ°ν μμμμ x λλ y μΆμ μμΉλ₯Ό μ€μ νλλ° νμ©
const createXScale = ( xAxisDataList : string [ ] , svgWidth : number ) => {
/*
d3.scaleBand(domain: string[], range: [number, number])
domainμλ μ
λ ₯κ°μ λ²μ£Όλ€μ μ€μ , rangeμλ ν΄λΉ λ²μ£Όλ€μ΄ νμλ μμΉ(μΆμ μμΉ)λ₯Ό μ€μ
d3.scaleBandμ λ°ν κ°μ ν¨μ. μ΄ ν¨μμ λ²μ£Όν λ°μ΄ν° κ°μ μ
λ ₯νλ©΄ ν΄λΉ κ°μ΄ μΆμ μμΉλ‘ λ³ν
*/
return d3 . scaleBand ( ) . domain ( xAxisData ) . range ( [ 0 , svgWidth ] ) ;
} ;
const xScale = createXScale ( [ 'a' , 'b' , 'c' ] , 300 ) ;
console . log ( xScale ( 'a' ) ) ; // 0
console . log ( xScale ( 'b' ) ) ; // 150
console . log ( xScale ( 'c' ) ) ; // 300
import * as d3 from 'd3' ;
// μ£Όλ‘ μ κ·Έλν, μ°μ λ, μΆκ³Ό κ°μ μκ°ν μμμμ λ°μ΄ν°μ κ°μ ν½μ
μ’νλ ν¬κΈ°λ‘ λ³ννλλ° νμ©
const createYScale = ( yAxisDataList : number [ ] , svgHeight : number ) => {
/*
d3.scaleLinear(domain: [number, number], range: [number, number])
domainμλ μ
λ ₯κ°μ λ²μλ₯Ό μ€μ , rangeμλ ν΄λΉ μ
λ ₯κ°λ€μ΄ νμλ μμΉ(μΆμ μμΉ)λ ν¬κΈ°μ λ²μλ₯Ό μ€μ
λ°ν κ°μ ν¨μ. μ΄ ν¨μμ μ
λ ₯κ°μ μ λ¬νλ©΄ ν΄λΉ κ°μ΄ μΆλ ₯ λ²μ λ΄μ μμΉλ‘ λ³ν
*/
return d3
. scaleLinear ( )
. domain ( [ 0 , d3 . max ( yAxisDataList ) ] )
. range ( [ 0 , svgHeight ] ) ;
} ;
const yScale = createYScale ( [ 10 , 130 ] , 960 ) ;
console . log ( yScale ( 10 ) ) ; // 80
console . log ( yScale ( 10 ) ) ; // 320
import * as d3 from 'd3' ;
const createXAxis = (
svgEl : d3 . Selection < SVGSVGElement , unknown , HTMLElement , any > ,
xSacle : d3 . ScaleBand < string >
) => {
/*
d3.axisBottom(sacle: AxisScale)
xμΆμ μ€μΌμΌκ³Ό μ°κ²°νμ¬ x μΆμ μμ±. μ΄ μ€μΌμΌμ μ
λ ₯ λλ©μΈμ κ°μ μΆλ ₯ λ²μμ λ§€ννλ μν μ μν
μΈμλ‘ scale ν¨μ μ λ¬
*/
svgEl . append ( 'g' ) . call ( d3 . axisBottom ( xScale ) ) ;
} ;
const createYAxis = (
svgEl : d3 . Selection < SVGSVGElement , unknown , HTMLElement , any > ,
yScale : d3 . ScaleLinear < number , number , never >
) => {
svgEl. append ( 'g' ) . call ( d3 . axisTop ( yScale ) ) ;
} ;
import * as d3 from 'd3' ;
const createBar = (
svgEl : d3 . Selection < SVGSVGElement , unknown , HTMLElement , any > ,
xSacle : d3 . ScaleBand < string > ,
yScale : d3 . ScaleLinear < number , number , never > ,
chartDataList : { xAxisData : string , yAxisData : number } [ ]
) => {
/*
Selection.data(any[])
λ°μ΄ν°λ₯Ό μ νν μμμ λ°μ΄ν° λ°μΈλ©νλλ° μ¬μ©. μ΄λ₯Ό ν΅ν΄ λ°μ΄ν°μ μμκ° μ°κ²°λμ΄ λ°μ΄ν°μ λ³νμ λ°λΌ μμμ μνλ₯Ό μ
λ°μ΄νΈ
λ°μ΄ν°λ₯Ό λ°μΈλ©νλ©΄ μλ‘μ΄ λ°μ΄ν°μ κ°―μμ λ°λΌ enter, update, exitμ κ°λ
μ μ¬μ©νμ¬ μμλ₯Ό κ΄λ¦¬ κ°λ₯
λ°μΈλ©λ λ°μ΄ν°λ μ νν μμ λ΄μμ μ½λ°± ν¨μ λ±μμ μ¬μ©
λ°μ΄ν° λ°μΈλ© ν μ΄ν λ©μλ 체μ΄λ λ λ²μ§Έ μΈμλ‘ μ½λ°±ν¨μ μ λ¬νμ¬ λ°μΈλ©ν λ°μ΄ν° μ κ·Ό κ°λ₯
ex) Selection.data(dataList).attr('key', (data: any) => { ,,, })
μ μμ μ²λΌ data λ©μλλ‘ λ°μ΄ν° λ°μΈλ© ν attr λ©μλ 체μ΄λ λ λ²μ§Έ μΈμλ‘ μ½λ°±ν¨μ μ λ¬νμ¬ λ°μΈλ©λ λ°μ΄ν°μ μ κ·Ό κ°λ₯
μ½λ°±ν¨μλ μλμ κ°μ μΈμλ₯Ό μ λ¬λ°μ
1. data: λ°μ΄ν° λ°μΈλ©λ μμμ λμνλ λ°μ΄ν° νλͺ©
2. index: λ°μ΄ν° λ°°μ΄ λ΄μμμ λ°μ΄ν° νλͺ©μ μΈλ±μ€
3. groupIndex: selectAll() λ©μλλ₯Ό μ¬μ©νμ¬ μ¬λ¬ κ·Έλ£Ήμ μ νν κ²½μ°μ ν΄λΉ κ·Έλ£Ήμ μΈλ±μ€λ₯Ό μ λ¬
4. nodes: μΌλΆ λ©μλλ μ΄λ²€νΈμμλ μ νλ μμμ λν μ 보
5. event: μ΄λ²€νΈμ κ΄λ ¨λ μ 보
*/
/*
Selection.enter()
μλ‘μ΄ λ°μ΄ν°μ λμνλ Selection(μμ)λ₯Ό μΆκ°νλ μν
λ°μΈλ©ν λ°μ΄ν°μ κ°μκ° Selection κ°μ²΄λ³΄λ€ λ§μ κ²½μ°, μλ‘μ΄ Selection κ°μ κ°μ²΄ μμ±νμ¬ λ°μ΄ν° λ°μΈλ©νκ³ κ°μ κ°μ²΄ λ°ν
μ΄ν append λ©μλλ₯Ό ν΅ν΄ κ°μ Selection κ°μ²΄λ₯Ό μ€μ Selection κ°μ²΄λ‘ μΆκ°
μ¦, selectAll -> data -> enter -> append μμλ‘ μ¬μ©
*/
svgEl
. append ( 'g' )
. selectAll ( 'rect' )
. data ( chartDataList )
. enter ( )
. append ( 'rect' )
. attr ( 'x' , ( chartData ) => xScale ( chartData . xAxisData ) )
. attr ( 'y' , ( chartData ) => yScale ( chartData . yAxisData ) )
. attr ( 'width' , 50 )
. attr ( 'height' , yScale ( 0 ) - yScale ( d . yAxisData ) ) ;
} ;
import * as d3 from 'd3' ;
const handleMouseMove = ( event : MouseEvent ) => {
/*
d3.bisect(array: ArrayLike<number>, x: number, lo?: number, hi?: number): number
첫 λ²μ§Έ μΈμλ‘ λ°°μ΄, λ λ²μ§Έ μΈμλ‘ μ²« λ²μ§Έ λ°°μ΄μ μ½μ
ν κ°, μΈ λ²μ§Έ μΈμλ κ²μ μμ, λ€ λ²μ§Έ μΈμλ κ²μ λ§μ§λ§ μΈλ±μ€ μ λ¬
μ λ ¬λ λ°°μ΄μμ νΉμ κ°μ΄ μ½μ
λ μμΉλ κ°μ μ°Ύλλ° μ¬μ©λλ ν¨μ
*/
const currentDataIndex =
d3 . bisect (
xAxisData . map ( ( data ) => xScale ( data ) ) ,
event . offsetX
) - 1 ;
} ;
import * as d3 from 'd3' ;
const createLinePath = (
svgEl : d3 . Selection < SVGSVGElement , unknown , HTMLElement , any > ,
xSacle : d3 . ScaleBand < string > ,
yScale : d3 . ScaleLinear < number , number , never > ,
chartDataList : { xAxisData : string , yAxisData : number } [ ]
) => {
// [xμ’ν, yμ’ν]λ₯Ό μμλ‘ κ°λ λ°°μ΄
const lineGeneratorParams = chartDataList . map ( ( chartData ) => [
xScale ( chartData . xAxisData ) ,
yScale ( chartData . yAxisData )
] ) ;
/*
d3.line()
.x(function: (data: [xPosition: number, yPosition: number]) => data[0])
.y(function: (data: [xPosition: number, yPosition: number]) => data[1])
(dataList: [xPosition: number, yPostion: number][])
μ κ·Έλν(line chart)λ₯Ό μμ±νκΈ° μν΄ μ¬μ©λλ ν¨μ. μ κ·Έλνλ λ°μ΄ν° ν¬μΈνΈλ₯Ό μ μΌλ‘ μ°κ²°νμ¬ λ°μ΄ν°μ μΆμ΄λ ν¨ν΄μ μκ°ννλλ° μ¬μ©
lineμ μμ±νκΈ° μν path μμμ d μ΄νΈλ¦¬λ·°νΈ κ°μΌλ‘ λ³νν΄μ£Όλ ν¨μλ₯Ό λ°ν
λ°νλ ν¨μ μΈμλ‘ x, y μ’ν κ°μ κ°λ λ°°μ΄ μ λ¬μ x, y λ©μλ μ½λ°±ν¨μ μΈμλ‘ λ°°μ΄ μμ μμ°¨ μ λ¬
*/
const lineGenerator = d3
. line ( )
// x μ’ν
. x ( ( data ) => data [ 0 ] )
// y μ’ν
. y ( ( data ) => data [ 1 ] ) ;
return svg
. append ( 'g' )
. append ( 'path' )
. data ( [ positionValueList ] )
. attr ( 'd' , ( positionValueList ) => lineGenerator ( positionValueList ) ) ;
} ;
import * as d3 from 'd3' ;
// νμ΄ μ°¨νΈλ₯Ό 그리기 μν΄ yAxis κ°μ λ°±λΆμ¨λ‘ λ³ν
const convertPercentDataList = ( dataList : { xAxisData : string , yAxisData : number } [ ] ) => {
const totalYAxisData = dataList . reduce (
( prevTotalValue : number , data : { xAxisData : string , yAxisData : number } ) =>
data . yAxisData + prevTotalValue ,
0
) ;
return dataList . map ( ( data ) => ( {
xAxisData : data . xAxisData ,
yAxisData : ( data . yAxisData / totalAmount ) * 100
} ) ) ;
} ;
const getPieDatalist = ( calculatedDataList : { xAxisData : string , yAxisData : number } [ ] ) => {
/*
d3.pie().value(Function)(dataList: any[])
value λ©μλ μΈμλ‘ μ½λ°± μ λ¬νλ©΄μ νΈμΆμ ν¨μ λ°ν, λ°νλ ν¨μ μΈμλ‘ λ°°μ΄ μ λ¬
value λ©μλ μΈμλ‘ μ λ¬ν μ½λ°±μ λ°±λΆμ¨λ‘ λ³νλ κ° λ°ν
μ
λ ₯ λ°μ΄ν° λ°°μ΄μ νμ΄ μ°¨νΈμ λ°μ΄ν° ν¬μΈνΈ λ°°μ΄λ‘ λ³ν. κ° λ°μ΄ν° ν¬μΈνΈμλ λ°μ΄ν° νλͺ©μ κ°, κ°λ λ±μ μ λ³΄κ° ν¬ν¨
*/
/*
d3.pie().value(Function)(dataList: any[]) λ°νκ°μ νμ
μ μλμ κ°μ΅λλ€.
{
data: any;
index: number;
endAngle: nuimber;
startAngle: number;
padAngle: number;
value: number;
}[]
*/
return d3 . pie ( ) . value ( ( data ) => data . yAxisData ) ( calculatedDataList ) ;
} ;
const createPieChart = (
svg : d3 . Selection < SVGSVGElement , unknown , HTMLElement , any > ,
pieDataList : d3 . PieArcDatum <
| number
| {
valueOf ( ) : number
}
> [ ]
) => {
const svgWidth = Number ( svg . attr ( 'width' ) ) ;
const svgHeight = Number ( svg . attr ( 'height' ) ) ;
const outerRadius = d3 . min ( [ svgWidth , svgHeight ] ) / 2 ;
/*
d3.arc()
.innerRadius(radius: number)
.outerRadius(radius: number)
.startAngle((data) => data.startAngle)
.endAngle((data) => data.endAngle)
(data)
νμ΄ μ°¨νΈ(pie chart)λ₯Ό 그리기 μν path μ΄νΈλ¦¬λ·°νΈμ d μ΄νΈλ¦¬λ·°νΈ κ°μΌλ‘ λ³ννλ ν¨μ λ°ν
innerRadius, outerRadius λ©μλ μΈμλ‘ κ°κ° λ΄λΆ, μΈλΆ μ λ°μ§λ¦ κ° μ λ¬
startAngle, endAngle λ©μλ μΈμλ‘ κ°κ° μμ, λ κ°λ κ° μ λ¬
λ°νλ ν¨μ νΈμΆμ d3.pie().value(Function)κ° λ°νν λ°°μ΄μ μμ μ λ¬
*/
const arcGenerator = d3
. arc ( )
. innerRadius ( outerRadius * 0.65 )
. outerRadius ( outerRadius )
// d3.pie().value(Function)(data) λ°νκ° λ°μΈλ©μ startAngle, endAngle κ°μΌλ‘ κ°λ κ° μ κ·Ό κ°λ₯
. startAngle ( ( data ) => data . startAngle )
. endAngle ( ( data ) => data . endAngle ) ;
svg
. append ( 'g' )
. attr ( 'transform' , `translate(${ svgWidth / 2 } , ${ svgHeight / 2 } )` )
. selectAll ( 'path' )
// d3.pie().value(Function)(data) λ°νκ° μ λ¬
. data ( pieDatalist )
. enter ( )
. append ( 'path' )
. attr ( 'd' , ( data ) => arcGenerator ( data ) )
. attr ( 'stroke' , '#fff' ) ;
} ;