D3 with React: Part 1: d3 way

d3 with react

React has been a popular choice among developers. The number of applications on the web using React will rise sharply in the coming future. Almost 80% of the enterprise applications and data driven applications require some sort of data visualization. Using d3 for any data visualization is, in my opinion, the standard option on the web today.

But using a third party library like d3 with newer frameworks and libraries like React has been a question of concern for many. In this post we are going to look at the basic technique of intergrating d3 with React.

Using d3js with Reactjs, the d3 way

First, lets create a React component and name it GraphComponent

import React, { Component } from 'react'
import * as d3 from 'd3'

class GraphComponent from Component {
    constructor() {
        super(...arguments)
    }

    render() {
        return (<div classNames='graph-container' ref={node => this.node = node}/>)
    }
}

export default GraphCOmponent

Here we have simple component which defines a constructor() and a render() method. The render() renders an empty div with class='graph-container'. If you have been playing with d3js then you know that to render a d3 graph we need a container element. The div.graph-container serves that purpose.

To initialize d3 graph, we need to wait for the DOM to mount and render the component. Lets intialize our graph. For this we add a method initD3Graph() in the class.

initD3Graph() {
    if (!this.node) {
         return
    }

    const margin = { top: 20, right: 20, bottom: 20, left: 20 }
    const height = 400 - margin.top - margin.bottom
    const width = this.node.clientHeight - margin.left - margin.right
    
    const container = d3.select(this.node)
    
    const svg = container.append('svg')
        .attr('height', height + margin.top + margin.bottom)
        .attr('width', width + margin.left + margin.right)
        
    const g = svg.append('g')
        .attr('height', height)
        .attr('width', width)
        .attr('transform', `translate(${margin.left},${margin.top})`)
        
    const scale = {
        x: d3.scaleBand().rangeRound([0, width]).padding(0.1),
        y: d3.scaleLinear().rangeRound([height, 0])
    }
    
    d3.csv('data.csv', (d) => {
        return d.value
    }, (error, data) => {
        if (error
            throw error
        
        x.domain(data.map(function(d) { return d.name; }))
        y.domain([0, d3.max(data, function(d) { return d.value; })])
        
        g.append('g')
            .attr('class', 'axis axis--x')
            .attr('transform', `translate(0,${height})`)
            .call(d3.axisBottom(scale.x))
        
        g.append('g')
            .attr('class', 'axis axis--y')
            .call(d3.axisLeft(scale.y).ticks(10, '%'))
            .append('text')
            .attr('transform', 'rotate(-90)')
            .attr('y', 6)
            .attr('dy', '0.71em')
            .attr('text-anchor', 'end')
            .text('Frequency')

        g.selectAll('.bar')
            .data(data) 
            .enter().append('rect')
            .attr('class', 'bar') 
            .attr('x', function(d) { return scale.x(d.name); })
            .attr('y', function(d) { return scale.y(d.value); })
            .attr('width', scale.x.bandwidth())
            .attr('height', function(d) { return height - scale.y(d.value) })
    })
}

While using d3 to generate graph, one thing to keep in mind while working with React is that, d3 requires the DOM to be ready before manipulating the DOM to insert svg elements. Luckily React provides a few life cycle methods to takecare of that.

componentDidMount() {
    this.initD3Graph()
}

componentDidMount() method is the least we require to initialize the graph. You can read more about React’s various lifecycle methods here.

Although, this method comes with a disadvantage that we are not utilizing React‘s virtual DOM to update the view, rather we are depending on d3‘s ability to manipulate DOM.