block by mbostock 7608400

Voronoi Arc Map

Full Screen

Hover to see flight routes associated with each airport. The interaction does not require JavaScript event handlers: it’s done using the CSS :hover state!

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.land {
  fill: #ddd;
}

.state-borders {
  fill: none;
  stroke: #fff;
}

.airport-arc {
  fill: none;
}

.airport:hover .airport-arc {
  stroke: #f00;
}

.airport-cell {
  fill: none;
  stroke: #000;
  stroke-opacity: 0.1;
  pointer-events: all;
}

</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

var projection = d3.geoAlbers()
    .translate([width / 2, height / 2])
    .scale(1280);

var radius = d3.scaleSqrt()
    .domain([0, 100])
    .range([0, 14]);

var path = d3.geoPath()
    .projection(projection)
    .pointRadius(2.5);

var voronoi = d3.voronoi()
    .extent([[-1, -1], [width + 1, height + 1]]);

d3.queue()
    .defer(d3.json, "/mbostock/raw/4090846/us.json")
    .defer(d3.csv, "airports.csv", typeAirport)
    .defer(d3.csv, "flights.csv", typeFlight)
    .await(ready);

function ready(error, us, airports, flights) {
  if (error) throw error;

  var airportByIata = d3.map(airports, function(d) { return d.iata; });

  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]);
  });

  airports = airports
      .filter(function(d) { return d.arcs.coordinates.length; });

  svg.append("path")
      .datum(topojson.feature(us, us.objects.land))
      .attr("class", "land")
      .attr("d", path);

  svg.append("path")
      .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
      .attr("class", "state-borders")
      .attr("d", path);

  svg.append("path")
      .datum({type: "MultiPoint", coordinates: airports})
      .attr("class", "airport-dots")
      .attr("d", path);

  var airport = svg.selectAll(".airport")
    .data(airports)
    .enter().append("g")
      .attr("class", "airport");

  airport.append("title")
      .text(function(d) { return d.iata + "\n" + d.arcs.coordinates.length + " flights"; });

  airport.append("path")
      .attr("class", "airport-arc")
      .attr("d", function(d) { return path(d.arcs); });

  airport.append("path")
      .data(voronoi.polygons(airports.map(projection)))
      .attr("class", "airport-cell")
      .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; });
}

function typeAirport(d) {
  d[0] = +d.longitude;
  d[1] = +d.latitude;
  d.arcs = {type: "MultiLineString", coordinates: []};
  return d;
}

function typeFlight(d) {
  d.count = +d.count;
  return d;
}

</script>