Reactjs + D3

Building bar chart with React and D3

This is a first post about building chart with React and D3. There is a plenty of 3rd party React charting libraries but if you are reading this, probably you are interested in learning how to building charts from scratch.

I’m planning to write series of React and D3 tutorials. In this first post we are going to build bar charts – vertical and horizontal – using D3 scales (plus few other packages from D3) to perform all the tricky calculations and then we will render React components making use of this calculated data.

Why React and D3?

You can use D3 on its own to create beautiful and interactive charts so why combine it with React? Well, if you are reading this post it is a good possibility that you are already working on some sort of React application and all DOM manipulation is done via React. Probably you would like to keep it (DOM manipulation) this way. When you use D3 in the way where you call .enter() and the .append(), you ‘ask’ D3 to create new DOM nodes. This seems to be perfectly fine but a downside of this is that is it not a ‘react way‘ where HTML is built using React components. State and props of each element on the screen isn’t easily available for debugging – what I mean by this is that you won’t be able to use Chrome React Developer Tools. Plus Event handling is delegated to D3.

Getting Started

I’ve created a github repository that will be used as a starting point. It is built using Create React App and it comes with integrated React-Router:

git clone https://github.com/eloleon/Front2Back-create-react-app-with-react-router.git

Lets install two D3 packages that were going to use later on. Navigate to Front2Back-create-react-app-with-react-router folder and run following command:

npm install d3-array d3-scale --save

This will install packages that have some nice array utils and contain all the scales related function.

Lets prepare structure. In src/Components folder create two files: BarChart.js and BarComponent.js, in src/Pages folder rename Page1.js to BarChartPage.js. Now, lets update routing to reflect new file names. In src/routes.js change Page1 import to following:

import BarChart from './Pages/BarChartPage';

and update Route – change path from /page1 to /barchart and BarChart component should be rendered for this route:

<Route path="/barchart" component={BarChart} />

In src/components/Navigation.js update Link to page1, now it should like this:

<Link to="/barchart">Bar Chart</Link>

Lets build a bar chart!

Okey, so the boring stuff is sorted. Lets move on to some visuals. First components that we are going to work on is BarChartPage. This will be our container, it will provide chart data,  width and height to BarChartComponent which will be then rendered . By keeping all data related information within this component, it will us to render it wherever we want. Here is the file contents:

import React, { Component } from 'react';
import BarChart from '../Components/BarChart';

export default class BarChartPage extends Component {
  render () {
    const chartWidth = 500;
    const chartHeight = 250;
    const sampleData = [5, 15, 10, 20, 25, 5, 30];
    return (
      <div>
        <BarChart
          chartWidth={chartWidth}
          chartHeight={chartHeight}
          data={sampleData}
        />
      </div>
    );
  }
}

Now lets work on BarChart. This component will contain most of the logic around sizing, positioning etc. It will render SVG element that will contain our chart. We are going to use two scales to help us with calculations:

  • band scale – this scale will help with geometry. It will give us width of each bar based on domain (an array with min and max values from our data) and range (an array with 0 as first element and chart width as a second)
  • linear scale – this scale will help us to determine height of each bar based on domain(an array with 0 as a first element and highest value from our data as a second) and range(an array with 0 as a first element and chart height as a second)

Also we will use D3 array utils to get max value from our data. We could easily do it with vanilla JS but since it is D3 related tutorial, lets use all the goodies from it. Here is initial code:

import React, { Component } from 'react';
import BarComponent from './BarComponent';
import { scaleBand, scaleLinear } from 'd3-scale';
import { max } from 'd3-array';

export default class BarChart extends Component {

  renderBars = () => {
    const { chartWidth, data, chartHeight } = this.props;

    // Scale used to get width and step
    const scaleX = scaleBand()
      .domain(data)
      .range([0, chartWidth])
      .paddingInner(0.02);
    
    const barWidth = scaleX.bandwidth();
    const barXStep = scaleX.step();

    // Scale to get height for each bar
    const scaleY = scaleLinear()
      .domain([0, max(data)])
      .range([0, chartHeight]);

    const bars = data.map( (d, i) => {
      return (
        <BarComponent
          key={i}
          y={0}
          x={barXStep * i}
          width={barWidth}
          height={scaleY(d)}
        />
      );
    });
    return bars;
  }

  render () {
    const { chartWidth, chartHeight } = this.props;

    return (
      <div>
        <h2>Vertical Bar Chart</h2>
        <svg width={chartWidth} height={chartHeight}>
          {this.renderBars()}
        </svg>
      </div>
    );
  }
}

As you can see BarChartComponent imports BarComponent. This is a simple React components that returns SVG rect element which will be used to render each bar. Here is how it looks like:

import React, { Component } from 'react';

export default class BarComponent extends Component {
  render () {
    const { fill, height, width, x, y } = this.props;
    return (
      <rect
        x={x}
        y={y}
        width={width}
        height={height}
        fill={fill}
      />
    );
  }
}

 

Navigate to Bar Chart page and you should see this:

Bar Chart - initial version
Bar Chart – initial version

It is a great start but as you can see it doesn’t look as nice as it could. First of all, all bars have the same color. We can do better than that and thankfully D3 provides a way of getting colors based on input. In our case we will use index from data array and schemeCategory10 scale to get color. We need Ordinal Scale and Category Scale (category10 in our case but it could be any of the built-in ones) from D3 so lets import them to BarChart.js, change existing import from ‘d3-scale’ to match the one below:

import { scaleBand, scaleLinear, scaleOrdinal, schemeCategory10 } from 'd3-scale';

Now we need to create ordinal scale that will use schemeCategory10 for range. Add following code after the code which creates scaleY:

// Scale that returns color
const scaleColor = scaleOrdinal()
  .range(schemeCategory10);

Last thing to do is to use this new scale. Ensure that each BarComponent will use it to get value for fill prop. Modify code which returns BarComponents:

const bars = data.map( (d, i) => {
      return (
        <BarComponent
          key={i}
          y={0}
          x={barXStep * i}
          width={barWidth}
          height={scaleY(d)}
          fill={scaleColor(d)}
        />
      );
    });

 

Now our chart should look like this:
Bar Chart with colors using CategoryScheme10

Leave a Reply

Your email address will not be published. Required fields are marked *