ReactJS and SVG — Part Three

Representing dynamic data using React and SVG — Multiple Graphics for Relational Data

Jacob Fletcher
TRBL
Published in
5 min readMar 20, 2018

--

This is the third of three parts on creating a visual representation of dynamic data. In the first part, we simply generated an SVG-based line graph from a single array of values. In the second part, we enhanced our component to handle multiple datasets as opposed to just one. In this part, we’ll give everything motion through the use of native animations.

For your convenience, I’ve created a GitHub repository for each part in this series. I’d encourage you to clone this project into your local environment and get familiar with it before continuing on.

This part is a direct progression from the last, so read it!

The Premise

So we have now have multiple line graphs being programmatically generated from data that the client controls. Everything is hooked up, and in working order. We’ve even got a navigational menu that the user can toggle, complete with labels to add context and a pleasing-to-the-eye color palette. The missing piece? Motion. This will give the user some visual queues as to which action they’ve just performed, ultimately improving the user’s experience.

SVG has a native <animate/> element that we will soon learn how to use. The reason we are opting for this approach is because SMIL animations are becoming obsolete, and there are serious limitations with JavaScript in the SVG context.

<animate/>

The <animate/> element is pretty straight-forward — nest it within any SVG element that you wish to animate. You then configure the animation of the parent element through its own set of attributes: attributeName, attributeType, from, to, begin, dur, and fill. For highly technical details, read the MDN documentation.

So we need to animate both the <polyline/> and the <circle/> elements — we need to go from one point to the next, within a certain duration. This means that we’ll need to keep track of both our last points and our next points. Let’s begin by initializing these variables in the constructor of our <Chart/> component:

this.lastArrayY = [];
this.lastPolyline = [];
this.nextPolyline = [];

The Data

Now we need to update our functions to track all the information we need. Let’s start with when the component first mounts — since there is no prior data to animate from, we just need to set it to be equal whatever array is active on startup:

componentWillMount() {
this.lastArrayY = this.state.menu[this.currentIndex].array;
this.generatePolylineArray();
}

Then we’ll update our generatePolylineArray function in order to save our last the lastPolyline as well as the nextPolyline. Not much has to change here:

generatePolylineArray() {
this.lastPolyline = this.nextPolyline;
let polyline = '';
const currentArrayY = this.state.menu[this.currentIndex].array;
this.arrayX.map((coordX, i) => {
return (this.arrayX.length === i) ? polyline += coordX : polyline += `${coordX},${currentArrayY[i]} `;
})
this.nextPolyline = polyline;
}

Finally, we’ll add just one line to our handleClick function in order to save our lastArrayY:

handleClick(key) {
this.currentIndex = key;
this.generatePolylineArray();
const newState = [...this.state.menu];
for (var i = 0; i < newState.length; i++) {
if (newState[i].active) {
this.lastArrayY = newState[i].array;
newState[i].active = false;
}
}
newState[key].active = true;
this.setState({newState});
}

There we go! Now we’re tracking the last and next polyline as well as the lastArrayY. So just pass these down to the <Graph/> component via props:

<Graph
active={menu[this.currentIndex].active}
lastArrayY={this.lastArrayY}
nextArrayY={menu[this.currentIndex].array}
arrayX={this.arrayX}
lastPolyline={this.lastPolyline}
nextPolyline={this.nextPolyline}
/>

The Graph

Okay, we’ve got some inherited props to work with! Let’s put it all together. Firstly, let’s re-read what we learned above about the <animate/> element:

The <animate/> element is pretty straight-forward — nest it within any SVG element that you wish to animate. You then configure the animation of the parent element through its own set of attributes: attributeName, attributeType, from, to, begin, dur, and fill. For highly technical details, read the MDN documentation.

We’re looking to animate both the <polyline/> and the <circle/> elements — so we need to go from one point to the next, within a certain duration. Well, we’ve done all of the hard work so now its just a matter of plugging it into the correct places:

<polyline points={this.props.lastPolyline} >
<animate
attributeName="points"
attributeType="XML"
from={this.props.lastPolyline}
to={this.props.nextPolyline}
begin=".01s"
dur="4s"
fill="freeze"
/>
</polyline>

The same is true for the <circle/> elements:

{props.arrayX.map((coordX, i) => {
return (
<circle
key={i}
cx={coordX}
cy={this.props.lastArrayY[i]}
r={this.radius}
>
<animate
attributeName="cy"
attributeType="XML"
from={this.props.lastArrayY[i]}
to={this.props.nextArrayY[i]}
begin=".01s"
dur="4s"
fill="freeze"
/>
</circle>
)
})}

Notice that we’ve implemented the begin attribute, and we’ve set it to .01s — this is due to something called the animation timeline in which the SVG <animate/> element is bound to. Since we’re looking to perform a brand new animation every time the user clicks we need to reset the browser’s timeline every time our props change so that our begin value will be reached and ultimately trigger the animation.

But, we don’t have access to the component’s lifecycle unless we first convert it from a function to a class. While we’re at it, let’s establish a reference to our SVG node via the ref prop so that we can access its native methods.

class Graph extends Component {
componentWillReceiveProps(nextProps) {
if (this.props.nextArrayY !== nextProps.nextArrayY) {
this.svgRef.setCurrentTime(0);
}
}
[...] return (
<svg ref={(svg) => {this.svgRef = svg}}
[...]

For more information about the animation timeline, read the MDN documentation.

Check, and mate.

If you actually made it to the end, with success, I applaud you. This is my first tutorial after many years of reaping the benefits of others’. Unfortunately, I wasn’t able to cover literally every line in this project, something really only a video series can do effectively — but if you cloned the corresponding repositories and followed along closely, then hopefully you weren’t left too much in the dark.

Where do we go from here? There is honestly even more we can do to enhance this component, like display a tooltip on hover with more specific details related to that respective datapoint, or even make the datapoint draggable so that the client can edit the dataset directly on the front-end. If this series draws a high demand for follow-up, I may consider publishing this down the road.

Thanks for reading! If you found value in this series whatsoever, please applaud or even comment — I’d love to chat.

Or on the off-chance you’re feeling burned out from this topic, check out James Mikrut’s post about our studio’s new website. I know your time is valuable but you have my word, this is a great way to spend it.

--

--