function drawStripedCircle({
svg,
selection,
features,
centroids,
width,
height,
margin,
svgHeight,
svgWidth,
stroke = "#fff",
strokeWidth = 1.25,
fontFamily = "var(--sans-serif)",
debug
} = {}) {
const canvas = selection
.selectAll(".small-multiple-canvas")
.data(features)
.join("g")
.attr("class", "small-multiple-canvas")
.attr(
"transform",
(d, i) => `translate(${centroids[i][0]},${centroids[i][1]})`
);
const D = features.map((f) => {
const name = getFeatureName(f);
const d = getDataByDistrict(data, name);
const harvestedPercentage = Math.min(
(100 * d.harvestedArea) / d.sownArea,
100
);
return { ...d, name, harvestedPercentage };
});
const PRD = D.map((d) => d.production);
const PRDExtent = d3.extent(PRD);
const ID = D.map(
(d) => `stripped-pattern-${d.name.toLowerCase().replaceAll(" ", "-")}`
);
const rScale = d3.scaleSqrt().domain(PRDExtent).range([5, 30]);
const computeR = (d, i) => (PRD[i] === 0 ? 0 : rScale(PRD[i]));
D.forEach((d, i) => {
const { harvestedPercentage } = d;
makePattern(
d3.select(svg),
ID[i],
computeR(null, i) * 2,
[100 - harvestedPercentage, harvestedPercentage],
colorScale.range()
);
});
canvas
.append("circle")
.attr("r", computeR)
.attr("fill", (d, i) => `url(#${ID[i]})`)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth);
canvas
.append("circle")
.attr("fill", "transparent")
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("r", computeR)
.append("title")
.text(
(d, i) => `${D[i].name}
Production: ${formatNumber(D[i].production)} metric tons
Area sown: ${formatNumber(D[i].sownArea)} acres
Area harvested: ${formatNumber(D[i].harvestedArea)} acres
Loss: ${formatPercentage(1 - D[i].harvestedPercentage / 100)}`
);
canvas
.append("text")
.attr("class", "district-label")
.attr("stroke-width", 0)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.attr("paint-order", "stroke")
.attr("fill", "#222")
.attr("fill-opacity", (d, i) => (computeR(d, i) === 0 ? 0 : 1))
.attr("stroke", "#fff")
.attr("stroke-width", 3)
.attr("stroke-opacity", (d, i) => (computeR(d, i) === 0 ? 0 : 0.75))
.attr("style", "font-size: 0.7rem")
.attr("font-weight", "500")
.attr("dy", (d, i) => computeR(d, i) * 1.15)
.style("font-family", fontFamily)
.text((d) => getFeatureName(d));
if (debug) {
selection
.selectAll(".debug-small-multiple-canvas")
.data(features)
.join("g")
.attr("class", "debug-small-multiple-canvas")
.append("rect")
.attr("x", (d, i) => centroids[i][0] - width / 2)
.attr("y", (d, i) => centroids[i][1] - height / 2)
.attr("width", width)
.attr("height", height)
.attr("stroke", "#0ff")
.attr("fill", "none");
}
// Draw legend
const ticks = rScale.ticks(2);
const r = rScale(d3.max(ticks));
const radiusLegend = circleLegend({
scale: rScale,
marginTop: 0,
marginBottom: 0,
stroke: "#666",
strokeWidth: 0.75,
tickFormat: (d, i, g) => {
const str = rScale.tickFormat(4, "s")(d);
return i === g.length - 1 ? `${str} metric tons produced` : str;
},
tickFont: `10px ${fontFamily}`,
tickStrokeWidth: 0.75,
tickStroke: "#666"
});
selection
.append("g")
.attr("class", "radius-legend")
.attr("transform", `translate(${margin},${svgHeight - r * 2 - margin})`)
.node()
.appendChild(radiusLegend);
const sqSize = 15;
const sqGap = 6;
const colorLegendWidth = 120;
const colorLegendFontSize = 12;
const colorLegend = selection
.append("g")
.attr("class", "color-legend")
.attr(
"transform",
`translate(${svgWidth - colorLegendWidth - margin},${margin})`
);
const swatches = colorLegend
.selectAll(".swatch")
.data(colorScale.domain())
.join("g")
.attr("class", "swatch");
swatches
.append("rect")
.attr("width", sqSize)
.attr("height", sqSize)
.attr("y", (d, i) => i * (sqGap + sqSize))
.attr("fill", colorScale);
swatches
.append("text")
.attr("x", sqGap + sqSize)
.attr("y", (d, i) => i * (sqGap + sqSize))
.attr("dy", sqSize * 0.66)
.attr("font-size", colorLegendFontSize)
.attr("font-family", fontFamily)
.attr("dominant-baseline", "middle")
.text((d) => d);
}