Stacked Bar chart in React with D3 using JSON data
Let’s create a stacked bar chart using React and D3.
The chart will display data for different stores, showing the distribution of sales across clothing, accessories, technology, and home goods.
1. Setting Up the Component:
- We’re creating a functional component called
StackedBarChart
. - It uses React’s
useState
,useEffect
, anduseRef
hooks. - The initial data for the bar chart is defined in the
data
state variable
First, install D3 by running
npm install d3 --save
Create a new React component called StackedBarChart
import React from "react";
export default function StackedBarChart()
{
return(
<div>
</div>)
}
Import D3 into your component
import * as d3 from “d3”;
Create sample data or import data from a json file
const [data, setData] = useState([
{ store: 'Store A', clothing: 3840, accessories: 1920, technology: 960, homegoods: 800 },
{ store: 'Store B', clothing: 1600, accessories: 1230, technology: 1790, homegoods: 670 },
{ store: 'Store C', clothing: 1150, accessories: 1920, technology: 960, homegoods: 1800 },
{ store: 'Store D', clothing: 4600, accessories: 1440, technology: 960, homegoods: 400 },
]);
2. Dimensions and SVG Container
- We set up dimensions for the chart using
margin
,width
, andheight
. - The
svgRef
is a reference to the SVG container element.
Create the dimensions for your D3 chart
const margin = { top: 0, right: 100, bottom: 30, left: 100 };
const width = 800 — margin.left — margin.right;
const height = 300 — margin.top — margin.bottom;
Create a ref for your svg component
const svg = d3.select(svgRef.current);
3. Creating Scales:
- We create
xScale
for the x-axis (store names) andyScale
for the y-axis (sales values). xScale
: Creates a band scale which helps position the bars horizontally (along the x-axis) based on the store names.yScale
: Creates a linear scale which helps position the bars vertically (along the y-axis) based on the sales values.
const xScale = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.5)
.align(0.1);
const yScale = d3.scaleLinear()
.rangeRound([height, 0]);
4. Set Domains and Colors for the Bars
The x-axis represents the store names (categories) in your stacked bar chart.
data.map((d) => d.store)
: Maps each data object (store) to its corresponding store name. Themap
function extracts thestore
property from each data object.xScale.domain(...)
: Sets the domain of the x-axis scale. The domain is the range of input values (in this case, the store names).- So, the x-axis scale will map each store name to a position along the x-axis.
The y-axis represents the sales values.
d3.max(series, (d) => d3.max(d, (d) => d[1]))
: Calculates the maximum sales value across all categories (stores). It goes through each series (stacked data) and finds the highest value within each category.[0, ...]
: Sets the domain from 0 (minimum value) to the maximum sales value..nice()
: Adjusts the domain to include “nice” round numbers (e.g., multiples of 10) for better readability.
xScale.domain(data.map((d) => d.store));
yScale.domain([0, d3.max(series, (d) => d3.max(d, (d) => d[1]))]).nice();
const color = d3.scaleOrdinal()
.domain(["clothing", "homegoods", "accessories", "technology"])
.range(["#1f77b4", "#ff7f0e", "#2ca02c", "#9467bd"]);
5. Stack Data
- The
d3.stack()
function is part of the D3.js library and is used for creating stacked data from an array of values. - It takes an array of data (the sales data for different stores) and transforms it into a format suitable for creating stacked bar charts.
- Here’s what each part of the
stack
definition means: .keys(["clothing", "accessories", "technology", "homegoods"])
: Specifies the keys (categories) within each data object that will be stacked. In this data, these keys represent different product types (e.g., clothing, accessories).d3.stackOrderNone
means that the original order of the data is preserved.d3.stackOffsetNone
means that there is no offset; the data starts from the baseline (y = 0).- After defining the stack, we apply it to our actual data (
data
) to create the stacked series.
const series = stack(data);
:
- The resulting
series
is an array of arrays, where each inner array represents a stack for a specific category (e.g., clothing, accessories). - Each inner array contains pairs of values representing the start and end points of each segment within the stack.
const stack = d3.stack()
.keys(["clothing", "accessories", "technology", "homegoods"])
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone);
const series = stack(data);
6. Drawing Bars:
svg.append("g")
: This line appends a new<g>
(group) element to the SVG. The<g>
element is used to group SVG shapes together.- The
.data()
method binds theseries
data to the selected<g>
elements .enter().append("g")
: This chain creates new<g>
elements for each data point in theseries
that doesn’t have a corresponding existing<g>
element. It’s a way to dynamically add groups based on the data.- The
.attr()
method sets thefill
attribute of each<g>
element. The color is determined by thed.key
value(The store name) - Then select all existing
<rect>
elements within each<g>
group, bind the data and create new<rect>
elements for each data point(Clothing, Accessories, homegoods, technology) - Lastly, position it along x and y axis and set it’s height and width.
svg.append("g")
.selectAll("g")
.data(series)
.enter().append("g")
.attr("transform", "translate(40, 10)")
.attr("fill", function (d) { return color(d.key); })
.selectAll("rect")
.data(function (d) { return d; })
.enter().append("rect")
.attr("x", function (d) { return xScale(d.data.store); })
.attr("y", function (d) { return yScale(d[1]); })
.attr("height", function (d) { return yScale(d[0]) - yScale(d[1]); })
.attr("width", xScale.bandwidth())
7. Creating Axes:
- We create the x-axis using
d3.axisBottom(xScale)
and append it to the SVG. - We create the y-axis using
d3.axisLeft(yScale)
and append it to the SVG.
// Create x-axis
const xAxis = d3.axisBottom(xScale);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(40,${height+10})`)
.call(xAxis);
// Create y-axis
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(40, 10)")
.call(yAxis);
}, []);
8. Rendering the SVG:
- The final return statement renders the SVG container with the specified dimensions.
return(
<svg ref={svgRef} style={{padding:”100px”}}
width={width + margin.left + margin.right}
height={height + margin.top + margin.bottom}>
</svg>
)
See it in action!