Stacked Bar chart in React with D3 using JSON data

Stuthi Neal
4 min readApr 27, 2024

--

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, and useRef 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, and height.
  • 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) and yScale 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. The map function extracts the store 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 the series data to the selected <g> elements
  • .enter().append("g"): This chain creates new <g> elements for each data point in the series 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 the fill attribute of each <g> element. The color is determined by the d.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!

--

--

Stuthi Neal

Full-stack developer and techie mom, crafting code for a brighter world. Committed to making tech accessible for all.