This example recreates the CalTrain timetable in the style of E. J. Marey’s graphical schedule. Stations are separated vertically in proportion to geography; thus, the slope of the line reflects the actual speed of the train: the steeper the line, the faster the train. This display also reveals when and where limited service trains are passed by baby bullets.
Next: Stemplots
<html>
<head>
<title>CalTrain Timetable</title>
<link type="text/css" rel="stylesheet" href="ex.css?3.2"/>
<script type="text/javascript" src="../protovis-r3.2.js"></script>
<script type="text/javascript" src="caltrain.js"></script>
<style type="text/css">
#fig {
height: 680px;
width: 1400px;
}
#controls {
padding-left: 100px;
}
</style>
</head>
<body><div id="center"><div id="fig">
<div id="controls" >
<b>Speed:</b>
<input type="checkbox" id="normal" checked onchange="speedn = this.checked; vis.render();"
><label for="normal" style="color:rgb(34,34,34);">Normal</label>
<input type="checkbox" id="limited" checked onchange="speedl = this.checked; vis.render();"
><label for="limited" style="color:rgb(183,116,9);">Limited</label>
<input type="checkbox" id="bullet" checked onchange="speedb = this.checked; vis.render();"
><label for="bullet" style="color:rgb(192,62,29);">Bullet</label>
<b style="margin-left:6em;">Direction:</b>
<input type="checkbox" id="northbound" checked onchange="dirn = this.checked; vis.render();"
><label for="northbound">Northbound</label>
<input type="checkbox" id="southbound" checked onchange="dirs = this.checked; vis.render();"
><label for="southbound">Southbound</label>
<b style="margin-left:6em;">Days:</b>
<input id="weekdays" name="daySelect" value="Wk" type="radio" checked onchange="oper = this.value; vis.render();"
><label for="weekdays">Weekdays</label>
<input name="daySelect" id="saturday" value="Sa" type="radio" onchange="oper = this.value; vis.render();"
><label for="saturday">Saturday</label>
<input name="daySelect" id="sunday" value="Su" type="radio" onchange="oper = this.value; vis.render();"
><label for="sunday">Sunday</label>
</div>
<script type="text/javascript+protovis">
// Start with showing all trains on weekdays
var dirn = true,
dirs = true,
oper = "Wk",
speedn = true,
speedl = true,
speedb = true;
// Flatten the data so that we have an array of (Train x Station x Time)
northbound = pv.flatten(northbound)
.key("train")
.key("station", function(i) stationsNS[stationsNS.length - i - 1].name)
.key("time")
.array();
southbound = pv.flatten(southbound)
.key("train")
.key("station", function(i) stationsNS[i].name)
.key("time")
.array();
// Label the trains with their directions of travel
northbound.forEach(function(stop) stop.bound = "N");
southbound.forEach(function(stop) stop.bound = "S");
// Concatinate the northbound and southbound trains to make a list of all trains
var allTrains = northbound.concat(southbound).filter(function(stop) stop.time.length > 1);
// Parse the stop time and do some extra clean up
allTrains.forEach(function(stop) {
// parse type
stop.type = stop.train.charAt(3);
stop.train = stop.train.substr(0,3);
// check to see if the stop is an interconnect
var time = stop.time.charAt(0) == "i" ? stop.time.substr(1) : stop.time;
if (time.length == 6) {
var h = parseInt(time.substr(0, 1), 10);
var m = parseInt(time.substr(2, 2), 10);
var pm = (time.substr(4, 2) == "pm");
} else {
var h = parseInt(time.substr(0, 2), 10);
var m = parseInt(time.substr(3, 2), 10); // parseInt("09") == 0 because it assumes octal
var pm = (time.substr(5, 2) == "pm");
}
if (h == 12) pm = !pm // for 12 we have to swap am and pm because time is retarded
time = h*60 + m + (pm?720:0); // calculate the number of minutes from midnight
stop.mins = time + ((time < 180)?1440:0); // make the day switch at 3am instead of midnight
});
// Nest the trains-stops by train as we will draw one line per train
var trains = pv.nest(allTrains)
.key(function(d) d.train)
.entries();
// Initialize the variables
var w = 1200,
h = 600,
minHour = 4,
maxHour = 26,
showMin = 4 * 60,
showMax = 26 * 60;
// Make the scale functions
var x = pv.Scale.linear(showMin, showMax).range(0, w),
s2d = pv.Scale.ordinal(stationsNS, function(s) s.name).range(stationsNS, function(s) s.dist),
y = pv.Scale.linear(stationsNS, function(s) s.dist).range(h, 0).by(s2d);
function hourText(d) {
var h = d/60 % 24;
return (h==0?'MIDNIGHT':(h==12?'NOON':(h<12?h:h-12)));
}
// The root panel, with padding for labels
var vis = new pv.Panel()
.width(w)
.height(h)
.left(100)
.right(100)
.bottom(30)
.top(30);
// Add hour lines
var hour = vis.add(pv.Rule)
.data(function() pv.range(Math.ceil(showMin / 60) * 60, showMax + 1, 60))
.left(x)
.strokeStyle("#eee");
hour.anchor("top").add(pv.Label)
.textStyle(function(h) (h / 60 % 24) % 12 ? "#999" : "#000")
.text(hourText);
hour.anchor("bottom").add(pv.Label)
.textStyle(function(h) (h / 60 % 24) % 12 ? "#999" : "#000")
.text(hourText);
// Add station lines
var station = vis.add(pv.Rule)
.data(stationsNS)
.bottom(function(s) y(s.name))
.strokeStyle(function(s) s.bullet ? "#bbb" : "#eee");
station.anchor("left").add(pv.Label)
.textStyle(function(s) s.bullet ? "#000" : "#888")
.text(function(s) s.name);
station.anchor("right").add(pv.Label)
.textStyle(function(s) s.bullet ? "#000" : "#888")
.text(function(s) s.name);
// A panel for each train
var panel = vis.add(pv.Panel)
.data(trains)
.visible(function(train) {
var t = train.values[0];
if (!speedb && t.type == "B") return false;
if (!speedl && t.type == "L") return false;
if (!speedn && /N|W|S/.test(t.type)) return false;
if (!dirn && t.bound == "N") return false;
if (!dirs && t.bound == "S") return false;
switch (oper) {
case "Wk": return /N|L|B/.test(t.type);
case "Su": return /W|S/.test(t.type);
default: return t.type == "W";
}
});
// A line curve (with dots) for each station
var line = panel.add(pv.Line)
.data(function(d) d.values)
.left(function(stop) x(stop.mins))
.bottom(function(stop) y(stop.station))
.strokeStyle(function(stop) types[stop.type])
.lineWidth(1);
// Ticks that indicate the stops
line.add(pv.Dot)
.radius(2.5)
.lineWidth(.75)
.strokeStyle("white")
.fillStyle(function(stop) types[stop.type])
.title(function(stop) stop.type + stop.train + "-" + stop.bound + "B: "
+ stop.station + " at " + stop.time);
vis.render();
</script>
</div></div></body>
</html>