Published unlisted
Edited
Sep 22, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function drawGraticules(
svgNode,
{
fontSize = 10,
fontFamily = defaultFontFamily,
tickFill = "black",
stroke = "#999",
strokeWidth = 0.5,
step = [1, 1],
clipId = DOM.uid("clip"),
debug = false
} = {}
) {
const {
projection,
marginTop,
marginRight,
marginLeft,
marginBottom,
height,
width
} = svgNode.props;
const extent = {
topLeft: projection.invert([marginLeft, marginTop]),
topRight: projection.invert([width - marginRight, marginTop]),
bottomRight: projection.invert([
width - marginRight,
height - marginBottom
]),
bottomLeft: projection.invert([marginLeft, height - marginRight])
};

const canvas = d3.select(svgNode);

const graticuleGenerator = d3.geoGraticule().step(step);
graticuleGenerator.extent([
[extent.topLeft[0], extent.topLeft[1]],
[extent.bottomRight[0], extent.bottomRight[1]]
]);

const graticules = graticuleGenerator();
const path = d3.geoPath(projection);

const [lonStep, latStep] = graticuleGenerator.step();
// console.log({ lonStep, latStep });

const defs = d3.select(svgNode).select("defs").node()
? d3.select(svgNode).select("defs")
: d3.select(svgNode).insert("defs", ":first-child");

defs
.append("clipPath")
.attr("id", clipId.id)
.append("rect")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - (marginLeft + marginRight))
.attr("height", height - (marginTop + marginBottom));

const g = d3
.select(svgNode)
.append("g")
.attr("class", "key-graticules")
.attr("font-family", fontFamily)
.attr("font-size", fontSize);

g.append("path")
.attr("class", "graticules")
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("fill", "none")
.attr("clip-path", clipId)
.attr("d", path(graticules));

g.append("rect")
.attr("class", "graticule-outline")
.attr("fill", "none")
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - (marginLeft + marginRight))
.attr("height", height - (marginTop + marginBottom));

if (debug) {
g.selectAll("circle.corner")
.data(Object.values(extent))
.join("circle")
.attr("class", "corner")
.attr("cx", (d) => projection(d)[0])
.attr("cy", (d) => projection(d)[1])
.attr("r", 4)
.attr("fill", "#f0f");
}
}
Insert cell
function drawMap(
geo,
{
width = 640,
height,
marginTop = 1,
marginLeft = 4,
marginBottom = 1,
marginRight = 1,
padding = 30,
projection = d3.geoIdentity().reflectY(true),

fill = "none",
stroke = "black",
strokeWidth = 0.75,
strokeLinejoin = "round",

backgroundFill = "#fff",
debug = false
} = {}
) {
// If height is not provided, compute from Geo and projection
if (height == null) {
const fauxProjection = d3.geoIdentity().reflectY(true);
const fauxPath = d3.geoPath(fauxProjection);
fauxProjection.fitWidth(
width - (marginLeft + marginRight + 2 * padding),
geo
);
height =
Math.ceil(fauxPath.bounds(geo)[1][1]) +
(marginTop + marginBottom + 2 * padding);
}

projection = projection.scale === undefined ? projection() : projection;
// https://github.com/d3/d3-geo/blob/main/README.md#projection_fitSize
projection.fitExtent(
[
[marginLeft + padding, marginTop + padding],
[width - (marginRight + padding), height - (marginBottom + padding)]
],
geo
);

const path = d3.geoPath(projection);

const svg = DOM.svg(width, height);

d3.select(svg)
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.style("background", backgroundFill);

const canvas = d3.select(svg).append("g").attr("class", "features");

if (debug) {
canvas
.append("rect")
.attr("fill", "none")
.attr("stroke", "#f0f")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - (marginLeft + marginRight))
.attr("height", height - (marginTop + marginBottom));
canvas
.append("rect")
.attr("fill", "none")
.attr("stroke", "#f0f")
.attr("x", marginLeft + padding)
.attr("y", marginTop + padding)
.attr("width", width - (marginLeft + marginRight + 2 * padding))
.attr("height", height - (marginTop + marginBottom + 2 * padding));
}

canvas
.append("path")
.datum(geo)
.attr("fill", "none")
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("stroke-linejoin", strokeLinejoin)
.attr("d", path);

return Object.assign(svg, {
props: {
projection,
width,
height,
marginTop,
marginLeft,
marginBottom,
marginRight,
padding,
geo
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { normalizeWinding } from "@saneef/utils-geo"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more