Created
November 26, 2013 23:22
-
-
Save reimund/7668091 to your computer and use it in GitHub Desktop.
D3.js: Animate path point by index using custom interpolator.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<script src="http://mbostock.github.com/d3/d3.v2.js"></script> | |
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script> | |
<style>path { stroke: steelblue; stroke-width: 1; fill: none; }</style> | |
<body> | |
<script> | |
$(window).load(function() { | |
// Animate between these two straight lines. | |
var d0 = [[ | |
{ x: 0, y: 1 }, | |
{ x: 1, y: 1 }, | |
{ x: 2, y: 1 }, | |
{ x: 3, y: 1 }, | |
]]; | |
var d1 = [[ | |
{ x: 0, y: 3 }, | |
{ x: 1, y: 3 }, | |
{ x: 2, y: 3 }, | |
{ x: 3, y: 3 }, | |
]]; | |
var w = h = 500, | |
duration = 1000, | |
x = d3.scale.linear().domain([-1, 4]).range([0, w]), | |
y = d3.scale.linear().domain([-2, 4]).range([0, h]); | |
var line = d3.svg.line() | |
.x(function(d) { return x(d.x); }) | |
.y(function(d) { return y(d.y); }) | |
var svg = d3.select('body').append('svg:svg') | |
.attr('width', w) | |
.attr('height', h); | |
var circles = svg.selectAll('circle') | |
.data(d0[0]) | |
.enter().append('svg:circle') | |
.attr('fill', '#f00') | |
.attr('r', 10) | |
.attr('transform', function(d, i) { return 'translate(' + x(d.x) + ', ' + y(d.y) + ')'; }) | |
.data(d1[0]) | |
.transition() | |
.duration(duration) | |
.attr('r', 10) | |
.attrTween('transform', function(d, i, a) { | |
var v = 'translate(' + x(d.x) + ', ' + y(d.y) + ')'; | |
return myInterpolateString(a, v, i, 4, interpolateNumberByIndex); | |
}); | |
var path = svg.selectAll('path') | |
.data(d0) | |
.enter().append('svg:path') | |
.attr('d', line) | |
.data(d1) | |
.transition() | |
.duration(duration) | |
.attr('d', line) | |
.attrTween('d', function(d, i, a) { | |
return myInterpolateString(a, line(d1[0]), undefined, 4, interpolateNumberByIndex); | |
}); | |
}); | |
function interpolateNumberByIndex(a, b, i, n) { | |
b -= a = +a; | |
return function(t) { | |
var x = Math.max(Math.min((2*t + (1-i/n)) - 1, 1), 0); | |
return a + b * x; | |
}; | |
} | |
var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; | |
// Taken from the d3 source and modified to call interpolateNumberByIndex. | |
function myInterpolateString(a, b, index, count, interpolator, easing) { | |
var m, // current match | |
i, // current index | |
j, // current index (for coalescing) | |
s0 = 0, // start index of current string prefix | |
s1 = 0, // end index of current string prefix | |
s = [], // string constants and placeholders | |
q = [], // number interpolators | |
n, // q.length | |
o, | |
l, | |
r; | |
// Coerce inputs to strings. | |
a = a + "", b = b + ""; | |
// Reset our regular expression! | |
d3_interpolate_number.lastIndex = 0; | |
// Find all numbers in b. | |
for (i = 0; m = d3_interpolate_number.exec(b); ++i) { | |
if (m.index) s.push(b.substring(s0, s1 = m.index)); | |
q.push({i: s.length, x: m[0]}); | |
s.push(null); | |
s0 = d3_interpolate_number.lastIndex; | |
} | |
if (s0 < b.length) s.push(b.substring(s0)); | |
if (null == count) | |
count = q.length; | |
// Find all numbers in a. | |
for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { | |
o = q[i]; | |
if (o.x == m[0]) { // The numbers match, so coalesce. | |
if (o.i) { | |
if (s[o.i + 1] == null) { // This match is followed by another number. | |
s[o.i - 1] += o.x; | |
s.splice(o.i, 1); | |
for (j = i + 1; j < n; ++j) q[j].i--; | |
} else { // This match is followed by a string, so coalesce twice. | |
s[o.i - 1] += o.x + s[o.i + 1]; | |
s.splice(o.i, 2); | |
for (j = i + 1; j < n; ++j) q[j].i -= 2; | |
} | |
} else { | |
if (s[o.i + 1] == null) { // This match is followed by another number. | |
s[o.i] = o.x; | |
} else { // This match is followed by a string, so coalesce twice. | |
s[o.i] = o.x + s[o.i + 1]; | |
s.splice(o.i + 1, 1); | |
for (j = i + 1; j < n; ++j) q[j].i--; | |
} | |
} | |
q.splice(i, 1); | |
n--; | |
i--; | |
} else { | |
// XXX: Modified to use our own number interpolator. | |
l = null == index ? i : index; | |
o.x = interpolator(parseFloat(m[0]), parseFloat(o.x), l, count, easing); | |
} | |
} | |
// Remove any numbers in b not found in a. | |
while (i < n) { | |
o = q.pop(); | |
if (s[o.i + 1] == null) { // This match is followed by another number. | |
s[o.i] = o.x; | |
} else { // This match is followed by a string, so coalesce twice. | |
s[o.i] = o.x + s[o.i + 1]; | |
s.splice(o.i + 1, 1); | |
} | |
n--; | |
} | |
// Special optimization for only a single match. | |
if (s.length === 1) { | |
return s[0] == null | |
? (o = q[0].x, function(t) { return o(t) + ""; }) | |
: function() { return b; }; | |
} | |
// Otherwise, interpolate each of the numbers and rejoin the string. | |
return function(t) { | |
for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); | |
return s.join(""); | |
}; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment