ReactJS and SVG — Part One

Representing dynamic data using React and SVG

Jacob Fletcher
TRBL
Published in
7 min readMar 20, 2018

--

Abstract

It’s easy to design vector graphics for static data; open Adobe Illustrator, decide on some visual format, eye-ball some statistics, export for web. But what if the dataset is not immediately known? Or worse, the marketing and sales departments want to swap out the dataset altogether. After all, statistics are subject to change over time. Sure, we could interact with a third-party graphing API such as Google Charts — but we sacrifice flexibility in terms of both design and functionality. On the flip side, we could build something with our own two hands (or ten fingers, rather) without giving up our almighty omnipotence!

So, today we’ll look into the building blocks of a React component that can generate these graphical elements dynamically. This is especially useful when a client is given permission to arbitrarily create and manipulate datasets using a CMS (Content Management System), or when data is populated in some other manner on-the-fly.

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.

Prerequisites:

  1. Grasp of ES6 JavaScript syntax
  2. Experience with React programming techniques
  3. Some understanding of SVG (Scalable Vector Graphics)

We’ll be pasting in some prewritten CSS (Cascading Style Sheets) to give our component some visual style. This article will not cover CSS to any degree.

From a simple array of 21 numbers, to this vector graphic.

The Premise

Before we begin, let’s quickly abstract this out. As we stated before, the goal is for the client to be able to create some arbitrary dataset for our component to generate programatically. For this project, we’ll allow the client to create an array of 21 numbers, and then we’ll generate a line graph based on this array (see the above figure). These numbers could represent any statistic, it all depends on what the client seeks to communicate. Despite this, one thing we know for certain — this needs to be as straightforward as humanly possible for the client:

5, 30, -5, -10, 15, -15, 20, 5, 8, -12, -20, 2, 3, -5, 8, -2, 22, -30, -15, -35, -20

That’s it. Easy peasy. Seemingly random (or based on actual statistics); something any user can create with a basic understanding of integers. So how then do we take this simple set of numbers and generate our visual representation of them? Let’s learn together.

Full Speed Ahead

First Things First

So we have our client’s data, now let’s begin initializing our React components in their most basic form. We’ll work between two separate components, <Chart/> and <Graph/>. What we’re trying to accomplish through this parent-child relationship is a separation of concerns. The parent <Chart/> will directly handle the data and user interaction, while the child <Graph/> will simply receive the appropriate data, perhaps perform some additional logic, and then render the results accordingly. As our two components increase in complexity throughout the next two parts in this series, our initial foresight here will prove to be quite rewarding.

The Chart

Since this will need to make use of the component lifecycle (and in part two, internal state), we will write it as a class-based (smart) component. This should look very familiar if you’ve ever worked with React to any degree:

import React, { Component } from 'react';import Graph from './Graph';class Chart extends Component {
render() {
return (
<div className="chart-body">
<Graph />
</div>
)
}
}
export default Chart;

As you can see we’ve went ahead and added in some initial JSX (JavaScript XML) markup, just so that we have a DOM (Document Object Model) to work with. Notice how we’ve place-held the <Graph /> child component — let’s go ahead and initialize this before we move on as well.

The Graph

Since this component doesn’t perform any additional logic (yet), and since it doesn’t need access to the component lifecycle (yet), we can simply build it out as a functional (dumb) component:

import React from 'react';const Graph = (props) => {
return (
<svg>
<polyline />
<circle />
</svg>
)
}
export default Graph;

Note that this particular component is solely responsible for the SVG markup.

Connecting the Dots (Literally)

The Data

Since we’ll be representing our data using a line graph, we need to generate a single <polyline> with many <circle> elements within our <svg> node. An important rule to note here is that the points in a line graph can vary vertically, but will remain constant horizontally. This wouldn’t be true if we allowed for the scale of the line graph to be adjusted, but we’re not. This means that the client’s random, subject-to-change array will control the vertical Y plane, while a predefined, constant array will control the horizontal X plane. We already have the former (see “The Premise” above), so let’s go ahead create the latter:

If we assume a viewBox width of 1000 pixels and distribute 21 points evenly within this width, we’re left with:

0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000

So now that we’ve got ourselves two datasets, the varying array and the constant array, we can finally make use of them in our component. To do so, we’ll first assign them to a variable within the constructor of the <Chart/> component with the names arrayX and arrayY, and then we’ll pass them through to our <Graph/> via props of the same name:

import React, { Component } from 'react';import Graph from './Graph';import './index.css';class Chart extends Component {
constructor(props) {
super(props);
this.arrayX = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000];
this.arrayY = [5, 30, -5, -10, 15, -15, 20, 5, 8, -12, -20, 2, 3, -5, 8, -2, 22, -30, -15, -35, -20];
}
render() {
return (
<div className="chart-body">
<Graph
arrayX={this.arrayX}
arrayY={this.arrayY}
/>
</div>
)
}
}
export default Chart;

The Polyline

In order to put this data to use in our <Graph> component and build out our <polyline>, we first need to understand how the <polyline> coordinate system works. Every <polyline> consists of a comma-seperated list of X and Y values, relative to the viewbox. This is how each point of the line is positioned within the SVG node.

We’ve done the work to get both the X and Y values in their own array of the equal lengths, so now we just need to create a function to smash these values together in the format described above. Let’s also be sure to run this function immediately when the component is mounted:

componentWillMount() {
this.generatePolylineArray();
}
generatePolylineArray() {
let polyline = '';
this.arrayX.map((coordX, i) => {
return polyline += `${coordX}, ${this.arrayY[i]} `;
})
this.polyline = polyline;
}

output:

0,5 50,30 100,-5 150,-10 200,15 250,-15 300,20 350,5 400,8 450,-12 500,-20 550,2 600,3 650,-5 700,8 750,-2 800,22 850,-30 900,-15 950,-35 1000,-20

Now we just need to plug this string into the points attribute of the <polyline> element. To do so, we’ll simply pass it to the <Graph/> component alongside our original arrays via a polyline prop:

<Graph
arrayX={this.arrayX}
arrayY={this.arrayY}
polyline={this.polyline}
/>

The Circles

Let’s now finally hop over to our <Graph/> component to populate our <polyline> with these points and while we’re at it, generate our <circle> elements. The <circle> element requires only a single cx and cy attribute for each X and Y coordinate, respectively:

import React from 'react';const Graph = (props) => {
return (
<svg x="0px" y="0px" viewBox="0 0 1000 2">
<polyline points={props.polyline} />
{props.arrayX.map((coordX, i) => {
return (
<circle
key={i}
cx={coordX}
cy={props.arrayY[i]}
r={4}
/>
)
})}
</svg>
)
}
export default Graph;

The only other required attribute for each circle is a radius length in pixels, by means of the r attribute. We went with 4, but you can choose any.

Part one: check.

There is much more room we can do to improve this project that I cover in the next parts of this series such as multiple, navigable graphs and animation to help improve usability.

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.

Something that was not covered in this tutorial is making an asynchronous request to a RESTful API to retrieve the client’s data that our component works with. Traditionally, I would use something like Express to process a GET request and serve a JSON object in response. But, for the sake of a highly-focused lesson, you will need to implement this yourself.

--

--