Creating a responsive bar chart using d3.js in React.js

Creating a responsive bar chart using d3.js in React.js

·

5 min read

Data visualization is an essential aspect of data analysis, and bar charts are a popular choice to display data. D3.js is a powerful library for creating data visualizations, and React.js is a popular JavaScript library for building user interfaces. In this blog, we will explore how to create a bar chart using D3.js in a React.js application.

Prerequisites

Before we get started, ensure that you have the following prerequisites:

  • Basic knowledge of HTML, CSS, and JavaScript

  • Node.js and npm installed on your system

  • A text editor like Visual Studio Code

Getting started

Let's create a new React.js project by running the following command in your terminal:

npx create-react-app responsive-bar-chart-using-d3js-reactjs

This command creates a new React.js project with the name responsive-bar-chart-using-d3js-reactjs. Navigate to the project directory by running the following command:

cd responsive-bar-chart-using-d3js-reactjs

Open the project directory in your text editor. Next, install the d3 library by running the following command:

npm install d3

This command installs the latest version of the d3 library in your project.

Creating the bar chart component

Create a new file named BarChart.tsx in the src/features directory of your project. This file will contain the code for the bar chart component. Here's the code for the component:

import * as d3 from 'd3';
import { FC, useEffect, useRef } from 'react';

import Spinner from 'components/Spinner';
import { useResizeObserver } from 'hooks/useResizeObserver';

const BarChart: FC<IChart> = ({ data, svgWrapperRef, margin, isYaxisRight = false }) => {
  const dimensions: any = useResizeObserver(svgWrapperRef);
  const svgRef = useRef<SVGSVGElement>(null);

  useEffect(() => {
    if (!svgRef?.current || !dimensions) return;

    // Initialize the height, width, and max value
    const innerWidth: number = dimensions?.width - margin?.right;
    const innerHeight: number = dimensions?.height - (margin?.top + margin?.bottom);
    const maxValue: number = Math.max(...data?.map(({ value }) => value));

    const svg = d3.select(svgRef?.current);

    // Create x-axis scale
    const xScale: any = d3
      .scaleBand()
      .domain([...data?.map(({ label }) => label)])
      .rangeRound([margin?.left, innerWidth])
      .padding(0.5);

    // Create y-axis scale
    const yScale: any = d3.scaleLinear().domain([0, maxValue]).rangeRound([innerHeight, margin?.top]);

    // Create x-axis
    const xAxis: any = d3.axisBottom(xScale).ticks(data?.length);
    svg
      .select('.x-axis')
      .style('transform', `translateY(${innerHeight}px)`)
      .style('color', 'steelblue')
      .style('font-size', 10)
      .call(xAxis)
      .selectAll('text')
      .style('text-anchor', 'end')
      .attr('dx', '-5px')
      .attr('dy', '4px')
      .attr('transform', 'rotate(-30)');

    // Create y-axis for left side
    const yAxis: any = d3.axisLeft(yScale).ticks(5);
    svg
      .select('.y-axis')
      .style('transform', `translateX(${margin?.left}px)`)
      .style('color', 'steelblue')
      .style('font-size', 10)
      .call(yAxis);

    // Create y-axis for right side
    const yAxisRight: any = d3.axisRight(yScale).ticks(5);
    svg
      .select('.y-axis-right')
      .style('transform', `translateX(${dimensions?.width - margin?.right}px)`)
      .style('color', 'steelblue')
      .style('font-size', 10)
      .call(yAxisRight);

    // Draw the bars
    svg
      .selectAll('.bar')
      .data([...data])
      .join('rect')
      .attr('class', 'bar')
      .style('transform', 'scale(1, -1)')
      .attr('x', ({ label }) => xScale(label))
      .attr('width', xScale.bandwidth())
      .attr('y', -innerHeight)
      .style('cursor', 'pointer')
      .style('fill', ({ fillColor }) => fillColor)
      .on('mouseenter', (event, item) => {
        svg
          .selectAll('.tooltip')
          .data([item?.value])
          .join((enter) => enter.append('text').attr('y', yScale(item?.value) - 4))
          .attr('class', 'tooltip')
          .text(`${item?.value}`)
          .attr('x', xScale(item?.label) + xScale.bandwidth() / 2)
          .style('font-size', '10px')
          .attr('text-anchor', 'middle')
          .transition()
          .duration(500)
          .attr('y', yScale(item?.value) - 8)
          .style('font-size', '14px')
          .style('fill', item?.fillColor)
          .style('opacity', 1);
      })
      .on('mouseleave', () => svg.select('.tooltip').remove())
      .transition()
      .style('fill', ({ fillColor }) => fillColor)
      .attr('height', ({ value }) => innerHeight - yScale(value));
  }, [data, dimensions]);

  if (!dimensions) {
    return (
      <div className="flex w-full justify-center items-center py-2">
        <Spinner className="text-gray-300 h-8 w-8" />
      </div>
    );
  }

  return (
    <div className="d3js">
      <svg ref={svgRef} width={`${dimensions?.width}`} height={dimensions?.height}>
        <g className="x-axis" />
        {isYaxisRight ? <g className="y-axis-right" /> : <g className="y-axis" />}
      </svg>
    </div>
  );
};

