How to integrate React and D3 – The right way

React has been the choice for many developers in last year and will continue to grow in coming years. The main reason I liked React would be the portability of using it along with other frameworks specially in existing project. Other then the performance improvement from virtual dom, React also excels in reusability and integration. In this How to integrate React and D3 – The right way tutorial we will take a look at integrating React with another very popular javascript component (d3.js), which is heavily used in visualization.

Why React + D3

The first question would be why we would need to even integrate React and D3. Even though D3 is a fantastic library it lacks in reusability and integration. You would need to put lots of additional effort in order to make a d3 chart reusable across your application. We will look into other advantages, but lets see how we can actually integrate React and D3.

How to integrate React + D3

At a high level D3 has following 3 functions:

DOM Update

Calculation (Math)

Transitions

When integrating React and D3, it would be best to have React to update the DOM (not D3). So most of the time we will use React to update the DOM however will be fully utilizing the Math functions provided by D3. We won’t be able to use D3 transitions in React since we will have React update the DOM. In short, we will not be able to use d3.select() and d3.transition() from react.

Note: In very few scenarios we will still let D3 update the DOM since converting those functions in React could be overkilling.

Advantages of Integrating React and D3:

Let’s come back to our why question again. Here are some facts, we will be covering them in our demo and example.

Reusability

As described earlier, the charts could be reused across the application without any additional coding. This would be a major reason for using React + D3

Update Chart

Updating the charts using React also will be very simple.We need to define the props and state properly in React to get the full benefit of this.We can also adapt Flux Architecture (We will talk about this in a future post) to extend the capabilities.

Responsiveness

This is another very important functionality. Using React we can create responsive charts without even having to add any custom code for every chart. We will be using React’s two way binding feature for this.

Once you integrate React and D3 you would almost create a library of charts which can be easily shared and modified. You unit testing will also become easy. You can relate this to the Procedural vs Object Oriented Programming. D3 is like Procedural and React + D3 is like Object Oriented.

Disadvantages of Integrating React and D3:

D3 can be integrated with React by another way (Like any other library). Instead of let React update the DOM, you can get the current DOM node in React and use D3 to update the DOM. However this is not an efficient of way of integrating them together. Lets look at few Disadvantages of Integrating React and D3 below:

JSX

Since you will not be using D3 to update the DOM, you need to write equivalent JSX code to create the svg elements.

Transitions

D3 transitions functions cannot be used. However its probably best to avoid the Transitions altogether to start with, if they don’t provide a real business value to your use case. You may try using React Animation however it will be a separate topic to discuss in future posts.Otherwise the only option is to use the core d3 library to perform the animation.

Enough of theory, let’s look at the example of what we will be building. We will create a small dashboard using React and D3. You can find the live demo here.

The above charts are fully reusable and responsive. You can test by resizing the browser window. Click on the Donut or the Progress Chart to update the data as well. You can jump into the code right away, here is the code available in github.

Setup:

We will be using ES5/Babel and in browser compilation of JSX. You can refer my previous post on the setup of ES5 here.

Lets first start by creating just the line for the chart without the Axis, Grid and the Dots.

We will have the width,height and chartId defined as propTypes. Use the getDefaultProps() function to setup the default values. We will define the width in this.state since we want to use two way binding for the width. We will use this later to create responsive chart. So set the this.state.width to this.props.width in getInitialState() function.

We will use fixed data set for time being. Create the data[] array. Then use the d3 math functions, d3.time.scale(), d3.scale.linear() and d3.svg.line() to create the xScale, yScale & line.

At the end, set d={line(data)} in the path element to create the chart.

Next we will add the Dots. Lets create another class named Dots. Here we shall pass the xScale & yScale as function and the dataset to calculate the position of the circle.
The below code is straight forward, let me know in case you have any questions.

Adding Axis & Grid:

In order to add the Axis and Grid we will use D3 to update the DOM since the calculation for setting up the those in React could be complex. So this time we will use D3 to draw them.

First add the following to the render() method of the LineChart class.

app.jsx

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

varyAxis=d3.svg.axis()

.scale(y)

.orient('left')

.ticks(5);

varxAxis=d3.svg.axis()

.scale(x)

.orient('bottom')

.tickValues(data.map(function(d,i){

if(i>0)

returnd.date;

}).splice(1))

.ticks(4);

varyGrid=d3.svg.axis()

.scale(y)

.orient('left')

.ticks(5)

.tickSize(-w,0,0)

.tickFormat("");

Then create the Axis & Grid classes. So here, we will call renderAxis() function from componentDidUpdate() and componentDidMount() method. in renderAxis() method we will call the ReactDOM.findDOMNode(this) to get the current dom element and then call d3.select(node).call(this.props.axis); function. Both the Axis and Grid classes are very same, in Grid class we will pass the yGrid function in the props and call the d3.select(node).call(this.props.grid);

Adding Tooltip:

Generally you can add a tooltip using simple html and css, however it would have some limitations. We will use a svg element in order to display the tooltip. onMouseOver and onMouseOut events from the Dots component should display the tooltip in this case. Let’s add the events in the circle element.

We will call a function during onMouseOver and onMouseOut events, which needs to be passed from the parent React Component (LineChart).

Since we want to display the data points for a specific circle, we will use html5data- element to define them in the circle element itself. I have added data-key and data-value here.

We need to add the showToolTip and hideToolTip function to the LineChart Component. We should be able to display the tool tip from anywhere in the SVG, not just from one component, that’s why we will define the event functions in the parent level. However let’s create the ToolTip component first, then we will add the showToolTip and hideToolTip function to the LineChart Component.

Here is the ToolTip class. It receives the tooltip object which has the boolean value to show/hide the component and the required data fields (data-key & data-value) which we had added in the circle elements. The show/hide property will be controlled by the visibility attribute of svg. We also need to calculate the location of the tooltip based on the x & y position of the circle component.

If you look closely, I am calculating the position so that the tooltip can be displayed either above or below the circle based on how many pixels are available. You can easily perform similar calculation for left and right position.

The polygon element would create the small arrow using a triangle.

NOTE : this implementation of the Tooltip is not complete and I will have a more reusable version available in future tutorials.

Now its time to add the showToolTip and hideToolTip function to the LineChart Component. We will use the event object here to capture the details from the calling circle element. First let’s set the fill to white. then set the tooltip object in this.state. In the hideToolTip function, we will reset the fill attribute and set the default tooltip to this.state. This will hide the tooltip.

app.jsx

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

showToolTip:function(e){

e.target.setAttribute('fill','#FFFFFF');

this.setState({tooltip:{

display:true,

data:{

key:e.target.getAttribute('data-key'),

value:e.target.getAttribute('data-value')

},

pos:{

x:e.target.getAttribute('cx'),

y:e.target.getAttribute('cy')

}

}

});

},

hideToolTip:function(e){

e.target.setAttribute('fill','#7dc7f4');

this.setState({tooltip:{display:false,data:{key:'',value:''}}});

}

We also need to update the getInitialState() function to add the default tooltip to this.state.

Add Responsiveness:

Now the only pending item we have is to add the Responsiveness to the chart. As I said before its very easy to make charts responsive. We already have the width defined as two way binding, now need to set the value of the width on browser resize event. We will use JQuery here to add the listener in componentWillMount and remove it in componentWillUnmount function. The updateSize function would get the current node and find the width using the JQuery width() function. Then we will set it in the this.state.width. This will trigger a re-render of the components.

We will also call the updateSize function from the componentDidMount in order to render the chart using current width. This will be helpful when your browser window width is already less then optimum.

app.jsx

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

componentWillMount:function(){

var_self=this;

$(window).on('resize',function(e){

_self.updateSize();

});

this.setState({width:this.props.width});

},

componentDidMount:function(){

this.updateSize();

},

componentWillUnmount:function(){

$(window).off('resize');

},

updateSize:function(){

varnode=ReactDOM.findDOMNode(this);

varparentWidth=$(node).width();

if(parentWidth<this.props.width){

this.setState({width:parentWidth-20});

}else{

this.setState({width:this.props.width});

}

}

Mixin:

This code is common across all the charts. So I have created a mixin and added the mixin in the React class.

Note : At the time of writing this article, React ES6 does not support mixin.

Donut Chart:

Lets look at the render function of the Donut Chart below. We will have one component to render the Paths and another for the Legends. We are having the data as two-way binding and onClick event we are calling updateData() function. In the demo, click on the DonutChart to update the data. This is default behavior using React’s two-way binding and no additional coding required.

We will add the ResizeMixin to add the responsiveness to the chart. However I have customized the DonutChartLegend class to render differently when the width of the pages changes. You can change the window size of the demo and see how the legends are changing position.

BarChart:

The BarChart is quite self explanatory, so I am not going in detail here. Please find the code below.

BarChart.jsx

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

