React is a library for building reactive user interfaces using JavaScript (or Typescript) and D3 (short for Data-Driven Documents) is a set of libraries for working with visualizations based on data
Before getting started, I would recommend familiarity with SVG, React, and D3
Some good references for SVG are on the MDN SVG Docs
Broadly, scales allow us to map from one set of values to another set of values,
Scales in D3 are a set of tools which map a dimension of data to a visual variable. They help us go from something like count in our data to something like width in our rendered SVG
We can create scales for a sample dataset like so:
1
typeDatum={
2
name:string
3
count:number
4
}
5
6
exportconstdata:Datum[] = [
7
{ name:'π', count:21},
8
{ name:'π', count:13},
9
{ name:'π', count:8},
10
{ name:'π', count:5},
11
{ name:'π', count:3},
12
{ name:'π', count:2},
13
{ name:'π', count:1},
14
{ name:'π', count:1},
15
]
Also, a common thing to do when working with scales is to define margins around out image, this is done simply as an object like so:
1
constmargin={
2
top:20,
3
right:20,
4
bottom:20,
5
left:35,
6
}
This just helps us simplify some position/layout things down the line
Scales work by taking a value from the domain (data space) and returning a value from range (visual space):
1
constwidth=600
2
constheight=400
3
4
constx=d3
5
.scaleLinear()
6
.domain([0,10]) // values of the data space
7
.range([0,width]) // values of the visual space
8
9
constposition=x(3) // position = scale(value)
Additionally, thereβs also the invert method which goes the other way - from range to domain
1
constposition=x(3) // position === 30
2
constvalue=x.invert(30) // value === 3
The invert method is useful for things like calculating a value from a mouse position
D3 has different Scale types:
Continuous (Linear, Power, Log, Identity, Time, Radial)
Sequential
Diverging
Quantize
Quantile
Threshold
Ordinal (Band, Point)
Continuous Scales
These scales map continuous data to other continuous data
D3 has a few different continuous scale types:
Linear
Power
Log
Identity
Radial
Time
Sequential Color
For my purposes at the moment Iβm going to be looking at the methods for Linear and Sequential Color scales, but the documentation explains all of the above very thoroughly and is worth a read for additional information on their usage
Linear
We can use a linear scale in the fruit example for mapping count to an x width:
1
constmaxX=d3.max(data,(d)=>d.count) asnumber
2
3
constx=d3
4
.scaleLinear<number>()
5
.domain([0,maxX])
6
.range([margin.left,width-margin.right])
If we donβt want the custom domain to range interpolation we can create a custom interpolator. An interpolator is a function that takes a value from the domain and returns the resulting range value
D3 has a few different interpolators included for tasks such as interpolating colors or rounding values
We can create a custom color domain to interpolate over and use the interpolateHsl or interpolateRgb functions:
1
constcolor=d3
2
.scaleLinear<string>()
3
.domain([0,maxX])
4
.range(['pink','lightgreen'])
5
.interpolate(d3.interpolateHsl)
Sequential Color
If for some reason we want to use the pre-included color scales
The scaleSequential scale is a method that allows us to map to a color range using an interpolator.
D3 has a few different interpolators we can use with this function like d3.interpolatePurples, d3.interpolateRainbow or d3.interpolateCool among others which look quite nice
We can create a color scale using the d3.interpolatePurples which will map the data to a scale of purples:
1
constcolor=d3
2
.scaleSequential()
3
.domain([0,maxX])
4
.interpolator(d3.interpolatePurples)
These can be used instead of the scaleLinear with interpolateHsl for example above but to provide a pre-calibrated color scale
Ordinal Scales
Ordinal scales have a discrete domain and range and are used for the mapping of discrete data. These are a good fit for mapping a scale with categorical data. D3 offers us the following scales:
Band Scale
Point Scale
Band Scale
A Band Scale is a type of Ordinal Scale where the output range is continuous and numeric
We can create a mapping for where each of our labels should be positioned with scaleBand:
1
constnames=data.map((d)=>d.name)
2
3
consty=d3
4
.scaleBand()
5
.domain(names)
6
.range([margin.top,height-margin.bottom])
7
.padding(0.1)
The domain can be any size array, unlike in the case of continuous scales where the are usually start and end values
Building a Bar Graph
When creating visuals with D3 there are a few different ways we can output to SVG data. D3 provides us with some methods for creating shapes and elements programmatically via a builder pattern - similar to how we create scales.
However, there are also cases where we would want to define out SVG elements manually, such as when working with React so that the react renderer can handle the rendering of the SVG elements and we can manage our DOM structure in a way thatβs a bit more representative of the way we work in React
The SVG Root
Every SVG image has to have an svg root element. To help ensure that this root scales correctly we also use it with a viewBox attribute which specifies which portion of the SVG is visible since the contents can go outside of the bounds of the View Box and we may not want to display this overflow content by default
Using the definitions for margin, width and height from before we can get the viewBox for the SVG weβre trying to render like so:
return<svgviewBox={viewBox}>{/* we will render the graph in here */}</svg>
At this point we donβt really have anything in the SVG, next up weβll do the following:
Add Bars to the SVG
Add Y Labels to the SVG
Add X Labels to the SVG
Bars
We can create Bars using the following:
1
constbars=data.map((d)=> (
2
<rect
3
key={y(d.name)}
4
fill={color(d.count)}
5
y={y(d.name)}
6
x={x(0)}
7
width={x(d.count) -x(0)}
8
height={y.bandwidth()}
9
/>
10
))
We make use of the x and y functions which help us get the positions for the rect as well as y.bandWidth() and x(d.count) to height and width for the element
We can then add that into the SVG using:
1
return (
2
<svgviewBox={viewBox}>
3
<g>{bars}</g>
4
</svg>
5
)
At this point, the resulting SVG will look like this:
Y Labels
Next, using similar concepts as above, we can add the Y Labels:
Next, we can add this into the SVG, and also wrapping the element in a g with a some basic text alignment and translation for positioning it correctly:
Note that D3 includes a d3-axis package but that doesnβt quite work given that weβre manually creating the SVG using React and not D3βs string-based rendering
We may want to add Ticks and Grid Lines on the X-Axis, we can do this using the scaleβs ticks method like so: