mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-23 20:41:23 +01:00
Merge branch 'upstream' into dev-submaps
This commit is contained in:
commit
7e8f77d0b3
76 changed files with 5756 additions and 1688 deletions
|
|
@ -141,8 +141,8 @@ class Battle {
|
|||
const state = pack.states[regiment.state];
|
||||
const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base
|
||||
const color = state.color[0] === "#" ? state.color : "#999";
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em; stroke: #333">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>
|
||||
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
|
||||
const body = `<tbody id="battle${state.i}-${regiment.i}">`;
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ class Battle {
|
|||
dist = added ? "0 " + distanceUnitInput.value : distance(r);
|
||||
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
|
||||
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" ></svg>
|
||||
<div style="width:6em">${s.name.slice(0, 11)}</div>
|
||||
<div style="width:1.2em">${r.icon}</div>
|
||||
<div style="width:13em">${r.name.slice(0, 24)}</div>
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ function editBiomes() {
|
|||
document.getElementById("biomesExport").addEventListener("click", downloadBiomesData);
|
||||
|
||||
body.addEventListener("click", function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList;
|
||||
if (cl.contains("fillRect")) biomeChangeColor(el);
|
||||
const el = ev.target;
|
||||
const cl = el.classList;
|
||||
if (el.tagName === "FILL-BOX") biomeChangeColor(el);
|
||||
else if (cl.contains("icon-info-circled")) openWiki(el);
|
||||
else if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
|
||||
if (customization === 6) selectBiomeOnLineClick(el);
|
||||
|
|
@ -94,9 +94,7 @@ function editBiomes() {
|
|||
|
||||
lines += `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability="${b.habitability[i]}"
|
||||
data-cells=${b.cells[i]} data-area=${area} data-population=${population} data-color=${b.color[i]}>
|
||||
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
b.color[i]
|
||||
}" class="fillRect pointer"></svg>
|
||||
<fill-box fill="${b.color[i]}"></fill-box>
|
||||
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Biome habitability percent" class="hide">%</span>
|
||||
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${
|
||||
|
|
@ -158,15 +156,15 @@ function editBiomes() {
|
|||
|
||||
function biomeChangeColor(el) {
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const biome = +el.parentNode.parentNode.dataset.id;
|
||||
const biome = +el.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
biomesData.color[biome] = fill;
|
||||
const callback = newFill => {
|
||||
el.fill = newFill;
|
||||
biomesData.color[biome] = newFill;
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.attr("fill", fill)
|
||||
.attr("stroke", fill);
|
||||
.attr("fill", newFill)
|
||||
.attr("stroke", newFill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
|
|
@ -270,7 +268,7 @@ function editBiomes() {
|
|||
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const line = `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability=${b.habitability[i]} data-cells=0 data-area=0 data-population=0 data-color=${b.color[i]}>
|
||||
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="fillRect pointer"></svg>
|
||||
<fill-box fill="${b.color[i]}"></fill-box>
|
||||
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Biome habitability percent" class="hide">%</span>
|
||||
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 step=1 class="biomeHabitability hide" value=${b.habitability[i]}>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ function editBurg(id) {
|
|||
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
|
||||
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
|
||||
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
|
||||
document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
|
||||
|
||||
function updateBurgValues() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
|
|
@ -543,6 +544,11 @@ function editBurg(id) {
|
|||
editNotes("burg" + id, name);
|
||||
}
|
||||
|
||||
function showTemperatureGraph() {
|
||||
const id = elSelected.attr("data-id");
|
||||
showBurgTemperatureGraph(id);
|
||||
}
|
||||
|
||||
function removeSelectedBurg() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
if (pack.burgs[id].capital) {
|
||||
|
|
|
|||
|
|
@ -108,9 +108,7 @@ function editCultures() {
|
|||
|
||||
lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
|
||||
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${c.color}" class="fillRect pointer">
|
||||
</svg>
|
||||
<fill-box fill="${c.color}"></fill-box>
|
||||
<input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
|
|
@ -148,7 +146,7 @@ function editCultures() {
|
|||
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseenter", ev => cultureHighlightOn(ev)));
|
||||
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseleave", ev => cultureHighlightOff(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectCultureOnLineClick));
|
||||
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", cultureChangeColor));
|
||||
body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", cultureChangeColor));
|
||||
body.querySelectorAll("div > input.cultureName").forEach(el => el.addEventListener("input", cultureChangeName));
|
||||
body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName));
|
||||
body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism));
|
||||
|
|
@ -248,16 +246,16 @@ function editCultures() {
|
|||
function cultureChangeColor() {
|
||||
const el = this;
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const culture = +el.parentNode.parentNode.dataset.id;
|
||||
const culture = +el.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
pack.cultures[culture].color = fill;
|
||||
const callback = newFill => {
|
||||
el.fill = newFill;
|
||||
pack.cultures[culture].color = newFill;
|
||||
cults
|
||||
.select("#culture" + culture)
|
||||
.attr("fill", fill)
|
||||
.attr("stroke", fill);
|
||||
debug.select("#cultureCenter" + culture).attr("fill", fill);
|
||||
.attr("fill", newFill)
|
||||
.attr("stroke", newFill);
|
||||
debug.select("#cultureCenter" + culture).attr("fill", newFill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
|
|
|
|||
|
|
@ -101,10 +101,8 @@ function editDiplomacy() {
|
|||
lines += `<div class="states" data-id=${state.i} data-name="${name}" data-relations="${relation}">
|
||||
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${state.i}"></use></svg>
|
||||
<div data-tip="${tipSelect}" style="width: 12em">${name}</div>
|
||||
<div data-tip="${tipChange}" class="changeRelations pointer" style="width: 6em">
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
|
||||
</svg>
|
||||
<div data-tip="${tipChange}" class="changeRelations" style="width: 6em">
|
||||
<fill-box fill="${color}" size=".9em"></fill-box>
|
||||
${relation}
|
||||
</div>
|
||||
</div>`;
|
||||
|
|
@ -195,9 +193,7 @@ function editDiplomacy() {
|
|||
([relation, {color, inText, tip}]) =>
|
||||
`<div style="margin-block: 0.2em" data-tip="${tip}"><label class="pointer">
|
||||
<input type="radio" name="relationSelect" value="${relation}" ${currentRelation === relation && "checked"} >
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect" />
|
||||
</svg>
|
||||
<fill-box fill="${color}" size=".8em"></fill-box>
|
||||
${inText}
|
||||
</label></div>`
|
||||
)
|
||||
|
|
|
|||
|
|
@ -425,10 +425,10 @@ function clearLegend() {
|
|||
function createPicker() {
|
||||
const pos = () => tip("Drag to change the picker position");
|
||||
const cl = () => tip("Click to close the picker");
|
||||
const closePicker = () => contaiter.style("display", "none");
|
||||
const closePicker = () => container.style("display", "none");
|
||||
|
||||
const contaiter = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
|
||||
contaiter
|
||||
const container = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
|
||||
container
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
|
|
@ -437,7 +437,7 @@ function createPicker() {
|
|||
.attr("opacity", 0.2)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
const picker = contaiter
|
||||
const picker = container
|
||||
.append("g")
|
||||
.attr("id", "picker")
|
||||
.call(
|
||||
|
|
@ -494,7 +494,7 @@ function createPicker() {
|
|||
|
||||
const colors = picker.append("g").attr("id", "pickerColors").attr("stroke", "#333333");
|
||||
const hatches = picker.append("g").attr("id", "pickerHatches").attr("stroke", "#333333");
|
||||
const hatching = d3.selectAll("g#hatching > pattern");
|
||||
const hatching = d3.selectAll("g#defs-hatching > pattern");
|
||||
const number = hatching.size();
|
||||
|
||||
const clr = d3.range(number).map(i => d3.hsl((i / number) * 360, 0.7, 0.7).hex());
|
||||
|
|
@ -504,8 +504,8 @@ function createPicker() {
|
|||
.attr("id", "picker_" + d)
|
||||
.attr("fill", d)
|
||||
.attr("class", i ? "" : "selected")
|
||||
.attr("x", i * 22 + 4)
|
||||
.attr("y", 40)
|
||||
.attr("x", (i % 14) * 22 + 4)
|
||||
.attr("y", 40 + Math.floor(i / 14)*20)
|
||||
.attr("width", 16)
|
||||
.attr("height", 16);
|
||||
});
|
||||
|
|
@ -515,20 +515,20 @@ function createPicker() {
|
|||
.append("rect")
|
||||
.attr("id", "picker_" + this.id)
|
||||
.attr("fill", "url(#" + this.id + ")")
|
||||
.attr("x", i * 22 + 4)
|
||||
.attr("y", 61)
|
||||
.attr("x", (i % 14) * 22 + 4)
|
||||
.attr("y", Math.floor(i / 14)*20 + 20 + (number * 2))
|
||||
.attr("width", 16)
|
||||
.attr("height", 16);
|
||||
.attr("height", 16)
|
||||
});
|
||||
|
||||
colors
|
||||
.selectAll("rect")
|
||||
.on("click", pickerFillClicked)
|
||||
.on("mousemove", () => tip("Click to fill with the color"));
|
||||
.on("mouseover", () => tip("Click to fill with the color"));
|
||||
hatches
|
||||
.selectAll("rect")
|
||||
.on("click", pickerFillClicked)
|
||||
.on("mousemove", () => tip("Click to fill with the hatching"));
|
||||
.on("mouseover", function() { tip("Click to fill with the hatching " + this.id) });
|
||||
|
||||
// append box
|
||||
const bbox = picker.node().getBBox();
|
||||
|
|
@ -544,10 +544,10 @@ function createPicker() {
|
|||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#5d4651")
|
||||
.on("mousemove", pos);
|
||||
picker.insert("text", ":first-child").attr("x", 291).attr("y", -10).attr("id", "pickerCloseText").text("✕");
|
||||
picker.insert("text", ":first-child").attr("x", width-20).attr("y", -10).attr("id", "pickerCloseText").text("✕");
|
||||
picker
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", 288)
|
||||
.attr("x", width-23)
|
||||
.attr("y", -21)
|
||||
.attr("id", "pickerCloseRect")
|
||||
.attr("width", 14)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ function mouseMove() {
|
|||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]); // pack cell id
|
||||
if (i === undefined) return;
|
||||
showNotes(d3.event, i);
|
||||
|
||||
showNotes(d3.event);
|
||||
const g = findGridCell(point[0], point[1]); // grid cell id
|
||||
if (tooltip.dataset.main) showMainTip();
|
||||
else showMapTooltip(point, d3.event, i, g);
|
||||
|
|
@ -78,7 +79,7 @@ function mouseMove() {
|
|||
}
|
||||
|
||||
// show note box on hover (if any)
|
||||
function showNotes(e, i) {
|
||||
function showNotes(e) {
|
||||
if (notesEditor.offsetParent) return;
|
||||
let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id;
|
||||
if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id;
|
||||
|
|
|
|||
|
|
@ -913,11 +913,9 @@ function editHeightmap() {
|
|||
|
||||
function uploadTemplate(dataLoaded) {
|
||||
const steps = dataLoaded.split("\r\n");
|
||||
if (!steps.length) {
|
||||
tip("Cannot parse the template, please check the file", false, "error");
|
||||
return;
|
||||
}
|
||||
if (!steps.length) return tip("Cannot parse the template, please check the file", false, "error");
|
||||
templateBody.innerHTML = "";
|
||||
|
||||
for (const s of steps) {
|
||||
const step = s.split(" ");
|
||||
if (step.length !== 5) {
|
||||
|
|
@ -1318,10 +1316,10 @@ function editHeightmap() {
|
|||
img.onload = function () {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = svgWidth;
|
||||
canvas.height = svgHeight;
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
document.body.insertBefore(canvas, optionsContainer);
|
||||
ctx.drawImage(img, 0, 0, svgWidth, svgHeight);
|
||||
ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
|
||||
const imgBig = canvas.toDataURL("image/png");
|
||||
const link = document.createElement("a");
|
||||
link.download = getFileName("Heightmap") + ".png";
|
||||
|
|
|
|||
|
|
@ -536,12 +536,11 @@ class Planimeter extends Measurer {
|
|||
}
|
||||
|
||||
// Scale bar
|
||||
function drawScaleBar(requestedScale) {
|
||||
function drawScaleBar(scaleLevel) {
|
||||
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
|
||||
scaleBar.selectAll("*").remove(); // fully redraw every time
|
||||
const scaleLevel = requestedScale || scale;
|
||||
|
||||
const distanceScale = distanceScaleInput.value;
|
||||
const distanceScale = +distanceScaleInput.value;
|
||||
const unit = distanceUnitInput.value;
|
||||
const size = +barSizeInput.value;
|
||||
|
||||
|
|
|
|||
|
|
@ -75,12 +75,9 @@ function overviewMilitary() {
|
|||
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
|
||||
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-state="${
|
||||
s.name
|
||||
}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
s.color
|
||||
}" class="fillRect"></svg>
|
||||
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}"
|
||||
data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
|
||||
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
|
||||
${lineData}
|
||||
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
"use strict";
|
||||
|
||||
function editNotes(id, name) {
|
||||
// elements
|
||||
const notesLegend = document.getElementById("notesLegend");
|
||||
const notesName = document.getElementById("notesName");
|
||||
const notesSelect = document.getElementById("notesSelect");
|
||||
const notesPin = document.getElementById("notesPin");
|
||||
|
||||
// update list of objects
|
||||
const select = document.getElementById("notesSelect");
|
||||
select.options.length = 0;
|
||||
notesSelect.options.length = 0;
|
||||
for (const note of notes) {
|
||||
select.options.add(new Option(note.id, note.id));
|
||||
notesSelect.options.add(new Option(note.id, note.id));
|
||||
}
|
||||
|
||||
// initiate pell (html editor)
|
||||
const notesText = document.getElementById("notesText");
|
||||
notesText.innerHTML = "";
|
||||
const editor = Pell.init({
|
||||
element: notesText,
|
||||
onChange: html => {
|
||||
const note = notes.find(note => note.id === select.value);
|
||||
if (!note) return;
|
||||
note.legend = html;
|
||||
showNote(note);
|
||||
}
|
||||
});
|
||||
// update pin notes icon
|
||||
const notesArePinned = options.pinNotes;
|
||||
if (notesArePinned) notesPin.classList.add("pressed");
|
||||
else notesPin.classList.remove("pressed");
|
||||
|
||||
// select an object
|
||||
if (notes.length || id) {
|
||||
|
|
@ -29,136 +26,161 @@ function editNotes(id, name) {
|
|||
if (!name) name = id;
|
||||
note = {id, name, legend: ""};
|
||||
notes.push(note);
|
||||
select.options.add(new Option(id, id));
|
||||
notesSelect.options.add(new Option(id, id));
|
||||
}
|
||||
select.value = id;
|
||||
|
||||
notesSelect.value = id;
|
||||
notesName.value = note.name;
|
||||
editor.content.innerHTML = note.legend;
|
||||
showNote(note);
|
||||
notesLegend.innerHTML = note.legend;
|
||||
initEditor();
|
||||
updateNotesBox(note);
|
||||
} else {
|
||||
editor.content.innerHTML = "There are no added notes. Click on element (e.g. label) and add a free text note";
|
||||
document.getElementById("notesName").value = "";
|
||||
// if notes array is empty
|
||||
notesName.value = "";
|
||||
notesLegend.innerHTML = "No notes added. Click on an element (e.g. label or marker) and add a free text note";
|
||||
}
|
||||
|
||||
// open a dialog
|
||||
$("#notesEditor").dialog({
|
||||
title: "Notes Editor",
|
||||
minWidth: "40em",
|
||||
width: "50vw",
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
width: "70vw",
|
||||
height: window.innerHeight * 0.75,
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
close: removeEditor
|
||||
});
|
||||
$("[aria-describedby='notesEditor']").css("top", "10vh");
|
||||
|
||||
if (modules.editNotes) return;
|
||||
modules.editNotes = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("notesSelect").addEventListener("change", changeObject);
|
||||
document.getElementById("notesSelect").addEventListener("change", changeElement);
|
||||
document.getElementById("notesName").addEventListener("input", changeName);
|
||||
document.getElementById("notesPin").addEventListener("click", () => (options.pinNotes = !options.pinNotes));
|
||||
document.getElementById("notesSpeak").addEventListener("click", () => speak(editor.content.innerHTML));
|
||||
document.getElementById("notesLegend").addEventListener("blur", updateLegend);
|
||||
document.getElementById("notesPin").addEventListener("click", toggleNotesPin);
|
||||
document.getElementById("notesFocus").addEventListener("click", validateHighlightElement);
|
||||
document.getElementById("notesDownload").addEventListener("click", downloadLegends);
|
||||
document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click());
|
||||
document.getElementById("legendsToLoad").addEventListener("change", function () {
|
||||
uploadFile(this, uploadLegends);
|
||||
});
|
||||
document.getElementById("notesClearStyle").addEventListener("click", clearStyle);
|
||||
document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove);
|
||||
|
||||
function showNote(note) {
|
||||
document.getElementById("notes").style.display = "block";
|
||||
async function initEditor() {
|
||||
if (!window.tinymce) {
|
||||
const url = "https://cdn.tiny.cloud/1/4i6a79ymt2y0cagke174jp3meoi28vyecrch12e5puyw3p9a/tinymce/5/tinymce.min.js";
|
||||
try {
|
||||
await import(url);
|
||||
} catch (error) {
|
||||
// error may be caused by failed request being cached, try again with random hash
|
||||
try {
|
||||
const hash = Math.random().toString(36).substring(2, 15);
|
||||
await import(`${url}#${hash}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (window.tinymce) {
|
||||
tinymce.init({
|
||||
selector: "#notesLegend",
|
||||
height: "90%",
|
||||
menubar: false,
|
||||
plugins: `autolink lists link charmap print formatpainter casechange code fullscreen image link media table paste hr checklist wordcount`,
|
||||
toolbar: `code | undo redo | bold italic strikethrough | forecolor backcolor | formatpainter removeformat | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media table | fontselect fontsizeselect | blockquote hr casechange checklist charmap | print fullscreen`,
|
||||
media_alt_source: false,
|
||||
media_poster: false,
|
||||
setup: editor => {
|
||||
editor.on("Change", updateLegend);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateLegend() {
|
||||
const note = notes.find(note => note.id === notesSelect.value);
|
||||
if (!note) return tip("Note element is not found", true, "error", 4000);
|
||||
|
||||
const isTinyEditorActive = window.tinymce?.activeEditor;
|
||||
note.legend = isTinyEditorActive ? tinymce.activeEditor.getContent() : notesLegend.innerHTML;
|
||||
updateNotesBox(note);
|
||||
}
|
||||
|
||||
function updateNotesBox(note) {
|
||||
document.getElementById("notesHeader").innerHTML = note.name;
|
||||
document.getElementById("notesBody").innerHTML = note.legend;
|
||||
}
|
||||
|
||||
function changeObject() {
|
||||
function changeElement() {
|
||||
const note = notes.find(note => note.id === this.value);
|
||||
if (!note) return;
|
||||
if (!note) return tip("Note element is not found", true, "error", 4000);
|
||||
|
||||
notesName.value = note.name;
|
||||
editor.content.innerHTML = note.legend;
|
||||
notesLegend.innerHTML = note.legend;
|
||||
updateNotesBox(note);
|
||||
|
||||
if (window.tinymce) tinymce.activeEditor.setContent(note.legend);
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
const id = document.getElementById("notesSelect").value;
|
||||
const note = notes.find(note => note.id === id);
|
||||
if (!note) return;
|
||||
const note = notes.find(note => note.id === notesSelect.value);
|
||||
if (!note) return tip("Note element is not found", true, "error", 4000);
|
||||
|
||||
note.name = this.value;
|
||||
showNote(note);
|
||||
}
|
||||
|
||||
function validateHighlightElement() {
|
||||
const select = document.getElementById("notesSelect");
|
||||
const element = document.getElementById(select.value);
|
||||
const element = document.getElementById(notesSelect.value);
|
||||
if (element) return highlightElement(element, 3);
|
||||
|
||||
// if element is not found
|
||||
if (element === null) {
|
||||
alertMessage.innerHTML = "Related element is not found. Would you like to remove the note?";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Element not found",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
removeLegend();
|
||||
},
|
||||
Keep: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
highlightElement(element, 3); // if element is found
|
||||
confirmationDialog({
|
||||
title: "Element not found",
|
||||
message: "Note element is not found. Would you like to remove the note?",
|
||||
confirm: "Remove",
|
||||
cancel: "Keep",
|
||||
onConfirm: removeLegend
|
||||
});
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
const data = JSON.stringify(notes);
|
||||
const notesData = JSON.stringify(notes);
|
||||
const name = getFileName("Notes") + ".txt";
|
||||
downloadFile(data, name);
|
||||
downloadFile(notesData, name);
|
||||
}
|
||||
|
||||
function uploadLegends(dataLoaded) {
|
||||
if (!dataLoaded) {
|
||||
tip("Cannot load the file. Please check the data format", false, "error");
|
||||
return;
|
||||
}
|
||||
if (!dataLoaded) return tip("Cannot load the file. Please check the data format", false, "error");
|
||||
notes = JSON.parse(dataLoaded);
|
||||
document.getElementById("notesSelect").options.length = 0;
|
||||
notesSelect.options.length = 0;
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
||||
function clearStyle() {
|
||||
editor.content.innerHTML = editor.content.textContent;
|
||||
}
|
||||
|
||||
function triggerNotesRemove() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the selected note?";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
confirmationDialog({
|
||||
title: "Remove note",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
removeLegend();
|
||||
},
|
||||
Keep: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
message: "Are you sure you want to remove the selected note? There is no way to undo this action",
|
||||
confirm: "Remove",
|
||||
onConfirm: removeLegend
|
||||
});
|
||||
}
|
||||
|
||||
function removeLegend() {
|
||||
const select = document.getElementById("notesSelect");
|
||||
const index = notes.findIndex(n => n.id === select.value);
|
||||
const index = notes.findIndex(n => n.id === notesSelect.value);
|
||||
notes.splice(index, 1);
|
||||
select.options.length = 0;
|
||||
notesSelect.options.length = 0;
|
||||
if (!notes.length) {
|
||||
$("#notesEditor").dialog("close");
|
||||
return;
|
||||
}
|
||||
notesText.innerHTML = "";
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
||||
function toggleNotesPin() {
|
||||
options.pinNotes = !options.pinNotes;
|
||||
this.classList.toggle("pressed");
|
||||
}
|
||||
|
||||
function removeEditor() {
|
||||
if (window.tinymce) tinymce.remove();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@ function showSupporters() {
|
|||
Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky,
|
||||
Dario Spadavecchia,Bas Kroot,John Patrick Callahan Jr,Alexandra Vesey,D,Exp1nt,james,Braxton Istace,w,Rurikid,AntiBlock,Redsauz,BigE0021,
|
||||
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger,Joshua Xiong,Moist mongol,
|
||||
Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut`;
|
||||
Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce,
|
||||
Jordan,William Markus,Sidr Dim,Alexander Whittaker,The Next Level,Patrick Valverde,Markus Peham,Daniel Cooper,the Beagles of Neorbus,Marley Moule,
|
||||
Maximilian Schielke,Johnathan Xavier Hutchinson,Ele,Rita`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, "")
|
||||
|
|
@ -304,10 +306,8 @@ function showSeedHistoryDialog() {
|
|||
|
||||
// generate map with historical seed
|
||||
function restoreSeed(id) {
|
||||
if (mapHistory[id].seed == seed) {
|
||||
tip("The current map is already generated with this seed", null, "error");
|
||||
return;
|
||||
}
|
||||
if (mapHistory[id].seed == seed) return tip("The current map is already generated with this seed", null, "error");
|
||||
|
||||
optionsSeed.value = mapHistory[id].seed;
|
||||
mapWidthInput.value = mapHistory[id].width;
|
||||
mapHeightInput.value = mapHistory[id].height;
|
||||
|
|
@ -545,7 +545,7 @@ function randomizeOptions() {
|
|||
|
||||
// 'Options' settings
|
||||
if (randomize || !locked("template")) randomizeHeightmapTemplate();
|
||||
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(15, 3, 2, 30);
|
||||
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(18, 5, 2, 30);
|
||||
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100);
|
||||
if (randomize || !locked("manors")) {
|
||||
manorsInput.value = 1000;
|
||||
|
|
@ -736,24 +736,43 @@ async function showLoadPane() {
|
|||
}
|
||||
});
|
||||
|
||||
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
|
||||
const fileSelect = document.getElementById("loadFromDropboxSelect");
|
||||
const files = await Cloud.providers.dropbox.list();
|
||||
// already connected to Dropbox: list saved maps
|
||||
if (Cloud.providers.dropbox.api) {
|
||||
document.getElementById("dropboxConnectButton").style.display = "none";
|
||||
document.getElementById("loadFromDropboxSelect").style.display = "block";
|
||||
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
|
||||
const fileSelect = document.getElementById("loadFromDropboxSelect");
|
||||
fileSelect.innerHTML = `<option value="" disabled selected>Loading...</option>`;
|
||||
|
||||
const files = await Cloud.providers.dropbox.list();
|
||||
|
||||
if (!files) {
|
||||
loadFromDropboxButtons.style.display = "none";
|
||||
fileSelect.innerHTML = `<option value="" disabled selected>Save files to Dropbox first</option>`;
|
||||
return;
|
||||
}
|
||||
|
||||
loadFromDropboxButtons.style.display = "block";
|
||||
fileSelect.innerHTML = "";
|
||||
files.forEach(file => {
|
||||
const opt = document.createElement("option");
|
||||
opt.innerText = file.name;
|
||||
opt.value = file.path;
|
||||
fileSelect.appendChild(opt);
|
||||
});
|
||||
|
||||
if (!files) {
|
||||
loadFromDropboxButtons.style.display = "none";
|
||||
fileSelect.innerHTML = `<option value="" disabled selected>Save files to Dropbox first</option>`;
|
||||
return;
|
||||
}
|
||||
|
||||
loadFromDropboxButtons.style.display = "block";
|
||||
fileSelect.innerHTML = "";
|
||||
files.forEach(file => {
|
||||
const opt = document.createElement("option");
|
||||
opt.innerText = file.name;
|
||||
opt.value = file.path;
|
||||
fileSelect.appendChild(opt);
|
||||
});
|
||||
// not connected to Dropbox: show connect button
|
||||
document.getElementById("dropboxConnectButton").style.display = "inline-block";
|
||||
document.getElementById("loadFromDropboxButtons").style.display = "none";
|
||||
document.getElementById("loadFromDropboxSelect").style.display = "none";
|
||||
}
|
||||
|
||||
async function connectToDropbox() {
|
||||
await Cloud.providers.dropbox.initialize();
|
||||
if (Cloud.providers.dropbox.api) showLoadPane();
|
||||
}
|
||||
|
||||
function loadURL() {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ function editProvinces() {
|
|||
cl = el.classList,
|
||||
line = el.parentNode,
|
||||
p = +line.dataset.id;
|
||||
if (cl.contains("fillRect")) changeFill(el);
|
||||
|
||||
if (el.tagName === "FILL-BOX") changeFill(el);
|
||||
else if (cl.contains("name")) editProvinceName(p);
|
||||
else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]);
|
||||
else if (cl.contains("icon-star-empty")) capitalZoomIn(p);
|
||||
|
|
@ -133,9 +134,7 @@ function editProvinces() {
|
|||
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${
|
||||
p.color
|
||||
}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
p.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
<fill-box fill="${p.color}"></fill-box>
|
||||
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly>
|
||||
<svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
|
||||
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly>
|
||||
|
|
@ -215,14 +214,14 @@ function editProvinces() {
|
|||
|
||||
function changeFill(el) {
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const p = +el.parentNode.parentNode.dataset.id;
|
||||
const p = +el.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
pack.provinces[p].color = fill;
|
||||
const callback = newFill => {
|
||||
el.fill = newFill;
|
||||
pack.provinces[p].color = newFill;
|
||||
const g = provs.select("#provincesBody");
|
||||
g.select("#province" + p).attr("fill", fill);
|
||||
g.select("#province-gap" + p).attr("stroke", fill);
|
||||
g.select("#province" + p).attr("fill", newFill);
|
||||
g.select("#province-gap" + p).attr("stroke", newFill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ function overviewRegiments(state) {
|
|||
updateHeaders();
|
||||
|
||||
$("#regimentsOverview").dialog({
|
||||
title: "Regiments Overview", resizable: false, width: fitContent(),
|
||||
title: "Regiments Overview",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
|
|
@ -31,11 +33,13 @@ function overviewRegiments(state) {
|
|||
header.querySelectorAll(".removable").forEach(el => el.remove());
|
||||
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
const label = capitalize(u.name.replace(/_/g, " "));
|
||||
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
||||
}
|
||||
header.querySelectorAll(".removable").forEach(function(e) {
|
||||
e.addEventListener("click", function() {sortLines(this);});
|
||||
header.querySelectorAll(".removable").forEach(function (e) {
|
||||
e.addEventListener("click", function () {
|
||||
sortLines(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -51,11 +55,13 @@ function overviewRegiments(state) {
|
|||
if (state !== -1 && s.i !== state) continue; // specific state is selected
|
||||
|
||||
for (const r of s.military) {
|
||||
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).join(" ");
|
||||
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name]||0}</div>`).join(" ");
|
||||
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" ");
|
||||
const lineData = options.military
|
||||
.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`)
|
||||
.join(" ");
|
||||
|
||||
lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
|
||||
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
|
||||
<input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly>
|
||||
|
|
@ -70,12 +76,15 @@ function overviewRegiments(state) {
|
|||
|
||||
lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
|
||||
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
|
||||
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name]||0)))}</div>`).join(" ")}
|
||||
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`).join(" ")}
|
||||
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", lines);
|
||||
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(regimentsHeader);
|
||||
|
||||
// add listeners
|
||||
|
|
@ -87,7 +96,7 @@ function overviewRegiments(state) {
|
|||
const filter = document.getElementById("regimentsFilter");
|
||||
filter.options.length = 0; // remove all options
|
||||
filter.options.add(new Option(`all`, -1, false, state === -1));
|
||||
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1);
|
||||
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
|
||||
}
|
||||
|
||||
|
|
@ -108,19 +117,20 @@ function overviewRegiments(state) {
|
|||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
const lines = body.querySelectorAll(":scope > div:not(.totalLine)");
|
||||
const array = Array.from(lines), cache = [];
|
||||
const array = Array.from(lines),
|
||||
cache = [];
|
||||
|
||||
const total = function(type) {
|
||||
const total = function (type) {
|
||||
if (cache[type]) cache[type];
|
||||
cache[type] = d3.sum(array.map(el => +el.dataset[type]));
|
||||
return cache[type];
|
||||
}
|
||||
};
|
||||
|
||||
lines.forEach(function(el) {
|
||||
el.querySelectorAll("div").forEach(function(div) {
|
||||
lines.forEach(function (el) {
|
||||
el.querySelectorAll("div").forEach(function (div) {
|
||||
const type = div.dataset.type;
|
||||
if (type === "rate") return;
|
||||
div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%";
|
||||
div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + "%" : "0%";
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
|
@ -145,15 +155,19 @@ function overviewRegiments(state) {
|
|||
|
||||
function addRegimentOnClick() {
|
||||
const state = +regimentsFilter.value;
|
||||
if (state === -1) {tip("Please select state from the list", false, "error"); return;}
|
||||
if (state === -1) {
|
||||
tip("Please select state from the list", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1];
|
||||
const x = pack.cells.p[cell][0],
|
||||
y = pack.cells.p[cell][1];
|
||||
const military = pack.states[state].military;
|
||||
const i = military.length ? last(military).i + 1 : 0;
|
||||
const n = +(pack.cells.h[cell] < 20); // naval or land
|
||||
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"};
|
||||
const reg = {a: 0, cell, i, n, u: {}, x, y, bx: x, by: y, state, icon: "🛡️"};
|
||||
reg.name = Military.getName(reg, military);
|
||||
military.push(reg);
|
||||
Military.generateNote(reg, pack.states[state]); // add legend
|
||||
|
|
@ -163,9 +177,9 @@ function overviewRegiments(state) {
|
|||
|
||||
function downloadRegimentsData() {
|
||||
const units = options.military.map(u => u.name);
|
||||
let data = "State,Id,Name,"+units.map(u => capitalize(u)).join(",")+",Total\n"; // headers
|
||||
let data = "State,Id,Name," + units.map(u => capitalize(u)).join(",") + ",Total\n"; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) {
|
||||
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function (el) {
|
||||
data += el.dataset.state + ",";
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
|
|
@ -176,5 +190,4 @@ function overviewRegiments(state) {
|
|||
const name = getFileName("Regiments") + ".csv";
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ function editReligions() {
|
|||
if (r.i) {
|
||||
lines += `<div class="states religions" data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-area=${area}
|
||||
data-population=${population} data-type=${r.type} data-form=${r.form} data-deity="${r.deity ? r.deity : ""}" data-expansionism=${r.expansionism}>
|
||||
<svg data-tip="Religion fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${r.color}" class="fillRect pointer"></svg>
|
||||
<fill-box fill="${r.color}"></fill-box>
|
||||
<input data-tip="Religion name. Click and type to change" class="religionName" value="${r.name}" autocorrect="off" spellcheck="false">
|
||||
<select data-tip="Religion type" class="religionType">${getTypeOptions(r.type)}</select>
|
||||
<input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false">
|
||||
|
|
@ -93,7 +93,9 @@ function editReligions() {
|
|||
</div>`;
|
||||
} else {
|
||||
// No religion (neutral) line
|
||||
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-color="" data-area=${area} data-population=${population} data-type="" data-form="" data-deity="" data-expansionism="">
|
||||
lines += `<div class="states" data-id=${r.i} data-name="${
|
||||
r.name
|
||||
}" data-color="" data-area=${area} data-population=${population} data-type="" data-form="" data-deity="" data-expansionism="">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Religion name. Click and type to change" class="religionName italic" value="${r.name}" autocorrect="off" spellcheck="false">
|
||||
<select data-tip="Religion type" class="religionType placeholder">${getTypeOptions(r.type)}</select>
|
||||
|
|
@ -124,7 +126,7 @@ function editReligions() {
|
|||
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseenter", ev => religionHighlightOn(ev)));
|
||||
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectReligionOnLineClick));
|
||||
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", religionChangeColor));
|
||||
body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", religionChangeColor));
|
||||
body.querySelectorAll("div > input.religionName").forEach(el => el.addEventListener("input", religionChangeName));
|
||||
body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType));
|
||||
body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm));
|
||||
|
|
@ -215,13 +217,13 @@ function editReligions() {
|
|||
function religionChangeColor() {
|
||||
const el = this;
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const religion = +el.parentNode.parentNode.dataset.id;
|
||||
const religion = +el.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
pack.religions[religion].color = fill;
|
||||
relig.select("#religion" + religion).attr("fill", fill);
|
||||
debug.select("#religionsCenter" + religion).attr("fill", fill);
|
||||
const callback = newFill => {
|
||||
el.fill = newFill;
|
||||
pack.religions[religion].color = newFill;
|
||||
relig.select("#religion" + religion).attr("fill", newFill);
|
||||
debug.select("#religionsCenter" + religion).attr("fill", newFill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
|
|
@ -459,7 +461,13 @@ function editReligions() {
|
|||
|
||||
// prepare svg
|
||||
alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>‍</div>";
|
||||
const svg = d3.select("#alertMessage").insert("svg", "#religionInfo").attr("id", "hierarchy").attr("width", width).attr("height", height).style("text-anchor", "middle");
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#religionInfo")
|
||||
.attr("id", "hierarchy")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.style("text-anchor", "middle");
|
||||
const graph = svg.append("g").attr("transform", `translate(10, -45)`);
|
||||
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
|
||||
const nodes = graph.append("g");
|
||||
|
|
@ -473,7 +481,24 @@ function editReligions() {
|
|||
.enter()
|
||||
.append("path")
|
||||
.attr("d", d => {
|
||||
return "M" + d.source.x + "," + d.source.y + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 + " " + d.target.x + "," + d.target.y;
|
||||
return (
|
||||
"M" +
|
||||
d.source.x +
|
||||
"," +
|
||||
d.source.y +
|
||||
"C" +
|
||||
d.source.x +
|
||||
"," +
|
||||
(d.source.y * 3 + d.target.y) / 4 +
|
||||
" " +
|
||||
d.target.x +
|
||||
"," +
|
||||
(d.source.y * 2 + d.target.y) / 3 +
|
||||
" " +
|
||||
d.target.x +
|
||||
"," +
|
||||
d.target.y
|
||||
);
|
||||
});
|
||||
|
||||
const node = nodes
|
||||
|
|
@ -578,7 +603,11 @@ function editReligions() {
|
|||
$("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
|
||||
tip("Click on religion to select, drag the circle to change religion", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectReligionOnMapClick).call(d3.drag().on("start", dragReligionBrush)).on("touchmove mousemove", moveReligionBrush);
|
||||
viewbox
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectReligionOnMapClick)
|
||||
.call(d3.drag().on("start", dragReligionBrush))
|
||||
.on("touchmove mousemove", moveReligionBrush);
|
||||
|
||||
body.querySelector("div").classList.add("selected");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ function editStates() {
|
|||
cl = el.classList,
|
||||
line = el.parentNode,
|
||||
state = +line.dataset.id;
|
||||
if (cl.contains("fillRect")) stateChangeFill(el);
|
||||
if (el.tagName === "FILL-BOX") stateChangeFill(el);
|
||||
else if (cl.contains("name")) editStateName(state);
|
||||
else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]);
|
||||
else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state);
|
||||
|
|
@ -102,7 +102,7 @@ function editStates() {
|
|||
// Neutral line
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-cells=${s.cells} data-area=${area}
|
||||
data-population=${population} data-burgs=${s.burgs} data-color="" data-form="" data-capital="" data-culture="" data-type="" data-expansionism="">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<svg width="1em" height="1em" class="placeholder"></svg>
|
||||
<input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly>
|
||||
<svg class="coaIcon placeholder hide"></svg>
|
||||
<input class="stateForm placeholder" value="none">
|
||||
|
|
@ -126,15 +126,10 @@ function editStates() {
|
|||
|
||||
const capital = pack.burgs[s.capital].name;
|
||||
COArenderer.trigger("stateCOA" + s.i, s.coa);
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}" data-color="${
|
||||
s.color
|
||||
}" data-cells=${s.cells}
|
||||
data-area=${area} data-population=${population} data-burgs=${s.burgs} data-culture=${pack.cultures[s.culture].name} data-type=${
|
||||
s.type
|
||||
} data-expansionism=${s.expansionism}>
|
||||
<svg data-tip="State fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
s.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}"
|
||||
data-color="${s.color}" data-cells=${s.cells} data-area=${area} data-population=${population} data-burgs=${s.burgs}
|
||||
data-culture=${pack.cultures[s.culture].name} data-type=${s.type} data-expansionism=${s.expansionism}>
|
||||
<fill-box fill="${s.color}"></fill-box>
|
||||
<input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly>
|
||||
<svg data-tip="Click to show and edit state emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
|
||||
<input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly>
|
||||
|
|
@ -149,9 +144,8 @@ function editStates() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<select data-tip="State type. Defines growth model. Click to change" class="cultureType ${hidden} show hide">${getTypeOptions(s.type)}</select>
|
||||
<span data-tip="State expansionism" class="icon-resize-full ${hidden} show hide"></span>
|
||||
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value" class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${
|
||||
s.expansionism
|
||||
}>
|
||||
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value"
|
||||
class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${s.expansionism}>
|
||||
<span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div>
|
||||
<span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
|
||||
|
|
@ -237,18 +231,18 @@ function editStates() {
|
|||
|
||||
function stateChangeFill(el) {
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const state = +el.parentNode.parentNode.dataset.id;
|
||||
const state = +el.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
pack.states[state].color = fill;
|
||||
statesBody.select("#state" + state).attr("fill", fill);
|
||||
statesBody.select("#state-gap" + state).attr("stroke", fill);
|
||||
const halo = d3.color(fill) ? d3.color(fill).darker().hex() : "#666666";
|
||||
const callback = function (newFill) {
|
||||
el.fill = newFill;
|
||||
pack.states[state].color = newFill;
|
||||
statesBody.select("#state" + state).attr("fill", newFill);
|
||||
statesBody.select("#state-gap" + state).attr("stroke", newFill);
|
||||
const halo = d3.color(newFill) ? d3.color(newFill).darker().hex() : "#666666";
|
||||
statesHalo.select("#state-border" + state).attr("stroke", halo);
|
||||
|
||||
// recolor regiments
|
||||
const solidColor = fill[0] === "#" ? fill : "#999";
|
||||
const solidColor = newFill[0] === "#" ? newFill : "#999";
|
||||
const darkerColor = d3.color(solidColor).darker().hex();
|
||||
armies.select("#army" + state).attr("fill", solidColor);
|
||||
armies
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
311
modules/ui/stylePresets.js
Normal file
311
modules/ui/stylePresets.js
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
// UI module to control the style presets
|
||||
"use strict";
|
||||
|
||||
const systemPresets = ["default", "ancient", "gloom", "light", "watercolor", "clean", "atlas", "cyberpunk", "monochrome"];
|
||||
const customPresetPrefix = "fmgStyle_";
|
||||
|
||||
// add style presets to list
|
||||
{
|
||||
const systemOptions = systemPresets.map(styleName => `<option value="${styleName}">${styleName}</option>`);
|
||||
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix));
|
||||
const customOptions = storedStyles.map(styleName => `<option value="${styleName}">${styleName.replace(customPresetPrefix, "")} [custom]</option>`);
|
||||
const options = systemOptions.join("") + customOptions.join("");
|
||||
document.getElementById("stylePreset").innerHTML = options;
|
||||
}
|
||||
|
||||
async function applyStyleOnLoad() {
|
||||
const desiredPreset = localStorage.getItem("presetStyle") || "default";
|
||||
const styleData = await getStylePreset(desiredPreset);
|
||||
const [appliedPreset, style] = styleData;
|
||||
|
||||
applyStyle(style);
|
||||
updateMapFilter();
|
||||
stylePreset.value = stylePreset.dataset.old = appliedPreset;
|
||||
setPresetRemoveButtonVisibiliy();
|
||||
}
|
||||
|
||||
async function getStylePreset(desiredPreset) {
|
||||
let presetToLoad = desiredPreset;
|
||||
|
||||
const isCustom = !systemPresets.includes(desiredPreset);
|
||||
if (isCustom) {
|
||||
const storedStyleJSON = localStorage.getItem(desiredPreset);
|
||||
if (!storedStyleJSON) {
|
||||
ERROR && console.error(`Custom style ${desiredPreset} in not found in localStorage. Applying default style`);
|
||||
presetToLoad = "default";
|
||||
} else {
|
||||
const isValid = JSON.isValid(storedStyleJSON);
|
||||
if (isValid) return [desiredPreset, JSON.parse(storedStyleJSON)];
|
||||
|
||||
ERROR && console.error(`Custom style ${desiredPreset} stored in localStorage is not valid. Applying default style`);
|
||||
presetToLoad = "default";
|
||||
}
|
||||
}
|
||||
|
||||
const style = await fetchSystemPreset(presetToLoad);
|
||||
return [presetToLoad, style];
|
||||
}
|
||||
|
||||
async function fetchSystemPreset(preset) {
|
||||
const style = await fetch(`./styles/${preset}.json`)
|
||||
.then(res => res.json())
|
||||
.catch(err => {
|
||||
ERROR && console.error("Error on loading style preset", preset, err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!style) throw new Error("Cannot fetch style preset", preset);
|
||||
return style;
|
||||
}
|
||||
|
||||
function applyStyle(style) {
|
||||
for (const selector in style) {
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) continue;
|
||||
for (const attribute in style[selector]) {
|
||||
const value = style[selector][attribute];
|
||||
|
||||
if (value === "null" || value === null) {
|
||||
el.removeAttribute(attribute);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attribute === "text-shadow") {
|
||||
el.style[attribute] = value;
|
||||
} else {
|
||||
el.setAttribute(attribute, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestStylePresetChange(preset) {
|
||||
const isConfirmed = sessionStorage.getItem("styleChangeConfirmed");
|
||||
if (isConfirmed) {
|
||||
changeStyle(preset);
|
||||
return;
|
||||
}
|
||||
|
||||
confirmationDialog({
|
||||
title: "Change style preset",
|
||||
message: "Are you sure you want to change the style preset? All unsaved style changes will be lost",
|
||||
confirm: "Change",
|
||||
onConfirm: () => {
|
||||
sessionStorage.setItem("styleChangeConfirmed", true);
|
||||
changeStyle(preset);
|
||||
},
|
||||
onCancel: () => {
|
||||
stylePreset.value = stylePreset.dataset.old;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function changeStyle(desiredPreset) {
|
||||
const styleData = await getStylePreset(desiredPreset);
|
||||
const [appliedPreset, style] = styleData;
|
||||
localStorage.setItem("presetStyle", appliedPreset);
|
||||
applyStyleWithUiRefresh(style);
|
||||
}
|
||||
|
||||
function applyStyleWithUiRefresh(style) {
|
||||
applyStyle(style);
|
||||
updateElements();
|
||||
selectStyleElement(); // re-select element to trigger values update
|
||||
updateMapFilter();
|
||||
stylePreset.dataset.old = stylePreset.value;
|
||||
|
||||
invokeActiveZooming();
|
||||
setPresetRemoveButtonVisibiliy();
|
||||
}
|
||||
|
||||
function addStylePreset() {
|
||||
$("#styleSaver").dialog({title: "Style Saver", width: "26em", position: {my: "center", at: "center", of: "svg"}});
|
||||
|
||||
const styleName = stylePreset.value.replace(customPresetPrefix, "");
|
||||
document.getElementById("styleSaverName").value = styleName;
|
||||
styleSaverJSON.value = JSON.stringify(collectStyleData(), null, 2);
|
||||
checkName();
|
||||
|
||||
if (modules.saveStyle) return;
|
||||
modules.saveStyle = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("styleSaverName").addEventListener("input", checkName);
|
||||
document.getElementById("styleSaverSave").addEventListener("click", saveStyle);
|
||||
document.getElementById("styleSaverDownload").addEventListener("click", styleDownload);
|
||||
document.getElementById("styleSaverLoad").addEventListener("click", () => styleToLoad.click());
|
||||
document.getElementById("styleToLoad").addEventListener("change", loadStyleFile);
|
||||
|
||||
function collectStyleData() {
|
||||
const style = {};
|
||||
const attributes = {
|
||||
"#map": ["background-color", "filter", "data-filter"],
|
||||
"#armies": ["font-size", "box-size", "stroke", "stroke-width", "fill-opacity", "filter"],
|
||||
"#biomes": ["opacity", "filter", "mask"],
|
||||
"#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#provinceBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#cells": ["opacity", "stroke", "stroke-width", "filter", "mask"],
|
||||
"#gridOverlay": ["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"],
|
||||
"#coordinates": ["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"],
|
||||
"#rose": ["transform"],
|
||||
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
|
||||
"#cults": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#landmass": ["opacity", "fill", "filter"],
|
||||
"#markers": ["opacity", "rescale", "filter"],
|
||||
"#prec": ["opacity", "stroke", "stroke-width", "fill", "filter"],
|
||||
"#population": ["opacity", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#rural": ["stroke"],
|
||||
"#urban": ["stroke"],
|
||||
"#freshwater": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#salt": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#sinkhole": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#frozen": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#lava": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#dry": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#sea_island": ["opacity", "stroke", "stroke-width", "filter", "auto-filter"],
|
||||
"#lake_island": ["opacity", "stroke", "stroke-width", "filter"],
|
||||
"#terrain": ["opacity", "set", "size", "density", "filter", "mask"],
|
||||
"#rivers": ["opacity", "filter", "fill"],
|
||||
"#ruler": ["opacity", "filter"],
|
||||
"#roads": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#trails": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#searoutes": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#statesBody": ["opacity", "filter"],
|
||||
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
|
||||
"#provs": ["opacity", "fill", "font-size", "font-family", "filter"],
|
||||
"#temperature": ["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
|
||||
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
|
||||
"#emblems": ["opacity", "stroke-width", "filter"],
|
||||
"#texture": ["opacity", "filter", "mask"],
|
||||
"#textureImage": ["x", "y"],
|
||||
"#zones": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
|
||||
"#oceanLayers": ["filter", "layers"],
|
||||
"#oceanBase": ["fill"],
|
||||
"#oceanicPattern": ["href", "opacity"],
|
||||
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
|
||||
"#legend": ["data-size", "font-size", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"],
|
||||
"#legendBox": ["fill", "fill-opacity"],
|
||||
"#burgLabels > #cities": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
|
||||
"#burgIcons > #cities": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
|
||||
"#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"],
|
||||
"#burgLabels > #towns": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
|
||||
"#burgIcons > #towns": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
|
||||
"#anchors > #towns": ["opacity", "fill", "size", "stroke", "stroke-width"],
|
||||
"#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
|
||||
"#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
|
||||
"#fogging": ["opacity", "fill", "filter"]
|
||||
};
|
||||
|
||||
for (const selector in attributes) {
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) continue;
|
||||
|
||||
style[selector] = {};
|
||||
for (const attr of attributes[selector]) {
|
||||
let value = el.style[attr] || el.getAttribute(attr);
|
||||
if (attr === "font-size" && el.hasAttribute("data-size")) value = el.getAttribute("data-size");
|
||||
style[selector][attr] = parseValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
function parseValue(value) {
|
||||
if (value === "null" || value === null) return null;
|
||||
if (value === "") return "";
|
||||
if (!isNaN(+value)) return +value;
|
||||
return value;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
function checkName() {
|
||||
const styleName = customPresetPrefix + styleSaverName.value;
|
||||
|
||||
const isSystem = systemPresets.includes(styleName) || systemPresets.includes(styleSaverName.value);
|
||||
if (isSystem) return (styleSaverTip.innerHTML = "default");
|
||||
|
||||
const isExisting = Array.from(stylePreset.options).some(option => option.value == styleName);
|
||||
if (isExisting) return (styleSaverTip.innerHTML = "existing");
|
||||
|
||||
styleSaverTip.innerHTML = "new";
|
||||
}
|
||||
|
||||
function saveStyle() {
|
||||
const styleJSON = styleSaverJSON.value;
|
||||
const desiredName = styleSaverName.value;
|
||||
|
||||
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
|
||||
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
|
||||
if (!desiredName) return tip("Please provide a preset name", false, "error");
|
||||
if (styleSaverTip.innerHTML === "default") return tip("You cannot overwrite default preset, please change the name", false, "error");
|
||||
|
||||
const presetName = customPresetPrefix + desiredName;
|
||||
applyOption(stylePreset, presetName, desiredName + " [custom]");
|
||||
localStorage.setItem("presetStyle", presetName);
|
||||
localStorage.setItem(presetName, styleJSON);
|
||||
|
||||
applyStyleWithUiRefresh(JSON.parse(styleJSON));
|
||||
tip("Style preset is saved and applied", false, "success", 4000);
|
||||
$("#styleSaver").dialog("close");
|
||||
}
|
||||
|
||||
function styleDownload() {
|
||||
const styleJSON = styleSaverJSON.value;
|
||||
const styleName = styleSaverName.value;
|
||||
|
||||
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
|
||||
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
|
||||
if (!styleName) return tip("Please provide a preset name", false, "error");
|
||||
|
||||
downloadFile(styleJSON, styleName + ".json", "application/json");
|
||||
}
|
||||
|
||||
function loadStyleFile() {
|
||||
const fileName = this.files[0]?.name.replace(/\.[^.]*$/, "");
|
||||
uploadFile(this, styleUpload);
|
||||
|
||||
function styleUpload(dataLoaded) {
|
||||
if (!dataLoaded) return tip("Cannot load the file. Please check the data format", false, "error");
|
||||
const isValid = JSON.isValid(dataLoaded);
|
||||
if (!isValid) return tip("Loaded data is not a valid JSON, please check the format", false, "error");
|
||||
|
||||
styleSaverJSON.value = JSON.stringify(JSON.parse(dataLoaded), null, 2);
|
||||
styleSaverName.value = fileName;
|
||||
checkName();
|
||||
tip("Style preset is uploaded", false, "success", 4000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestRemoveStylePreset() {
|
||||
const isDefault = systemPresets.includes(stylePreset.value);
|
||||
if (isDefault) return tip("Cannot remove system preset", false, "error");
|
||||
|
||||
confirmationDialog({
|
||||
title: "Remove style preset",
|
||||
message: "Are you sure you want to remove the style preset? This action cannot be undone.",
|
||||
confirm: "Remove",
|
||||
onConfirm: removeStylePreset
|
||||
});
|
||||
}
|
||||
|
||||
function removeStylePreset() {
|
||||
localStorage.removeItem("presetStyle");
|
||||
localStorage.removeItem(stylePreset.value);
|
||||
stylePreset.selectedOptions[0].remove();
|
||||
|
||||
changeStyle("default");
|
||||
}
|
||||
|
||||
function updateMapFilter() {
|
||||
const filter = svg.attr("data-filter");
|
||||
mapFilters.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed"));
|
||||
if (!filter) return;
|
||||
mapFilters.querySelector("#" + filter).classList.add("pressed");
|
||||
}
|
||||
|
||||
function setPresetRemoveButtonVisibiliy() {
|
||||
const isDefault = systemPresets.includes(stylePreset.value);
|
||||
removeStyleButton.style.display = isDefault ? "none" : "inline-block";
|
||||
}
|
||||
161
modules/ui/temperature-graph.js
Normal file
161
modules/ui/temperature-graph.js
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
"use strict";
|
||||
|
||||
function showBurgTemperatureGraph(id) {
|
||||
const b = pack.burgs[id];
|
||||
const lat = mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT;
|
||||
const burgTemp = grid.cells.temp[pack.cells.g[b.cell]];
|
||||
const prec = grid.cells.prec[pack.cells.g[b.cell]];
|
||||
|
||||
// prettier-ignore
|
||||
const weights = [
|
||||
[
|
||||
[10.782752257744338, 2.7100404240962126], [-2.8226802110591462, 51.62920138583541], [-6.6250956268643835, 4.427939197315455], [-59.64690518541339, 41.89084162654791], [-1.3302059550553835, -3.6964487738450913],
|
||||
[-2.5844898544535497, 0.09879268612455298], [-5.58528252533573, -0.23426224364501905], [26.94531337690372, 20.898158905988907], [3.816397481634785, -0.19045424064580757], [-4.835697931609101, -10.748232783636434]
|
||||
],
|
||||
[
|
||||
[-2.478952081870123, 0.6405800134306895, -7.136785640930911, -0.2186529024764509, 3.6568435212735424, 31.446026153530838, -19.91005187482281, 0.2543395274783306, -7.036924569659988, -0.7721371621651565],
|
||||
[-197.10583739743538, 6.889921141533474, 0.5058941504631129, 7.7667203434606416, -53.74180550086929, -15.717331715167001, -61.32068414155791, -2.259728220978728, 35.84049189540032, 94.6157364730977],
|
||||
[-5.312011591880851, -0.09923148954215096, -1.7132477487917586, -22.55559652066422, 0.4806107280554336, -26.5583974109492, 2.0558257347014863, 25.815645234787432, -18.569029876991156, -2.6792003366730035],
|
||||
[20.706518520569514, 18.344297403881875, 99.52244671131733, -58.53124969563653, -60.74384321042212, -80.57540534651835, 7.884792406540866, -144.33871131678563, 80.134199744324, 20.50745285622448],
|
||||
[-52.88299538575159, -15.782505343805528, 16.63316001054924, 88.09475330556671, -17.619552086641818, -19.943999528182427, -120.46286026828177, 19.354752020806302, 43.49422099308949, 28.733924806541363],
|
||||
[-2.4621368711159897, -1.2074759925679757, -1.5133898639835084, 2.173715352424188, -5.988707597991683, 3.0234147182203843, 3.3284199340000797, -1.8805161326360575, 5.151910934121654, -1.2540553911612116]
|
||||
],
|
||||
[
|
||||
[-0.3357437479474717, 0.01430651794222215, -0.7927524256670906, 0.2121636229648523, 1.0587803023358318, -3.759288325505095],
|
||||
[-1.1988028704442968, 1.3768997508052783, -3.8480086358278816, 0.5289387340947143, 0.5769459339961177, -1.2528318145750772],
|
||||
[1.0074966649240946, 1.155301164699459, -2.974254371052421, 0.47408176553219467, 0.5939042688615264, -0.7631976947131744]
|
||||
]
|
||||
];
|
||||
// From (-∞, ∞) to ~[-1, 1]
|
||||
const In1 = [(Math.abs(lat) - 26.950680212887473) / 48.378128506956, (prec - 12.229929140832644) / 29.94402033696607];
|
||||
|
||||
let lastIn = In1;
|
||||
let lstOut = [];
|
||||
|
||||
for (let levelN = 0; levelN < weights.length; levelN++) {
|
||||
const layerN = weights[levelN];
|
||||
for (let i = 0; i < layerN.length; i++) {
|
||||
lstOut[i] = 0;
|
||||
for (let j = 0; j < layerN[i].length; j++) {
|
||||
lstOut[i] = lstOut[i] + lastIn[j] * layerN[i][j];
|
||||
}
|
||||
// sigmoid
|
||||
lstOut[i] = 1 / (1 + Math.exp(-lstOut[i]));
|
||||
}
|
||||
lastIn = lstOut.slice(0);
|
||||
}
|
||||
|
||||
// Standard deviation for average temperature for the year from [0, 1] to [min, max]
|
||||
const yearSig = lstOut[0] * 62.9466411977018 + 0.28613807855649165;
|
||||
// Standard deviation for the difference between the minimum and maximum temperatures for the year
|
||||
const yearDelTmpSig = lstOut[1] * 13.541688670361175 + 0.1414213562373084 > yearSig ? yearSig : lstOut[1] * 13.541688670361175 + 0.1414213562373084;
|
||||
// Expected value for the difference between the minimum and maximum temperatures for the year
|
||||
const yearDelTmpMu = lstOut[2] * 15.266666666666667 + 0.6416666666666663;
|
||||
|
||||
// Temperature change shape
|
||||
const delT = yearDelTmpMu / 2 + (0.5 * yearDelTmpSig) / 2;
|
||||
const minT = burgTemp - Math.max(yearSig + delT, 15);
|
||||
const maxT = burgTemp + (burgTemp - minT);
|
||||
|
||||
const chartWidth = Math.max(window.innerWidth / 2, 580);
|
||||
const chartHeight = 300;
|
||||
|
||||
// drawing starting point from top-left (y = 0) of SVG
|
||||
const xOffset = 60;
|
||||
const yOffset = 10;
|
||||
|
||||
const year = new Date().getFullYear(); // use current year
|
||||
const startDate = new Date(year, 0, 1);
|
||||
const endDate = new Date(year, 11, 31);
|
||||
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
|
||||
const xscale = d3.scaleTime().domain([startDate, endDate]).range([0, chartWidth]);
|
||||
const yscale = d3.scaleLinear().domain([minT, maxT]).range([chartHeight, 0]);
|
||||
|
||||
const tempMean = [];
|
||||
const tempMin = [];
|
||||
const tempMax = [];
|
||||
|
||||
months.forEach((month, index) => {
|
||||
const rate = index / 11;
|
||||
let formTmp = Math.cos(rate * 2 * Math.PI) / 2;
|
||||
if (lat > 0) formTmp = -formTmp;
|
||||
|
||||
const x = rate * chartWidth + xOffset;
|
||||
const tempAverage = formTmp * yearSig + burgTemp;
|
||||
const tempDelta = yearDelTmpMu / 2 + (formTmp * yearDelTmpSig) / 2;
|
||||
|
||||
tempMean.push([x, yscale(tempAverage) + yOffset]);
|
||||
tempMin.push([x, yscale(tempAverage - tempDelta) + yOffset]);
|
||||
tempMax.push([x, yscale(tempAverage + tempDelta) + yOffset]);
|
||||
});
|
||||
|
||||
drawGraph();
|
||||
$("#alert").dialog({title: "Annual temperature in " + b.name, width: "auto", position: {my: "center", at: "center", of: "svg"}});
|
||||
|
||||
function drawGraph() {
|
||||
alertMessage.innerHTML = "";
|
||||
const getCurve = data => round(d3.line().curve(d3.curveBasis)(data), 2);
|
||||
|
||||
const legendSize = 60;
|
||||
const chart = d3
|
||||
.select("#alertMessage")
|
||||
.append("svg")
|
||||
.attr("width", chartWidth + 120)
|
||||
.attr("height", chartHeight + yOffset + legendSize);
|
||||
|
||||
const legend = chart.append("g");
|
||||
const legendY = chartHeight + yOffset + legendSize * 0.8;
|
||||
const legendX = n => (chartWidth * n) / 4;
|
||||
const legendTextX = n => legendX(n) + 10;
|
||||
legend.append("circle").attr("cx", legendX(1)).attr("cy", legendY).attr("r", 4).style("fill", "red");
|
||||
legend.append("text").attr("x", legendTextX(1)).attr("y", legendY).attr("alignment-baseline", "central").text("Day temperature");
|
||||
legend.append("circle").attr("cx", legendX(2)).attr("cy", legendY).attr("r", 4).style("fill", "orange");
|
||||
legend.append("text").attr("x", legendTextX(2)).attr("y", legendY).attr("alignment-baseline", "central").text("Mean temperature");
|
||||
legend.append("circle").attr("cx", legendX(3)).attr("cy", legendY).attr("r", 4).style("fill", "blue");
|
||||
legend.append("text").attr("x", legendTextX(3)).attr("y", legendY).attr("alignment-baseline", "central").text("Night temperature");
|
||||
|
||||
const xGrid = d3.axisBottom(xscale).ticks().tickSize(-chartHeight);
|
||||
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth);
|
||||
|
||||
const grid = chart.append("g").attr("class", "epgrid").attr("stroke-dasharray", "4 1");
|
||||
grid.append("g").attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`).call(xGrid); // prettier-ignore
|
||||
grid.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yGrid);
|
||||
grid.selectAll("text").remove();
|
||||
|
||||
// add zero degree line
|
||||
if (minT < 0 && maxT > 0) {
|
||||
grid
|
||||
.append("line")
|
||||
.attr("x1", xOffset)
|
||||
.attr("y1", yscale(0) + yOffset)
|
||||
.attr("x2", chartWidth + xOffset)
|
||||
.attr("y2", yscale(0) + yOffset)
|
||||
.attr("stroke", "gray");
|
||||
}
|
||||
|
||||
const xAxis = d3.axisBottom(xscale).ticks().tickFormat(d3.timeFormat("%B"));
|
||||
const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(convertTemperature);
|
||||
|
||||
const axis = chart.append("g");
|
||||
axis
|
||||
.append("g")
|
||||
.attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`)
|
||||
.call(xAxis);
|
||||
axis.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yAxis);
|
||||
axis.select("path.domain").attr("d", `M0.5,0.5 H${chartWidth + 0.5}`);
|
||||
|
||||
const curves = chart.append("g").attr("fill", "none").style("stroke-width", 2.5);
|
||||
curves.append("path").attr("d", getCurve(tempMean)).attr("data-type", "daily").attr("stroke", "orange").on("mousemove", printVal);
|
||||
curves.append("path").attr("d", getCurve(tempMin)).attr("data-type", "night").attr("stroke", "blue").on("mousemove", printVal);
|
||||
curves.append("path").attr("d", getCurve(tempMax)).attr("data-type", "day").attr("stroke", "red").on("mousemove", printVal);
|
||||
|
||||
function printVal() {
|
||||
const [x, y] = d3.mouse(this);
|
||||
const type = this.getAttribute("data-type");
|
||||
const temp = convertTemperature(yscale.invert(y - yOffset));
|
||||
const month = months[rn(((x - xOffset) / chartWidth) * 12)] || months[0];
|
||||
tip(`Average ${type} temperature in ${month}: ${temp}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ function editUnits() {
|
|||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
});
|
||||
|
||||
const drawBar = () => drawScaleBar(scale);
|
||||
|
||||
// add listeners
|
||||
document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit);
|
||||
document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale);
|
||||
|
|
@ -19,9 +21,9 @@ function editUnits() {
|
|||
document.getElementById("heightExponentInput").addEventListener("input", changeHeightExponent);
|
||||
document.getElementById("heightExponentOutput").addEventListener("input", changeHeightExponent);
|
||||
document.getElementById("temperatureScale").addEventListener("change", changeTemperatureScale);
|
||||
document.getElementById("barSizeOutput").addEventListener("input", drawScaleBar);
|
||||
document.getElementById("barSizeInput").addEventListener("input", drawScaleBar);
|
||||
document.getElementById("barLabel").addEventListener("input", drawScaleBar);
|
||||
document.getElementById("barSizeOutput").addEventListener("input", drawBar);
|
||||
document.getElementById("barSizeInput").addEventListener("input", drawBar);
|
||||
document.getElementById("barLabel").addEventListener("input", drawBar);
|
||||
document.getElementById("barPosX").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barPosY").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity);
|
||||
|
|
@ -46,19 +48,18 @@ function editUnits() {
|
|||
prompt("Provide a custom name for a distance unit", {default: ""}, custom => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock("distanceUnit");
|
||||
drawScaleBar();
|
||||
drawScaleBar(scale);
|
||||
calculateFriendlyGridSize();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
drawScaleBar();
|
||||
drawScaleBar(scale);
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeDistanceScale() {
|
||||
distanceScale = +document.getElementById("distanceScaleInput").value;
|
||||
drawScaleBar();
|
||||
drawScaleBar(scale);
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +139,7 @@ function editUnits() {
|
|||
localStorage.removeItem("barBackColor");
|
||||
localStorage.removeItem("barPosX");
|
||||
localStorage.removeItem("barPosY");
|
||||
drawScaleBar();
|
||||
drawScaleBar(scale);
|
||||
|
||||
// population
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ function editZones() {
|
|||
closeDialogs();
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
const body = document.getElementById("zonesBodySection");
|
||||
|
||||
updateFilters();
|
||||
zonesEditorAddLines();
|
||||
|
||||
if (modules.editZones) return;
|
||||
|
|
@ -18,6 +20,8 @@ function editZones() {
|
|||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("zonesFilterType").addEventListener("click", updateFilters);
|
||||
document.getElementById("zonesFilterType").addEventListener("change", filterZonesByType);
|
||||
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
|
||||
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
|
||||
document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
|
||||
|
|
@ -33,55 +37,60 @@ function editZones() {
|
|||
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 (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);
|
||||
});
|
||||
|
||||
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);
|
||||
const el = ev.target;
|
||||
const zone = zones.select("#" + el.parentNode.dataset.id);
|
||||
|
||||
if (el.classList.contains("zoneName")) zone.attr("data-description", el.value);
|
||||
else if (el.classList.contains("zoneType")) zone.attr("data-type", el.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 = document.getElementById("zonesFilterType");
|
||||
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
|
||||
|
||||
filterSelect.innerHTML = "<option value='all'>all</option>" + types.map(type => `<option value="${type}">${type}</option>`).join("");
|
||||
filterSelect.value = typeToFilterBy;
|
||||
}
|
||||
|
||||
// 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 typeToFilterBy = document.getElementById("zonesFilterType").value;
|
||||
const zones = Array.from(document.querySelectorAll("#zones > g"));
|
||||
const filteredZones = typeToFilterBy === "all" ? zones : zones.filter(zone => zone.dataset.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 = 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 inactive = zoneEl.style.display === "none";
|
||||
const focused = defs.select("#fog #focus" + zoneEl.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>
|
||||
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
|
||||
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">
|
||||
<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>
|
||||
<span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span>
|
||||
|
|
@ -95,13 +104,13 @@ function editZones() {
|
|||
</div>`;
|
||||
});
|
||||
|
||||
body.innerHTML = lines;
|
||||
body.innerHTML = lines.join("");
|
||||
|
||||
// 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) * populationRate;
|
||||
zonesFooterPopulation.dataset.population = totalPop;
|
||||
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
||||
zonesFooterNumber.innerHTML = `${filteredZones.length} of ${zones.length}`;
|
||||
zonesFooterCells.innerHTML = pack.cells.i.length;
|
||||
zonesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
zonesFooterPopulation.innerHTML = si(totalPop);
|
||||
|
|
@ -127,6 +136,19 @@ function editZones() {
|
|||
zones.select("#" + zone).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";
|
||||
}
|
||||
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
|
||||
$(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"));
|
||||
|
|
@ -142,7 +164,7 @@ function editZones() {
|
|||
function enterZonesManualAssignent() {
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
customization = 10;
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "inline-block";
|
||||
|
||||
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
|
|
@ -256,7 +278,7 @@ function editZones() {
|
|||
function exitZonesManualAssignment(close) {
|
||||
customization = 0;
|
||||
removeCircle();
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "none";
|
||||
|
||||
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
|
||||
|
|
@ -275,9 +297,9 @@ function editZones() {
|
|||
|
||||
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);
|
||||
const callback = newFill => {
|
||||
el.fill = newFill;
|
||||
document.getElementById(el.parentNode.dataset.id).setAttribute("fill", newFill);
|
||||
};
|
||||
|
||||
openPicker(fill, callback);
|
||||
|
|
@ -344,37 +366,22 @@ function editZones() {
|
|||
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 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 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="fillRect pointer"></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();
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
|
||||
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,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.description + ",";
|
||||
data += el.dataset.type + ",";
|
||||
data += el.dataset.cells + ",";
|
||||
data += el.dataset.area + ",";
|
||||
data += el.dataset.population + "\n";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue