function mapcanvas(
projection,
c,
{
background = "#fefef2",
clip = false,
keepcanvas = false,
show_structure = false,
show_sphere = true,
show_equator = false,
show_land = true,
fill = () => "black",
...opts
} = {}
) {
const context = c.getContext("2d");
var path = d3.geoPath(projection, context);
if (clip) context.beginPath(), path(clip), context.clip();
function render() {
if (!keepcanvas) context.clearRect(0, 0, width, c.height);
if (show_sphere) {
context.beginPath();
path({ type: "Sphere" });
if (background) {
context.fillStyle = background;
context.fill();
}
context.beginPath(),
path(graticule),
(context.strokeStyle = "#ccc"),
context.stroke();
}
if (show_land) {
var l = land.features ? land.features : [land];
l.forEach((f, i) => {
context.beginPath(),
path(f),
(context.fillStyle = fill(f, i)),
context.fill();
});
}
if (show_equator)
context.beginPath(),
path(
d3
.geoGraticule()
.step([0, 100])
.extent([
[-179.99, -25],
[179.99, 25]
])()
),
(context.strokeStyle = "brown"),
context.stroke();
if (show_sphere || show_structure || !show_land)
context.beginPath(),
path({ type: "Sphere" }),
(context.strokeStyle = "#000"),
context.stroke();
// Polyhedral projections expose their structure as projection.tree()
// To draw them we need to cancel the rotate
if (show_structure && projection.tree) {
var rotate = projection.rotate();
projection.rotate([0, 0, 0]);
// run the tree of faces to get all sites and folds
var sites = [],
folds = [],
i = 0;
function recurse(face) {
var site = d3.geoCentroid({
type: "MultiPoint",
coordinates: face.face
});
site.id = face.id || i++;
sites.push(site);
if (face.children) {
face.children.forEach(function (child) {
folds.push({
type: "LineString",
coordinates: child.shared.map((e) =>
d3.geoInterpolate(e, face.centroid)(1e-5)
)
});
recurse(child);
});
}
}
recurse(projection.tree());
// sites & numbers
context.beginPath(),
path.pointRadius(10)({ type: "MultiPoint", coordinates: sites }),
(context.fillStyle = "white"),
(context.strokeStyle = "black"),
context.fill(),
context.stroke();
sites.forEach((site) => {
(context.textAlign = "center"),
(context.fillStyle = "black"),
(context.font = "16px Georgia"),
(context.textBaseline = "middle"),
context.fillText(
site.id,
projection(site)[0],
projection(site)[1] - 1
);
});
// folding lines
folds.forEach((fold) => {
context.beginPath(),
(context.lineWidth = 0.5),
context.setLineDash([3, 4]),
(context.strokeStyle = "#888"),
path(fold),
context.stroke(),
context.setLineDash([]);
});
// restore the projection’s rotate
projection.rotate(rotate);
}
if (false)
d3.select(context.canvas).on("mousemove", function (event) {
var gni = projection.invert(d3.pointer(event, this));
context.beginPath(),
path.pointRadius(2)({ type: "Point", coordinates: gni }),
(context.fillStyle = "green"),
(context.strokeStyle = "black"),
context.fill(),
context.stroke();
});
}
context.canvas.path = path;
(context.canvas.render = render)();
return context.canvas;
}