A parallel coordinates plot of the time taken for each of the 723 athletes who ran the Lakeland 50 between the 8 checkpoints.
Click and drag on an checkpoint ‘y axis’ to filter the results to just include those that reached the checkpoint at that time.
forked from ColinEberhardt‘s block: Lakeland 50 Splits
'use strict';
function parseTime(time) {
if (!time) {
return undefined;
}
var parts = time.split(':');
if (parts.length !== 3) {
return undefined;
}
parts = parts.map(Number);
return parts[0] * 60 + parts[1] + parts[0] / 60;
}
d3.csv('results.csv', function (row, _, columns) {
// parse the times into seconds
var checkpoints = columns.slice(3);
checkpoints.forEach(function (checkpoint) {
row[checkpoint] = parseTime(row[checkpoint]);
});
return row;
}, csvLoaded);
var keyValueToObject = function keyValueToObject(keyValues) {
var obj = {};
keyValues.forEach(function (k) {
obj[k.key] = k.value;
});
return obj;
};
function csvLoaded(error, data) {
if (error) {
console.error(error);
}
// compute the checkpoint deltas
var checkpoints = data.columns.slice(3);
data.forEach(function (row) {
checkpoints.forEach(function (checkpoint, index) {
if (index > 1) {
row[checkpoint + '-Delta'] = row[checkpoint] - row[checkpoints[index - 1]];
} else {
row[checkpoint + '-Delta'] = row[checkpoint];
}
});
});
var checkpointFilter = {
checkpoint: checkpoints[0],
values: [0, 0],
valuesDomain: [0, 0]
};
// compute the scale domians
var yExtent = fc.extentLinear().pad([0.1, 0.1]);
var yScales = checkpoints.map(function (checkpoint) {
return d3.scaleLinear().domain(yExtent(data.map(function (d) {
return d[checkpoint + '-Delta'];
})));
});
var xScale = d3.scalePoint().domain(checkpoints);
var lineData = d3.line().defined(function (d) {
return d.value;
}).x(function (d) {
return xScale(d.checkpoint);
}).y(function (d, i) {
return yScales[i](d.value);
});
var rowToLine = function rowToLine(row) {
var arr = checkpoints.map(function (checkpoint) {
return {
value: row[checkpoint + '-Delta'],
checkpoint: checkpoint
};
});
return lineData(arr);
};
var brush = d3.brushY().on('brush', function (d, i) {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === 'draw') return;
checkpointFilter = {
checkpoint: d,
values: d3.event.selection,
valuesDomain: d3.event.selection.map(yScales[i].invert)
};
d3.select('#chart').node().requestRedraw();
});
var xScaleLocation;
d3.select('#chart').on('measure', function (d, i, nodes) {
yScales.forEach(function (scale) {
return scale.range([event.detail.height, 0]);
});
xScale.range([0, event.detail.width]);
brush.extent([[-8, 0], [8, event.detail.height]]);
xScaleLocation = event.detail.height;
}).on('draw', function (d, i, nodes) {
var svg = d3.select(nodes[i]).select('svg');
var pathJoin = fc.dataJoin('g', 'run');
var join = pathJoin(svg, data);
join.enter().append('path');
join.select('path').attr('d', rowToLine);
join.classed('highlight', function (d) {
return d[checkpointFilter.checkpoint + '-Delta'] > checkpointFilter.valuesDomain[1] && d[checkpointFilter.checkpoint + '-Delta'] < checkpointFilter.valuesDomain[0];
});
join.enter().append('text').text(function (d) {
return d.Name;
});
join.select('text').attr('transform', function (d) {
return 'translate(-5, ' + yScales[0](d[checkpoints[0]]) + ')';
}
// render the y-axes
);var axisJoin = fc.dataJoin('g', 'y-axis');
axisJoin(svg, yScales).each(function (d, index, group) {
var axis = d3.axisRight().scale(d);
d3.select(group[index]).attr('transform', 'translate(' + xScale(checkpoints[index]) + ', 0)').call(axis);
});
// render the x-axis
var xAxisJoin = fc.dataJoin('g', 'x-axis');
xAxisJoin(svg, [0]).classed('x-scale', true).call(d3.axisBottom().scale(xScale)).attr('transform', 'translate(0, ' + xScaleLocation + ')');
// render the brushes
var brushJoin = fc.dataJoin('g', 'brush');
brushJoin(svg, checkpoints).attr('transform', function (d, i) {
return 'translate(' + xScale(checkpoints[i]) + ', 0)';
}).call(brush).call(brush.move, function (d) {
if (checkpointFilter.checkpoint === d) {
return checkpointFilter.values;
} else {
return undefined;
}
}).selectAll("rect").attr("x", -8).attr("width", 16);
}).node().requestRedraw();
}
<!DOCTYPE html>
<html>
<script src="https://unpkg.com/d3@4.6.0"></script>
<script src="https://unpkg.com/d3fc@13.0.1"></script>
<style>
body {
font-family: sans-serif;
}
g.run path {
fill: none;
stroke: black;
stroke-width: 0.02;
}
g.run text {
text-anchor: end;
font-size: 10px;
opacity: 0.00;
}
g.run.highlight text {
opacity: 0.5;
}
g.run.highlight path {
stroke: steelblue;
stroke-width: 1;
opacity: 1;
}
.x-scale path {
display: none;
}
</style>
<body>
<d3fc-group auto-resize>
<d3fc-svg id='chart' style='width: 85%; height: 400px; display: block; margin: 50px; margin-left: 100px'/>
</d3fc-group>
<script src="script.js"></script>
</body>
</html>