Public
Edited
Aug 8, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
addTooltips(Plot.plot({
// width: 975,
// height: 610,
color: {
scheme: "reds",
label: "Unmet Demand",
legend: true,
n: 10,
domain: [0,10],
},
marks: [
Plot.geo(regions, {
fill: d => computeUnmet(d.properties.ISO_1),
// tip: true,
// channels: {
// name: d => d.properties.NAME_1,
// iso: d => d.properties.ISO_1
// }
title: (d) =>
`${d.properties.NAME_1} / ${iso2name_uk.get(d.properties.ISO_1)}\nAllocated: ${getAllocation(d.properties.ISO_1)}\nTotal Demand: ${getDemand(d.properties.ISO_1)}\n=> Unmet Demand: ${computeUnmet(d.properties.ISO_1)}`
}),
Plot.geo(regions, {stroke: "white"}),
Plot.text(regions, Plot.centroid({
text: (d) => `${iso2name_uk.get(d.properties.ISO_1)}\n${getAllocation(d.properties.ISO_1)} / ${getDemand(d.properties.ISO_1)}`,
//text: (d) => `\u0427 ${d.properties.ISO_1} ${iso2name_uk.get(d.properties.ISO_1)}`,
fill: "white",
stroke: "black",
fontSize: 16,
})),
]
})
)
Insert cell
Insert cell
Insert cell
//ua_topo = FileAttachment("ukraine2.json").json()
ua_topo = {
var ua_topo_json = await FileAttachment("Ukraine-regions@1.json").json();
// Keep just the inclStates
ua_topo_json.objects.UKR_adm1.geometries = ua_topo_json.objects.UKR_adm1.geometries.filter(d => inclCodes.indexOf(d.properties.ISO_1) > -1);
return(ua_topo_json);
}
Insert cell
//ua_geojson = FileAttachment("UKR1_simplified.geojson").json()
regions = topojson.feature(ua_topo, ua_topo.objects.UKR_adm1)
Insert cell
//tjSimplify.filter(states, d => inclCodes.indexOf(d.properties.ISO_1) > -1)
Insert cell
statemap = new Map(regions.features.filter(d => inclCodes.indexOf(d.properties.ISO_1) > -1).map(d => [d.properties.NAME_1, d]))
Insert cell
Insert cell
regionData = d3.csv("https://docs.google.com/spreadsheets/d/e/2PACX-1vR792Vvir2BIafIxLp_9FlkcElgrmTRgTvA6O9qWbpcrrOZ1MGSh3EIAiuRphCJQhCBA50uSadv01Md/pub?gid=0&single=true&output=csv")
Insert cell
regionData
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
inclRegionData = regionData.filter(d => inclCodes.indexOf(d.iso) > -1)
Insert cell
regionDataMap = new Map(inclRegionData.map(d => [d.iso, parseInt(d.initial_demand)]))
Insert cell
Insert cell
iso2name_uk.get("UA-71")
Insert cell
Insert cell
Insert cell
Insert cell
inclCodes = {
var allCodes = regionData.map(d => d.iso);
return(allCodes.filter(d => reducedCodes.indexOf(d) > -1));
}
Insert cell
reducedCodes = [
"UA-05", // Vinnytsia
// "UA-30", // Kyiv city
"UA-32", // Kyiv oblast
"UA-74",
"UA-18",
"UA-71", // Cherkasy
"UA-53", // Poltava
]
Insert cell
function genLabel(name_uk) {
// Get URL for flag svg
var flagUrl = name2flag_uk.get(name_uk)
var flagHtml = html`<img src='${flagUrl}' height='20px'></img>`;
var nameHtml = html`<span class='region-name-label'>${name_uk}</span>`;
var combinedHtml = html`${flagHtml} ${nameHtml}`
return(combinedHtml);
}
Insert cell
function genSlider(cname) {
var sliderElt = Inputs.range([0, 10], {
value: globs.defaultAlloc, step: 1, label: genLabel(cname), width: globs.sliderWidth
});
var styleStr = `--label-width: ${globs.labelWidth}`;
Object.assign(sliderElt, {style: styleStr});
return(sliderElt);
}
Insert cell
function getDemand(isoCode) {
return(regionDataMap.get(isoCode));
}
Insert cell
name2flag_uk = new Map(inclRegionData.map(d => [d.name_uk, d.flag_svg]))
Insert cell
Insert cell
Insert cell
function getAllocation(isoCode) {
// First, rname to iso code
//var isoCode = name2iso[regionName];
// Next, iso code to alloc key
var allocKey = iso2alloc[isoCode];
// And now we can just get the alloc directly
return(alloc[allocKey]);
}
Insert cell
function computeUnmet(isoCode) {
var totalDemand = getDemand(isoCode);
var allocated = getAllocation(isoCode);
return(totalDemand - allocated);
}
Insert cell
function resetAlloc() {
set(viewof kyiv, globs.defaultAlloc);
set(viewof cherkasy, globs.defaultAlloc);
set(viewof chernihiv, globs.defaultAlloc);
set(viewof poltava, globs.defaultAlloc);
set(viewof vinnytsia, globs.defaultAlloc);
set(viewof zhytomyr, globs.defaultAlloc);
}
Insert cell
function set(input, value) {
input.value = value;
input.dispatchEvent(new Event("input"));
}
Insert cell
Insert cell
// Computes remaining *total* unmet demand
totalUnmet = inclCodes.map(d => computeUnmet(d)).reduce((a,b) => a + b)
Insert cell
// Computes remaining *total* budget available
totalAllocated = inclCodes.map(d => getAllocation(d)).reduce((a,b) => a + b)
Insert cell
totalRemaining = globs.totalBudget - totalAllocated;
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
color = d3.scaleQuantize([0, 10], d3.schemeReds[9])
Insert cell
format = d => Math.round(`${d}`)
Insert cell
globs = ({
totalBudget: 50,
defaultAlloc: 0,
gridCellWidth: '276px',
labelWidth: '145px',
sliderWidth: 80,
});
Insert cell
Insert cell
Insert cell
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
import {addTooltips} from "@mkfreeman/plot-tooltip"
Insert cell
feather = require("feather-icons") // https://www.npmjs.com/package/feather-icons
Insert cell
extIcon = md`${feather.icons['external-link'].toSvg({width: 16, height: 16})}`
Insert cell
Insert cell
<style>
.budget-bar {
color: white;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
height: 100%;
}
/* https://github.com/observablehq/inputs/blob/main/src/range.js */
input[type="number"] {
font-size: 8pt;
width: 30px !important;
-webkit-appearance: none;
margin: 0;
-moz-appearance: textfield;
pointer-events: none;
}
.region-name-label {
font-size: 120%;
line-height: 1.0;
height: 100%;
display: table-caption;
}
</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