Skip to content

Instantly share code, notes, and snippets.

@lorenzopub
Created July 30, 2017 16:27
Show Gist options
  • Save lorenzopub/494ef5270075e1e8fc314dda86c7fb9f to your computer and use it in GitHub Desktop.
Save lorenzopub/494ef5270075e1e8fc314dda86c7fb9f to your computer and use it in GitHub Desktop.
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment