Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting transition values in D3

I'm getting positions every 100ms and apply them to the DOM like this:

const div = d3.select(container).selectAll('div').data(positions)

div.enter()
  .append('div')

div.transition()
  .duration(100)
  .style({
    top: d => d.y,
    left: d => d.x,
  })

div.exit()
  .remove()

So the elements get a smooth animation to the new positions in the 100ms it takes to get the next positions. This works fine.

But I have different elements in the DOM that depend on the position of the first elements. They are rendered with the same position data, but in a different module of the software.

My problem is, that this transition interpolates data that is not available to the other modules. The elements of these other modules seem to deliver "optically wrong" visualizations, because they're based on the raw data.

Example: A point moves into a square and the square gets highlighted, but the position of the point is interpolated and the position the square uses to check if it should be highlighted is not. So the square gets highlighted even if the point isn't inside it.

How do I get around this? Can I grab those interpolated values somewhere?

like image 455
K.. Avatar asked Nov 09 '15 17:11

K..


2 Answers

The easiest way that I can think of is to use attrTween, styleTween or tween methods of the transition (https://github.com/mbostock/d3/wiki/Transitions#attrTween). They take a function that must return a function (interpolator). That interpolator will be called many times to perform transition, so you can keep calling your callback (cb) that will be notifying other modules about the change.

selection.styleTween('top', function(){
  var interpol = d3.interpolate(0,10); //interpolate from 0 to 10
  return function(t) { // this guy will be called many times to calc transition
    // t will be changing from 0 to 1
    var val = interpol(t);
    cb(val); // <-- callback that will notify other modules about the change in value
    return val; // <-- you have to return val so d3 transitions element
  }
});
like image 169
sergeyz Avatar answered Oct 25 '22 00:10

sergeyz


If I'm understanding you correctly, you want to take complete control of the tween function:

  div
    .transition()
    .duration(1000)
    .tween("postion", function(d) {
      var div = d3.select(this),
        startTop = parseInt(div.style('top')),
        startLeft = parseInt(div.style('left')),  
        yInterp = d3.interpolateRound(startTop, d.y),
        xInterp = d3.interpolateRound(startLeft, d.x);
      return function(t) {            
        div.style("top", yInterp(t) + "px");
        div.style("left", xInterp(t) + "px");
      };
    });

Working code:

<!DOCTYPE html>
<html>

  <head>
    <script data-require="[email protected]" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
  </head>

  <body>
    <div id="parent">
      <div style="width: 20px; top: 20px; left: 30px; height: 20px; background-color: steelblue; position: absolute"></div>
      <div style="width: 20px; top: 0px; left: 40px; height: 20px; background-color: orange; position: absolute"></div>
    </div>
    <script>
    
      var positions = [
        {x: 50, y: 50},
        {x: 100, y: 100}
      ];
    
      var div = d3.select('#parent')
        .selectAll('div')
        .data(positions);

      div
        .transition()
        .duration(1000)
        .tween("postion", function(d) {
          var div = d3.select(this),
            startTop = parseInt(div.style('top')),
            startLeft = parseInt(div.style('left')),  
            yInterp = d3.interpolateRound(startTop, d.y),
            xInterp = d3.interpolateRound(startLeft, d.x);
          return function(t) {            
            div.style("top", yInterp(t) + "px");
            div.style("left", xInterp(t) + "px");
          };
        });
    </script>
  </body>

</html>
like image 27
Mark Avatar answered Oct 24 '22 22:10

Mark