Create a gist now

Instantly share code, notes, and snippets.

@veltman /README.md
Last active Jul 3, 2016

Transition hacking with chains

Similar to this transition hacking example, this turns an asynchronous d3 transition into something that can be paused/fast-forwarded/rewound while still using the nice syntax, easing, interpolation, etc. of d3.transition.

This version respects chained transitions and delays by expecting an absolute time value in milliseconds rather than a relative time value between 0 and 1.

Caveat #1: This doesn't yet work if you have multiple transitions in a chain modifying the same property.

Caveat #2: this is probably a pretty bad idea! Maybe it can be done by tinkering directly with the internal timer instead?

d3.selection.prototype.record = function(realtime) {
var self = this,
tweeners = [];
// Get tweeners on each element in the selection
self.each(function(d,i){
// using .count should work as long as it's called right after the transition is created?
// can use .active if you wait for the "start" event
var node = this,
pending = getTransitions.call(this);
pending.forEach(function(transition){
// Probably unnecessary?
var ease = transition.ease || id;
transition.tween.values().forEach(function(tween){
// Create a tweener with the tween function and the element's datum and index
// Tweens with no change return false
var tweener = (tween.call(node,d,i) || noop).bind(node);
// Function that calls the tweener with an eased time value
tweeners.push(function(t){
if (realtime) {
t = relativeTime(t,transition.duration,transition.delay);
}
tweener(ease(t));
});
});
});
});
// Return a function that takes relative time between 0 and 1,
// or an absolute number of ms
return function(t){
// Interrupt any active transitions on the selection
// Cancel any scheduled transitions
self.interrupt().transition();
// Apply every tweener function with the provided value t
// TODO: only call one tweener per attribute based on the time
tweeners.forEach(function(tweener){
tweener(t);
});
};
function getTransitions(){
return d3.entries(this.__transition__)
.filter(function(tr){
return tr.key !== "active" && tr.key !== "count";
})
.map(function(tr){
return tr.value;
});
}
function relativeTime(ms,duration,delay) {
return Math.min(1,Math.max(0,(ms - delay)/duration));
}
function noop() {}
function id(d) { return d; }
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
text-align: center;
font: 16px sans-serif;
}
button {
margin: 0 0.5em;
}
circle {
stroke: #000;
stroke-width: 1.5px;
}
</style>
<body>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.14/d3.min.js"></script>
<script src="d3-record.js"></script>
<script>
var margin = {top: 100, right: 100, bottom: 100, left: 100},
width = 960 - margin.left - margin.right,
height = 440 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(d3.range(5))
.rangePoints([0, width]);
var y = x.copy()
.rangePoints([0, height]);
var color = d3.scale.linear()
.domain(d3.extent(x.domain()))
.range(["hsl(297,50%,47%)", "hsl(81,78%,61%)"])
.interpolate(d3.interpolateHcl);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var dots = svg.selectAll("circle")
.data(x.domain())
.enter()
.append("circle")
.attr("r",20)
.attr("cx",0)
.attr("cy",y)
.style("fill",color);
// Declare a normal transition
dots.transition()
.duration(2500)
.delay(function(d){
return d * 100;
})
.attr("cx",x)
.transition()
.attr("r",40)
.filter(function(d){
return d % 2;
})
.transition()
.style("opacity",0);
// "Record" the transition into a function that can jump to any time
// `true` means it expects an absolute time in ms
var jumpToTime = dots.record(true);
d3.select("body").append("div")
.text("Jump to: ")
.selectAll("button")
.data(d3.range(0,8500,1000))
.enter()
.append("button")
.text(function(d){
return Math.round(d/1000) + " sec";
})
.on("click",jumpToTime);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment