It is a comprehensive framework for building any sort (almost literally) of interactive visualizations for the web.
It is however, not just a simple high-level graphing library.
It has reactive design patterns like binding the data to DOM elements. Very similar to how a modern JS framework (Angular, React, Vue) approaches data binding!
Reading: D3 is not a Data Visualization Library, Elijah Meeks
How to learn D3.js, Amelia Watterberger
D3 Graph Gallery, Yan Holtz
bl.ocks.org (defunct)
Animation
3D
I'll be including D3 the easiest way - embedding as a script pulled from a CDN. You can of course use npm / import and design a more modern way of scaffolding your projects.
One thing to note is that for reactivity, we'll be using D3's default data binding functions.
If you use a JS framework / compiler — Vue, React, Angular, Svelte, etc. — it is a good idea that your data binding does not clash with D3's one.
Let's start with a simple template. We'll go through selections, which is a core component of D3.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
d3.select("body").append("p").text("Hello World!");
</script>
</body>
</html>
Note: We're appending a paragraph tag of "Hello World" to the body.
<div id="hellos">
<p>Hello World!</p>
<p>Hello World 2!</p>
<p>Hello World 3!</p>
</div>
<script>
d3.select("#hellos p").style("color", "green");
d3.selectAll("#hellos p").style("color", "blue");
</script>
Notice the difference between the select and selectAll statements.
It's similar to JQuery (or other modern frameworks) in that you can manipulate any DOM element. Try d3.select("body").style("background-color", "red")
let data = [10, 20, 30];
let hellos = d3.select("#hellos");
hellos.selectAll("p")
.data(data)
.enter()
.append("p")
.style("font-size", function(d) { return d + "px" })
.text("Hello World")
Notice the design pattern: Select a bunch of elements (that don't exist), bind that to the data, and then append said elements.
One style to write javascript is to use method chaining, which is a design pattern you will see in many javascript libraries, like JQuery or Lodash. D3 also follows this style, and it makes your code more readable.
Notice the use of anonymous functions to manipulate the data.
let data = [
{text: "Hello World 1", size: 10},
{text: "Hello World 2", size: 20}
]
let hellos = d3.select("#hellos");
hellos.selectAll("p")
.data(data)
.enter()
.append("p")
.style("font-size", d => d.size + "px")
.text(d => d.text)
Notice the use of arrow notation (ES6) to write anonymous functions.
let data = [
{text: "Hello World 1", size: 10, color: "green", font: "Courier New"},
{text: "Hello World 2", size: 20, color: "blue", font: "Arial"},
{text: "Hello World 3", size: 30, color: "red", font: "Times New Roman"}
];
Given this dataset, can you print out each individual text, and style the elements?
hellos.selectAll("p")
.data(data)
.enter()
.append("p")
.text(d => d.text)
.transition()
.duration((d,i) => i * 1000)
.style("font-size", d => d.size + "px")
.style("color", d => d.color);
Remember how to draw shapes using SVGs?
<svg width="1000" height="1000"></svg>
let data = [
{cx: 400, cy: 600, r:150, fill: "green", stroke: "black"},
{cx: 100, cy: 200, r:50, fill: "blue", stroke: "purple"},
{cx: 650, cy: 100, r:100, fill: "red", stroke: "orange"}
]
Given this dataset, can you add these as circles to the svg, with the appropriate theming?
d3.select("svg").selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => d.cx)
...
Fill in the rest.
Set stroke-width to be 5 so the stroke is clearer.
Let's say we want to draw our 1000 x 1000 chart data size onto an SVG that is 600 x 600
How do we map our quantitative data scale to the SVG drawing scale? Use domain / range to automatically calculate the mapping.
let xScale = d3.scaleLinear()
.domain([0,1000])
.range([0,600]);
let yScale = d3.scaleLinear()
.domain([0,1000])
.range([600,0]);
More on scales and domain/range here.
Note that the my yScale is inverted here. Because for standard charts, we draw from the bottom-left up rather than top-left down.
let margin = {top: 20, right: 20, bottom: 40, left: 40},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
let chart = d3.select("svg")
.attr("width", 600)
.attr("height", 600)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let xScale = d3.scaleLinear()
.domain([0,1000])
.range([0,width]);
let yScale = d3.scaleLinear()
.domain([0,1000])
.range([height,0]);
chart.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => xScale(d.cx))
.attr("cy", d => yScale(d.cy))
.attr("r", d => d.r)
.attr("fill", d => d.fill);
chart
.append("g")
.call(d3.axisBottom(xScale));
chart
.append("g")
.call(d3.axisLeft(yScale));
Axes are added to origin point (0,0). Use a transform to translate the xAxis to the bottom of the chart.
chart
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale));
chart
.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + 40) + ")")
.text("X-axis label")
.attr("text-align", "middle");
chart
.append("text")
.attr("transform", "translate(-40, " + (height / 2) + ") rotate(-90)")
.text("Y-axis label")
.attr("text-align", "middle");
Let's map radius to a sequential colorscale.
let colorScale = d3.scaleLinear()
.domain([0, 300])
.range([0,1]);
...
.attr("fill", d => d3.interpolateViridis(colorScale(d.r)));
...
.on("mouseover", (event, d) => {
d3.select(event.currentTarget)
.attr("stroke", "black")
.attr("stroke-width", 2);
})
.on("mouseout", (event, d) => {
d3.select(event.currentTarget)
.attr("stroke", "none");
})
Do note that this event firing is syntax for the latest D3 v7. Migration docs here. Syntax for event firing is different for previous versions.
Bl.ocks.org: Simple D3 bar chart
https://bl.ocks.org/chi-loong/c36e1cf66502faa45f8dd8102a5c5531
Transitions. Enter, update and exit design pattern
Bl.ocks.org: D3 bar chart part 2
https://bl.ocks.org/Chi-Loong/e3389dfb6873c85caf445f0faba52ec4
D3 is not just for standard charts. There are tons of libraries out there that are probably easier to use to create standard charts.
But if you want to write your own interaction, animations, or code art with D3, you can. Here's a code art piece.
Chi-Loong | V/R