@riacarmin
0.
1.
2.
3.
Intro
D3.js 101
with React
[demo]
🤷♀️
[
{ item: 'apples', quantity: 20 },
{ item: 'oranges', quantity: 12 },
{ item: 'kiwis', quantity: 6 },
{ item: 'bananas', quantity: 4 },
]
this.svg = d3
.select(this.element)
.append('svg')
.attr(
'height',
this.height + this.margin.top + this.margin.bottom
)
.attr(
'width',
this.width + this.margin.left + this.margin.right
)
.style('font', '10px sans-serif');
this.canvas = this.svg
.append('g')
.attr(
'transform',
`translate(${this.margin.left}, ${this.margin.top})`
);
// Set scales
// ==========================================================
const xScale = d3
.scaleBand()
.domain(this.data.map((d) => d.item))
.range([0, this.width])
.padding(0.25);
const yScale = d3
.scaleLinear()
.domain([0, d3.max(this.data, (d) => d.quantity)])
.range([this.height, 0]);
const colorScale = d3
.scaleLinear()
.domain([0, d3.max(this.data, (d) => d.quantity)])
.range(['#E2F5E1', '#017E54']);
// General Update Pattern
// ==========================================================
const enter = (enter) =>
enter
.append('rect')
.attr('x', (d) => xScale(d.item))
.attr('width', xScale.bandwidth)
.attr('y', (d) => yScale(d.quantity))
.attr('height', (d) => this.height - yScale(d.quantity))
.attr('fill', (d) => colorScale(d.quantity));
const update = (update) => {}
const exit = (exit) => {}
this.canvas
.selectAll('rect')
.data(this.data)
.join(enter, update, exit);
<Component />
<Chart />
chart.d3.js
import React, { useState, useCallback } from 'react';
import randomNumber from '../../utils/helpers/randomNumber';
import Chart from '../Chart';
import ScatterChart from './d3/scatter';
const DATA = Array.from(Array(6)).map(() => ({
time: randomNumber(1, 20),
points: randomNumber(0, 20),
}));
const Competition = () => {
const [data, setData] = useState(DATA);
return <Chart data={data} d3={ScatterChart} />
};
export default Competition;
<Component />
import React, { useEffect, useRef } from 'react';
const Chart = ({ className, data, d3: D3 }) => {
if (!D3 || !validate(D3)) {
return <div />;
}
const chartRef = useRef(null);
const chartClass = useRef(null);
// Render =================================================
return <div className={className} ref={chartRef}></div>;
};
<Chart />
// Initialize chart svg container =================================
useEffect(() => {
if (!chartRef.current) {
return false;
}
chartClass.current = new D3(chartRef.current);
return () => {
chartClass.current.destroy();
};
}, []);
<Chart />
export default class Chart {
constructor(element) {
this.element = element;
this.data = null;
this.margin = { top: 40, right: 60, bottom: 40, left: 60 };
this.size = element ? element.getBoundingClientRect() : {};
this.init();
}
get height() { … }
get width() { … }
init = () => { … };
setData = (data) => { … };
resize = (element) => { … };
render = () => { … };
destroy = () => { … };
}
chart.d3.js
const debouncedData = useDebounce(data, 150);
// Update chart on changes in data =========================================
useEffect(() => {
debouncedData &&
chartClass.current &&
chartClass.current.setData(debouncedData);
chartClass.current && chartClass.current.render();
}, [debouncedData]);
<Chart />
// Resize chart on window.resize ========================
const isClient = typeof window === 'object';
useEffect(() => {
if (!isClient) {
return false;
}
const handleResize = () =>
chartClass.current.resize &&
chartClass.current.resize(chartRef.current);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
<Chart />
@riacarmin