I'm adapting Mike Bostock's point along path interpolation model to accept an array of n individual paths and interpolate along each consecutively. Being relatively new to D3 the code below shows as far as I've got, which is to run the point interpolation for both paths concurrently. Now I'm a bit stuck over how to restructure this to make the process consecutive (with just one moving object). Really I need to be able to stop between paths to listen for a mouseclick, but I can figure out that code once the structure is there. Most grateful for assistance.
Here's the jsfiddle.
Code for posterity:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
path {
fill: none;
stroke: #000;
stroke-width: 3px;
}
circle {
stroke: #fff;
stroke-width: 3px;
}
</style>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script><script>
var pathdata = [
[[240, 100],
[290, 200],
[340, 50]],
[[340, 50],
[90, 150],
[140, 50],
[190, 200]]
];
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var paths = svg.selectAll("path")
.data(pathdata)
.enter()
.append("path")
.attr("d", d3.svg.line())
.attr("id",function(d, i) { return "path" + i });
// plot path vertices
svg.selectAll(".point")
.data([].concat.apply([], pathdata))
.enter().append("circle")
.attr("r", 5)
.attr("fill", "red")
.attr("transform", function(d) { return "translate(" + d + ")"; });
// interpolate along path0
var circle = svg.append("circle")
.attr("r", 10)
.attr("fill", "steelblue")
.attr("transform", "translate(" + pathdata[0][1] + ")")
.transition()
.duration(4000)
.attrTween("transform", translateAlong(d3.select("#path0")[0][0]));
// interpolate along path1
var circle = svg.append("circle")
.attr("r", 10)
.attr("fill", "steelblue")
.attr("transform", "translate(" + pathdata[1][1] + ")")
.transition()
.duration(4000)
.attrTween("transform", translateAlong(d3.select("#path1")[0][0]));
function translateAlong(path) {
console.log(path);
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
</script>
</body>
</html>
I'm also wondering if it might be better to format the input data along one of the following lines?
// 3rd field for path id
var points_alt1 = [
[240, 100, 0],
[290, 200, 0],
[340, 50, 0],
[340, 50, 1],
[90, 150, 1],
[140, 50, 1],
[190, 200, 1]
]
or..
// 3rd field for interpolation end-points
var points_alt2 = [
[240, 100, 0],
[290, 200, 0],
[340, 50, 1],
[340, 50, 0],
[90, 150, 0],
[140, 50, 0],
[190, 200, 1]
]
Create a function that takes as params a d3 selection of paths and an integer index of the path (within the selection) along which you want to animate. This function finds the appropriate path within that selection, starts up a transition of a circle along it, and subscribes to the
'end'
event of the transition, at which point it triggers the next animation.Here's the working fiddle
P.S
I wouldn't restructure the data as you proposed, because you need to have distinct SVG
<path>
elements – one for each "chapter" in the sequence. Having a distinct array for each of path, as you do now, is what enables you to create these<path>
s viadata()
binding.Depending on what you're trying to achieve, you may even want to further nest each path array, wrapping it with an object
{}
, to hold meta data about the path: