Making a d3.js scatter-plot interactive — part two
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:
- Performance — as a general rule, don’t re-run code that is not necessary.
- 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:
- Load web page with default
Year
value, i.e.2004
. - Join data to SVG elements, i.e.
circle
. - Display chart. This is the Enter part of d3 update pattern.
- User selects a new
Year
value. - All the existing circles have to shift to new
cx
andcy
positions. This is the Update part of d3 update pattern. - If the new
Year
value does not have data for a particular circle, i.e. data for aDistrict
is missing for the newYear
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”];
});
- We select a new
Year
value and first, remove any circles that do not need to be on the screen anymore. These are essentiallyDistricts
that had a value in the defaultYear
but for some reason (usually missing data) that sameDistrict
does not have data for the newYear
. So, run theexit
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:
_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.