L = {
// Leaflet 1.9.4 is bundled with the site (assets/vendor/leaflet/) rather than
// loaded from a CDN, so the page keeps working whatever happens upstream.
const L = await require(await FileAttachment("../../assets/vendor/leaflet/leaflet.js").url());
if (!document.getElementById("leaflet-css")) {
const link = document.createElement("link");
link.id = "leaflet-css";
link.rel = "stylesheet";
link.href = await FileAttachment("../../assets/vendor/leaflet/leaflet.css").url();
document.head.appendChild(link);
}
return L;
}
// Data: canton facts (data/swiss_data/cantons.csv) and boundaries
cantons = FileAttachment("../../data/swiss_data/cantons.csv").csv({typed: true})
geo = FileAttachment("../../data/geojson/switzerland.geojson").json()
statsMap = new Map(cantons.map(d => [d.geo_name, d]))
flagsDir = "../../assets/swiss_flags/"
mutable selected = null
// The map
mapContainer = {
const container = document.createElement("div");
container.style.height = "520px";
container.style.width = "100%";
container.style.borderRadius = "8px";
container.style.border = "1px solid #dee2e6";
container.style.overflow = "hidden";
// Yield the container first so Leaflet can measure it
yield container;
const home = {center: [46.8, 8.2], zoom: 7.8};
const map = L.map(container, {zoomSnap: 0, zoomDelta: 1, wheelPxPerZoomLevel: 5})
.setView(home.center, home.zoom);
// Swiss tiles from OpenStreetMap Switzerland, bounded to the country
L.tileLayer("https://tile.osm.ch/switzerland/{z}/{x}/{y}.png", {
maxZoom: 18,
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
bounds: [[45, 5], [48, 11]]
}).addTo(map);
const baseStyle = {color: "#2c3e50", weight: 1, fillColor: "#2c3e50", fillOpacity: 0.12};
const hoverStyle = {weight: 1.5, fillOpacity: 0.28};
const selectedStyle = {weight: 2.5, color: "#e67e22", fillColor: "#ffffff", fillOpacity: 0};
const geoLayer = L.geoJSON(geo, {
style: () => baseStyle,
onEachFeature: (feature, layer) => {
const info = statsMap.get(feature.properties.name);
layer.bindTooltip(info ? info.name : feature.properties.name, {sticky: true, direction: "top", offset: [0, -6]});
layer.on("mouseover", e => { if (mutable selected !== feature) e.target.setStyle(hoverStyle); });
layer.on("mouseout", e => { if (mutable selected !== feature) geoLayer.resetStyle(e.target); });
layer.on("click", e => {
mutable selected = feature;
geoLayer.resetStyle();
e.target.setStyle(selectedStyle);
e.target.bringToFront();
map.flyToBounds(e.target.getBounds(), {padding: [20, 20], duration: 1.0});
L.DomEvent.stopPropagation(e);
});
}
}).addTo(map);
// Back to the whole country (used by the button and by clicks outside the cantons)
container.resetMap = () => {
mutable selected = null;
geoLayer.resetStyle();
map.flyTo(home.center, home.zoom, {duration: 1.0});
};
map.on("click", container.resetMap);
}Switzerland is a federation of 26 cantons, each with its own constitution, parliament and schools, and six of them are “half-cantons” with a single seat in the Council of States. Click a canton on the map to zoom in and discover its coat of arms, its capital, a few key figures and a short portrait. Click the lake or outside the borders, or use the button, to return to the whole country.
Data & method
- Data. Cantonal facts compiled in
data/swiss_data/cantons.csv: resident population from the Federal Statistical Office (provisional figures at 31 December 2025), area, official languages and year of joining the Confederation. Coats of arms from Wikimedia Commons (public domain). Cantonal boundaries fromdata/geojson/switzerland.geojson. - Processing. Clicking a canton fits the map to its bounding box and fills the card from the table; the density is computed from population and area.
- Tools. Leaflet 1.9.4 for the map, with the Swiss OpenStreetMap tiles (© OpenStreetMap contributors, ODbL), and Observable JavaScript for the interaction.