varBarChart=React.createClass({

getDefaultProps:function(){

return{

width:300,

height:70,

chartId:'v_chart'

};

},

getInitialState:function(){

return{

width:0

};

},

mixins:[resizeMixin],

render:function(){

vardata=[

{month:'Jan',value:40},

{month:'Feb',value:50},

{month:'Mar',value:65},

{month:'Apr',value:60},

{month:'May',value:70},

{month:'Jun',value:55},

{month:'Jul',value:80},

{month:'Aug',value:55},

{month:'Sep',value:75},

{month:'Oct',value:50},

{month:'Nov',value:60},

{month:'Dec',value:75}

];

varmargin={top:5,right:5,bottom:5,left:5},

w=this.state.width-(margin.left+margin.right),

h=this.props.height-(margin.top+margin.bottom);

vartransform='translate('+margin.left+','+margin.top+')';

varx=d3.scale.ordinal()

.domain(data.map(function(d){

returnd.month;

}))

.rangeRoundBands([0,this.state.width],.35);

vary=d3.scale.linear()

.domain([0,100])

.range([this.props.height,0]);

varrectBackground=(data).map(function(d,i){

return(

<rect fill="#58657f"rx="3"ry="3"key={i}

x={x(d.month)}y={margin.top-margin.bottom}

height={h}

width={x.rangeBand()}/>

)

});

varrectForeground=(data).map(function(d,i){

return(

<rect fill="#74d3eb"rx="3"ry="3"key={i}

x={x(d.month)}y={y(d.value)}className="shadow"

height={h-y(d.value)}

width={x.rangeBand()}/>

)

});

return(

<div>

<svg id={this.props.chartId}width={this.state.width}

height={this.props.height}>

<gtransform={transform}>

{rectBackground}

{rectForeground}

</g>

</svg>

</div>

);

}

});

window.BarChart=BarChart;

Conclusion:

At the end, I will like to emphasis on some of the advantages we discussed earlier.

Reusability

By now you must be convinced that event single class/component created in React is reusable. This is a huge advantage when integrating D3 and react in enterprise level.

Update Chart

We haven’t actually written any code to update the chart. All we have done is to define the data in this.state to make it a two-way bindable. Rest has been taken care by react.

Responsiveness

Again, for this we added a mixin as a common code to all our charts and that did most of the tricks. Look into the BarChart.jsx and you won’t find any additional code to make the chart responsive. This is huge.

Even though I haven’t put much effort on the code quality, the code is very well structured and can be easily unit tested. You can also expose these components as reusable charts and keep adding more features to it.

I hope this tutorial on How to integrate React and D3 – The right way would help you get a solid understanding of how we can use D3 with React.

Related

Comments

Hey, Thanks for the tutorial. I am quiet new to both D3 and React in general and i am having issues in just drawing the line for step1. I follow the code base till return but not able to display data in my React app.

Hi Aditya,
You need to probably play with the viewBox attribute.I tried and it did work for me.Looks like you are trying to scale SVG using the viewBox attribute, which is bit different than being responsive. If you are looking for zoom option then fine, otherwise you probably want to control how the chart is being displayed, like change position of the legend, reduce data point or reduce tick value etc.

Hi A Developer Diary,
Thanks for the reply. It appears that the ticks or the axis is not being controlled correctly. The ticks which seems omitted in full screen appears when you shrink the window. I have tried playing the viewbox attribute and the bottom padding but somehow its not working optimally. Also either it is aligned centrally or the y axis moves out of the window.

Hi M,
If you are not using Chrome, I would suggest to use chrome and find whether you are getting any error in the console. Here we are using in browser jsx compiler so we don’t need to compile them before loading the page.

Quick update everything is working great. I just have one question, now I’m trying to use react with node.js to interact with mySQL. So if you have any examples that deal with such case it would be great to direct me to it. Also, I’m thinking of using AJAX to interact with node.js, is it a good approach ?

Hi, thank you for the tutorial I am implementing your code starting with the line chart into a react app I keep getting an error that says “h is not defined”. I am not sure where I am going wrong because I do have h defined.

Hi Lars,
I agree with you. I wanted to convey a different message however ended up with a controversial statement. I advocate Animation and Transition a lot and use them everywhere. Anyway what I wanted to point out really is, “However its probably best to avoid the Transitions altogether to start with, if they don’t provide a real business value to your use case.”

Again, thanks for pointing this out and giving me an opportunity to update the statement.

Great tutorial.
You mentioned the animation in the beginning of the tutorial, is that something you could show an example?
“D3 transitions functions cannot be used.” “You may try using React Animation however it will be a separate topic to discuss in future posts.Otherwise the only option is to use the core d3 library to perform the animation.”
I’m using the core d3 library, but I’m not sure how to add the animations to the component.

Thanks a lot. I think I have tried *every* way of making d3 work with React, and this was really the most intuitive and I got my bar chart done in less than 30 minutes.

Actually a bit more because D3 was upgraded to v4, and I had to make a few changes to scales to have it working again. Do you plan to upgrade your example in a new branch ? I could send a PR with my changes to the BarChart at least.

Hey I noticed that the third date along the bottom of the screen is wrong. It says Feb instead of Sun. I am curious why this is. I tried using different values than d.date but I get an error. any idea why we must use d.date in this situation? It tells me there is a problem with the transform if I try to change it to something else