Compare / diff new data with previous d3.js update data

I would like to present the difference between the current dataset and the previous dataset calculated by the client.

Imagine that I already have three circles attached to the data [1, 2, 3] . Now I would like to update the data and do something based on the difference between the new values ​​and the old?

 var new_data = [2, 2, 2]; // This is the new data I'd like to compare with the old svg.selectAll("circle").data(new_data) .transition().duration(2000) .attr("fill", "red") // eg I'd like to colour the circles red if the change // is negative, blue if positive, black if no change. .attr("r", function(d) { return d * 10; }); 

Here is an example JSFiddle with the above code.

+8
javascript
source share
2 answers

You have two options for saving old data attached to an element to identify changes after a new data connection.

The first option, as you suggested, is to use data attributes. This SO Q & A describes this approach . What to consider:

  • all your data values ​​will be forcibly bound to strings.
  • you will need a separate method / method attribute for each aspect of the data.
  • you manipulate the DOM so that it can slow down if you have a lot of elements or a lot of data for each
  • data is now part of the DOM, so you can save it with an image or access other scripts

The second option is to store the data as the Javascript property of the DOM object for the element, just as d3 stores the active data as the __data__ property . I discussed this method in this forum post .

General approach:

 selection = selection.property(" __oldData__", function(d){ return d; } ); //store the old data as a property of the node .data(newData, dataKeyFunction); //over-write the default data property with new data //and store the new data-joined selection in your variable selection.enter() /*etc*/; selection.attr("fill", function(d) { // Within any d3 callback function, // you can now compare `d` (the new data object) // with `this.__oldData__` (the old data object). // Just remember to check whether `this.__oldData__` exists // to account for the just-entered elements. if (this.__oldData__) { //old data exists var dif = d.value - this.__oldData__.value; return (dif) ? //is dif non-zero? ( (dif > 0)? "blue" : "red" ) : "black" ; } else { return "green"; //value for new data } }); selection.property("__oldData__", null); //delete the old data once it no longer needed //(not required, but a good idea if it using up a lot of memory) 

Of course, you can use any name for the old data property, it just means that a lot of "_" characters will be added around it to avoid spoiling any of the browser-based DOM properties.

+13
source share

With D3 v4, you can use the built-in support for local variables . The internal implementation is basically the same as proposed by AmeliaBR's answer , but it frees you from having to store old data yourself. When using d3.local() you can set the value bound to a specific DOM node, hence the local variable name. In the snippet below, this is done for each circle line

 .each(function(d) { previousData.set(this, d) }); // Store previous data locally... 

You can further retrieve this value for any particular node that it has been saved:

 .attr("fill", function(d) { var diff = previousData.get(this) - d; // Retrieve previously stored data. return diff < 0 ? "red" : diff > 0 ? "blue" : "black"; }) 

This complete code might look something like this:

 var old_data = [1, 2, 3]; // When the data gets updated I'd like to 'remember' these values // Create a local variable for storing previous data. var previousData = d3.local(); var svg = d3.select("body").append("svg") .attr("width", 500) .attr("height", 200); var p = d3.select("body") .append("p") .text("Old data. Click on the circles to update the data."); var circle = svg.selectAll("circle") .data(old_data) .enter().append("circle") .attr("fill", "black") .attr("r", function(d) { return d * 10; }) .attr("cx", function(d){ return d * 40; }) .attr("cy", function(d){ return d * 40; }) .each(function(d) { previousData.set(this, d) }); // Store previous data locally on each node svg.on("click", function(d) { p.text("Updated data."); var new_data = [2, 2, 2]; // This is the new data I'd like to compare with the old circle.data(new_data) .transition().duration(2000) .attr("fill", function(d) { var diff = previousData.get(this) - d; // Retrieve previously stored data. return diff < 0 ? "red" : diff > 0 ? "blue" : "black"; }) .attr("r", function(d) { return d * 10; }); }); 
 <script src="https://d3js.org/d3.v4.js"></script> 
+6
source share

All Articles