A lollipop chart showing the pay gap between the lowest, median, and highest paid employees at universities. Hover over circles to reveal the data
forked from tlfrd's block: Lollipop Chart
license: mit |
A lollipop chart showing the pay gap between the lowest, median, and highest paid employees at universities. Hover over circles to reveal the data
forked from tlfrd's block: Lollipop Chart
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { | |
font-family: sans-serif; | |
} | |
text { | |
font-size: 12px; | |
} | |
text.x-title { | |
font-weight: bold; | |
} | |
.grid-line { | |
stroke: black; | |
opacity: 0.2; | |
} | |
.lollipop-line { | |
stroke: black; | |
stroke-width: 1px; | |
} | |
circle { | |
stroke-width: 1px; | |
stroke: black; | |
} | |
circle.lollipop-start { | |
fill: #00c100; | |
} | |
.lollipop-end { | |
fill: #d700d7; | |
} | |
.lollipop-median { | |
fill: white; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
var margin = {top: 100, right: 100, bottom: 50, left: 150}; | |
var width = 960 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom; | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var paygaps = [ | |
{name: "Brunel", max: 295000, min: 8676, median: 36875}, | |
{name: "Goldsmiths", max: 242640, min: 17154, median: 32125}, | |
{name: "Royal Veterinary", max: 263000, min: 17533, median: 29222}, | |
{name: "London Met", max: 254521, min: 17798, median: 45613}, | |
{name: "East London", max: 250000, min: 6188, median: 42373} | |
]; | |
// need to rewrite so start, min, lowest are the same | |
var classToPos = { | |
"lollipop-start": "min", | |
"lollipop-median": "median", | |
"lollipop-end": "max" | |
} | |
var legendLabels = [ | |
{label: "Lowest", class: "lollipop-start"}, | |
{label: "Median", class: "lollipop-median"}, | |
{label: "Highest", class: "lollipop-end"} | |
]; | |
var padding = 0; | |
var y = d3.scaleBand() | |
.domain(paygaps.map(function(d) { return d.name })) | |
.range([0, height]) | |
.padding(padding); | |
var x = d3.scaleLinear() | |
.domain([0, d3.max(paygaps, function(d) { return d.max })]) | |
.range([0, width]) | |
.nice(); | |
// code for positioning legend | |
var legend = svg.append("g"); | |
var legendX = width / 2 - 120; | |
var legendY = -50; | |
var spaceBetween = 70; | |
var legendPosition = { | |
x: legendX + 70, | |
y: legendY - 4 | |
}; | |
// add circles | |
legend.selectAll("circle") | |
.data(legendLabels) | |
.enter().append("circle") | |
.attr("cx", function(d, i) { | |
return legendPosition.x + spaceBetween * i; | |
}) | |
.attr("cy", legendPosition.y) | |
.attr("r", 5) | |
.attr("class", function(d) { return d.class }); | |
// add labels | |
legend.selectAll("text") | |
.data(legendLabels) | |
.enter().append("text") | |
.attr("x", function(d, i) { | |
return legendPosition.x + spaceBetween * i + 10; | |
}) | |
.attr("y", legendPosition.y + 4) | |
.text(function(d) { return d.label }); | |
var yAxis = d3.axisLeft().scale(y) | |
.tickSize(0); | |
var xAxis = d3.axisTop().scale(x) | |
.tickFormat(function(d,i) { | |
if (i == 0) { | |
return "£0" | |
} else { | |
return d3.format(".2s")(d); | |
} | |
}); | |
var yAxisGroup = svg.append("g") | |
.attr("transform", "translate(-20, 0)") | |
.call(yAxis) | |
.select(".domain").remove(); | |
var xAxisGroup = svg.append("g") | |
.call(xAxis); | |
xAxisGroup.append("text") | |
.attr("class", "x-title") | |
.attr("x", legendX) | |
.attr("y", legendY) | |
.text("Yearly Salary (£)") | |
.attr("fill", "black"); | |
var lineGenerator = d3.line(); | |
var axisLinePath = function(d) { | |
return lineGenerator([[x(d) + 0.5, 0], [x(d) + 0.5, height]]); | |
}; | |
var axisLines = xAxisGroup.selectAll("path") | |
.data(x.ticks(10)) | |
.enter().append("path") | |
.attr("class", "grid-line") | |
.attr("d", axisLinePath); | |
var lollipopLinePath = function(d) { | |
return lineGenerator([[x(d.min), y(d.name) + (y.bandwidth() / 2) ], [x(d.max), y(d.name) + (y.bandwidth() / 2)]]); | |
}; | |
var lollipopsGroup = svg.append("g").attr("class", "lollipops"); | |
var lollipops = lollipopsGroup.selectAll("g") | |
.data(paygaps) | |
.enter().append("g") | |
.attr("class", "lollipop") | |
lollipops.append("path") | |
.attr("class", "lollipop-line") | |
.attr("d", lollipopLinePath); | |
var startCircles = lollipops.append("circle") | |
.attr("class", "lollipop-start") | |
.attr("r", 5) | |
.attr("cx", function(d) { | |
return x(d.min); | |
}) | |
.attr("cy", function(d) { | |
return y(d.name) + y.bandwidth() / 2; | |
}) | |
.on("mouseover", showLabel) | |
.on("mouseout", hideLabel); | |
var medianCircles = lollipops.append("circle") | |
.attr("class", "lollipop-median") | |
.attr("r", 5) | |
.attr("cx", function(d) { | |
return x(d.median); | |
}) | |
.attr("cy", function(d) { | |
return y(d.name) + y.bandwidth() / 2; | |
}) | |
.on("mouseover", showLabel) | |
.on("mouseout", hideLabel); | |
var endCircles = lollipops.append("circle") | |
.attr("class", "lollipop-end") | |
.attr("r", 5) | |
.attr("cx", function(d) { | |
return x(d.max); | |
}) | |
.attr("cy", function(d) { | |
return y(d.name) + y.bandwidth() / 2; | |
}) | |
.on("mouseover", showLabel) | |
.on("mouseout", hideLabel); | |
function showLabel() { | |
var selection = d3.select(this); | |
var pos = classToPos[selection.attr("class")]; | |
d3.select(this.parentNode).append("text") | |
.attr("x", function(d) { return x(d[pos]); }) | |
.attr("y", function(d) { return y(d.name) + y.bandwidth() / 2; }) | |
.attr("dy", "-1em") | |
.attr("text-anchor", "middle") | |
.text(function(d) { | |
return d3.format(".2s")(d[pos]); | |
}); | |
} | |
function hideLabel(d) { | |
d3.select(this.parentNode).select("text").remove(); | |
} | |
</script> | |
</body> |