mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
366 lines
16 KiB
JavaScript
366 lines
16 KiB
JavaScript
"use strict";
|
|
function editZones() {
|
|
closeDialogs();
|
|
if (!layerIsOn("toggleZones")) toggleZones();
|
|
const body = document.getElementById("zonesBodySection");
|
|
zonesEditorAddLines();
|
|
|
|
if (modules.editZones) return;
|
|
modules.editZones = true;
|
|
|
|
$("#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"}
|
|
});
|
|
|
|
// add listeners
|
|
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
|
|
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
|
|
document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
|
|
document.getElementById("zonesPercentage").addEventListener("click", togglePercentageMode);
|
|
document.getElementById("zonesManually").addEventListener("click", enterZonesManualAssignent);
|
|
document.getElementById("zonesManuallyApply").addEventListener("click", applyZonesManualAssignent);
|
|
document.getElementById("zonesManuallyCancel").addEventListener("click", cancelZonesManualAssignent);
|
|
document.getElementById("zonesAdd").addEventListener("click", addZonesLayer);
|
|
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("icon-trash-empty")) {zoneRemove(zone); return;}
|
|
if (cl.contains("icon-eye")) {toggleVisibility(el); return;}
|
|
if (cl.contains("icon-pin")) {focusOnZone(zone, cl); return;}
|
|
if (cl.contains("zoneFill")) {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);
|
|
});
|
|
|
|
// add line for each zone
|
|
function zonesEditorAddLines() {
|
|
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
|
let lines = "";
|
|
|
|
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 population = rural + urban;
|
|
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}`;
|
|
const inactive = this.style.display === "none";
|
|
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="zoneFill"></svg>
|
|
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
|
|
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
|
<div data-tip="Cells count" class="stateCells hide">${c.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>
|
|
<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="Remove zone" class="icon-trash-empty hide"></span>
|
|
</div>`;
|
|
});
|
|
|
|
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;
|
|
zonesFooterPopulation.dataset.population = totalPop;
|
|
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
|
zonesFooterCells.innerHTML = pack.cells.i.length;
|
|
zonesFooterArea.innerHTML = si(totalArea) + unit;
|
|
zonesFooterPopulation.innerHTML = si(totalPop);
|
|
|
|
// add listeners
|
|
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();}
|
|
$("#zonesEditor").dialog({width: fitContent()});
|
|
}
|
|
|
|
function zoneHighlightOn(event) {
|
|
const zone = event.target.dataset.id;
|
|
zones.select("#"+zone).style("outline", "1px solid red");
|
|
}
|
|
|
|
function zoneHighlightOff(event) {
|
|
const zone = event.target.dataset.id;
|
|
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"));
|
|
if (next) zone.insertBefore(next);
|
|
}
|
|
|
|
function enterZonesManualAssignent() {
|
|
if (!layerIsOn("toggleZones")) toggleZones();
|
|
customization = 10;
|
|
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");
|
|
$("#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);
|
|
|
|
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");
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
function dragZoneBrush() {
|
|
const r = +zonesBrush.value;
|
|
|
|
d3.event.on("drag", () => {
|
|
if (!d3.event.dx && !d3.event.dy) return;
|
|
const p = d3.mouse(this);
|
|
moveCircle(p[0], p[1], r);
|
|
|
|
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 base = zone.attr("id") + "_"; // id generic part
|
|
const dataCells = zone.attr("data-cells");
|
|
let cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
|
|
|
const erase = document.getElementById("zonesRemove").classList.contains("pressed");
|
|
if (erase) {
|
|
// remove
|
|
selection.forEach(i => {
|
|
const index = cells.indexOf(i);
|
|
if (index === -1) return;
|
|
zone.select("polygon#" + base + i).remove();
|
|
cells.splice(index, 1);
|
|
});
|
|
} else {
|
|
// add
|
|
selection.forEach(i => {
|
|
if (cells.includes(i)) return;
|
|
cells.push(i);
|
|
zone.append("polygon").attr("points", getPackPolygon(i)).attr("id", base + i);
|
|
});
|
|
}
|
|
|
|
zone.attr("data-cells", cells);
|
|
});
|
|
}
|
|
|
|
function moveZoneBrush() {
|
|
showMainTip();
|
|
const point = d3.mouse(this);
|
|
const radius = +zonesBrush.value;
|
|
moveCircle(point[0], point[1], radius);
|
|
}
|
|
|
|
function applyZonesManualAssignent() {
|
|
zones.selectAll("g").each(function() {
|
|
if (this.dataset.cells) return;
|
|
// all zone cells are removed
|
|
unfocus(this.id);
|
|
this.style.display = "block";
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
exitZonesManualAssignment();
|
|
}
|
|
|
|
function exitZonesManualAssignment(close) {
|
|
customization = 0;
|
|
removeCircle();
|
|
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"}});
|
|
|
|
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");
|
|
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 inactive = zone.style("display") === "none";
|
|
inactive ? zone.style("display", "block") : zone.style("display", "none");
|
|
el.classList.toggle("inactive");
|
|
}
|
|
|
|
function focusOnZone(zone, cl) {
|
|
const inactive = cl.contains("inactive");
|
|
cl.toggle("inactive");
|
|
|
|
if (inactive) {
|
|
if (defs.select("#fog #focus"+zone).size()) return;
|
|
const dataCells = zones.select("#"+zone).attr("data-cells");
|
|
if (!dataCells) return;
|
|
const data = dataCells.split(",").map(c => +c);
|
|
const g = defs.select("#fog").append("g").attr("fill", "black").attr("stroke", "black").attr("id", "focus"+zone);
|
|
g.selectAll("path").data(data).enter().append("path").attr("d", d => "M" + getPackPolygon(d) + "Z");
|
|
fogging.attr("display", "block");
|
|
} else unfocus(zone);
|
|
}
|
|
|
|
function unfocus(z) {
|
|
defs.select("#focus"+z).remove();
|
|
if (!defs.selectAll("#fog path").size()) fogging.attr("display", "none"); // all states are de-focused
|
|
}
|
|
|
|
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])
|
|
});
|
|
|
|
drawLegend("Zones", data);
|
|
}
|
|
|
|
function togglePercentageMode() {
|
|
if (body.dataset.type === "absolute") {
|
|
body.dataset.type = "percentage";
|
|
const totalCells = +zonesFooterCells.innerHTML;
|
|
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) + "%";
|
|
});
|
|
|
|
} else {
|
|
body.dataset.type = "absolute";
|
|
zonesEditorAddLines();
|
|
}
|
|
}
|
|
|
|
function addZonesLayer() {
|
|
const id = getNextId("zone");
|
|
const description = "Unknown zone";
|
|
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;
|
|
|
|
const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0>
|
|
<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="zoneFill"></svg>
|
|
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
|
|
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
|
<div data-tip="Cells count" class="stateCells hide">0</div>
|
|
<span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span>
|
|
<div data-tip="Zone area" class="biomeArea hide">0 ${unit}</div>
|
|
<span class="icon-male hide"></span>
|
|
<div class="culturePopulation hide">0</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 inactive hide placeholder"></span>
|
|
<span data-tip="Toggle zone visibility" class="icon-eye hide placeholder"></span>
|
|
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
|
</div>`;
|
|
|
|
body.insertAdjacentHTML("beforeend", line);
|
|
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
|
}
|
|
|
|
function downloadZonesData() {
|
|
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
|
let data = "Id,Fill,Description,Cells,Area "+unit+",Population\n"; // headers
|
|
|
|
body.querySelectorAll(":scope > div").forEach(function(el) {
|
|
data += el.dataset.id + ",";
|
|
data += el.dataset.fill + ",";
|
|
data += el.dataset.description + ",";
|
|
data += el.dataset.cells + ",";
|
|
data += el.dataset.area + ",";
|
|
data += el.dataset.population + "\n";
|
|
});
|
|
|
|
const dataBlob = new Blob([data], {type: "text/plain"});
|
|
const url = window.URL.createObjectURL(dataBlob);
|
|
const link = document.createElement("a");
|
|
document.body.appendChild(link);
|
|
link.download = getFileName("Zones") + ".csv";
|
|
link.href = url;
|
|
link.click();
|
|
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
|
|
}
|
|
|
|
function toggleEraseMode() {
|
|
this.classList.toggle("pressed");
|
|
}
|
|
|
|
function zoneRemove(zone) {
|
|
zones.select("#"+zone).remove();
|
|
unfocus(zone);
|
|
zonesEditorAddLines();
|
|
}
|
|
|
|
}
|