1

I have a force simulation graph using d3 v4. Each node is bound to some data, which I use for example to determine the radius of each node.

The underlying bound data is updated periodically, and for some nodes it changes, and for others it stays the same.

I want to be able to select just those DOM elements for which the bound data changes, so that I can highlight these elements on my graph.

For example, suppose that initially my data (which is bound to the forceSimulation nodes) is:
data = [{id: 1, type: 0}, {id: 2, type: 1}] and it is then updated to:
data = [{id: 1, type: 1}, {id: 2, type: 1}] I'd like to be able to select just the DOM element that corresponds to id=1 so that I can for example make the colour change temporarily.

The update selection contains both id=1 and id=2 - I could maintain an internal mapping of previous data values and compare, but this seems inefficient.

Thanks, Adam

Adam Dossa
  • 228
  • 1
  • 8
  • 1
    `d3.local()`? Second answer from here demos a possible model: https://stackoverflow.com/questions/23409250/compare-diff-new-data-with-previous-data-on-d3-js-update – Ryan Morton Nov 09 '17 at 17:53

1 Answers1

2

If a single datum attribute can be checked to see if the bound data has changed, one method would be to track that attribute as a property using selection.property and a custom property such as type. When appending the data you could define the property fairly easily:

  .append("circle")
  .property("type",function(d) { return d.type; });

Then, when updating, you could filter based on which data are matching or not matching the property:

  circles.data(newdata)
    .filter(function(d) {
      return d.type != d3.select(this).property("type")
    })

This filter will return those elements that have changed their type. Now we can re-assign the property to reflect the new type and transition those filtered elements.

The snippet below should demonstrate this, the datum is just a number one or two (represented by blue and orange), and is used to set the property type. Click on the svg to update the data, only those circles which change their datum will temporarily change their radius, while also changing their color to reflect their new datum.

var svg = d3.select("body")
  .append("svg")
  .attr("width",400)
  .attr("height",400);
  
var circles = svg.selectAll("circle")
  .data(data())
  .enter("circle")
  .append("circle")
  .attr("cy",function(d,i) {
    return Math.floor(i/5) * 40 + 20;
  })
  .attr("cx", function(d,i) {
    return i%5 * 40 + 20
  })
  .attr("r", 8)
  .attr("fill",function(d) { return (d) ? "steelblue" : "orange"})
  .property("type",function(d) { return d; });
  
// update on click:
svg.on("click", function() {
  circles.data(data())
    .filter(function(d) {
      return d != d3.select(this).property("type") // filter out unchanged data
    })
    .property("type",function(d) { return d; })  // update to reflect new data
    .transition()
    .attr("r", 20)
    .attr("fill","crimson")
    .duration(500)
    .transition()
    .attr("fill",function(d) { return (d) ? "steelblue" : "orange" })
    .attr("r",8)
    .duration(500);
})
  

function data() {
  var output = [];
  d3.range(20).map(function(d) {
    output.push(Math.round(Math.random()));
  })
  return output;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Andrew Reid
  • 37,021
  • 7
  • 64
  • 83