React & Data Visualization
03 Jun 2015This post is based on the talk I gave about React & Data Vsiualization on a React Meetup in Shanghai on 05/30. And the code sample I used here is in react-data-visualization
This post is not about
- React tutorial
- Data visualisation tutorial
- D3 introduction
Process of Data Visualization
- Analysis data
- Data aggregation
- Visualize data(rendering)
How With D3?
D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG, and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.
Here I will give an example of using the speaker votes data from this year’s JSConf China, which simly contain the name and votes count.
name votes
Thomas Gorissen 56
Tomasz Janczuk 25
Brian Holt 59
Mikael Karon 29
Mathias Buus 35
Chriest Yu 28
Evan You(尤雨溪) 249
Eyal Arubas 30
雷宗民 82
Prepare HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Demo</title>
<script type="text/javascript" src="../node_modules/d3/d3.min.js"></script>
</head>
<body>
<script>
</script>
</body>
</html>
Append SVG
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Set Scale
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%");
Load Data
d3.tsv("../data/votes.tsv", type, function(error, data) {
// do something here with data
// set domain
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.votes; })]);
});
Append Bar
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.votes); })
.attr("height", function(d) { return height - y(d.votes); });
Append Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("votes");
Result
How with React?
Now we know the basic step of using D3.js to do Data Visualization, let’s try to do it with React. The steps are still much similar, analysis data and transform data, then use React to render it.
Three simple guidelines
- One source of truth
- Stateless all the things
- Don’t make to many assumptions
A typical React App
'use strict';
import React from 'react';
class App extends React.Component {
displayName: 'App'
constructor() {
super();
this.state = {
name: 'Fraser Xu'
};
}
render() {
let { name } = this.state;
return (
<h1>Hi {name}</h1>
)
}
}
export default App;
What We Want With React
I like using React because everything I use is a component, that can be any component writen by myself in the project or 3rd party by awesome people on NPM. When we want to use it, just import
or require
it, and then pass in the data, and we get the visualization result.
constructor() {
super();
this.state = {
data: [1, 2, 3, 4, 5]
};
}
render() {
let { data } = this.state;
return (
<ChartComponent data={data} />
)
}
The Chart Component
Now we have the basic structure, what we need to do next is to implement our own chart component.
But before we start, let’s check what choice we have in terms of making graphs in React.
With D3
- Low level API
- Flexible code structure
- Easy customisation
- Lots of code
With High Level Chart Library
- High level API
- Ready for use style
- Hard to customisation
Let’s start with using D3.
A chart component With D3
'use strict';
import React from 'react';
import d3 from 'd3';
class Barchart extends React.Component {
displayName: 'Barchart'
constructor(props) {
super(props);
}
componentDidMount() {
this.renderBarChart();
}
renderBarChart() {
// render chart
}
render() {
return (
<svg />
)
}
}
Barchart.defaultProps = {
width: 800,
height: 200,
fillColor: '#d70206'
}
export default Barchart;
What we need to do is the rederBarChart
function. Here we have a problem again. Both React
and D3
need to own the DOM, but how do we decide which one to use.
Use D3 build DOM
- Powerful D3 functions and layout available for use
- Lost control of DOM by React
- No Virtual DOM
Use SVG build DOM
- Virtual DOM
- Lost built-in D3 update and animation
The D3 way redenering logic
let { width, height, fillColor, data } = this.props;
let values = data.slice();
let yScale = d3.scale.linear()
.range([height, 0]);
yScale.domain([0, Math.max.apply(null, values)]);
let svg = React.findDOMNode(this);
let chart = d3.select(svg)
.attr('width', this.props.width)
.attr('height', this.props.height + 1);
let barWidth = width / values.length;
let bar = chart.selectAll('g')
.data(values)
.enter().append('g')
.attr('transform', (d, i) => `translate(${i * barWidth}, 0)`);
bar.append('rect')
.attr('y', (d) => yScale(d))
.attr('height', (d) => height - yScale(d))
.attr('width', (d) => barWidth - 1)
.attr('fill', fillColor);
The React(SVG) way redenering logic
A Chart component that wrap up the SVG.
'use strict';
import React from 'react';
class Chart extends React.Component {
displayName: 'Chart'
constructor(props) {
super(props);
}
render() {
return (
<svg width={this.props.width} height={this.props.height}>{this.props.children}</svg>
)
}
}
export default Chart;
A rect bar component
'use strict';
import React from 'react';
class Bar extends React.Component {
displayName: 'Bar'
constructor(props) {
super(props);
}
render() {
return (
<rect fill={this.props.color}
width={this.props.width} height={this.props.height}
x={this.props.offset} y={this.props.availableHeight - this.props.height} />
)
}
}
Bar.defaultProps = {
width: 800,
height: 200,
fillColor: '#d70206'
}
export default Bar;
Together with data
'use strict';
import React from 'react';
import d3 from 'd3';
import Bar from './Bar';
class DataSeries extends React.Component {
displayName: 'DataSeries'
constructor(props) {
super(props);
}
render() {
let { data, width, height, color } = this.props;
let yScale = d3.scale.linear().domain([0, d3.max(data)])
.range([0, height]);
let xScale = d3.scale.ordinal().domain(d3.range(data.length))
.rangeRoundBands([0, width], 0.05);
let bars = this.props.data.map((point, i) => {
return (
<Bar
height={yScale(point)}
width={xScale.rangeBand()}
offset={xScale(i)}
availableHeight={height}
color={color}
key={i}
/>
)
});
return (
<g>{bars}</g>
);
}
}
export default DataSeries;
Finally the chart!
'use strict';
import React from 'react';
import Chart from './Chart';
import DataSeries from './DataSeries';
class App extends React.Component {
displayName: 'App'
render() {
return (
<Chart width={this.props.width} heigh={this.props.heigh}>
<DataSeries
data={[ 30, 10, 5, 8, 15, 10 ]}
width={this.props.width}
height={this.props.height}
color="cornflowerblue"
/>
</Chart>
)
}
}
React.render(<App width={600} height={800} />, document.body);
How with high level libraries?
Since we can already do Data Visualization with React and D3, why should I still bother with high level libraries. The reason is clear, it’s simple to.
I wrapped the Chartist.js library and made this react-chartist component available on NPM.
First you just need to install with npm install react react-chartist --save
'use strict';
import React from 'react';
import ChartistGraph from 'react-chartist';
class App extends React.Component {
displayName: 'App'
render() {
let data = {
labels: ['W1', 'W2', 'W3', 'W4', 'W5'],
series: [
[1, 2, 4, 8, 6]
]
};
let options = {
high: 10,
axisX: {
labelInterpolationFnc: function(value, index) {
return index % 2 === 0 ? value : null;
}
}
};
let type = 'Bar'
return (
<div>
<ChartistGraph data={data} options={options} type={type} />
</div>
)
}
}
React.render(<App />, document.body);
That’s it. This approach works well for most case in our product. But once you have really complex Data Visualization to be impletment, you still have to use D3.
Disclaimer
This post is not telling you that you should use React
and D3
together, but a way to demostrate that the possibility of using them together and potentially help speed up your development workflow.
Conclusion
Choose the way you like!
Further reading
The following links are what I checked while preparing this post and you should check them out.