Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do smooth transition for map reprojection in d3 js

I'd like to do a smooth transition between alber/orthographic in a mini app I am building, much like this example:

http://mbostock.github.io/d3/talk/20111018/#27

It seems like this smooth transition is broken in v3 however, with a rather choppy transition of the map paths:

https://www.evernote.com/shard/s236/sh/46b002bd-9c5b-4e9b-87ef-270c303eb677/2eaeebb267a3fc59df5a8447bbbcc58b/res/37917835-5aad-4509-b534-31a3e3034762/Worst_Tornado_Outbreaks_of_All_Time-20130611-074050.jpg.jpg?resizeSmall&width=832

Code is pretty straight forward, I initialize the map as albers, then run ortho() to update it.

function ortho() {
  var self = this, 
    h = 1000,
    w = document.width;

  this.projection = d3.geo.orthographic()
    .scale(500)
    .translate([ (w - 300) / 2, h / 2])
    .clipAngle(90)
    .rotate([90, 0, 0])
    .precision(.1);

  this.path = d3.geo.path()
    .projection(this.projection);

  //update path WITH transition
  d3.selectAll('path')
    .transition()
    .duration(900)
    .attr('d', app.path);

}

The map changes from albers to orthographic, but the transition is not smooth. Any thoughts would be great.

like image 520
benheb Avatar asked Jun 11 '13 13:06

benheb


1 Answers

If you interpolate the path using D3’s naïve string interpolator (d3.interpolateString), then the number of coordinates in the starting path and the number of the coordinates in the ending path must match exactly, including in the same order. But this is almost never the case due to clipping, cutting and resampling. Shape interpolation is possible (using multiple strategies), but it’s a hard problem to solve in the general case. See this explanation (part of the Path Transitions) for why naïve interpolation is insufficient.

Instead of interpolating the path, you want to interpolate the projection. Interpolating the projection does not require an exact correspondence between coordinates and therefore avoids interpolation artifacts. See these examples for a demonstration:

  • http://bl.ocks.org/mbostock/5731632
  • http://bl.ocks.org/mbostock/3711652
  • http://www.jasondavies.com/maps/transition/

As shown in the first example, here is an implementation you can use:

function interpolatedProjection(a, b) {
  var projection = d3.geo.projection(raw).scale(1),
      center = projection.center,
      translate = projection.translate,
      α;

  function raw(λ, φ) {
    var pa = a([λ *= 180 / Math.PI, φ *= 180 / Math.PI]), pb = b([λ, φ]);
    return [(1 - α) * pa[0] + α * pb[0], (α - 1) * pa[1] - α * pb[1]];
  }

  projection.alpha = function(_) {
    if (!arguments.length) return α;
    α = +_;
    var ca = a.center(), cb = b.center(),
        ta = a.translate(), tb = b.translate();
    center([(1 - α) * ca[0] + α * cb[0], (1 - α) * ca[1] + α * cb[1]]);
    translate([(1 - α) * ta[0] + α * tb[0], (1 - α) * ta[1] + α * tb[1]]);
    return projection;
  };

  delete projection.scale;
  delete projection.translate;
  delete projection.center;
  return projection.alpha(0);
}

Create the interpolated projection using two projections a and b, and then set the interpolated alpha to a value between 0 (for a) and 1 (for b).

like image 148
mbostock Avatar answered Oct 16 '22 00:10

mbostock