Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate an object along a GeoJSON path using d3.js?

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:

  1. Create a transition and set its duration
  2. For each frame of the transition, use the % complete to find the coordinates along the path
  3. Move the object to the coordinates found in step 2

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 +")";
    }
});
like image 305
Zac Avatar asked Jul 22 '13 18:07

Zac


1 Answers

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/

like image 67
Zac Avatar answered Sep 22 '22 07:09

Zac