function smallMultiplesOverMap(
data,
{
features,
drawChart = () => {},
drawLegend = () => {},
projection = d3.geoTransverseMercator(),
drawOnlyPolygonFeatures = true,
width = 640,
height = 400,
chartSize = 60,
margin = 20,
featuresFill = main.grey["200"],
featuresStroke = main.grey["700"],
featuresStrokeWidth = 0.75,
backgroundFill = "white",
debug = false
} = {}
) {
const mapExtents = [
[margin, margin],
[width - margin, height - margin]
];
features = normalizeWinding(features);
// Remove point and lines
features = drawOnlyPolygonFeatures
? featureRemoveNonPolygonFeatures(features)
: features;
// overlay = overlay != null ? featurePolygonsOnly(overlay) : null;
projection = projection.scale === undefined ? projection() : projection;
// Adding margin to chart
// https://github.com/d3/d3-geo/blob/main/README.md#projection_fitSize
projection.fitExtent(mapExtents, features);
const C = features.features.map((f) => d3.geoCentroid(f)).map(projection);
const T = features.features.map((f) =>
getValueByPath(f, "properties.Province")
);
const path = d3.geoPath(projection);
const svg = DOM.svg(width, height);
d3.select(svg).style("background", backgroundFill);
// Draw features
const boundaries = d3
.select(svg)
.append("g")
.attr("class", "features")
.selectAll(".feature")
.data(features.features)
.join("path")
.attr("class", "feature")
.attr("d", path)
.attr("fill", featuresFill)
.attr("stroke", featuresStroke)
.attr("stroke-width", featuresStrokeWidth)
.attr("stroke-linejoin", "round");
boundaries.append("title").text((d, i) => T[i]);
// Draw charts
const charts = d3.select(svg).append("g").attr("class", "charts");
charts
.selectAll(".chart")
.data(C)
.join("g")
.attr("class", "chart")
.each(function (d, i) {
const [cx, cy] = d;
const g = d3.select(this);
g.attr("transform", `translate(${d})`);
if (typeof drawChart === "function") {
drawChart(g, features.features[i], data, {
cx,
cy,
size: chartSize,
svg
});
}
});
if (typeof drawLegend === "function") {
const legend = d3.select(svg).append("g").attr("class", "legend");
drawLegend(legend, { margin, width, height });
}
if (debug) {
const debugEls = d3.select(svg).append("g").attr("class", "debug");
debugEls
.selectAll(".d-centroid")
.data(C)
.join("circle")
.attr("class", "d-centroid")
.each(function (d) {
const [cx, cy] = d;
d3.select(this)
.attr("r", 4)
.attr("cx", cx)
.attr("cy", cy)
.attr("fill", "#f0f");
});
debugEls
.selectAll(".d-incirclce")
.data(C)
.join("circle")
.attr("class", "d-incirclce")
.each(function (d) {
const [cx, cy] = d;
d3.select(this)
.attr("r", chartSize / Math.SQRT2) // r = a / sq.root(2), formula for circumcircle of a square
.attr("cx", cx)
.attr("cy", cy)
.attr("stroke", "#f0f")
.attr("fill", "none");
});
debugEls
.selectAll(".d-chart-bounds")
.data(C)
.join("rect")
.attr("class", "d-chart-bounds")
.each(function (d) {
const [cx, cy] = d;
d3.select(this)
.attr("width", chartSize)
.attr("height", chartSize)
.attr("x", cx - chartSize / 2)
.attr("y", cy - chartSize / 2)
.attr("stroke", "#f0f")
.attr("fill", "none");
});
}
return svg;
}