D3 is still Useful
- Traditional D3 Use
Here's an example of a bar chart from d3-graph-gallery
https://codesandbox.io/s/d3-classic-bar-plot-tvhmy
D3 was the first framework I saw that made use of reactivity. Traditional d3
graphs usually took the form of d3.select('#app').data(data).enter()
. In the
example from above the bar graphs are created via:
svg.selectAll('mybar')
.data(data)
.enter()
.append('rect')
.attr('x', function (d) {
return x(d.Country)
})
.attr('y', function (d) {
return y(d.Value)
})
.attr('width', x.bandwidth())
.attr('height', function (d) {
return height - y(d.Value)
})
.attr('fill', '#69b3a2')
Here a rectangle is created for each index of the data array. If the data changes, then all rectangles appended and their attributes will also update.
This is an extremely powerful framework from creating graphs, but it also wants to be in charge of manipulating and rendering part of the DOM.
Manipulating already existing SVGs
Imagine your design team has kicked over an SVG example of a graph they want on the page. To actually make the graph real you'll have to set the correct height of each rectangle. To complicate things further, the raw values from your dataset need to be mapped to your SVGs size & coordinate system.
This is where d3 still shines.
In this iteration: https://codesandbox.io/s/kind-voice-31xno
I've set the rectangles heights all to 0
, and instead let their heights be
set via vanilla JS.
Parsing the CSV data
In an application you'll likely already have a way of fetching data. fetch
or
axios
being the most common. For that reason, it doesn't really make sense to
use a second one from d3
. Instead, assuming the data has been fetched, you
can parse it with d3
.
import { csvParse } from 'd3-dsv'
const csvData = `Country,Value
United States,12394
Russia,6148
Germany (FRG),1653
France,2162
United Kingdom,1214
China,1131
Spain,814
Netherlands,1167
Italy,660
Israel,1263`
const data = csvParse(csvData)
This results in [{Country: "United States", Value: 12394}, ...]
Mapping the data to the coordinate system
This can be done using scale functions. For the x
axis,
scaleBand is used. This will
space out each bar evenly on the x
axis.
// via npm install d3-scale@3
import { scaleBand } from 'd3-scale'
var x = scaleBand()
.range([0, width])
.domain(data.map((d) => d.Country))
.padding(0.2)
The result given a width of 400px
is:
United States - X: 7.254901960784309px,
Russia - X: 43.52941176470588px,
Germany (FRG) - X: 79.80392156862744px,
France - X: 116.078431372549px,
United Kingdom - X: 152.35294117647058px,
China - X: 188.62745098039215px,
Spain - X: 224.9019607843137px,
Netherlands - X: 261.17647058823525px,
Italy - X: 297.45098039215685px,
Israel - X: 333.7254901960784px
For the Y
axis, scaleLinear is
used. This will map the data points to positions relative to the height of the
SVG.
// via npm install d3-scale@3
import { scaleLinear } from 'd3-scale'
var y = scaleLinear().domain([0, 13000]).range([height, 0])
The result given the height of 400px
(minus 60px for margin) is:
United States - Y: 13.984615384615385,
Russia - Y: 158.1230769230769,
Germany (FRG) - Y: 261.8538461538461,
France - Y: 250.10769230769233,
United Kingdom - Y: 271.9846153846154,
China - Y: 273.90000000000003,
Spain - Y: 281.2153846153846,
Netherlands - Y: 273.0692307692307,
Italy - Y: 284.7692307692308,
Israel - Y: 270.8538461538462
Manipulating the SVG data in vanilla JS
const yAttributes = data.map((d) => y(d.Value))
const xAttributes = data.map((d) => x(d.Country))
document.querySelectorAll('rect').forEach((elem, index) => {
elem.setAttribute('x', xAttributes[index])
elem.setAttribute('y', yAttributes[index])
elem.setAttribute('height', height - yAttributes[index])
elem.setAttribute('width', 29.019607843137255)
})
The obvious caveat here is that this isn't responsive. But it's fairly strait forward to select all the elements and update them upon an event like user interaction.
Not all d3 components play nice
It's worth noting in the example I grabbed the axis were created using
d3.axisBottom
and d3.axisLeft
. These functions unfortunately
aren't decoupled
from the d3's DOM manipulation logic.
Building axis is something you'd still be on the hook for most frontend stacks (i.e. vue.js for example).