bongbang
bongbang

Reputation: 1712

Factoring out attributes for reuse in D3 transition

In the minimal example below, an SVG element transitions to a different state and then reverts to the original. How can I factor out the original attributes so that they won't have to be repeated? selection.each() and transition.each() have been giving me unexpected confusing results.

var circle = d3.select('svg').append('circle')
  .attr('cx', 50) // this part should be factored out
  .attr('cy', 130)
  .attr('r', 25)
  .attr('fill', 'blue');

circle.transition().duration(1000).delay(500)
  .attr('cx', 200)
  .attr('cy', 50)
  .attr('r', 50)
  .attr('fill', 'red')
  .transition().duration(1000).delay(500)
  .attr('cx', 50) // repeated
  .attr('cy', 130)
  .attr('r', 25)
  .attr('fill', 'blue');
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>

<svg width='300' height='170'>
</svg>

Upvotes: 0

Views: 379

Answers (2)

bongbang
bongbang

Reputation: 1712

What I needed was call() instead of each(). I knew it was possible!

function revert(selection) {
  selection
  .attr('cx', 50)
  .attr('cy', 130)
  .attr('r', 25)
  .attr('fill','blue');
}

var circle = d3.select('svg').append('circle')
  .call(revert);

circle.transition().duration(1000).delay(500)
  .attr('cx', 200)
  .attr('cy', 50)
  .attr('r', 50)
  .attr('fill','red')
  .transition().duration(1000).delay(500)
  .call(revert);
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>

<svg width='300' height='170'>
</svg>

Upvotes: 0

altocumulus
altocumulus

Reputation: 21578

One solution might be to leverage the power of D3's data binding. You could define configuration objects containing both, the original values to be restored later on as well as the values to which the element should transition to. By binding this information to the DOM elements created by D3 you may access them in later calls. These configuration objects may look like this:

var circle = [{   // D3 binds data contained in arrays
  orig: {         // The original values
    "cx": 50,
    // ...
  },
  trans: {        // The values to transition to
    "cx": 200,
    // ...
  }
}];

Throwing in the d3-selection-multi module you can directly use the above defined configuration objects by handing them to the selection.attrs() method:

The function’s return value must be an object with string values, which are then used to set attributes on the current element.

// Array of configuration objects
var circle = [{
  orig: {
    "cx": 50,
    "cy": 130,
    "r":  25,
    "fill": "red"
  },
  trans: {
    "cx": 200,
    "cy": 50,
    "r":  50,
    "fill": "blue"
  }
}];

// Create new elements by binding data and using data joins
var circle = d3.select('svg').selectAll('circle')
  .data(circle)                               // Bind configuration objects to elements
  .enter().append('circle')
    .attrs(function(d) { return d.orig; });   // Use the original values

circle.transition().duration(1000).delay(500)
    .attrs(function(d) { return d.trans; })   // Transition to new values
  .transition().duration(1000).delay(500)
    .attrs(function(d) { return d.orig; });   // And transition back to original values
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>

<svg width='300' height='170'>
</svg>

Upvotes: 1

Related Questions