D3.js는 브라우저에서 html, css, svg, canvas등을 사용하여 데이터 시각화를 도와주는 라이브러리이다. 데이터에 어떠한 그래프가 적합할지 잘 생각해야 한다.
예를 들면,
0 base line
을 사용해야 하는 경우: Bar chart, Area chart💫 그래프 스타일링은, Data Visualization Style Guide 참고
데이터를 불러올 때, .csv
파일 포맷을 사용하거나, csv파일이 담긴 url
을 사용한다. 또는 json
파일을 사용한다.
cvs
format은 comma-separated values로 이루어진 형식임
// data.csv
country,population
China,1415046
India,1354052
United States,326767
...
D3.js에서 indentation chaining이 자주 사용된다.
const rightEyebrow = eyesG
.append('rect')
.attr(...)
.attr(...)
.transition().duration(2000)
.attr(...)
key
를 저장하는 것이 좋다.svg.selectAll('circle')
.data(fruits)
.enter()
.append('circle')
svg.selectAll('circle')
.data(fruits)
.exit()
.remove()
selection.data()
자체가 update()
이다.var rects = d3.select('svg')
.selectAll('rect')
.data(data)
var newrects = rects.enter()
.append('rect')
.style('fill', 'red')
rects.merge(newrects)
.attr('width', d => d.value)
.attr('height', 20)
.attr('y', (d, i) => i*20)
rects.exit()
.remove()
selection.join()
을 사용하면, 업데이트가 필요할 때 enter
, update
, exit
을 일일히 설정하지 않아도 된다.// The same above example
d3.select('svg')
.selectAll('rect')
.data(data)
.join('rect') // (*)
.style('fill', 'red')
.attr('width', d => d.value)
.attr('height', 20)
.attr('y', (d, i) => i*20)
다이나믹한 그래프 혹은 재사용가능한 컴포넌트를 만들 때 사용되는 패턴이다. (변화하는 데이터에 따라서 DOM element도 업데이트 됨)
- Listen event ➡️ change state/data ➡️ update DOM
- 💫 General Update Pattern 대신
selection.join()
사용하기
enter
와 update
를 합침)attribute
는 merge
아래에 위치 시킨다.merge
를 사용해서 다시 refactoring 한다면 아래와 같다.// 💫 General Update Pattern
const render = (selection, props) => {
const { fruits } = props;
// (*) circles itself is an update selection
const circles = selection.selectAll('circle').data(fruits);
circles
.enter()
.append('circle')
.attr('cx', (d, i) => i * 120 + 60)
.attr('cy', height / 2)
.merge(circles) selection. // (*) merge (enter and update)
.attr('r', d => radiusScale(d.type))
.attr('fill', d => colorScale(d.type));
// exit
circles.exit().remove();
};
const circles = selection.selectAll('circle').data(fruits);
const text = selection.selectAll('text').data(fruits);
circles // (*)
.enter().append('circle')
.attr('cx', (d, i) => i * 120 + 60)
.attr('cy', height / 2)
.merge(circles)
.attr('r', d => radiusScale(d.type))
.attr('fill', d => colorScale(d.type));
circles.exit().remove();
text // (*)
.enter().append('text')
.attr('x', (d, i) => i * 120 + 60)
.attr('y', height / 2)
.merge(text)
.text(d => d.type)
text.exit().remove();
const groups = selection.selectAll('g').data(fruits);
const groupsEnter = groups.enter().append('g');
groupsEnter
.merge(groups)
.attr('transform', (d, i) =>
`translate(${i * 180 + 100},${height / 2})`
);
groups.exit().remove();
groupsEnter.append('circle')
.merge(groups.select('circle'))
.attr('r', d => radiusScale(d.type))
.attr('fill', d => colorScale(d.type));
groupsEnter.append('text')
.merge(groups.select('text'))
.text(d => d.type)
.attr('y', 120);
<g>
<circlr />
<text />
</g>
<g>
<circlr />
<text />
</g>
...
const renderBowl = selection.selectAll('rect')
.data([null]) // (*)
.enter().append('rect')
.attr('y', 110)
.attr('width', 920)
.attr('height', 300)
.attr('rx', 300 / 2);
/* 이렇게 작성한다면, 렌더링마다 "rect"가 생성된다.
const renderBowl = selection.append('rect')
.attr('y', 110)
.attr('width', 920)
.attr('height', 300)
.attr('rx', 300 / 2);
*/
D3.js에서도 재사용 가능한 컴포넌트를 만들 수 있다. 구조는 리액트와 비슷하다.
// (*) index.js
render(svg, {
fruits,
height: +svg.attr('height')
});
// (*) fruitBowl.js
const render = (selection, props) => {
const { fruits, height } = props;
const circles = selection.selectAll('circle').data(fruits);
// General Update Pattern
circles
.enter()
.append('circle')
.attr('cx', (d, i) => i * 120 + 60)
.attr('cy', height / 2)
.merge(circles)
.attr('r', d => radiusScale(d.type))
.attr('fill', d => colorScale(d.type));
circles.exit().remove();
};
selection.transition()
d3.select("body")
.transition()
.style("background-color", "red");
// Transition function
const t = d3.transition()
.duration(750)
.ease(d3.easeLinear);
d3.selectAll(".apple").transition(t)
.style("fill", "red");
d3.selectAll(".orange").transition(t)
.style("fill", "orange");
자주 사용하는 method 정리
DOM에 있는 element를 선택함
const svg = select('svg')
const svg = selectAll('rect')
Attribute를 추가하거나 업데이트 함
d3.selectAll('rect').attr('width', 10)
attr('x', 10)/(attr('y', 10))
xPosition, yPositionattr('width', 10)/(attr('height', 10))
높이, 너비attr('cx', 100)/(attr('cy', 200))
position the x-centre, position the Y-centre of circleattr('r', 50)
radiusattr('transform', `translate(0, 100)`)
Transform(translate, scale, rotate)아래처럼 function과 함께 사용할 수 있음
d3.selectAll('circle')
.attr('cx', function(d, i) {
return i * 100;
});
domain() 범위의 입력값이 들어오면 range() 범위의 결과값으로 바꿔주는 함수를 만든다. scale 종류는 총 12개가 있다.
domain([min, max])
: Set of values of datarange([min, max])
: set of resulting values of a functiondomain
은 데이터의 최소, 최대값으로, range
는 그 데이터를 사용하여 표출할 범위의 너비, 높이 픽셀값으로 설정한다. (svg의 너비등)
const xScale = scaleLinear()
.domain([0, max(data, d => d.population)])
.range([0, innerWidth]);
const yScale = scaleBand()
.domain(data.map(d.country))
.range([0, innerHeight])
.padding(0.1);
mapG.call(choroplethMap, {
features,
colorScale,
colorValue
});
D3.js와 리액트를 함께 사용할 경우, 두 라이브러이 모두 DOM을 핸들링하려고 하기 때문에 적절하게 믹싱하는 것이 중요하다.
예를 들어, DOM element를 선택해야 할 때, D3.js에서는 select()
를 사용하지만 리액트에서는 useRef()
을 사용할 수 있다. 따라서 아래와 같이 함께 사용할 수 있다. D3.js에서 제공하는 transition
을 사용할 때도 마찬가지이다.
function BarChart() {
const [data, setData] = useState([...])
const rectRef = useRef<GroupTag>(null);
const handleDrawRect = useCallback(
(group) => {
group
.selectAll('rect')
.data(data)
.join('rect')
//...
},
[data]
);
useEffect(() => {
const rectGroup = select(rectRef.current); // (*)
handleDrawRect(rectGroup);
}, [handleDrawRect]);
return (
<svg width={WIDTH} height={HEIGHT}>
<g ref={rectRef} />
</Group>
</svg>
);
}