From 26fd857a9a8d3c4a59a7de9bd8573b9e00601051 Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 19:59:16 -0230 Subject: [PATCH 01/11] Replace population bars with population fill layer --- public/index.css | 31 +++++----- public/modules/io/load.js | 5 +- public/modules/ui/layers.js | 109 ++++++++++++++++++------------------ public/modules/ui/style.js | 12 ---- 4 files changed, 75 insertions(+), 82 deletions(-) diff --git a/public/index.css b/public/index.css index d2061efd9a..4d2e6b5bce 100644 --- a/public/index.css +++ b/public/index.css @@ -122,10 +122,9 @@ a { user-select: none; } -#population, -#cells, -#compass { - fill: none; +#cells, +#compass { + fill: none; } #landmass { @@ -175,12 +174,13 @@ a { } t, -#regions, -#cults, -#relig, -#biomes, -#provincesBody, -#terrs, +#regions, +#cults, +#relig, +#biomes, +#population, +#provincesBody, +#terrs, #tooltip, #temperature, #texture, @@ -213,11 +213,12 @@ t, stroke-linejoin: round; } -#statesBody, -#provincesBody, -#relig, -#biomes, -#cults { +#statesBody, +#provincesBody, +#relig, +#biomes, +#population, +#cults { stroke-linejoin: round; fill-rule: evenodd; } diff --git a/public/modules/io/load.js b/public/modules/io/load.js index 80c0697d8c..d7e9fa0179 100644 --- a/public/modules/io/load.js +++ b/public/modules/io/load.js @@ -464,7 +464,10 @@ async function parseLoadedData(data, mapVersion) { if (isVisible(borders) && hasChild(borders, "path")) turnOn("toggleBorders"); if (isVisible(routes) && hasChild(routes, "path")) turnOn("toggleRoutes"); if (hasChildren(temperature)) turnOn("toggleTemperature"); - if (hasChild(population, "line")) turnOn("togglePopulation"); + if (hasChild(population, "path, line")) { + turnOn("togglePopulation"); + drawPopulation(); + } if (isVisible(ice)) turnOn("toggleIce"); if (hasChild(prec, "circle")) turnOn("togglePrecipitation"); if (isVisible(emblems) && hasChild(emblems, "use")) turnOn("toggleEmblems"); diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index e4071ea1ef..d2e16642c0 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -359,76 +359,77 @@ function drawPrecipitation() { } function togglePopulation(event) { - if (!population.selectAll("line").size()) { + if (!population.selectAll("path, line").size()) { turnButtonOn("togglePopulation"); drawPopulation(); if (event && isCtrlClick(event)) editStyle("population"); } else { if (event && isCtrlClick(event)) return editStyle("population"); turnButtonOff("togglePopulation"); - - const isD3data = population.select("line").datum(); - if (!isD3data) { - // just remove - population.selectAll("line").remove(); - } else { - // remove with animation - const hide = d3.transition().duration(1000).ease(d3.easeSinIn); - population - .select("#rural") - .selectAll("line") - .transition(hide) - .attr("y2", d => d[1]) - .remove(); - population - .select("#urban") - .selectAll("line") - .transition(hide) - .delay(1000) - .attr("y2", d => d[1]) - .remove(); - } + population.selectAll("*").remove(); } } function drawPopulation() { - population.selectAll("line").remove(); + TIME && console.time("drawPopulation"); const { cells, burgs } = pack; - const show = d3.transition().duration(2000).ease(d3.easeSinIn); + const urban = new Float64Array(cells.i.length); + const total = new Float64Array(cells.i.length); + let maxPopulation = 0; + + for (const cellId of cells.i) { + if (cells.h[cellId] < 20) continue; + + const burg = burgs[cells.burg[cellId]]; + const ruralPopulation = cells.pop[cellId] * populationRate; + urban[cellId] = + burg && !burg.removed ? (burg.population || 0) * populationRate * urbanization : 0; + total[cellId] = ruralPopulation + urban[cellId]; + if (total[cellId] > maxPopulation) maxPopulation = total[cellId]; + } - const rural = Array.from( - cells.i.filter(i => cells.pop[i] > 0), - i => [...cells.p[i], cells.p[i][1] - cells.pop[i] / 5] + const colors = new Array(cells.i.length); + for (const cellId of cells.i) { + if (cells.h[cellId] < 20) continue; + colors[cellId] = getPopulationColor(urban[cellId], total[cellId], maxPopulation); + } + + const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true, waterGap: true }); + const paths = Object.entries(isolines).map(([color, { fill, waterGap }], index) => + getGappedFillPaths("population", fill, waterGap, color, index) ); - population - .select("#rural") - .selectAll("line") - .data(rural) - .enter() - .append("line") - .attr("x1", d => d[0]) - .attr("y1", d => d[1]) - .attr("x2", d => d[0]) - .attr("y2", d => d[1]) - .transition(show) - .attr("y2", d => d[2]); + ensureEl("population").innerHTML = paths.join(""); - const urban = burgs.filter(b => b.i && !b.removed).map(b => [b.x, b.y, b.y - (b.population / 5) * urbanization]); - population - .select("#urban") - .selectAll("line") - .data(urban) - .enter() - .append("line") - .attr("x1", d => d[0]) - .attr("y1", d => d[1]) - .attr("x2", d => d[0]) - .attr("y2", d => d[1]) - .transition(show) - .delay(500) - .attr("y2", d => d[2]); + TIME && console.timeEnd("drawPopulation"); +} + +function getPopulationColor(urbanPopulation, totalPopulation, maxPopulation) { + if (!totalPopulation || !maxPopulation) return "#000000"; + + const hue = urbanPopulation / totalPopulation; + const value = (Math.log1p(totalPopulation) / Math.log1p(maxPopulation)) ** 1.5; + return hsvToHex(hue, 1, value); +} + +function hsvToHex(hueDegrees, saturation, value) { + const hue = ((hueDegrees % 360) + 360) % 360; + const chroma = value * saturation; + const segment = hue / 60; + const intermediate = chroma * (1 - Math.abs((segment % 2) - 1)); + + let [red, green, blue] = [0, 0, 0]; + if (segment < 1) [red, green] = [chroma, intermediate]; + else if (segment < 2) [red, green] = [intermediate, chroma]; + else if (segment < 3) [green, blue] = [chroma, intermediate]; + else if (segment < 4) [green, blue] = [intermediate, chroma]; + else if (segment < 5) [red, blue] = [intermediate, chroma]; + else [red, blue] = [chroma, intermediate]; + + const match = value - chroma; + const toHex = channel => Math.round((channel + match) * 255).toString(16).padStart(2, "0"); + return `#${toHex(red)}${toHex(green)}${toHex(blue)}`; } function toggleCells(event) { diff --git a/public/modules/ui/style.js b/public/modules/ui/style.js index 274a26647b..35ce8f314c 100644 --- a/public/modules/ui/style.js +++ b/public/modules/ui/style.js @@ -227,18 +227,6 @@ function selectStyleElement() { styleReliefSet.value = terrain.attr("set"); } - if (styleElement === "population") { - stylePopulation.style.display = "block"; - stylePopulationRuralStrokeInput.value = stylePopulationRuralStrokeOutput.value = population - .select("#rural") - .attr("stroke"); - stylePopulationUrbanStrokeInput.value = stylePopulationUrbanStrokeOutput.value = population - .select("#urban") - .attr("stroke"); - styleStrokeWidth.style.display = "block"; - styleStrokeWidthInput.value = el.attr("stroke-width") || 0; - } - if (styleElement === "regions") { styleStates.style.display = "block"; styleStatesBodyOpacity.value = statesBody.attr("opacity") || 1; From fe6c1faa136d91d83c39b6ce5409d98951233655 Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 20:12:21 -0230 Subject: [PATCH 02/11] Fix population colors and water clipping --- public/index.css | 13 +++++++------ public/modules/ui/layers.js | 3 ++- public/modules/ui/style.js | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/public/index.css b/public/index.css index 4d2e6b5bce..fdb462f722 100644 --- a/public/index.css +++ b/public/index.css @@ -223,12 +223,13 @@ t, fill-rule: evenodd; } -#statesBody, -#provincesBody, -#relig, -#cults { - mask: url(#land); -} +#statesBody, +#provincesBody, +#relig, +#population, +#cults { + mask: url(#land); +} #borders { stroke-linejoin: round; diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index d2e16642c0..96f61d42e7 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -408,7 +408,8 @@ function drawPopulation() { function getPopulationColor(urbanPopulation, totalPopulation, maxPopulation) { if (!totalPopulation || !maxPopulation) return "#000000"; - const hue = urbanPopulation / totalPopulation; + const urbanShare = urbanPopulation / totalPopulation; + const hue = 240 + 120 * urbanShare; const value = (Math.log1p(totalPopulation) / Math.log1p(maxPopulation)) ** 1.5; return hsvToHex(hue, 1, value); } diff --git a/public/modules/ui/style.js b/public/modules/ui/style.js index 35ce8f314c..35a67ca721 100644 --- a/public/modules/ui/style.js +++ b/public/modules/ui/style.js @@ -165,7 +165,6 @@ function selectStyleElement() { "compass", "coordinates", "gridOverlay", - "population", "prec", "routes", "temperature", From 98fb71512d47f53f8e1ffe30fca8ef541ff8e803 Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 20:17:46 -0230 Subject: [PATCH 03/11] Force refresh of population HSV renderer --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index 7873ad831f..ae95a62320 100644 --- a/src/index.html +++ b/src/index.html @@ -8931,7 +8931,7 @@ - + From 0ee9b252bf25a77a743e7ac732f058d96004646c Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 20:25:10 -0230 Subject: [PATCH 04/11] Fix population paths bleeding into water --- public/modules/ui/layers.js | 18 +++++++++++++++--- src/index.html | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 96f61d42e7..9ea7d66ea3 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -373,6 +373,8 @@ function togglePopulation(event) { function drawPopulation() { TIME && console.time("drawPopulation"); + population.attr("mask", "url(#land)"); + const { cells, burgs } = pack; const urban = new Float64Array(cells.i.length); const total = new Float64Array(cells.i.length); @@ -395,9 +397,9 @@ function drawPopulation() { colors[cellId] = getPopulationColor(urban[cellId], total[cellId], maxPopulation); } - const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true, waterGap: true }); - const paths = Object.entries(isolines).map(([color, { fill, waterGap }], index) => - getGappedFillPaths("population", fill, waterGap, color, index) + const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true }); + const paths = Object.entries(isolines).map(([color, { fill }], index) => + getGappedFillPaths("population", closeSvgPathRings(fill), "", color, index) ); ensureEl("population").innerHTML = paths.join(""); @@ -405,6 +407,16 @@ function drawPopulation() { TIME && console.timeEnd("drawPopulation"); } +function closeSvgPathRings(pathData) { + if (!pathData) return ""; + + return pathData + .split(/(?=M)/) + .filter(Boolean) + .map(ring => (/[zZ]\s*$/.test(ring) ? ring : `${ring}Z`)) + .join(""); +} + function getPopulationColor(urbanPopulation, totalPopulation, maxPopulation) { if (!totalPopulation || !maxPopulation) return "#000000"; diff --git a/src/index.html b/src/index.html index ae95a62320..75282be0d0 100644 --- a/src/index.html +++ b/src/index.html @@ -8931,7 +8931,7 @@ - + From 8f3cb06b02c082d4d2f2a2c8701452cd9bca5db8 Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 20:32:27 -0230 Subject: [PATCH 05/11] Remove population layer strokes --- public/index.css | 15 +++++++++++---- public/modules/ui/layers.js | 10 +++++++++- public/modules/ui/style-presets.js | 2 +- public/modules/ui/style.js | 1 - src/index.html | 2 +- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/public/index.css b/public/index.css index fdb462f722..e3452b9940 100644 --- a/public/index.css +++ b/public/index.css @@ -219,10 +219,17 @@ t, #biomes, #population, #cults { - stroke-linejoin: round; - fill-rule: evenodd; -} - + stroke-linejoin: round; + fill-rule: evenodd; +} + +#population, +#population path { + stroke: none !important; + stroke-width: 0 !important; + stroke-dasharray: none !important; +} + #statesBody, #provincesBody, #relig, diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 9ea7d66ea3..1a9689d3a5 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -373,7 +373,15 @@ function togglePopulation(event) { function drawPopulation() { TIME && console.time("drawPopulation"); - population.attr("mask", "url(#land)"); + population + .attr("mask", "url(#land)") + .attr("stroke", "none") + .attr("stroke-width", 0) + .attr("stroke-dasharray", null) + .attr("stroke-linecap", null) + .style("stroke", "none") + .style("stroke-width", 0) + .style("stroke-dasharray", "none"); const { cells, burgs } = pack; const urban = new Float64Array(cells.i.length); diff --git a/public/modules/ui/style-presets.js b/public/modules/ui/style-presets.js index b129c76b0a..341b4e2175 100644 --- a/public/modules/ui/style-presets.js +++ b/public/modules/ui/style-presets.js @@ -221,7 +221,7 @@ function addStylePreset() { "#landmass": ["opacity", "fill", "filter"], "#markers": ["opacity", "rescale", "filter"], "#prec": ["opacity", "stroke", "stroke-width", "fill", "filter"], - "#population": ["opacity", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], + "#population": ["opacity", "filter", "mask"], "#markets": ["opacity", "stroke-width", "fill-opacity", "stroke-opacity", "filter"], "#goodsCells": ["opacity", "filter"], "#goodsIcons": ["opacity", "stroke-width", "data-circle", "filter"], diff --git a/public/modules/ui/style.js b/public/modules/ui/style.js index 35a67ca721..1937b72b37 100644 --- a/public/modules/ui/style.js +++ b/public/modules/ui/style.js @@ -146,7 +146,6 @@ function selectStyleElement() { "coordinates", "gridOverlay", "legend", - "population", "routes", "temperature", "zones" diff --git a/src/index.html b/src/index.html index 75282be0d0..bab3621f16 100644 --- a/src/index.html +++ b/src/index.html @@ -8931,7 +8931,7 @@ - + From 12b9baf62e7bdda87ee04614fed82b3301ef94d4 Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 20:43:10 -0230 Subject: [PATCH 06/11] Render population beneath projected coastline --- public/index.css | 1 - public/modules/ui/layers.js | 19 ++++++++++++++++++- public/modules/ui/style-presets.js | 2 +- src/index.html | 8 ++++---- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/public/index.css b/public/index.css index e3452b9940..a11b392236 100644 --- a/public/index.css +++ b/public/index.css @@ -233,7 +233,6 @@ t, #statesBody, #provincesBody, #relig, -#population, #cults { mask: url(#land); } diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 1a9689d3a5..390fe660a0 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -367,6 +367,7 @@ function togglePopulation(event) { if (event && isCtrlClick(event)) return editStyle("population"); turnButtonOff("togglePopulation"); population.selectAll("*").remove(); + document.getElementById("populationOceanCover")?.remove(); } } @@ -374,7 +375,7 @@ function drawPopulation() { TIME && console.time("drawPopulation"); population - .attr("mask", "url(#land)") + .attr("mask", null) .attr("stroke", "none") .attr("stroke-width", 0) .attr("stroke-dasharray", null) @@ -411,10 +412,25 @@ function drawPopulation() { ); ensureEl("population").innerHTML = paths.join(""); + drawPopulationOceanCover(); TIME && console.timeEnd("drawPopulation"); } +function drawPopulationOceanCover() { + document.getElementById("populationOceanCover")?.remove(); + + coastline.node().before(population.node()); + + const cover = document.createElementNS("http://www.w3.org/2000/svg", "use"); + cover.id = "populationOceanCover"; + cover.setAttribute("href", "#ocean"); + cover.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#ocean"); + cover.setAttribute("mask", "url(#water)"); + cover.setAttribute("pointer-events", "none"); + population.node().after(cover); +} + function closeSvgPathRings(pathData) { if (!pathData) return ""; @@ -1068,6 +1084,7 @@ function moveLayer(event, ui) { const next = getLayer(ui.item.next().attr("id")); if (prev) el.insertAfter(prev); else if (next) el.insertBefore(next); + if (population.selectAll("*").size()) drawPopulationOceanCover(); } // define connection between option layer buttons and actual svg groups to move the element diff --git a/public/modules/ui/style-presets.js b/public/modules/ui/style-presets.js index 341b4e2175..92dc524096 100644 --- a/public/modules/ui/style-presets.js +++ b/public/modules/ui/style-presets.js @@ -221,7 +221,7 @@ function addStylePreset() { "#landmass": ["opacity", "fill", "filter"], "#markers": ["opacity", "rescale", "filter"], "#prec": ["opacity", "stroke", "stroke-width", "fill", "filter"], - "#population": ["opacity", "filter", "mask"], + "#population": ["opacity", "filter"], "#markets": ["opacity", "stroke-width", "fill-opacity", "stroke-opacity", "filter"], "#goodsCells": ["opacity", "filter"], "#goodsIcons": ["opacity", "stroke-width", "data-circle", "filter"], diff --git a/src/index.html b/src/index.html index bab3621f16..6bf60f8402 100644 --- a/src/index.html +++ b/src/index.html @@ -133,7 +133,7 @@ - + - + - + From 085ffbc3ff8ff3c45386e324061ded9f1219a68a Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 20:51:50 -0230 Subject: [PATCH 07/11] Restore masked population cell strokes --- public/index.css | 3 +-- public/modules/ui/layers.js | 36 ++++++++++-------------------------- src/index.html | 4 ++-- 3 files changed, 13 insertions(+), 30 deletions(-) diff --git a/public/index.css b/public/index.css index a11b392236..85f9087f61 100644 --- a/public/index.css +++ b/public/index.css @@ -225,14 +225,13 @@ t, #population, #population path { - stroke: none !important; - stroke-width: 0 !important; stroke-dasharray: none !important; } #statesBody, #provincesBody, #relig, +#population, #cults { mask: url(#land); } diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 390fe660a0..ffde6d53b5 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -367,7 +367,6 @@ function togglePopulation(event) { if (event && isCtrlClick(event)) return editStyle("population"); turnButtonOff("togglePopulation"); population.selectAll("*").remove(); - document.getElementById("populationOceanCover")?.remove(); } } @@ -375,14 +374,14 @@ function drawPopulation() { TIME && console.time("drawPopulation"); population - .attr("mask", null) - .attr("stroke", "none") - .attr("stroke-width", 0) + .attr("mask", "url(#land)") + .attr("stroke", null) + .attr("stroke-width", null) .attr("stroke-dasharray", null) .attr("stroke-linecap", null) - .style("stroke", "none") - .style("stroke-width", 0) - .style("stroke-dasharray", "none"); + .style("stroke", null) + .style("stroke-width", null) + .style("stroke-dasharray", null); const { cells, burgs } = pack; const urban = new Float64Array(cells.i.length); @@ -407,30 +406,16 @@ function drawPopulation() { } const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true }); - const paths = Object.entries(isolines).map(([color, { fill }], index) => - getGappedFillPaths("population", closeSvgPathRings(fill), "", color, index) - ); + const paths = Object.entries(isolines).map(([color, { fill }], index) => { + const closedFill = closeSvgPathRings(fill); + return /* html */ ``; + }); ensureEl("population").innerHTML = paths.join(""); - drawPopulationOceanCover(); TIME && console.timeEnd("drawPopulation"); } -function drawPopulationOceanCover() { - document.getElementById("populationOceanCover")?.remove(); - - coastline.node().before(population.node()); - - const cover = document.createElementNS("http://www.w3.org/2000/svg", "use"); - cover.id = "populationOceanCover"; - cover.setAttribute("href", "#ocean"); - cover.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#ocean"); - cover.setAttribute("mask", "url(#water)"); - cover.setAttribute("pointer-events", "none"); - population.node().after(cover); -} - function closeSvgPathRings(pathData) { if (!pathData) return ""; @@ -1084,7 +1069,6 @@ function moveLayer(event, ui) { const next = getLayer(ui.item.next().attr("id")); if (prev) el.insertAfter(prev); else if (next) el.insertBefore(next); - if (population.selectAll("*").size()) drawPopulationOceanCover(); } // define connection between option layer buttons and actual svg groups to move the element diff --git a/src/index.html b/src/index.html index 6bf60f8402..ff65e1ba49 100644 --- a/src/index.html +++ b/src/index.html @@ -133,7 +133,7 @@ - + From dfb63daf4815849f208347e89345aa9478309022 Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 20:58:02 -0230 Subject: [PATCH 08/11] Lower population layer and widen masked stroke --- public/main.js | 2 +- public/modules/ui/layers.js | 4 +++- src/index.html | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/public/main.js b/public/main.js index ff5a68ab5d..7a6ca3816c 100644 --- a/public/main.js +++ b/public/main.js @@ -45,6 +45,7 @@ let texture = viewbox.append("g").attr("id", "texture"); let terrs = viewbox.append("g").attr("id", "terrs"); let lakes = viewbox.append("g").attr("id", "lakes"); let biomes = viewbox.append("g").attr("id", "biomes"); +let population = viewbox.append("g").attr("id", "population"); let cells = viewbox.append("g").attr("id", "cells"); let gridOverlay = viewbox.append("g").attr("id", "gridOverlay"); let coordinates = viewbox.append("g").attr("id", "coordinates"); @@ -72,7 +73,6 @@ let goods = viewbox.append("g").attr("id", "goods").style("display", "none"); let markets = viewbox.append("g").attr("id", "markets"); let tradeAnimation = viewbox.append("g").attr("id", "tradeAnimation"); let prec = viewbox.append("g").attr("id", "prec").style("display", "none"); -let population = viewbox.append("g").attr("id", "population"); let emblems = viewbox.append("g").attr("id", "emblems").style("display", "none"); let icons = viewbox.append("g").attr("id", "icons"); let labels = viewbox.append("g").attr("id", "labels"); diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index ffde6d53b5..478af0b1cb 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -373,6 +373,8 @@ function togglePopulation(event) { function drawPopulation() { TIME && console.time("drawPopulation"); + cells.node().before(population.node()); + population .attr("mask", "url(#land)") .attr("stroke", null) @@ -408,7 +410,7 @@ function drawPopulation() { const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true }); const paths = Object.entries(isolines).map(([color, { fill }], index) => { const closedFill = closeSvgPathRings(fill); - return /* html */ ``; + return /* html */ ``; }); ensureEl("population").innerHTML = paths.join(""); diff --git a/src/index.html b/src/index.html index ff65e1ba49..24afb05f11 100644 --- a/src/index.html +++ b/src/index.html @@ -133,7 +133,7 @@ + Population +
  • Precipitation
  • -
  • - Population -
  • - + - + From 78f08485e5481d266857cf73c24c84b3aba597ad Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 21:10:23 -0230 Subject: [PATCH 09/11] Fix population layer rendering order --- public/modules/ui/layers.js | 2 +- src/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 478af0b1cb..4fd4b8e315 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -373,7 +373,7 @@ function togglePopulation(event) { function drawPopulation() { TIME && console.time("drawPopulation"); - cells.node().before(population.node()); + biomes.node().after(population.node()); population .attr("mask", "url(#land)") diff --git a/src/index.html b/src/index.html index 24afb05f11..01c8fe7264 100644 --- a/src/index.html +++ b/src/index.html @@ -8931,7 +8931,7 @@ - + From 6858285483182b6d6dab8d48685382478cb39a6d Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 21:18:09 -0230 Subject: [PATCH 10/11] Restrict population strokes to coastlines --- public/modules/ui/layers.js | 9 ++++++--- src/index.html | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 4fd4b8e315..7d84cad279 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -407,10 +407,13 @@ function drawPopulation() { colors[cellId] = getPopulationColor(urban[cellId], total[cellId], maxPopulation); } - const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true }); - const paths = Object.entries(isolines).map(([color, { fill }], index) => { + const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true, waterGap: true }); + const paths = Object.entries(isolines).map(([color, { fill, waterGap }], index) => { const closedFill = closeSvgPathRings(fill); - return /* html */ ``; + const coastalStroke = waterGap + ? /* html */ `` + : ""; + return /* html */ `${coastalStroke}`; }); ensureEl("population").innerHTML = paths.join(""); diff --git a/src/index.html b/src/index.html index 01c8fe7264..1c205a42ad 100644 --- a/src/index.html +++ b/src/index.html @@ -8931,7 +8931,7 @@ - + From d3c733d2614b3ea576e6e0778ef2344d01c776ae Mon Sep 17 00:00:00 2001 From: Inviktos Date: Mon, 22 Jun 2026 21:22:55 -0230 Subject: [PATCH 11/11] Clip population coast strokes to water side --- public/modules/ui/layers.js | 14 ++++++++++---- src/index.html | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/public/modules/ui/layers.js b/public/modules/ui/layers.js index 7d84cad279..54747763f1 100644 --- a/public/modules/ui/layers.js +++ b/public/modules/ui/layers.js @@ -408,15 +408,21 @@ function drawPopulation() { } const isolines = getIsolines(pack, cellId => colors[cellId] || null, { fill: true, waterGap: true }); - const paths = Object.entries(isolines).map(([color, { fill, waterGap }], index) => { + const populationShapes = Object.entries(isolines).map(([color, { fill, waterGap }], index) => { const closedFill = closeSvgPathRings(fill); + const coastalClipId = `population-coast-clip${index}`; + const outsideFillClip = /* html */ ``; const coastalStroke = waterGap - ? /* html */ `` + ? /* html */ `` : ""; - return /* html */ `${coastalStroke}`; + const fillPath = /* html */ ``; + return { outsideFillClip, coastalStroke, fillPath }; }); - ensureEl("population").innerHTML = paths.join(""); + const coastClips = populationShapes.map(({ outsideFillClip }) => outsideFillClip).join(""); + const coastalStrokes = populationShapes.map(({ coastalStroke }) => coastalStroke).join(""); + const fillPaths = populationShapes.map(({ fillPath }) => fillPath).join(""); + ensureEl("population").innerHTML = `${coastClips}${coastalStrokes}${fillPaths}`; TIME && console.timeEnd("drawPopulation"); } diff --git a/src/index.html b/src/index.html index 1c205a42ad..a541fb7475 100644 --- a/src/index.html +++ b/src/index.html @@ -8931,7 +8931,7 @@ - +