feat: routes overview - fix distanceScale value

This commit is contained in:
Azgaar 2024-08-01 13:36:14 +02:00
parent 28bc6ccde6
commit 834d6f6cc7
17 changed files with 328 additions and 130 deletions

View file

@ -86,7 +86,7 @@ function getMapInfo() {
function getSettings() { function getSettings() {
return { return {
distanceUnit: distanceUnitInput.value, distanceUnit: distanceUnitInput.value,
distanceScale: distanceScaleInput.value, distanceScale,
areaUnit: areaUnit.value, areaUnit: areaUnit.value,
heightUnit: heightUnit.value, heightUnit: heightUnit.value,
heightExponent: heightExponentInput.value, heightExponent: heightExponentInput.value,

View file

@ -44,7 +44,7 @@ function prepareMapData() {
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|"); const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|");
const settings = [ const settings = [
distanceUnitInput.value, distanceUnitInput.value,
distanceScaleInput.value, distanceScale,
areaUnit.value, areaUnit.value,
heightUnit.value, heightUnit.value,
heightExponentInput.value, heightExponentInput.value,

View file

@ -601,6 +601,11 @@ window.Routes = (function () {
return routePoints; return routePoints;
} }
function getLength(routeId) {
const path = routes.select("#route" + routeId).node();
return path.getTotalLength();
}
function remove(route) { function remove(route) {
const routes = pack.cells.routes; const routes = pack.cells.routes;
@ -630,6 +635,7 @@ window.Routes = (function () {
generateName, generateName,
preparePointsArray, preparePointsArray,
getPoints, getPoints,
getLength,
remove remove
}; };
})(); })();

View file

@ -37,12 +37,22 @@ class Battle {
// add listeners // add listeners
document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev)); document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev));
document.getElementById("battleType").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev)); document
document.getElementById("battleNameShow").addEventListener("click", () => Battle.prototype.context.showNameSection()); .getElementById("battleType")
document.getElementById("battleNamePlace").addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value)); .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("battleNameFull").addEventListener("change", ev => Battle.prototype.context.changeName(ev));
document.getElementById("battleNameCulture").addEventListener("click", () => Battle.prototype.context.generateName("culture")); document
document.getElementById("battleNameRandom").addEventListener("click", () => Battle.prototype.context.generateName("random")); .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("battleNameHide").addEventListener("click", this.hideNameSection);
document.getElementById("battleAddRegiment").addEventListener("click", this.addSide); document.getElementById("battleAddRegiment").addEventListener("click", this.addSide);
document.getElementById("battleRoll").addEventListener("click", () => Battle.prototype.context.randomize()); 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("battleWiki").addEventListener("click", () => wiki("Battle-Simulator"));
document.getElementById("battlePhase_attackers").addEventListener("click", ev => this.toggleChange(ev)); 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").addEventListener("click", ev => this.toggleChange(ev));
document.getElementById("battlePhase_defenders").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders")); document
document.getElementById("battleDie_attackers").addEventListener("click", () => Battle.prototype.context.rollDie("attackers")); .getElementById("battlePhase_defenders")
document.getElementById("battleDie_defenders").addEventListener("click", () => Battle.prototype.context.rollDie("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() { defineType() {
@ -82,8 +100,12 @@ class Battle {
document.getElementById("battleType").className = "icon-button-" + this.type; document.getElementById("battleType").className = "icon-button-" + this.type;
const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers"); const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers");
const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content; const attackers = sideSpecific
const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers; ? 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_attackers").nextElementSibling.innerHTML = "";
document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = ""; document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = "";
@ -139,26 +161,37 @@ class Battle {
regiment.survivors = Object.assign({}, regiment.u); regiment.survivors = Object.assign({}, regiment.u);
const state = pack.states[regiment.state]; 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 color = state.color[0] === "#" ? state.color : "#999";
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em; stroke: #333"> 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> <rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`; <text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
const body = `<tbody id="battle${state.i}-${regiment.i}">`; const body = `<tbody id="battle${state.i}-${regiment.i}">`;
let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment" data-tip="${regiment.name}">${regiment.name.slice(0, 24)}</td>`; let initial = `<tr class="battleInitial"><td>${icon}</td><td class="regiment" data-tip="${
let casualties = `<tr class="battleCasualties"><td></td><td data-tip="${state.fullName}">${state.fullName.slice(0, 26)}</td>`; regiment.name
}">${regiment.name.slice(0, 24)}</td>`;
let casualties = `<tr class="battleCasualties"><td></td><td data-tip="${state.fullName}">${state.fullName.slice(
0,
26
)}</td>`;
let survivors = `<tr class="battleSurvivors"><td></td><td data-tip="Supply line length, affects morale">Distance to base: ${distance} ${distanceUnitInput.value}</td>`; let survivors = `<tr class="battleSurvivors"><td></td><td data-tip="Supply line length, affects morale">Distance to base: ${distance} ${distanceUnitInput.value}</td>`;
for (const u of options.military) { for (const u of options.military) {
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.u[u.name] || 0}</td>`; initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${
regiment.u[u.name] || 0
}</td>`;
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td>`; casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td>`;
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.u[u.name] || 0}</td>`; survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${
regiment.u[u.name] || 0
}</td>`;
} }
initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.a || 0}</td></tr>`; initial += `<td data-tip="Initial forces" style="width: 2.5em; text-align: center">${regiment.a || 0}</td></tr>`;
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td></tr>`; casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td></tr>`;
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.a || 0}</td></tr>`; survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${
regiment.a || 0
}</td></tr>`;
const div = side === "attackers" ? battleAttackers : battleDefenders; const div = side === "attackers" ? battleAttackers : battleDefenders;
div.innerHTML += body + initial + casualties + survivors + "</tbody>"; div.innerHTML += body + initial + casualties + survivors + "</tbody>";
@ -173,17 +206,23 @@ class Battle {
.filter(s => s.military && !s.removed) .filter(s => s.military && !s.removed)
.map(s => s.military) .map(s => s.military)
.flat(); .flat();
const distance = reg => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value; const distance = reg =>
const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === 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 body.innerHTML = regiments
.map(r => { .map(r => {
const s = pack.states[r.state], const s = pack.states[r.state],
added = isAdded(r), added = isAdded(r),
dist = added ? "0 " + distanceUnitInput.value : distance(r); 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} 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"> data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
<svg width=".9em" height=".9em" style="margin-bottom:-1px; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" ></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:6em">${s.name.slice(0, 11)}</div>
<div style="width:1.2em">${r.icon}</div> <div style="width:1.2em">${r.icon}</div>
<div style="width:13em">${r.name.slice(0, 24)}</div> <div style="width:13em">${r.name.slice(0, 24)}</div>
@ -267,7 +306,10 @@ class Battle {
} }
generateName(type) { 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("battleNamePlace").value = this.place = place;
document.getElementById("battleNameFull").value = this.name = this.defineName(); document.getElementById("battleNameFull").value = this.name = this.defineName();
$("#battleScreen").dialog({title: this.name}); $("#battleScreen").dialog({title: this.name});
@ -286,35 +328,161 @@ class Battle {
calculateStrength(side) { calculateStrength(side) {
const scheme = { const scheme = {
// field battle phases // 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 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 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 // 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 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 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 // 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 blockade: {
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 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 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 bombardment: {
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 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 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 looting: {
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 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 // ambush phases
surprise: {melee: 2, ranged: 2.4, mounted: 1, machinery: 1, naval: 1, armored: 1, aviation: 0.8, magical: 1.2}, // increased 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 // 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 landing: {
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 melee: 0.8,
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 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 // air battle phases
maneuvering: {melee: 0, ranged: 0.1, mounted: 0, machinery: 0.2, naval: 0, armored: 0, aviation: 1, magical: 0.2}, // aviation 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 forces = this.getJoinedForces(this[side].regiments);
const phase = this[side].phase; const phase = this[side].phase;
const adjuster = Math.max(populationRate / 10, 10); // population adjuster, by default 100 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; const UIvalue = this[side].power ? Math.max(this[side].power | 0, 1) : 0;
document.getElementById("battlePower_" + side).innerHTML = UIvalue; document.getElementById("battlePower_" + side).innerHTML = UIvalue;
} }
@ -723,11 +892,13 @@ class Battle {
const status = battleStatus[+P(0.7)]; const status = battleStatus[+P(0.7)];
const result = `The ${this.getTypeName(this.type)} ended in ${status}`; 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( const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(
this.defenders.regiments, this.attackers.regiments,
0 1
)}. ${result}. )} and ${getSide(this.defenders.regiments, 0)}. ${result}.
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`; \r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(
this.defenders.casualties
)}%`;
notes.push({id: `marker${i}`, name: this.name, legend}); notes.push({id: `marker${i}`, name: this.name, legend});
tip(`${this.name} is over. ${result}`, true, "success", 4000); tip(`${this.name} is over. ${result}`, true, "success", 4000);

View file

@ -1175,7 +1175,6 @@ function getAreaUnit(squareMark = "²") {
} }
function getArea(rawArea) { function getArea(rawArea) {
const distanceScale = byId("distanceScaleInput")?.value;
return rawArea * distanceScale ** 2; return rawArea * distanceScale ** 2;
} }

View file

@ -48,8 +48,7 @@ function editLake() {
document.getElementById("lakeArea").value = si(getArea(l.area)) + " " + getAreaUnit(); document.getElementById("lakeArea").value = si(getArea(l.area)) + " " + getAreaUnit();
const length = d3.polygonLength(l.vertices.map(v => pack.vertices.p[v])); const length = d3.polygonLength(l.vertices.map(v => pack.vertices.p[v]));
document.getElementById("lakeShoreLength").value = document.getElementById("lakeShoreLength").value = si(length * distanceScale) + " " + distanceUnitInput.value;
si(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
const lakeCells = Array.from(cells.i.filter(i => cells.f[i] === l.i)); const lakeCells = Array.from(cells.i.filter(i => cells.f[i] === l.i));
const heights = lakeCells.map(i => cells.h[i]); const heights = lakeCells.map(i => cells.h[i]);

View file

@ -1792,7 +1792,6 @@ function toggleScaleBar(event) {
function drawScaleBar(scaleBar, scaleLevel) { function drawScaleBar(scaleBar, scaleLevel) {
if (!scaleBar.size() || scaleBar.style("display") === "none") return; if (!scaleBar.size() || scaleBar.style("display") === "none") return;
const distanceScale = +distanceScaleInput.value;
const unit = distanceUnitInput.value; const unit = distanceUnitInput.value;
const size = +scaleBar.attr("data-bar-size"); const size = +scaleBar.attr("data-bar-size");

View file

@ -66,7 +66,7 @@ class Measurer {
} }
getDash() { getDash() {
return rn(30 / distanceScaleInput.value, 2); return rn(30 / distanceScale, 2);
} }
drag() { drag() {
@ -205,7 +205,7 @@ class Ruler extends Measurer {
updateLabel() { updateLabel() {
const length = this.getLength(); 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); const [x, y] = last(this.points);
this.el.select("text").attr("x", x).attr("y", y).text(text); this.el.select("text").attr("x", x).attr("y", y).text(text);
} }
@ -337,7 +337,7 @@ class Opisometer extends Measurer {
updateLabel() { updateLabel() {
const length = this.el.select("path").node().getTotalLength(); 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); const [x, y] = last(this.points);
this.el.select("text").attr("x", x).attr("y", y).text(text); this.el.select("text").attr("x", x).attr("y", y).text(text);
} }
@ -475,7 +475,7 @@ class RouteOpisometer extends Measurer {
updateLabel() { updateLabel() {
const length = this.el.select("path").node().getTotalLength(); 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); const [x, y] = last(this.points);
this.el.select("text").attr("x", x).attr("y", y).text(text); this.el.select("text").attr("x", x).attr("y", y).text(text);
} }

View file

@ -545,6 +545,7 @@ function applyStoredOptions() {
lock(key); lock(key);
if (key === "points") changeCellsDensity(+value); if (key === "points") changeCellsDensity(+value);
if (key === "distanceScale") distanceScale = +value;
// add saved style presets to options // add saved style presets to options
if (key.slice(0, 5) === "style") applyOption(stylePreset, key, key.slice(5)); if (key.slice(0, 5) === "style") applyOption(stylePreset, key, key.slice(5));
@ -605,7 +606,8 @@ function randomizeOptions() {
// 'Units Editor' settings // 'Units Editor' settings
const US = navigator.language === "en-US"; 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("distanceUnit")) distanceUnitInput.value = US ? "mi" : "km";
if (!stored("heightUnit")) heightUnit.value = US ? "ft" : "m"; if (!stored("heightUnit")) heightUnit.value = US ? "ft" : "m";
if (!stored("temperatureScale")) temperatureScale.value = US ? "°F" : "°C"; if (!stored("temperatureScale")) temperatureScale.value = US ? "°F" : "°C";

View file

@ -81,7 +81,7 @@ function editRiver(id) {
function updateRiverLength(river) { function updateRiverLength(river) {
river.length = rn(elSelected.node().getTotalLength() / 2, 2); 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; byId("riverLength").value = lengthUI;
} }
@ -91,7 +91,7 @@ function editRiver(id) {
const meanderedPoints = addMeandering(cells); const meanderedPoints = addMeandering(cells);
river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth)); 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; byId("riverWidth").value = width;
} }
@ -235,7 +235,7 @@ function editRiver(id) {
.data() .data()
.map(([x, y]) => findCell(x, y)); .map(([x, y]) => findCell(x, y));
const river = getRiver(); const river = getRiver();
const riverLen = rn(river.length * distanceScaleInput.value); const riverLen = rn(river.length * distanceScale);
showElevationProfile(points, riverLen, true); showElevationProfile(points, riverLen, true);
} }

View file

@ -35,8 +35,8 @@ function overviewRivers() {
for (const r of pack.rivers) { for (const r of pack.rivers) {
const discharge = r.discharge + " m³/s"; const discharge = r.discharge + " m³/s";
const length = rn(r.length * distanceScaleInput.value) + " " + unit; const length = rn(r.length * distanceScale) + " " + unit;
const width = rn(r.width * distanceScaleInput.value, 3) + " " + unit; const width = rn(r.width * distanceScale, 3) + " " + unit;
const basin = pack.rivers.find(river => river.i === r.basin)?.name; const basin = pack.rivers.find(river => river.i === r.basin)?.name;
lines += /* html */ `<div lines += /* html */ `<div
@ -67,9 +67,9 @@ function overviewRivers() {
const averageDischarge = rn(d3.mean(pack.rivers.map(r => r.discharge))); const averageDischarge = rn(d3.mean(pack.rivers.map(r => r.discharge)));
riversFooterDischarge.innerHTML = averageDischarge + " m³/s"; riversFooterDischarge.innerHTML = averageDischarge + " m³/s";
const averageLength = rn(d3.mean(pack.rivers.map(r => r.length))); 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); 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 // add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => riverHighlightOn(ev))); 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) { body.querySelectorAll(":scope > div").forEach(function (el) {
const d = el.dataset; const d = el.dataset;
const discharge = d.discharge + " m³/s"; const discharge = d.discharge + " m³/s";
const length = rn(d.length * distanceScaleInput.value) + " " + distanceUnitInput.value; const length = rn(d.length * distanceScale) + " " + distanceUnitInput.value;
const width = rn(d.width * distanceScaleInput.value, 3) + " " + 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"; data += [d.id, d.name, d.type, discharge, length, width, d.basin].join(",") + "\n";
}); });

View file

@ -37,7 +37,7 @@ function editRoute(id) {
// add listeners // add listeners
byId("routeCreateSelectingCells").on("click", showCreationDialog); byId("routeCreateSelectingCells").on("click", showCreationDialog);
byId("routeSplit").on("click", togglePressed); byId("routeSplit").on("click", togglePressed);
byId("routeJoin").on("click", joinRoutes); byId("routeJoin").on("click", openJoinRoutesDialog);
byId("routeElevationProfile").on("click", showRouteElevationProfile); byId("routeElevationProfile").on("click", showRouteElevationProfile);
byId("routeLegend").on("click", editRouteLegend); byId("routeLegend").on("click", editRouteLegend);
byId("routeRemove").on("click", removeRoute); byId("routeRemove").on("click", removeRoute);
@ -71,9 +71,8 @@ function editRoute(id) {
} }
function updateRouteLength(route) { function updateRouteLength(route) {
route.length = rn(elSelected.node().getTotalLength() / 2, 2); route.length = Routes.getLength(route.i);
const lengthUI = `${rn(route.length * distanceScale)} ${distanceUnitInput.value}`; byId("routeLength").value = rn(route.length * distanceScale) + " " + distanceUnitInput.value;
byId("routeLength").value = lengthUI;
} }
function drawControlPoints(points) { function drawControlPoints(points) {
@ -247,29 +246,55 @@ function editRoute(id) {
} }
} }
function joinRoutes() { function openJoinRoutesDialog() {
const route = getRoute(); const route = getRoute();
const firstCell = route.cells.at(0); const firstCell = route.cells.at(0);
const lastCell = route.cells.at(-1); const lastCell = route.cells.at(-1);
let joinedRoute = null; const candidateRoutes = pack.routes.filter(r => {
for (const nextRoute of pack.routes) { if (r.i === route.i) return false;
if (joinedRoute) break; if (r.group !== route.group) return false;
if (nextRoute.i === route.i) continue; if (r.cells.at(0) === lastCell) return true;
if (nextRoute.cells.at(0) === lastCell) joinedRoute = nextRoute; if (r.cells.at(-1) === firstCell) return true;
if (nextRoute.cells.at(-1) === firstCell) joinedRoute = nextRoute; if (r.cells.at(0) === firstCell) return true;
if (nextRoute.cells.at(0) === firstCell) joinedRoute = nextRoute; if (r.cells.at(-1) === lastCell) return true;
if (nextRoute.cells.at(-1) === lastCell) joinedRoute = nextRoute; return false;
} });
if (joinedRoute) { if (candidateRoutes.length) {
join(route, joinedRoute); 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 `<option value="${r.i}">${r.name} (${length})</option>`;
});
alertMessage.innerHTML = /* html */ `<div>Route to join with:
<select>${options.join("")}</select>
</div>`;
$("#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); tip("Routes joined", false, "success", 5000);
$("#alert").dialog("close");
}
}
});
} else { } else {
tip("No routes to join with. Route must start or end at current route's start or end cell", false, "error", 4000); 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) { function joinRoutes(route, joinedRoute) {
if (!route.points) route.points = debug.selectAll("#controlPoints > *").data(); if (!route.points) route.points = debug.selectAll("#controlPoints > *").data();
if (!joinedRoute.points) joinedRoute.points = Routes.getPoints(joinedRoute, Routes.preparePointsArray()); if (!joinedRoute.points) joinedRoute.points = Routes.getPoints(joinedRoute, Routes.preparePointsArray());
@ -302,7 +327,6 @@ function editRoute(id) {
drawCells(); drawCells();
redrawRoute(); redrawRoute();
} }
}
function showCreationDialog() { function showCreationDialog() {
const route = getRoute(); const route = getRoute();
@ -346,8 +370,8 @@ function editRoute(id) {
function showRouteElevationProfile() { function showRouteElevationProfile() {
const route = getRoute(); const route = getRoute();
const routeLen = rn(route.length * distanceScaleInput.value); const length = rn(route.length * distanceScale);
showElevationProfile(route.cells, routeLen, false); showElevationProfile(route.cells, length, false);
} }
function editRouteLegend() { function editRouteLegend() {

View file

@ -29,12 +29,11 @@ function overviewRoutes() {
function routesOverviewAddLines() { function routesOverviewAddLines() {
body.innerHTML = ""; body.innerHTML = "";
let lines = ""; let lines = "";
const unit = distanceUnitInput.value;
for (const route of pack.routes) { for (const route of pack.routes) {
route.name = route.name || Routes.generateName(route); route.name = route.name || Routes.generateName(route);
route.length = route.length || getRouteLength(route.i); route.length = route.length || Routes.getLength(route.i);
const length = rn(route.length * distanceScale) + " " + unit; const length = rn(route.length * distanceScale) + " " + distanceUnitInput.value;
lines += /* html */ `<div lines += /* html */ `<div
class="states" class="states"
@ -56,7 +55,7 @@ function overviewRoutes() {
// update footer // update footer
routesFooterNumber.innerHTML = pack.routes.length; routesFooterNumber.innerHTML = pack.routes.length;
const averageLength = rn(d3.mean(pack.routes.map(r => r.length))); const averageLength = rn(d3.mean(pack.routes.map(r => r.length)));
routesFooterLength.innerHTML = averageLength * distanceScale + " " + unit; routesFooterLength.innerHTML = averageLength * distanceScale + " " + distanceUnitInput.value;
// add listeners // add listeners
body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", routeHighlightOn)); body.querySelectorAll("div.states").forEach(el => el.on("mouseenter", routeHighlightOn));
@ -68,11 +67,6 @@ function overviewRoutes() {
applySorting(routesHeader); applySorting(routesHeader);
} }
function getRouteLength(routeId) {
const path = routes.select("#route" + routeId).node();
return rn(path.getTotalLength() / 2, 2);
}
function routeHighlightOn(event) { function routeHighlightOn(event) {
if (!layerIsOn("toggleRoutes")) toggleRoutes(); if (!layerIsOn("toggleRoutes")) toggleRoutes();
const routeId = +event.target.dataset.id; const routeId = +event.target.dataset.id;

View file

@ -503,7 +503,7 @@ styleGridScale.addEventListener("input", function () {
function calculateFriendlyGridSize() { function calculateFriendlyGridSize() {
const size = styleGridScale.value * 25; 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; styleGridSizeFriendly.value = friendly;
} }

View file

@ -258,11 +258,16 @@ window.UISubmap = (function () {
byId("latitudeInput").value = latitudeOutput.value; byId("latitudeInput").value = latitudeOutput.value;
// fix scale // 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( populationRateInput.value = populationRateOutput.value = rn(
(populationRate = populationRateOutput.value / scale), (populationRate = populationRateOutput.value / scale),
2 2
); );
customization = 0; customization = 0;
startResample(options); startResample(options);
}, 1000); }, 1000);

View file

@ -55,6 +55,7 @@ function editUnits() {
} }
function changeDistanceScale() { function changeDistanceScale() {
distanceScale = +this.value;
renderScaleBar(); renderScaleBar();
calculateFriendlyGridSize(); calculateFriendlyGridSize();
} }
@ -90,10 +91,9 @@ function editUnits() {
} }
function restoreDefaultUnits() { function restoreDefaultUnits() {
// distanceScale
distanceScale = 3; distanceScale = 3;
byId("distanceScaleOutput").value = 3; byId("distanceScaleOutput").value = distanceScale;
byId("distanceScaleInput").value = 3; byId("distanceScaleInput").value = distanceScale;
unlock("distanceScale"); unlock("distanceScale");
// units // units

View file

@ -105,13 +105,12 @@ function editWorld() {
calculateMapCoordinates(); calculateMapCoordinates();
const mc = mapCoordinates; const mc = mapCoordinates;
const scale = +distanceScaleInput.value;
const unit = distanceUnitInput.value; const unit = distanceUnitInput.value;
const meridian = toKilometer(eqD * 2 * scale); const meridian = toKilometer(eqD * 2 * distanceScale);
byId("mapSize").innerHTML = `${graphWidth}x${graphHeight}`; 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("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("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`; byId("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;