The winning times for the Women's Eights Head of the River Race (1993 to 2017).
xxxxxxxxxx
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WEHoRR winning times and river flow</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;
}
:focus {
outline: 0;
}
@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 and river flow</h1>
</div>
<p>The winning times and river flow 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>https://wehorr.org/results/ https://nrfa.ceh.ac.uk/</i>
</span>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/regression@2.0.1/dist/regression.js"></script>
<script src="https://unpkg.com/tippy.js@2.0.9/dist/tippy.all.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/kcnarf/d3-distancelimitedvoronoi/d3v4/distance-limited-voronoi.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;
d.gb = d.gb === "1" ? true : false;
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('.interactive-region');
});
var chart = function () {
var svg = null;
var width;
var height;
var chartWidth;
var chartHeight;
var x;
var yLeft;
function chart(selection, isResize) {
selection.each(function (data) {
var result = regression.linear(data.map(function (d) { return [d.flow, d.time]; }));
var gradient = result.equation[0];
var yIntercept = result.equation[1];
console.log(result.r2);
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 marginTop = 24 + 12 * clampedRatio;
var margin = {
top: marginTop,
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");
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 bottom")
.attr("dy", "1.1em")
.attr("text-anchor", "middle")
.text("Mean daily flow (m³/s)");
g.append("g")
.attr("class", "points");
g.append("g")
.attr("class", "voronoi");
}
svg.attr("height", "100%")
.attr("width", width);
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.select(".title")
.attr("x", width - margin.right);
svg.select(".label.left")
.attr("x", -marginTop - chartHeight / 2)
.attr("y", 8);
svg.select(".label.bottom")
.attr("x", marginLeft + chartWidth / 2)
.attr("y", chartHeight + marginTop + marginBottom / (1.5));
x = d3.scaleLinear().range([0, chartWidth]).nice();
yLeft
= d3.scaleLinear().rangeRound([chartHeight, 0]);
x.domain(d3.extent(data.map(function (d) { return d.flow; }))).nice();
yLeft
.domain([d3.min(data, function (d) { return d.time - 10; }), d3.max(data, function (d) { return d.time; })]).nice();
var limitedVoronoi = d3.distanceLimitedVoronoi()
.x(function (d) { return x(d.flow); })
.y(function (d) { return yLeft(d.time); })
.limit(chartWidth / 12)
var limitedCells = limitedVoronoi(data.filter(function (d) { return d.flow > 0; }));
var interactiveRegions = svg.select(".voronoi")
.selectAll(".interactive-region")
.data(limitedCells)
.attr("d", function (d) { return d.path; })
.attr("title", function (d) { return `<strong>${d.datum.year}</strong><br />Time: ${d.datum.timeStr}<br />Flow: ${d.datum.flow} m<sup>3</sup>/s<br />${d.datum.crew}`; });
interactiveRegions
.enter()
.append("path")
.attr("class", "interactive-region")
.style("fill", d3.rgb(230, 230, 230))
.style('fill-opacity', 0)
.style("cursor", "pointer")
.attr("d", function (d) { return d.path; })
.attr("title", function (d) { return `<strong>${d.datum.year}</strong><br />Time: ${d.datum.timeStr}<br />Flow: ${d.datum.flow} m<sup>3</sup>/s<br />${d.datum.crew}`; });
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", -5)
.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);
}));
var points = svg.select(".points")
.selectAll(".point")
.data(data.filter(function (d) { return d.flow > 0; }), function (d) { return d.year; })
.attr("r", chartWidth / 100)
.attr("cx", function (d) { return x(d.flow); })
.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.flow); })
.attr("cy", function (d) {
return yLeft
(gradient * d.flow + yIntercept);
})
.attr("r", chartWidth / 100)
.attr("fill", function (d) { console.log(d); return d.gb ? "red" : "#137B80"; })
.merge(points)
.transition(t)
.attr("opacity", 1)
.transition(t)
.attr("cx", function (d) { return x(d.flow); })
.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>
Updated missing url https://rawgit.com/Kcnarf/d3-distanceLimitedVoronoi/d3v4/distance-limited-voronoi.js to https://cdn.jsdelivr.net/gh/kcnarf/d3-distancelimitedvoronoi/d3v4/distance-limited-voronoi.js
https://d3js.org/d3.v4.min.js
https://unpkg.com/regression@2.0.1/dist/regression.js
https://unpkg.com/tippy.js@2.0.9/dist/tippy.all.min.js
https://rawgit.com/Kcnarf/d3-distanceLimitedVoronoi/d3v4/distance-limited-voronoi.js