Published
Edited
Oct 9, 2022
1 fork
Importers
Also listed in…
mapping
Insert cell
Insert cell
map = {
const container = html`<div style="height:600px;">`;
yield container;
const map = L.map(container).setView([37.8, -96.9], 4);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "&copy; <a href=https://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
}).addTo(map);

// Add an SVG element to Leaflet’s overlay pane. Leaflet automatically repositions the overlay pane when the
// map pans. Note that the SVG element is initialized with no width or height; the dimensions must be set
// dynamically because they change on zoom.
const svg = d3.select(map.getPanes().overlayPane).append("svg").style("position", "relative"),
g = svg.append("g").attr("class", "leaflet-zoom-hide");

// Adapt Leaflet’s latLngToLayerPoint and LatLng methods to project a single point,
// passing the resulting coordinates to an output geometry stream (https://github.com/d3/d3-geo#streams)
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}

// Now that we can project points, we can create a d3.geoPath to convert GeoJSON to SVG...
const transform = d3.geoTransform({point: projectPoint}),
path = d3.geoPath().projection(transform);

var feature = g.selectAll("path")
.data(states.features)
.join("path")
.attr("d", path)
.style("fill", "#000")
.style("fill-opacity", .2)
.style("stroke", "#fff")
.style("stroke-width", "1.5px")
.style("pointer-events", "all")
.on("mouseover", function () {
const element = d3.select(this);
const datum = element.data()[0].properties.index;
element.style("fill", colorScale(datum)).style("fill-opacity", .7);
})
.on("mouseout", function () { d3.select(this).style("fill", "#000").style("fill-opacity", .2) });

// It can be difficult to compute a bounding box with arbitrary projections, but fortunately D3 provides a
// convenient mechanism for computing the projected bounding box of our features using our custom transform
// to convert the longitude and latitude to pixels:
var bounds = path.bounds(states),
topLeft = bounds[0], // [x, y]
bottomRight = bounds[1];
svg.attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");

g.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");

// With Leaflet’s viewreset event, the SVG can be repositioned and rerendered whenever the map zooms
map.on("zoom", reset);
reset();

// Reposition the SVG to cover the features.
function reset() {
const bounds = path.bounds(states),
topLeft = bounds[0],
bottomRight = bounds[1];

svg .attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");

g .attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");

feature.attr("d", path);
}
}
Insert cell
colorScale.domain()
Insert cell
colorScale.range()
Insert cell
colorScale = d3.scaleOrdinal(d3.schemeCategory10)
.domain([...new Set(states.features.map(d => d.properties.index))])
Insert cell
L = {
const L = await require("leaflet@1/dist/leaflet.js");
if (!L._style) {
const href = await require.resolve("leaflet@1/dist/leaflet.css");
document.head.appendChild(L._style = html`<link href=${href} rel=stylesheet>`);
}
return L;
}
Insert cell
states = {
const states = topojson.feature(us, us.objects.states);
states.features = states.features.map((d, i) => {d.properties = { ...d.properties, index: (i%4).toString() }; return d;})
return states
}
Insert cell
import {us, topojson} from "@pbogden/my-u-s-map-svg"
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