How to create reusable charts with React and D3 Part3

Welcome to the How to create reusable charts with React and D3 Part3, this would be the final part of our series. We will first focus on adding the Donut Chart, then Stacked Bar Chart. Afterwards we will make the range filter (date range) to reload data. This last part should be simple and easier to follow along.

Create Donut Chart

The definition of the Donut chart would be very simple. We will define few attribute like custom colorscale, enable3d etc. We will also have a child legend element to add the legends.

There are three section to the Donut Chart and we will create a React Component for all of them.

DonutChartShadow

DonutChartPath

DonutChartLegend

Create DonutChartShadow

We can provide a 3d view of the chart by adding a shadow. We will dynamically calculate the color of the shadow so that once you set a color scale, your shadow would change automatically with it. I am using d3.hsl() built-in function to generate the shadow colors. Here is the code.

JavaScript

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

varDonutChartShadow=React.createClass({

propTypes:{

width:React.PropTypes.number,

height:React.PropTypes.number,

data:React.PropTypes.array,

pie:React.PropTypes.func,

color:React.PropTypes.func,

innerRadiusRatio:React.PropTypes.number,

shadowSize:React.PropTypes.number

},

getDefaultProps:function(){

return{

shadowSize:10

};

},

componentWillMount:function(){

varradius=this.props.height;

varouterRadius=radius/this.props.innerRadiusRatio+1;

varinnerRadius=outerRadius-this.props.shadowSize;

this.arc=d3.svg.arc()

.outerRadius(outerRadius)

.innerRadius(innerRadius);

this.transform='translate('+radius/2+','+radius/2+')';

},

createChart:function(_self){

varpaths=(this.props.pie(this.props.data)).map(function(d,i){

varc=d3.hsl(_self.props.color(i));

c=d3.hsl((c.h+5),(c.s-.07),(c.l-.10));

return(

<path fill={c}d={_self.arc(d)}key={i}></path>

)

});

return paths;

},

render:function(){

var paths = this.createChart(this);

return(

<g transform={this.transform}>

{paths}

</g>

)

}

});

Create DonutChartPath

The Path and Shadow components are almost alike and we should combine them in one component. I have kept them separate here for simplicity however you should combine them.

JavaScript

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

varDonutChartPath=React.createClass({

propTypes:{

width:React.PropTypes.number,

height:React.PropTypes.number,

data:React.PropTypes.array,

pie:React.PropTypes.func,

color:React.PropTypes.func,

innerRadiusRatio:React.PropTypes.number

},

componentWillMount:function(){

varradius=this.props.height;

varouterRadius=radius/2;

varinnerRadius=radius/this.props.innerRadiusRatio;

this.arc=d3.svg.arc()

.outerRadius(outerRadius)

.innerRadius(innerRadius);

this.transform='translate('+radius/2+','+radius/2+')';

},

createChart:function(_self){

varpaths=(this.props.pie(this.props.data)).map(function(d,i){

return(

<path fill={_self.props.color(i)}d={_self.arc(d)}key={i}></path>

)

});

return paths;

},

render:function(){

var paths = this.createChart(this);

return(

<g transform={this.transform}>

{paths}

</g>

)

}

});

Create DonutChartLegend

Here is the code to generate the legends. We are controlling the visibility of the legends based on the width of our chart. In case it narrows, we will hide the legends.

Create Stacked Bar Chart

In case you are new to Stack Chart, I would recommend you to go through my earlier post on creating Stacked Bar Chart. This tutorial provides step by step guidance on how to create a stacked bar chart.

Here is our StackChart JSX element. We will define data, axis and grid. Based on the data it will automatically plot the chart. We will have a special two color scheme mode to have each bar a different color. Otherwise each category will be having a different color.

JavaScript

1

2

3

4

5

6

<StackChart data={this.state.dataBar}xData="month"margin={margin}

id="stacked-bar"keys={keys}color={color}twoColorScheme={true}>

<yGrid orient="left"className="y-grid"ticks={5}/>

<xAxis orient="bottom"className="axis"ticks={5}/>

<yAxis orient="left"className="axis"ticks={5}/>

</StackChart>

In the createChart() function we will define the d3.layout.stack() and transform our JSON data. We will also create the xScale and yScale.

