Frontend/Vue

d3js 사용법

Mev01 2021. 5. 27. 16:28

프로젝트에서 사용하기 위해 d3를 사용하였는데, 빠른 적용을 위해서 d3의 examples를 이용하였습니다.

그중 제가 사용한 것은 zoomable circle chart입니다.

 

vuex의 store에 데이터를 저장해놓았다가 버튼을 클릭 시 해당 차트가 보이도록 설정하였습니다.

 

 

example 가져오기


chart = {
  const root = pack(data);
  let focus = root;
  let view;

  const svg = d3.create("svg")
      .attr("viewBox", `-${width / 2} -${height / 2} ${width} ${height}`)
      .style("display", "block")
      .style("margin", "0 -14px")
      .style("background", color(0))
      .style("cursor", "pointer")
      .on("click", (event) => zoom(event, root));

  const node = svg.append("g")
    .selectAll("circle")
    .data(root.descendants().slice(1))
    .join("circle")
      .attr("fill", d => d.children ? color(d.depth) : "white")
      .attr("pointer-events", d => !d.children ? "none" : null)
      .on("mouseover", function() { d3.select(this).attr("stroke", "#000"); })
      .on("mouseout", function() { d3.select(this).attr("stroke", null); })
      .on("click", (event, d) => focus !== d && (zoom(event, d), event.stopPropagation()));

  const label = svg.append("g")
      .style("font", "10px sans-serif")
      .attr("pointer-events", "none")
      .attr("text-anchor", "middle")
    .selectAll("text")
    .data(root.descendants())
    .join("text")
      .style("fill-opacity", d => d.parent === root ? 1 : 0)
      .style("display", d => d.parent === root ? "inline" : "none")
      .text(d => d.data.name);

  zoomTo([root.x, root.y, root.r * 2]);

  function zoomTo(v) {
    const k = width / v[2];

    view = v;

    label.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
    node.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
    node.attr("r", d => d.r * k);
  }

  function zoom(event, d) {
    const focus0 = focus;

    focus = d;

    const transition = svg.transition()
        .duration(event.altKey ? 7500 : 750)
        .tween("zoom", d => {
          const i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
          return t => zoomTo(i(t));
        });

    label
      .filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
      .transition(transition)
        .style("fill-opacity", d => d.parent === focus ? 1 : 0)
        .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
        .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
  }

  return svg.node();
}

해당 example에 가보면 위의 코드가 있습니다.

 

<template>
    <div >
        <div style="margin-left: 45%;">
            <button @click="generateChart" ></button>
        </div>
        <div id='arc'></div>
    </div>
</template>

<script>
import * as d3 from "d3";
import { mapGetters} from 'vuex';

export default {
    name: 'CommChart',
    computed: {
        ...mapGetters([
            'commData',
        ]),
    },
    methods: {
        generateChart(){
        	// example 코드
        },
    }
}
</script>

위 코드는 전체 vue파일의 형식입니다.

methods에 generateChart 함수를 생성하여 버튼이 클릭하면 id='arc'인 div 안에 차트가 나타나도록 했습니다.

차트의 데이터는 vuex를 통해서 commData에 저장되어 있습니다.

generateChart 함수안에 d3에서 가져온 코드를 넣습니다.

 

 

example 변수 선언


d3 example 페이지에 가보면 example 코드의 변수들에 대한 설명이 위와 같이 있습니다.

각 변수들의 윗문장은 변수의 형식, 아랫 문장은 해당 변수들을 선언하는 방법이라고 해석했습니다.

 

const pack = data => d3.pack()
                    .size([width, height])
                    .padding(20)
                    (d3.hierarchy(data)
                    .sum(d => d.value)
                    .sort((a, b) => b.value - a.value));
const data = this.commData;
const width = 932;
const height = width;
const color = d3.scaleLinear()
                .domain([0, 5])
                .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
                .interpolate(d3.interpolateHcl);

그래서 해당 변수들을 선언하는 방법을 모으면 위 코드와 같이 됩니다.

this.commData는 이미 json형식으로 되어있어서 그대로 넣어주었습니다.

import * as d3 from "d3";

d3 변수는 위 코드의 d3를 이용하여서 따로 코드가 필요하지 않습니다.

 

example 코드 수정


수정은 다음과 같은 단계를 거칩니다.

  1. chart Object를 제거합니다.
  2. 변수 선언 코드를 넣습니다.
  3. id가 arc인 div안에 차트를 만듭니다.

해당 단계를 모두 거치면 다음과 같은 코드가 됩니다.

/*			2번			*/
const pack = data => d3.pack()
        .size([width, height])
        .padding(20)
        (d3.hierarchy(data)
        .sum(d => d.value)
        .sort((a, b) => b.value - a.value));
const data = this.commData;
const width = 932;
const height = width;
const color = d3.scaleLinear()
                .domain([0, 5])
                .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
                .interpolate(d3.interpolateHcl);
/*			2번 end			*/

const root = pack(data);
let focus = root;
let view;

// <svg> element 생성
let svg = d3
	/*			3번			*/
    .select("#arc")
    .append("svg")
    /*			3번	end		*/
    .attr("viewBox", `-${width / 2} -${height / 2} ${width} ${height}`)
    .style("display", "block")
    .style("margin", "0px")
    .style("background", color(0))
    .style("cursor", "pointer")
    .on("click", (event) => zoom(event, root));

const node = svg.append("g")
    .selectAll("circle")
    .data(root.descendants().slice(1))
    .join("circle")
    .attr("fill", d => d.children ? color(d.depth) : "white")
    .attr("pointer-events", d => !d.children ? "none" : null)
    .on("mouseover", function() { d3.select(this).attr("stroke", "#000"); })
    .on("mouseout", function() { d3.select(this).attr("stroke", null); })
    .on("click", (event, d) => focus !== d && (zoom(event, d), event.stopPropagation()));

const label = svg.append("g")
    .style("font", "35px sans-serif")
    .attr("pointer-events", "none")
    .attr("text-anchor", "middle")
    .selectAll("text")
    .data(root.descendants())
    .join("text")
    .style("fill-opacity", d => d.parent === root ? 1 : 0)
    .style("display", d => d.parent === root ? "inline" : "none")
    .text(d => d.data.name);

zoomTo([root.x, root.y, root.r * 2]);

function zoomTo(v) {
    const k = width / v[2];

    view = v;

    label.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
    node.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
    node.attr("r", d => d.r * k);
}

function zoom(event, d) {
    const focus0 = focus;

    focus = d;

    const transition = svg.transition()
        .duration(event.altKey ? 7500 : 750)
        .tween("zoom", d => {
        const i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
        return t => zoomTo(i(t));
        });

    label
    .filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
    .transition(transition)
        .style("fill-opacity", d => d.parent === focus ? 1 : 0)
        .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
        .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}

 

위 코드를 vue 형식 코드의 함수 안에 넣으면 끝입니다.

 

 

'Frontend > Vue' 카테고리의 다른 글

axios 사용법  (0) 2021.07.12
Vue 로컬 포트번호 변경  (0) 2021.07.12
v-for와 v-if 분리하는 방법  (0) 2021.05.14
Error : Component template should contain exactly one root element.  (0) 2021.05.13