33
loading...
This website collects cookies to deliver better user experience
D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG, and CSS.
yarn add d3
yarn add --dev @types/d3
interface Data {
label: string;
value: number;
}
const DATA: Data[] = [
{ label: "Apples", value: 100 },
{ label: "Bananas", value: 200 },
{ label: "Oranges", value: 50 },
{ label: "Kiwis", value: 150 }
];
data
prop and return SVG elements to visualize given data.interface BarChartProps {
data: Data[];
}
function BarChart({ data }: BarChartProps) {
const margin = { top: 0, right: 0, bottom: 0, left: 0 };
const width = 500 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
return (
<svg
width={width + margin.left + margin.right}
height={height + margin.top + margin.bottom}
>
<g transform={`translate(${margin.left}, ${margin.top})`}></g>
</svg>
);
}
width
and height
attributes. So far, so good. But you might wonder what is this g
element for. Basically, you can think of it as a container for elements that will come next - x-axis, y-axis and bars that will represent our data. By manipulating its transform
attribute with margin
values, we will create some space to properly render all the above-mentioned elements.scaleBand
.const scaleX = scaleBand()
.domain(data.map(({ label }) => label))
.range([0, width]);
AxisBottom
component which will render g
element that will be used for drawing horizontal axis by calling axisBottom
function on it.interface AxisBottomProps {
scale: ScaleBand<string>;
transform: string;
}
function AxisBottom({ scale, transform }: AxisBottomProps) {
const ref = useRef<SVGGElement>(null);
useEffect(() => {
if (ref.current) {
select(ref.current).call(axisBottom(scale));
}
}, [scale]);
return <g ref={ref} transform={transform} />;
}
AxisBottom
in our BarChart
component, the code will look like this 👇export function BarChart({ data }: BarChartProps) {
const margin = { top: 0, right: 0, bottom: 20, left: 0 };
const width = 500 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const scaleX = scaleBand()
.domain(data.map(({ label }) => label))
.range([0, width]);
return (
<svg
width={width + margin.left + margin.right}
height={height + margin.top + margin.bottom}
>
<g transform={`translate(${margin.left}, ${margin.top})`}>
<AxisBottom scale={scaleX} transform={`translate(0, ${height})`} />
</g>
</svg>
);
}
transform
property of AxisBottom
component to place it at the very bottom of our SVG container, since originally this would be rendered in the top-left corner.scaleLinear
for scale. On our y-axis, we want to display ticks for values from our data. Ticks are just "steps" between minimum and a maximum value in a given domain. To do that, we will pass [0, max]
for our domain and [height, 0]
for range. Notice how height
goes first - it's because we want ticks to have maximum value on top of our y-axis, not at the bottom.const scaleY = scaleLinear()
.domain([0, Math.max(...data.map(({ value }) => value))])
.range([height, 0]);
AxisLeft
component. It's almost the same what we did in AxisBottom
but this time we will use axisLeft
function to draw our vertical axis.interface AxisLeftProps {
scale: ScaleLinear<number, number, never>;
}
function AxisLeft({ scale }: AxisLeftProps) {
const ref = useRef<SVGGElement>(null);
useEffect(() => {
if (ref.current) {
select(ref.current).call(axisLeft(scale));
}
}, [scale]);
return <g ref={ref} />;
}
BarChart
the code will look like this 👇export function BarChart({ data }: BarChartProps) {
const margin = { top: 10, right: 0, bottom: 20, left: 30 };
const width = 500 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const scaleX = scaleBand()
.domain(data.map(({ label }) => label))
.range([0, width]);
const scaleY = scaleLinear()
.domain([0, Math.max(...data.map(({ value }) => value))])
.range([height, 0]);
return (
<svg
width={width + margin.left + margin.right}
height={height + margin.top + margin.bottom}
>
<g transform={`translate(${margin.left}, ${margin.top})`}>
<AxisBottom scale={scaleX} transform={`translate(0, ${height})`} />
<AxisLeft scale={scaleY} />
</g>
</svg>
);
}
transform
property.scaleX
and scaleY
we declared earlier to compute x
, y
, width
and height
attributes for each value from our data. For rendering bar we will use SVG rect
element.interface BarsProps {
data: BarChartProps["data"];
height: number;
scaleX: AxisBottomProps["scale"];
scaleY: AxisLeftProps["scale"];
}
function Bars({ data, height, scaleX, scaleY }: BarsProps) {
return (
<>
{data.map(({ value, label }) => (
<rect
key={`bar-${label}`}
x={scaleX(label)}
y={scaleY(value)}
width={scaleX.bandwidth()}
height={height - scaleY(value)}
fill="teal"
/>
))}
</>
);
}
BarChart
the final version of it will look like this 👇export function BarChart({ data }: BarChartProps) {
const margin = { top: 10, right: 0, bottom: 20, left: 30 };
const width = 500 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const scaleX = scaleBand()
.domain(data.map(({ label }) => label))
.range([0, width])
.padding(0.5);
const scaleY = scaleLinear()
.domain([0, Math.max(...data.map(({ value }) => value))])
.range([height, 0]);
return (
<svg
width={width + margin.left + margin.right}
height={height + margin.top + margin.bottom}
>
<g transform={`translate(${margin.left}, ${margin.top})`}>
<AxisBottom scale={scaleX} transform={`translate(0, ${height})`} />
<AxisLeft scale={scaleY} />
<Bars data={data} height={height} scaleX={scaleX} scaleY={scaleY} />
</g>
</svg>
);
}
Bars
, but besides that we used padding
method on our scaleX
to create some space between rectangles and improve chart readability.