5

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?

K..
  • 4,044
  • 6
  • 40
  • 85

2 Answers2

3

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
  }
});
sergeyz
  • 1,339
  • 10
  • 14
  • Is there no way to get the already running transition without implementing my own? There is one, I like it, I just want its values. – K.. Nov 10 '15 at 13:28
  • 1
    I didn't get your question. Please rephrase. – sergeyz Nov 10 '15 at 17:40
  • Your example with the `styleTween` forces me to implement my own transition with the help of `d3.interpolate()` I just hoped for a solution that lets me use the transition that I already had described in the example and still get the interpolated values somehow :) – K.. Nov 10 '15 at 17:42
2

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="d3@3.5.3" 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>
Mark
  • 106,305
  • 20
  • 172
  • 230
  • The problem is, I don't interpolate from 0, but from the old position. But when I try to select the div in the tween function and get its top-style, I only get "auto" and not the last position :\ – K.. Nov 10 '15 at 14:10
  • 1
    @K.., I'm not sure I understand, you can get the starting positions in the function. See updates above. – Mark Nov 10 '15 at 14:29
  • In your example you're using 0, but If I do this, everytime `d.x` changes It gets interpolated from 0 not from it's last position. – K.. Nov 10 '15 at 14:35
  • 1
    @K.., Take another look at the code above. I modified it to start at the last position and not from 0. – Mark Nov 10 '15 at 14:36
  • I always get `'auto'` when I call `div.style('top')`. Even I set the `style('top')` to a not-auto start-position in `enter()` – K.. Nov 10 '15 at 14:40
  • 1
    @K.. `auto` means the `top` and/or `left` style is not set. You could calculate it's position with something like [this](http://stackoverflow.com/a/288708/16363). – Mark Nov 10 '15 at 14:47
  • 1
    @K.., I can set them in the append without any trouble: http://plnkr.co/edit/A3IyHdUAikaFQRw4mBMo?p=preview, if you want me to look further, you'll have to reproduce your situation. – Mark Nov 10 '15 at 14:59
  • Thanks man, I was missing the `+ 'px'` in the `style('top', d => d.y)` – K.. Nov 10 '15 at 15:23