D3는 Data Driven Documents의 약자로써 D가 3개라는 뜻이라고 한다.
데이터 시각화 Javascript 라이브러리로, 데이터를 바탕으로 HTML의 DOM을 직접 조작하는 것이 특징이다.
나는 여기서 D3는 DOM을 직접 조작하고,React는 Virtual DOM으로 문서를 조작하기 때문에 서로 충돌되는 부분이 있을 것이라고 생각이 들었다.
그래서 찾아보니 오히려 시너지가 나는 부분이 있다고 한다.
React가 D3에게 DOM을 제공하고 D3는 이것을 받아서 데이터를 화면에 그려내면 React Hooks의 상태관리 기능과 D3의 데이터 시각화 기능을 한꺼번에 활용할 수 있기 때문이다.
그러니까 한 마디로 하자면, 리액트의 장점인 반응형으로 데이터 시각화를 이루어낼 수 있다는 것이다.
그렇다면 직접 적용한 코드를 적어보겠다.
참고로, D3웹페이지에 들어가면 다양한 차트를 볼 수 있다.https://d3js.org
D3는 100% 오픈소스로서 상업적, 비상업적 목적 모두 자유롭게 사용이 가능하기에 사용하고 싶은 차트를 클릭해 보면 사용 예제가 전부 나와있다.
우선 "데이터 시각화" 이기 때문에 데이터가 필요한데 임의 데이터를 넣어줘도 되고 DB에 데이터가 있다면 끌어다 쓰면 된다.
일단 성공한 나의 D3 차트이다.
부서별 급여 차트이고,
데이터는 급여, 직급 수 등 자유롭게 넣을 수 있다. (백단에서 작업을 해줘야 함)
코드를 살펴보면, (어떤 데이터를 가져다 쓸건지는 백단에 보낼 데이터 선택하는 코드이기 때문에 제외하겠음. 코드 전체를 보고 싶다면 깃으로...)
import React, { useState, useEffect, useRef } from "react";
import * as d3 from "d3";
import debounce from "lodash/debounce";
const PADDING = 30;
function useResize(ref) {
const [state, setState] = useState();
useEffect(() => {
const getSize = debounce(() => {
if (!ref || !ref.current) {
return;
}
const width = ref.current.offsetWidth;
const height = ref.current.offsetHeight;
setState({
width,
height
});
}, 1000);
window.addEventListener("resize", getSize);
getSize();
return () => window.removeEventListener("resize", getSize);
}, [ref]);
return state;
}
const LineChart = (props) => {
const [lineData, setLineData] = useState();
const [markers, setMarkers] = useState();
const rootRef = useRef(null);
const xAxisRef = useRef(null);
const yAxisRef = useRef(null);
const yLabelRef = useRef(null);
const size = useResize(rootRef);
useEffect(() => {
if (!size || !props.data) {
return;
}
const data = props.data;
const { width, height } = size;
const isSalaryData = props.option === 'salary';
const xScale = d3
.scaleLinear()
.domain([0, data.length - 1])
.range([PADDING, width - PADDING]);
const yScale = d3
.scaleLinear()
.domain(isSalaryData ? [1, 10] : [0, 30])
.range([height - PADDING, PADDING]);
const lineGenerator = d3
.line()
.x((d, i) => xScale(i))
.y((d) => yScale(isSalaryData ? d.salary / 1000000 : d.count))
.curve(d3.curveMonotoneX);
const xAxis = d3
.axisBottom(xScale)
.ticks(data.length)
.tickFormat((d, i) => isSalaryData ? d : data[i].job_title);
const yAxis = d3.axisLeft(yScale).ticks(height / 50);
d3.select(xAxisRef.current).call(xAxis);
d3.select(yAxisRef.current).call(yAxis);
d3.select(yLabelRef.current).text(isSalaryData ? "단위: 백만" : "명");
setLineData(lineGenerator(data));
setMarkers(
data.map((d, i) => ({
x: xScale(i),
y: yScale(isSalaryData ? d.salary / 1000000 : d.count),
}))
);
}, [size, props]);
return (
<div className="chart-area" ref={rootRef} style={{ maxWidth: '80%', margin: 'auto' }}>
{size && (
<svg width={size.width} height={size.height}>
<g id="axes">
<g
id="x-axis"
ref={xAxisRef}
transform={`translate(0, ${size.height - PADDING})`}
/>
<g
id="y-axis"
ref={yAxisRef}
transform={`translate(${PADDING}, 0)`}
/>
<text
ref={yLabelRef}
transform="rotate(-90)"
x={-(size.height / 2)}
y={15}
textAnchor="middle"
/>
</g>
<g id="chart">
{lineData && (
<path stroke="#48bb78" className="chart-line" d={lineData} />
)}
{markers &&
markers.map((marker, i) => (
<circle
key={i}
cx={marker.x}
cy={marker.y}
r={4}
className="chart-marker"
/>
))}
</g>
</svg>
)}
</div>
);
};
export default LineChart;
PADDING이라는 변수는 말 그대로 여백을 의미하는 것으로, 차트의 상하좌우에 사용된다.
useEffect(() => {
const getSize = debounce(() => {
if (!ref || !ref.current) {
return;
}
const width = ref.current.offsetWidth;
const height = ref.current.offsetHeight;
setState({
width,
height
});
}, 1000);
useEffect를 통해 getSize라는 함수가 윈도우 리사이즈 이벤트에 의해 호출되도록 한다. ref가 유효하지 않으면 함수를 종료하고 ref.current의 너비와 높이를 측정하여 상태를 업데이트한다.
const rootRef = useRef(null);
const xAxisRef = useRef(null);
const yAxisRef = useRef(null);
const yLabelRef = useRef(null);
const size = useResize(rootRef);
이 변수들은 ref를 생성하여 각 DOM 요소에 접근한다. useResize를 호출하여 size 상태를 가져온다.
const xScale = d3
.scaleLinear()
.domain([0, data.length - 1])
.range([PADDING, width - PADDING]);
const yScale = d3
.scaleLinear()
.domain(isSalaryData ? [1, 10] : [0, 30])
.range([height - PADDING, PADDING]);
변수명과 같이 스케일, 범위를 조정한다.
const lineGenerator = d3
.line()
.x((d, i) => xScale(i))
.y((d) => yScale(isSalaryData ? d.salary / 1000000 : d.count))
.curve(d3.curveMonotoneX);
선 그래프를 생성하는 함수를 만든다. 데이터의 x와 y 값을 스케일에 매핑하고 커브를 준다.
이러한 함수 및 변수들을 통해 차트를 정의한다.
D3는 처음 하는 입장에서 완벽히 사용하기에는 어려움이 있지만, 복잡한 데이터 시각화를 지원하고 다양한 시각화 기법과 애니메이션을 지원하는 등의 뛰어난 기능이 많기 때문에 복잡한 기능을 공부할 가치가 있다고 생각한다.
'JavaScript > React' 카테고리의 다른 글
[React] React 맥(Mac os) VSCODE 리액트 프로젝트 생성하는 방법(개발환경 세팅) (0) | 2023.06.29 |
---|