D3.js

김동현·2023년 2월 5일
0

D3.js

목록 보기
1/2
post-thumbnail

D3.js

Data Driven Document의 약자로 데이터를 시각화하기 위해서 사용하는 라이브러입니다.

D3.js는 SVG(Scalable Vector Graphic)와 DOM API를 통해 해상도 깨짐이 없는 시각화가 가능합니다.

D3.js 메서드

Select DOM Element

Selection은 _groups 프로퍼티를 갖고 있으며, _groups 프로퍼티에는 배열이 바인딩되어 있습니다.

이 배열 안에 선택된 DOM Element들이 존재합니다.

Selection {
    _groups: [DOM Element,,,],
    _parents: [DOM Element]
}

  • d3.select(css-selector)
    인수로 CSS 선택자를 전달하면 문서 내 첫 번재로 매칭된 하나의 DOM Element를 _groups 배열의 요소로 갖는 Selection 객체를 반환합니다.
    만약 매칭된 DOM Element가 없다면 빈 Selection을 반환합니다.

  • d3.selectAll(css-selector)
    : 인수로 CSS 선택자를 전달하면 매칭되는 모든 DOM Element들을 _groups 배열의 요소로 갖는 Selection 객체를 반환합니다.
    만약 매칭된 DOM Element가 없다면 빈 Selection을 반환합니다.


반환된 Selection 객체로 selectselectAll 메서드 체이닝이 가능하며 중첩된 DOM Element를 선택할 수도 있습니다.

d3.select('body')
  .select('.canvas')
  .select('svg')
  .selectAll('rect');

DOM Manipulation

Selection 객체를 통해 선택된 DOM Element을 동적으로 다룰 수 있습니다.


  • d3.selection.text(text)
    : 선택된 DOM Element의 text content를 추가하거나 수정할 수 있습니다.
    반환값은 기존 선택된 DOM Element를 선택한 Selection 객체를 반환합니다.

  • d3.selection.append(tag-name)
    : 새롭게 DOM Element를 생성하고, 선택된 DOM Element 끝에 추가합니다.
    이때 새롭게 추가된 Element를 선택한 Selection 객체를 반환합니다.

  • d3.selection.remove()
    : 선택된 DOM Element들을 제거합니다.
    반환값은 제거된 DOM Element를 선택한 Selection 객체를 반환합니다.

  • d3.selection.attr(attr-name, attr-value)
    : 선택된 DOM Element에 HTML Attribute를 설정합니다.
    반환값은 기존 선택된 DOM Element를 선택한 Selection 객체를 반환합니다.

  • d3.selection.property(property-name, property-value)
    : attr 메서드로 접근되지 않는 것들은 property 메서드로 접근해여 설정합니다. 반환값은 기존 선택된 DOM Element를 선택한 Selection 객체를 반환합니다.

  • d3.selection.style(property, property-value)
    : 선택된 DOM Element에 인라인 스타일을 설정합니다.
    반환값은 기존 선택된 DOM Element를 선택한 Selection 객체를 반환합니다.


<!DOCTYPE html>
<html lang='ko'>
  <head>
     ,,,
     <script src="https://d3js.org/d3.v7.min.js"></script>
   </head>
   <body>
     <div class="canvas"></div>
     
     <script>
       const svg = d3.select('canvas')
                      .append('svg')  // svg 태그 생성하고 추가
                      .attr('width', 1000)  // svg 태그 width 어트리뷰트 설정
                      .attr('height', 800);  // svg 태그 height 어트리뷰트 설정
      
      
      svg.append('text')  // text 태그 생성하고 추가
          .attr('x', 500)  // text 태그 x 어트리뷰트 설정
          .attr('y', 400)  // text 태그 y 어트리뷰트 설정
          .text('hello, world');  // text 태그의 text content 설정
          
      svg.select('text')
         .remove();  // text 태그 제거
    </script>
  </body>
</html>

Data Binding

D3에서는 Selection 객체에 대해서 .data() 메서드를 통해 선택된 요소에 데이터를 바인딩할 수 있습니다.

