Continuing my learning of D3 I have started to delve into geographic visualizations. Some things I was interested in learning were:

  • How to translate latitude/longitude into x/y coordinates to be used in a SVG so, for instance, I could place dots on a map as map markers.
  • How to connect my “map markers” via SVG paths (cool looking paths not straight lines) and animate the drawing of those paths.
  • How to place pointers on the end of my paths so once they were drawn they convey direction.

I chose to use a silly visualization that drew lines between a list of coordinates representing stops on a fictitious road trip. Here’s a link to the end result.

I started with Mike Bostock’s Clipped Map Tiles example.

Translating latitude/longitude into x/y turns out is super simple. This involves simply passing the coordinates to your GEO Projection. I then simply took an array of coordinates and annotated them with x/y values via my projection and did the normal D3 enter selection thing to add them to my map using a transition so the markers would scale in. I delayed each transition based on a constant transition duration which is also used for drawing paths so that when one path completes the next marker shows up (nothing fancy, just basic timing).

To connect the dots I used d3.svg.diagonal and then found another D3 Example that illustrated how to animate my paths. The only thing I had to deal with was that the example is only dealing with a single path not multiple paths so I had to annotate my link objects with calculated path lengths for each and then pass a callback in to set the stroke-dasharray and stroke-dashoffset attributes like:

link.each(function(d){
  d.totalLength = this.getTotalLength();
});
...
link
  .attr("stroke-dasharray", function(d) { return d.totalLength + " " + d.totalLength; })
  .attr("stroke-dashoffset", function(d) { return d.totalLength; })

I can’t say I completely have my head wrapped around the SVG aspect of this so it’s a bit of magic from my perspective (at the moment).

For attaching pointers to the end of my lines I found this interesting svg that contains a <marker> that can be attached to the end of an SVG line, polyline or path.

<marker id="triangle"
  viewBox="0 0 10 10" refX="0" refY="5"
  markerUnits="strokeWidth"
  markerWidth="4" markerHeight="3"
  orient="auto">
  <path d="M 0 0 L 10 5 L 0 10 z" />
</marker>

I ended up using D3 to create that bit of svg content at runtime like:

svg.append('marker')
 .attr('id','pointer').attr('viewBox','0 0 10 10').attr('refX','0').attr('refY','5')
 .attr('markerUnits','strokeWidth').attr('markerWidth','4').attr('markerHeight','3').attr('orient','auto')
 .append('path').attr('d','M 0 0 L 10 5 L 0 10 z');

And added the pointer to my paths at the moment they were completed like:

...
.transition()
  // add pointer only upon completion of animation.
  .each('end',function(d){
    d3.select(this).attr('marker-end','url(#pointer)');
    ...
  })

It’s cool to note that marker-end is also a valid CSS property so if lines were static you could attach arrows to them via css. In fact that would be preferable except in this case if you do that the pointer shows up before the path is drawn which is funky to say the least. But for example via CSS you could do:

.link {
  fill: none;
  stroke: #777;
  stroke-width: 2px;
  marker-end: url('#pointer');
}

And of course the pointer itself can be styled via CSS.

#pointer {
  fill: orange;
}

Lastly I would guess that the MapBox folks would not want their unrestricted tile server content to be used in any sort of production scenario but in this instance I found several other unrestricted map ids that are commented out in the source.

var mapBoxMap = 'mapbox.natural-earth-2';
/*
mapBoxMap = 'examples.map-i86nkdio';
mapBoxMap = 'examples.map-0l53fhk2';
mapBoxMap = 'examples.map-i87786ca';
mapBoxMap = 'examples.map-zr0njcqy';
*/

I’m sure there are more out there like some of the cool ones in the Tile Mill Gallery though I don’t think some of those above are in the gallery.