Skip to content

Instantly share code, notes, and snippets.

@minikomi
Created September 26, 2014 05:04
Show Gist options
  • Save minikomi/65c575f14ae6d9c44b2e to your computer and use it in GitHub Desktop.
Save minikomi/65c575f14ae6d9c44b2e to your computer and use it in GitHub Desktop.
daily programmer #181
<!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