Built with blockbuilder.org and textures.js
xxxxxxxxxx
<head>
<meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v0.3.min.js"></script>
<script src="textures.js"></script>
<style>
body { margin:50;top:50;right:50;bottom:50;left:50; font-family: monospace }
.reference-line { stroke: lightgrey }
.leader-line { stroke: grey }
circle { stroke: grey; stroke-width: 2px }
.team-name-bkgd { stroke: white; stroke-width: 5px; fill: white; font-size: 12px }
.arrow > text {
text-anchor: end;
fill: grey
}
.arrow > line {
stroke: grey
}
</style>
</head>
<body>
<h1>Record year for managerial changes in the Premiership</h1>
<h2>With Arsene Wenger resigning at the end of the 2017/18 season, a record 10 changes occurred. Will Conte fall next?</h2>
<p>The tolerance for underperforming Premiership teams is low. By comparing the teams whose manager resigned or was sacked with The Guardian's prediction for league placings with a team's final position in the league, we can see the managerial changes occured where those teams underperformed.</p>
<p>Or, maybe in Arsenal's case, where the tolerance for predicted mediocrity had run dry.</p>
<div id="legend"></div>
<div id="chart"></div>
<p>Sources: League predictions from <a href="https://amp.theguardian.com/football/2018/may/15/premier-league-2017-18-season-predictions-versus-reality">The Guardian</a>. Part of the <a href="https://www.makeovermonday.co.uk/data/">Makeover Monday project</a>.
<script>
const width = 500
const height = width
const margin = {"top": 10, "bottom": 20, "left": 100, "right": 50}
const green = "rgb(160,232,91)"
const purple = "rgb(162,31,161)"
const scaleDomain = [20, 1] // 20 is low, 1 is top
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var xScale = d3.scaleLinear()
.domain(scaleDomain)
.range([0, width])
var yScale = d3.scaleLinear()
.domain(scaleDomain)
.range([height, 0])
var yLeft = d3.axisLeft(yScale).ticks(20)
var yRight = d3.axisRight(yScale).ticks(20)
var radius = d3.scaleSqrt()
.range([5, 20])
var textureAbove = textures.lines()
.size(8)
.strokeWidth(3)
.orientation("6/8")
.stroke(green)
.background("white");
var textureBelow = textures.lines()
.size(8)
.strokeWidth(3)
.orientation("6/8")
.stroke(purple)
.background("white");
svg.call(textureAbove);
svg.call(textureBelow);
// var colour = d3.scaleSequential(d3.interpolateCool)
var g = svg.append("g")
.attr("transform", "translate(" + margin.left +"," + margin.top + ")")
let axisLeft = g.append("g")
.attr("class", "axis axis--y")
.call(yLeft);
let axisRight = g.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + width + ",0)")
.call(yRight);
d3.selectAll(".domain").remove()
axisRight.selectAll(".tick").selectAll("line").remove()
axisLeft.selectAll(".tick").selectAll("line")
.attr("x2", width)
.style("stroke", "lightgrey")
.style("stroke-dasharray", "2,2")
d3.selectAll(".tick").selectAll("text")
.style("fill", "grey")
g.append("line")
.attr("class", "reference-line")
.attr("x1", 0)
.attr("y1", height)
.attr("x2", width)
.attr("y2", 0)
d3.csv("epl.csv", convertTextToNumbers, function (error, data) {
data.sort(function(a, b){
return b.differenceAbs - a.differenceAbs
})
radius.domain(d3.extent(data, function(d) { return d.differenceAbs; }))
let team = g.selectAll(".team")
.data(data)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(" + xScale(d.predicted) +"," + yScale(d.actual) + ")"
})
team.append("line")
.attr("class", "leader-line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", function(d){ return yScale(d.predicted) - yScale(d.actual) })
team.append("circle")
.attr("r", function(d){ return radius(0) - 0.5 })
.attr("cy", function(d){ return yScale(d.predicted) - yScale(d.actual) })
.style("fill", "white")
team.append("circle")
.attr("r", function(d){ return radius(Math.abs(d.difference)) })
.style("fill", function(d){
return fillColour(d)
})
.style("stroke", function(d){
return strokeColour(d)
})
team.append("text")
.attr("class", "team-name-bkgd")
team.append("text")
.attr("class", "team-name")
team.selectAll("text")
.text(function(d){ return d.team })
.attr("text-anchor", function(d){
return d.above ? "end" : "start"
})
.attr("x", function(d){
let r = radius(d.differenceAbs)
return d.above ? -(r + 3) : r + 3
})
.attr("y", "0.35em")
appendLegend()
appendArrow()
})
function appendArrow() {
let arrowG = svg.append("g")
.attr("class", "arrow")
/*let arrowLength = height * 0.7
let startX = margin.left / 2
let startY = (height - arrowLength) / 2
let endY = startY + arrowLength
arrowG.append("line")
.attr("x1", startX)
.attr("y1", startY)
.attr("x2", startX)
.attr("y2", endY)
.style("stroke", "grey")
arrowG.append("line")
.attr("x1", startX)
.attr("y1", startY)
.attr("x2", startX - 5)
.attr("y2", startY + 5)
.style("stroke", "grey")
arrowG.append("line")
.attr("x1", startX)
.attr("y1", startY)
.attr("x2", startX + 5)
.attr("y2", startY + 5)
.style("stroke", "grey")*/
let arrowTextG = arrowG.append("g")
.attr("transform", "translate(" + (margin.left - 25) +",0)")
.attr("class", "arrow")
let tStart = 14
let tGap = 15
arrowTextG.append("text")
.text("League")
.attr("y", tStart)
arrowTextG.append("text")
.text("Position")
.attr("y", tStart + (1 * tGap))
arrowTextG.append("text")
.text("Predicted")
.attr("y", tStart + (2 * tGap))
arrowTextG.append("text")
.text("v")
.attr("y", tStart + (3 * tGap))
arrowTextG.append("text")
.text("Actual")
.attr("y", tStart + (4 * tGap))
}
function appendLegend() {
let legendHeight = 120
let legendCentre = width/2
var items = [
{ "difference": 1, mrgChange: false },
{ "difference": 1, mrgChange: true },
{ "difference": -1, mrgChange: true },
{ "difference": -1, mrgChange: false },
{ "difference": 0, mrgChange: false },
]
let legend = d3.select("#legend").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", legendHeight + margin.top + 0)
.append("g")
.attr("transform", "translate(" + margin.left +"," + margin.top + ")")
var legendItems = legend.selectAll(".legend-item")
.data(items)
.enter()
.append("g")
.attr("transform", function(d, i){
let x = 5
let y = (i * 25) + 10
return "translate(" + x + "," + y + ")"
})
legendItems.append("circle")
.attr("r", function(d){ return radius(Math.abs(d.difference)) })
.style("fill", function(d){
return d.difference == 0 ? "white" :fillColour(d)
})
.style("stroke", function(d){
return d.difference == 0 ? "grey" : strokeColour(d)
})
legendItems.append("text")
.text(function(d){
if (d.difference == 0 ) {
return "The Guardian's predicted position"
}
else {
let arrow = "->"
let m = d.mrgChange ? "Manager sacked/resigned" : "Kept manager"
let p = d.difference > 0 ? "Performed as expected or better" : "Under performed"
return p + " " + arrow + " " + m
}
})
.attr("x", 25)
.attr("y", "0.35em")
}
function convertTextToNumbers(d) {
d.predicted = +d.predicted;
d.actual = +d.actual;
d.difference = +d.difference;
d.differenceAbs = Math.abs(d.difference);
d.above = d.difference >= 0 ? true : false
d.mrgChange = d.mrgChange == "true" ? true : false
return d
}
function fillColour(d) {
return d.mrgChange ? (d.difference >= 0 ? textureAbove.url() : textureBelow.url()) : "white"
}
function strokeColour(d) {
return d.difference >= 0 ? green : purple
}
</script>
</body>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-scale-chromatic.v0.3.min.js