Here we show CalTrain weekday arrival times. The minutes of each northbound and southbound arrival are placed to the left and right, respectively, of their corresponding hour. The time is colored to indicate whether the train service is normal, limited or bullet. Peak times of morning and evening rush hour can be seen by increased activity.
Next: Merge Sort
<html>
<head>
<title>Stemplot</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 {
width: 240px;
height: 460px;
}
#station {
padding-bottom: 10px;
text-align: center;
}
</style>
</head>
<body><div id="center"><div id="fig">
<div id="station">
<b>Station:</b>
<select id="stationSelect"
onchange="station = this.value; updateTrains(); vis.render();"
onkeyup="station = this.value; updateTrains(); vis.render();">
</select>
</div>
<script type="text/javascript+protovis">
var station = "Palo Alto";
var stationSelect = document.getElementById('stationSelect');
stationsNS.forEach(function(s) {
var optn = document.createElement("OPTION");
optn.text = s.name;
optn.value = s.name;
optn.selected = (s.name == station);
stationSelect.options.add(optn);
});
// 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");
// Concatenate 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
if (stop.time.charAt(0) == "i") {
var time = stop.time.substr(1);
stop.conn = true;
} else {
var time = stop.time;
stop.conn = false;
}
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
h = h + (pm?12:0); // calculate the number of minutes from midnight
stop.hour = h + ((h < 3)?24:0); // make the day switch at 3am instead of midnight
stop.time = m < 10 ? "0" + m : m;
})
var trains;
function updateTrains() {
var stationTrains = allTrains.filter(function(t) {
if (t.station != station) return false;
return (t.type=="N") || (t.type=="L") || (t.type=="B");
});
// Nest the trains-stops by train as we will draw one line per train
trains = pv.nest(stationTrains)
.key(function(d) d.hour)
.key(function(d) d.bound)
.sortValues(function(a, b) a.time - b.time)
.entries();
}
updateTrains();
// Initialize the variables
var w = 240,
h = 420,
boxSize = 20,
midSize = 30,
minHour = 2,
maxHour = 24;
// Make the scale functions
var y = pv.Scale.linear(minHour, maxHour).range(0, h);
// The root panel, with padding for labels
var vis = new pv.Panel()
.width(w)
.height(h);
// Add the vertical rules
vis.add(pv.Rule)
.data([-1, 1])
.strokeStyle("#ccc")
.left(function(m) w / 2 + m * midSize / 2.4)
.top(0)
.bottom(0);
// Add the destination labels
vis.add(pv.Label)
.data(['Northbound', 'Southbound'])
.left(function(d) d == 'Southbound' ? (w / 2 + midSize / 2) : null)
.right(function(d) d == 'Northbound' ? (w / 2 + midSize / 2) : null)
.top(boxSize)
.font("13px sans-serif")
.textAlign(function(d) d == 'Southbound' ? 'left' : 'right');
// Add a panel for each hour
var hour = vis.add(pv.Panel)
.data(function() trains)
.top(function(d) y(d.key));
// Add the center hour label for each hour panel
hour.add(pv.Label)
.left(w / 2)
.textAlign("center")
.font("bold 10px sans-serif")
.text(function(d) {
var h = d.key;
return (h > 12 ? (h > 24 ? h - 24 : h - 12) : h) + ((h > 11 && h < 24) ? 'p' : 'a');
});
// Add the left and right panels and populate them with the corresponding times
hour.add(pv.Panel)
.data(function(d) d.values)
.left(function(d) d.key == 'S' ? (w / 2 + midSize / 2) : 0)
.right(function(d) d.key == 'N' ? (w / 2 + midSize / 2) : 0)
.add(pv.Label)
.data(function(d) d.values)
.left(function(d) d.bound == 'S' ? (boxSize * this.index) : null)
.right(function(d) d.bound == 'N' ? (boxSize * this.index) : null)
.textAlign(function(d) d.bound == 'S' ? "left" : "right")
.textStyle(function(d) types[d.type])
.text(function(d) d.time);
// render everything
vis.render();
</script>
</div></div></body>
</html>