Created
September 26, 2014 05:04
-
-
Save minikomi/65c575f14ae6d9c44b2e to your computer and use it in GitHub Desktop.
daily programmer #181
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title></title> | |
<style> | |
body{ | |
margin: 0 auto; | |
width: 960px; | |
} | |
.title{ | |
font-size: 11px; | |
font-family: "Hiragino Kaku Gothic Pro", Verdana, Arial, Helvetica, "ヒラギノ角ゴ Pro W3", "Osaka", "MS Pゴシック", sans-serif; | |
fill: #444; | |
} | |
.axis path, | |
line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.axis text { | |
font: 10px sans-serif; | |
} | |
text.label { | |
font-family: "Hiragino Kaku Gothic Pro", Verdana, Arial, Helvetica, "ヒラギノ角ゴ Pro W3", "Osaka", "MS Pゴシック", sans-serif; | |
font-size: 9px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1> | |
<a href="http://www.reddit.com/r/dailyprogrammer/comments/2hcwzn/09242014_challenge_181_intermediate_average_speed/"> | |
Daily Programmer #181 - Average Speed Cameras | |
</a> | |
</h1> | |
<h2>Input Data</h2> | |
<textarea id="input" style="width: 600px; height: 400px;"> | |
Speed limit is 60.00 mph. | |
Speed camera number 1 is 0 metres down the motorway. | |
Speed camera number 2 is 600 metres down the motorway. | |
Speed camera number 3 is 855 metres down the motorway. | |
Speed camera number 4 is 1355 metres down the motorway. | |
Start of log for camera 1. | |
Vehicle G122 IVL passed camera 1 at 09:36:12. | |
Vehicle H151 KEE passed camera 1 at 09:36:15. | |
Vehicle U109 FIJ passed camera 1 at 09:36:20. | |
Vehicle LO04 CHZ passed camera 1 at 09:36:23. | |
Vehicle I105 AEV passed camera 1 at 09:36:28. | |
Vehicle J828 EBC passed camera 1 at 09:36:29. | |
Vehicle WF EP7 passed camera 1 at 09:36:32. | |
Vehicle H108 KYL passed camera 1 at 09:36:33. | |
Vehicle R815 FII passed camera 1 at 09:36:34. | |
Vehicle QW04 SQU passed camera 1 at 09:36:34. | |
Start of log for camera 2. | |
Vehicle G122 IVL passed camera 2 at 09:36:42. | |
Vehicle LO04 CHZ passed camera 2 at 09:36:46. | |
Vehicle H151 KEE passed camera 2 at 09:36:51. | |
Vehicle QW04 SQU passed camera 2 at 09:36:53. | |
Vehicle J828 EBC passed camera 2 at 09:36:53. | |
Vehicle R815 FII passed camera 2 at 09:36:55. | |
Vehicle U109 FIJ passed camera 2 at 09:36:56. | |
Vehicle H108 KYL passed camera 2 at 09:36:57. | |
Vehicle I105 AEV passed camera 2 at 09:37:05. | |
Vehicle WF EP7 passed camera 2 at 09:37:10. | |
Start of log for camera 3. | |
Vehicle LO04 CHZ passed camera 3 at 09:36:55. | |
Vehicle G122 IVL passed camera 3 at 09:36:56. | |
Vehicle H151 KEE passed camera 3 at 09:37:03. | |
Vehicle QW04 SQU passed camera 3 at 09:37:03. | |
Vehicle J828 EBC passed camera 3 at 09:37:04. | |
Vehicle R815 FII passed camera 3 at 09:37:09. | |
Vehicle U109 FIJ passed camera 3 at 09:37:11. | |
Vehicle H108 KYL passed camera 3 at 09:37:12. | |
Vehicle I105 AEV passed camera 3 at 09:37:20. | |
Vehicle WF EP7 passed camera 3 at 09:37:23. | |
Start of log for camera 4. | |
Vehicle LO04 CHZ passed camera 4 at 09:37:13. | |
Vehicle QW04 SQU passed camera 4 at 09:37:24. | |
Vehicle J828 EBC passed camera 4 at 09:37:26. | |
Vehicle G122 IVL passed camera 4 at 09:37:28. | |
Vehicle R815 FII passed camera 4 at 09:37:28. | |
Vehicle H151 KEE passed camera 4 at 09:37:29. | |
Vehicle H108 KYL passed camera 4 at 09:37:36. | |
Vehicle I105 AEV passed camera 4 at 09:37:42. | |
Vehicle WF EP7 passed camera 4 at 09:37:44. | |
Vehicle U109 FIJ passed camera 4 at 09:37:45. | |
</textarea> | |
<input type="button" style="margin-bottom: 40px;" value="Redraw" id="redraw" /> | |
<h2>Chart</h2> | |
<div id="grid"></div> | |
<script src="http://d3js.org/d3.v3.min.js" type="text/javascript"></script> | |
<script> | |
// set up data ----------------------------------------------------------------- | |
function plot() { | |
var rawinput = document.getElementById("input") | |
.value | |
var lines = rawinput.split("\n"); | |
var limitmatches = lines[0].match(/is ([\d\.]+) (mph|km\/h)/) | |
, limitunits = limitmatches[2] | |
, converter = (limitunits == "mph") ? 0.44704 : 0.277778 | |
, meters_per_s = limitmatches[1] * converter; | |
var speedcameras = []; | |
lines = lines.slice(1); | |
var finished = false; | |
while(!finished) { | |
if(lines.length === 0) break; | |
var l = lines[0] | |
var m = l.match(/number (\d+) is (\d+)/) | |
if(m) { | |
speedcameras.push({camera: m[1], distance: +m[2]}) | |
lines = lines.slice(1) | |
} else { | |
finished = true; | |
} | |
} | |
var maxDistance = d3.max(speedcameras, function(c){return c.distance}); | |
var vehicles = {}; | |
var currentCam; | |
var minTime = Infinity; | |
var maxTime = 0; | |
var sHeight = 10; | |
var cols = d3.scale.category20(); | |
lines.forEach(function(l){ | |
var infomatch = l.match(/Start of log for camera (\d+)\./); | |
var logmatch = l.match(/Vehicle ([A-Z,0-9,\s]+) passed camera \d+ at ([0-9,:]+)/); | |
if(infomatch){ | |
currentCam = +infomatch[1] | |
} else if (logmatch) { | |
var vehicle = logmatch[1] | |
var timecode = logmatch[2].split(":") | |
var time = new Date() | |
time.setHours(timecode[0]); | |
time.setMinutes(timecode[1]); | |
time.setSeconds(timecode[2]); | |
if (time < minTime) { | |
minTime = time; | |
} | |
if (time > maxTime) { | |
maxTime = time; | |
} | |
var vobj = {cam: currentCam, time: time, col: cols(vehicle)}; | |
if(vehicles[vehicle]) { | |
vehicles[vehicle].push(vobj) | |
} else { | |
vehicles[vehicle] = [vobj]; | |
} | |
} | |
}); | |
vehicles = d3.map(vehicles).entries(); | |
function attachSpeeds(v){ | |
v.speeds = v.value.slice(1).reduce(function(acc,d){ | |
var delta_d = speedcameras[d.cam-1].distance - speedcameras[acc.last.cam-1].distance; | |
var delta_t = (d.time - acc.last.time) / 1000; | |
var speed = delta_d/delta_t; | |
acc.speeds.push(speed); | |
acc.last = d; | |
return acc; | |
}, {vn: v.key, speeds: [], last: v.value[0]}).speeds; | |
return v; | |
} | |
vehicles = vehicles.map(attachSpeeds); | |
// set up axes ---------------------------------------------------------------- | |
var y = d3.time.scale() | |
.domain([minTime, maxTime]); | |
var height = y.ticks(d3.time.seconds).length * sHeight; | |
y.rangeRound([0,height]); | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.ticks(d3.time.seconds, 2) | |
.tickFormat(d3.time.format('%H:%m:%S')) | |
var x = d3.scale.linear() | |
.domain([0, maxDistance]) | |
.range([0,400]); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.orient("top") | |
.ticks(10) | |
// set up svg ------------------------------------------------------------------ | |
var margin = {top: 20, right: 10, bottom: 20, left: 80}; | |
var width = 960, | |
height; | |
var svg = d3.select("#grid").html("").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 + ")"); | |
// draw ------------------------------------------------------------------------ | |
svg.append('g') | |
.attr('class', 'y axis') | |
.call(yAxis); | |
svg.append('g') | |
.attr('class', 'x axis') | |
.call(xAxis); | |
var cameras = svg.selectAll(".camera") | |
.data(speedcameras).enter() | |
.append("g") | |
.attr("class", "camera") | |
.attr("transform",function(d){ | |
return "translate(" + x(d.distance) + ", 0)"; | |
}) | |
cameras.append("line") | |
.attr("x1", 0) | |
.attr("x2", 0) | |
.attr("y1", 0) | |
.attr("y2", height) | |
.attr("class", "cameraline") | |
.attr("stroke-dasharray", "2, 4") | |
cameras.append("text") | |
.attr("x", 0) | |
.attr("y", height + 20) | |
.attr("text-anchor", "middle") | |
.text(function(d){return "Cam " + d.camera + " (" + d.distance + "m)"}) | |
.attr("font-size", "12px") | |
.attr("font-family", "sans-serif") | |
var vehicleGroup = svg.selectAll(".vehicle") | |
.data(vehicles) | |
.enter() | |
.append("g") | |
.attr("class", "vehicle") | |
.attr("data-vehicle", function(d){return d.key}) | |
var lineFunction = d3.svg.line() | |
.x(function(d){return x(speedcameras[d.cam - 1].distance)}) | |
.y(function(d){return y(d.time)}) | |
.interpolate("linear") | |
vehicleGroup.selectAll("path").data(function(d){return [d.value]}) | |
.enter().append("path") | |
.attr("d", lineFunction) | |
.attr("stroke", function(d){return d[0].col}) | |
.attr("fill", "none") | |
vehicleGroup.selectAll(".point").data(function(d){return d.value}) | |
.enter() | |
.append("circle") | |
.attr("r", 4) | |
.attr("cx", function(d){return x(speedcameras[d.cam - 1].distance)}) | |
.attr("cy", function(d){return y(d.time)}) | |
.attr("fill", function(d){return d.col}) | |
.attr("class", "point") | |
var table = svg.append("g").attr("class", "vehicle-table") | |
.attr("transform", "translate(560,40)") | |
.attr("font-size", "12px") | |
.attr("font-family", "sans-serif") | |
table.append("text") | |
.text("Vehicle") | |
.attr("font-weight", "bold") | |
.attr("text-anchor", "end") | |
.attr("x", -40) | |
.attr("y", -30); | |
table.append("line") | |
.attr("x1", -100) | |
.attr("x2", ((speedcameras.length -1) * 80) - 20) | |
.attr("y1", -20) | |
.attr("y2", -20) | |
table.append("line") | |
.attr("x1", -30) | |
.attr("x2", -30) | |
.attr("y1", -20) | |
.attr("y2", vehicles.length * 28) | |
.attr("stroke-dasharray", "1,3") | |
for(i = 1; i < speedcameras.length; i++) { | |
table.append("text") | |
.text("Cam" + (i) + "-" + (i+1)) | |
.attr("text-anchor", "end") | |
.attr("x", 28 +((i-1) * 80)) | |
.attr("y", -30) | |
} | |
var rows = table.selectAll(".row").data(vehicles).enter() | |
.append("g") | |
.attr("data-vehicle", function(d){return d.key}) | |
.attr("transform", function(_, i){return "translate(0," + i * 30 + ")"}) | |
.on("mouseover", function(d){ | |
d3.selectAll(".vehicle") | |
.attr("opacity", 0.2) | |
d3.selectAll(".vehicle") | |
.filter(function(v){ | |
return d.key == v.key | |
}) | |
.attr("opacity", 1) | |
}).on("mouseout", function(d){ | |
d3.selectAll(".vehicle") | |
.attr("opacity", 1) | |
}) | |
.attr("style", "cursor: pointer") | |
rows.append("rect") | |
.attr("x", -100) | |
.attr("y", -10) | |
.attr("fill", "transparent") | |
.attr("height", 30) | |
.attr("width", 500) | |
rows.append("text").text(function(d){return d.key}) | |
.attr("font-weight", "bold") | |
.attr("text-anchor", "end") | |
.attr("dx", -40) | |
.attr("fill", function(d){ | |
return cols(d.key) | |
}) | |
rows.selectAll(".speed").data(function(d){return d.speeds}) | |
.enter() | |
.append("text") | |
.attr("x", function(_, i){return 40 +(i * 80)}) | |
.attr("text-anchor", "end") | |
.text(function(d){return d3.format('05.2f')(d / converter) + limitunits}) | |
.attr("fill", function(d){ | |
if(d > meters_per_s) { | |
return "#f00" | |
} else { | |
return "#444" | |
} | |
}); | |
} | |
plot(); | |
d3.select("#redraw").on("click", plot) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment