function Choropleth(
data,
{
id = (d) => d.id,
value = () => undefined,
title,
format,
scale = d3.scaleSequential,
domain,
range = d3.interpolateBlues,
width = 640,
height,
projection,
features,
featureId = (d) => d.id,
borders,
outline = projection && projection.rotate ? { type: "Sphere" } : null,
unknown = "#ccc",
fill = "white",
stroke = "white",
strokeLinecap = "round",
strokeLinejoin = "round",
strokeWidth,
strokeOpacity,
patternMap
} = {}
) {
const N = d3.map(data, id);
const V = d3.map(data, value).map((d) => (d == null ? NaN : +d));
const Im = new d3.InternMap(N.map((id, i) => [id, i]));
const If = d3.map(features.features, featureId);
// Compute default domains.
if (domain === undefined) domain = d3.extent(V);
// Construct scales.
const color = scale(domain, range);
if (unknown !== undefined) color.unknown(unknown);
// Compute titles.
if (title === undefined) {
format = color.tickFormat(100, format);
title = (f, i) => `${f.properties.name}\n${format(V[i])}`;
} else if (title !== null) {
const T = title;
const O = d3.map(data, (d) => d);
title = (f, i) => T(f, O[i]);
}
// Compute the default height. If an outline object is specified, scale the projection to fit
// the width, and then compute the corresponding height.
if (height === undefined) {
if (outline === undefined) {
height = 400;
} else {
const [[x0, y0], [x1, y1]] = d3
.geoPath(projection.fitWidth(width, outline))
.bounds(outline);
const dy = Math.ceil(y1 - y0),
l = Math.min(Math.ceil(x1 - x0), dy);
projection.scale((projection.scale() * (l - 1)) / l).precision(0.2);
height = dy;
}
}
// Construct a path generator.
const path = d3.geoPath(projection);
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
if (outline != null)
svg
.append("path")
.attr("fill", fill)
.attr("stroke", "currentColor")
.attr("d", path(outline));
if (patternMap !== undefined) {
const pattern = svg
.append("defs")
.selectAll("pattern")
.data(patternMap.values())
.join("pattern")
.attrs((d, i) => d[1].patternArgs);
const line1 = pattern.append("line").attrs((d, i) => d[1].line1Args);
const line2 = pattern.append("line").attrs((d, i) => d[1].line2Args);
}
svg
.append("g")
.selectAll("path")
.data(features.features)
.join("path")
.attr("fill", (d, i) => `url(#pattern-${If[i]})`)
.attr("d", path)
.append("title")
.text((d, i) => title(d, Im.get(If[i])));
if (borders != null)
svg
.append("path")
.attr("pointer-events", "none")
.attr("fill", "none")
.attr("stroke", stroke)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth)
.attr("stroke-opacity", strokeOpacity)
.attr("d", path(borders));
return Object.assign(svg.node(), { scales: { color } });
}