The render() function should look familiar. We are first creating all the elements (axis & grid), however we are adding the bars separately. We need to go though the stacked array and create the rect element. We have our twoColorScheme defined here to change the color accordingly.

Add Range Filter

We will finish our Dashboard by adding the range filter functionality. We need to modify the Range Component. We have one Range component in each charts and a global one on top. We should refresh all the data by clicking on the global one, however the Chart’s header range only should impact the specific chart’s data.

We have two states, 7 Day and 30 Days. So need a boolean(defaultSelection) to hold the state of this to values, false indicates 7 Days.

If we call the global range as master, then if master =true we shall generate an event to have all the listeners (Charts) reset itself.

In case the master=false, then we shall call the function named loadData passed as prop by passing the defaultSelection value.

Here is the code of the Range Class.

JavaScript

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

varRange=React.createClass({

propTypes:{

loadData:React.PropTypes.func,

defaultSelection:React.PropTypes.bool,

master:React.PropTypes.bool

},

getDefaultProps:function(){

return{

defaultSelection:false,

master:false

};

},

getInitialState:function(){

return{

defaultSelection:false

};

},

componentWillReceiveProps:function(newProps){

if(newProps.defaultSelection!=this.state.defaultSelection){

this.setState({defaultSelection:newProps.defaultSelection});

}

},

componentWillMount:function(){

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

},

toggleSection:function(){

if(this.props.master){

eventEmitter.emitEvent("reload",[!this.state.defaultSelection]);

}else{

this.props.loadData(!this.state.defaultSelection);

}

this.setState({defaultSelection:!this.state.defaultSelection});

},

selectColor:function(){

if(this.state.defaultSelection){

this.fill7='#e58c72';

this.fill30='#8f8f8f';

}else{

this.fill30='#e58c72';

this.fill7='#8f8f8f';

}

},

render:function(){

this.selectColor();

return(

<div onClick={this.toggleSection}className="filter-selection">

<span className="range-span">

<svg width="10"height="10">

<circle cx="5"cy="5"r="5"fill={this.fill7}/>

</svg>

<span className="padding-left-5">7days</span>

</span>

<span className="range-span">

<svg width="10"height="10">

<circle cx="5"cy="5"r="5"fill={this.fill30}/>

</svg>

<span className="padding-left-5">30days</span>

</span>

</div>

);

}

});

So while defining the Range JSX element from each Chart’s header, we will pass a function which would reload the data for that chart as the loadData prop. Our random data generation code would go inside this reload (in this case reloadBarData()) function.

In the MainRangeSelection, which is the global one, we just set the master prop value to true.

1

2

3

4

5

6

7

8

9

10

11

varMainRangeSelection=React.createClass({

render:function(){

return(

<div className="row range-custom">

<div className="range-custom-child">

<Range master={true}/>

</div>

</div>

);

}

});

We will use EventEmitter to create our custom events. When the Range class emits the event named reload (below is the snippet), we can listen to it and update all the charts as required.

1

2

3

4

5

6

7

8

9

toggleSection:function(){

if(this.props.master){

eventEmitter.emitEvent("reload",[!this.state.defaultSelection]);

}else{

this.props.loadData(!this.state.defaultSelection);

}

this.setState({defaultSelection:!this.state.defaultSelection});

},

We need to add the addListener() and removeListener() method to the componentWillMount and componentWillUnmount respectively. We should implement this not just for the charts, but for the cards as well. We probably want to create a mixin for this as well.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

componentWillMount:function(){

this.reloadBarData();

this.reloadPieData();

eventEmitter.addListener("reload",this.reloadData);

},

componentWillUnmount:function(){

eventEmitter.removeListener("reload",this.reloadData);

},

reloadData:function(defaultValue){

this.reloadBarData(defaultValue);

this.reloadPieData(defaultValue);

},

Here is the final version of the code.

Final demo.

Conclusion

In this series we have go through many important topics of both React and D3. We have created reusable, customizable , extensible , responsive and configurable D3 charts in React.We have use this.props.children to define our components. The EventEmitter is a very helpful lightweight library. We have broken down our components into multiple reusable React classes and used them across the demo. At the end I hope this articles will help you to understand the concepts on How to create reusable charts with React and D3. Feel free to post your feedback, suggestions or questions in the comment section.