Skip to content

Instantly share code, notes, and snippets.

@magjac
Last active August 13, 2017 08:45
Show Gist options
  • Save magjac/ed5ddb4fc7ad88c272ed55af04526644 to your computer and use it in GitHub Desktop.
Save magjac/ed5ddb4fc7ad88c272ed55af04526644 to your computer and use it in GitHub Desktop.
Smooth Chained Transitions Despite Long Computational Time at the End of the First Transition
height: 250

This example shows how to accomodate for a long computational time in the end event callback of the first of two chained transitions. The solution is to add a delay to the second transition that is equal to or larger than the computational time. See the problem in action here.

Timeline:

  • 0 s:
  • A small red rectangle is drawn
  • The first transition is scheduled to start at 2 s and end at 8 s
  • The second transition is scheduled to start at 8 s (after the first plus a delay of 2 s) and end at 12 s
  • 2 s:
  • The first transition starts and calls its event callback
  • The small rectangle starts to grow to a medium size rectange
  • 6 s:
  • The first transition ends and calls its event callback
  • 10 s:
  • The first transtion end event callback returns after wasting 2 s
  • The second transition starts
  • The rectangle starts to grow to a large rectangle and fade to blue
  • 12 s:
  • The second transition ends

Open the console if you want to see timestamped events during the transitions.

Note: a bug in d3-timer that is fixed, but at the time of this writing not yet released, requires this example to use a non official d3-timer.min.js.

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3-selection.v1.min.js"></script>
<!-- This script is d3-timer.min.js with https://github.com/d3/d3-timer/pull/28 that fixes https://github.com/d3/d3-timer/issues/27 -->
<script>!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t.d3=t.d3||{})}(this,function(t){"use strict";function n(){return h||(x(e),h=v.now()+y)}function e(){h=0}function o(){this._call=this._time=this._next=null}function i(t,n,e){var i=new o;return i.restart(t,n,e),i}function r(){n(),++_;for(var t,e=f;e;)(t=h-e._time)>=0&&e._call.call(null,t),e=e._next;--_}function u(){h=(d=v.now())+y,_=m=0;try{r()}finally{_=0,c(),h=0}}function l(){var t=v.now(),n=t-d;n>w&&(y-=n,d=t)}function c(){for(var t,n,e=f,o=1/0;e;)e._call?(o>e._time&&(o=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:f=n);s=t,a(o)}function a(t){_||(m&&(m=clearTimeout(m)),t-h>24?(t<1/0&&(m=setTimeout(u,t-v.now()-y)),p&&(p=clearInterval(p))):(p||(d=v.now(),p=setInterval(l,w)),_=1,x(u)))}var f,s,_=0,m=0,p=0,w=1e3,d=0,h=0,y=0,v="object"==typeof performance&&performance.now?performance:Date,x="object"==typeof window&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};o.prototype=i.prototype={constructor:o,restart:function(t,e,o){if("function"!=typeof t)throw new TypeError("callback is not a function");o=(null==o?n():+o)+(null==e?0:+e),this._next||s===this||(s?s._next=this:f=this,s=this),this._call=t,this._time=o,a()},stop:function(){this._call&&(this._call=null,this._time=1/0,a())}};t.now=n,t.timer=i,t.timerFlush=r,t.timeout=function(t,n,e){var i=new o;return n=null==n?0:+n,i.restart(function(e){i.stop(),t(e+n)},n,e),i},t.interval=function(t,e,i){var r=new o,u=e;return null==e?(r.restart(t,e,i),r):(e=+e,i=null==i?n():+i,r.restart(function n(o){o+=u,r.restart(n,u+=e,i),t(o)},e,i),r)},Object.defineProperty(t,"__esModule",{value:!0})});</script>
<script src="//d3js.org/d3-dispatch.v1.min.js"></script>
<script src="//d3js.org/d3-interpolate.v1.min.js"></script>
<script src="//d3js.org/d3-color.v1.min.js"></script>
<script src="//d3js.org/d3-ease.v1.min.js"></script>
<script src="//d3js.org/d3-transition.v1.min.js"></script>
<script src="//d3js.org/d3-scale.v1.min.js"></script>
<script src="//d3js.org/d3-zoom.v1.min.js"></script>
<div style="text-align: center;"></div>
<script>
var start = Date.now();
function elapsed() {
return Date.now() - start;
}
function waste(time) {
var t0 = Date.now();
while (Date.now() - t0 < time)
;
}
var svg = d3.select("div").append("svg")
.attr("width", 800)
.attr("height", 500);
var g = svg.append("g");
var start1Waste = 0; // Can't compensate for wasted time at start
var end1Waste = 2000; // Can be compensated with delay in next transition
var start2Waste = 0; // Can't compensate for wasted time at start
var end2Waste = 0;
g.selectAll("rect")
.data([10, 100, 200])
.enter()
.append("rect")
.attr("width", d => d)
.attr("height", d => d)
.attr("fill", "none")
.attr("stroke", "black")
var rect = g.append("rect")
.attr("width", 10)
.attr("height", 10)
.style("fill", "#d62728")
.transition()
.delay(2000)
.duration(4000)
.ease(d3.easeLinear)
.attr("width", 100)
.attr("height", 100)
.on("start", function () {
console.log('Start event 1', elapsed() / 1000);
waste(start1Waste);
console.log('Start event 1 returning', elapsed() / 1000);
})
.on("end", function () {
console.log('End event 1', elapsed() / 1000);
waste(end1Waste);
console.log('End event 1 returning', elapsed() / 1000);
})
.transition()
.delay(end1Waste)
.attr("width", 200)
.attr("height", 200)
.style("fill", "#1f77b4")
.on("start", function () {
console.log('Start event 2', elapsed() / 1000);
waste(start2Waste);
console.log('Start event 2 returning', elapsed() / 1000);
})
.on("end", function () {
console.log('End event 2', elapsed() / 1000);
waste(end2Waste);
console.log('End event 2 returning', elapsed() / 1000);
})
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment