D3 with React: Part 2: The react way

D3 With React: The React way

In the last post , D3 with React: Part 1: d3 way, we saw how we can use d3 with react by using d3’s DOM selection and manipulation mechanism. In this post, we are going a side step and will see how we can utilize React’s rendering to render the d3 graph.

Using d3js with Reactjs, the react 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.

Let’s add an init method that will initialize the properties for the graph.

componentDidMount() {
    this.initD3Graph()
}

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 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
        
        scale.x.domain(data.map(function(d) { return d.name; }))
        scale.y.domain([0, d3.max(data, function(d) { return d.value; })])

       this.setState({
          margin,
          width,
          height,
          scale,
          data
       })
    })
}

The initD3Graph() method should be called after the component has been mounted and the reference to the graph container is available.

Let’s update our render method to include graph elements. Instead of letting d3 handle the DOM manipulation and element creation, we use React to render the graph elements.

render() { 
  const {data, height, margin, scale, width} = this.state

  if (data && data.length > 0) {
   const bars = data.map(datum => {
      return (
         <rect class="bar" x={scale.x(datum.name)} y={scale.y(datum.value)} width={scale.x.bandwidth()} height={height - scale.y(datum.value)}/>
      )
   })

   const svg = (
    <svg height={height + margin.top + margin.bottom} width={width + margin.left + margin.right}>
     <g height={height} width={width} transform={`translate(${margin.left},${margin.top})`}>
      <g className="axis axis--x" transform={`translate(0,${height})`} />
      <g className="axis axis--y">
       <text transform="rotate(-90)" y="6" dy="0.71em" text-anchor="end">Frequency</text>
      </g>
      {bars}
     </g>
    </svg>
   )
  }
  return (
    <div classNames="graph-container" ref={node => this.node = node}>
     {svg}
    </div>
  ) 
}

To add the axis we add a draw() method which is called only after the component has finished updating.

componentDidUpdate() {
   this.draw()
}


draw() {
  const {scale} = this.state

  d3.select(".axis.axis--x).call(d3.axisBottom(scale.x))
  d3.select(".axis.axis--y).call(d3.axisLeft(scale.y).ticks(10, '%'))
}