React &
Data Visualization
@fraserxu
Wiredcraft
This talk is *not* about
- React tutorial
- Data visualisation tutorial
- D3 introduction
React
A JavaScript library for building user interfaces
Process of Data Visualization
- Analysis data
- Data aggregation
- Visualize data(rendering)
Raw Data
Data aggregation
d3.csv('data/shanghai_air.csv', function(data) {
d3.select('#area')
.on('change', function() {
redraw(data, this.value)
})
.selectAll('option')
.data(data
.map(function(d) { return d['监测点'] })
.filter(function(e, i, arr) {
return arr.lastIndexOf(e) === i
}))
.enter().append('option')
.attr('value', function(d) { return d })
.attr('selected', function(d) { if (d === '') return true})
.text(function(d) {
if (d === '') {
return '全市'
} else {
return d
}
})
redraw(data)
})
Visualize data(rendering)
How?
What?
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.
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/letter.tsv", type, function(error, data) {
// do something here with data
// set domain
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
});
Append Bar
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
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("Frequency");
How?
Three simple guidelines
- One source of truth
- Stateless all the things
- Don't make to many assumptions
A 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
'use strict';
import React from 'react';
import ChartComponent form './ChartComponent';
class App extends React.Component {
displayName: 'App'
constructor() {
super();
this.state = {
data: [1, 2, 3, 4, 5]
};
}
render() {
let { data } = this.state;
return (
<ChartComponent data={data} />
)
}
}
export default App;
With D3
- Low level API
- Flexible code structure
- Easy customisation
- Lots of code
With Chart Library
- High level API
- Ready for use style
- Hard to customisation
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;
Use D3 build DOM
- Powerful D3
- 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);
How to use
'use strict';
import React from 'react';
import Barchart from './Barchart';
class App extends React.Component {
displayName: 'App'
render() {
let data = {
series: [
[1, 2, 4, 8, 6]
]
};
return (
<div>
<Barchart data={data.series[0]} />
</div>
)
}
}
React.render(<App />, document.body);
The React(SVG) way redenering logic
'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;
'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;
'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;
'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);
With chart Library(react-chartist)
'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);
Disclaimer
Conclusion
Choose the way you like!
Further reading
QA?
deck
By fraser xu
deck
- 3,941