Public
Edited
Feb 26, 2023
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
map = {
const container = html`<div style="height: 600px">`;
yield container;
const center = [40.7128, -74.0060];
const zoom = 10;
const map = L.map(container, {
minZoom: zoom,
keyboard: false,
scrollWheelZoom: false,
}).setView(center, zoom);
L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.{ext}', {
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
subdomains: 'abcd',
ext: 'png'
}).addTo(map);
function boroughColorForFeature(feature) {
// Use + to convert string to integer, since boroughColorsByCode is keyed by integer.
return boroughColorsByCode.get(+feature.properties.boro_code);
}
L.geoJSON(boroughsGeoJSON, {
style: function(feature) {
const color = boroughColorForFeature(feature);
return { color };
}
}).addTo(map);
}
Insert cell
Insert cell
Insert cell
leafletMapWithD3PathsAndAnimation = {
const container = html`<div style="height: 600px">`;
const center = [40.7128, -74.0060];
const zoom = 10;
const map = L.map(container, {
minZoom: zoom,
keyboard: false,
scrollWheelZoom: false,
}).setView(center, zoom);
L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.{ext}', {
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
subdomains: 'abcd',
ext: 'png'
}).addTo(map);
// Add the borough D3 paths to the overlay pane, similar to what L.GeoJSON does.
// https://leafletjs.com/reference-1.7.1.html#map-pane
const overlayPane = d3.select(map.getPanes().overlayPane);
// Create an SVG for our borough D3 paths and attach it to the overlay pane.
L.svg({ pane: 'overlayPane' }).addTo(map);
// Apply the 'leaflet-zoom-hide' class so that the layers disappear during zoom, reappearing afterward.
// Otherwise, the unscaled layers will stick around during animation, which looks awkward.
const boroughsSvg = overlayPane.select('svg').classed('leaflet-zoom-hide', true);
// Create the projection and path generator. Whenever we map a longitude and latitude, we need to project it
// onto the map's co-ordinates, using the Leaflet API.
const projection = d3.geoTransform({
point: function(longitude, latitude) {
const point = map.latLngToLayerPoint(new L.LatLng(latitude, longitude));
this.stream.point(point.x, point.y);
}
});
const path = d3.geoPath().projection(projection);
function boroughColorForFeature(feature) {
// Use + to convert string to integer, since boroughColorsByCode is keyed by integer.
return boroughColorsByCode.get(+feature.properties.boro_code);
}

const strokeWidth = 3;
const animationStrokeWidth = 6;
// Draw the boroughs, using the path generator.
const boroughPaths =
boroughsSvg.selectAll('path').data(boroughsGeoJSON.features)
.enter()
.append('path')
.attr('id', d => `borough-path-${d.properties.boro_code}`)
.style('pointer-events', 'auto')
.style('cursor', 'pointer')
.attr('d', path)
.attr('fill', boroughColorForFeature)
.attr('fill-opacity', 0.2)
.attr('stroke', boroughColorForFeature)
.attr('stroke-width', strokeWidth);
// If the map is zoomed, rescale the borough paths.
map.on('zoomend', function() {
boroughPaths.attr('d', path);
});

// Patch the container with this function for demo purposes.
container.animateBorough = function(boroughCode) {
const boroughPath = d3.select(`#borough-path-${boroughCode}`);
boroughPath
.raise()
.transition()
.duration(animationTransitionDurationMs)
.ease(d3.easeBounce)
.attr('stroke-width', animationStrokeWidth)
.end()
.then(() => {
boroughPath.attr('stroke-width', strokeWidth);
})
.catch((err) => {
boroughPath.attr('stroke-width', strokeWidth);
});
};
yield container;
}
Insert cell
animate = {
let i = 0;
while (true) {
yield leafletMapWithD3PathsAndAnimation.animateBorough(boroughCodes[(i % 5)]);
yield Promises.delay(animationTransitionDurationMs);
i++;
}
};
Insert cell
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>`);
document.head.appendChild(html`<style>
.leaflet-container {
background-color: white;
}
</style>`);
}
return L;
}
Insert cell
d3 = require("d3@6")
Insert cell
boroughsGeoJSON = FileAttachment("Borough Boundaries.geojson").json()
Insert cell
boroughCodesAndNames = [
[1, "Manhattan"],
[2, "Bronx"],
[3, "Brooklyn"],
[4, "Queens"],
[5, "Staten Island"]
]
Insert cell
boroughCodes = boroughCodesAndNames.map(([boroughCode, _]) => boroughCode)
Insert cell
boroughNamesByCode = new Map(boroughCodesAndNames)
Insert cell
// https://colorbrewer2.org/#type=qualitative&scheme=Set2&n=5
boroughColors = ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854']
Insert cell
boroughColorsByCode = new Map(boroughColors.map((d, i) => [i + 1, d]))
Insert cell
animationTransitionDurationMs = 1000
Insert cell
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