D3 Lap Chart
{ | |
"lapCount": 58, | |
"laps": [ | |
{ | |
"name": "Sebastian Vettel", | |
"placing": [1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
"pitstops": [9], | |
"mechanical": [25] | |
}, | |
{ | |
"name": "Mark Webber", | |
"placing": [2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 5, 5, 5, 5, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 9, 9], | |
"pitstops": [10, 32, 56] | |
}, | |
{ | |
"name": "Fernando Alonso", | |
"placing": [3, 18, 18, 18, 18, 15, 13, 13, 15, 13, 10, 10, 10, 9, 8, 8, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], | |
"pitstops": [8], | |
"accident": [0] | |
}, | |
{ | |
"name": "Jenson Button", | |
"placing": [4, 6, 6, 6, 6, 6, 19, 19, 12, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
"pitstops": [6, 34], | |
"accident": [0] | |
}, | |
{ | |
"name": "Felipe Massa", | |
"placing": [5, 2, 2, 2, 2, 2, 3, 3, 9, 7, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 5, 5, 6, 6, 6, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], | |
"pitstops": [8] | |
}, | |
{ | |
"name": "Nico Rosberg", | |
"placing": [6, 5, 5, 5, 5, 5, 5, 5, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 5, 5], | |
"pitstops": [8, 33] | |
}, | |
{ | |
"name": "Michael Schumacher", | |
"placing": [7], | |
"accident": [0] | |
}, | |
{ | |
"name": "Rubens Barrichello", | |
"placing": [8, 9, 9, 9, 9, 9, 8, 7, 10, 8, 7, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 9, 8, 8], | |
"pitstops": [8, 31], | |
"accident": [55] | |
}, | |
{ | |
"name": "Robert Kubica", | |
"placing": [9, 4, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], | |
"pitstops": [8] | |
}, | |
{ | |
"name": "Adrian Sutil", | |
"placing": [10, 8, 8, 8, 8, 8, 7, 9, 5, 2], | |
"mechanical": [9] | |
}, | |
{ | |
"name": "Lewis Hamilton", | |
"placing": [11, 7, 7, 7, 7, 7, 6, 6, 11, 11, 8, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6], | |
"pitstops": [8, 34], | |
"accident": [55] | |
}, | |
{ | |
"name": "Sebastien Buemi", | |
"placing": [12], | |
"accident": [0] | |
}, | |
{ | |
"name": "Vitantonio Liuzzi", | |
"placing": [13, 12, 12, 12, 12, 12, 11, 11, 3, 9, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7], | |
"pitstops": [9] | |
}, | |
{ | |
"name": "Pedro De La Rosa", | |
"placing": [14, 11, 11, 11, 11, 11, 10, 10, 13, 12, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 12, 12], | |
"pitstops": [8] | |
}, | |
{ | |
"name": "Nico Hulkenberg", | |
"placing": [15], | |
"accident": [0] | |
}, | |
{ | |
"name": "Kamui Kobayashi", | |
"placing": [16], | |
"accident": [0] | |
}, | |
{ | |
"name": "Jaime Alguersuari", | |
"placing": [17, 13, 13, 13, 13, 13, 12, 12, 4, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 11, 11], | |
"pitstops": [9, 27] | |
}, | |
{ | |
"name": "Vitaly Petrov", | |
"placing": [18, 10, 10, 10, 10, 10, 9, 8, 14, 14], | |
"pitstops": [8], | |
"accident": [9] | |
}, | |
{ | |
"name": "Heikki Kovalainen", | |
"placing": [19, 15, 15, 15, 14, 14, 14, 15, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13], | |
"pitstops": [8] | |
}, | |
{ | |
"name": "Jarno Trulli", | |
"placing": [20, 20, 20, 20, 20, 17, 15, 14, 17, 15, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10], | |
"pitstops": [1, 8, 29], | |
"mechanical": [0] | |
}, | |
{ | |
"name": "Bruno Senna", | |
"placing": [21, 14, 14, 14, 15], | |
"mechanical": [4] | |
}, | |
{ | |
"name": "Karun Chandock", | |
"placing": [22, 16, 16, 16, 16, 19, 18, 18, 19, 19, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 16, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14], | |
"pitstops": [8, 46] | |
}, | |
{ | |
"name": "Timo Glock", | |
"placing": [23, 17, 17, 17, 17, 16, 16, 16, 8, 17, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14], | |
"pitstops": [8, 9], | |
"mechanical": [41] | |
}, | |
{ | |
"name": "Luca di Grassi", | |
"placing": [24, 19, 19, 19, 19, 18, 17, 17, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 16, 16, 16, 16, 16, 17, 16], | |
"pitstops": [10, 25], | |
"mechanical": [26] | |
} | |
], | |
"lapped": [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 17, 17, 16, 16, 16, 16, 16, 16, 16,15, 15, 15, 14, 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13 , 13], | |
"safety": [1, 2, 3, 4] | |
} |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" | |
"http://www.w3.org/TR/html4/loose.dtd"> | |
<html> | |
<head> | |
<title>Formula 1 Lap Chart</title> | |
<meta http-equiv="X-UA-Compatible" content="chrome=1"> | |
<link href="style.css" media="screen" rel="stylesheet" type="text/css"/> | |
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.7.0"></script> | |
</head> | |
<body> | |
<span class="title">Australian Formula 1 Grand Prix, 2010 | |
<a href="http://creativecommons.org/licenses/by-sa/3.0/" target="_blank"><img align="top" | |
alt="Creative Commons License" | |
src="http://i.creativecommons.org/l/by-sa/3.0/80x15.png"/></a> | |
</span> | |
<span class="attrib">By <a href="http://vislives.com/" target="_blank">Chris Pudney</a></span> | |
<div id="chart"></div> | |
<span class="legend">M = mechanical failure | P = pit stop | X = accident | <span | |
class="safety">safety-car deployed</span> | <span class="lapped">lapped</span></span> | |
<script src="lap-chart.js" type="text/javascript"></script> | |
</body> | |
</html> |
// Dimensions. | |
const DIMENSIONS = getWindowDimensions(); | |
const WIDTH = DIMENSIONS.width; | |
const HEIGHT = DIMENSIONS.height - 100; | |
// Insets. | |
const INSETS = {'left': 150, 'right': 150, 'top': 30, 'bottom': 30}; | |
// Padding. | |
const PADDING = {'left': 20, 'right': 20, 'top': 15, 'bottom': 15}; | |
// Tick-mark length. | |
const TICK_MARK_LENGTH = 8; | |
// Marker radius. | |
const MARKER_RADIUS = 12; | |
// Scales. | |
const SCALES = {}; | |
// Opacity of dimmed objects. | |
var DIMMED_OPACITY = 0.3; | |
var HIGHLIGHT_OPACITY = 1.0; | |
// Visualize when document has loaded. | |
// | |
window.onload = function() { | |
// Load data. | |
d3.json("2010au.json", function(data) { | |
// Check integrity. | |
integrityCheck(data); | |
// Sort laps on finishing order. | |
data.laps.sort(function(a, b) { | |
var aLaps = a.placing.length; | |
var bLaps = b.placing.length; | |
return aLaps == bLaps ? a.placing[aLaps - 1] - b.placing[bLaps - 1] : bLaps - aLaps; | |
}); | |
// Process lap markers.. | |
data.pitstops = processLapMarkers(data, "pitstops"); | |
data.mechanical = processLapMarkers(data, "mechanical"); | |
data.accident = processLapMarkers(data, "accident"); | |
// Visualize the data. | |
visualize(data); | |
}); | |
}; | |
// Check data. | |
// | |
// data: the data to check. | |
// | |
function integrityCheck(data) { | |
var laps = data.laps; | |
var lapCount = data.lapCount; | |
// Check lap data. | |
checkLaps(laps, lapCount); | |
// Check lapped data. | |
checkLapped(data.lapped, lapCount, laps.length); | |
// Check safety car data. | |
checkSafetyCar(data.safety, lapCount); | |
} | |
// Check lap data. | |
// | |
// laps: the lap data. | |
// lapCount: number of laps. | |
// | |
function checkLaps(laps, lapCount) { | |
for (var j = 0; | |
j < laps.length; | |
j++) { | |
// Has name? | |
var name = laps[j].name; | |
if (name == undefined || name.length == 0) { | |
alert("Warning: invalid name for element " + j); | |
} | |
// Has placings? | |
var places = laps[j].placing; | |
if (places == undefined) { | |
alert("Warning: missing placings for element " + j + " (" + name + ")"); | |
} | |
else if (places.length == 0 || places.length > lapCount + 1) { | |
alert("Warning: invalid number of placings (" + places.length + ") for element " + j + | |
" (" + name + ") - expected between 1 and " + (lapCount - 1)); | |
} | |
// Check markers. | |
var maxLaps = places.length; | |
checkMarker(laps[j].pitstops, "pitstop", maxLaps, j, name); | |
checkMarker(laps[j].mechanical, "mechanical", maxLaps, j, name); | |
checkMarker(laps[j].accident, "accident", maxLaps, j, name); | |
} | |
for (var i = 0; | |
i < lapCount; | |
i++) { | |
var positions = []; | |
for (j = 0; | |
j < laps.length; | |
j++) { | |
places = laps[j].placing; | |
if (places.length > i) { | |
// Valid placing? | |
var placing = places[i]; | |
if (isNaN(placing) || placing < 1 || placing % 1 != 0) { | |
alert("Warning: invalid placing '" + placing + "' for " + laps[j].name) | |
} | |
else { | |
var count = positions[placing]; | |
positions[placing] = isNaN(count) ? 1 : count + 1 | |
} | |
} | |
} | |
// Check for duplicate/missing positions. | |
for (j = 1; | |
j < positions.length; | |
j++) { | |
count = positions[j]; | |
if (count != 1) { | |
alert("Warning: data inconsistent: lap " + i + ", position " + j + ", count " + count); | |
} | |
} | |
} | |
} | |
// Check integrity of marker data. | |
// | |
// marker: marker data. | |
// name: driver name. | |
// type: text description of marker. | |
// max: maximum allowed lap value of marker. | |
// index: index of driver in list. | |
// | |
function checkMarker(marker, type, max, index, name) { | |
if (marker != undefined) { | |
// Check marker. | |
for (var i = 0; | |
i < marker.length; | |
i++) { | |
var stop = marker[i]; | |
if (isNaN(stop) || stop < 0 || stop >= max || stop % 1 != 0) { | |
alert("Warning: invalid " + type + " (" + stop + ") for element " + index + " (" + name + ")"); | |
} | |
} | |
} | |
} | |
// Check lapped data. | |
// | |
// lapped: the lapped data. | |
// lapCount: number of laps. | |
// driverCount: number of drivers. | |
// | |
function checkLapped(lapped, lapCount, driverCount) { | |
if (lapped != undefined) { | |
var lappedLength = lapped.length; | |
if (lappedLength != lapCount) { | |
alert("Lapped array length (" + lappedLength + ") incorrect - expected length " + lapCount); | |
} | |
for (var j = 1; | |
j < lappedLength; | |
j++) { | |
// Valid position. | |
var position = lapped[j]; | |
if (isNaN(position) || position % 1 != 0 || position < -1 || position > driverCount) { | |
alert("Invalid lapped position: element " + j + " (" + position | |
+ "); expected integer between -1 and " + driverCount); | |
} | |
} | |
} | |
} | |
// Check safety car data. | |
// | |
// safety: safety car data. | |
// lapCount: number of laps. | |
// | |
function checkSafetyCar(safety, lapCount) { | |
if (safety != undefined) { | |
for (var i = 0; | |
i < safety.length; | |
i++) { | |
// Valid lap? | |
var lap = safety[i]; | |
if (isNaN(lap) || lap < 0 || lap % 1 != 0 || lap > lapCount) { | |
alert("Invalid safety car lap: element " + i + " (" + lap | |
+ "); expected integer between 0 and " + lapCount); | |
} | |
} | |
} | |
} | |
// Process lap markers. | |
// | |
// data: lap data. | |
// key: marker key. | |
// | |
function processLapMarkers(data, key) { | |
var markers = []; | |
var p = 0; | |
for (var i = 0; | |
i < data.laps.length; | |
i++) { | |
var lapData = data.laps[i]; | |
var laps = lapData[key]; | |
if (laps != undefined) { | |
for (var j = 0; | |
j < laps.length; | |
j++) { | |
var lap = laps[j]; | |
var marker = {}; | |
marker.start = lapData.placing[0]; | |
marker.lap = lap; | |
marker.placing = lapData.placing[lap]; | |
marker.name = lapData.name; | |
markers[p++] = marker; | |
} | |
} | |
} | |
return markers; | |
} | |
// Create the visualization. | |
// | |
// data the lap data object. | |
// | |
function visualize(data) { | |
// Configure scales. | |
configureScales(data); | |
// Root panel. | |
var vis = d3.select('#chart') | |
.append('svg:svg') | |
.attr('width', WIDTH) | |
.attr('height', HEIGHT); | |
// Add safety car element. | |
addSafetyElement(vis, data.safety); | |
// Add lapped element. | |
addLappedElement(vis, data.lapped); | |
// Lap tick-lines. | |
addLapTickLines(vis, data.lapCount); | |
// Lap labels. | |
addLapLabels(vis, data.lapCount, SCALES.y.range()[0] - PADDING.bottom, '0.0em', 'top'); | |
addLapLabels(vis, data.lapCount, SCALES.y.range()[1] + PADDING.top, '0.35em', 'bottom'); | |
// Add placings poly-lines. | |
addPlacingsLines(vis, data.laps); | |
// Add name labels. | |
addDriverLabels(vis, data.laps, 'pole', SCALES.x(0) - PADDING.right, 'end') | |
.attr('y', function (d) { | |
return SCALES.y(d.placing[0] - 1); | |
}); | |
addDriverLabels(vis, data.laps, 'flag', SCALES.x(data.lapCount) + PADDING.left, 'start') | |
.attr('y', function (d, i) { | |
return SCALES.y(i); | |
}); | |
// Add markers. | |
addMarkers(vis, data.pitstops, "pitstop", "P"); | |
addMarkers(vis, data.mechanical, "mechanical", "M"); | |
addMarkers(vis, data.accident, "accident", "X"); | |
} | |
// Configure the scales. | |
// | |
// data: data set. | |
// | |
function configureScales(data) { | |
SCALES.x = d3.scale.linear() | |
.domain([0, data.lapCount]) | |
.range([INSETS.left, WIDTH - INSETS.right]); | |
SCALES.y = d3.scale.linear() | |
.domain([0, data.laps.length - 1]) | |
.range([INSETS.top, HEIGHT - INSETS.bottom]); | |
SCALES.clr = d3.scale.category20(); | |
} | |
// Highlight driver. | |
// | |
// vis: the data visualization root. | |
// index: index of driver to highlight. | |
// | |
function highlight(vis, name) { | |
// Dim others. | |
vis.selectAll('polyline') | |
.style('opacity', function(d) { | |
return d.name == name ? HIGHLIGHT_OPACITY : DIMMED_OPACITY; | |
}); | |
vis.selectAll('circle') | |
.style('opacity', function(d) { | |
return d.name == name ? HIGHLIGHT_OPACITY : DIMMED_OPACITY; | |
}); | |
vis.selectAll('text.label') | |
.style('opacity', function(d) { | |
return d.name == name ? HIGHLIGHT_OPACITY : DIMMED_OPACITY; | |
}); | |
} | |
// Remove highlights. | |
// | |
// vis: the data visualization root. | |
// | |
function unhighlight(vis) { | |
// Reset opacity. | |
vis.selectAll('polyline') | |
.style('opacity', HIGHLIGHT_OPACITY); | |
vis.selectAll('circle') | |
.style('opacity', HIGHLIGHT_OPACITY); | |
vis.selectAll('text.label') | |
.style('opacity', HIGHLIGHT_OPACITY); | |
} | |
// Add safety car laps (rectanle elements). | |
// | |
// vis: the data visualization root. | |
// data: safety car laps. | |
// | |
function addSafetyElement(vis, data) { | |
if (data != undefined) { | |
var y = SCALES.y.range()[0]; | |
var height = SCALES.y.range()[1] - y; | |
var width = SCALES.x(1) - SCALES.x(0); | |
vis.selectAll('rect.safety') | |
.data(data) | |
.enter() | |
.append('svg:rect') | |
.attr('class', 'safety') | |
.attr('x', function(d) { | |
return SCALES.x(d - 0.5); | |
}) | |
.attr('y', function() { | |
return y; | |
}) | |
.attr('height', function() { | |
return height; | |
}) | |
.attr('width', function() { | |
return width; | |
}); | |
} | |
} | |
// Add lapped rectangle elements. | |
// | |
// vis: the data visualization root. | |
// data: the lapped data. | |
// | |
function addLappedElement(vis, data) { | |
if (data != undefined) { | |
var width = SCALES.x(1) - SCALES.x(0); | |
vis.selectAll('rect.lapped') | |
.data(data) | |
.enter() | |
.append('svg:rect') | |
.attr('class', 'lapped') | |
.attr('x', function(d, i) { | |
return SCALES.x(i + 0.5); | |
}) | |
.attr('y', function(d) { | |
return SCALES.y(d > 0 ? d - 1.5 : 0); | |
}) | |
.attr('height', function(d) { | |
return d > 0 ? SCALES.y.range()[1] - SCALES.y(d - 1.5) : 0; | |
}) | |
.attr('width', function(d) { | |
return d > 0 ? width : 0; | |
}); | |
} | |
} | |
// Add lap tick-lines. | |
// | |
// vis: the data visualization root. | |
// lapCount: number of laps. | |
// | |
function addLapTickLines(vis, lapCount) { | |
vis.selectAll('line.tickLine') | |
.data(SCALES.x.ticks(lapCount)) | |
.enter().append('svg:line') | |
.attr('class', 'tickLine') | |
.attr('x1', function(d) { | |
return SCALES.x(d + 0.5); | |
}) | |
.attr('x2', function(d) { | |
return SCALES.x(d + 0.5); | |
}) | |
.attr('y1', SCALES.y.range()[0] - TICK_MARK_LENGTH) | |
.attr('y2', SCALES.y.range()[1] + TICK_MARK_LENGTH) | |
.attr('visibility', function(d) { | |
return d <= lapCount ? 'visible' : 'hidden' | |
}); | |
} | |
// Add lap labels. | |
// | |
// vis: the data visualization root. | |
// data: lap data. | |
// y: y position of labels. | |
// dy: y offset. | |
// cssClass: CSS class id. | |
// | |
function addLapLabels(vis, data, y, dy, cssClass) { | |
vis.selectAll('text.lap.' + cssClass) | |
.data(SCALES.x.ticks(data)) | |
.enter().append('svg:text') | |
.attr('class', 'lap ' + cssClass) | |
.attr('x', function(d) { | |
return SCALES.x(d); | |
}) | |
.attr('y', y) | |
.attr('dy', dy) | |
.attr('text-anchor', 'middle') | |
.text(function(d, i) { | |
return i > 0 ? i : ''; | |
}); | |
} | |
// Add placings polyline elements. | |
// | |
// vis: the visualization root. | |
// laps: lap data. | |
// | |
function addPlacingsLines(vis, laps) { | |
vis.selectAll('polyline.placing') | |
.data(laps) | |
.enter() | |
.append('svg:polyline') | |
.attr('class', 'placing') | |
.attr('points', function(d) { | |
var points = []; | |
for (var i = 0; | |
i < d.placing.length; | |
i++) { | |
points[i] = SCALES.x(i) + ',' + SCALES.y(d.placing[i] - 1); | |
} | |
if (points.length > 0) | |
points.push(SCALES.x(i - 0.5) + ',' + SCALES.y(d.placing[i - 1] - 1)); | |
return points.join(' '); | |
}) | |
.style('stroke', function(d) { | |
return SCALES.clr(d.placing[0]); | |
}) | |
.on('mouseover', function(d) { | |
highlight(vis, d.name); | |
}) | |
.on('mouseout', function() { | |
unhighlight(vis); | |
}); | |
} | |
// Add driver name labels. | |
// | |
// vis: the data visualization root. | |
// laps: the lap data. | |
// cssClass: CSS class id. | |
// textAnchor: text-anchor value. | |
// | |
function addDriverLabels(vis, laps, cssClass, x, textAnchor) { | |
return vis.selectAll('text.label.' + cssClass) | |
.data(laps) | |
.enter() | |
.append('svg:text') | |
.attr('class', 'label ' + cssClass) | |
.attr('x', x) | |
.attr('dy', '0.35em') | |
.attr('text-anchor', textAnchor) | |
.text(function(d) { | |
return d.name; | |
}) | |
.style('fill', function(d) { | |
return SCALES.clr(d.placing[0]); | |
}) | |
.on('mouseover', function(d) { | |
highlight(vis, d.name); | |
}) | |
.on('mouseout', function() { | |
unhighlight(vis); | |
}); | |
} | |
// Add markers. | |
// | |
// vis: the visualization root. | |
// data: marker data. | |
// class: marker sub-class. | |
// label: marker label. | |
// | |
function addMarkers(vis, data, cssClass, label) { | |
label = label || "P"; | |
// Place circle glyph. | |
vis.selectAll("circle.marker." + cssClass) | |
.data(data) | |
.enter() | |
.append("svg:circle") | |
.attr("class", "marker " + cssClass) | |
.attr("cx", function(d) { | |
return SCALES.x(d.lap); | |
}) | |
.attr("cy", function(d) { | |
return SCALES.y(d.placing - 1); | |
}) | |
.attr("r", MARKER_RADIUS) | |
.style("fill", function(d) { | |
return SCALES.clr(d.start); | |
}) | |
.on('mouseover', function(d) { | |
highlight(vis, d.name); | |
}) | |
.on('mouseout', function() { | |
unhighlight(vis); | |
}); | |
// Place text. | |
vis.selectAll("text.label.marker" + cssClass) | |
.data(data) | |
.enter() | |
.append("svg:text") | |
.attr("class", "label marker" + cssClass) | |
.attr("x", function(d) { | |
return SCALES.x(d.lap); | |
}) | |
.attr("y", function(d) { | |
return SCALES.y(d.placing - 1); | |
}) | |
.attr("dy", "0.35em") | |
.attr("text-anchor", "middle") | |
.text(label) | |
.on('mouseover', function(d) { | |
highlight(vis, d.name); | |
}) | |
.on('mouseout', function() { | |
unhighlight(vis); | |
}); | |
} | |
// Gets the window dimensions. | |
// | |
function getWindowDimensions() { | |
var width = 630; | |
var height = 460; | |
if (document.body && document.body.offsetWidth) { | |
width = document.body.offsetWidth; | |
height = document.body.offsetHeight; | |
} | |
if (document.compatMode == 'CSS1Compat' && document.documentElement && document.documentElement.offsetWidth) { | |
width = document.documentElement.offsetWidth; | |
height = document.documentElement.offsetHeight; | |
} | |
if (window.innerWidth && window.innerHeight) { | |
width = window.innerWidth; | |
height = window.innerHeight; | |
} | |
return {'width': width, 'height': height}; | |
} |
body { | |
margin: 0; | |
padding: 0; | |
background-color: #000000; | |
font-family: sans-serif; | |
font-size: 12px; | |
color: #666666; | |
overflow: hidden; | |
} | |
a:link { | |
color: #ccccff; | |
} | |
a:visited { | |
color: #cccccc; | |
} | |
a:active { | |
color: #ffccff; | |
} | |
a.hover { | |
color: #ffcccc; | |
} | |
.title { | |
float: left; | |
font-size: 14px; | |
font-weight: bold; | |
color: #ffffff; | |
} | |
.attrib { | |
float: right; | |
font-size: 14px; | |
color: #ffffff; | |
} | |
text.lap { | |
fill: #999999; | |
} | |
text.label { | |
fill: #000000; | |
} | |
text.label.marker { | |
font-weight: bold; | |
} | |
polyline.placing { | |
fill: none; | |
stroke-width: 5; | |
} | |
line.tickLine { | |
stroke: #999999; | |
} | |
.lapped { | |
background-color: #333333; | |
stroke: #333333; | |
fill: #333333; | |
} | |
.safety{ | |
background-color: #330000; | |
stroke: #330000; | |
fill: #330000; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment