Making a d3.js scatter-plot interactive — part two

Sabahat Iqbal
Analytics Vidhya
Published in
5 min readJun 26, 2020

--

This tutorial picks up from a previous tutorial. As a reminder, this is what the data structure looks like:

Poverty Rate is the y-axis value and the indicator in the last column is the x-axis value and changes with each dataset.

In the previous tutorial, we changed the Province value but in this tutorial, we will update the Year. (All code on Github and demo available here). This means we will need to invoke the update selection because we want to select data points that are already displayed on the page and change them somehow (as opposed to invoking exit which removes data points from the page or enter which adds data points to the page).

As in the previous tutorial, we have to first “capture” the user’s choice, in this case that is the Year value. This can be done in various ways but we will use the drop-down element again, placed next to the Province drop-down:

Now, filter the data using Year value, in addition to Province value. All of this is basic JS, so nothing new here:

//Function that builds the right chart depending on user choice on website:
function updateChart(someData) {
let dataAdultLit = d3
.nest()
.key(function (d) {
return d[“Year”];
})
.entries(someData[0]);
let selectedDate = new Date($(“#yearChoice”).val()).toString();let filteredData = dataAdultLit.filter(
(each) => each.key === selectedDate
)[0];
filteredData =
$(“#geographicChoice”).val() === “allProv”
? filteredData[“values”]
: filteredData[“values”].filter(
(each) => each[“Province”] === $(“#geographicChoice”).val()
);
//More code...}

Add in a function that captures the user’s Year choice and then triggers an update of the chart. Again, nothing new:

//Add in event listener for Year choice.
$(“#yearChoice”).on(“change”, function () {
updateChart(allData);
});

d3 update pattern: Enter, Update, Exit

Now, we need to think about what parts of the chart need to change every time user selects a new Year. Obviously, with a new Year, the only elements that should change are the circles in the chart. The elements that won’t change? x-axis, y-axis, axes labels, color scale. It is best practice to take that code out of the updateChart function since we do not want those elements to be recreated every single time the user changes the Year value. There are two reasons for this:

  1. Performance — as a general rule, don’t re-run code that is not necessary.
  2. Aesthetics — if the x-axis and y-axis code runs each time Year is updated, we will end up with multiple axes on top of each other on the page. They may not be noticeable at first but after a while the lines and tick marks get thicker. This is bad.

So, we have to move stuff outside of updateChart . Great. But be careful. We should not move those lines of code that are linked to the arguments in updateChart , in this case, the dataset.

In our case, because none of the attributes for x-axis, y-axis, or color scale use any data from the dataset, we can safely move all of that code out of the function. An example of when we would not be able to move code out of the function: if, for example, the domain attribute for either axis relied on a value within the dataset (which is an argument in updateChart)

So, now we know that we want to update the circles’ positions on the chart. How do we do that? By updating their cx and cy attributes. So the mental model is:

  1. Load web page with default Year value, i.e. 2004.
  2. Join data to SVG elements, i.e. circle .
  3. Display chart. This is the Enter part of d3 update pattern.
  4. User selects a new Year value.
  5. All the existing circles have to shift to new cx and cy positions. This is the Update part of d3 update pattern.
  6. If the new Year value does not have data for a particular circle, i.e. data for a District is missing for the new Year value, then that circle has to be removed. This is the Exit part of d3 update pattern. We saw this in the previous tutorial.

So we know we have to run through each part: Enter, Update, Exit. Except we don’t do it in that order. Instead, after the data join:

// JOIN data to elements.
let circles = svg.selectAll(“circle”).data(filteredData, function (d) {
return d[“District”];
});
  1. We select a new Year value and first, remove any circles that do not need to be on the screen anymore. These are essentially Districts that had a value in the default Year but for some reason (usually missing data) that same District does not have data for the new Year . So, run the exit step:
//EXIT old elements not present in new data.
circles.exit().remove();

2. Then, we run the Update step. Note: there is no actual update keyword (like there is for exit and enter). Essentially, we’re just running the same code as enter but only changing those attributes that need to be changed as a result of the new data, i.e. new Year, :

//UPDATE existing elements to new position in graph:
circles
.attr(“cy”, function (d) {
return y(d[“Poverty Rate (%)”]);
})
.attr(“cx”, function (d) {
return x(
d[
“Adult literacy, 25 or more years old (% of population aged 25 or more)”
]
);
});

3. Finally, we run the enter step:

// ENTER new elements present in new data.
circles
.enter()
.append(“circle”)
.attr(“class”, “enter”)
.attr(“fill”, function (d) {
return color(d[“Province”]);
})
.attr(“cy”, function (d) {
return y(d[“Poverty Rate (%)”]);
})
.attr(“cx”, function (d) {
return x(
d[
“Adult literacy, 25 or more years old (% of population aged 25 or more)”
]
);
})
.attr(“r”, 5);
}

When data is joined to the SVG circle elements, it immediately returns three selections. We can see this is we console.log(circles) immediately after the data join step:

Result of data join.

_enter selection includes all the data elements that exist as a result of the data join but are not represented on the chart. At the initial launch of web page, this is the only one that has any data tied to it.

_exit selection includes all the SVG elements that no longer have a data element linked to it. This is empty at initial page launch.

_groups selection includes all the SVG elements on the chart. This is empty at initial page launch.

Every time the data changes, those selections get updated so you can do something with them, i.e. add SVG elements to the page with certain attributes, remove SVG elements from the page, change the attributes of the SVG elements already on the page.

The concept of Enter, Update, Exit can be hard to grasp. So don’t worry if you don’t get it right away. It takes practice.

Please leave comments if you run into any problems.

--

--