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):

Data in developer tools

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:

text

But what happens if we remove the first object? Give it a try:

text

That’s weird. How about swapping the objects in the data array without changing their contents?

text

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:

text

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!



blog comments powered by Disqus