interface IData {
  label: string;
  value: number;
  fillColor: string;
}

interface IChart {
  data: IData[];
  svgWrapperRef: any;
  margin: {
    top: number;
    right: number;
    bottom: number;
    left: number;
  };
  isYaxisRight?: boolean;
}

export default BarChart;

In the above code, we import FC, useRef, and useEffect from the react library and import the d3 library. We also define a new component named BarChart that takes four props: data, svgWrapperRef, margin, and isYaxisRight. The component returns an SVG element with a ref to the svgRef variable.

We use the useEffect hook to add the D3.js code for the bar chart. We also pass the data, and dimensions, props to the dependencies array of the useEffect hook so that the code inside the hook runs every time any of these props change.

Let’s import the BarChart component in the app.tsx or other components and pass the props. Here's the code for the app.tsx

import { useEffect, useRef, useState } from 'react';

import BarChart from './BarChart';
import { randomNumber } from 'utils/helpers';

const Feature = () => {
  const margin = { top: 20, right: 10, bottom: 20, left: 30 };
  const [data, setData] = useState<any[]>([]);
  const svgLargeRef: any = useRef<SVGSVGElement>();
  const svgMediumRef: any = useRef<SVGSVGElement>();
  const svgSmallRef: any = useRef<SVGSVGElement>();

  useEffect(() => {
    const makeRandomData = () => {
      setData([]);
      ['JS', 'Python', '.Net', 'Java', 'GoLang', 'UI/UX']?.map((label) => {
        setData((prevData) => [...prevData, { label, value: randomNumber(10, 99), fillColor: `#${randomNumber()}` }]);
      });

      setTimeout(() => {
        makeRandomData();
      }, 1000 * 10);
    };

    makeRandomData();
  }, []);

  return (
    <div className="relative px-5 py-2">
      <div className="flex justify-center">
        <div className="w-[90vw] h-full">
          <h2 className="flex justify-between items-center font-semibold text-black mb-2">D3.js Chart</h2>
          <div className="flex w-full h-[75vh] justify-center items-start border border-dashed border-black text-white p-4 space-x-2">
            <div ref={svgLargeRef} className="w-5/12 h-72 border border-gray-300 p-1">
              <BarChart data={data} svgWrapperRef={svgLargeRef} margin={margin} isYaxisRight={false} />
            </div>
            <div ref={svgMediumRef} className="w-4/12 h-72 border border-gray-300 p-1">
              <BarChart data={data} svgWrapperRef={svgMediumRef} margin={margin} isYaxisRight={false} />
            </div>
            <div ref={svgSmallRef} className="w-3/12 h-72 border border-gray-300 p-1">
              <BarChart
                data={data}
                svgWrapperRef={svgSmallRef}
                margin={{ ...margin, left: 10, right: 30 }}
                isYaxisRight={true}
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Feature;

Summary

That's it! You should now have a basic bar chart that displays your data. From here, you can customize the styling and add interactivity as needed.

I hope you found this tutorial helpful! If you have any questions or feedback, feel free to let me know.

Download full source code from GitHub