Using SVG patterns to create small multiple pie charts with image fills. I dynamically calculate the spacing and size of the pattern to allow for space between each pie chart (using a small d3 plugin I have written called d3-patterngrid).
Visualising how many women earn over 140k at London universities.
TODO:
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3-patterngrid.js"></script>
<style>
.coin {
stroke: black;
}
text {
font-family: monospace;
}
.pie-segment {
stroke: black;
fill: white
}
.name-label, .fraction-label {
font-size: 10px;
}
.name-label, .heading {
font-weight: bold;
}
.sub-heading {
font-size: 12px;
}
</style>
</head>
<body>
<script>
var width = 960,
height = 500;
var imageURL = "https://i.imgur.com/diLMHJQ.png";
var dataURL = "https://raw.githubusercontent.com/tlfrd/pay-ratios/master/data/over140k.json";
var imageMode = true;
var initialPosition = { x: 90, y: 95 };
var circleSize = { width: 75, height: 75 };
var spacing = {
h: 30,
v: 70
};
var labelPositionTop = -48,
labelPositionBottom = 55;
var gridLength = 8;
var path = d3.arc()
.outerRadius(circleSize.width / 2)
.innerRadius(0)
.startAngle(0);
var generateArc = function(fraction) {
return path({endAngle: Math.PI * 2 * fraction})
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var defs = svg.append("defs");
var coinPattern = patternGrid.circleLayout()
.config({
image: imageURL,
radius: circleSize.width,
padding: [spacing.h, spacing.v],
margin: [initialPosition.y, initialPosition.x],
id: "money"
});
// Load data
var angleGrid = [];
d3.json(dataURL, function(error, data) {
var over140 = data.number_over_140k;
// Filter out universities where number of women is not provided
over140 = over140.filter(function(a) {
return a.women !== "-";
})
over140.forEach(function(a) {
var angle = a.women / a.over || 0;
var angleObj = {
name: a.name,
angle: 1 - angle,
women: a.women,
total: a.over
};
angleGrid.push(angleObj);
});
// Sort the grid
angleGrid.sort(function(a, b) {
if (a.angle < b.angle) return -1;
if (a.angle > b.angle) return 1;
if (a.angle === b.angle) return 0;
});
// Assign positions
angleGrid.forEach(function(a, i) {
a.x = i % gridLength;
a.y = Math.floor(i / gridLength);
});
var circles = svg.append("g")
.selectAll("circle")
.data(angleGrid)
.enter().append("circle")
.attr("class", "coin")
.attr("cx", function(d) {
return initialPosition.x + (circleSize.width + spacing.h) * d.x;
})
.attr("cy", function(d) {
return initialPosition.y + (circleSize.height + spacing.v) * d.y;
})
.attr("r", circleSize.width / 2)
.attr("fill", "url(#money)");
var arcs = svg.append("g")
.selectAll("path")
.data(angleGrid.filter(d => d.angle))
.enter().append("path")
.attr("transform", function(d) {
var xPos = initialPosition.x + (circleSize.width + spacing.h) * d.x;
var yPos = initialPosition.y + (circleSize.height + spacing.v) * d.y;
return "translate(" + xPos + ", " + yPos + ")";
})
.attr("class", "pie-segment")
.attr("d", d => generateArc(d.angle));
var namelabels = svg.append("g")
.selectAll("text")
.data(angleGrid)
.enter().append("text")
.attr("class", "name-label")
.attr("x", function(d) {
return initialPosition.x + (circleSize.width + spacing.h) * d.x;
})
.attr("y", function(d) {
return initialPosition.y + (circleSize.height + spacing.v) * d.y;
})
.attr("dy", labelPositionTop)
.attr("text-anchor", "middle")
.text(d => d.name);
var fractionclabels = svg.append("g")
.selectAll("text")
.data(angleGrid)
.enter().append("text")
.attr("class", "fraction-label")
.attr("x", function(d) {
return initialPosition.x + (circleSize.width + spacing.h) * d.x;
})
.attr("y", function(d) {
return initialPosition.y + (circleSize.height + spacing.v) * d.y;
})
.attr("dy", labelPositionBottom)
.attr("text-anchor", "middle")
.text(d => d.women + " / " + d.total);
});
var legend = svg.append("g")
.attr("class", "legend")
.attr("text-anchor", "end")
.attr("transform", "translate(" + [width - 100, height - 60] + ")");
legend.append("text")
.attr("class", "heading")
.attr("dy", -20)
.text("London Universities");
legend.append("text")
.attr("class", "sub-heading")
.text("No. of Women / Total earning over 140k");
svg.on("click", changeStyle);
function changeStyle() {
if (imageMode) {
imageMode = false;
svg.selectAll(".coin")
.style("fill", "white");
svg.selectAll(".pie-segment")
.style("fill", "black");
} else {
imageMode = true;
svg.selectAll(".coin")
.style("fill", "url(#money)");
svg.selectAll(".pie-segment")
.style("fill", "white");
}
}
</script>
</body>
https://d3js.org/d3.v4.min.js