I'm using D3.js to generate and render a path from a GeoJSON file. That works fine, but now I'd like to animate an object along that path. I know how to do that using D3 and standard SVG:
That's simple. But the problem I'm having is that d3.geo.path() doesn't seem to return any length or position data like a standard D3 path object (such as the helpful getPointAtLength() method). So I'm unable to find the x,y coordinates of a point at, say, 25% along the path.
Is there a way to obtain this data? (Or is there a better way, such as converting the d3.geo.path() to a regular D3 path?)
Below is a truncated version of my code; a live example is here: http://jsfiddle.net/5m35J/4/
json = {
... // snipped for brevity
};
// Draw a GeoJSON line on the map:
map = $('#map');
xy = d3.geo.mercator().scale(480000).translate([630700, 401100]);
path = d3.geo.path().projection(xy);
vis = d3.select("#map")
.append("svg:svg")
.attr("width", 960)
.attr("height", 600);
vis.append("svg:g")
.attr("class", "route")
.selectAll("path")
.data(json.features)
.enter()
.append("svg:path")
.attr("d", path)
.attr("fill-opacity", 0.5)
.attr("fill", "#fff")
.attr("stroke", "#333");
// Draw a red circle on the map:
//len = 100; // how do I find the length of the path?
origin_x = 100;
origin_y = 100;
group = vis.append("svg:g");
circle = group.append("circle")
.attr({
r: 10,
fill: '#f33',
transform: function () {
//var p = path.getPointAtLength(0)
//return "translate(" + [p.x, p.y] + ")";
return "translate("+ origin_x +","+ origin_y +")";
}
});
// Animate the circle:
duration = 5000;
circle.transition()
.duration(duration)
.ease("linear")
.attrTween("transform", function (d, i) {
return function (t) {
//var p = path.node().getPointAtLength(len*t) // d3.geo.path() doesn't provide a getPointAtLength() method!
//return "translate("+[p.x,p.y]+")"
var current_x = origin_x + origin_x * t;
var current_y = origin_y + origin_y * t;
return "translate("+ current_x +","+ current_y +")";
}
});
Well, I figured it out, but I'm not completely sure if my solution is the "right" way to do it. Basically, I used D3 to select the raw SVG elements that were created by the d3.geo.path() object.
Note the changes to the targetPath, pathNode, and pathLength variables, and also to the transform() and attrTween() functions:
// Draw a red circle on the map:
group = vis.append("svg:g");
var targetPath = d3.selectAll('.route')[0][0],
pathNode = d3.select(targetPath).selectAll('path').node(),
pathLength = pathNode.getTotalLength();
circle = group.append("circle")
.attr({
r: 10,
fill: '#f33',
transform: function () {
var p = pathNode.getPointAtLength(0)
return "translate(" + [p.x, p.y] + ")";
}
});
// Animate the circle:
duration = 10000;
circle.transition()
.duration(duration)
.ease("linear")
.attrTween("transform", function (d, i) {
return function (t) {
var p = pathNode.getPointAtLength(pathLength*t);
return "translate(" + [p.x, p.y] + ")";
}
});
Live example is here: http://jsfiddle.net/5m35J/6/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With