D3 Joins: The Basics
Getting Started
Suppose we started with an array of numbers, which we'll define using a variable namednums
.
let nums = [1, 2, 3, 4, 5, 6];
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 thenums
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 variablenums
.
// The data array
let nums = [1, 2, 3, 4, 5, 6];
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:- A data element
- A visual element
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 bothnums
and existing_rects
, we have variables
representing the two sets of ingredients we
need to perform a data join:
- Data elements (in this case, numbers)
- Visual elements (in this case, rect elements)
.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);
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);