Skip to content

8dong/D3.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Visualizing data with D3.js

Selecting Elements

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와 μœ μ‚¬ν•˜κ²Œ, 각 λΆ€λͺ¨ λ…Έλ“œλŠ” λ°°μ—΄ λ‚΄μ˜ 각각의 μ›μ†Œλ‘œ ν‘œν˜„λ©λ‹ˆλ‹€.
*/

Modifying elements

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'

Controll flow

import * as d3 from 'd3';

/*  
  selection.call(function: (element: Selection) => void)
  μ„ νƒν•œ μš”μ†Œμ— 인자둜 μ „λ‹¬ν•œ ν•¨μˆ˜λ₯Ό 적용. 이 ν•¨μˆ˜λŠ” μ„ νƒν•œ μš”μ†Œλ₯Ό λŒ€μƒμœΌλ‘œ μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ±°λ‚˜ 섀정을 λ³€κ²½
  첫 번째 인수둜 μ„ νƒλœ μš”μ†Œ 전달
*/
d3.select('css selector').call((element: Selection) => {
  // element μ‘°μž‘,,,
});

Scale

Band scales

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

Linear scales

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

Axis

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));
};

Joining Data

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));
};

Bisecting data

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;
};

Generator

Lines

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));
};

Pies & Arcs

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');
};

About

Visualizing data with D3.js πŸ“ˆ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published