Skip to content

Instantly share code, notes, and snippets.

@Andrew-Reid
Last active October 23, 2016 23:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Andrew-Reid/35d89fbcfbcfe9e819908ea77fc5bef6 to your computer and use it in GitHub Desktop.
Save Andrew-Reid/35d89fbcfbcfe9e819908ea77fc5bef6 to your computer and use it in GitHub Desktop.
Animated Paths in a Flow Map

This is an animated flow map (line darkness representing volume of trade). Each path is animated to enter and exit the map. Click "open" above to view the entire map.

The map uses an intermediary point between departure and destination locations to aesthetically bend the paths, which also causes them to largely cross water as opposed to land. This also allows a greater differentiation of points on the US west coast, as direct lines would overlap entirely.

The map represents trade from Victoria, Departure Bay (Nainamo), Burrard Inlet (Vancouver), and Newcastle Island in around 1859/1860. The selected three exports represent the most common commodities exported from the region based on the data used to build this dataset. The stone, from Newcastle Island, was used in many notable public buildings, including the US Mint building in San Francisco.

dept via arr vol
Victoria VictoriaOffshoreSF San Francisco 69042
Victoria VictoriaOffshoreOS Portland 4908.2
Victoria VictoriaOffshoreAS Honolulu 5968
Victoria VictoriaOffshoreSC San Diego 1000
Victoria VictoriaOffshoreMX Mazatlan 600
Victoria VictoriaOffshoreSC San Pedro 430
Victoria VictoriaOffshoreAK Ounalaska 332
Victoria VictoriaOffshoreAK Sitka 2565.25
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<script src="http://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v1.min.js"></script>
<style>
.commodityButton, .commodityLabel { cursor:pointer; }
.route { fill: none; stroke: #333; stroke-width: 2px; }
.land { fill:#a8ddb5; }
svg { background: #43a2ca; }
.port { fill: #8c6bb1; stroke:#084081; stroke-width: 2px; }
.source-port { fill: #fdae6b; stroke:#d94801; stroke-width: 2px; }
</style>
</head>
<body>
<div id="map"> </div>
<script>
var width = 950; var height = 650;
var commodities = ["lumber","coal","stone"];
var projection = d3.geoKavrayskiy7()
.scale(302)
.rotate([-205,-10])
.translate([width/2,height/2])
.precision(0.1);
var path = d3.geoPath().projection(projection);
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
// Create a few groups to layer elements correctly
var g1 = svg.append("g"); var g2 = svg.append("g");
d3.json("world.json",function(error,world) {
g1.insert("path")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
// Place and label Victoria
g2.append("circle")
.attr("cx", function() { return projection(ports["Victoria"].loc)[0];})
.attr("cy", function() { return projection(ports["Victoria"].loc)[1];})
.attr("r",6)
.attr("class","source-port source");
g2.append("text")
.attr("x", function() { return projection(ports["Victoria"].loc)[0] + 5;})
.attr("y", function() { return projection(ports["Victoria"].loc)[1] + 0;})
.text("Victoria");
// Add some buttons
var buttons = g2.selectAll(".commodityButton")
.data(commodities)
.enter()
.append("rect")
.attr("x", function(d,i) { return i * 90 + 20} )
.attr("y", 20)
.attr("rx",20).attr("ry",20).attr("width",80).attr("height",80)
.attr("fill","#aaa").attr("stroke","#999")
.attr("class","commodityButton")
.on("click",function(d) { replaceCommodity(d); });
var text = g2.selectAll(".commodityLabel")
.data(commodities).enter()
.append("text")
.attr("x",function (d,i) { return i * 90 + 60} )
.attr("y",65)
.attr("text-anchor","middle")
.text(function(d) { return d; })
.attr("class","commodityLabel")
.on("click",function(d) { replaceCommodity(d); });
// Start with a set of routes
drawCommodity("coal");
});
///////////////////////////////////////////////////////////////////////////////////////////////
// Draw a set of routes
function drawCommodity(commodity) {
d3.csv(commodity + ".csv", function (error, routes) {
var maxVolume = d3.max(routes, function(d) { return d.vol; });
var line = d3.line().curve(d3.curveBasis);
routes.forEach( function(d,i) {
var routePath = g1.append("path")
.attr("d", line ([ projection(ports[d.dept].loc),projection(intermediatePoints[d.via]),projection(ports[d.arr].loc)]) )
.attr("class", d.arr.replace(" ", "-") + " " + "route")
.attr("stroke-opacity", Math.sqrt(d.vol / maxVolume) )
.attr("stroke-width", 1 );
var totalLength = routePath.node().getTotalLength() + 10;
routePath
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.on("start", drawPorts(d) )
.attr("stroke-dashoffset", 0);
});
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Replace a commodity
function replaceCommodity(commodity) {
d3.selectAll(".port").remove();
d3.selectAll(".port-label").remove();
var routes = g1.selectAll(".route")
.transition()
.duration(1000)
.attr("stroke-dashoffset", function() { return -this.getTotalLength(); })
.transition().duration(0).remove();
drawCommodity(commodity);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Draw ports and labels associated with routes
function drawPorts(d) {
var point = g2.append("circle")
.attr("cx", projection(ports[d.arr].loc)[0])
.attr("cy", projection(ports[d.arr].loc)[1])
.attr("r",4.5)
.attr("class","port")
.attr("opacity",0.1)
.transition().duration(2000)
.attr("id",d.arr.replace(" ", "-"))
.attr("opacity",1);
var text = g2.append("text")
.attr("x", projection(ports[d.arr].loc)[0] + ports[d.arr]["off"][0])
.attr("y", projection(ports[d.arr].loc)[1] - ports[d.arr]["off"][1])
.text(d.arr)
.attr("opacity",0.1)
.attr("class","port-label")
.transition().duration(2000)
.attr("opacity",1)
.attr("id",d.arr.replace(" ", "-") + "-label");
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Place references and text offsets
var ports = {
Honolulu:{loc:[-157.816667,21.3],off:[-67,0],},
Victoria:{loc:[-123.365556,48.428611],off:[5,5],},
"San Francisco":{loc:[-122.416667,37.783333],off:[5,8],},
Dunedin:{loc:[170.502954,-45.878666],off:[10,5],},
Sydney:{loc:[151.209444,-33.859972],off:[-45,10],},
Yokohama:{loc:[139.653905,35.455161],off:[5,15],},
"Hong Kong":{loc:[114.204277,22.330869],off:[-50,11],},
Callao:{loc:[-77.131981,-11.876547],off:[10,5],},
Batavia:{loc:[106.850648,-6.209157],off:[-20,10],},
Shanghai:{loc:[121.475449,31.230331],off:[-60,7],},
Melbourne:{loc:[144.96668,-37.812339],off:[-75,-3],},
Valparaiso:{loc:[-71.616667,-33.05],off:[6,-5],},
Lambayaque:{loc:[-79.902088,-6.699651],off:[10,5],},
Arica:{loc:[-70.333333,-18.483333],off:[8,-2],},
Iquique:{loc:[-70.15,-20.216667],off:[8,-4],},
Coquimbo:{loc:[-71.345873,-29.949609],off:[2,7],},
Tahiti:{loc:[-149.561182,-17.538858],off:[10,5],},
Guaymas:{loc:[-110.900017,27.920381],off:[10,5],},
Sitka:{loc:[-135.330889,57.052526],off:[10,5],},
Ounalaska:{loc:[-166.529263,53.884106],off:[-67,5],},
Portland:{loc:[-122.675930,45.532801],off:[10,0],},
"San Diego":{loc:[-117.164123,32.718556],off:[10,2],},
"San Pedro":{loc:[-118.29,33.74],off:[10,8],},
Mazatlan:{loc:[-106.412742,23.250984],off:[10,5],},
Arica:{loc:[-70.333333,-18.483333],off:[8,6],},
"Port Chalmers":{loc:[170.621466,-45.815739],off:[5,10],}
};
// These are just to give the routes a stylistic curve, they were chosen by hand
// I was unable to find a satisfactory algorithm for spacing them automatically for this
var intermediatePoints = {
VictoriaOffshore:[-141.358218,41.384491], // For others
VictoriaOffshoreUS:[-131.967321,39.045791], // Alternate for US destinations
VictoriaOffshoreSA:[-159.469220, 0.368191], // For South America
VictoriaOffshoreMX:[-146.558129,21], // For Mexico
VictoriaOffshoreAU:[-175.839426,-28], // For Australia
VictoriaOffshoreAS:[-163.976757,25], // For Asia
VictoriaOffshoreAK:[-141.374371,51.328740], // For Alaska
VictoriaOffshoreOS:[-127.683328,43.675809], // For Oregon
VictoriaOffshoreSF:[-135.683328,35.675809], // For San Francisco
VictoriaOffshoreSC:[-138.683328,30.675809] // for southern California
};
</script>
</body>
</html>
dept via arr vol
Victoria VictoriaOffshoreSA Callao 24
Victoria VictoriaOffshoreMX Honolulu 20
Victoria VictoriaOffshoreSA Valparaiso 20
Victoria VictoriaOffshoreUS San Francisco 14
Victoria VictoriaOffshoreAS Shanghai 11
Victoria VictoriaOffshoreAU Sydney 10
Victoria VictoriaOffshoreAU Melbourne 9
Victoria VictoriaOffshoreSA Iquique 4
Victoria VictoriaOffshoreMX Guaymas 3
Victoria VictoriaOffshoreAU Dunedin 3
Victoria VictoriaOffshoreAU Batavia 1
Victoria VictoriaOffshoreSA Coquimbo 1
Victoria VictoriaOffshoreAS Hong Kong 1
Victoria VictoriaOffshoreAS Yokohama 1
Victoria VictoriaOffshoreSA Lambayaque 1
Victoria VictoriaOffshoreSA Tahiti 1
Victoria VictoriaOffshoreSA Arica 1
dept via arr vol
Victoria VictoriaOffshoreSF San Francisco 1
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment