Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into letter-spacing

This commit is contained in:
Azgaar 2024-09-01 13:03:23 +02:00
commit 6e64912e27
25 changed files with 1057 additions and 885 deletions

View file

@ -228,34 +228,26 @@ function editBurg(id) {
const burgsToRemove = burgsInGroup.filter(b => !(pack.burgs[b].capital || pack.burgs[b].lock));
const capital = burgsToRemove.length < burgsInGroup.length;
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
}?
<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${
burgsToRemove.length
}`;
$("#alert").dialog({
resizable: false,
confirmationDialog({
title: "Remove burg group",
buttons: {
Remove: function () {
$(this).dialog("close");
$("#burgEditor").dialog("close");
hideGroupSection();
burgsToRemove.forEach(b => removeBurg(b));
message: `Are you sure you want to remove ${
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
}?<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${
burgsToRemove.length
}. This action cannot be reverted`,
confirm: "Remove",
onConfirm: () => {
$("#burgEditor").dialog("close");
hideGroupSection();
burgsToRemove.forEach(b => removeBurg(b));
if (!basic && !capital) {
// entirely remove group
const labelG = document.querySelector("#burgLabels > #" + group.id);
const iconG = document.querySelector("#burgIcons > #" + group.id);
const anchorG = document.querySelector("#anchors > #" + group.id);
if (labelG) labelG.remove();
if (iconG) iconG.remove();
if (anchorG) anchorG.remove();
}
},
Cancel: function () {
$(this).dialog("close");
if (!basic && !capital) {
const labelG = document.querySelector("#burgLabels > #" + group.id);
const iconG = document.querySelector("#burgIcons > #" + group.id);
const anchorG = document.querySelector("#anchors > #" + group.id);
if (labelG) labelG.remove();
if (iconG) iconG.remove();
if (anchorG) anchorG.remove();
}
}
});
@ -509,19 +501,13 @@ function editBurg(id) {
}
});
} else {
alertMessage.innerHTML = "Are you sure you want to remove the burg?";
$("#alert").dialog({
resizable: false,
confirmationDialog({
title: "Remove burg",
buttons: {
Remove: function () {
$(this).dialog("close");
removeBurg(id); // see Editors module
$("#burgEditor").dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
message: "Are you sure you want to remove the burg? <br>This action cannot be reverted",
confirm: "Remove",
onConfirm: () => {
removeBurg(id); // see Editors module
$("#burgEditor").dialog("close");
}
});
}

View file

@ -245,7 +245,7 @@ function overviewBurgs(settings = {stateId: null, cultureId: null}) {
confirmationDialog({
title: "Remove burg",
message: "Are you sure you want to remove the burg? This actiove cannot be reverted",
message: "Are you sure you want to remove the burg? <br>This action cannot be reverted",
confirm: "Remove",
onConfirm: () => {
removeBurg(burg);

View file

@ -168,6 +168,7 @@ function showMapTooltip(point, e, i, g) {
if (burgsOverview?.offsetParent) highlightEditorLine(burgsOverview, burg, 5000);
return;
}
if (group === "labels") return tip("Click to edit the Label");
if (group === "markers") return tip("Click to edit the Marker. Hold Shift to not close the assosiated note");
@ -199,9 +200,11 @@ function showMapTooltip(point, e, i, g) {
if (group === "coastline") return tip("Click to edit the coastline");
if (group === "zones") {
const zone = path[path.length - 8];
tip(zone.dataset.description);
if (zonesEditor?.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000);
const element = path[path.length - 8];
const zoneId = +element.dataset.id;
const zone = pack.zones.find(zone => zone.i === zoneId);
tip(zone.name);
if (zonesEditor?.offsetParent) highlightEditorLine(zonesEditor, zoneId, 5000);
return;
}

View file

@ -261,7 +261,7 @@ function editHeightmap(options) {
Military.generate();
Markers.generate();
addZones();
Zones.generate();
TIME && console.timeEnd("regenerateErasedData");
INFO && console.groupEnd("Edit Heightmap");
}

View file

@ -181,6 +181,7 @@ function restoreLayers() {
if (layerIsOn("toggleIce")) drawIce();
if (layerIsOn("toggleEmblems")) drawEmblems();
if (layerIsOn("toggleMarkers")) drawMarkers();
if (layerIsOn("toggleZones")) drawZones();
// some layers are rendered each time, remove them if they are not on
if (!layerIsOn("toggleBorders")) borders.selectAll("path").remove();
@ -1872,18 +1873,29 @@ function fitScaleBar(scaleBar, fullWidth, fullHeight) {
function toggleZones(event) {
if (!layerIsOn("toggleZones")) {
turnButtonOn("toggleZones");
$("#zones").fadeIn();
drawZones();
if (event && isCtrlClick(event)) editStyle("zones");
} else {
if (event && isCtrlClick(event)) {
editStyle("zones");
return;
}
if (event && isCtrlClick(event)) return editStyle("zones");
turnButtonOff("toggleZones");
$("#zones").fadeOut();
zones.selectAll("*").remove();
}
}
function drawZones() {
const filterBy = byId("zonesFilterType").value;
const isFiltered = filterBy && filterBy !== "all";
const visibleZones = pack.zones.filter(
({hidden, cells, type}) => !hidden && cells.length && (!isFiltered || type === filterBy)
);
zones.html(visibleZones.map(drawZone).join(""));
}
function drawZone({i, cells, type, color}) {
const path = getVertexPath(cells);
return `<path id="zone${i}" data-id="${i}" data-type="${type}" d="${path}" fill="${color}" />`;
}
function toggleEmblems(event) {
if (!layerIsOn("toggleEmblems")) {
turnButtonOn("toggleEmblems");

View file

@ -782,7 +782,7 @@ function showExportPane() {
}
async function exportToJson(type) {
const {exportToJson} = await import("../dynamic/export-json.js?v=1.97.08");
const {exportToJson} = await import("../dynamic/export-json.js?v=1.100.00");
exportToJson(type);
}

View file

@ -389,20 +389,13 @@ function editRoute(id) {
}
function removeRoute() {
alertMessage.innerHTML = "Are you sure you want to remove the route";
$("#alert").dialog({
resizable: false,
width: "22em",
confirmationDialog({
title: "Remove route",
buttons: {
Remove: function () {
Routes.remove(getRoute());
$(this).dialog("close");
$("#routeEditor").dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
message: "Are you sure you want to remove the route? <br>This action cannot be reverted",
confirm: "Remove",
onConfirm: () => {
Routes.remove(getRoute());
$("#routeEditor").dialog("close");
}
});
}

View file

@ -144,22 +144,14 @@ function overviewRoutes() {
function triggerRouteRemove() {
const routeId = +this.parentNode.dataset.id;
alertMessage.innerHTML = `Are you sure you want to remove the route?`;
$("#alert").dialog({
resizable: false,
width: "22em",
confirmationDialog({
title: "Remove route",
buttons: {
Remove: function () {
const route = pack.routes.find(r => r.i === routeId);
Routes.remove(route);
routesOverviewAddLines();
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
message: "Are you sure you want to remove the route? <br>This action cannot be reverted",
confirm: "Remove",
onConfirm: () => {
const route = pack.routes.find(r => r.i === routeId);
Routes.remove(route);
routesOverviewAddLines();
}
});
}

View file

@ -64,7 +64,7 @@ async function getStylePreset(desiredPreset) {
async function fetchSystemPreset(preset) {
try {
const res = await fetch(`./styles/${preset}.json?v=${version}`);
const res = await fetch(`./styles/${preset}.json?v=${VERSION}`);
return await res.json();
} catch (err) {
throw new Error("Cannot fetch style preset", preset);

View file

@ -159,18 +159,6 @@ window.UISubmap = (function () {
return canvas;
}
// currently unused alternative to PNG version
async function loadPreviewSVG($container, w, h) {
$container.innerHTML = /*html*/ `
<svg id="submapPreviewSVG" viewBox="0 0 ${graphWidth} ${graphHeight}">
<rect width="100%" height="100%" fill="${byId("styleOceanFill").value}" />
<rect fill="url(#oceanic)" width="100%" height="100%" />
<use href="#map"></use>
</svg>
`;
return byId("submapPreviewSVG");
}
// Resample the whole map to different cell resolution or shape
const resampleCurrentMap = debounce(function () {
WARN && console.warn("Resampling current map");

View file

@ -167,9 +167,9 @@ function regenerateStates() {
Military.generate();
if (layerIsOn("toggleEmblems")) drawEmblems();
if (document.getElementById("burgsOverviewRefresh")?.offsetParent) burgsOverviewRefresh.click();
if (document.getElementById("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
if (document.getElementById("militaryOverviewRefresh")?.offsetParent) militaryOverviewRefresh.click();
if (byId("burgsOverviewRefresh")?.offsetParent) burgsOverviewRefresh.click();
if (byId("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
if (byId("militaryOverviewRefresh")?.offsetParent) militaryOverviewRefresh.click();
}
function recreateStates() {
@ -445,8 +445,8 @@ function regenerateBurgs() {
emblems.selectAll("use").remove();
if (layerIsOn("toggleEmblems")) drawEmblems();
if (document.getElementById("burgsOverviewRefresh")?.offsetParent) burgsOverviewRefresh.click();
if (document.getElementById("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
if (byId("burgsOverviewRefresh")?.offsetParent) burgsOverviewRefresh.click();
if (byId("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
}
function regenerateEmblems() {
@ -521,7 +521,7 @@ function regenerateCultures() {
function regenerateMilitary() {
Military.generate();
if (!layerIsOn("toggleMilitary")) toggleMilitary();
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
if (byId("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
}
function regenerateIce() {
@ -534,7 +534,7 @@ function regenerateMarkers() {
Markers.regenerate();
turnButtonOn("toggleMarkers");
drawMarkers();
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
if (byId("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
}
function regenerateZones(event) {
@ -545,10 +545,9 @@ function regenerateZones(event) {
else addNumberOfZones(gauss(1, 0.5, 0.6, 5, 2));
function addNumberOfZones(number) {
zones.selectAll("g").remove(); // remove existing zones
addZones(number);
if (document.getElementById("zonesEditorRefresh").offsetParent) zonesEditorRefresh.click();
if (!layerIsOn("toggleZones")) toggleZones();
Zones.generate(number);
if (byId("zonesEditorRefresh").offsetParent) zonesEditorRefresh.click();
if (layerIsOn("toggleZones")) drawZones();
}
}
@ -559,7 +558,7 @@ function unpressClickToAddButton() {
}
function toggleAddLabel() {
const pressed = document.getElementById("addLabel").classList.contains("pressed");
const pressed = byId("addLabel").classList.contains("pressed");
if (pressed) {
unpressClickToAddButton();
return;
@ -627,22 +626,22 @@ function addLabelOnClick() {
function toggleAddBurg() {
unpressClickToAddButton();
document.getElementById("addBurgTool").classList.add("pressed");
byId("addBurgTool").classList.add("pressed");
overviewBurgs();
document.getElementById("addNewBurg").click();
byId("addNewBurg").click();
}
function toggleAddRiver() {
const pressed = document.getElementById("addRiver").classList.contains("pressed");
const pressed = byId("addRiver").classList.contains("pressed");
if (pressed) {
unpressClickToAddButton();
document.getElementById("addNewRiver").classList.remove("pressed");
byId("addNewRiver").classList.remove("pressed");
return;
}
addFeature.querySelectorAll("button.pressed").forEach(b => b.classList.remove("pressed"));
addRiver.classList.add("pressed");
document.getElementById("addNewRiver").classList.add("pressed");
byId("addNewRiver").classList.add("pressed");
closeDialogs(".stable");
viewbox.style("cursor", "crosshair").on("click", addRiverOnClick);
tip("Click on map to place new river or extend an existing one. Hold Shift to place multiple rivers", true, "warn");
@ -728,7 +727,7 @@ function addRiverOnClick() {
}
// continue old river
document.getElementById("river" + oldRiverId)?.remove();
byId("river" + oldRiverId)?.remove();
riverCells.forEach(i => (cells.r[i] = oldRiverId));
oldRiverCells.forEach(cell => {
if (h[cell] > h[min]) {
@ -796,13 +795,13 @@ function addRiverOnClick() {
if (d3.event.shiftKey === false) {
Lakes.cleanupLakeData();
unpressClickToAddButton();
document.getElementById("addNewRiver").classList.remove("pressed");
byId("addNewRiver").classList.remove("pressed");
if (addNewRiver.offsetParent) riversOverviewRefresh.click();
}
}
function toggleAddMarker() {
const pressed = document.getElementById("addMarker")?.classList.contains("pressed");
const pressed = byId("addMarker")?.classList.contains("pressed");
if (pressed) {
unpressClickToAddButton();
return;
@ -830,7 +829,7 @@ function addMarkerOnClick() {
const isMarkerSelected = markers.length && elSelected?.node()?.parentElement?.id === "markers";
const selectedMarker = isMarkerSelected ? markers.find(marker => marker.i === +elSelected.attr("id").slice(6)) : null;
const selectedType = document.getElementById("addedMarkerType").value;
const selectedType = byId("addedMarkerType").value;
const selectedConfig = Markers.getConfig().find(({type}) => type === selectedType);
const baseMarker = selectedMarker || selectedConfig || {icon: "❓"};
@ -840,13 +839,13 @@ function addMarkerOnClick() {
selectedConfig.add("marker" + marker.i, cell);
}
const markersElement = document.getElementById("markers");
const markersElement = byId("markers");
const rescale = +markersElement.getAttribute("rescale");
markersElement.insertAdjacentHTML("beforeend", drawMarker(marker, rescale));
if (d3.event.shiftKey === false) {
document.getElementById("markerAdd").classList.remove("pressed");
document.getElementById("markersAddFromOverview").classList.remove("pressed");
byId("markerAdd").classList.remove("pressed");
byId("markersAddFromOverview").classList.remove("pressed");
unpressClickToAddButton();
}
}

View file

@ -14,7 +14,6 @@ function editZones() {
$("#zonesEditor").dialog({
title: "Zones Editor",
resizable: false,
width: fitContent(),
close: () => exitZonesManualAssignment("close"),
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
});
@ -31,34 +30,40 @@ function editZones() {
byId("zonesManuallyCancel").on("click", cancelZonesManualAssignent);
byId("zonesAdd").on("click", addZonesLayer);
byId("zonesExport").on("click", downloadZonesData);
byId("zonesRemove").on("click", toggleEraseMode);
byId("zonesRemove").on("click", e => e.target.classList.toggle("pressed"));
body.on("click", function (ev) {
const el = ev.target,
cl = el.classList,
zone = el.parentNode.dataset.id;
if (el.tagName === "FILL-BOX") changeFill(el);
else if (cl.contains("culturePopulation")) changePopulation(zone);
else if (cl.contains("icon-trash-empty")) zoneRemove(zone);
else if (cl.contains("icon-eye")) toggleVisibility(el);
else if (cl.contains("icon-pin")) toggleFog(zone, cl);
if (customization) selectZone(el);
const line = ev.target.closest("div.states");
const zone = pack.zones.find(z => z.i === +line.dataset.id);
if (!zone) return;
if (customization) {
if (zone.hidden) return;
body.querySelector("div.selected").classList.remove("selected");
line.classList.add("selected");
return;
}
if (ev.target.closest("fill-box")) changeFill(ev.target.closest("fill-box").getAttribute("fill"), zone);
else if (ev.target.classList.contains("zonePopulation")) changePopulation(zone);
else if (ev.target.classList.contains("zoneRemove")) zoneRemove(zone);
else if (ev.target.classList.contains("zoneHide")) toggleVisibility(zone);
else if (ev.target.classList.contains("zoneFog")) toggleFog(zone, ev.target.classList);
});
body.on("input", function (ev) {
const el = ev.target;
const zone = zones.select("#" + el.parentNode.dataset.id);
const line = ev.target.closest("div.states");
const zone = pack.zones.find(z => z.i === +line.dataset.id);
if (!zone) return;
if (el.classList.contains("zoneName")) zone.attr("data-description", el.value);
else if (el.classList.contains("zoneType")) zone.attr("data-type", el.value);
if (ev.target.classList.contains("zoneName")) changeDescription(zone, ev.target.value);
else if (ev.target.classList.contains("zoneType")) changeType(zone, ev.target.value);
});
// update type filter with a list of used types
function updateFilters() {
const zones = Array.from(document.querySelectorAll("#zones > g"));
const types = unique(zones.map(zone => zone.dataset.type));
const filterSelect = byId("zonesFilterType");
const types = unique(pack.zones.map(zone => zone.type));
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
filterSelect.innerHTML =
@ -68,47 +73,42 @@ function editZones() {
// add line for each zone
function zonesEditorAddLines() {
const unit = " " + getAreaUnit();
const typeToFilterBy = byId("zonesFilterType").value;
const zones = Array.from(document.querySelectorAll("#zones > g"));
const filteredZones = typeToFilterBy === "all" ? zones : zones.filter(zone => zone.dataset.type === typeToFilterBy);
const filteredZones =
typeToFilterBy === "all" ? pack.zones : pack.zones.filter(zone => zone.type === typeToFilterBy);
const lines = filteredZones.map(zoneEl => {
const c = zoneEl.dataset.cells ? zoneEl.dataset.cells.split(",").map(c => +c) : [];
const description = zoneEl.dataset.description;
const type = zoneEl.dataset.type;
const fill = zoneEl.getAttribute("fill");
const area = getArea(d3.sum(c.map(i => pack.cells.area[i])));
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
const lines = filteredZones.map(({i, name, type, cells, color, hidden}) => {
const area = getArea(d3.sum(cells.map(i => pack.cells.area[i])));
const rural = d3.sum(cells.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;
d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
const population = rn(rural + urban);
const populationTip = `Total population: ${si(population)}; Rural population: ${si(
rural
)}; Urban population: ${si(urban)}. Click to change`;
const inactive = zoneEl.style.display === "none";
const focused = defs.select("#fog #focus" + zoneEl.id).size();
const focused = defs.select("#fog #focusZone" + i).size();
return `<div class="states" data-id="${zoneEl.id}" data-fill="${fill}" data-description="${description}"
data-type="${type}" data-cells=${c.length} data-area=${area} data-population=${population}>
<fill-box fill="${fill}"></fill-box>
<input data-tip="Zone description. Click and type to change" style="width: 11em" class="zoneName" value="${description}" autocorrect="off" spellcheck="false">
return /* html */ `<div class="states" data-id="${i}" data-color="${color}" data-description="${name}"
data-type="${type}" data-cells=${cells.length} data-area=${area} data-population=${population} style="${
hidden && "opacity: 0.5"
}">
<fill-box fill="${color}"></fill-box>
<input data-tip="Zone description. Click and type to change" style="width: 11em" class="zoneName" value="${name}" autocorrect="off" spellcheck="false">
<input data-tip="Zone type. Click and type to change" class="zoneType" value="${type}">
<span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">${c.length}</div>
<div data-tip="Cells count" class="stateCells hide">${cells.length}</div>
<span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span>
<div data-tip="Zone area" class="biomeArea hide">${si(area) + unit}</div>
<div data-tip="Zone area" class="biomeArea hide">${si(area) + " " + getAreaUnit()}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<div data-tip="${populationTip}" class="zonePopulation hide pointer">${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 data-tip="Toggle zone focus" class="zoneFog icon-pin ${focused ? "" : "inactive"} hide ${
cells.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>
<span data-tip="Toggle zone visibility" class="zoneHide icon-eye hide ${
cells.length ? "" : " placeholder"
}"></span>
<span data-tip="Remove zone" class="zoneRemove icon-trash-empty hide"></span>
</div>`;
});
@ -121,14 +121,13 @@ function editZones() {
(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 = /* html */ `${filteredZones.length} of ${zones.length}`;
zonesFooterNumber.innerHTML = `${filteredZones.length} of ${pack.zones.length}`;
zonesFooterCells.innerHTML = pack.cells.i.length;
zonesFooterArea.innerHTML = si(totalArea) + unit;
zonesFooterArea.innerHTML = si(totalArea) + " " + getAreaUnit();
zonesFooterPopulation.innerHTML = si(totalPop);
// add listeners
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", ev => zoneHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", ev => zoneHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", zoneHighlightOn));
body.querySelectorAll("div.states").forEach(el => el.on("mouseleave", zoneHighlightOff));
if (body.dataset.type === "percentage") {
body.dataset.type = "absolute";
@ -138,25 +137,17 @@ function editZones() {
}
function zoneHighlightOn(event) {
const zone = event.target.dataset.id;
zones.select("#" + zone).style("outline", "1px solid red");
const zoneId = event.target.dataset.id;
zones.select("#zone" + zoneId).style("outline", "1px solid red");
}
function zoneHighlightOff(event) {
const zone = event.target.dataset.id;
zones.select("#" + zone).style("outline", null);
const zoneId = event.target.dataset.id;
zones.select("#zone" + zoneId).style("outline", null);
}
function filterZonesByType() {
const typeToFilterBy = this.value;
const zones = Array.from(document.querySelectorAll("#zones > g"));
for (const zone of zones) {
const type = zone.dataset.type;
const visible = typeToFilterBy === "all" || type === typeToFilterBy;
zone.style.display = visible ? "block" : "none";
}
drawZones();
zonesEditorAddLines();
}
@ -167,23 +158,24 @@ function editZones() {
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"));
if (next) zone.insertBefore(next);
function movezone(_ev, ui) {
const zone = pack.zones.find(z => z.i === +ui.item[0].dataset.id);
const oldIndex = pack.zones.indexOf(zone);
const newIndex = ui.item.index();
if (oldIndex === newIndex) return;
pack.zones.splice(oldIndex, 1);
pack.zones.splice(newIndex, 0, zone);
drawZones();
}
function enterZonesManualAssignent() {
if (!layerIsOn("toggleZones")) toggleZones();
customization = 10;
document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "none"));
byId("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"));
@ -197,21 +189,32 @@ function editZones() {
.on("touchmove mousemove", moveZoneBrush);
body.querySelector("div").classList.add("selected");
zones.selectAll("g").each(function () {
this.setAttribute("data-init", this.getAttribute("data-cells"));
});
}
function selectZone(el) {
body.querySelector("div.selected").classList.remove("selected");
el.classList.add("selected");
// draw zones as individual cells
zones.selectAll("*").remove();
const filterBy = byId("zonesFilterType").value;
const isFiltered = filterBy && filterBy !== "all";
const visibleZones = pack.zones.filter(zone => !zone.hidden && (!isFiltered || zone.type === filterBy));
const data = visibleZones.map(({i, cells, color}) => cells.map(cell => ({cell, zoneId: i, fill: color}))).flat();
zones
.selectAll("polygon")
.data(data, d => `${d.zoneId}-${d.cell}`)
.enter()
.append("polygon")
.attr("points", d => getPackPolygon(d.cell))
.attr("fill", d => d.fill)
.attr("data-zone", d => d.zoneId)
.attr("data-cell", d => d.cell);
}
function selectZoneOnMapClick() {
if (d3.event.target.parentElement.parentElement.id !== "zones") return;
const zone = d3.event.target.parentElement.id;
const el = body.querySelector("div[data-id='" + zone + "']");
selectZone(el);
if (d3.event.target.parentElement.id !== "zones") return;
const zoneId = d3.event.target.dataset.zone;
const el = body.querySelector("div[data-id='" + zoneId + "']");
body.querySelector("div.selected").classList.remove("selected");
el.classList.add("selected");
}
function dragZoneBrush() {
@ -219,43 +222,41 @@ function editZones() {
const eraseMode = byId("zonesRemove").classList.contains("pressed");
const landOnly = byId("zonesBrushLandOnly").checked;
const selected = body.querySelector("div.selected");
const zone = zones.select("#" + selected.dataset.id);
const base = zone.attr("id") + "_"; // id generic part
d3.event.on("drag", () => {
if (!d3.event.dx && !d3.event.dy) return;
const [x, y] = d3.mouse(this);
moveCircle(x, y, radius);
let selection = radius > 5 ? findAll(x, y, radius) : [findCell(x, y, radius)];
let selection = radius > 5 ? findAll(x, y, radius) : [findCell(x, y)];
if (landOnly) selection = selection.filter(i => pack.cells.h[i] >= 20);
if (!selection) return;
if (!selection.length) return;
const dataCells = zone.attr("data-cells");
let cells = dataCells ? dataCells.split(",").map(i => +i) : [];
const zoneId = +body.querySelector("div.selected")?.dataset.id;
const zone = pack.zones.find(z => z.i === zoneId);
if (!zone) return;
if (eraseMode) {
// remove
selection.forEach(i => {
const index = cells.indexOf(i);
if (index === -1) return;
zone.select("polygon#" + base + i).remove();
cells.splice(index, 1);
});
const data = zones
.selectAll("polygon")
.data()
.filter(d => !(d.zoneId === zoneId && selection.includes(d.cell)));
zones
.selectAll("polygon")
.data(data, d => `${d.zoneId}-${d.cell}`)
.exit()
.remove();
} else {
// add
selection.forEach(i => {
if (cells.includes(i)) return;
cells.push(i);
zone
.append("polygon")
.attr("points", getPackPolygon(i))
.attr("id", base + i);
});
const data = selection.map(cell => ({cell, zoneId, fill: zone.color}));
zones
.selectAll("polygon")
.data(data, d => `${d.zoneId}-${d.cell}`)
.enter()
.append("polygon")
.attr("points", d => getPackPolygon(d.cell))
.attr("fill", d => d.fill)
.attr("data-zone", d => d.zoneId)
.attr("data-cell", d => d.cell);
}
zone.attr("data-cells", cells);
});
}
@ -263,39 +264,29 @@ function editZones() {
showMainTip();
const point = d3.mouse(this);
const radius = +zonesBrush.value;
moveCircle(point[0], point[1], radius);
moveCircle(...point, radius);
}
function applyZonesManualAssignent() {
zones.selectAll("g").each(function () {
if (this.dataset.cells) return;
// all zone cells are removed
unfog("focusZone" + this.id);
this.style.display = "block";
});
const data = zones.selectAll("polygon").data();
const zoneCells = data.reduce((acc, d) => {
if (!acc[d.zoneId]) acc[d.zoneId] = [];
acc[d.zoneId].push(d.cell);
return acc;
}, {});
const filterBy = byId("zonesFilterType").value;
const isFiltered = filterBy && filterBy !== "all";
const visibleZones = pack.zones.filter(zone => !zone.hidden && (!isFiltered || zone.type === filterBy));
visibleZones.forEach(zone => (zone.cells = zoneCells[zone.i] || []));
drawZones();
zonesEditorAddLines();
exitZonesManualAssignment();
}
// restore initial zone cells
function cancelZonesManualAssignent() {
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);
});
drawZones();
exitZonesManualAssignment();
}
@ -313,60 +304,47 @@ function editZones() {
restoreDefaultEvents();
clearMainTip();
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");
function changeFill(fill, zone) {
const callback = newFill => {
el.fill = newFill;
byId(el.parentNode.dataset.id).setAttribute("fill", newFill);
zone.color = newFill;
drawZones();
zonesEditorAddLines();
};
openPicker(fill, callback);
}
function toggleVisibility(el) {
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 toggleVisibility(zone) {
const isHidden = Boolean(zone.hidden);
if (isHidden) delete zone.hidden;
else zone.hidden = true;
drawZones();
zonesEditorAddLines();
}
function toggleFog(z, cl) {
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;
cl.contains("inactive") ? fog(id, path) : unfog(id);
function toggleFog(zone, cl) {
const inactive = cl.contains("inactive");
cl.toggle("inactive");
if (inactive) {
const path = zones.select("#zone" + zone.i).attr("d");
fog("focusZone" + zone.i, path);
} else {
unfog("focusZone" + zone.i);
}
}
function toggleLegend() {
if (legend.selectAll("*").size()) {
clearLegend();
return;
} // hide legend
const data = [];
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]);
});
const filterBy = byId("zonesFilterType").value;
const isFiltered = filterBy && filterBy !== "all";
const visibleZones = pack.zones.filter(zone => !zone.hidden && (!isFiltered || zone.type === filterBy));
const data = visibleZones.map(({i, name, color}) => ["zone" + i, color, name]);
drawLegend("Zones", data);
}
@ -380,8 +358,7 @@ function editZones() {
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) + "%";
el.querySelector(".zonePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + "%";
});
} else {
body.dataset.type = "absolute";
@ -390,28 +367,23 @@ function editZones() {
}
function addZonesLayer() {
const id = getNextId("zone");
const description = "Unknown zone";
const zoneId = pack.zones.length ? Math.max(...pack.zones.map(z => z.i)) + 1 : 0;
const name = "Unknown zone";
const type = "Unknown";
const fill = "url(#hatch" + (id.slice(4) % 42) + ")";
zones
.append("g")
.attr("id", id)
.attr("data-description", description)
.attr("data-type", type)
.attr("data-cells", "")
.attr("fill", fill);
const color = "url(#hatch" + (zoneId % 42) + ")";
pack.zones.push({i: zoneId, name, type, color, cells: []});
zonesEditorAddLines();
drawZones();
}
function downloadZonesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
let data = "Id,Fill,Description,Type,Cells,Area " + unit + ",Population\n"; // headers
let data = "Id,Color,Description,Type,Cells,Area " + unit + ",Population\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) {
data += el.dataset.id + ",";
data += el.dataset.fill + ",";
data += el.dataset.color + ",";
data += el.dataset.description + ",";
data += el.dataset.type + ",";
data += el.dataset.cells + ",";
@ -423,27 +395,24 @@ function editZones() {
downloadFile(data, name);
}
function toggleEraseMode() {
this.classList.toggle("pressed");
function changeDescription(zone, value) {
zone.name = value;
zones.select("#zone" + zone.i).attr("data-description", value);
}
function changeType(zone, value) {
zone.type = value;
zones.select("#zone" + zone.i).attr("data-type", value);
}
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 burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
const landCells = zone.cells.filter(i => pack.cells.h[i] >= 20);
if (!landCells.length) return tip("Zone does not have any land cells, cannot change population", false, "error");
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate);
const burgs = pack.burgs.filter(b => !b.removed && landCells.includes(b.cell));
const rural = rn(d3.sum(landCells.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
d3.sum(landCells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization
);
const total = rural + urban;
const l = n => Number(n).toLocaleString();
@ -485,12 +454,12 @@ function editZones() {
function applyPopulationChange() {
const ruralChange = ruralPop.value / rural;
if (isFinite(ruralChange) && ruralChange !== 1) {
cells.forEach(i => (pack.cells.pop[i] *= ruralChange));
landCells.forEach(i => (pack.cells.pop[i] *= ruralChange));
}
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
const points = ruralPop.value / populationRate;
const pop = rn(points / cells.length);
cells.forEach(i => (pack.cells.pop[i] = pop));
const pop = rn(points / landCells.length);
landCells.forEach(i => (pack.cells.pop[i] = pop));
}
const urbanChange = urbanPop.value / urban;
@ -508,8 +477,16 @@ function editZones() {
}
function zoneRemove(zone) {
zones.select("#" + zone).remove();
unfog("focusZone" + zone);
zonesEditorAddLines();
confirmationDialog({
title: "Remove zone",
message: "Are you sure you want to remove the zone? <br>This action cannot be reverted",
confirm: "Remove",
onConfirm: () => {
pack.zones = pack.zones.filter(z => z.i !== zone.i);
zones.select("#zone" + zone.i).remove();
unfog("focusZone" + zone.i);
zonesEditorAddLines();
}
});
}
}