Published unlisted
Edited
Sep 18, 2022
Insert cell
# District Map
Insert cell
districts = FileAttachment("nv_districts.geojson").json()
Insert cell
noZoom = {
//set up container
const svgHeight = 0.25*width;
const top = htl.html`
<div class="wrapper">
<div class="info">
</div>
<svg viewBox="0 0 ${width} ${svgHeight}">
</svg>
</div>
`
//initialize projection and path functions
const projection = d3.geoAlbersUsa();
const path = d3.geoPath().projection(projection)
//set scale and center
projection.scale(1).translate([0,0]);
const b = path.bounds(districts);
const s = 0.95/Math.max((b[1][0]-b[0][0])/width, (b[1][1]-b[0][1])/svgHeight);
const t = [(width - s*(b[1][0]+b[0][0]))/2, (svgHeight - s*(b[1][1] + b[0][1]))/2];
projection.scale(s).translate(t);
//draw the districts
d3.select(top).select('svg').append('g').selectAll('path').data(districts.features)
.enter()
.append('path')
.attr('d', path)
.attr('id', d => d.properties.district)
.classed("district", true);
//initialize the info box
d3.select(top).select('div.info').text('Click or touch a district for info');
//dispatch for clicks on the map
function mapClick(event){
const coords = d3.pointer(event);
const longlat = projection.invert(coords);
d3.select(top).select('div.info').text(`Click or touch a district for info`);
districts.features.forEach(d => {
d3.select(top).select('#' + d.properties.district).classed('selected', false);
if (d3.geoContains(d,longlat)) {
d3.select(top).select('div.info').text(`${d.properties.district}`)
d3.select(top).select('#' + d.properties.district).classed('selected', true);
}
})
}
//add listener for clicks on the map
d3.select(top).select('svg')
.on('click', mapClick)
return top;
}
Insert cell
withZoom = {
//set up container
const svgHeight = 0.25*width;
const top = htl.html`
<div class="wrapper">
<div class="info">
</div>
<svg viewBox="0 0 ${width} ${svgHeight}">
</svg>
</div>
`
//initialize projection and path functions
const projection = d3.geoAlbersUsa();
const path = d3.geoPath().projection(projection)
//set scale and center
projection.scale(1).translate([0,0]);
const b = path.bounds(districts);
const s = 0.95/Math.max((b[1][0]-b[0][0])/width, (b[1][1]-b[0][1])/svgHeight);
const t = [(width - s*(b[1][0]+b[0][0]))/2, (svgHeight - s*(b[1][1] + b[0][1]))/2];
projection.scale(s).translate(t);
//draw the districts
d3.select(top).select('svg').append('g').selectAll('path').data(districts.features)
.enter()
.append('path')
.attr('d', path)
.attr('id', d => d.properties.district)
.classed("district", true);
//initialize the info box
d3.select(top).select('div.info').text('Click or touch a district for info');
//dispatch for clicks on the map
function mapClick(event){
const coords = d3.pointer(event);
const longlat = projection.invert(coords);
d3.select(top).select('div.info').text(`Click or touch a district for info`);
districts.features.forEach(d => {
d3.select(top).select('#' + d.properties.district).classed('selected', false);
if (d3.geoContains(d,longlat)) {
d3.select(top).select('div.info').text(`${d.properties.district}`)
d3.select(top).select('#' + d.properties.district).classed('selected', true);
}
})
}
//add listener for clicks on the map
d3.select(top).select('svg')
.on('click', mapClick)
//zoom
d3.select(top).select('svg').call(d3.zoom()
.extent([[0,0], [width, svgHeight]])
.scaleExtent([1,8])
.on("zoom", zoomed));
function zoomed({transform}) {
d3.select(top).select('svg').select('g').attr("transform", transform);
}
return top;
}
Insert cell
htl.html`
<style>
.info {
border: 1px solid black;
}
.district {
stroke: gray;
stroke-width: 1px;
fill: none;
}
.selected {
stroke: black;
stroke-width: 1px;
fill: blue;
}
.zoom-overlay {
fill: none;
pointer-events: all;
}
</style>
`
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