D3 Joins: The Basics

Getting Started

Suppose we started with an array of numbers, which we'll define using a variable named nums.
let nums = [1, 2, 3, 4, 5, 6];
In the figure to the right, we represent this data array using a sequence of red boxes.

Now let's assume that our goal is to create a bar chart to visualize these values. What should we do first?

Scroll through the rest of this page to see each step in the process.

The Goal

Our goal is to create a sinple bar chart. We'll create a series of bars (like the black bars in the figure to the right) such that we have one bar for each number in the nums array. The height of each bar will need to be proportional to the value of its corresponding number.

Again, this is our goal. To start, however, we only have the array of numbers. We have some work to do to create the corresponding bars.

The Data

The first step is actually already done! We need to define an array of the data that we wish to visualize. In this case, we have a simple array of numbers stored in the variable nums.
// The data array
let nums = [1, 2, 3, 4, 5, 6];

More generally, we can work with any array of data. It might be an array of numbers like in this example, or it could be an array of objects created by parsing a CSV and applying a few filters to work with only a subset of the original data. Or it could be an array populated by a remote query against a relational database.

In one way or another, we want to create a data array as our first step.

Joining Data to Graphics with Selections

To create a visualization with D3, we need to think in terms of data joins. This is a concept that is at the heart of the D3 library, and it comes up in almost everything you do with D3. We'll talk about the simplest form of a data join in this example. More complex situations will be covered in other examples.

At its heart, a data join connects two types of things:
  1. A data element
  2. A visual element

The data elements are in our nums array. However, the visual elements don't yet exist! After all, that's what we're trying to create! Eventually, we'll represent each number visually using SVG rect elements. These allow us to draw rectangles of arbitrary shapes and sizes. But these rectangles don't exist in our visualization yet.

Nonetheless, to perform a data join we need to refer to both the data elements and our visual elements even if one of those is empty. Therefore, we need create what D3 calls a selection of our rectangles. Since we don't have any rectangles yet, the selection will be empty. It's D3's way of representing our situation: we want to have rectangles eventually but right now we don't have any.

// The data array
let nums = [1, 2, 3, 4, 5, 6];

// This will create an empty selection!
let existing_rects = d3.select("svg").selectAll("rect");

Performing the Data Join

Now that we have both nums and existing_rects, we have variables representing the two sets of ingredients we need to perform a data join:
  1. Data elements (in this case, numbers)
  2. Visual elements (in this case, rect elements)

We now connect these two variables using the D3 selection's .data(...) function. Here are the three lines of code we've discussed so far:
// The data array
let nums = [1, 2, 3, 4, 5, 6];

// This will create an empty selection since we have no rectangles yet!
let existing_rects = d3.select("svg").selectAll("rect");

// Perform the data join
let data_join_result = existing_rects.data(nums);
Of course, our nums array has 6 items while the existing_rects selection has zero. This mismatch in the number of items (6 data items but 0 visual items) means that there are 6 data items for which we don't have a corresponding visual element.

These 6 data items with no matching visual element are indicated by dashed lines and question marks in the figure to the right.

Adding new Rectangles

All that's left is to join the 6 un-matched data items produced by the previous step with new rectangles to represent them in our visualization. These new rectangles will make up our bar chart.

We use the D3 selection's .join(...) function for this. We specify the type of element we wish to add (in this case, SVG rect elements) and define the required attributes: x, y, height, width. Some, such as width, are constants. Others, such as the bar height, are functions of the individual data elements (d) or the position of those elements in the nums array (i).
// The data array
let nums = [1, 2, 3, 4, 5, 6];

// This will create an empty selection since we have no rectangles yet!
let existing_rects = d3.select("svg").selectAll("rect");

// Perform the data join
let data_join_result = existing_rects.data(nums);

// Join the unmatched data items to new rectangles.
data_join_result.join("rect")
    .style("fill", "black")
    .attr('x', (d,i) => i*55)
    .attr('y', d => 300 - d*20)
    .attr("height", d => d*20)
    .attr("width", 50);

Next Steps...

Want to experiment with an interactive version of this program? Click here!

As mentioned previously, this lesson introduces the simplest form of D3 data join. The next lesson will go more into joins in more depth by introducing the Enter, Update, and Exit concepts.
All content authored by David Gotz. Copyright © 2021-2022. All rights reserved.