데이터가 바인딩된 Selection 객체는 Manipulation 메서드의 인수로 콜백함수를 전달하여 바인딩된 배열의 요소에 접근할 수 있습니다.

요소에 바인딩될 데이터의 타입은 배열 타입만을 허용되며, 배열의 요소로는 자바스크립트에서 사용되는 모든 값을 허용합니다.

즉, 선택된 요소에 바인딩된 배열의 요소를 순차적으로 바인딩해줍니다.


  • d3.selection.data([...value])
    : 인수로 배열을 전달합니다. 반환값으로는 데이터가 바인딩된 Selection 객체를 반환합니다.

만약 Selection 객체 내 선택된 요소가 없다면 바인딩되지 않으며, 선택된 요소가 하나라면 배열의 첫 번째 요소만이 바인딩됩니다.

<!DOCTYPE html>
<html lang='ko'>
  <head>
     ,,,
     <script src="https://d3js.org/d3.v7.min.js"></script>
   </head>
   <body>
     <div class="canvas"></div>
     
     <script>
       const svg = d3.select('.canvas')
                      .append('svg')
                      .attr('width', 1000)
                      .attr('height', 800);
                      
       const dataArr = ['a', 'b', 'c', 'd'];
       
       // 1. 선택된 요소가 없는 경우
       svg.select('.text1')
           .data(dataArr)
           .text((d, idx) => {
             console.log(d);
             console.log(idx);
             
             return d;
           });

       // 2. 선택된 요소가 하나인 경우
       svg.select('.text2')
          .append('p')
          .attr('class', 'text2')
          .data(dataArr)
          .text((d, idx) => {
            console.log(d);  // a
            console.log(idx); // 0
            
            return d;
          });
    </script>
  </body>
</html>

위 코드에서 1번의 경우 svg 태그 내 text1을 class 값으로 갖는 태그가 존재하지 않아 빈 Selection 객체를 반환합니다. 빈 Selection 객체에 데이터를 바인딩하더라도 선택된 요소가 존재하지 않기 때문에 데이터가 바인딩되지 않습니다.

2번의 경우에는 append 메서드를 통해 p 태그를 선택하고, class 값으로 text2를 설정했습니다. 그러므로 Selection 객체에는 하나의 요소가 존재하며 데이터를 바인딩하면 배열의 첫 번째 요소만이 선택된 하나의 요소에 바인딩됩니다.

즉, 선택된 요소의 순서대로 바인딩한 배열의 요소가 바인딩됩니다.


  • d3.selection.data([...value]).enter()
    : data 메서드로 바인딩한 데이터의 개수가 선택된 DOM Element보다 많은 경우 가상 객체를 생성하여 남은 데이터를 바인딩시켜줍니다.

이는 앞서 작성된 코드의 1번 같은 경우 빈 Selection 객체에 데이터를 바인딩하는 경우 바인딩된 데이터의 요소만큼 빈 객체를 생성하고 데이터를 바인딩시켜 줍니다.

2번의 경우에도 선택된 요소가 하나이므로 배열의 첫 번째 요소만이 바인딩되지만, enter 메서드를 체이닝하면 바인딩되지 않은 남은 요소의 개수만큼 가상 객체를 생성하고 순차적으로 데이터를 바인딩시켜 줍니다.

<!DOCTYPE html>
<html lang='ko'>
  <head>
     ,,,
     <script src="https://d3js.org/d3.v7.min.js"></script>
   </head>
   <body>
     <div class="canvas"></div>
     
     <script>
       const svg = d3.select('.canvas')
                      .append('svg')
                      .attr('width', 1000)
                      .attr('height', 800);
                      
       const dataArr = ['a', 'b', 'c', 'd'];
       
       // 1. 선택된 요소가 없는 경우
       svg.select('.text1')
           .data(dataArr)
           .enter()
           .text((d, idx) => {
             console.log(d);  // a b c d
             console.log(idx);  // 0 1 2 3
             
             return d;
           });

       // 2. 선택된 요소가 하나인 경우
       svg.select('.text2')
          .append('text')
          .attr('class', 'text2')
          .data(dataArr)
          .enter()
          .text((d, idx) => {
            console.log(d);  // a b c d
            console.log(idx); // 0 1 2 3
            
            return d;
          });
    </script>
  </body>
</html>

  • d3.selection.exit()
    : exit 메서드는 enter 메서드와 반대로 바인딩한 데이터의 개수가 선택된 DOM Element보다 적은 경우 데이터가 바인딩되지 않은 요소를 선택한 Selection 객체를 반환합니다. 이후 .remove() 메서드를 통해 DOM Element를 제거할 수 있습니다.
<!DOCTYPE html>
<html lang='ko'>
  <head>
     ,,,
     <script src="https://d3js.org/d3.v7.min.js"></script>
   </head>
   <body>
     <div class="canvas"></div>
     
     <script>
       const svg = d3.select('.canvas')
                      .append('svg')
                      .attr('width', 1000)
                      .attr('height', 800);
                      
       const dataArr = ['a', 'b'];
       
       svg.append('text');
       svg.append('text');
       svg.append('text');
       
       svg.selectAll('text')
          .data(dataArr)
          .exit()
          .remove();
    </script>
  </body>
</html>

위 코드에서 svg 태그의 자식으로 text 태그 3개를 생성했습니다. 이후 text 태그를 선택하고 데이터를 바인딩하면 마지막 text 태그에는 데이터가 바인딩되지 않아 exit 메서드를 호출하면 바인딩되지 않은 마지막 text 태그가 선택되고 이를 remove 메서드를 호출하여 제거할 수 있습니다.

Transition

  • d3.selection.transition()
    : 선택된 DOM Element에 transition 효과의 시작을 알리는 메서드입니다.

  • d3.selection.duration(number)
    : transition 효과의 지속시간을 설정하는 메서드입니다.

  • d3.selection.ease(fn)
    : ease 메서드는 transition 효과의 motion을 설정하는 메서드입니다.

  • d3.selection.delay(number)
    : delay 메서드는 transition 효과의 지연시간을 설정하는 메서드입니다.


<!DOCTYPE html>
<html lang='ko'>
  <head>
     ,,,
     <script src="https://d3js.org/d3.v7.min.js"></script>
   </head>
   <body>
     <div class="canvas"></div>
     
     <script>
       const svg = d3.select('.canvas')
                      .append('svg')
                      .attr('width', 1000)
                      .attr('height', 800);
                      
       svg.transition()  // transition 효과 설정
          .duration(3000)  // transition 지속 시간 3000ms
          .style('background-color', 'red');  // background-color값 red로
    </script>
  </body>
</html>

참고로 transition 메서드를 호출한 이후에는 .style 메서드를 체이닝하여 스타일을 설정해주어야 합니다.

EventHandler

  • d3.selection.on(eventType, function)
    : 선택된 요소에 이벤트핸들러를 등록할 수 있습니다. 첫 번째 인수로는 이벤트 타입, 두 번째 인수로는 이벤트 핸들러를 전달합니다.
    이벤트 핸들러는 첫 번째 인수로 이벤트 객체를 전달받으며, 만약 앞서 data 메서드로 데이터를 바인딩한 경우 두 번째 인수로 바인딩한 데이터가 전달됩니다.

Scales

D3.js의 Sacales 함수의 경우 실제 데이터를 시각적으로 표현할 때 실제 데이터를 비율에 맞게 값을 변환시켜주는 함수입니다.

이는 실제 데이터를 x 좌표와 y 좌표의 값으로 변환시키기 위해서 사용합니다.


  • d3.scaleLinear().domain([min, max]).range([min, max])
    : domain 메서드에는 실제 값의 최소 최대값을, range에는 좌표 평면상 표현될 최소 최대값을 갖는 배열을 전달해줍니다.
    이후 반환된 함수를 통해 domain에 전달한 실제값의 범위 내 값을 인수로 전달시 이를 비율에 맞게 좌표 평면상 표현될 값을 반환해줍니다.
const yScale = d3.scaleLinear()
                  .domain([0, 60000000])
                  .range([0, 800]);
                  
yScale(0);  // 0
yScale(30000000); // 400
yScale(60000000);  // 800

위 코드는 대한민국 총 인구수를 비율에 맞게 값을 변환하려고 합니다. 이때 range 메서드에는 좌표 평면상 표현될 값의 범위를 작성하고, domain에는 실제 값의 범위를 작성합니다.

반환된 함수의 인수로 domain에 전달한 범위의 실제 값을 전달하면 ranage 메서드의 인수로 전달한 범위에 맞게 변환한 값을 반환합니다.


  • d3.scaleBand().domain([...value]).range([min, max])
    : domain 메서드에는 사용되는 모든 실제 값들을, range에는 좌표 평면상 표현될 최소 최대값을 갖는 배열을 전달해줍니다.
    이후 반환되는 함수를 통해 domain에 전달한 실제 값 중 하나를 전달하면 range에 전달한 범위 내 일정한 간격을 갖도록 변환된 값을 반환합니다.
const xScale = d3.scaleBand()
                  .domain(['1970', '1980', '1990', '2000', '2010'])
                  .range([0, 1000]);
                  
xScale('1970');  // 0
xScale('1980');  // 250
xScale('1990');  // 500
xScale('2000');  // 750
xScale('2000');  // 1000

위 코드에서처럼 domain에는 사용되는 실제 값 모두 작성하고, range에는 좌표 평면상 표현될 값의 범위를 작성합니다.

이후 반환되는 함수의 인수로 domain에 작성된 값을 전달하면 일정한 간격으로 띄어진 값을 반환합니다.


  • d3.scale.padding(number)
    : scale 함수로 padding 메서드를 체이닝하면 padding 영역을 설정할 수 있습니다.

Axis

D3.js에서는 차트의 축을 생성하기 위한 함수를 제공합니다. 이때 인수로 scale 함수를 인수로 전달해줍니다.

축을 생성하기 위해 scale 함수를 전달하면 range 범위를 통해 적절하게 생성합니다.

이후 d3.selection.call(axis) 메서드의 인수로 Axis 함수의 반환값을 전달하여 축을 실제로 그릴 수 있습니다.


  • d3.axisTop(scale)
    : 상단 부분에 수평축을 생성합니다. 이때 인수로 Scale 함수를 전달하면 그에 맞는 축을 생성하게 됩니다.

  • d3.axisRight(scale)
    : 우측 부분에 수직축을 생성합니다.

  • d3.axisBottom(scale)
    : 하단 부분에 수평축을 생성합니다.

  • d3.axisLeft(scale)
    : 좌측 부분에 수직축을 생성합니다.

const xScale = d3.scaleBand()
                .domain(['a', 'b', 'c', 'd'])
                .range([0, 1000]);
                
const yScale = d3.scaleLinear()
                 .domain([0, 100])
                 .range([0, 800]);
                 
const xAxis = svg.append('g')
                 .call(xScale);
                 
const yAxis = svg.append('g')
                 .attr('transform', `translate(0, ${800})`)
                 .call(yScale);

  • d3.axis.ticks(number)
    : 생성될 축에 표시될 단위의 개수를 설정할 수 있습니다.

  • d3.axis.tickFormat(d => { ,,, })
    : 생성될 축에 표시될 단위의 포맷을 설정할 수 있습니다. 콜백 함수를 전달하면 콜백 함수의 인수로 기존 단위값이 전달되고 반환값으로 작성된 값이 단위로 표시됩니다.

Generator

생성기는 데이터를 받아 SVG 코드를 반환합니다. 즉, 생성기란 path 요소를 통해 원하는 path 도형을 생성하기 위한 d 어트리뷰트의 값을 생성해주는 함수입니다.

  • d3.line().x(function).y(function)
    : line을 그릴 수 있는 d 어트리뷰트 값을 반환하는 함수를 반환합니다. 이후 반환된 함수를 호출하면 path 요소의 d 어트리뷰트 값을 반환합니다.
    x 메서드에는 x 좌표를 계산하는 함수를, y 메서드에는 y 좌표를 계산하는 함수를 전달합니다.
const lineGenerator = d3.line()
                        .x(d => xScale(d.실제x값))
                        .y(d => yScale(d.실제y값));
               
const lineEl = svg.append('path')
                   .attr('d', line(data))
                   .attr('fill', 'none')
                   .attr('stroke', 'blue')
                   .attr('stroke-width', 3);

  • d3.arc().innerRadius().outerRadius().startAngle().endAngle()
    : innerRadius 메서드에는 내부 원의 반지름, outerRadius 메서드에는 외부 원의 반지름, startAngle 메서드에는 시작 위치, endAngle 메서드에는 끝 위치를 작성합니다.
    즉, startAngle과 endAngle 메서드를 통해 차지할 영역을 지정할 수 있습니다.
const arcGenerator = d3.arc()
                       .innerRadius(50)  // 내부 원의 반지름 50
                       .outerRadius(100)  // 외부 원의 반지름 100
                       .startAngle(Math.PI * 0.5)  // 영역의 시작 위치는 90도(Math.PI * 0.5)
                       .endAngle(Math.PI * 1);  // 영역의 끝 위치는 180도(Math.PI * 1)

d3.select('svg')
  .append('g')
  .append('path')
  .attr('fill', '#0066ff')
  .attr('d', arcGenerator());

위 코드는 아래와 같은 결과를 출력하게 됩니다. 내부 빈 영역의 반지름은 50, 전체 원의 반지름은 100, 영역의 시작 지점은 0, 영역의 끝 지점은 180도를 나타내는 Math.PI로 설정되었습니다.

Layout

레이아웃은 데이터를 입력받아 그래프를 그릴 수 있는 데이터로 변환하여 반환해줍니다.


  • d3.pie().value(d => d.value)
    : pie 메서드를 호출하면 PI 그래프를 그리기 위한 데이터를 가공하여 반환해주는 함수입니다. 즉, startAngle과 endAngle 값을 가공하여 반환해줍니다.
    추가적으로 value 메서드를 체이닝하면 인수로 전달된 콜백함수의 반환값을 가공시켜 줍니다.
const sampleData = [
  { year: 'a', value: 78 },
  { year: 'b', value: 28 },
  { year: 'c', value: 36 },
  { year: 'd', value: 48 }
];

const pieLayout = d3.pie().value(d => d.value);

pieLayout(sampleData);

/*
[
    {
        "data": {
            "year": "a",
            "value": 78
        },
        "index": 0,
        "value": 78,
        "startAngle": 0,
        "endAngle": 2.579412915578988,
        "padAngle": 0
    },
    {
        "data": {
            "year": "b",
            "value": 28
        },
        "index": 3,
        "value": 28,
        "startAngle": 5.357242209279437,
        "endAngle": 6.283185307179586,
        "padAngle": 0
    },
    {
        "data": {
            "year": "c",
            "value": 36
        },
        "index": 2,
        "value": 36,
        "startAngle": 4.166743940550673,
        "endAngle": 5.357242209279437,
        "padAngle": 0
    },
    {
        "data": {
            "year": "d",
            "value": 48
        },
        "index": 1,
        "value": 48,
        "startAngle": 2.579412915578988,
        "endAngle": 4.166743940550673,
        "padAngle": 0
    }
]
*/

위 그림과 같이 실제 값을 PI 그래프를 그리기 위한 데이터로 변환하여 반환시켜줍니다.

const data = [10, 30, 20, 100, 50, 80];

const arcGen = d3.arc()
                 .outerRadius(100)
                 .innerRadius(50);

const pieLayout = d3.pie()
                    .value(d => d);

const pieGoup = d3.select('svg')
              .append('g');

pieGroup.selectAll()
        .data(pieLayout(data))
        .enter()
        .append('path')
        .attr('d', arcGen)
        .attr('fill', '#0066ff')
        .attr('stroke', '#fff')
        .attr('stroke-width', 3);
profile
Frontend Dev

0개의 댓글