🌙

How To “Learn D3” In 2023

This is my extended answer to a question I get asked all the time: how do I learn D3? I've written many responses, so I figured they best belong in one standalone resource. The answer is simple yet complicated, because most people are asking the wrong question.

This is an extended version of a previous blog post, “Making Visualizations Literally with Svelte & D3.” Since that blog post’s release, I’ve dove deeper into Svelte + D3, by 1) publishing new projects, 2) releasing an online course, and 3) hosting multiple workshops. This blog post consolidates all of my thoughts into one place.

01.

Do you really want to learn D3?

Let’s clarify one important concept up front. When most people say they "want to learn D3", they actually mean they want to learn interactive data visualization, and they think D3 is the tool to do that. Few beginners actually want to "learn D3," they just want to learn how to make those beautiful and complex "D3" projects.

But D3 is far too expansive to be the only tool beginners use, and far too complex to be the first tool beginners start with. D3 is a collection of modules that vary widely in their purposes, ranging from DOM manipulation, to array manipulation, to SVG path construction. In the future, beginners ought to think of D3 as a tool in their interactive toolkit, and only reach for it when it is the best tool for the job.

This new approach will reframe D3 from an all-expansive data visualization library to a toolkit that best handles specific components of a project, like scaling data and manipulating arrays. Meanwhile, a JavaScript framework can take on the heavy work of DOM manipulation. I call this the framework-first approach to data visualization.

Some JavaScript frameworks you might have heard of inlude React, Vue, Angular, or Svelte.

This conversation isn't new; it started in 2018 with Elijah Meeks' D3 is not a Data Visualization Library:

D3.js is an incredibly successful library yet there’s a disconnect between what people think D3 does and what it actually does. As a result, learning D3 is intimidating and confusing. By better understanding its structure and separating it into more manageable pieces, it can be easier to choose which parts of the library to learn and which parts to avoid — key lessons not only for D3 novices but for expert users like myself that might want to reexamine how they use D3.

Despite his prescience, it wasn't until a few years later that the conversation hit the mainstream, as folks like Amelia Wattenberger and Matthias Stahl published impressive graphics using JavaScript frameworks like React and Svelte. And behind the scenes, this framework-first approach has taken the visualization world by storm—most recently evinced by the near-universal usage of Svelte in election graphics in 2022 and its use in newsrooms more broadly.

And despite the framework-first approach becoming increasingly prevalent, there aren't a lot of resources to learn about what it exactly is, and how to get started. This post hopes to add to and reframe the conversation around learning interactive data visualization.

02.

Why is D3 hard to learn?

We can begin with the perennial question: why is D3 so hard to learn? I think people who "try to learn D3" usually run into problems because they think that D3 is the single tool they need to master; in reality, "learning D3" is learning an entire toolkit of tools on the web, including (but not limited to) HTML, CSS, JavaScript, SVG, and D3. (Have fun!).

When folks that are brand new to the web start "learning D3," they rarely understand what in their curriculum is D3, what is HTML, what is CSS, what is SVG, and what is JS. And it’s not really their fault—D3 trying to subsume each of these individual languages (creating markup from select() method chaining and styling in .attr() or .style() tags) muddies the water of understanding for all of them. And so interactive data visualization will require you to learn the tools of the web, but when D3 tries to "hijack" parts of the web, it conceals each web tool’s individual purpose.

For people who already understand the web, D3 is great: it provides convenient functions to manipulate all parts of your web application in one place. But "learning D3" before you’ve learned the fundamentals of the web is like trying to start a one-person band without having touched any of the instruments you’re going to be using. (Or something like that. Please help with a better analogy.)

03.

The framework-first approach

Instead of overusing D3, we should adopt a framework-first approach. Rather than using D3 for all parts of visualization on the web, we use it for what it's best suited for (data manipulation, scaling data, SVG path construction, etc.) and use the web's native tools for everything else (authoring and manipulating the document).

When I say "framework-first," I mean that we should use JavaScript frameworks like React, Svelte, Vue, and others to build our interactive data visualizations. These frameworks are designed to make building web applications easier, and they offer the same benefits for data visualization.

So, would Svelte, React, et al. make interactive dataviz easier to learn? The answer is yes, not because a framework makes things easy but because it makes things clear. If you were learning Svelte, for example, you would learn the discrete purposes of a <script /> tag, of your markup, and of your <style /> tag: JavaScript, HTML/SVG, and CSS, respectively. Yes, you are learning three new tools, but you’re not trying to learn them through the lens of D3.

Let's consider a trivial example to illustrate this point: a simple scatterplot. We'll compare the code needed to produce this scatterplot, in D3 and in Svelte. (Feel free to just glance for now, as we'll dive in more deeply in a moment.)

D3 input
import * as d3 from "d3"; // 1. Create data const data = [ { x: 50, y: 50, r: 25 }, { x: 100, y: 100, r: 25 }, { x: 150, y: 150, r: 25 }, { x: 200, y: 200, r: 25 }, { x: 250, y: 250, r: 25 } ]; // 2. Instantiate dimensions const width = 300; const height = 300; // 3. Create scaling functions const xScale = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.x)]) .range([0, width - 30]); const yScale = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.y)]) .range([height, 30]); // 4. Create SVG element, append dimensions const svg = d3 .select("body") .append("svg") .attr("width", width) .attr("height", height); // 5. Create a circle for each datapoint svg .selectAll("circle") .data(data) .join("circle") .attr("cx", (d) => xScale(d.x)) .attr("cy", (d) => yScale(d.y)) .attr("r", (d) => d.r) .attr("fill", "plum") .attr("stroke", "black");
  
  
  
    
  
Svelte input
<script> import * as d3 from "d3"; // 1. Create data const data = [ { x: 50, y: 50, r: 25 }, { x: 100, y: 100, r: 25 }, { x: 150, y: 150, r: 25 }, { x: 200, y: 200, r: 25 }, { x: 250, y: 250, r: 25 } ]; // 2. Instantiate dimensions const width = 300; const height = 300; // 3. Create scaling functions const xScale = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.x)]) .range([0, width - 30]); const yScale = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.y)]) .range([height, 30]); </script> <!-- 4. Create SVG element, append dimensions --> <svg {width} {height}> <!-- 5. Create a circle for each datapoint --> {#each data as d} <circle cx={xScale(d.x)} cy={yScale(d.y)} r={d.r} fill='plum' stroke='black' /> {/each} </svg>
  
  
  
    
  

The output of that code, in either context, would be a chunk of SVG code that, when embedded in a document, would render five circles.

<svg width="300" height="300"> <circle cx="54" cy="246" r="25" fill="plum" stroke="black"/> <circle cx="108" cy="192" r="25" fill="plum" stroke="black"/> <circle cx="162" cy="138" r="25" fill="plum" stroke="black"/> <circle cx="216" cy="84" r="25" fill="plum" stroke="black"/> <circle cx="270" cy="30" r="25" fill="plum" stroke="black"/> </svg>
  
  html
  
    
  

This an important lesson many beginners forget: whether you're using D3 or some framework, your output will just be SVG. (Unless you're using canvas or something else.) The question is just how you get there.

You might notice that, in both examples, steps 1 through 3 are identical. That's because the basic setup is the same: we create some data, get the dimensions of our chart, and then use D3 (in particular, d3-scale) to map raw data to our dimensions.

The examples diverge in steps 4 and 5, which are our DOM manipulation steps. Let's dive deeper into those steps to understand what D3 is doing behind the scenes, and why a framework-first approach is much more intuitive.

Our chart begins with an SVG element. Creating a container SVG element is a prerequisite for any SVG chart. How would we create our SVG element in D3?

const svg = d3.select('body') .append('svg')
  
  js
  
    
  

Let's compare that with creating an SVG element in Svelte.

<svg></svg>
  
  svelte
  
    
  

Next, we need to assign the SVG element a width and height.

In D3, we assign those attributes in the existing selection chain, in .attr() methods.

.attr('width', 300) .attr('height', 300)
  
  js
  
    
  

In Svelte, we write those attributes directly.

<svg width="300" height="300"></svg>
  
  svelte
  
    
  

Once our SVG element is created, we'll want to render some circles. In D3, we'd use three methods—selectAll(), data(), and join()—to bind data to our selection.

svg .selectAll("circle") .data(data) .join("circle")
  
  js
  
    
  

In Svelte, we can use the each directive to bind data to our selection. Within the each block, we write the element we want to render.

<svg width="300" height="300"> {#each data as d} <circle /> {/each} </svg>
  
  svelte
  
    
  

Now that we have circles, we want to bind data to their attributes. In D3, we use the attr() method to bind data to attributes.

.attr("cx", d => xScale(d.x)) .attr("cy", d => yScale(d.y)) .attr("r", d => d.r)
  
  js
  
    
  

While in Svelte, we just write the attributes directly, and wrap them in curly braces ({}) to bind data.

<circle cx={xScale(d.x)} cy={yScale(d.y)} r={d.r} />
  
  svelte
  
    
  

And so the differences between D3 and Svelte are equivalent to the differences between instructions and authoring. In D3, we write instructions to tell JavaScript what to render; in Svelte, we write our output directly.

Scroll →

D3 input
Svelte input
Compiled output for both
<svg width="300" height="300"> <circle cx="54" cy="246" r="25" /> <circle cx="108" cy="192" r="25" /> <circle cx="162" cy="138" r="25" /> <circle cx="216" cy="84" r="25" /> <circle cx="270" cy="30" r="25" /> </svg>

The Svelte code above is “declarative programming”: you're declaratively authoring the DOM, rather than imperatively giving commands as you would in D3. Rather than writing code that says, “select this element, and then append this other element to it,” You're writing those elements directly.

In a framework-first application, beginners will be able to tell if a bug lies in their markup by directly changing their markup, whereas a developer working in a pure-D3 application might not know (without doing some intentional debugging) if their issue lies in their selection method chaining, in their SVG syntax, or in some JavaScript function. Of course, advanced practitioners will know how to solve these sorts of problems. But for those learning interactive data visualization, D3 confuses more than it clears up.

If interactive data visualizations live on the web, they should leverage the tools of the web. A framework-first approach ensures the student understands the fundamentals of the web by separating concerns more intentionally.

04.

Use D3 when it makes sense

In this new framework-first approach, we still make great use of D3, because D3 is great! D3 provides convenient functions for array manipulation, scaling of data, and shape and path manipulation. But when D3 isn’t needed and a framework would be better suited (for example, using Svelte to write your markup directly, rather than using a D3 selection method), select the right tool for the job.

D3 for the data, frameworks for the DOM

The greatest relief a framework-first approach offers is its ability to manage the DOM in a more intuitive way. (When I say, “manage the DOM”, I mean, “control what exactly appears, disappears, and moves on the screen.”) Rather than using long chains of D3 selection methods, we can write our markup directly.

But there are obviously other steps in the data visualization process, that occur before we even get to the DOM. For example, we need to 1) transform our data and 2) scale that data to positions on the chart. These steps are still best suited for D3, because D3 is great at array manipulation and scaling.

A common workflow for a modern interactive visualization might follow this pattern:

  1. Import and transform data. ( d3-array from
    D3
    )
  2. Scale data to positions on the chart. (d3-scale from
    D3
    )
  3. Write markup directly. (A framework like
    Svelte
    )
  4. Bind data to DOM elements. (A framework like
    Svelte
    )
  5. Add interactivity. (A framework like
    Svelte
    )

Occasionally, your project also might require other specialized D3 modules to help generate SVG paths. For example, you would reach for d3-geo or d3-shape to generate paths for a map or an area chart, respectively. But the more cleanly you can separate concerns in your workflow—using D3 for data and a framework for the DOM—the smoother your workflow will be.

And so in this new approach, not all D3 modules are created equal. Here's an overview of each of the modules currently present in D3, organized by their function and usefulness in a framework-first workflow (in my opinion):

The D3 Tools to Use in a Framework-First Data Visualization

Frequency of use

Never
Rarely
Sometimes
Often

Grabbing data

Manipulating data

Manipulating the DOM

Scaling data to positions on the chart

Drawing SVG shapes

Manipulating color

Manipulating time

Geographic projections

User interaction

The categorization of functions is borrowed, with permission, from Amelia Wattenberger's great blog post. You can contribute to our collective understanding of D3 by filling out this survey from Sebastian Lammers.

One simple phrase that encapsulates the above, from Matthias Stahl: use D3 for the math and the paths. And a framework for the rest.

This new framework-first approach forces us to use D3 more intentionally. Rather than acting as a hammer searching for nails (how can I solve this problem using D3?), we only reach for the hammer when we need it (would D3 be best-suited to solve this problem, and how so?).

05.

The actual roadmap

Enough about theory. If I haven’t convinced you, reach out. Otherwise, here’s how to actually get started learning D3 making interactive data visualizations in 2023.

1. Learn the fundamentals of the web

Learn HTML, CSS, and beginner JavaScript. There are plenty of resources online, but here are some good starting points:

But don’t commit too heavily to these tutorials or mastering these tools. In step 2, you’ll find a JavaScript framework which makes authoring complex applications more simple. It’s important you understand the main concepts of HTML, CSS, and JavaScript, but you’ll continue to learn them as you dive into a framework.

2. Choose a JavaScript framework, and learn it

Learn some JavaScript framework.

The most popular and fastest-growing framework within the visualization community in particular is Svelte, but React is another great choice because it has 1) an incredibly large user base, 2) an existing ecosystem of packages and libraries, and 3) great employability prospects beyond visualization. (React is sort of the GOAT, and Svelte is the new kid on the block (baby goat?) that is growing in prominence.)

Another honorable mention is Vue, which is left out of the conversation lately but still very promising.


If you do choose Svelte, I have a few existing resources, including this workshop and this blog post. You can also access my online course with Newline here. Matthias Stahl has also put together some great resources for learning Svelte, including this conference talk.

3. Learn SVG

In every “D3” project, you're giving JavaScript instructions to write SVG (or whatever your output is) to the DOM. And in your framework-first approach, you will write to the DOM directly. (The difference, as we've already went over: where D3 would require you to write d3.select("body").append("svg"), a framework enables you to write <svg />; both will create the output, <svg />.)

So, it’s worth getting familiar with SVG (scalable vector graphics), which is usually the main markup language used to design data visualizations.

Syntactically, SVG is like HTML (which is another reason it’s worth learning HTML first), except it produces images rather than text. Once you begin to understand SVG, you can write it directly in your framework-first application, and embed data inline as needed.

Admittedly, fewer resources exist to "learn SVG," but the workflow for using new SVG elements is quite simple:

  1. Identify the element you want to use (if you are making a scatterplot, a <circle />)
  2. Find the documentation for that element (on MDN)
  3. Gather the attributes you need to assign (for a circle, cx, cy, and r)
  4. Assign them, and then edit as needed

One great resource for learning SVG paths in particular is this interactive guide from Nanda Syahrasyad.

4. Write SVG in a framework-first application

Now that you understand SVG, which will be your final output, write it directly in some JavaScript framework . Having learned the fundamentals of the web, you should have a good handle on debugging separate parts of your application, and having learned the ins and outs of your JavaScript framework , you should know how to embed data inline within your markup.

5. Learn D3 when you need to

In your journey, you'll notice there are a few problems that can't be solved by your framework of choice. This may be the case if you are creating a complex and specific type of visualization, like a force diagram, chord diagram, or sankey diagram (all of which have D3 plugins). Or, you may need to use D3 for routine tasks, like scaling data or manipulating arrays. We've already reviewed above which modules would be best to learn first.

And so in this new paradigm, we reverse the order of our learning roadmap: rather than learning D3 and then tackling a complex project, we become familiar with the web, then our output language (SVG), and when needed, we reach into our toolbox for D3.

Thanks to Javier García Fernández, Amit Grinson, Matthias Stahl, and Sebastian Lammers for reviewing and giving feedback on this article.

🔇