|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
svg { |
|
font: 10px sans-serif; |
|
} |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.station line { |
|
stroke: #ddd; |
|
stroke-dasharray: 1,1; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.station text { |
|
text-anchor: end; |
|
} |
|
|
|
.train path { |
|
fill: none; |
|
stroke-width: 1.5px; |
|
} |
|
|
|
.train .N path { stroke: rgb(34,34,34); } |
|
.train .N circle { fill: rgb(34,34,34); stroke: #fff; } |
|
|
|
.train .L path { stroke: rgb(183,116,9); } |
|
.train .L circle { fill: rgb(183,116,9); stroke: #fff; } |
|
|
|
.train .B path { stroke: rgb(192,62,29); } |
|
.train .B circle { fill: rgb(192,62,29); stroke: #fff; } |
|
|
|
</style> |
|
<body> |
|
<script src="http://d3js.org/d3.v3.js"></script> |
|
<script> |
|
|
|
var formatTime = d3.time.format("%I:%M%p"); |
|
|
|
var stations = []; // lazily loaded |
|
|
|
var margin = {top: 20, right: 10, bottom: 20, left: 100}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var x = d3.time.scale() |
|
.range([0, width]); |
|
|
|
var y = d3.scale.linear() |
|
.range([0, height]); |
|
|
|
var xAxis = d3.svg.axis() |
|
.scale(x) |
|
.tickFormat(formatTime); |
|
|
|
var line = d3.svg.line() |
|
.x(function(d) { return x(d.time); }) |
|
.y(function(d) { return y(d.station.distance); }); |
|
|
|
var svg = d3.select("body").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 + ")"); |
|
|
|
d3.tsv("schedule.tsv", type, function(error, trains) { |
|
y.domain(d3.extent(stations, function(d) { return d.distance; })); |
|
|
|
x.domain([ |
|
d3.min(trains, function(d) { return d.direction === "S" ? d.stops[0].time : d.stops[d.stops.length - 1].time; }), |
|
d3.max(trains, function(d) { return d.direction === "N" ? d.stops[0].time : d.stops[d.stops.length - 1].time; }) |
|
]); |
|
|
|
var station = svg.append("g") |
|
.attr("class", "station") |
|
.selectAll("g") |
|
.data(stations) |
|
.enter().append("g") |
|
.attr("transform", function(d) { return "translate(0," + y(d.distance) + ")"; }); |
|
|
|
station.append("text") |
|
.attr("x", -6) |
|
.attr("dy", ".35em") |
|
.text(function(d) { return d.name; }); |
|
|
|
station.append("line") |
|
.attr("x2", width); |
|
|
|
svg.append("g") |
|
.attr("class", "x top axis") |
|
.call(xAxis.orient("top")); |
|
|
|
svg.append("g") |
|
.attr("class", "x bottom axis") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(xAxis.orient("bottom")); |
|
|
|
var train = svg.append("g") |
|
.attr("class", "train") |
|
.selectAll("g") |
|
.data(trains.filter(function(d) { return /[NLB]/.test(d.type); })) |
|
.enter().append("g") |
|
.attr("class", function(d) { return d.type; }); |
|
|
|
train.append("path") |
|
.attr("d", function(d) { return line(d.stops); }); |
|
|
|
train.selectAll("circle") |
|
.data(function(d) { return d.stops; }) |
|
.enter().append("circle") |
|
.attr("transform", function(d) { return "translate(" + x(d.time) + "," + y(d.station.distance) + ")"; }) |
|
.attr("r", 2); |
|
}); |
|
|
|
function type(d, i) { |
|
|
|
// Extract the stations from the "stop|*" columns. |
|
if (!i) for (var k in d) { |
|
if (/^stop\|/.test(k)) { |
|
var p = k.split("|"); |
|
stations.push( |
|
{ key: k |
|
, name: p[1] |
|
, distance: +p[2] |
|
, zone: +p[3] |
|
, lat: +p[4] |
|
, lng: +p[5] |
|
}); |
|
} |
|
} |
|
|
|
return { number: d.number |
|
, type: d.type |
|
, direction: d.direction |
|
, stops: stations |
|
.map(function(s) { return {station:s, time:parseTime(d[s.key])}; }) |
|
.filter(function(s) { return s.time != null; }) |
|
}; |
|
} |
|
|
|
function parseTime(s) { |
|
var t = formatTime.parse(s); |
|
if (t != null && t.getHours() < 3) t.setDate(t.getDate() + 1); |
|
return t; |
|
} |
|
|
|
</script> |