Skip to content

Instantly share code, notes, and snippets.

@msbarry
Last active August 29, 2015 13:57
Show Gist options
  • Save msbarry/9402961 to your computer and use it in GitHub Desktop.
Save msbarry/9402961 to your computer and use it in GitHub Desktop.
Hubway Sankey
var nodes = [];
nodes[3] = "Colleges of the Fenway - Boston";
nodes[4] = "Tremont St. at Berkeley St. - Boston";
nodes[5] = "Northeastern U / North Parking Lot - Boston";
nodes[6] = "Cambridge St. at Joy St. - Boston";
nodes[7] = "Fan Pier - Boston";
nodes[8] = "Union Square - Brighton Ave. at Cambridge St. - Boston";
nodes[9] = "Agganis Arena - 925 Comm Ave. - Boston";
nodes[10] = "B.U. Central - 725 Comm. Ave. - Boston";
nodes[11] = "Longwood Ave / Binney St - Boston";
nodes[12] = "Ruggles Station / Columbus Ave. - Boston";
nodes[14] = "HMS / HSPH - Ave. Louis Pasteur at Longwood Ave. - Boston";
nodes[15] = "Harvard Real Estate - Brighton Mills - 370 Western Ave - Boston";
nodes[16] = "Back Bay / South End Station - Boston";
nodes[17] = "Harvard University Housing - 111 Western Ave. at Soldiers Field Park - Boston";
nodes[18] = "Harvard Real Estate - 219 Western Ave. at North Harvard St. - Boston";
nodes[19] = "Buswell Park - Boston";
nodes[20] = "Aquarium Station - 200 Atlantic Ave. - Boston";
nodes[21] = "Prudential Center / Belvidere - Boston";
nodes[22] = "South Station - 700 Atlantic Ave. - Boston";
nodes[24] = "Seaport Square - Seaport Blvd. at Boston Wharf - Boston";
nodes[25] = "Tremont St / W Newton St - Boston";
nodes[26] = "Washington St. at Waltham St. - Boston";
nodes[27] = "Roxbury Crossing Station - Boston";
nodes[29] = "Innovation Lab - 125 Western Ave. at Batten Way - Boston";
nodes[30] = "Brigham Cir / Huntington Ave - Boston";
nodes[31] = "Seaport Hotel - Boston";
nodes[32] = "Landmark Centre - Boston";
nodes[33] = "Kenmore Sq / Comm Ave - Boston";
nodes[36] = "Boston Public Library - 700 Boylston St. - Boston";
nodes[39] = "Washington St. at Rutland St. - Boston";
nodes[40] = "Lewis Wharf - Atlantic Ave. - Boston";
nodes[41] = "Packard's Corner - Comm. Ave. at Brighton Ave. - Boston";
nodes[42] = "Boylston St. at Arlington St. - Boston";
nodes[43] = "Rowes Wharf - Atlantic Ave - Boston";
nodes[44] = "Faneuil Hall - Union St. at North St. - Boston";
nodes[45] = "Yawkey Way at Boylston St. - Boston";
nodes[46] = "Christian Science Plaza - Boston";
nodes[47] = "Cross St. at Hanover St. - Boston";
nodes[48] = "Post Office Square - Boston";
nodes[49] = "Stuart St. at Charles St. - Boston";
nodes[50] = "Boylston St / Berkeley St - Boston";
nodes[51] = "Washington St. at Lenox St. - Boston";
nodes[52] = "Newbury St / Hereford St - Boston";
nodes[53] = "Beacon St / Mass Ave - Boston";
nodes[54] = "Tremont St / West St - Boston";
nodes[55] = "Boylston / Mass Ave - Boston";
nodes[57] = "Columbus Ave. at Mass. Ave. - Boston";
nodes[58] = "The Esplanade - Beacon St. at Arlington St. - Boston";
nodes[59] = "Chinatown Gate Plaza - Surface Rd. at Beach St. - Boston";
nodes[62] = "Longwood Ave/Riverway - Boston";
nodes[63] = "Dorchester Ave. at Gillette Park - Boston";
nodes[64] = "Congress / Sleeper - Boston";
nodes[65] = "Boston Convention & Exhibition Center - Boston";
nodes[66] = "Allston Green District - Commonwealth Ave & Griggs St - Boston";
nodes[67] = "MIT at Mass Ave / Amherst St - Cambridge";
nodes[68] = "Central Square at Mass Ave / Essex St - Cambridge";
nodes[69] = "Coolidge Corner - Beacon St @ Centre St - Brookline";
nodes[70] = "Harvard Kennedy School at Bennett St / Eliot St - Cambridge";
nodes[71] = "Conway Park - Somerville Avenue - Somerville";
nodes[72] = "One Broadway / Kendall Sq at Main St / 3rd St - Cambridge";
nodes[73] = "Harvard Square at Brattle St / Eliot St - Cambridge";
nodes[74] = "Harvard Square at Mass Ave/ Dunster - Cambridge";
nodes[75] = "Lafayette Square at Mass Ave / Main St / Columbia St - Cambridge";
nodes[76] = "Central Sq Post Office / Cambridge City Hall at Mass Ave / Pleasant St - Cambridge";
nodes[77] = "Somerville City Hall - Somerville";
nodes[78] = "Union Square - Somerville - Somerville";
nodes[79] = "Beacon St at Washington / Kirkland - Somerville";
nodes[80] = "MIT Stata Center at Vassar St / Main St - Cambridge";
nodes[81] = "Boylston St / Washington St - Boston";
nodes[83] = "South Bay Plaza - Boston";
nodes[84] = "CambridgeSide Galleria - CambridgeSide PL at Land Blvd - Cambridge";
nodes[86] = "Brookline Village - Station Street @ MBTA - Brookline";
nodes[87] = "Harvard University Housing - 115 Putnam Ave at Peabody Terrace - Cambridge";
nodes[88] = "Inman Square at Vellucci Plaza / Hampshire St - Cambridge";
nodes[89] = "Harvard Law School at Mass Ave / Jarvis St - Cambridge";
nodes[90] = "Lechmere Station at Cambridge St / First St - Cambridge";
nodes[91] = "One Kendall Square at Hampshire St / Portland St - Cambridge";
nodes[92] = "University of Massachusetts Boston - Boston";
nodes[93] = "JFK / UMASS Station - Boston";
nodes[94] = "Charlestown - Main St at Austin St - Boston";
nodes[95] = "Cambridge St - at Columbia St / Webster Ave - Cambridge";
nodes[96] = "Cambridge Main Library at Broadway / Trowbridge St - Cambridge";
nodes[98] = "Charlestown - Warren St at Chelsea St - Boston";
nodes[99] = "Mayor Thomas M. Menino - Government Center - Boston";
nodes[100] = "Dudley Square - Boston";
nodes[101] = "BIDMC - Brookline at Burlington St - Boston";
nodes[102] = "Boston Medical Center - East Concord at Harrison Ave - Boston";
nodes[103] = "Boylston at Fairfield - Boston";
nodes[104] = "Andrew Station - Dorchester Ave at Humboldt Pl - Boston";
nodes[105] = "Mt Pleasant Ave / Dudley Town Common - Boston";
nodes[106] = "West Broadway at Dorchester St - Boston";
nodes[107] = "South Boston Library - 646 East Broadway - Boston";
nodes[108] = "E. Cottage St at Columbia Rd - Boston";
nodes[109] = "Upham's Corner - Ramsey St at Dudley St - Boston";
nodes[110] = "New Balance - Guest St. at Life St. - Boston";
nodes[111] = "TD Garden - Causeway at Portal Park #2 - Boston";
nodes[112] = "Franklin St. / Arch St. - Boston";
nodes[113] = "Charles Circle - Charles St. at Cambridge St. - Boston";
nodes[114] = "TD Garden - Causeway at Portal Park #1 - Boston";
nodes[115] = "Spaulding Rehabilitation Hospital - Charlestown Navy Yard - Boston";
nodes[116] = "Charles St at Beacon St - Boston";
nodes[117] = "Milk St at India St - Boston";
nodes[118] = "Hayes Square at Vine St. - Boston";
nodes[119] = "New Balance Store - Boylston at Dartmouth - Boston";
nodes[120] = "JP Monument - South St at Centre St - Boston";
nodes[121] = "JP Centre - Centre Street at Myrtle Street - Boston";
nodes[122] = "Hyde Square at Barbara St - Boston";
nodes[123] = "Egleston Square at Columbus Ave - Boston";
nodes[124] = "Green St T - Boston";
nodes[125] = "Jackson Square T at Centre St - Boston";
nodes[126] = "Washington Square at Washington St. / Beacon St. - Brookline";
nodes[127] = "JFK Crossing at Harvard St. / Thorndike St. - Brookline";
nodes[128] = "Kendall T at Main St - Cambridge";
nodes[129] = "Harvard University River Houses / Plympton St at Memorial Drive - Cambridge";
nodes[130] = "Harvard University Gund Hall at Quincy St / Kirkland S - Cambridge";
nodes[131] = "Lower Cambridgeport at Magazine St/Riverside Rd - Cambridge";
nodes[132] = "Harvard University / SEAS Cruft-Pierce Halls at 29 Oxford St - Cambridge";
nodes[133] = "Harvard University Radcliffe Quadrangle at Shepard St / Garden St - Cambridge";
nodes[134] = "Mass Ave / Linear Park - Cambridge";
nodes[135] = "359 Broadway - Broadway at Fayette Street - Cambridge";
nodes[136] = "Biogen Idec - Binney St / Sixth St - Cambridge";
nodes[137] = "Porter Square Station - Cambridge";
nodes[138] = "Wilson Square - Somerville";
nodes[139] = "Davis Square - Somerville";
nodes[140] = "Ball Square - Somerville";
nodes[141] = "Powder House Circle - Somerville";
nodes[142] = "Packard Ave / Powderhouse Blvd - Somerville";
nodes[143] = "Somerville Hospital at Highland Ave / Crocker St - Somerville";
nodes[144] = "Teele Square at 239 Holland St - Somerville";
nodes[145] = "Summer St at Cutter St - Somerville";
for (var i = 0; i < nodes.length; i++) {
nodes[i] = {name:(nodes[i] || "unk")};
}

This visualization uses a Sankey Diagram to visualize paths of Hubway bicycle rides from several stops in Cambridge to several stops in Boston. Each bicycle rental station is represented by a rectangle and the path between rectangles is proportional to the number of trips taken from the rectangle on the left to the rectangle on the right. Colors of paths match the destination station.

Sankey diagrams are typically used to visualize flow rates through a network of nodes and links. This implementation of a sankey layout uses the D3 Sankey Plugin which works as follows:

  1. Specify list of nodes with names and links with a source node, destination node, and value
  2. Compute the value of each node as the sum of incoming link values or outgoing link values, whichever is greater
  3. Assign nodes to columns by iteratively moving nodes to the left of nodes that they link to (so the "flow" is from left to right)
  4. Compute the height of each node proportional to its value such that the column with the largest total node value fills the entire height of the chart
  5. Order the nodes in each column vertically using an iterative relaxation technique: repeatedly move each node towards the "center of mass" of nodes in adjacent columns that it links to, while preventing overlap of rectangles

Code adapted from bost.ocks.org/mike/sankey/. Data courtesy of Hubway Data Challenge

source target value
113 16 3
113 22 7
128 113 3
128 16 29
128 21 11
128 22 28
128 45 15
128 46 3
128 55 4
128 6 5
128 67 4
128 75 12
128 80 3
128 84 6
128 96 3
40 22 3
42 22 5
46 16 3
47 22 5
55 45 7
6 22 5
67 16 4
72 113 7
72 16 25
72 21 13
72 22 23
72 40 3
72 45 13
72 47 5
72 55 3
72 84 5
72 91 4
75 16 7
75 21 5
80 16 3
84 22 11
91 16 6
91 21 6
91 22 45
91 42 5
91 45 14
96 22 3
<!DOCTYPE html>
<style type="text/css">
/**
* This code uses http://bost.ocks.org/mike/sankey/ created by Mike Bostock as a template.
*/
body {
font-size: 12px;
font-family: Arial;
width: 960px;
margin: 1em auto 0.3em auto;
}
svg {
font: 10px sans-serif;
}
#chart {
height: 500px;
}
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .3;
}
.link:hover {
stroke-opacity: .5;
}
</style>
<body>
<p id="chart">
<script src="readme-stations.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://rawgithub.com/d3/d3-plugins/master/sankey/sankey.js"></script>
<script>
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 960 - margin.left - margin.right,
height = 460 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return d + " rides"; },
color = d3.scale.category20();
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
d3.csv("data.csv", function(links) {
var visited = {};
links.forEach(function (l) {
visited[+l.source] = true;
visited[+l.target] = true;
l.source = window.nodes[+l.source];
l.target = window.nodes[+l.target];
l.value = +l.value;
});
var energy = {
links: links,
nodes: window.nodes.filter(function (d, i) { return visited[i]; })
};
sankey
.nodes(energy.nodes)
.links(energy.links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.enter().append("path")
.attr("class", "link")
.style("stroke", function(d) { return d.target.color = color(d.target.name.replace(/ .*/, "")); })
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(energy.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment