Skip to content

Instantly share code, notes, and snippets.

@tophtucker
Last active July 5, 2016 21:22
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 tophtucker/81c467db46a37dfaf75cacb70552b818 to your computer and use it in GitHub Desktop.
Save tophtucker/81c467db46a37dfaf75cacb70552b818 to your computer and use it in GitHub Desktop.
MTA spaghetti
height: 1700
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MTA spaghetti</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 960px;
height: 1700px;
background: #F2EAD6;
}
svg {
overflow: visible;
width: 100%;
height: 100%;
}
.links line {
stroke: #aaa;
stroke-width: 10;
}
.nodes circle {
pointer-events: all;
stroke: black;
stroke-width: 2;
fill: white;
}
.nodes circle.forking {
stroke: red;
}
.controls {
position: fixed;
top: 1em;
left: 1em;
z-index: 3;
}
button {
background: white;
border: 1px solid black;
border-radius: 50%;
width: 5em;
height: 5em;
padding: 1em;
cursor: pointer;
opacity: .5;
}
button:hover {
opacity: 1;
}
button.active {
border: 1px solid white;
background: black;
color: white;
}
img.fork {
position: absolute;
pointer-events: none;
z-index: 2;
transform: translate(-50%,0%);
transform-origin: 50% 0%;
}
text {
font-family: sans-serif;
font-size: 10px;
fill: rgba(0,0,0,.2);
display: none;
pointer-events: none;
}
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (max-width: 666px) {
body, html {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
svg {
overflow: hidden;
}
text {
display: none;
}
}
</style>
<body>
<svg></svg>
<div class="controls">
<button class="fork">Fork</button>
<button class="music">Music</button>
<button class="reset">Reset</button>
<!-- <button class="repel">Repel</button> -->
</div>
<audio loop src="http://hrustevich.com/data/uploads/mp3/2013/tr01.mp3"></audio>
</body>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
var svg = d3.select("svg"),
width = svg.node().getBoundingClientRect().width,
height = svg.node().getBoundingClientRect().height;
var lineColors = {
'1': '#E00034',
'3': '#E00034',
'A': '#0039A6',
'E': '#0039A6',
'4': '#009B3A',
'R': '#FECB00',
'D': '#FF6319',
'F': '#FF6319',
'M': '#FF6319',
'7': '#B634BB',
'L': '#939598',
'J': '#955214',
'transfer': '#4D4D4D',
'ground': '#F2EAD6',
'water': '#A2CAEA',
'park': '#A8D7B9'
};
var simulation = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d) { return d.id; })
.distance(function(d) { return d.distance; })
)
.force("center", d3.forceCenter(width / 2, height / 2));
d3.queue()
.defer(d3.tsv, "stations.tsv")
.defer(d3.tsv, "transfers.tsv")
.await(function(error, stations, transfers) {
if (error) throw error;
if(innerWidth <= 666) projectStations(stations);
stations.forEach(parseStation);
var links = getLinks(stations, transfers);
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.style('stroke', function(d) {
return lineColors[d.type];
})
.style('stroke-width', function(d) {
return d.type === 'transfer' ? 4 : 10;
});
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(stations)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("r", 4)
node.append("text")
.attr("dx", "0.5em")
.attr("dy", "-0.5em")
.text(function(d) { return d.name; });
simulation
.nodes(stations)
.on("tick", ticked);
simulation.force("link")
.links(links);
// d3.select('button.repel')
// .on('mouseenter', function() {
// simulation
// .alphaTarget(0.3).restart()
// .force("charge", d3.forceManyBody());
// })
// .on('mouseleave', function() {
// simulation
// .alphaTarget(0)
// .force("charge", null);
// });
d3.select('button.music')
.on('click', function() {
if(!d3.select(this).classed('active')) {
d3.select(this).classed('active', true);
d3.select('audio').node().play();
} else {
d3.select(this).classed('active', false);
d3.select('audio').node().pause();
}
});
d3.select('button.reset')
.on('mousedown', startReset)
.on('touchstart', startReset)
.on('mouseup', stopReset)
.on('touchend', stopReset);
function startReset() {
d3.select(this).classed('active', true);
simulation
.alphaTarget(0.3).restart()
.force("resetX", d3.forceX(function(d) {
return d.x0;
}))
.force("resetY", d3.forceY(function(d) {
return d.y0;
}));
}
function stopReset() {
d3.select(this).classed('active', false);
simulation
.alphaTarget(0)
.force("resetX", null)
.force("resetY", null);
}
d3.select('button.fork')
.on("click", function() {
if(!d3.select(this).classed('active')) {
// enable fork
d3.select(this).classed('active', true);
simulation
.force("fork", forceFork(.1, (window.innerWidth > 666 ? 100 : 40), d3.select('body')))
.alphaDecay(0).restart();
} else {
// disable fork
d3.select(this).classed('active', false);
d3.select('img.fork').remove();
simulation
.force("fork", null)
.alphaDecay(0.0228).restart();
}
})
.each(function() {
this.click();
});
// ticked();
// simulation.stop();
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; })
.classed("forking", function(d) { return d.forking; });
}
});
// fit stations to screen
function projectStations(stations) {
var xExtent = d3.extent(stations.map(function(d) { return +d.x; }));
var x = d3.scaleLinear()
.domain(xExtent)
.range([0,window.innerWidth]);
var yExtent = d3.extent(stations.map(function(d) { return +d.y; }));
var y = d3.scaleLinear()
.domain(yExtent)
.range([0,window.innerHeight]);
// "contain" behavior, in background-position terms:
// scale both dimensions by the more-constrained dimension
var t = (x(1) - x(0) > y(1) - y(0)) ? y : x;
stations.forEach(function(station) {
station.x = t(+station.x);
station.y = t(+station.y);
})
}
function parseStation(station) {
station.order = +station.order;
station.x = +station.x;
station.y = +station.y;
station.x0 = station.x;
station.y0 = station.y;
station.id = station.line + ' ' + station.name;
}
function getLinks(stations, transfers) {
var links = [];
stations.forEach(function(station) {
var nextStation = stations.filter(function(st) {
return st.line == station.line && st.order == station.order + 1;
});
if(nextStation.length) {
links.push({
'source': station.id,
'target': nextStation[0].id,
'distance': distance(station, nextStation[0]),
'type': station.line
})
} else {
return;
}
});
transfers.forEach(function(transfer) {
var source = stations.filter(function(st) {
return st.id == transfer.fromLine + ' ' + transfer.fromStation;
})[0];
var target = stations.filter(function(st) {
return st.id == transfer.toLine + ' ' + transfer.toStation;
})[0];
links.push({
'source': source.id,
'target': target.id,
'distance': distance(source, target),
'type': 'transfer'
});
});
return links;
}
function distance(a,b) {
return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
}
function difference(a,b) {
return d3.zip(a,b).map(function(x) {
return x.reduce(function(a, b) {
return a - b;
});
});
}
function dragstarted(d) {
d.fx = d.x;
d.fy = d.y;
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
// simulation.fix(d);
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
// simulation.fix(d, d3.event.x, d3.event.y);
}
function dragended(d) {
d.fx = undefined;
d.fy = undefined;
if (!d3.event.active) simulation.alphaTarget(0);
// simulation.unfix(d);
}
function forceFork(_, __, ___) {
var active = false,
clockwise = 1,
nodes,
fork,
forkAngle = 0,
strength = _ || 1,
radius = __ || 100,
container = ___ || d3.select('body'),
center = {x: 0, y: 0},
moves = [];
function force(alpha) {
if(!active) return;
forkAngle += clockwise * strength;
fork.style('transform', 'translate(-50%,0%) rotate(' + forkAngle + 'rad) scale(0.85)');
if(moves.length >= 2) {
var x0 = moves[0],
x1 = moves[moves.length-1],
dx = difference(x1,x0);
moves = [x1];
}
for (var i = 0, n = nodes.length, node, k = alpha; i < n; ++i) {
node = nodes[i];
node.forking = distance(node, center) < radius;
if(node.forking) {
node.vx += clockwise * strength * -(node.y - center.y);
node.vy += clockwise * strength * (node.x - center.x);
if(dx) {
node.vx += dx[0];
node.vy += dx[1];
}
}
}
}
force.initialize = function(_) {
nodes = _;
fork = container.append('img')
.classed('fork', true)
.attr('src', 'fork.png?v=2')
.attr('width', radius * 2.5);
container
.on('mousedown.spin', start)
.on('touchstart.spin', start)
.on('mouseup.spin', stop)
.on('touchend.spin', stop)
.on('mousemove.position', position)
.on('touchstart.position', position)
.on('touchmove.position', position);
function start() {
clockwise = d3.event.shiftKey ? -1 : 1;
active = true;
}
function stop() {
active = false;
fork.style('transform', 'translate(-50%,0%) rotate(' + forkAngle + 'rad) scale(1)');
moves = [];
}
function position() {
var pt = d3.mouse(this);
if(active) moves.push(pt);
fork
.style('left', pt[0] + 'px')
.style('top', pt[1] + 'px');
center = {
x: pt[0],
y: pt[1]
};
}
}
return force;
}
</script>
line order name x y
1 1 Van Cordlandt Park 242 St 202 132
1 2 238 St 202 160
1 3 231 St 202 189
1 4 225 St Marble Hill 202 218
1 5 215 St 156 276
1 6 207 St 156 299
1 7 Dyckman St 146 333
1 8 191 St 146 356
1 9 181 St 146 387
1 10 Washington Heights 168 St 146 462
1 11 157 St 146 505
1 12 145 St 146 584
1 13 137 City College 146 623
1 14 125 St 146 655
1 15 116 St Columbia University 146 697
1 16 110 St Cathedral Parkway 146 730
1 17 103 St 156 775
1 18 96 St 156 815
1 19 86 St 156 856
1 20 79 St 156 905
1 21 72 St 156 953
1 22 66 St 182 1012
1 23 59 St Columbus Circle 227 1057
1 24 50 St 321 1151
1 25 Times Sq 42 St 334 1211
1 26 34 St Penn Station 334 1292
1 27 28 St 334 1321
1 28 23 St 334 1351
1 29 18 St 334 1380
1 30 14 St 334 1409
1 31 Christopher St Sheridan Sq 334 1495
1 32 Houston St 334 1550
1 33 Canal St 334 1614
1 34 Franklin St 334 1652
1 35 Chambers St West Broadway 355 1700
1 36 Cortlandt St 404 1785
1 37 Rector St 429 1827
1 38 South Ferry 537 1889
3 1 Harlem 148 St 389 574
3 2 145 St 454 589
3 3 135 St 454 630
3 4 125 St 454 670
3 5 116 St 454 701
3 6 110 St Central Park North 454 722
3 7 96 St 166 815
3 8 72 St 166 953
3 9 Times Sq 42 St 344 1211
3 10 34 St Penn Station 344 1292
3 11 14 St 344 1409
3 12 Chambers St West Broadway 364 1695
3 13 Park Pl 463 1713
3 14 Fulton St 656 1758
3 15 Wall St 656 1824
A 1 Inwood 207 St 129 300
A 2 Dyckman St 111 318
A 3 190 St 107 358
A 4 181 St 107 386
A 5 175 St GW Bridge Bus Terminal 128 416
A 6 Washington Heights 168 St 168 456
A 7 145 St 241 586
A 8 125 St 241 673
A 9 59 St Columbus Circle 251 1043
A 10 42 St Port Authority Bus Terminal 251 1211
A 11 34 St Penn Station 251 1292
A 12 14 St 251 1398
A 13 West 4 St 433 1525
A 14 Canal St 433 1620
A 15 Chambers St Church St 433 1682
A 16 Fulton St 637 1749
E 1 Court Sq 785 1137
E 2 Lexington Av 626 1137
E 3 5 Av 497 1137
E 4 7 Av 381 1137
E 5 50 St 261 1153
E 6 42 St Port Authority Bus Terminal 261 1211
4 1 125 St 605 670
4 2 86 St 605 855
4 3 59 St Lexington Ave 605 1075
4 4 42 St Grand Central 605 1231
4 5 14 St Union Sq 585 1388
4 6 Brooklyn Bridge Chambers St 585 1678
4 7 Fulton St 557 1759
4 8 Wall St 557 1821
4 9 Bowling Green 557 1854
R 1 59 St Lexington Ave 626 1065
R 2 Midtown 57 St 354 1089
R 3 49 St 354 1159
R 4 Times Sq 42 St 363 1200
R 5 34 St Herald Sq 435 1272
R 6 28 St 488 1325
R 7 23 St 514 1351
R 8 14 St Union Sq 535 1409
R 9 8 St NYU 535 1453
R 10 Prince St 535 1560
R 11 Canal St 545 1632
R 12 City Hall 545 1682
R 13 Cortlandt St 507 1766
R 14 Rector St 507 1822
R 15 Whitehall St 589 1876
D 1 155 St 8 Av 284 515
D 2 145 St 231 586
D 3 125 St 231 673
D 4 59 St Columbus Circle 241 1043
D 5 7 Av 381 1127
D 6 47-50 St Rockefeller Center 453 1157
D 7 42 St Bryant Park 453 1201
D 8 34 St Herald Sq 453 1292
D 9 West 4 St 453 1525
D 10 Broadway-Lafayette St 606 1547
D 11 Grand St 692 1608
F 1 Roosevelt Island 719 1023
F 2 Lexington Av 626 1023
F 3 57 St 463 1089
F 4 47-50 St Rockefeller Center 463 1157
M 1 Court Sq 785 1127
M 2 Lexington Av 626 1127
M 3 5 Av 497 1127
M 4 47-50 St Rockefeller Center 463 1157
7 1 42 St Grand Central 588 1221
7 2 5 Av 498 1221
7 3 Times Sq 42 St 322 1221
7 4 34 St Hudson Yards 140 1286
L 1 1 Av 709 1398
L 2 3 Av 651 1398
L 3 14 St Union Sq 565 1398
L 4 6 Av 420 1398
L 5 14 St 8 Av 271 1398
J 1 Essex St 755 1581
J 2 Bowery 662 1581
J 3 Canal St 638 1632
J 4 Brooklyn Bridge Chambers St 638 1678
J 5 Fulton St 622 1759
J 6 Broad St 622 1835
fromLine fromStation toLine toStation
1 Washington Heights 168 St A Washington Heights 168 St
1 59 St Columbus Circle D 59 St Columbus Circle
1 Times Sq 42 St A 42 St Port Authority Bus Terminal
1 South Ferry R Whitehall St
4 59 St Lexington Ave R 59 St Lexington Ave
R Times Sq 42 St 1 Times Sq 42 St
R 34 St Herald Sq D 34 St Herald Sq
D 145 St A 145 St
D 125 St A 125 St
D 59 St Columbus Circle A 59 St Columbus Circle
7 42 St Grand Central 4 42 St Grand Central
7 5 Av D 42 St Bryant Park
7 Times Sq 42 St 1 Times Sq 42 St
A West 4 St D West 4 St
L 14 St Union Sq R 14 St Union Sq
L 14 St Union Sq 4 14 St Union Sq
L 6 Av 1 14 St
L 14 St 8 Av A 14 St
J Canal St R Canal St
J Brooklyn Bridge Chambers St 4 Brooklyn Bridge Chambers St
J Fulton St A Fulton St
J Fulton St 4 Fulton St
3 96 St 1 96 St
3 72 St 1 72 St
3 Times Sq 42 St 1 Times Sq 42 St
3 34 St Penn Station 1 34 St Penn Station
3 14 St 1 14 St
3 Chambers St West Broadway 1 Chambers St West Broadway
3 Park Pl A Chambers St Church St
3 Fulton St A Fulton St
F Lexington Av R 59 St Lexington Ave
F 47-50 St Rockefeller Center D 47-50 St Rockefeller Center
M 47-50 St Rockefeller Center F 47-50 St Rockefeller Center
E Court Sq M Court Sq
E Lexington Av M Lexington Av
E 5 Av M 5 Av
E 7 Av D 7 Av
E 42 St Port Authority Bus Terminal A 42 St Port Authority Bus Terminal
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment