Unlisted
Edited
Jan 18, 2023
1 star
Insert cell
Insert cell
viewof latlon = latLonInput()
Insert cell
latlon
Insert cell
Insert cell
function latLonInput({
geo = nepalGeo,
provincePath = "properties.Province", // Path to the province name relative to a feature
districtPath = "properties.DISTRICT", // Path to the district name relative to a feature

provinceInputLabel = "Province",
districtInputLabel = "District",

width = 600,
height = 400,
margin = 10,

stroke = "#999", // stroke color for borders
strokeWidth, // stroke width for borders
strokeOpacity, // stroke width for borders
strokeLinecap = "round", // stroke line cap for borders
strokeLinejoin = "round", // stroke line join for borders

fill = "#f4f4f4"
} = {}) {
let latlon;
geo = normalizeWinding(geo);
const projection = d3.geoTransverseMercator().rotate([-80, 0, 0]);
const svg = DOM.svg(width, height);

const id = `${prefix}-${generateRandomSeed()}`;
const districtOptionsId = `${id}-dist-options`;

const districts = d3.rollup(
geo.features,
(v) => v.map((d) => getValueByPath(d, districtPath)),
(d) => getValueByPath(d, provincePath)
);

const provinceInput = autoSelect({
options: Array.from(districts.keys()),
title: provinceInputLabel
});
let districtInput;
const districtInputHolder = htl.html`<div>`;
const form = htl.html`<form class=${blockClass} data-state="idle">
${provinceInput}
${districtInputHolder}
${svg}
</form>`;

function dispatchInputEvent() {
form.dispatchEvent(new Event("input", { bubbles: true }));
}

let currentProvince = "";
let currentDistrict = "";

function onDistrictChange() {
currentProvince = provinceInput.value;
currentDistrict = districtInput.value;
updateMap();
}
function populateDistrictOptions(options) {
districtInputHolder.innerHTML = null;
if (options == null) return;

districtInput = autoSelect({
options,
title: districtInputLabel,
list: districtOptionsId
});
districtInput.oninput = onDistrictChange;
districtInputHolder.appendChild(districtInput);
}

function updateMap() {
svg.innerHTML = null;

const filteredGeo = filterGeo(geo, currentProvince, currentDistrict, {
provincePath,
districtPath
});
projection.fitExtent(
[
[margin, margin],
[width - margin, height - margin]
],
filteredGeo
);
const path = d3.geoPath(projection);

const shapes = d3
.select(svg)
.selectAll(".boundary")
.data(filteredGeo.features)
.join("path")
.attr("class", "boundary")
.attr("stroke", stroke)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth)
.attr("stroke-opacity", strokeOpacity)
.attr("fill", fill)
.attr("d", path);

const marker = d3
.select(svg)
.append("circle")
.attr("r", 4)
.attr("fill", "transparent");

shapes.on("click", function (e) {
const m = d3.pointer(e);
latlon = projection.invert(m).reverse();

marker.call(function () {
marker.attr("cx", m[0]).attr("cy", m[1]).attr("fill", "red");
});
dispatchInputEvent();
});
}
function onProvinceChange() {
currentProvince = provinceInput.value;
currentDistrict = "";

populateDistrictOptions(districts.get(currentProvince));
updateMap();
}

form.onchange = preventDefault;
form.onsubmit = preventDefault;
provinceInput.oninput = onProvinceChange;

attachStyles(invalidation);
updateMap();

return Object.defineProperty(form, "value", {
get() {
return latlon;
}
});
}
Insert cell
function filterGeo(
geo,
province,
district,
{ districtPath, provincePath } = {}
) {
if (province == "" && district == "") {
return geo;
}

let geoCopy = { ...geo };

if (province !== "") {
const features = featureFilter(
geoCopy,
(f) => getValueByPath(f, provincePath) === province
);
geoCopy = { ...geoCopy, features };
}

if (district !== "") {
const features = featureFilter(
geoCopy,
(f) => getValueByPath(f, districtPath) === district
);

geoCopy = { ...geoCopy, features };
}

return geoCopy;
}
Insert cell
prefix = "map-input-nepal-"
Insert cell
msns = {
const ns = Inputs.text().classList[0];
return ns.replace("oi-", prefix);
}
Insert cell
blockClass = `${msns}-form`
Insert cell
attachStyles = (placeOfUseInvalidation) => {
const elId = `${msns}-style`;

if (document.getElementById(elId)) return;

const style = html`<style id=${elId}>
.${blockClass} > * + * {
margin-top: 1rem;
}

.${blockClass} svg {
display: block;
max-width: 100%;
height: auto;
}
</style>`;

document.head.append(style);

placeOfUseInvalidation.then(() => style.remove());
invalidation.then(() => style.remove());
}
Insert cell
function preventDefault(event) {
event.preventDefault();
}
Insert cell
function generateRandomSeed() {
return Math.floor(Math.random() * 1_000_000_000);
}
Insert cell
// To apply base styles when the notebook is downloaded/exported
substratum({invalidation})
Insert cell
import {substratum} from "@adb/substratum"
Insert cell
Insert cell
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