diff --git a/modules/dynamic/export-json.js b/modules/dynamic/export-json.js index 4f66dc95..df0d6815 100644 --- a/modules/dynamic/export-json.js +++ b/modules/dynamic/export-json.js @@ -86,7 +86,7 @@ function getMapInfo() { function getSettings() { return { distanceUnit: distanceUnitInput.value, - distanceScale: distanceScaleInput.value, + distanceScale, areaUnit: areaUnit.value, heightUnit: heightUnit.value, heightExponent: heightExponentInput.value, diff --git a/modules/io/save.js b/modules/io/save.js index fd8c586b..73e6845f 100644 --- a/modules/io/save.js +++ b/modules/io/save.js @@ -44,7 +44,7 @@ function prepareMapData() { const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|"); const settings = [ distanceUnitInput.value, - distanceScaleInput.value, + distanceScale, areaUnit.value, heightUnit.value, heightExponentInput.value, diff --git a/modules/routes-generator.js b/modules/routes-generator.js index ca086222..24b4f02d 100644 --- a/modules/routes-generator.js +++ b/modules/routes-generator.js @@ -601,6 +601,11 @@ window.Routes = (function () { return routePoints; } + function getLength(routeId) { + const path = routes.select("#route" + routeId).node(); + return path.getTotalLength(); + } + function remove(route) { const routes = pack.cells.routes; @@ -630,6 +635,7 @@ window.Routes = (function () { generateName, preparePointsArray, getPoints, + getLength, remove }; })(); diff --git a/modules/ui/battle-screen.js b/modules/ui/battle-screen.js index 2936549c..12ad7bdd 100644 --- a/modules/ui/battle-screen.js +++ b/modules/ui/battle-screen.js @@ -37,12 +37,22 @@ class Battle { // add listeners document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev)); - document.getElementById("battleType").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev)); - document.getElementById("battleNameShow").addEventListener("click", () => Battle.prototype.context.showNameSection()); - document.getElementById("battleNamePlace").addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value)); + document + .getElementById("battleType") + .nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev)); + document + .getElementById("battleNameShow") + .addEventListener("click", () => Battle.prototype.context.showNameSection()); + document + .getElementById("battleNamePlace") + .addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value)); document.getElementById("battleNameFull").addEventListener("change", ev => Battle.prototype.context.changeName(ev)); - document.getElementById("battleNameCulture").addEventListener("click", () => Battle.prototype.context.generateName("culture")); - document.getElementById("battleNameRandom").addEventListener("click", () => Battle.prototype.context.generateName("random")); + document + .getElementById("battleNameCulture") + .addEventListener("click", () => Battle.prototype.context.generateName("culture")); + document + .getElementById("battleNameRandom") + .addEventListener("click", () => Battle.prototype.context.generateName("random")); document.getElementById("battleNameHide").addEventListener("click", this.hideNameSection); document.getElementById("battleAddRegiment").addEventListener("click", this.addSide); document.getElementById("battleRoll").addEventListener("click", () => Battle.prototype.context.randomize()); @@ -52,11 +62,19 @@ class Battle { document.getElementById("battleWiki").addEventListener("click", () => wiki("Battle-Simulator")); document.getElementById("battlePhase_attackers").addEventListener("click", ev => this.toggleChange(ev)); - document.getElementById("battlePhase_attackers").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "attackers")); + document + .getElementById("battlePhase_attackers") + .nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "attackers")); document.getElementById("battlePhase_defenders").addEventListener("click", ev => this.toggleChange(ev)); - document.getElementById("battlePhase_defenders").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders")); - document.getElementById("battleDie_attackers").addEventListener("click", () => Battle.prototype.context.rollDie("attackers")); - document.getElementById("battleDie_defenders").addEventListener("click", () => Battle.prototype.context.rollDie("defenders")); + document + .getElementById("battlePhase_defenders") + .nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders")); + document + .getElementById("battleDie_attackers") + .addEventListener("click", () => Battle.prototype.context.rollDie("attackers")); + document + .getElementById("battleDie_defenders") + .addEventListener("click", () => Battle.prototype.context.rollDie("defenders")); } defineType() { @@ -82,8 +100,12 @@ class Battle { document.getElementById("battleType").className = "icon-button-" + this.type; const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers"); - const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content; - const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers; + const attackers = sideSpecific + ? sideSpecific.content + : document.getElementById("battlePhases_" + this.type).content; + const defenders = sideSpecific + ? document.getElementById("battlePhases_" + this.type + "_defenders").content + : attackers; document.getElementById("battlePhase_attackers").nextElementSibling.innerHTML = ""; document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = ""; @@ -139,26 +161,37 @@ class Battle { regiment.survivors = Object.assign({}, regiment.u); 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 distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScale) | 0; // distance between regiment and its base const color = state.color[0] === "#" ? state.color : "#999"; const icon = ` ${regiment.icon}`; const body = ``; - let initial = `${icon}${regiment.name.slice(0, 24)}`; - let casualties = `${state.fullName.slice(0, 26)}`; + let initial = `${icon}${regiment.name.slice(0, 24)}`; + let casualties = `${state.fullName.slice( + 0, + 26 + )}`; let survivors = `Distance to base: ${distance} ${distanceUnitInput.value}`; for (const u of options.military) { - initial += `${regiment.u[u.name] || 0}`; + initial += `${ + regiment.u[u.name] || 0 + }`; casualties += `0`; - survivors += `${regiment.u[u.name] || 0}`; + survivors += `${ + regiment.u[u.name] || 0 + }`; } initial += `${regiment.a || 0}`; casualties += `0`; - survivors += `${regiment.a || 0}`; + survivors += `${ + regiment.a || 0 + }`; const div = side === "attackers" ? battleAttackers : battleDefenders; div.innerHTML += body + initial + casualties + survivors + ""; @@ -173,17 +206,23 @@ class Battle { .filter(s => s.military && !s.removed) .map(s => s.military) .flat(); - const distance = reg => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value; - const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg); + const distance = reg => + rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScale) + " " + distanceUnitInput.value; + const isAdded = reg => + context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg); body.innerHTML = regiments .map(r => { const s = pack.states[r.state], added = isAdded(r), dist = added ? "0 " + distanceUnitInput.value : distance(r); - return `
- +
${s.name.slice(0, 11)}
${r.icon}
${r.name.slice(0, 24)}
@@ -267,7 +306,10 @@ class Battle { } generateName(type) { - const place = type === "culture" ? Names.getCulture(pack.cells.culture[this.cell], null, null, "") : Names.getBase(rand(nameBases.length - 1)); + const place = + type === "culture" + ? Names.getCulture(pack.cells.culture[this.cell], null, null, "") + : Names.getBase(rand(nameBases.length - 1)); document.getElementById("battleNamePlace").value = this.place = place; document.getElementById("battleNameFull").value = this.name = this.defineName(); $("#battleScreen").dialog({title: this.name}); @@ -286,35 +328,161 @@ class Battle { calculateStrength(side) { const scheme = { // field battle phases - skirmish: {melee: 0.2, ranged: 2.4, mounted: 0.1, machinery: 3, naval: 1, armored: 0.2, aviation: 1.8, magical: 1.8}, // ranged excel + skirmish: { + melee: 0.2, + ranged: 2.4, + mounted: 0.1, + machinery: 3, + naval: 1, + armored: 0.2, + aviation: 1.8, + magical: 1.8 + }, // ranged excel melee: {melee: 2, ranged: 1.2, mounted: 1.5, machinery: 0.5, naval: 0.2, armored: 2, aviation: 0.8, magical: 0.8}, // melee excel pursue: {melee: 1, ranged: 1, mounted: 4, machinery: 0.05, naval: 1, armored: 1, aviation: 1.5, magical: 0.6}, // mounted excel - retreat: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.2, armored: 0.1, aviation: 0.8, magical: 0.05}, // reduced + retreat: { + melee: 0.1, + ranged: 0.01, + mounted: 0.5, + machinery: 0.01, + naval: 0.2, + armored: 0.1, + aviation: 0.8, + magical: 0.05 + }, // reduced // naval battle phases shelling: {melee: 0, ranged: 0.2, mounted: 0, machinery: 2, naval: 2, armored: 0, aviation: 0.1, magical: 0.5}, // naval and machinery excel - boarding: {melee: 1, ranged: 0.5, mounted: 0.5, machinery: 0, naval: 0.5, armored: 0.4, aviation: 0, magical: 0.2}, // melee excel + boarding: { + melee: 1, + ranged: 0.5, + mounted: 0.5, + machinery: 0, + naval: 0.5, + armored: 0.4, + aviation: 0, + magical: 0.2 + }, // melee excel chase: {melee: 0, ranged: 0.15, mounted: 0, machinery: 1, naval: 1, armored: 0, aviation: 0.15, magical: 0.5}, // reduced - withdrawal: {melee: 0, ranged: 0.02, mounted: 0, machinery: 0.5, naval: 0.1, armored: 0, aviation: 0.1, magical: 0.3}, // reduced + withdrawal: { + melee: 0, + ranged: 0.02, + mounted: 0, + machinery: 0.5, + naval: 0.1, + armored: 0, + aviation: 0.1, + magical: 0.3 + }, // reduced // siege phases - blockade: {melee: 0.25, ranged: 0.25, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions - sheltering: {melee: 0.3, ranged: 0.5, mounted: 0.2, machinery: 0.5, naval: 0.2, armored: 0.1, aviation: 0.25, magical: 0.25}, // no active actions + blockade: { + melee: 0.25, + ranged: 0.25, + mounted: 0.2, + machinery: 0.5, + naval: 0.2, + armored: 0.1, + aviation: 0.25, + magical: 0.25 + }, // no active actions + sheltering: { + melee: 0.3, + ranged: 0.5, + mounted: 0.2, + machinery: 0.5, + naval: 0.2, + armored: 0.1, + aviation: 0.25, + magical: 0.25 + }, // no active actions sortie: {melee: 2, ranged: 0.5, mounted: 1.2, machinery: 0.2, naval: 0.1, armored: 0.5, aviation: 1, magical: 1}, // melee excel - bombardment: {melee: 0.2, ranged: 0.5, mounted: 0.2, machinery: 3, naval: 1, armored: 0.5, aviation: 1, magical: 1}, // machinery excel - storming: {melee: 1, ranged: 0.6, mounted: 0.5, machinery: 1, naval: 0.1, armored: 0.1, aviation: 0.5, magical: 0.5}, // melee excel + bombardment: { + melee: 0.2, + ranged: 0.5, + mounted: 0.2, + machinery: 3, + naval: 1, + armored: 0.5, + aviation: 1, + magical: 1 + }, // machinery excel + storming: { + melee: 1, + ranged: 0.6, + mounted: 0.5, + machinery: 1, + naval: 0.1, + armored: 0.1, + aviation: 0.5, + magical: 0.5 + }, // melee excel defense: {melee: 2, ranged: 3, mounted: 1, machinery: 1, naval: 0.1, armored: 1, aviation: 0.5, magical: 1}, // ranged excel - looting: {melee: 1.6, ranged: 1.6, mounted: 0.5, machinery: 0.2, naval: 0.02, armored: 0.2, aviation: 0.1, magical: 0.3}, // melee excel - surrendering: {melee: 0.1, ranged: 0.1, mounted: 0.05, machinery: 0.01, naval: 0.01, armored: 0.02, aviation: 0.01, magical: 0.03}, // reduced + looting: { + melee: 1.6, + ranged: 1.6, + mounted: 0.5, + machinery: 0.2, + naval: 0.02, + armored: 0.2, + aviation: 0.1, + magical: 0.3 + }, // melee excel + surrendering: { + melee: 0.1, + ranged: 0.1, + mounted: 0.05, + machinery: 0.01, + naval: 0.01, + armored: 0.02, + aviation: 0.01, + magical: 0.03 + }, // reduced // ambush phases surprise: {melee: 2, ranged: 2.4, mounted: 1, machinery: 1, naval: 1, armored: 1, aviation: 0.8, magical: 1.2}, // increased - shock: {melee: 0.5, ranged: 0.5, mounted: 0.5, machinery: 0.4, naval: 0.3, armored: 0.1, aviation: 0.4, magical: 0.5}, // reduced + shock: { + melee: 0.5, + ranged: 0.5, + mounted: 0.5, + machinery: 0.4, + naval: 0.3, + armored: 0.1, + aviation: 0.4, + magical: 0.5 + }, // reduced // langing phases - landing: {melee: 0.8, ranged: 0.6, mounted: 0.6, machinery: 0.5, naval: 0.5, armored: 0.5, aviation: 0.5, magical: 0.6}, // reduced - flee: {melee: 0.1, ranged: 0.01, mounted: 0.5, machinery: 0.01, naval: 0.5, armored: 0.1, aviation: 0.2, magical: 0.05}, // reduced - waiting: {melee: 0.05, ranged: 0.5, mounted: 0.05, machinery: 0.5, naval: 2, armored: 0.05, aviation: 0.5, magical: 0.5}, // reduced + landing: { + melee: 0.8, + ranged: 0.6, + mounted: 0.6, + machinery: 0.5, + naval: 0.5, + armored: 0.5, + aviation: 0.5, + magical: 0.6 + }, // reduced + flee: { + melee: 0.1, + ranged: 0.01, + mounted: 0.5, + machinery: 0.01, + naval: 0.5, + armored: 0.1, + aviation: 0.2, + magical: 0.05 + }, // reduced + waiting: { + melee: 0.05, + ranged: 0.5, + mounted: 0.05, + machinery: 0.5, + naval: 2, + armored: 0.05, + aviation: 0.5, + magical: 0.5 + }, // reduced // air battle phases maneuvering: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.2, naval: 0, armored: 0, aviation: 1, magical: 0.2}, // aviation @@ -324,7 +492,8 @@ class Battle { const forces = this.getJoinedForces(this[side].regiments); const phase = this[side].phase; const adjuster = Math.max(populationRate / 10, 10); // population adjuster, by default 100 - this[side].power = d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster; + this[side].power = + d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster; const UIvalue = this[side].power ? Math.max(this[side].power | 0, 1) : 0; document.getElementById("battlePower_" + side).innerHTML = UIvalue; } @@ -723,11 +892,13 @@ class Battle { const status = battleStatus[+P(0.7)]; const result = `The ${this.getTypeName(this.type)} ended in ${status}`; - const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide( - this.defenders.regiments, - 0 - )}. ${result}. - \r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`; + const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide( + this.attackers.regiments, + 1 + )} and ${getSide(this.defenders.regiments, 0)}. ${result}. + \r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses( + this.defenders.casualties + )}%`; notes.push({id: `marker${i}`, name: this.name, legend}); tip(`${this.name} is over. ${result}`, true, "success", 4000); diff --git a/modules/ui/editors.js b/modules/ui/editors.js index ab91e641..79dcef4e 100644 --- a/modules/ui/editors.js +++ b/modules/ui/editors.js @@ -1175,7 +1175,6 @@ function getAreaUnit(squareMark = "²") { } function getArea(rawArea) { - const distanceScale = byId("distanceScaleInput")?.value; return rawArea * distanceScale ** 2; } diff --git a/modules/ui/lakes-editor.js b/modules/ui/lakes-editor.js index 7d766672..e4977624 100644 --- a/modules/ui/lakes-editor.js +++ b/modules/ui/lakes-editor.js @@ -48,8 +48,7 @@ function editLake() { document.getElementById("lakeArea").value = si(getArea(l.area)) + " " + getAreaUnit(); const length = d3.polygonLength(l.vertices.map(v => pack.vertices.p[v])); - document.getElementById("lakeShoreLength").value = - si(length * distanceScaleInput.value) + " " + distanceUnitInput.value; + document.getElementById("lakeShoreLength").value = si(length * distanceScale) + " " + distanceUnitInput.value; const lakeCells = Array.from(cells.i.filter(i => cells.f[i] === l.i)); const heights = lakeCells.map(i => cells.h[i]); diff --git a/modules/ui/layers.js b/modules/ui/layers.js index fd1e48fd..d6aefff5 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -1792,7 +1792,6 @@ function toggleScaleBar(event) { function drawScaleBar(scaleBar, scaleLevel) { if (!scaleBar.size() || scaleBar.style("display") === "none") return; - const distanceScale = +distanceScaleInput.value; const unit = distanceUnitInput.value; const size = +scaleBar.attr("data-bar-size"); diff --git a/modules/ui/measurers.js b/modules/ui/measurers.js index e083936c..1f51cd7d 100644 --- a/modules/ui/measurers.js +++ b/modules/ui/measurers.js @@ -66,7 +66,7 @@ class Measurer { } getDash() { - return rn(30 / distanceScaleInput.value, 2); + return rn(30 / distanceScale, 2); } drag() { @@ -205,7 +205,7 @@ class Ruler extends Measurer { updateLabel() { const length = this.getLength(); - const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value; + const text = rn(length * distanceScale) + " " + distanceUnitInput.value; const [x, y] = last(this.points); this.el.select("text").attr("x", x).attr("y", y).text(text); } @@ -337,7 +337,7 @@ class Opisometer extends Measurer { updateLabel() { const length = this.el.select("path").node().getTotalLength(); - const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value; + const text = rn(length * distanceScale) + " " + distanceUnitInput.value; const [x, y] = last(this.points); this.el.select("text").attr("x", x).attr("y", y).text(text); } @@ -475,7 +475,7 @@ class RouteOpisometer extends Measurer { updateLabel() { const length = this.el.select("path").node().getTotalLength(); - const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value; + const text = rn(length * distanceScale) + " " + distanceUnitInput.value; const [x, y] = last(this.points); this.el.select("text").attr("x", x).attr("y", y).text(text); } diff --git a/modules/ui/options.js b/modules/ui/options.js index f4d95951..a35c5815 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -545,6 +545,7 @@ function applyStoredOptions() { lock(key); if (key === "points") changeCellsDensity(+value); + if (key === "distanceScale") distanceScale = +value; // add saved style presets to options if (key.slice(0, 5) === "style") applyOption(stylePreset, key, key.slice(5)); @@ -605,7 +606,8 @@ function randomizeOptions() { // 'Units Editor' settings const US = navigator.language === "en-US"; - if (randomize || !locked("distanceScale")) distanceScaleOutput.value = distanceScaleInput.value = gauss(3, 1, 1, 5); + if (randomize || !locked("distanceScale")) + distanceScale = distanceScaleOutput.value = distanceScaleInput.value = gauss(3, 1, 1, 5); if (!stored("distanceUnit")) distanceUnitInput.value = US ? "mi" : "km"; if (!stored("heightUnit")) heightUnit.value = US ? "ft" : "m"; if (!stored("temperatureScale")) temperatureScale.value = US ? "°F" : "°C"; diff --git a/modules/ui/rivers-editor.js b/modules/ui/rivers-editor.js index 95f7593e..4a061179 100644 --- a/modules/ui/rivers-editor.js +++ b/modules/ui/rivers-editor.js @@ -81,7 +81,7 @@ function editRiver(id) { function updateRiverLength(river) { river.length = rn(elSelected.node().getTotalLength() / 2, 2); - const lengthUI = `${rn(river.length * distanceScaleInput.value)} ${distanceUnitInput.value}`; + const lengthUI = `${rn(river.length * distanceScale)} ${distanceUnitInput.value}`; byId("riverLength").value = lengthUI; } @@ -91,7 +91,7 @@ function editRiver(id) { const meanderedPoints = addMeandering(cells); river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth)); - const width = `${rn(river.width * distanceScaleInput.value, 3)} ${distanceUnitInput.value}`; + const width = `${rn(river.width * distanceScale, 3)} ${distanceUnitInput.value}`; byId("riverWidth").value = width; } @@ -235,7 +235,7 @@ function editRiver(id) { .data() .map(([x, y]) => findCell(x, y)); const river = getRiver(); - const riverLen = rn(river.length * distanceScaleInput.value); + const riverLen = rn(river.length * distanceScale); showElevationProfile(points, riverLen, true); } diff --git a/modules/ui/rivers-overview.js b/modules/ui/rivers-overview.js index 577f702e..7fc32b45 100644 --- a/modules/ui/rivers-overview.js +++ b/modules/ui/rivers-overview.js @@ -35,8 +35,8 @@ function overviewRivers() { for (const r of pack.rivers) { const discharge = r.discharge + " m³/s"; - const length = rn(r.length * distanceScaleInput.value) + " " + unit; - const width = rn(r.width * distanceScaleInput.value, 3) + " " + unit; + const length = rn(r.length * distanceScale) + " " + unit; + const width = rn(r.width * distanceScale, 3) + " " + unit; const basin = pack.rivers.find(river => river.i === r.basin)?.name; lines += /* html */ `
r.discharge))); riversFooterDischarge.innerHTML = averageDischarge + " m³/s"; const averageLength = rn(d3.mean(pack.rivers.map(r => r.length))); - riversFooterLength.innerHTML = averageLength * distanceScaleInput.value + " " + unit; + riversFooterLength.innerHTML = averageLength * distanceScale + " " + unit; const averageWidth = rn(d3.mean(pack.rivers.map(r => r.width)), 3); - riversFooterWidth.innerHTML = rn(averageWidth * distanceScaleInput.value, 3) + " " + unit; + riversFooterWidth.innerHTML = rn(averageWidth * distanceScale, 3) + " " + unit; // add listeners body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => riverHighlightOn(ev))); @@ -143,8 +143,8 @@ function overviewRivers() { body.querySelectorAll(":scope > div").forEach(function (el) { const d = el.dataset; const discharge = d.discharge + " m³/s"; - const length = rn(d.length * distanceScaleInput.value) + " " + distanceUnitInput.value; - const width = rn(d.width * distanceScaleInput.value, 3) + " " + distanceUnitInput.value; + const length = rn(d.length * distanceScale) + " " + distanceUnitInput.value; + const width = rn(d.width * distanceScale, 3) + " " + distanceUnitInput.value; data += [d.id, d.name, d.type, discharge, length, width, d.basin].join(",") + "\n"; }); diff --git a/modules/ui/routes-editor.js b/modules/ui/routes-editor.js index fe51e002..7400e1ad 100644 --- a/modules/ui/routes-editor.js +++ b/modules/ui/routes-editor.js @@ -37,7 +37,7 @@ function editRoute(id) { // add listeners byId("routeCreateSelectingCells").on("click", showCreationDialog); byId("routeSplit").on("click", togglePressed); - byId("routeJoin").on("click", joinRoutes); + byId("routeJoin").on("click", openJoinRoutesDialog); byId("routeElevationProfile").on("click", showRouteElevationProfile); byId("routeLegend").on("click", editRouteLegend); byId("routeRemove").on("click", removeRoute); @@ -71,9 +71,8 @@ function editRoute(id) { } function updateRouteLength(route) { - route.length = rn(elSelected.node().getTotalLength() / 2, 2); - const lengthUI = `${rn(route.length * distanceScale)} ${distanceUnitInput.value}`; - byId("routeLength").value = lengthUI; + route.length = Routes.getLength(route.i); + byId("routeLength").value = rn(route.length * distanceScale) + " " + distanceUnitInput.value; } function drawControlPoints(points) { @@ -247,61 +246,86 @@ function editRoute(id) { } } - function joinRoutes() { + function openJoinRoutesDialog() { const route = getRoute(); const firstCell = route.cells.at(0); const lastCell = route.cells.at(-1); - let joinedRoute = null; - for (const nextRoute of pack.routes) { - if (joinedRoute) break; - if (nextRoute.i === route.i) continue; - if (nextRoute.cells.at(0) === lastCell) joinedRoute = nextRoute; - if (nextRoute.cells.at(-1) === firstCell) joinedRoute = nextRoute; - if (nextRoute.cells.at(0) === firstCell) joinedRoute = nextRoute; - if (nextRoute.cells.at(-1) === lastCell) joinedRoute = nextRoute; - } + const candidateRoutes = pack.routes.filter(r => { + if (r.i === route.i) return false; + if (r.group !== route.group) return false; + if (r.cells.at(0) === lastCell) return true; + if (r.cells.at(-1) === firstCell) return true; + if (r.cells.at(0) === firstCell) return true; + if (r.cells.at(-1) === lastCell) return true; + return false; + }); - if (joinedRoute) { - join(route, joinedRoute); - tip("Routes joined", false, "success", 5000); + if (candidateRoutes.length) { + const options = candidateRoutes.map(r => { + r.name = r.name || Routes.generateName(r); + r.length = r.length || getRouteLength(r.i); + const length = rn(r.length * distanceScale) + " " + unit; + return ``; + }); + alertMessage.innerHTML = /* html */ `
Route to join with: + +
`; + + $("#alert").dialog({ + title: "Join routes", + width: fitContent(), + position: {my: "center", at: "center", of: "svg"}, + buttons: { + Cancel: () => { + $("#alert").dialog("close"); + }, + Join: () => { + const selectedRouteId = +alertMessage.querySelector("select").value; + const selectedRoute = pack.routes.find(r => r.i === selectedRouteId); + joinRoutes(route, selectedRoute); + tip("Routes joined", false, "success", 5000); + $("#alert").dialog("close"); + } + } + }); } else { tip("No routes to join with. Route must start or end at current route's start or end cell", false, "error", 4000); } + } - function join(route, joinedRoute) { - if (!route.points) route.points = debug.selectAll("#controlPoints > *").data(); - if (!joinedRoute.points) joinedRoute.points = Routes.getPoints(joinedRoute, Routes.preparePointsArray()); + function joinRoutes(route, joinedRoute) { + if (!route.points) route.points = debug.selectAll("#controlPoints > *").data(); + if (!joinedRoute.points) joinedRoute.points = Routes.getPoints(joinedRoute, Routes.preparePointsArray()); - if (route.cells.at(-1) === joinedRoute.cells.at(0)) { - // joinedRoute starts at the end of current route - route.cells = [...route.cells, ...joinedRoute.cells.slice(1)]; - route.points = [...route.points, ...joinedRoute.points.slice(1)]; - } else if (route.cells.at(0) === joinedRoute.cells.at(-1)) { - // joinedRoute ends at the start of current route - route.cells = [...joinedRoute.cells, ...route.cells.slice(1)]; - route.points = [...joinedRoute.points, ...route.points.slice(1)]; - } else if (route.cells.at(0) === joinedRoute.cells.at(0)) { - // joinedRoute and current route both start at the same cell - route.cells = [...route.cells.reverse(), ...joinedRoute.cells.slice(1)]; - route.points = [...route.points.reverse(), ...joinedRoute.points.slice(1)]; - } else if (route.cells.at(-1) === joinedRoute.cells.at(-1)) { - // joinedRoute and current route both end at the same cell - route.cells = [...route.cells, ...joinedRoute.cells.reverse().slice(1)]; - route.points = [...route.points, ...joinedRoute.points.reverse().slice(1)]; - } - - for (let i = 0; i < route.cells.length; i++) { - const cellId = route.cells[i]; - const nextCellId = route.cells[i + 1]; - if (nextCellId) addConnection(cellId, nextCellId, route.i); - } - - Routes.remove(joinedRoute); - drawControlPoints(route.points); - drawCells(); - redrawRoute(); + if (route.cells.at(-1) === joinedRoute.cells.at(0)) { + // joinedRoute starts at the end of current route + route.cells = [...route.cells, ...joinedRoute.cells.slice(1)]; + route.points = [...route.points, ...joinedRoute.points.slice(1)]; + } else if (route.cells.at(0) === joinedRoute.cells.at(-1)) { + // joinedRoute ends at the start of current route + route.cells = [...joinedRoute.cells, ...route.cells.slice(1)]; + route.points = [...joinedRoute.points, ...route.points.slice(1)]; + } else if (route.cells.at(0) === joinedRoute.cells.at(0)) { + // joinedRoute and current route both start at the same cell + route.cells = [...route.cells.reverse(), ...joinedRoute.cells.slice(1)]; + route.points = [...route.points.reverse(), ...joinedRoute.points.slice(1)]; + } else if (route.cells.at(-1) === joinedRoute.cells.at(-1)) { + // joinedRoute and current route both end at the same cell + route.cells = [...route.cells, ...joinedRoute.cells.reverse().slice(1)]; + route.points = [...route.points, ...joinedRoute.points.reverse().slice(1)]; } + + for (let i = 0; i < route.cells.length; i++) { + const cellId = route.cells[i]; + const nextCellId = route.cells[i + 1]; + if (nextCellId) addConnection(cellId, nextCellId, route.i); + } + + Routes.remove(joinedRoute); + drawControlPoints(route.points); + drawCells(); + redrawRoute(); } function showCreationDialog() { @@ -346,8 +370,8 @@ function editRoute(id) { function showRouteElevationProfile() { const route = getRoute(); - const routeLen = rn(route.length * distanceScaleInput.value); - showElevationProfile(route.cells, routeLen, false); + const length = rn(route.length * distanceScale); + showElevationProfile(route.cells, length, false); } function editRouteLegend() { diff --git a/modules/ui/routes-overview.js b/modules/ui/routes-overview.js index cb626025..4393eefe 100644 --- a/modules/ui/routes-overview.js +++ b/modules/ui/routes-overview.js @@ -29,12 +29,11 @@ function overviewRoutes() { function routesOverviewAddLines() { body.innerHTML = ""; let lines = ""; - const unit = distanceUnitInput.value; for (const route of pack.routes) { route.name = route.name || Routes.generateName(route); - route.length = route.length || getRouteLength(route.i); - const length = rn(route.length * distanceScale) + " " + unit; + route.length = route.length || Routes.getLength(route.i); + const length = rn(route.length * distanceScale) + " " + distanceUnitInput.value; lines += /* html */ `
r.length))); - routesFooterLength.innerHTML = averageLength * distanceScale + " " + unit; + routesFooterLength.innerHTML = averageLength * distanceScale + " " + distanceUnitInput.value; // add listeners body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", routeHighlightOn)); @@ -68,11 +67,6 @@ function overviewRoutes() { applySorting(routesHeader); } - function getRouteLength(routeId) { - const path = routes.select("#route" + routeId).node(); - return rn(path.getTotalLength() / 2, 2); - } - function routeHighlightOn(event) { if (!layerIsOn("toggleRoutes")) toggleRoutes(); const routeId = +event.target.dataset.id; diff --git a/modules/ui/style.js b/modules/ui/style.js index 67e87928..f6e5dd02 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -503,7 +503,7 @@ styleGridScale.addEventListener("input", function () { function calculateFriendlyGridSize() { const size = styleGridScale.value * 25; - const friendly = `${rn(size * distanceScaleInput.value, 2)} ${distanceUnitInput.value}`; + const friendly = `${rn(size * distanceScale, 2)} ${distanceUnitInput.value}`; styleGridSizeFriendly.value = friendly; } diff --git a/modules/ui/submap.js b/modules/ui/submap.js index 2a476faa..0341f8af 100644 --- a/modules/ui/submap.js +++ b/modules/ui/submap.js @@ -258,11 +258,16 @@ window.UISubmap = (function () { byId("latitudeInput").value = latitudeOutput.value; // fix scale - distanceScaleInput.value = distanceScaleOutput.value = rn((distanceScale = distanceScaleOutput.value / scale), 2); + distanceScale = + distanceScaleInput.value = + distanceScaleOutput.value = + rn((distanceScale = distanceScaleOutput.value / scale), 2); + populationRateInput.value = populationRateOutput.value = rn( (populationRate = populationRateOutput.value / scale), 2 ); + customization = 0; startResample(options); }, 1000); diff --git a/modules/ui/units-editor.js b/modules/ui/units-editor.js index 86079026..ff911567 100644 --- a/modules/ui/units-editor.js +++ b/modules/ui/units-editor.js @@ -55,6 +55,7 @@ function editUnits() { } function changeDistanceScale() { + distanceScale = +this.value; renderScaleBar(); calculateFriendlyGridSize(); } @@ -90,10 +91,9 @@ function editUnits() { } function restoreDefaultUnits() { - // distanceScale distanceScale = 3; - byId("distanceScaleOutput").value = 3; - byId("distanceScaleInput").value = 3; + byId("distanceScaleOutput").value = distanceScale; + byId("distanceScaleInput").value = distanceScale; unlock("distanceScale"); // units diff --git a/modules/ui/world-configurator.js b/modules/ui/world-configurator.js index 9ac0062c..67569592 100644 --- a/modules/ui/world-configurator.js +++ b/modules/ui/world-configurator.js @@ -105,13 +105,12 @@ function editWorld() { calculateMapCoordinates(); const mc = mapCoordinates; - const scale = +distanceScaleInput.value; const unit = distanceUnitInput.value; - const meridian = toKilometer(eqD * 2 * scale); + const meridian = toKilometer(eqD * 2 * distanceScale); byId("mapSize").innerHTML = `${graphWidth}x${graphHeight}`; - byId("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`; + byId("mapSizeFriendly").innerHTML = `${rn(graphWidth * distanceScale)}x${rn(graphHeight * distanceScale)} ${unit}`; byId("meridianLength").innerHTML = rn(eqD * 2); - byId("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`; + byId("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * distanceScale)} ${unit}`; byId("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : ""; byId("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;