Skip to content

Instantly share code, notes, and snippets.

@reimund
Created November 26, 2013 23:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reimund/7668091 to your computer and use it in GitHub Desktop.
Save reimund/7668091 to your computer and use it in GitHub Desktop.
D3.js: Animate path point by index using custom interpolator.
<!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