d3.jschartsgreat-circle

Drawing Connecting Lines ("Great Arcs") on a D3 Symbol Map


I am using Version 4 of the D3 Library and am, to this point, unable to draw connecting lines between points on a Symbol Map. In the example, from an earlier version of the library, drawing connecting lines is accomplished with this code:

// calculate the Great Arc between each pair of points
var arc = d3.geo.greatArc()
  .source(function(d) { return locationByAirport[d.source]; })
  .target(function(d) { return locationByAirport[d.target]; });

[snip]

// Draw the Great Arcs on the Chart.
g.selectAll("path.arc")
    .data(function(d) { return linksByOrigin[d.iata] || []; })
  .enter().append("svg:path")
    .attr("class", "arc")
    .attr("d", function(d) { return path(arc(d)); });

The comments are mine (and could be wrong), the code is from the Symbol Map example, above.

In version 4, d3.geo.greatArc() appears to have been deprecated in favor of d3.geoDistance(). I cannot say this for sure, but I can find no reference to greatArc in version 4. Unfortunately, I have no idea how to set up a call to geoDistance() to get the same information that greatArc() used to return. The documentation provided for geoDistance() is not enough for me to understand how to use it.

So, my question is: How do I draw lines between points (lat/long pairs) on a D3 Symbol Chart using Version 4 of the library?


Solution

  • The documentation on Spherical Shapes has it:

    To generate a great arc (a segment of a great circle), simply pass a GeoJSON LineString geometry object to a d3.geoPath. D3’s projections use great-arc interpolation for intermediate points, so there’s no need for a great arc shape generator.

    This means you can render great arcs by creating GeoJSON LineString objects containing the coordinates of both the start and end point in its coordinates property:

    {type: "LineString", coordinates: [[lonStart, latStart], [lonEnd, latEnd]]}
    

    As this is a standard GeoJSON object, a path generator (d3.geoPath) will then be able to digest it and – using the underlying projection – do great-arc interpolation to create a projected great arc.

    For a working demo have a look at Mike Bostock's Block built using D3 v4, which is similar to your example. Note, that the Block uses MultiLineString objects to account for multiple flights to and from any particular airport, which can be fed to the path generator the same way as the simple LineString objects, though. The example creates the great arcs like follows:

    1. While reading in the airports' information create empty MultiLineString objects for every airport:

      d3.queue()
          .defer(d3.csv, "airports.csv", typeAirport)
      
      // ...
      
      function typeAirport(d) {
        d[0] = +d.longitude;
        d[1] = +d.latitude;
        d.arcs = {type: "MultiLineString", coordinates: []};
        return d;
      }
      
    2. Iterate over the flights and push the coordinates of the source and target airport to the coordinates property of the MultiLineString object.

      flights.forEach(function(flight) {
        var source = airportByIata.get(flight.origin),
            target = airportByIata.get(flight.destination);
        source.arcs.coordinates.push([source, target]);
        target.arcs.coordinates.push([target, source]);
      });
      
    3. Create a suitable geo path generator.

      var path = d3.geoPath()
          .projection(projection)
          .pointRadius(2.5);
      
    4. Bind the data making it available for the path generator to actually draw the great arcs.

      var airport = svg.selectAll(".airport")
        .data(airports)
        .enter().append("g")
          .attr("class", "airport");
      
      // ...
      
      airport.append("path")
          .attr("class", "airport-arc")
          .attr("d", function(d) { return path(d.arcs); });  // great arc's path