[JS] D3.js를 사용해서 Tree Visualization 구현하기

Suyeon·2020년 12월 28일
0

D3.js

목록 보기
4/6
post-thumbnail

Tree Visualization

강의 참고👇
Data Visualization with D3.js - Full Tutorial Course

Tree Visualization에 대해 자세히 알고싶다면 d3-hierarchy 참고

Hierarchy

  • 데이터는 hierarchy형태의 포맷을 가져야한다. 그렇지 않은 경우에는 d3.stratify()를 사용하여 hierarchy형태의 포멧으로 변환한다.
{
  "name": "Eve",
  "children": [
    {
      "name": "Cain"
    },
    {
      "name": "Seth",
      "children": [
        {
          "name": "Enos"
        },
        {
          "name": "Noam"
        }
      ]
    },
    {
      "name": "Abel"
    },
    // ...

Tree Layout 만들기 (horizontal)

import { select, json, tree, hierarchy, linkHorizontal } from 'd3';

const width = document.body.clientWidth;
const height = document.body.clientHeight;

const svg = select('div')
  .append('svg')
  .attr('width', width)
  .attr('height', height);

const treeLayout = tree().size([height, width]); // (*)

json('world-country.json').then(data => {
  const root = hierarchy(data);  // (*)
  const links = treeLayout(root).links();  // (*)
  const linkPathGenerator = linkHorizontal()  // (*)
    .x(d => d.y)
    .y(d => d.x);

  svg
    .selectAll('path')
    .data(links)
    .enter()
    .append('path')
    .attr('d', linkPathGenerator);
});
/* css */
path {
  fill: none;
  stroke: black;
}

  • linkVertical()을 사용한다.
  const linkPathGenerator = linkVertical()
    .x(d => d.x)
    .y(d => d.y);

Node에 text label 추가하기

  svg
    .selectAll('text')
    .data(root.descendants()) // (*)
    .enter()
    .append('text')
    .attr('x', d => d.y)
    .attr('y', d => d.x)
    .text(d => d.data.data.id);
});

Text label에 스타일 추가하기

1️⃣ Center labels vertically

  svg
    .selectAll('text')
    .data(root.descendants())
    .enter()
    .append('text')
    .attr('x', d => d.y)
    .attr('y', d => d.x)
    .attr('dy', '0.32em') // (*) 
    .text(d => d.data.data.id);

2️⃣ CSS

  • 라벨이 node에 가려지는 것을 방지하기 위해서 white shadow를 추가한다.
path {
  fill: none;
  stroke: #57bdc3;
}

text {
  text-shadow: 
    -1px -1px 3px white, 
    -1px 1px 3px white, 
    1px -1px 3px white,
    1px 1px 3px white;
  pointer-events: none; /* Prevent cursor for text */
}

Margin 추가하기

  • .attr('font-size', d => 3.2 - d.depth + 'em'): Tree Depth에 따라서 font-size를 조정한다. (Depth가 깊어질 수록 font-size가 작아짐)
  • .attr('text-anchor', d => (d.children ? 'middle' : 'start')): Country name은 start로, 다른 텍스트는 middle로 정렬한다.
import { select, json, tree, hierarchy, linkHorizontal } from 'd3';

const width = document.body.clientWidth;
const height = document.body.clientHeight;

// Add margins
const margin = { top: 0, left: 80, right: 50, bottom: 0 };
const innerWidth = width - margin.right - margin.left;
const innerHeight = height - margin.top - margin.bottom;

const g = select('div') 
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .append('g') // (*)
  .attr('transform', `translate(${margin.left}, ${margin.top})`);

// tree() sets x and y value
const treeLayout = tree().size([innerHeight, innerWidth]);

json('world-country.json').then(data => {
  // Create tree layout
  const root = hierarchy(data);
  const links = treeLayout(root).links();
  const linkPathGenerator = linkHorizontal()
    .x(d => d.y)
    .y(d => d.x);

  g.selectAll('path')
    .data(links)
    .enter()
    .append('path')
    .attr('d', linkPathGenerator);

  // Add text label to node
  g.selectAll('text')
    .data(root.descendants())
    .enter()
    .append('text')
    .attr('x', d => d.y)
    .attr('y', d => d.x)
    .attr('dy', '0.32em') // Center labels vertically
    .attr('text-anchor', d => (d.children ? 'middle' : 'start')) // Align only country names in start
    .attr('font-size', d => 3.2 - d.depth + 'em') // Makes country names smaller / others bigger
    .text(d => d.data.data.id);
});

Panning & Zooming 추가하기

  • 처음 Zoom할 때, margin이 없어지는 것을 방지하기 위해서 g태그를 이용한 레이아웃을 2개로 나눈다. (스무스한 zooming)
const svg = select('div')
  .append('svg')
  .attr('width', width)
  .attr('height', height);

const zoomG = svg.append('g');  // (*)

const g = zoomG // (*)
  .append('g')
  .attr('transform', `translate(${margin.left}, ${margin.top})`);

// Add Zooming
svg.call(
  zoom().on('zoom', ({ transform }) => {
    zoomG.attr('transform', transform); // (*)
  })
);

Source Code

import { select, json, tree, hierarchy, linkHorizontal, zoom } from 'd3';

const width = document.body.clientWidth;
const height = document.body.clientHeight;

// Add margins
const margin = { top: 0, left: 80, right: 50, bottom: 0 };
const innerWidth = width - margin.right - margin.left;
const innerHeight = height - margin.top - margin.bottom;

const svg = select('div')
  .append('svg')
  .attr('width', width)
  .attr('height', height);

const zoomG = svg.append('g');

const g = zoomG
  .append('g')
  .attr('transform', `translate(${margin.left}, ${margin.top})`);

// tree() sets x and y value
const treeLayout = tree().size([innerHeight, innerWidth]);

// Add Zooming
svg.call(
  zoom().on('zoom', ({ transform }) => {
    zoomG.attr('transform', transform);
  })
);

json('world-country.json').then(data => {
  // Create tree layout
  const root = hierarchy(data);
  const links = treeLayout(root).links();
  const linkPathGenerator = linkHorizontal()
    .x(d => d.y)
    .y(d => d.x);

  g.selectAll('path')
    .data(links)
    .enter()
    .append('path')
    .attr('d', linkPathGenerator);

  // Add text label to node
  g.selectAll('text')
    .data(root.descendants())
    .enter()
    .append('text')
    .attr('x', d => d.y)
    .attr('y', d => d.x)
    .attr('dy', '0.32em') // Center labels vertically
    .attr('text-anchor', d => (d.children ? 'middle' : 'start')) // Align only country names in start
    .attr('font-size', d => 3.2 - d.depth + 'em') // Makes country names smaller / others bigger
    .text(d => d.data.data.id);
});
profile
Hello World.

0개의 댓글