block by zanarmstrong 73c995405555f6b4d893

Caltrain Bike Bumps

Full Screen

index.html

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Caltrain Bike Bump Charts</title>
		<link href='//fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
		<link rel="stylesheet" href="bike.css">
		<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
	</head>
	<body>
	<p id="screenCoords"></p>
		<h1>Caltrain Bike Bumps in 2015</h1>
		<p>Just bumped from Caltrain? Report it at <a href="sfbike.org/bumpform">sfbike.org/bumpform</a>.</p>
		<div id="playPause"></div>
		<div class="module fade"><p id="quote" class="quote"></p></div>
		<p id="date" class="description"></p>
		<p id="numberBumped" class="description"></p>
		<p id="location" class="description"></p>
		<div id="svgDiv"></div>
		<img src="caltrain.png"></img>
			<!-- call JS files -->
		<script src="bike.js"></script>
	</body>
</html>

bike.css

body {
	margin: 30px;
	font-family: 'Raleway', sans-serif;
}

p {
    line-height: 1.2;
    margin-top: 0px;
    margin-bottom: 5px;
    color: #626262;
}

img {
	margin-top: 30px;
}

.module {
  width: 700px;
  margin: 30px 0 0 0;
  overflow: hidden;
  height: 102px;
}
.module p {
 	margin: 0;
	font-style: italic;
	font-size: 28px;
}

.fade {
  position: relative;
  height: 102px; 
}
.fade:after {
  content: "";
  text-align: right;
  position: absolute;
  bottom: 0;
  right: 0;
  width: 30%;
  height: 36px;
  background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 50%);
}

.description {
	font-size: 16px;
}

.bumps {
	opacity: .3;
}

.bumps.selected {
	opacity: 1;
	stroke: black;
	stroke-width: 2;
}

.station {
	fill: #8d8d8d;
	stroke: none;
}

.station.selected {
	fill: black;
}

.stationLine {
	stroke: #8d8d8d;
}

.play {
	stroke-width: 2px;
	stroke: #363636;
	fill: #363636;
	stroke-linejoin: round;
}

.pause {
	stroke: #363636;
	stroke-width: 4px;
	stroke-linecap: round;
}

.hidden {
	display: none;
}

#playPause {
	position: absolute;
	left: 620px;
	top: 220px;
}

bike.js

"use strict";

// variables
var margin = {
    top: 0,
    right: 0,
    bottom: 0,
    left: 0
  },
  width = 700 - margin.left - margin.right,
  height = 380;

var docMargin = {
  top: 30,
  right: 30,
  bottom: 30,
  left: 30
}

var lineDrop = 20,
  quoteNum = 0;

var playOn = true;

// created manually based on image --> will need to improve this
var stations = {
  "4th & King": 132,
  "22nd Street": 141,
  "Bayshore": 160,
  "South SF": 185,
  "San Bruno": 195,
  "Millbrae": 224,
  // confirm value for Broadway
  "Broadway": 230,
  "Burlingame": 245,
  "San Mateo": 277,
  "Hayward Park": 295,
  "Hillsdale": 308,
  "Belmont": 330,
  "San Carlos": 347,
  "Redwood City": 376,
  "Menlo Park": 422,
  "Palo Alto": 438,
  "California Ave": 459,
  "San Antonio": 493,
  "Mountain View": 516,
  "Sunnyvale": 551,
  "Lawrence": 576,
  "Santa Clara": 622,
  "College Park": 639,
  "San Jose": 655
}

var stationList = [
  "4th & King",
  "22nd Street",
  "Bayshore",
  "South SF",
  "San Bruno",
  "Millbrae",
  "Broadway",
  "Burlingame",
  "San Mateo",
  "Hayward Park",
  "Hillsdale",
  "Belmont",
  "San Carlos",
  "Redwood City",
  "Menlo Park",
  "Palo Alto",
  "California Ave",
  "San Antonio",
  "Mountain View",
  "Sunnyvale",
  "Lawrence",
  "Santa Clara",
  "College Park",
  "San Jose"
]

var scales = {
  // todo - remove hardcode here
  dateScale: d3.time.scale()
    .domain([d3.time.format("%Y-%m-%d").parse("2015-01-01"), d3.time.format("%Y-%m-%d").parse("2015-02-19")])
    .range([lineDrop, height - 30])
}

