data visualization for react developers
Shirley Wu
Workshop goal
Use D3 to calculate data
React to render visualizations
why?
D3's learning curve:
enter-update-exit
==
React's virtual DOM
(let React do the hard work!)
My Fem workshops
Data Visualization
for React Developers
↓
Intro to D3
↓
Building Custom
Data Visualizations
Basic chart types
and when to use them
↓
The making of
a chart with SVG
↓
Going from data
to SVG shapes
↓
Using React to
render SVG and Canvas
↓
Exceptions and
finishing touches
data types
- Categorical (genres)
- Ordinal (t-shirt sizes)
- Quantitative (temperatures)
- Temporal (dates)
- Spatial (cities)
Bar chart
For categorical comparisons
Domain: categorical
Range: quantitative
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5133372/bar.png)
basic charts
and when to use them
basic charts
and when to use them
Histogram
For categorical distributions
Domain: quantitative bins
Range: frequency of quantitative bin
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5133373/histogram.png)
Scatterplot
For correlation
2 attributes and the relationship between their quantitative values
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5133374/scatter.png)
basic charts
and when to use them
Line chart
For temporal trends
Domain: temporal
Range: quantitative
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5133376/line.png)
basic charts
and when to use them
Tree
For hierarchy
Parent-child relations
Multiple tiers of category
basic charts
and when to use them
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5138894/cluster.png)
Node-link diagram
For connection
Relationship between entities
basic charts
and when to use them
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5138917/network.png)
Chloropleth
For spatial trends
Domain: spatial regions
Range: quantitative
basic charts
and when to use them
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5138887/chloropleth.png)
Best for:
- Regional patterns
- Only one variable
- Relative data (normalize for population)
Not good for:
- Subtle differences in data
(Source: Datawrapper)
Pie chart
For hierarchical part-to-whole
Best for:
- When values are around 25%, 30%, or 75%
- 3 or 4 values
Not good for:
- Comparing fine differences
- Multiple totals
basic charts
and when to use them
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141017/pie.png)
(Source: Datawrapper)
What we'll be
building today
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5139078/building.png)
SVG Elements
rect
x: x-coordinate of top-left
y: y-coordinate of top-left
width
height
circle
cx: x-coordinate of center
cy: y-coordinate of center
r: radius
text
x: x-coordinate
y: y-coordinate
dx: x-coordinate offset
dy: y-coordinate offset
text-anchor: horizontal text alignment
Hi!
path
d: path to follow
Moveto, Lineto, Curveto, Arcto
SVG coordinate system
0,0
x
y
25,50
125,100
making of a
bar chart
365 <rect />'s
- x: date
- y: high temp
- height: low - high temp
- fill: average temp
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5140511/Screenshot_2018-07-27_12.46.53.png)
making of a
line chart
2 <path />'s
- d: line commands that connect points
- for each point
x: date
y: temperature - fill: red for high temp
blue for low temp
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5140512/Screenshot_2018-07-27_12.46.43.png)
making of a
radial chart
365 <path />'s
- d: line + curve commands to make slices
- for each slice
angle: day
inner radius: low temp
outer radius: high temp - fill: average temp
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5140513/Screenshot_2018-07-27_12.47.02.png)
making of a chart
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/2022862/Screen_Shot_2015-12-02_at_10.10.31_AM.png)
Play with the SVG elements!
(Observable notebook)
going from data
to svg shapes
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5140698/d3.png)
One of the (many) things
D3 is good for!
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5140716/Screenshot_2018-07-27_00.18.44.png)
rect
x: x-coordinate of top-left
y: y-coordinate of top-left
width
height
data to Svg:
bar chart
data to svg:
Scales
d3.scaleLinear()
.domain([min, max]) // input
.range([min, max]); // output
scale: mapping from
data attributes (domain)
to display (range)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639158/scale.jpg)
date → x-value
value → y-value
value → opacity
etc.
// get min/max
var width = 800;
var height = 600;
var data = [
{date: new Date('01-01-2015'), temp: 0},
{date: new Date('01-01-2017'), temp: 3}
];
var min = d3.min(data, d => d.date);
var max = d3.max(data, d => d.date);
// or use extent, which gives back [min, max]
var extent = d3.extent(data, d => d.temp);
var xScale = d3.scaleTime()
.domain([min, max])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain(extent)
.range([height, 0]);
data to svg:
Scales
data to svg:
Scales i use often
Quantitative | Continuous domain Continuous range |
scaleLinear scaleLog scaleTime |
Continuous domain Discrete range |
scaleQuantize | |
Categorical | Discrete domain Discrete range |
scaleOrdinal |
Discrete domain Continuous range |
scaleBand |
data to svg:
exercise
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
data to Svg:
line chart
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5140715/Screenshot_2018-07-26_23.54.34.png)
path
d: path to follow
Moveto, Lineto, Curveto, Arcto
var data = [
{date: '2007-3-24', value: 93.24},
{date: '2007-3-24', value: 95.35},
{date: '2007-3-24', value: 98.84},
{date: '2007-3-24', value: 99.92},
{date: '2007-3-24', value: 99.80},
{date: '2007-3-24', value: 99.47},
…
];
var line = d3.line()
.x((d) => { return xScale(new Date(d.date)); })
.y((d) => { return yScale(d.value); });
line(data)
Input: array of objects
Output: string that can be used in path's d attribute
M5,19.0625L13,21.875L21,12.03125
L29,12.03125L37,23.28125L45,26.09375
L53,7.8125L61,5L69,12.03125L77,10.625
L85,13.4375L93,21.875L101,14.84375
L109,13.4375L117,21.875L125,19.0625
L133,21.875L141,17.65625L149,13.4375L157,16.25
L165,16.25L173,14.84375L181,19.0625L189,19.0625
L197,16.25L205,16.25L213,13.4375
L221,12.03125L229,10.625L237,10.625L245,12.03125
data to Svg:
d3.line()
data to svg:
exercise
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
data to Svg:
radial chart
path
d: path to follow
Moveto, Lineto, Curveto, Arcto
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5140717/Screenshot_2018-07-27_00.19.15.png)
var pie = {
"data": 1, "value": 1,
"startAngle": 6.050474740247008,
"endAngle": 6.166830023713296,
};
var arc = d3.arc()
.innerRadius(0)
.outerRadius(100)
.startAngle(d => d.startAngle)
.endAngle(d => d.endAngle);
arc(pie);
// M-23.061587074244123,-97.30448705798236A100,100,0,0,1,-11.609291412523175,-99.32383577419428L0,0Z
data to Svg:
d3.arc()
data to svg:
exercise
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
Let's break down the D3 API to just what we care about ✌️
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
I usually end up using native array functions or lodash for these
(it depends on what you feel comfortable with!)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
But some functions are really helpful for getting data ready for D3's scale/shape/layout functions
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
I use this a lot for getting data into what SVG needs to draw
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
These are really great dataviz-specific interactions that I use frequently
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
What we'll use React for
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
REACT RENDERS:
architecture
Division of responsibilities:
-
Chart component
-
Gets passed in raw data as prop
-
Translates raw data to screen space
-
Renders the calculated data
-
Manages state for interactions that don’t require redrawing of the chart (hover, click)
-
-
Root component
-
Manages updates to raw data
-
Manages state for interactions that require redrawing of charts (filter, aggregate, sort, etc.)
-
REACT RENDERS:
architecture
Where to calculate data:
-
getDerivedStateFromProps
-
Pro: simple and straightforward
-
Con: asynchronous, race conditions if not careful
-
-
Render
-
Pro: very straightforward
-
Con: will recalculate on every lifecycle
-
-
componentDidMount & componentDidUpdate
-
Pro: no race condition
-
Con: less straightforward
-
REACT RENDERS:
architecture
Assumes:
- React manages state (no redux or similar)
- Multiple charts that all share some part of the data or state
Main takeaway:
- D3 calculations can go anywhere (that makes sense for your project) as long as React can access it in its render function
REACT RENDERS:
exercise
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
Draw a bar chart!
react renders:
exercise
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
Draw a radial chart!
react renders:
architecture
componentDidUpdate(nextProps, nextState) {
// prevents infinite loop
if (this.props.someId !== nextProps.someId) {
this.calculateData();
}
}
componentDidMount() {
// Make sure component is rendered first
if (this.SVG.current) {
this.calculateData();
}
}
calculateData() {
...
this.setState({data})
}
(Adapted from code by Jhon Paredes)
Functions where D3 needs access to the DOM
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141033/d3_api_v2.png)
D3 renders
- Instantiate D3 function in componentDidMount
- Create <g /> container in render
- Place D3 code in componentDidMount and/or componentDidUpdate
**Never ever let D3 and React manage same parts of the DOM!
OR BUGS!!
D3 renders:
axes
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639376/Screenshot_2017-03-27_20.23.18.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639377/Screenshot_2017-03-27_20.22.24.png)
Axes are very important in making the data readable, and D3 makes it easy.
D3 renders:
axes
- Create axisLeft or axisBottom at beginning of React lifecycle and set corresponding scale
- Create an SVG group element in render
- Call axis on the group element in componentDidUpdate
const yAxis = d3.axisLeft()
.scale(yScale);
<g ref='group' />
d3.select(this.refs.group)
.call(yAxis);
d3 renders:
exercise
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
Create axes for the bar chart!
d3 renders:
transitions
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141961/temp_animate.gif)
d3 renders:
transitions*
*It works, it's performant, but the code is ugly. I don't highly recommend it.
// in componentDidUpdate
d3.select(this.refs.bars)
.selectAll('rect')
.data(this.state.bars)
.transition()
.attr('y', d => d.y)
.attr('height', d => d.height)
.attr('fill', d => d.fill);
// in render
<g ref='bars'>
{this.state.bars.map((d, i) =>
(<rect key={i} x={d.x} width='2' />))}
</g>
In componentDidUpdate:
- Select elements to transition
- Bind data
- Call transition
- Set the attributes to transition
Make sure React doesn't manage the attributes D3 is transitioning!
d3 renders:
brush
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5150081/temp_brush.gif)
d3 renders:
brush
// in componentDidMount
this.brush = d3.brush()
.extent([[0, 0], [width ,height]])
.on('end', () => ...);
d3.select(this.refs.brush)
.call(this.brush);
// in render
<g ref='brush' />
In componentDidMount:
- Create brush instance
- Define brushable area (extent)
- Pass in a function to execute on every brush, or brush end
d3 renders:
exercise (together)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
readability:
legends
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141044/legend.jpg)
readability:
annotations
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/5141045/annotation.png)
large datasets:
canvas
// in render
<canvas ref=’canvas’
style={{width: `${width}px`, height: `${ height}px`}}
// retina screen friendly
width={2 * width} height={2 * height} />
// in componentDidMount
ctx = this.refs.canvas.getContext('2d')
Performant because only one DOM element that we're "drawing" shapes on
large datasets:
canvas
ctx.fillRect(x, y, width, height)
// or ctx.strokeRect(x, y, width, height)
ctx.beginPath()
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
ctx.fill() // or ctx.stroke()
ctx.beginPath()
// moveTo, lineTo, bezierCurveTo
ctx.fill() // or ctx.stroke()
canvas:
exercise (together)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
Render the bar chart with canvas!
canvas:
exercise (together)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3639414/giphy.gif)
Render the radial chart with canvas!
Thank you to
my beta-testers 💕
Carol
Juan
Jhon
Sam
Hector
Alexander
![](https://s3.amazonaws.com/media-p.slid.es/uploads/415741/images/3654588/giphy__1_.gif)
Front-end Masters: Data Visualization for React Developers
By Shirley Wu
Front-end Masters: Data Visualization for React Developers
- 11,122