D3 uses a very powerful data binding approach. However, many D3 examples only show how to deal with very simple data, such as [1,2,3]
. Let’s have a look at what happens when data items are objects, for example
var data = [
{x: 70, y: 20, label: "first"},
{x: 10, y: 60, label: "second"}
];
The binding itself does not change when going from numbers to objects - just bind data to your selection as before and go ahead with the usual D3 functions-as-values approach
selection
.data(data)
.attr("x", function(d) { return d.x; });
Inspecting a DOM element from the selection and navigating to its properties shows that the __data__
property is set to the corresponding item from the list (this is how d3 data binding internally works):
Now let’s visualize our data as SVG Text
objects with text coming from d.label
positioned at (d.x, d.y)
, and add a couple of transitions:
function render() {
var svg = d3.select("#viz svg");
var ditems = svg.selectAll("text").data(data);
// enter
ditems.enter()
.append("text");
// update
ditems.transition().duration(500)
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text(function(d) { return d.label; });
// exit
ditems.exit()
.transition()
.style("opacity", 0)
.remove();
}
Here, the update
transition will interpolate object coordinates, and the exit
transition makes the text fade out before it is removed from the DOM. Here is what it looks like:
But what happens if we remove the first object? Give it a try:
That’s weird. How about swapping the objects in the data
array without changing their contents?
As we only re-ordered the objects and still see some changes, it has something to do with indexes. In fact, by default D3 assumes that the objects are identified by their index in the data array, and this works well in many scenarios. Here, however, we don’t want to use indexes and need to identify objects differently, which can be done by passing a key
function to selection.data(values, key). For example, if we know the label
is going to be unique then we can simply make the key function return it:
selection.data(data, function(d) { return d.label; })
With that, swapping the objects will now have no effect and removing the first element will behave as expected:
In case when labels are not necessarily unique, we would need to use something else as an identifier. For example, an id
field could be added to each object and used as the key.
Happy data-binding!