store populationRate and urbanization in memory

This commit is contained in:
Azgaar 2021-06-27 14:24:55 +03:00
parent 3892370816
commit 5ed08e156a
19 changed files with 2870 additions and 1552 deletions

View file

@ -9,7 +9,10 @@ function editZones() {
modules.editZones = true;
$("#zonesEditor").dialog({
title: "Zones Editor", resizable: false, width: fitContent(), close: () => exitZonesManualAssignment("close"),
title: "Zones Editor",
resizable: false,
width: fitContent(),
close: () => exitZonesManualAssignment("close"),
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
});
@ -25,19 +28,37 @@ function editZones() {
document.getElementById("zonesExport").addEventListener("click", downloadZonesData);
document.getElementById("zonesRemove").addEventListener("click", toggleEraseMode);
body.addEventListener("click", function(ev) {
const el = ev.target, cl = el.classList, zone = el.parentNode.dataset.id;
if (cl.contains("culturePopulation")) {changePopulation(zone); return;}
if (cl.contains("icon-trash-empty")) {zoneRemove(zone); return;}
if (cl.contains("icon-eye")) {toggleVisibility(el); return;}
if (cl.contains("icon-pin")) {toggleFog(zone, cl); return;}
if (cl.contains("fillRect")) {changeFill(el); return;}
body.addEventListener("click", function (ev) {
const el = ev.target,
cl = el.classList,
zone = el.parentNode.dataset.id;
if (cl.contains("culturePopulation")) {
changePopulation(zone);
return;
}
if (cl.contains("icon-trash-empty")) {
zoneRemove(zone);
return;
}
if (cl.contains("icon-eye")) {
toggleVisibility(el);
return;
}
if (cl.contains("icon-pin")) {
toggleFog(zone, cl);
return;
}
if (cl.contains("fillRect")) {
changeFill(el);
return;
}
if (customization) selectZone(el);
});
body.addEventListener("input", function(ev) {
const el = ev.target, zone = el.parentNode.dataset.id;
if (el.classList.contains("religionName")) zones.select("#"+zone).attr("data-description", el.value);
body.addEventListener("input", function (ev) {
const el = ev.target,
zone = el.parentNode.dataset.id;
if (el.classList.contains("religionName")) zones.select("#" + zone).attr("data-description", el.value);
});
// add line for each zone
@ -45,17 +66,17 @@ function editZones() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
let lines = "";
zones.selectAll("g").each(function() {
zones.selectAll("g").each(function () {
const c = this.dataset.cells ? this.dataset.cells.split(",").map(c => +c) : [];
const description = this.dataset.description;
const fill = this.getAttribute("fill");
const area = d3.sum(c.map(i => pack.cells.area[i])) * (distanceScaleInput.value ** 2);
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate.value;
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value;
const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2;
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
const population = rural + urban;
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
const inactive = this.style.display === "none";
const focused = defs.select("#fog #focus"+this.id).size();
const focused = defs.select("#fog #focus" + this.id).size();
lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}" data-cells=${c.length} data-area=${area} data-population=${population}>
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
@ -67,8 +88,8 @@ function editZones() {
<span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<span data-tip="Drag to raise or lower the zone" class="icon-resize-vertical hide"></span>
<span data-tip="Toggle zone focus" class="icon-pin ${focused?'':' inactive'} hide ${c.length?'':' placeholder'}"></span>
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive?' inactive':''} hide ${c.length?'':' placeholder'}"></span>
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? "" : " inactive"} hide ${c.length ? "" : " placeholder"}"></span>
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? " inactive" : ""} hide ${c.length ? "" : " placeholder"}"></span>
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
</div>`;
});
@ -76,8 +97,8 @@ function editZones() {
body.innerHTML = lines;
// update footer
const totalArea = zonesFooterArea.dataset.area = graphWidth * graphHeight * (distanceScaleInput.value ** 2);
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization.value) * populationRate.value;
const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2);
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate;
zonesFooterPopulation.dataset.population = totalPop;
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
zonesFooterCells.innerHTML = pack.cells.i.length;
@ -88,48 +109,53 @@ function editZones() {
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => zoneHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => zoneHighlightOff(ev)));
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
if (body.dataset.type === "percentage") {
body.dataset.type = "absolute";
togglePercentageMode();
}
$("#zonesEditor").dialog({width: fitContent()});
}
function zoneHighlightOn(event) {
const zone = event.target.dataset.id;
zones.select("#"+zone).style("outline", "1px solid red");
zones.select("#" + zone).style("outline", "1px solid red");
}
function zoneHighlightOff(event) {
const zone = event.target.dataset.id;
zones.select("#"+zone).style("outline", null);
zones.select("#" + zone).style("outline", null);
}
$(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone});
function movezone(ev, ui) {
const zone = $("#"+ui.item.attr("data-id"));
const prev = $("#"+ui.item.prev().attr("data-id"));
if (prev) {zone.insertAfter(prev); return;}
const next = $("#"+ui.item.next().attr("data-id"));
const zone = $("#" + ui.item.attr("data-id"));
const prev = $("#" + ui.item.prev().attr("data-id"));
if (prev) {
zone.insertAfter(prev);
return;
}
const next = $("#" + ui.item.next().attr("data-id"));
if (next) zone.insertBefore(next);
}
function enterZonesManualAssignent() {
if (!layerIsOn("toggleZones")) toggleZones();
customization = 10;
document.querySelectorAll("#zonesBottom > button").forEach(el => el.style.display = "none");
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none"));
document.getElementById("zonesManuallyButtons").style.display = "inline-block";
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
zonesFooter.style.display = "none";
body.querySelectorAll("div > input, select, svg").forEach(e => e.style.pointerEvents = "none");
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "none"));
$("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
tip("Click to select a zone, drag to paint a zone", true);
viewbox.style("cursor", "crosshair")
.on("click", selectZoneOnMapClick)
.call(d3.drag().on("start", dragZoneBrush))
.on("touchmove mousemove", moveZoneBrush);
viewbox.style("cursor", "crosshair").on("click", selectZoneOnMapClick).call(d3.drag().on("start", dragZoneBrush)).on("touchmove mousemove", moveZoneBrush);
body.querySelector("div").classList.add("selected");
zones.selectAll("g").each(function() {this.setAttribute("data-init", this.getAttribute("data-cells"));});
zones.selectAll("g").each(function () {
this.setAttribute("data-init", this.getAttribute("data-cells"));
});
}
function selectZone(el) {
@ -154,9 +180,9 @@ function editZones() {
const selection = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)];
if (!selection) return;
const selected = body.querySelector("div.selected");
const zone = zones.select("#"+selected.dataset.id);
const zone = zones.select("#" + selected.dataset.id);
const base = zone.attr("id") + "_"; // id generic part
const dataCells = zone.attr("data-cells");
let cells = dataCells ? dataCells.split(",").map(i => +i) : [];
@ -175,7 +201,10 @@ function editZones() {
selection.forEach(i => {
if (cells.includes(i)) return;
cells.push(i);
zone.append("polygon").attr("points", getPackPolygon(i)).attr("id", base + i);
zone
.append("polygon")
.attr("points", getPackPolygon(i))
.attr("id", base + i);
});
}
@ -191,10 +220,10 @@ function editZones() {
}
function applyZonesManualAssignent() {
zones.selectAll("g").each(function() {
zones.selectAll("g").each(function () {
if (this.dataset.cells) return;
// all zone cells are removed
unfog("focusZone"+this.id);
unfog("focusZone" + this.id);
this.style.display = "block";
});
@ -204,15 +233,20 @@ function editZones() {
// restore initial zone cells
function cancelZonesManualAssignent() {
zones.selectAll("g").each(function() {
zones.selectAll("g").each(function () {
const zone = d3.select(this);
const dataCells = zone.attr("data-init");
const cells = dataCells ? dataCells.split(",").map(i => +i) : [];
zone.attr("data-cells", cells);
zone.selectAll("*").remove();
const base = zone.attr("id") + "_"; // id generic part
zone.selectAll("*").data(cells).enter().append("polygon")
.attr("points", d => getPackPolygon(d)).attr("id", d => base + d);
zone
.selectAll("*")
.data(cells)
.enter()
.append("polygon")
.attr("points", d => getPackPolygon(d))
.attr("id", d => base + d);
});
exitZonesManualAssignment();
@ -221,56 +255,68 @@ function editZones() {
function exitZonesManualAssignment(close) {
customization = 0;
removeCircle();
document.querySelectorAll("#zonesBottom > button").forEach(el => el.style.display = "inline-block");
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block"));
document.getElementById("zonesManuallyButtons").style.display = "none";
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
zonesFooter.style.display = "block";
body.querySelectorAll("div > input, select, svg").forEach(e => e.style.pointerEvents = "all");
if(!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "all"));
if (!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
restoreDefaultEvents();
clearMainTip();
zones.selectAll("g").each(function() {this.removeAttribute("data-init");});
zones.selectAll("g").each(function () {
this.removeAttribute("data-init");
});
const selected = body.querySelector("div.selected");
if (selected) selected.classList.remove("selected");
}
function changeFill(el) {
const fill = el.getAttribute("fill");
const callback = function(fill) {
const callback = function (fill) {
el.setAttribute("fill", fill);
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill);
}
};
openPicker(fill, callback);
}
function toggleVisibility(el) {
const zone = zones.select("#"+el.parentNode.dataset.id);
const zone = zones.select("#" + el.parentNode.dataset.id);
const inactive = zone.style("display") === "none";
inactive ? zone.style("display", "block") : zone.style("display", "none");
el.classList.toggle("inactive");
}
function toggleFog(z, cl) {
const dataCells = zones.select("#"+z).attr("data-cells");
const dataCells = zones.select("#" + z).attr("data-cells");
if (!dataCells) return;
const path = "M" + dataCells.split(",").map(c => getPackPolygon(+c)).join("M") + "Z", id = "focusZone"+z;
const path =
"M" +
dataCells
.split(",")
.map(c => getPackPolygon(+c))
.join("M") +
"Z",
id = "focusZone" + z;
cl.contains("inactive") ? fog(id, path) : unfog(id);
cl.toggle("inactive");
}
function toggleLegend() {
if (legend.selectAll("*").size()) {clearLegend(); return;}; // hide legend
if (legend.selectAll("*").size()) {
clearLegend();
return;
} // hide legend
const data = [];
zones.selectAll("g").each(function() {
zones.selectAll("g").each(function () {
const id = this.dataset.id;
const description = this.dataset.description;
const fill = this.getAttribute("fill");
data.push([id, fill, description])
data.push([id, fill, description]);
});
drawLegend("Zones", data);
@ -283,12 +329,11 @@ function editZones() {
const totalArea = +zonesFooterArea.dataset.area;
const totalPopulation = +zonesFooterPopulation.dataset.population;
body.querySelectorAll(":scope > div").forEach(function(el) {
el.querySelector(".stateCells").innerHTML = rn(+el.dataset.cells / totalCells * 100, 2) + "%";
el.querySelector(".biomeArea").innerHTML = rn(+el.dataset.area / totalArea * 100, 2) + "%";
el.querySelector(".culturePopulation").innerHTML = rn(+el.dataset.population / totalPopulation * 100, 2) + "%";
body.querySelectorAll(":scope > div").forEach(function (el) {
el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%";
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + "%";
el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + "%";
});
} else {
body.dataset.type = "absolute";
zonesEditorAddLines();
@ -298,7 +343,7 @@ function editZones() {
function addZonesLayer() {
const id = getNextId("zone");
const description = "Unknown zone";
const fill = "url(#hatch" + id.slice(4)%14 + ")";
const fill = "url(#hatch" + (id.slice(4) % 14) + ")";
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill);
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
@ -323,9 +368,9 @@ function editZones() {
function downloadZonesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
let data = "Id,Fill,Description,Cells,Area "+unit+",Population\n"; // headers
let data = "Id,Fill,Description,Cells,Area " + unit + ",Population\n"; // headers
body.querySelectorAll(":scope > div").forEach(function(el) {
body.querySelectorAll(":scope > div").forEach(function (el) {
data += el.dataset.id + ",";
data += el.dataset.fill + ",";
data += el.dataset.description + ",";
@ -343,68 +388,83 @@ function editZones() {
}
function changePopulation(zone) {
const dataCells = zones.select("#"+zone).attr("data-cells");
const cells = dataCells ? dataCells.split(",").map(i => +i).filter(i => pack.cells.h[i] >= 20) : [];
if (!cells.length) {tip("Zone does not have any land cells, cannot change population", false, "error"); return;}
const dataCells = zones.select("#" + zone).attr("data-cells");
const cells = dataCells
? dataCells
.split(",")
.map(i => +i)
.filter(i => pack.cells.h[i] >= 20)
: [];
if (!cells.length) {
tip("Zone does not have any land cells, cannot change population", false, "error");
return;
}
const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate.value);
const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate.value * urbanization.value);
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate);
const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization);
const total = rural + urban;
const l = n => Number(n).toLocaleString();
alertMessage.innerHTML = `
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length?'':"disabled"}>
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"}>
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function() {
const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
if (isNaN(totalNew)) return;
totalPop.innerHTML = l(totalNew);
totalPopPerc.innerHTML = rn(totalNew / total * 100);
}
totalPopPerc.innerHTML = rn((totalNew / total) * 100);
};
ruralPop.oninput = () => update();
urbanPop.oninput = () => update();
$("#alert").dialog({
resizable: false, title: "Change zone population", width: "24em", buttons: {
Apply: function() {applyPopulationChange(); $(this).dialog("close");},
Cancel: function() {$(this).dialog("close");}
}, position: {my: "center", at: "center", of: "svg"}
resizable: false,
title: "Change zone population",
width: "24em",
buttons: {
Apply: function () {
applyPopulationChange();
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
},
position: {my: "center", at: "center", of: "svg"}
});
function applyPopulationChange() {
const ruralChange = ruralPop.value / rural;
if (isFinite(ruralChange) && ruralChange !== 1) {
cells.forEach(i => pack.cells.pop[i] *= ruralChange);
cells.forEach(i => (pack.cells.pop[i] *= ruralChange));
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate.value;
const points = ruralPop.value / populationRate;
const pop = rn(points / cells.length);
cells.forEach(i => pack.cells.pop[i] = pop);
cells.forEach(i => (pack.cells.pop[i] = pop));
}
const urbanChange = urbanPop.value / urban;
if (isFinite(urbanChange) && urbanChange !== 1) {
burgs.forEach(b => b.population = rn(b.population * urbanChange, 4));
burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4)));
}
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
const points = urbanPop.value / populationRate.value / urbanization.value;
const points = urbanPop.value / populationRate / urbanization;
const population = rn(points / burgs.length, 4);
burgs.forEach(b => b.population = population);
burgs.forEach(b => (b.population = population));
}
zonesEditorAddLines();
}
}
function zoneRemove(zone) {
zones.select("#"+zone).remove();
unfog("focusZone"+zone);
zones.select("#" + zone).remove();
unfog("focusZone" + zone);
zonesEditorAddLines();
}
}