var color = {
  north: "#FF6F00",
  south: "#E0009D",
  unknown: "#8d8d8d"
}

// standard svg starter
var svg = d3.select('#svgDiv')
  .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 + ')');

// station line & dots
var stationGroup = svg.append("g")

stationGroup.append("line")
  .classed("stationLine", true)
  .attr("y1", lineDrop)
  .attr("y2", lineDrop)
  .attr("x1", 132 - docMargin.left)
  .attr("x2", 655 - docMargin.left)
  .attr("stroke", color.station);

stationGroup.selectAll(".station")
  .data(stationList)
  .enter()
  .append("circle")
  .attr("class", "station")
  .attr("cy", lineDrop)
  .attr("r", 3)
  .attr("cx", function(d, i) {
    return stations[d] - docMargin.left
  })
  .on("mouseover", function(d) {
    console.log(d)
  });

var intervalId = -1;

function startInterval(data) {
  if (data != undefined) {
    intervalId = setInterval(function() {
      quoteNum = (quoteNum + 1) % data.length;
      setUp(quoteNum, data)
    }, 2000);
  }
}

function setUp(j, data) {
  updateText(data[j]);
  d3.selectAll(".bumps")
    .data(data)
    .classed("selected",
      function(d, i) {
        if (i == j) {
          return true
        } else {
          return false
        }
      });
  selectStation(data[j]["Departure station "]);
}


// get bump data
d3.csv("bikeCaltrain.csv", function(error, data){
  setUpBumpCircles(data);
  setUp(quoteNum, data)
  startInterval(data);
  playPauseSetup(data);

})


function setUpBumpCircles(data) {
  svg.selectAll(".bumps")
    .data(data)
    .enter()
    .append("circle")
    .attr("class", "bumps")
    .attr("cy", function(d) {
      return scales.dateScale(d3.time.format("%m/%d/%Y").parse(d["Date of bicycle bump(s) "]))
    })
    .attr("cx", function(d) {
      return stations[d["Departure station "]] - docMargin.left
    })
    .attr("r", function(d) {
      return Math.pow(d['Total number of bumped bikes '], .5) * 4
    })
    .attr("fill", function(d) {
      if (d['Travel direction'] == "North") {
        return color.north;
      } else if (d['Travel direction'] == "South"){
        return color.south
      } else {
        return color.unknown
      }
    })
    .on("mouseover", function(d) {
      var currentStation = d["Departure station "]
      updateText(d);
      d3.selectAll(".bumps").data(data).classed("selected", false)
      d3.select(this).classed("selected", true)
      selectStation(currentStation);
      clearInterval(intervalId);
    })
    .on("mouseout", function(d) {
      updateText(d);
      d3.select(this).classed("selected", false)
      d3.selectAll(".station").data(stationList).attr("fill", color.station);
      if(playOn){
        startInterval(data);
      }
    })
}

function updateText(dataObj) {
  if (dataObj["Comments "] != undefined && dataObj["Comments "] != "") {
    console.log(dataObj["Comments "])
    document.getElementById("quote").innerHTML = dataObj["Comments "]
  } else {
    document.getElementById("quote").innerHTML = "<span style='color: #EBEBEB'>[no comment]</span>"
  }
  if (dataObj["Scheduled departure time "] != "") {
    document.getElementById("date").innerHTML =
      d3.time.format("%a %B %d, %Y")(
        d3.time.format("%m/%d/%Y").parse(dataObj["Date of bicycle bump(s) "])
      ) +
      " at " +
      dataObj["Scheduled departure time "]
  } else {
    document.getElementById("date").innerHTML = d3.time.format("%a %B %d, %Y")(
      d3.time.format("%m/%d/%Y").parse(dataObj["Date of bicycle bump(s) "]))
  }
  document.getElementById("numberBumped").innerHTML = "<strong>" + dataObj['Total number of bumped bikes '] + " people</strong> with bikes bumped"
  if (dataObj['Travel direction'] == "North") {
    var highlightColor = color.north;
  } else {
    highlightColor = color.south;
  }
  document.getElementById("location").innerHTML = "Traveling " + "<span style='color:" + highlightColor + "'>" +
    dataObj['Travel direction'] + "</span>" + " from <strong>" +
    dataObj['Departure station '] + "</strong>";
}


function selectStation(name) {
  d3.selectAll(".station")
    .data(stationList)
    .classed("selected",
      function(d) {
        if (d == name) {
          return true
        } else {
          return false
        }
      });
}


function playPauseUpdate(){
    d3.selectAll(".play").classed("hidden", playOn);
    d3.selectAll(".pause").classed("hidden", !playOn);
}

function playPauseSetup(data) {
  // handle play/pause

  var circleCenter = {
    x: 16,
    y: 50
  };

  function switchOnOff() {
    playOn = !playOn;
    d3.selectAll(".play").classed("hidden", playOn);
    d3.selectAll(".pause").classed("hidden", !playOn);
    if(playOn == false){
      console.log('clearing interval')
      clearInterval(intervalId)
    } else {
      console.log('starting interval')
      startInterval(data)
    }
  }

  var playPauseGroup = d3.select("#playPause")
    .append('svg')
    .attr('width', 32)
    .attr('height', 68)
    .on("click", switchOnOff);

  playPauseGroup.append('circle')
    .classed("playPauseCircle", true)
    .attr({
      cx: circleCenter.x,
      cy: circleCenter.y,
      r: 15,
      fill: "#BBBABA"
    })

  playPauseGroup.append('polygon')
    .classed("play", true)
    .classed("hidden", playOn)
    .attr("points", function() {
      return (circleCenter.x - 4) +
        "," + (circleCenter.y - 6) +
        " " + (circleCenter.x + 7) +
        "," + (circleCenter.y) +
        " " + (circleCenter.x - 4) +
        "," + (circleCenter.y + 6)
    });

  playPauseGroup.append('line')
    .classed("pause", true)
    .classed("hidden", !playOn)
    .attr({
      x1: circleCenter.x - 3,
      x2: circleCenter.x - 3,
      y1: circleCenter.y - 5,
      y2: circleCenter.y + 3
    });

  playPauseGroup.append('line')
    .classed("pause", true)
    .classed("hidden", !playOn)
    .attr({
      x1: circleCenter.x + 4,
      x2: circleCenter.x + 4,
      y1: circleCenter.y - 5,
      y2: circleCenter.y + 3
    });
}

bikeCaltrain.csv

Timestamp,Date of bicycle bump(s) ,Total number of bumped bikes ,Departure station ,Travel direction,Scheduled departure time ,Train number,Train type,Comments 
2/2/2015 10:17:21,01/05/2015,5,4th & King,South,5:14pm,370,,"Unfortunately your staff were turning away people when the south end bike cart was more than suffice to handle all cyclist. Your staff needs to do a better job at counting the number of available space before turn cyclist away. Simply counting number of bike through the door is not adequate since most folding bikes are left ""folded"" and does not take up room.  It should be the responsibility of your staff to ensure every space is filled when available. Your conductors and station agents really need to take a more hands on approach and walk to the carts and confirm."
2/2/2015 10:17:21,01/05/2015,1,4th & King,South,,282,Bombardier,"I have experienced this multiple times, and this customer experience is unacceptable. Please take action to increase bicycle capacity as soon as possible."
2/2/2015 10:17:21,01/06/2015,5,4th & King,South,,376,,We were told that the bike car was full.
2/2/2015 10:17:21,01/06/2015,7,Palo Alto,North,,371,Bombardier,I would like to encourage Caltrain to increase capacity for bikes on the new train cars or add another bike car. It is a rather unpleasant experience to have a time schedule planned and not be able to meet it because I am not allowed on the train.
2/2/2015 10:17:21,01/07/2015,4,Redwood City,North,,269,,Not good...
2/2/2015 10:17:21,01/07/2015,8,4th & King,South,5:14pm,,Bombardier,
2/2/2015 10:17:21,01/12/2015,8,22nd Street,South,7:19am,,,Could see at least 4 spots inside (racks with 3 bikes) but he wouldn't let any of us on
2/2/2015 10:17:21,01/12/2015,8,22nd Street,South,,314,,
2/2/2015 10:17:21,01/13/2015,1,Palo Alto,North,,323,,"I would love to see an additional bike car on this train, or a conversion to a gallery train which has more space for bikes."
2/2/2015 10:17:21,01/14/2015,2,Hillsdale,North,,323,Gallery,
2/2/2015 10:17:21,01/14/2015,4,4th & King,South,5:14pm,370,Gallery,
2/2/2015 10:17:21,01/20/2015,4,Menlo Park,North,5:19pm,,,
2/2/2015 10:17:21,01/21/2015,10,Palo Alto,North,,375,,Worth noting: the car didn't look full and there was a surveyor on the train. Not sure what that means...
2/2/2015 10:17:21,01/21/2015,1,Menlo Park,North,6:19pm,279,,
2/2/2015 10:17:21,01/23/2015,2,Menlo Park,North,5:57pm,277,,
2/2/2015 10:17:21,01/26/2015,10,Hillsdale,North,,217,,People now calling work to say they will be late.
2/2/2015 10:17:21,01/27/2015,6,Hillsdale,North,,217,,We were told that the bike car was full.
2/2/2015 10:17:21,01/27/2015,6,Hillsdale,North,,217,,Although I got on I was the only one.
2/2/2015 10:17:21,01/27/2015,1,Redwood City,North,8:45am,227,,
2/3/2015 18:03:04,02/03/2015,14,Palo Alto,North,5:54pm,277,Gallery,Might miss rental inspection
2/4/2015 0:11:22,02/03/2015,4,San Mateo,,,269,,This has never happened at this station to me and is an ominous sign of lack of bike capacity on Caltrain.
2/4/2015 0:23:05,02/03/2015,16,Palo Alto,North,5:54pm,277,,Please increase room for bikes on CalTrain. Maybe hit up Facebook or one of the other dot com entities for some funds. Seems like they have an abundance of employees in SF that bike commute. Just saying.
2/4/2015 0:27:08,02/03/2015,1,Palo Alto,,,277,,"While I understand there is limited space, this is quite an inconvenience for those who depend on Caltrain for transportation. It is only the first week on February."
2/4/2015 0:29:33,02/03/2015,6,Redwood City,,,279,,"Tonight I needed to get home to relieve my nanny at 7, but that won't happen because you stubbornly refuse to address the actual issue of bikes being bumped. Tonight I will pay for it with an extra 45 minutes of pay to my nanny, a wasted 45 minutes of time I could have spent with my daughter, and a pair of gloves that I apparently drop while running between cars.  Awesome! Implementing a queuing system would help fairness. Another improvement would be to publish detailed bump statistics by train and station so I can at least try to determine which trains to avoid. Another option, particularly at RWC for the timed transfer to local service would be allow more bikes on because there are always a ton more that get off at the very next stop. It makes no sense to bump a bunch of people only to have half the bikes get off right away. I only wish you would DO something to improve the random kick in the face that is get bumped from Caltrain. There are many options, just try something. "
2/4/2015 9:07:25,02/04/2015,3,22nd Street,South,9:02am,332,Gallery,"I was lucky enough to get on, others weren't so lucky. The train was at bike capacity, but still plenty of spare seats."
2/4/2015 17:32:19,02/04/2015,3,Redwood City,North,5:25pm,269,Gallery,I was last on. Count for rear bike car only. 
2/5/2015 11:08:28,02/03/2015,3,Menlo Park,North,5:57pm,277,Gallery,
2/5/2015 19:32:49,02/05/2015,5,Millbrae,,8:03am,220,,
2/5/2015 19:36:04,02/05/2015,3,Palo Alto,North,,269,,Bikes bumped from north car.
2/10/2015 9:12:38,02/10/2015,3,Hillsdale,North,,323,,"What's most upsetting is that I was bumped from the first car and sprinted down to the second car, only to have the doors closed a few seconds before I got there. Through the windows I could see two racks with only two bikes each on them. So the whole bumping business could have been avoided altogether with better communication. Most bikers try their best to work within the system, but in 18 months of bike commuting via Caltrain, I've mostly seen conductors act rudely or impatiently with bikers (with some refreshing exceptions). I get that Caltrain is the only commuter rail option for the peninsula, so commuters have no leverage. But conductors are making a crappy situation worse with their attitude."
2/11/2015 21:55:58,02/10/2015,8,4th & King,South,6:56pm,,,
2/11/2015 22:00:28,02/10/2015,13,Palo Alto,North,,267,,"This was my first time ever riding Caltrain. There was plenty of room on my 6:45 ride to Palo Alto, but I was disappointed by the overwhelming lack of capacity for bicycle commuters on my way back. It was really cramped and uncomfortable. Please support us bicyclists by adding enough bike capacity on your trains."
2/12/2015 5:04:57,02/11/2015,3,Redwood City,North,5:25pm,269,Gallery,
2/12/2015 8:12:48,02/12/2015,15,Redwood City,North,7:30am,319,Bombardier,There was space for bikes but the conductor was freaking out and did not let us on. 
2/12/2015 8:25:33,02/03/2015,3,San Mateo,North,5:36pm,269,Gallery,
2/12/2015 9:34:33,02/11/2015,3,Mountain View,North,7:57am,323,Bombardier,Only one bike was allowed to board. I was there 15 mins early to be sure to get a spot but came out empty. Missed my meeting in the city
2/12/2015 9:45:07,02/12/2015,12,Hillsdale,North,7:51am,217,Bombardier,Combo train due to fatality (should have been a gallery but was a bombardier and it was packed)
2/12/2015 13:43:44,02/10/2015,5,Menlo Park,North,6:46pm,385,Gallery,"It was so disheartening.  It made me really sad.  Had such a long day and just wanted to go home.  As ridership increases, can we not add more trains and/or more bike cars?"
2/12/2015 14:32:59,02/12/2015,9,San Antonio,North,7:27am,217,Bombardier,"whenever trains get combined, they bumped bikers. it really is annoying."
2/12/2015 18:58:49,02/12/2015,5,Palo Alto,North,6:43 pm,385,Gallery,A ton of bikes were getting on and filled it up. It made me late for date night :(
2/16/2015 17:35:17,02/16/2015,3,Millbrae,South,8:17am,,Bombardier,"1st SB train of the day when operating on a holiday schedule is a bombardier and far over capacity for bikes by the time it hits Millbrae...really?

Is this mendacity of sheer incompetence?

Made an extra effort to travel to a station several miles from my home station to attempt to get to work at best an hour late and ended up being several hours late, no announcement of the train on the announcement board, conductor tells me there's another train behind, which is correct, but 20 minutes behind isn't directly behind right, and isn't that always true in some capacity, there's always another train behind?"
2/19/2015 8:20:17,2/19/2015,12,Millbrae,South,8:15am,322,Gallery,There seemed room onboard
2/19/2015 8:43:08,2/19/2015,12,Millbrae,South,8:15am,322,Gallery,There seemed room onboard
2/19/2015 8:44:53,2/19/2015,8,Millbrae,South,8:32am,324,Gallery,

caltrainLatLong.csv

station,lat,long
4th & King,37.77645,-122.39471
22nd Street,37.75767,-122.39264
Bayshore,37.70954,-122.40132
South SF,37.65590,-122.40526
San Bruno,37.6297005,-122.411359
Millbrae,37.600006,-122.386534
Broadway,37.587466,-122.363233
Burlingame,37.587466,-122.363233
San Mateo,37.568209,-122.323933
Hayward Park,37.552346,-122.308916
Hillsdale,37.537503,-122.298001
Belmont,37.520504,-122.276075
San Carlos,37.507361,-122.260365
Redwood City,37.485412,-122.231957
Menlo Park,37.45418,-122.18202
Palo Alto,37.44307,-122.1649
California Ave,37.428835,-122.142703
San Antonio,37.407157,-122.107231
Mountain View,37.393879,-122.076327
Sunnyvale,37.378427,-122.030742
Lawrence,37.370815,-121.997258
Santa Clara,37.352864,-121.937178
College Park,37.342599,-121.915577
San Jose,37.330419,-121.902129

caltrainLatLong.txt

station,lat,long
4th and King,37.78310,-122.39576
22cd St San Francisco,37.75767,-122.39264
Bayshore,37.70954,-122.40132
South SF,37.65590,-122.40526