Skip to content

Instantly share code, notes, and snippets.

@johnwalley
Last active January 13, 2020 03:46
Show Gist options
  • Save johnwalley/8cf264cb5eb7f2277a4b5103d2ede851 to your computer and use it in GitHub Desktop.
Save johnwalley/8cf264cb5eb7f2277a4b5103d2ede851 to your computer and use it in GitHub Desktop.
WEHoRR Winning Times
license: mit
We can make this file beautiful and searchable if this error is corrected: It looks like row 3 should actually have 4 columns, instead of 6 in line 2.
year,time,flow,crew
2017,18:31.1,,Leander Club
2016,19:17.7,74.8,Leander RC, Reading RC, Tees RC
2015,18:58.6,62.47,Army BC, Imperial College BC, London RC, Marlow RC, Minerva-Bath RC, Oxford Brookes University
2014,17:42.21,137.9,Army RC/Gloucester RC/Imperial College BC/London RC/ Minerva-Bath RC/Oxford Brookes University BC/Tees RC
2013,18:16.57,177.7,Imperial College BC A
2012,20:10.92,16.8,Thames RC A
2011,18:06.57,47,Leander Club
2010,18:10.67,87.4,Gloucester / Imperial College / Marlow / Reading University / Thames / UL
2009,18:28.27,104,London / Hollandia / Thames / Marlow / UL
2008,19:32.81,54,Osiris BC A
2007,18:14.9,122,Marlow / Rebecca / Tideway Scullers / Thames
2006,19:13.61,72.3,Birmhm U/Mlw/Read U/Rebc/Thames/TSS
2005,19:20.83,41.1,Leander Club
2004,18:18.79,44.3,IC/MARLO/READING UNI/ROB ROY/THA/TSS
2003,19:36.58,87.7,IC/KINGST/MARLW/OXF BR/THAMES
2001,19:22.28,132,THAMES RC A
2000,18:06.4,180,MARLOW/NCRA/QT/SHEFF/THAMES
1999,18:33.88,75.8,MARLOW RC A
1998,18:28.31,123,MARLOW A
1997,18:52.90,35.9,THAMES RC A
1996,18:24.52,51,KINGSTON/THAMES/TIDEWAY SCULLERS
1995,18:14.6,120,KINGSTON/THAMES/TSS
1993,19:03.7,,TIDEWAY SCULLERS SCHOOL 'A'
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WEHoRR winning times</title>
<style>
html {
height: 100%;
}
body {
height: 100vh;
margin: 0px;
display: flex;
font-family: sans-serif;
background-color: #f0ece9;
flex-direction: column;
overflow: hidden;
}
#header {
background-color: #f5f3f2;
padding-left: 22px;
padding-top: 12px;
border-bottom: 1px solid white;
}
#header #headline {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#header h1 {
font-family: Helvetica, sans-serif;
font-size: 20px;
color: #635f5d;
margin-top: 0px;
margin-bottom: 5px;
}
#header p {
font-family: Georgia, serif;
font-size: 14px;
font-style: italic;
color: #8e8883;
margin-top: 5px;
margin-bottom: 5px;
}
#footer {
height: 22px;
line-height: 22px;
font-family: Georgia, serif;
font-size: 12px;
color: #625e5b;
background-color: #e6e2df;
padding-left: 22px;
border-top: 1px dashed #cdcdc9;
}
#footer span {
display: inline-block;
vertical-align: middle;
}
div#chart {
flex: 1;
}
.title {
font-family: Helvetica, sans-serif;
font-size: 30px;
fill: #625e5b;
}
svg text {
fill: #635f5d;
}
.axis--x {
font-size: 2vw;
}
.axis--x .domain {
display: none;
}
.axis--x .tick line {
stroke: white;
}
.label {
font-size: 2vw;
}
.axis--yLeft {
font-size: 2vw;
}
.axis--yLeft .domain {
display: none;
}
.axis--yLeft .tick line {
stroke: white;
}
.axis--yRight {
font-size: 2vw;
}
.axis--yRight .domain {
display: none;
}
.axis--yRight.tick line {
stroke: white;
}
#tooltip {
position: absolute;
pointer-events: none;
padding: 6px;
background-color: white;
border: 1px solid black;
border-radius: 4px;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
}
#tooltip.hidden {
display: none;
}
@media screen and (max-width: 500px) {
.axis--x {
font-size: 10px;
}
.axis--yLeft {
font-size: 10px;
}
.axis--yRight {
font-size: 10px;
}
.label {
font-size: 10px;
}
}
@media screen and (min-width: 800px) {
.axis--x {
font-size: 16px;
}
.axis--yLeft {
font-size: 16px;
}
.axis--yRight {
font-size: 16px;
}
.label {
font-size: 16px;
}
}
</style>
<div id="header">
<div id="headline">
<h1>WEHoRR winning times</h1>
</div>
<p>The winning times for the Women's Eights Head of the River Race (1993 to 2017)</p>
</div>
<div id="chart"></div>
<div id="footer">
<span>
<strong>Sources</strong>:
<i>http://wehorr.org/results/ http://nrfa.ceh.ac.uk/</i>
</span>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/regression.js"></script>
<script src="https://unpkg.com/[email protected]/dist/tippy.all.min.js"></script>
<script>
d3.csv("data.csv", function (d) {
d.year = +d.year;
d.timeStr = d.time;
var time = d3.timeParse("%M:%S.%L")(d.time);
d.time = 60 * time.getMinutes() + time.getSeconds();
d.flow = +d.flow;
return d;
}, function (error, data) {
if (error) throw error;
var barChart = chart();
var change = function (isResize) {
var selection = d3.select("#chart");
selection.datum(data).call(barChart, isResize);
}
window.onresize = function () {
change(true);
}
change(false);
tippy('.point');
tippy('.bar', { placement: 'bottom' });
});
var chart = function () {
var svg = null;
var width;
var height;
var chartWidth;
var chartHeight;
var x;
var xBar;
var yLeft;
var yRight;
function chart(selection, isResize) {
selection.each(function (data) {
var result = regression.linear(data.map(function (d) { return [d.year, d.time]; }));
var gradient = result.equation[0];
var yIntercept = result.equation[1];
var duration = isResize ? 0 : 1000;
width = selection.node().getBoundingClientRect().width;
height = selection.node().getBoundingClientRect().height;
var ratio = (width - 500) / (800 - 500);
var clampedRatio = Math.max(0, Math.min(1, ratio));
var marginBottom = 48 + 48 * clampedRatio;
var marginLeft = 64 + 24 * clampedRatio;
var marginRight = 64 + 24 * clampedRatio;
var margin = {
top: 22,
right: marginRight,
bottom: marginBottom,
left: marginLeft
};
chartWidth = width - margin.left - margin.right;
chartHeight = height - margin.top - margin.bottom;
if (!svg) {
svg = selection.append("svg")
.style("vertical-align", "bottom"); // <-- Makes all the difference
var g = svg.append("g");
g.append("g")
.attr("class", "axis axis--x");
g.append("g")
.attr("class", "axis axis--yLeft");
g.append("g")
.attr("class", "axis axis--yRight");
svg.append("text")
.attr("class", "label left")
.attr("transform", "rotate(-90)")
.attr("dy", "1.1em")
.attr("text-anchor", "middle")
.text("Winning time (minutes)");
svg.append("text")
.attr("class", "label right")
.attr("transform", "rotate(-90)")
.attr("dy", "1.1em")
.attr("text-anchor", "middle")
.text("Mean daily flow (m³/s)");
}
svg.attr("height", "100%")
.attr("width", width);
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("g")
.attr("class", "bars");
g.append("g")
.attr("class", "points");
svg.select(".title")
.attr("x", width - margin.right);
svg.select(".label.left")
.attr("x", -chartHeight / 2)
.attr("y", 8);
svg.select(".label.right")
.attr("x", -chartHeight / 2)
.attr("y", chartWidth + marginLeft + 48);
x = d3.scaleLinear().range([0, chartWidth]).nice();
xBar = d3.scaleBand().padding(0.5).range([chartWidth, 0.0]);
yLeft
= d3.scaleLinear().rangeRound([chartHeight, 0]);
yRight
= d3.scaleLinear().rangeRound([0, chartHeight]);
x.domain(d3.extent(data.map(function (d) { return d.year; }))).nice();
xBar.domain(data.map(function (d) { return d.year; }));
yLeft
.domain([d3.min(data, function (d) { return d.time - 10; }), d3.max(data, function (d) { return d.time; })]).nice();
yRight
.domain([0, d3.max(data, function (d) { return d.flow; })]).nice();
var t = d3.transition()
.duration(duration);
var xAxis = svg.select(".axis--x")
.attr("transform", "translate(0," + chartHeight + ")");
xAxis.call(d3.axisBottom(x).tickSize(-chartHeight).tickFormat(d3.format("d")))
.selectAll("text")
.attr("y", 0);
xAxis.selectAll("text")
.attr("y", 0)
.attr("x", -20)
.attr("dy", ".35em")
.attr("transform", "rotate(-90)")
.style("text-anchor", "end");
svg.select(".axis--yLeft")
.call(d3.axisLeft(yLeft
).tickValues([18 * 60, 19 * 60, 20 * 60]).tickSize(-chartWidth).tickFormat(function (d) {
return d3.format("2d")(Math.floor(d / 60)) + ":" + d3.format("02d")(d % 60);
}));
svg.select(".axis--yRight")
.attr("transform", "translate(" + chartWidth + ",0)")
.call(d3.axisRight(yRight).tickSize(0));
var bars = svg.select(".bars")
.selectAll(".bar")
.data(data, function (d) { return d.year; })
.attr("x", function (d) { return x(d.year) - xBar.bandwidth() / 2; })
.attr("y", function (d) { return yRight(0); })
.attr("width", xBar.bandwidth())
.attr("height", function (d) { return yRight(d.flow); });
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function (d) { return x(d.year) - xBar.bandwidth() / 2; })
.attr("y", function (d) { return yRight(0); })
.attr("width", xBar.bandwidth())
.attr("height", function (d) { return 0; })
.attr("opacity", 0)
.attr("fill", "#E6842A")
.style("cursor", "pointer")
.on("mouseover", function (d) {
d3.select(this).attr("fill", "#F6B656");
})
.on("mouseout", function (d) {
d3.select(this).attr("fill", "#E6842A");
})
.attr("title", function (d) { return `<strong>${d.year}</strong><br />Time: ${d.timeStr}<br />Flow: ${d.flow} m<sup>3</sup>/s<br />${d.crew}`; })
.merge(bars)
.transition(t)
.attr("opacity", 1)
.transition(t)
.attr("height", function (d) { return yRight(d.flow); });
bars.exit()
.transition(t)
.attr("opacity", 0)
.remove();
var points = svg.select(".points")
.selectAll(".point")
.data(data, function (d) { return d.year; })
.attr("r", chartWidth / 100)
.attr("cx", function (d) { return x(d.year); })
.attr("cy", function (d) {
return yLeft
(d.time);
});
points.enter()
.append("circle")
.attr("class", "point")
.attr("opacity", 0)
.attr("cx", function (d) { return x(d.year); })
.attr("cy", function (d) {
return yLeft
(gradient * d.year + yIntercept);
})
.attr("r", chartWidth / 100)
.attr("fill", function (d) { return "#137B80"; })
.attr("title", function (d) { return `<strong>${d.year}</strong><br />Time: ${d.timeStr}<br />Flow: ${d.flow} m<sup>3</sup>/s<br />${d.crew}`; })
.style("cursor", "pointer")
.on("mouseover", function (d) {
d3.select(this).attr("fill", "#42A5B3");
})
.on("mouseout", function (d) {
d3.select(this).attr("fill", "#137B80");
})
.merge(points)
.transition(t)
.attr("opacity", 1)
.transition(t)
.attr("cx", function (d) { return x(d.year); })
.attr("cy", function (d) {
return yLeft
(d.time);
});
points.exit()
.transition(t)
.attr("cy", function (d) {
return yLeft
(0);
})
.attr("opacity", 0)
.remove();
var line = svg.select("g")
.selectAll(".line")
.data([{
x1: x.invert(x.range()[0]),
y1: gradient * x.invert(x.range()[0]) + yIntercept,
x2: x.invert(x.range()[1]),
y2: gradient * x.invert(x.range()[1]) + yIntercept,
}]);
line
.attr("x1", function (d) { return x(d.x1); })
.attr("y1", function (d) {
return yLeft
(d.y1);
})
.attr("x2", function (d) { return x(d.x2); })
.attr("y2", function (d) {
return yLeft
(d.y2);
});
line.enter()
.append("line")
.attr("class", "line")
.attr("x1", function (d) { return x(d.x1); })
.attr("y1", function (d) {
return yLeft
(d.y1);
})
.attr("x2", function (d) { return x(d.x2); })
.attr("y2", function (d) {
return yLeft
(d.y2);
})
.attr("stroke", "#8E8883")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "20, 20")
.attr("stroke-linecap", "round")
.attr("opacity", 0)
.transition(t)
.delay(2 * duration)
.attr("opacity", 1);
});
}
return chart;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment