Animated Charts in React Native using D3 and ART

A few months ago, React Native included D3 and ART libraries as part of its distribution but so far there is not much documentation about how to use them. In this post, I will share my learnings in creating different kinds of animated charts using only D3 and ART. I will try to keep it short, but I’m including links to references and source code for more information.

My example application looks like this:

Animated Pie and AreaSplines charts

If you just want to jump into the final code, you can access it from here: https://github.com/mdvacca/rn-d3-art-charts.

How D3 and ART works in React Native

Introduction

D3 is a JavaScript library for producing dynamic, interactive data visualizations. It is basically a way to convert data into rich graphs that can be rendered using vector libraries (svg, canvas, etc). D3 doesn’t render the graph itself, but it generates the information (coordinates or svg paths) required to render them using a vector library.

ART is a vector drawing API that knows how to render multiple vector outputs (canvas, svg, vml, etc). The react-native implementation of ART is not 100% complete yet (e.g. we can’t attach events to surfaces yet) but it can be used to generate powerful charts, like animated Pie charts, AreaSplines, Line, etc…

The full D3 API reference can be found here and the actual implementation of ART in react-native here.

Let’s render a Pie chart

  1. Import ART library

First, you need to import ART and D3 libraries into our project:

npm install art —-save
npm install d3 —-save
npm install d3-shape —-save

If you are using iOS, you also need to link the ART library into your project (you can follow the steps described here), this is required because ART is part of react native but it is linked by default to improve performance of project not using ART.

2. Use D3 to generate coordinates of our Pie Chart:

To create a Pie chart, we are going to use the library d3-shape that provides a variety of shape generators to create simple and complex graphs (like symbols, arcs, lines, areas, rounded annular sectors and centripetal splines). Each shape generator exposes accessors that control how the input data is mapped to a visual representation. The output of the shape generator is the path attribute of a svg shape object.

To render a Pie chart, we will need two kind of shapes: Pie Generator and an Arc Generator:

The pie generator does not produce a shape directly, but instead computes the necessary angles to represent a tabular dataset as a pie or donut chart; later these angles can be passed to an arc generator to generate the svg path of the shape.

For example if we have a dataset like this:

// data represents the distribution of spendings in a month
data = [
{"number": 8, "name": 'Fun activities'},
{"number": 7, "name": 'Dog'},
{"number": 16, "name": 'Food'},
{"number": 23, "name": 'Car'},
{"number": 42, "name": 'Rent'},
{"number": 4, "name": 'Misc'},
];

We can create a Pie using the d3-shape pie generator like this:

var arcs = d3.shape.pie()
.value(this._value)
(this.props.data);

This code will generate the corresponding angles (startAngle and endAngle) for each section of the pie, for example:

[{“data”:
{“number”:8,
”name”:”Fun activities”},
”index”:3,
”value”:8,
startAngle”:5.0893800988154645,
endAngle”:5.592034923389831,
”padAngle”:0
},
....
{"data":{"number":4,"name":"Misc"},"index":5,"value":4,"startAngle":6.031857894892402,"endAngle":6.283185307179586,"padAngle":0}]

The format of the data returned by the pie generator matches the one expected by the d3-shape arc generator.

The arc generator will take each of the angles of the arcs and it will generate the svg path that will be used by ART to render the pie later. This shape also provides parameters to customize the rendering of the shape. For example we can use different outer radius to highlight one section of the Pie.

// calculate the path for the pie's arc number [i]
var path = d3.shape.arc()
.outerRadius(this.props.pieWidth/2) // Radius of the pie
.padAngle(.05) // Angle between sections
.innerRadius(30) // Inner radius: to create a donut or pie
(arcs[i]);

Using the example data for the first item of the array, this code will return just a svg path:

M-68.9646319937036,-29.476762610114324A75,75,0,0,1,-49.345310456503256,-56.48044206582762L-20.635195356782273,-21.775874553905552A30,30,0,0,0,-27.086713440010442,-12.896121704557451Z

3. Render the D3 output using ART:

In the Step 2 we learned how to generate each of the svg paths of the Pie, now we will use ART to render them.

Before rendering anything, we need to create the root Surface container (“kind of <canvas> of html5”) that is going to be used by ART to render the shapes, lines and points of our graph. Since the Pie contains several sections and we want to group them, we will use the Group ART element.

Finally, we need to render the sections of the Pie. For this we are going to use the Shape class, this class not only renders svg path (using the prop d), but it also provides properties to customize how the graph will look. For example, we can change the stroke property to define the color of the shape and strokeWidth to change the width of the lines. Let’s see how to render the path generated in Step 2:

<Surface width={this.props.width} height={this.props.height}>
<Group x={x} y={y}>
<Shape
d={"M-68.9646319937036,-29.476762610114324A75,75,0,0,1,-49.345310456503256,-56.48044206582762L-20.635195356782273,-21.775874553905552A30,30,0,0,0,-27.086713440010442,-12.896121704557451Z"}
stroke={"#2ca02c"} // green line
strokeWidth={3}
/>
</Group>
</Surface>

This code will render the first section of the pie:

We are almost there, we just need to render all the svg paths calculated in Step 2 and we will have our first Pie chart:

<Surface width={this.props.width} height={this.props.height}>
<Group x={x} y={y}>
{
// pieChart has all the svg paths calculated in step 2)
pieChart.paths.map( (item, index) =>
(<Shape
key={'pie_shape_' + index}
fill={_getcolor(index)}
stroke={_getcolor(index)}

d={item.path}
/>))
}
</Group>
</Surface>

This code will render:

You can take a look to the full code of my project to see how to render labels and highlight sections.

Note that ART exposes more classes: LinearGradient, RadialGradient, Pattern, Transform, Path, Surface, Group, ClippingRectangle, Shape and Text.

Animated Shapes

Based on this excellent article written by Harry Wolff, we know that we can add animations to our charts using Morph.Tween(). The idea is to store into the state of the component an instance of Morph.Tween() that contains information about the old and new path of a shape. And in each step of the animation we move the tween by a delta. e.g.

import Morph from 'art/morph/path';
...
// Start of the animation:
this.setState({
path: Morph.Tween(
pathFrom, // OLD SVG path
pathTo, // NEW SVG path
),...

// Each step of the animation:
delta = (timestamp - start) / AnimationDurationMs;
this.state.path.tween(delta);

With this approach we can animate one Shape element, but in this case, we want to add multiple animations that needs to run at the same time for different kind of Shapes. That’s why I extended the same concept of creating a component called AnimShape. This component is a wrapper on top of Shape that animates the change of the state of a Shape

AnimShape contains the same props as Shape, but the property d expects a function (instead of a string). This function will be used by AnimShape to create the path of the Shape.

Now we can include animations to our Pie chart just replacing Shape by AnimShape like this:

<Group x={x} y={y}>
{
this.props.data.map( (item, index) =>
(<AnimShape
key={'pie_shape_' + index}
color={this._color(index)}
createPath={ () => this._createPieChart(index) }
/>)
)
}

This approach make it very simple to add animations for any kind of shapes. It is also a way to centralize the management of the animations. On the other hand, this way “might” have some performance issues when rendering many shapes because it is rendering multiple independent animations at the same time.

I hope this helps anyone trying to render native charts into React Native using D3 and ART.