mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-24 13:01:24 +01:00
merge completed... now to fix all the bugs...
This commit is contained in:
commit
87c4d80fbc
3472 changed files with 466748 additions and 6517 deletions
159
modules/ui/3d.js
159
modules/ui/3d.js
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,8 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
class Battle {
|
||||
constructor(attacker, defender) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
closeDialogs('.stable');
|
||||
customization = 13; // enter customization to avoid unwanted dialog closing
|
||||
|
||||
Battle.prototype.context = this; // store context
|
||||
|
|
@ -14,21 +14,21 @@ class Battle {
|
|||
this.defenders = {regiments: [], distances: [], morale: 100, casualties: 0, power: 0};
|
||||
|
||||
this.addHeaders();
|
||||
this.addRegiment("attackers", attacker);
|
||||
this.addRegiment("defenders", defender);
|
||||
this.addRegiment('attackers', attacker);
|
||||
this.addRegiment('defenders', defender);
|
||||
this.place = this.definePlace();
|
||||
this.defineType();
|
||||
this.name = this.defineName();
|
||||
this.randomize();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
this.getInitialMorale();
|
||||
|
||||
$("#battleScreen").dialog({
|
||||
$('#battleScreen').dialog({
|
||||
title: this.name,
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: "center", at: "center", of: "#map"},
|
||||
position: {my: 'center', at: 'center', of: '#map'},
|
||||
close: () => Battle.prototype.context.cancelResults()
|
||||
});
|
||||
|
||||
|
|
@ -36,42 +36,42 @@ class Battle {
|
|||
modules.Battle = true;
|
||||
|
||||
// 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("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("battleNameHide").addEventListener("click", this.hideNameSection);
|
||||
document.getElementById("battleAddRegiment").addEventListener("click", this.addSide);
|
||||
document.getElementById("battleRoll").addEventListener("click", () => Battle.prototype.context.randomize());
|
||||
document.getElementById("battleRun").addEventListener("click", () => Battle.prototype.context.run());
|
||||
document.getElementById("battleApply").addEventListener("click", () => Battle.prototype.context.applyResults());
|
||||
document.getElementById("battleCancel").addEventListener("click", () => Battle.prototype.context.cancelResults());
|
||||
document.getElementById("battleWiki").addEventListener("click", () => wiki("Battle-Simulator"));
|
||||
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('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('battleNameHide').addEventListener('click', this.hideNameSection);
|
||||
document.getElementById('battleAddRegiment').addEventListener('click', this.addSide);
|
||||
document.getElementById('battleRoll').addEventListener('click', () => Battle.prototype.context.randomize());
|
||||
document.getElementById('battleRun').addEventListener('click', () => Battle.prototype.context.run());
|
||||
document.getElementById('battleApply').addEventListener('click', () => Battle.prototype.context.applyResults());
|
||||
document.getElementById('battleCancel').addEventListener('click', () => Battle.prototype.context.cancelResults());
|
||||
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_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_attackers').addEventListener('click', (ev) => this.toggleChange(ev));
|
||||
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'));
|
||||
}
|
||||
|
||||
defineType() {
|
||||
const attacker = this.attackers.regiments[0];
|
||||
const defender = this.defenders.regiments[0];
|
||||
const getType = () => {
|
||||
const typesA = Object.keys(attacker.u).map(name => options.military.find(u => u.name === name).type);
|
||||
const typesD = Object.keys(defender.u).map(name => options.military.find(u => u.name === name).type);
|
||||
const typesA = Object.keys(attacker.u).map((name) => options.military.find((u) => u.name === name).type);
|
||||
const typesD = Object.keys(defender.u).map((name) => options.military.find((u) => u.name === name).type);
|
||||
|
||||
if (attacker.n && defender.n) return "naval"; // attacker and defender are navals
|
||||
if (typesA.every(t => t === "aviation") && typesD.every(t => t === "aviation")) return "air"; // if attackers and defender have only aviation units
|
||||
if (attacker.n && !defender.n && typesA.some(t => t !== "naval")) return "landing"; // if attacked is naval with non-naval units and defender is not naval
|
||||
if (!defender.n && pack.burgs[pack.cells.burg[this.cell]].walls) return "siege"; // defender is in walled town
|
||||
if (P(0.1) && [5, 6, 7, 8, 9, 12].includes(pack.cells.biome[this.cell])) return "ambush"; // 20% if defenders are in forest or marshes
|
||||
return "field";
|
||||
if (attacker.n && defender.n) return 'naval'; // attacker and defender are navals
|
||||
if (typesA.every((t) => t === 'aviation') && typesD.every((t) => t === 'aviation')) return 'air'; // if attackers and defender have only aviation units
|
||||
if (attacker.n && !defender.n && typesA.some((t) => t !== 'naval')) return 'landing'; // if attacked is naval with non-naval units and defender is not naval
|
||||
if (!defender.n && pack.burgs[pack.cells.burg[this.cell]].walls) return 'siege'; // defender is in walled town
|
||||
if (P(0.1) && [5, 6, 7, 8, 9, 12].includes(pack.cells.biome[this.cell])) return 'ambush'; // 20% if defenders are in forest or marshes
|
||||
return 'field';
|
||||
};
|
||||
|
||||
this.type = getType();
|
||||
|
|
@ -79,25 +79,25 @@ class Battle {
|
|||
}
|
||||
|
||||
setType() {
|
||||
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 attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content;
|
||||
const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers;
|
||||
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;
|
||||
|
||||
document.getElementById("battlePhase_attackers").nextElementSibling.innerHTML = "";
|
||||
document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = "";
|
||||
document.getElementById("battlePhase_attackers").nextElementSibling.append(attackers.cloneNode(true));
|
||||
document.getElementById("battlePhase_defenders").nextElementSibling.append(defenders.cloneNode(true));
|
||||
document.getElementById('battlePhase_attackers').nextElementSibling.innerHTML = '';
|
||||
document.getElementById('battlePhase_defenders').nextElementSibling.innerHTML = '';
|
||||
document.getElementById('battlePhase_attackers').nextElementSibling.append(attackers.cloneNode(true));
|
||||
document.getElementById('battlePhase_defenders').nextElementSibling.append(defenders.cloneNode(true));
|
||||
}
|
||||
|
||||
definePlace() {
|
||||
const cells = pack.cells,
|
||||
i = this.cell;
|
||||
const burg = cells.burg[i] ? pack.burgs[cells.burg[i]].name : null;
|
||||
const getRiver = i => {
|
||||
const river = pack.rivers.find(r => r.i === i);
|
||||
return river.name + " " + river.type;
|
||||
const getRiver = (i) => {
|
||||
const river = pack.rivers.find((r) => r.i === i);
|
||||
return river.name + ' ' + river.type;
|
||||
};
|
||||
const river = !burg && cells.r[i] ? getRiver(cells.r[i]) : null;
|
||||
const proper = burg || river ? null : Names.getCulture(cells.culture[this.cell]);
|
||||
|
|
@ -105,28 +105,28 @@ class Battle {
|
|||
}
|
||||
|
||||
defineName() {
|
||||
if (this.type === "field") return "Battle of " + this.place;
|
||||
if (this.type === "naval") return "Naval Battle of " + this.place;
|
||||
if (this.type === "siege") return "Siege of " + this.place;
|
||||
if (this.type === "ambush") return this.place + " Ambush";
|
||||
if (this.type === "landing") return this.place + " Landing";
|
||||
if (this.type === "air") return `${this.place} ${P(0.8) ? "Air Battle" : "Dogfight"}`;
|
||||
if (this.type === 'field') return 'Battle of ' + this.place;
|
||||
if (this.type === 'naval') return 'Naval Battle of ' + this.place;
|
||||
if (this.type === 'siege') return 'Siege of ' + this.place;
|
||||
if (this.type === 'ambush') return this.place + ' Ambush';
|
||||
if (this.type === 'landing') return this.place + ' Landing';
|
||||
if (this.type === 'air') return `${this.place} ${P(0.8) ? 'Air Battle' : 'Dogfight'}`;
|
||||
}
|
||||
|
||||
getTypeName() {
|
||||
if (this.type === "field") return "field battle";
|
||||
if (this.type === "naval") return "naval battle";
|
||||
if (this.type === "siege") return "siege";
|
||||
if (this.type === "ambush") return "ambush";
|
||||
if (this.type === "landing") return "landing";
|
||||
if (this.type === "air") return "battle";
|
||||
if (this.type === 'field') return 'field battle';
|
||||
if (this.type === 'naval') return 'naval battle';
|
||||
if (this.type === 'siege') return 'siege';
|
||||
if (this.type === 'ambush') return 'ambush';
|
||||
if (this.type === 'landing') return 'landing';
|
||||
if (this.type === 'air') return 'battle';
|
||||
}
|
||||
|
||||
addHeaders() {
|
||||
let headers = "<thead><tr><th></th><th></th>";
|
||||
let headers = '<thead><tr><th></th><th></th>';
|
||||
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, " "));
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
headers += `<th data-tip="${label}">${u.icon}</th>`;
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ 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 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>
|
||||
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
|
||||
|
|
@ -160,28 +160,28 @@ class Battle {
|
|||
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>`;
|
||||
|
||||
const div = side === "attackers" ? battleAttackers : battleDefenders;
|
||||
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
|
||||
const div = side === 'attackers' ? battleAttackers : battleDefenders;
|
||||
div.innerHTML += body + initial + casualties + survivors + '</tbody>';
|
||||
this[side].regiments.push(regiment);
|
||||
this[side].distances.push(distance);
|
||||
}
|
||||
|
||||
addSide() {
|
||||
const body = document.getElementById("regimentSelectorBody");
|
||||
const body = document.getElementById('regimentSelectorBody');
|
||||
const context = Battle.prototype.context;
|
||||
const regiments = pack.states
|
||||
.filter(s => s.military && !s.removed)
|
||||
.map(s => s.military)
|
||||
.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) * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
const isAdded = (reg) => context.defenders.regiments.some((r) => r === reg) || context.attackers.regiments.some((r) => r === reg);
|
||||
|
||||
body.innerHTML = regiments
|
||||
.map(r => {
|
||||
.map((r) => {
|
||||
const s = pack.states[r.state],
|
||||
added = isAdded(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}
|
||||
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>
|
||||
<div style="width:6em">${s.name.slice(0, 11)}</div>
|
||||
|
|
@ -191,43 +191,43 @@ class Battle {
|
|||
<div style="width:4em">${dist}</div>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
.join('');
|
||||
|
||||
$("#regimentSelectorScreen").dialog({
|
||||
$('#regimentSelectorScreen').dialog({
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
title: "Add regiment to the battle",
|
||||
position: {my: "left center", at: "right+10 center", of: "#battleScreen"},
|
||||
title: 'Add regiment to the battle',
|
||||
position: {my: 'left center', at: 'right+10 center', of: '#battleScreen'},
|
||||
close: addSideClosed,
|
||||
buttons: {
|
||||
"Add to attackers": () => addSideClicked("attackers"),
|
||||
"Add to defenders": () => addSideClicked("defenders"),
|
||||
Cancel: () => $("#regimentSelectorScreen").dialog("close")
|
||||
'Add to attackers': () => addSideClicked('attackers'),
|
||||
'Add to defenders': () => addSideClicked('defenders'),
|
||||
Cancel: () => $('#regimentSelectorScreen').dialog('close')
|
||||
}
|
||||
});
|
||||
|
||||
applySorting(regimentSelectorHeader);
|
||||
body.addEventListener("click", selectLine);
|
||||
body.addEventListener('click', selectLine);
|
||||
|
||||
function selectLine(ev) {
|
||||
if (ev.target.className === "inactive") {
|
||||
tip("Regiment is already in the battle", false, "error");
|
||||
if (ev.target.className === 'inactive') {
|
||||
tip('Regiment is already in the battle', false, 'error');
|
||||
return;
|
||||
}
|
||||
ev.target.classList.toggle("selected");
|
||||
ev.target.classList.toggle('selected');
|
||||
}
|
||||
|
||||
function addSideClicked(side) {
|
||||
const selected = body.querySelectorAll(".selected");
|
||||
const selected = body.querySelectorAll('.selected');
|
||||
if (!selected.length) {
|
||||
tip("Please select a regiment first", false, "error");
|
||||
tip('Please select a regiment first', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$("#regimentSelectorScreen").dialog("close");
|
||||
selected.forEach(line => {
|
||||
$('#regimentSelectorScreen').dialog('close');
|
||||
selected.forEach((line) => {
|
||||
const state = pack.states[line.dataset.s];
|
||||
const regiment = state.military.find(r => r.i == +line.dataset.i);
|
||||
const regiment = state.military.find((r) => r.i == +line.dataset.i);
|
||||
Battle.prototype.addRegiment.call(context, side, regiment);
|
||||
Battle.prototype.calculateStrength.call(context, side);
|
||||
Battle.prototype.getInitialMorale.call(context);
|
||||
|
|
@ -235,7 +235,7 @@ class Battle {
|
|||
// move regiment
|
||||
const defenders = context.defenders.regiments,
|
||||
attackers = context.attackers.regiments;
|
||||
const shift = side === "attackers" ? attackers.length * -8 : (defenders.length - 1) * 8;
|
||||
const shift = side === 'attackers' ? attackers.length * -8 : (defenders.length - 1) * 8;
|
||||
regiment.px = regiment.x;
|
||||
regiment.py = regiment.y;
|
||||
Military.moveRegiment(regiment, defenders[0].x, defenders[0].y + shift);
|
||||
|
|
@ -243,34 +243,34 @@ class Battle {
|
|||
}
|
||||
|
||||
function addSideClosed() {
|
||||
body.innerHTML = "";
|
||||
body.removeEventListener("click", selectLine);
|
||||
body.innerHTML = '';
|
||||
body.removeEventListener('click', selectLine);
|
||||
}
|
||||
}
|
||||
|
||||
showNameSection() {
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("battleNameSection").style.display = "inline-block";
|
||||
document.querySelectorAll('#battleBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('battleNameSection').style.display = 'inline-block';
|
||||
|
||||
document.getElementById("battleNamePlace").value = this.place;
|
||||
document.getElementById("battleNameFull").value = this.name;
|
||||
document.getElementById('battleNamePlace').value = this.place;
|
||||
document.getElementById('battleNameFull').value = this.name;
|
||||
}
|
||||
|
||||
hideNameSection() {
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("battleNameSection").style.display = "none";
|
||||
document.querySelectorAll('#battleBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('battleNameSection').style.display = 'none';
|
||||
}
|
||||
|
||||
changeName(ev) {
|
||||
this.name = ev.target.value;
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
$('#battleScreen').dialog({title: this.name});
|
||||
}
|
||||
|
||||
generateName(type) {
|
||||
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});
|
||||
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});
|
||||
}
|
||||
|
||||
getJoinedForces(regiments) {
|
||||
|
|
@ -324,38 +324,38 @@ 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;
|
||||
document.getElementById('battlePower_' + side).innerHTML = UIvalue;
|
||||
}
|
||||
|
||||
getInitialMorale() {
|
||||
const powerFee = diff => Math.min(Math.max(100 - diff ** 1.5 * 10 + 10, 50), 100);
|
||||
const distanceFee = dist => Math.min(d3.mean(dist) / 50, 15);
|
||||
const powerFee = (diff) => minmax(100 - diff ** 1.5 * 10 + 10, 50, 100);
|
||||
const distanceFee = (dist) => Math.min(d3.mean(dist) / 50, 15);
|
||||
const powerDiff = this.defenders.power / this.attackers.power;
|
||||
this.attackers.morale = powerFee(powerDiff) - distanceFee(this.attackers.distances);
|
||||
this.defenders.morale = powerFee(1 / powerDiff) - distanceFee(this.defenders.distances);
|
||||
this.updateMorale("attackers");
|
||||
this.updateMorale("defenders");
|
||||
this.updateMorale('attackers');
|
||||
this.updateMorale('defenders');
|
||||
}
|
||||
|
||||
updateMorale(side) {
|
||||
const morale = document.getElementById("battleMorale_" + side);
|
||||
morale.dataset.tip = morale.dataset.tip.replace(morale.value, "");
|
||||
const morale = document.getElementById('battleMorale_' + side);
|
||||
morale.dataset.tip = morale.dataset.tip.replace(morale.value, '');
|
||||
morale.value = this[side].morale | 0;
|
||||
morale.dataset.tip += morale.value;
|
||||
}
|
||||
|
||||
randomize() {
|
||||
this.rollDie("attackers");
|
||||
this.rollDie("defenders");
|
||||
this.rollDie('attackers');
|
||||
this.rollDie('defenders');
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
}
|
||||
|
||||
rollDie(side) {
|
||||
const el = document.getElementById("battleDie_" + side);
|
||||
const el = document.getElementById('battleDie_' + side);
|
||||
const prev = +el.innerHTML;
|
||||
do {
|
||||
el.innerHTML = rand(1, 6);
|
||||
|
|
@ -369,131 +369,131 @@ class Battle {
|
|||
const powerRatio = this.attackers.power / this.defenders.power;
|
||||
|
||||
const getFieldBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "skirmish", this.defenders.phase || "skirmish"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'skirmish', this.defenders.phase || 'skirmish']; // previous phase
|
||||
|
||||
// chance if moral < 25
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
if (P(1 - morale[0] / 25)) return ['retreat', 'pursue'];
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat'];
|
||||
|
||||
// skirmish phase continuation depends on ranged forces number
|
||||
if (prev[0] === "skirmish" && prev[1] === "skirmish") {
|
||||
if (prev[0] === 'skirmish' && prev[1] === 'skirmish') {
|
||||
const forces = this.getJoinedForces(this.attackers.regiments.concat(this.defenders.regiments));
|
||||
const total = d3.sum(Object.values(forces)); // total forces
|
||||
const ranged =
|
||||
d3.sum(
|
||||
options.military
|
||||
.filter(u => u.type === "ranged")
|
||||
.map(u => u.name)
|
||||
.map(u => forces[u])
|
||||
.filter((u) => u.type === 'ranged')
|
||||
.map((u) => u.name)
|
||||
.map((u) => forces[u])
|
||||
) / total; // ranged units
|
||||
if (P(ranged) || P(0.8 - i / 10)) return ["skirmish", "skirmish"];
|
||||
if (P(ranged) || P(0.8 - i / 10)) return ['skirmish', 'skirmish'];
|
||||
}
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
return ['melee', 'melee']; // default option
|
||||
};
|
||||
|
||||
const getNavalBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "shelling", this.defenders.phase || "shelling"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'shelling', this.defenders.phase || 'shelling']; // previous phase
|
||||
|
||||
if (prev[0] === "withdrawal") return ["withdrawal", "chase"];
|
||||
if (prev[0] === "chase") return ["chase", "withdrawal"];
|
||||
if (prev[0] === 'withdrawal') return ['withdrawal', 'chase'];
|
||||
if (prev[0] === 'chase') return ['chase', 'withdrawal'];
|
||||
|
||||
// withdrawal phase when power imbalanced
|
||||
if (!prev[0] === "boarding") {
|
||||
if (powerRatio < 0.5 || (P(this.attackers.casualties) && powerRatio < 1)) return ["withdrawal", "chase"];
|
||||
if (powerRatio > 2 || (P(this.defenders.casualties) && powerRatio > 1)) return ["chase", "withdrawal"];
|
||||
if (!prev[0] === 'boarding') {
|
||||
if (powerRatio < 0.5 || (P(this.attackers.casualties) && powerRatio < 1)) return ['withdrawal', 'chase'];
|
||||
if (powerRatio > 2 || (P(this.defenders.casualties) && powerRatio > 1)) return ['chase', 'withdrawal'];
|
||||
}
|
||||
|
||||
// boarding phase can start from 2nd iteration
|
||||
if (prev[0] === "boarding" || P(i / 10 - 0.1)) return ["boarding", "boarding"];
|
||||
if (prev[0] === 'boarding' || P(i / 10 - 0.1)) return ['boarding', 'boarding'];
|
||||
|
||||
return ["shelling", "shelling"]; // default option
|
||||
return ['shelling', 'shelling']; // default option
|
||||
};
|
||||
|
||||
const getSiegePhase = () => {
|
||||
const prev = [this.attackers.phase || "blockade", this.defenders.phase || "sheltering"]; // previous phase
|
||||
let phase = ["blockade", "sheltering"]; // default phase
|
||||
const prev = [this.attackers.phase || 'blockade', this.defenders.phase || 'sheltering']; // previous phase
|
||||
let phase = ['blockade', 'sheltering']; // default phase
|
||||
|
||||
if (prev[0] === "retreat" || prev[0] === "looting") return prev;
|
||||
if (prev[0] === 'retreat' || prev[0] === 'looting') return prev;
|
||||
|
||||
if (P(1 - morale[0] / 30) && powerRatio < 1) return ["retreat", "pursue"]; // attackers retreat chance if moral < 30
|
||||
if (P(1 - morale[1] / 15)) return ["looting", "surrendering"]; // defenders surrendering chance if moral < 15
|
||||
if (P(1 - morale[0] / 30) && powerRatio < 1) return ['retreat', 'pursue']; // attackers retreat chance if moral < 30
|
||||
if (P(1 - morale[1] / 15)) return ['looting', 'surrendering']; // defenders surrendering chance if moral < 15
|
||||
|
||||
if (P((powerRatio - 1) / 2)) return ["storming", "defense"]; // start storm
|
||||
if (P((powerRatio - 1) / 2)) return ['storming', 'defense']; // start storm
|
||||
|
||||
if (prev[0] !== "storming") {
|
||||
const machinery = options.military.filter(u => u.type === "machinery").map(u => u.name); // machinery units
|
||||
if (prev[0] !== 'storming') {
|
||||
const machinery = options.military.filter((u) => u.type === 'machinery').map((u) => u.name); // machinery units
|
||||
|
||||
const attackers = this.getJoinedForces(this.attackers.regiments);
|
||||
const machineryA = d3.sum(machinery.map(u => attackers[u]));
|
||||
if (i && machineryA && P(0.9)) phase[0] = "bombardment";
|
||||
const machineryA = d3.sum(machinery.map((u) => attackers[u]));
|
||||
if (i && machineryA && P(0.9)) phase[0] = 'bombardment';
|
||||
|
||||
const defenders = this.getJoinedForces(this.defenders.regiments);
|
||||
const machineryD = d3.sum(machinery.map(u => defenders[u]));
|
||||
if (machineryD && P(0.9)) phase[1] = "bombardment";
|
||||
const machineryD = d3.sum(machinery.map((u) => defenders[u]));
|
||||
if (machineryD && P(0.9)) phase[1] = 'bombardment';
|
||||
|
||||
if (i && prev[1] !== "sortie" && machineryD < machineryA && P(0.25) && P(morale[1] / 70)) phase[1] = "sortie"; // defenders sortie
|
||||
if (i && prev[1] !== 'sortie' && machineryD < machineryA && P(0.25) && P(morale[1] / 70)) phase[1] = 'sortie'; // defenders sortie
|
||||
}
|
||||
|
||||
return phase;
|
||||
};
|
||||
|
||||
const getAmbushPhase = () => {
|
||||
const prev = [this.attackers.phase || "shock", this.defenders.phase || "surprise"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'shock', this.defenders.phase || 'surprise']; // previous phase
|
||||
|
||||
if (prev[1] === "surprise" && P(1 - (powerRatio * i) / 5)) return ["shock", "surprise"];
|
||||
if (prev[1] === 'surprise' && P(1 - (powerRatio * i) / 5)) return ['shock', 'surprise'];
|
||||
|
||||
// chance if moral < 25
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
if (P(1 - morale[0] / 25)) return ['retreat', 'pursue'];
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat'];
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
return ['melee', 'melee']; // default option
|
||||
};
|
||||
|
||||
const getLandingPhase = () => {
|
||||
const prev = [this.attackers.phase || "landing", this.defenders.phase || "defense"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'landing', this.defenders.phase || 'defense']; // previous phase
|
||||
|
||||
if (prev[1] === "waiting") return ["flee", "waiting"];
|
||||
if (prev[1] === "pursue") return ["flee", P(0.3) ? "pursue" : "waiting"];
|
||||
if (prev[1] === "retreat") return ["pursue", "retreat"];
|
||||
if (prev[1] === 'waiting') return ['flee', 'waiting'];
|
||||
if (prev[1] === 'pursue') return ['flee', P(0.3) ? 'pursue' : 'waiting'];
|
||||
if (prev[1] === 'retreat') return ['pursue', 'retreat'];
|
||||
|
||||
if (prev[0] === "landing") {
|
||||
const attackers = P(i / 2) ? "melee" : "landing";
|
||||
const defenders = i ? prev[1] : P(0.5) ? "defense" : "shock";
|
||||
if (prev[0] === 'landing') {
|
||||
const attackers = P(i / 2) ? 'melee' : 'landing';
|
||||
const defenders = i ? prev[1] : P(0.5) ? 'defense' : 'shock';
|
||||
return [attackers, defenders];
|
||||
}
|
||||
|
||||
if (P(1 - morale[0] / 40)) return ["flee", "pursue"]; // chance if moral < 40
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"]; // chance if moral < 25
|
||||
if (P(1 - morale[0] / 40)) return ['flee', 'pursue']; // chance if moral < 40
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat']; // chance if moral < 25
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
return ['melee', 'melee']; // default option
|
||||
};
|
||||
|
||||
const getAirBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "maneuvering", this.defenders.phase || "maneuvering"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'maneuvering', this.defenders.phase || 'maneuvering']; // previous phase
|
||||
|
||||
// chance if moral < 25
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
if (P(1 - morale[0] / 25)) return ['retreat', 'pursue'];
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat'];
|
||||
|
||||
if (prev[0] === "maneuvering" && P(1 - i / 10)) return ["maneuvering", "maneuvering"];
|
||||
if (prev[0] === 'maneuvering' && P(1 - i / 10)) return ['maneuvering', 'maneuvering'];
|
||||
|
||||
return ["dogfight", "dogfight"]; // default option
|
||||
return ['dogfight', 'dogfight']; // default option
|
||||
};
|
||||
|
||||
const phase = (function (type) {
|
||||
switch (type) {
|
||||
case "field":
|
||||
case 'field':
|
||||
return getFieldBattlePhase();
|
||||
case "naval":
|
||||
case 'naval':
|
||||
return getNavalBattlePhase();
|
||||
case "siege":
|
||||
case 'siege':
|
||||
return getSiegePhase();
|
||||
case "ambush":
|
||||
case 'ambush':
|
||||
return getAmbushPhase();
|
||||
case "landing":
|
||||
case 'landing':
|
||||
return getLandingPhase();
|
||||
case "air":
|
||||
case 'air':
|
||||
return getAirBattlePhase();
|
||||
default:
|
||||
getFieldBattlePhase();
|
||||
|
|
@ -503,23 +503,23 @@ class Battle {
|
|||
this.attackers.phase = phase[0];
|
||||
this.defenders.phase = phase[1];
|
||||
|
||||
const buttonA = document.getElementById("battlePhase_attackers");
|
||||
buttonA.className = "icon-button-" + this.attackers.phase;
|
||||
const buttonA = document.getElementById('battlePhase_attackers');
|
||||
buttonA.className = 'icon-button-' + this.attackers.phase;
|
||||
buttonA.dataset.tip = buttonA.nextElementSibling.querySelector("[data-phase='" + phase[0] + "']").dataset.tip;
|
||||
|
||||
const buttonD = document.getElementById("battlePhase_defenders");
|
||||
buttonD.className = "icon-button-" + this.defenders.phase;
|
||||
const buttonD = document.getElementById('battlePhase_defenders');
|
||||
buttonD.className = 'icon-button-' + this.defenders.phase;
|
||||
buttonD.dataset.tip = buttonD.nextElementSibling.querySelector("[data-phase='" + phase[1] + "']").dataset.tip;
|
||||
}
|
||||
|
||||
run() {
|
||||
// validations
|
||||
if (!this.attackers.power) {
|
||||
tip("Attackers army destroyed", false, "warn");
|
||||
tip('Attackers army destroyed', false, 'warn');
|
||||
return;
|
||||
}
|
||||
if (!this.defenders.power) {
|
||||
tip("Defenders army destroyed", false, "warn");
|
||||
tip('Defenders army destroyed', false, 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -558,8 +558,8 @@ class Battle {
|
|||
const casualtiesA = (casualties * defense) / (attack + defense); // attackers casualties, ~5% per iteration
|
||||
const casualtiesD = (casualties * attack) / (attack + defense); // defenders casualties, ~5% per iteration
|
||||
|
||||
this.calculateCasualties("attackers", casualtiesA);
|
||||
this.calculateCasualties("defenders", casualtiesD);
|
||||
this.calculateCasualties('attackers', casualtiesA);
|
||||
this.calculateCasualties('defenders', casualtiesD);
|
||||
this.attackers.casualties += casualtiesA;
|
||||
this.defenders.casualties += casualtiesD;
|
||||
|
||||
|
|
@ -568,14 +568,14 @@ class Battle {
|
|||
this.defenders.morale = Math.max(this.defenders.morale - casualtiesD * 100 - 1, 0);
|
||||
|
||||
// update table values
|
||||
this.updateTable("attackers");
|
||||
this.updateTable("defenders");
|
||||
this.updateTable('attackers');
|
||||
this.updateTable('defenders');
|
||||
|
||||
// prepare for next iteration
|
||||
this.iteration += 1;
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
}
|
||||
|
||||
calculateCasualties(side, casualties) {
|
||||
|
|
@ -591,9 +591,9 @@ class Battle {
|
|||
|
||||
updateTable(side) {
|
||||
for (const r of this[side].regiments) {
|
||||
const tbody = document.getElementById("battle" + r.state + "-" + r.i);
|
||||
const battleCasualties = tbody.querySelector(".battleCasualties");
|
||||
const battleSurvivors = tbody.querySelector(".battleSurvivors");
|
||||
const tbody = document.getElementById('battle' + r.state + '-' + r.i);
|
||||
const battleCasualties = tbody.querySelector('.battleCasualties');
|
||||
const battleSurvivors = tbody.querySelector('.battleSurvivors');
|
||||
|
||||
let index = 3; // index to find table element easily
|
||||
for (const u of options.military) {
|
||||
|
|
@ -615,35 +615,35 @@ class Battle {
|
|||
|
||||
const hideSection = function () {
|
||||
button.style.opacity = 1;
|
||||
div.style.display = "none";
|
||||
div.style.display = 'none';
|
||||
};
|
||||
if (div.style.display === "block") {
|
||||
if (div.style.display === 'block') {
|
||||
hideSection();
|
||||
return;
|
||||
}
|
||||
|
||||
button.style.opacity = 0.5;
|
||||
div.style.display = "block";
|
||||
div.style.display = 'block';
|
||||
|
||||
document.getElementsByTagName("body")[0].addEventListener("click", hideSection, {once: true});
|
||||
document.getElementsByTagName('body')[0].addEventListener('click', hideSection, {once: true});
|
||||
}
|
||||
|
||||
changeType(ev) {
|
||||
if (ev.target.tagName !== "BUTTON") return;
|
||||
if (ev.target.tagName !== 'BUTTON') return;
|
||||
this.type = ev.target.dataset.type;
|
||||
this.setType();
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
this.name = this.defineName();
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
$('#battleScreen').dialog({title: this.name});
|
||||
}
|
||||
|
||||
changePhase(ev, side) {
|
||||
if (ev.target.tagName !== "BUTTON") return;
|
||||
if (ev.target.tagName !== 'BUTTON') return;
|
||||
const phase = (this[side].phase = ev.target.dataset.phase);
|
||||
const button = document.getElementById("battlePhase_" + side);
|
||||
button.className = "icon-button-" + phase;
|
||||
const button = document.getElementById('battlePhase_' + side);
|
||||
button.className = 'icon-button-' + phase;
|
||||
button.dataset.tip = ev.target.dataset.tip;
|
||||
this.calculateStrength(side);
|
||||
}
|
||||
|
|
@ -654,34 +654,49 @@ class Battle {
|
|||
const relativeCasualties = this.defenders.casualties / (this.attackers.casualties + this.attackers.casualties);
|
||||
const battleStatus = getBattleStatus(relativeCasualties, maxCasualties);
|
||||
function getBattleStatus(relative, max) {
|
||||
if (isNaN(relative)) return ["standoff", "standoff"]; // if no casualties at all
|
||||
if (max < 0.05) return ["minor skirmishes", "minor skirmishes"];
|
||||
if (relative > 95) return ["attackers flawless victory", "disorderly retreat of defenders"];
|
||||
if (relative > 0.7) return ["attackers decisive victory", "defenders disastrous defeat"];
|
||||
if (relative > 0.6) return ["attackers victory", "defenders defeat"];
|
||||
if (relative > 0.4) return ["stalemate", "stalemate"];
|
||||
if (relative > 0.3) return ["attackers defeat", "defenders victory"];
|
||||
if (relative > 0.5) return ["attackers disastrous defeat", "decisive victory of defenders"];
|
||||
if (relative >= 0) return ["attackers disorderly retreat", "flawless victory of defenders"];
|
||||
return ["stalemate", "stalemate"]; // exception
|
||||
if (isNaN(relative)) return ['standoff', 'standoff']; // if no casualties at all
|
||||
if (max < 0.05) return ['minor skirmishes', 'minor skirmishes'];
|
||||
if (relative > 95) return ['attackers flawless victory', 'disorderly retreat of defenders'];
|
||||
if (relative > 0.7) return ['attackers decisive victory', 'defenders disastrous defeat'];
|
||||
if (relative > 0.6) return ['attackers victory', 'defenders defeat'];
|
||||
if (relative > 0.4) return ['stalemate', 'stalemate'];
|
||||
if (relative > 0.3) return ['attackers defeat', 'defenders victory'];
|
||||
if (relative > 0.5) return ['attackers disastrous defeat', 'decisive victory of defenders'];
|
||||
if (relative >= 0) return ['attackers disorderly retreat', 'flawless victory of defenders'];
|
||||
return ['stalemate', 'stalemate']; // exception
|
||||
}
|
||||
|
||||
this.attackers.regiments.forEach(r => applyResultForSide(r, "attackers"));
|
||||
this.defenders.regiments.forEach(r => applyResultForSide(r, "defenders"));
|
||||
this.attackers.regiments.forEach((r) => applyResultForSide(r, 'attackers'));
|
||||
this.defenders.regiments.forEach((r) => applyResultForSide(r, 'defenders'));
|
||||
|
||||
function applyResultForSide(r, side) {
|
||||
const id = "regiment" + r.state + "-" + r.i;
|
||||
const id = 'regiment' + r.state + '-' + r.i;
|
||||
|
||||
// add result to regiment note
|
||||
const note = notes.find(n => n.id === id);
|
||||
const note = notes.find((n) => n.id === id);
|
||||
if (note) {
|
||||
const status = side === "attackers" ? battleStatus[0] : battleStatus[1];
|
||||
const status = side === 'attackers' ? battleStatus[0] : battleStatus[1];
|
||||
const losses = r.a ? Math.abs(d3.sum(Object.values(r.casualties))) / r.a : 1;
|
||||
const regStatus = losses === 1 ? "is destroyed" : losses > 0.8 ? "is almost completely destroyed" : losses > 0.5 ? "suffered terrible losses" : losses > 0.3 ? "suffered severe losses" : losses > 0.2 ? "suffered heavy losses" : losses > 0.05 ? "suffered significant losses" : losses > 0 ? "suffered unsignificant losses" : "left the battle without loss";
|
||||
const regStatus =
|
||||
losses === 1
|
||||
? 'is destroyed'
|
||||
: losses > 0.8
|
||||
? 'is almost completely destroyed'
|
||||
: losses > 0.5
|
||||
? 'suffered terrible losses'
|
||||
: losses > 0.3
|
||||
? 'suffered severe losses'
|
||||
: losses > 0.2
|
||||
? 'suffered heavy losses'
|
||||
: losses > 0.05
|
||||
? 'suffered significant losses'
|
||||
: losses > 0
|
||||
? 'suffered unsignificant losses'
|
||||
: 'left the battle without loss';
|
||||
const casualties = Object.keys(r.casualties)
|
||||
.map(t => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null))
|
||||
.filter(c => c);
|
||||
const casualtiesText = casualties.length ? " Casualties: " + list(casualties) + "." : "";
|
||||
.map((t) => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null))
|
||||
.filter((c) => c);
|
||||
const casualtiesText = casualties.length ? ' Casualties: ' + list(casualties) + '.' : '';
|
||||
const legend = `\r\n\r\n${battleName} (${options.year} ${options.eraShort}): ${status}. The regiment ${regStatus}.${casualtiesText}`;
|
||||
note.legend += legend;
|
||||
}
|
||||
|
|
@ -691,57 +706,47 @@ class Battle {
|
|||
armies.select(`g#${id} > text`).text(Military.getTotal(r)); // update reg box
|
||||
}
|
||||
|
||||
// append battlefield marker
|
||||
void (function addMarkerSymbol() {
|
||||
if (svg.select("#defs-markers").select("#marker_battlefield").size()) return;
|
||||
const symbol = svg.select("#defs-markers").append("symbol").attr("id", "marker_battlefield").attr("viewBox", "0 0 30 30");
|
||||
symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none");
|
||||
symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1);
|
||||
symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0).attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️");
|
||||
})();
|
||||
const i = last(pack.markers)?.i + 1 || 0;
|
||||
{
|
||||
// append battlefield marker
|
||||
const marker = {i, x: this.x, y: this.y, cell: this.cell, icon: '⚔️', type: 'battlefields', dy: 52};
|
||||
pack.markers.push(marker);
|
||||
const markerHTML = drawMarker(marker);
|
||||
document.getElementById('markers').insertAdjacentHTML('beforeend', markerHTML);
|
||||
}
|
||||
|
||||
const getSide = (regs, n) => (regs.length > 1 ? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name);
|
||||
const getLosses = casualties => Math.min(rn(casualties * 100), 100);
|
||||
const getSide = (regs, n) =>
|
||||
regs.length > 1 ? `${n ? 'regiments' : 'forces'} of ${list([...new Set(regs.map((r) => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + ' ' + regs[0].name;
|
||||
const getLosses = (casualties) => Math.min(rn(casualties * 100), 100);
|
||||
|
||||
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}.
|
||||
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 id = getNextId("markerElement");
|
||||
notes.push({id, 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);
|
||||
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", "#marker_battlefield")
|
||||
.attr("data-id", "#marker_battlefield")
|
||||
.attr("data-x", this.x)
|
||||
.attr("data-y", this.y)
|
||||
.attr("x", this.x - 15)
|
||||
.attr("y", this.y - 30)
|
||||
.attr("data-size", 1)
|
||||
.attr("width", 30)
|
||||
.attr("height", 30);
|
||||
|
||||
$("#battleScreen").dialog("destroy");
|
||||
$('#battleScreen').dialog('destroy');
|
||||
this.cleanData();
|
||||
}
|
||||
|
||||
cancelResults() {
|
||||
// move regiments back to initial positions
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach(r => Military.moveRegiment(r, r.px, r.py));
|
||||
$("#battleScreen").dialog("close");
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach((r) => Military.moveRegiment(r, r.px, r.py));
|
||||
$('#battleScreen').dialog('close');
|
||||
this.cleanData();
|
||||
}
|
||||
|
||||
cleanData() {
|
||||
battleAttackers.innerHTML = battleDefenders.innerHTML = ""; // clean DOM
|
||||
battleAttackers.innerHTML = battleDefenders.innerHTML = ''; // clean DOM
|
||||
customization = 0; // exit edit mode
|
||||
|
||||
// clean temp data
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach(r => {
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach((r) => {
|
||||
delete r.px;
|
||||
delete r.py;
|
||||
delete r.casualties;
|
||||
|
|
|
|||
|
|
@ -1,55 +1,55 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function editBiomes() {
|
||||
if (customization) return;
|
||||
closeDialogs("#biomesEditor, .stable");
|
||||
if (!layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (layerIsOn("toggleStates")) toggleStates();
|
||||
if (layerIsOn("toggleCultures")) toggleCultures();
|
||||
if (layerIsOn("toggleReligions")) toggleReligions();
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
closeDialogs('#biomesEditor, .stable');
|
||||
if (!layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
if (layerIsOn('toggleStates')) toggleStates();
|
||||
if (layerIsOn('toggleCultures')) toggleCultures();
|
||||
if (layerIsOn('toggleReligions')) toggleReligions();
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
|
||||
const body = document.getElementById("biomesBody");
|
||||
const body = document.getElementById('biomesBody');
|
||||
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||
refreshBiomesEditor();
|
||||
|
||||
if (modules.editBiomes) return;
|
||||
modules.editBiomes = true;
|
||||
|
||||
$("#biomesEditor").dialog({
|
||||
title: "Biomes Editor",
|
||||
$('#biomesEditor').dialog({
|
||||
title: 'Biomes Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: closeBiomesEditor,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("biomesEditorRefresh").addEventListener("click", refreshBiomesEditor);
|
||||
document.getElementById("biomesEditStyle").addEventListener("click", () => editStyle("biomes"));
|
||||
document.getElementById("biomesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("biomesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("biomesManually").addEventListener("click", enterBiomesCustomizationMode);
|
||||
document.getElementById("biomesManuallyApply").addEventListener("click", applyBiomesChange);
|
||||
document.getElementById("biomesManuallyCancel").addEventListener("click", () => exitBiomesCustomizationMode());
|
||||
document.getElementById("biomesRestore").addEventListener("click", restoreInitialBiomes);
|
||||
document.getElementById("biomesAdd").addEventListener("click", addCustomBiome);
|
||||
document.getElementById("biomesRegenerateReliefIcons").addEventListener("click", regenerateIcons);
|
||||
document.getElementById("biomesExport").addEventListener("click", downloadBiomesData);
|
||||
document.getElementById('biomesEditorRefresh').addEventListener('click', refreshBiomesEditor);
|
||||
document.getElementById('biomesEditStyle').addEventListener('click', () => editStyle('biomes'));
|
||||
document.getElementById('biomesLegend').addEventListener('click', toggleLegend);
|
||||
document.getElementById('biomesPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('biomesManually').addEventListener('click', enterBiomesCustomizationMode);
|
||||
document.getElementById('biomesManuallyApply').addEventListener('click', applyBiomesChange);
|
||||
document.getElementById('biomesManuallyCancel').addEventListener('click', () => exitBiomesCustomizationMode());
|
||||
document.getElementById('biomesRestore').addEventListener('click', restoreInitialBiomes);
|
||||
document.getElementById('biomesAdd').addEventListener('click', addCustomBiome);
|
||||
document.getElementById('biomesRegenerateReliefIcons').addEventListener('click', regenerateIcons);
|
||||
document.getElementById('biomesExport').addEventListener('click', downloadBiomesData);
|
||||
|
||||
body.addEventListener("click", function (ev) {
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList;
|
||||
if (cl.contains("fillRect")) biomeChangeColor(el);
|
||||
else if (cl.contains("icon-info-circled")) openWiki(el);
|
||||
else if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
|
||||
if (cl.contains('fillRect')) 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);
|
||||
});
|
||||
|
||||
body.addEventListener("change", function (ev) {
|
||||
body.addEventListener('change', function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList;
|
||||
if (cl.contains("biomeName")) biomeChangeName(el);
|
||||
else if (cl.contains("biomeHabitability")) biomeChangeHabitability(el);
|
||||
if (cl.contains('biomeName')) biomeChangeName(el);
|
||||
else if (cl.contains('biomeHabitability')) biomeChangeHabitability(el);
|
||||
});
|
||||
|
||||
function refreshBiomesEditor() {
|
||||
|
|
@ -76,14 +76,14 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function biomesEditorAddLines() {
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
const b = biomesData;
|
||||
let lines = "",
|
||||
let lines = '',
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
|
||||
for (const i of b.i) {
|
||||
if (!i || biomesData.name[i] === "removed") continue; // ignore water and removed biomes
|
||||
if (!i || biomesData.name[i] === 'removed') continue; // ignore water and removed biomes
|
||||
const area = b.area[i] * distanceScaleInput.value ** 2;
|
||||
const rural = b.rural[i] * populationRate;
|
||||
const urban = b.urban[i] * populationRate * urbanization;
|
||||
|
|
@ -94,7 +94,9 @@ 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>
|
||||
<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>
|
||||
<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=${b.habitability[i]}>
|
||||
|
|
@ -105,40 +107,40 @@ function editBiomes() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
|
||||
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ""}
|
||||
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ''}
|
||||
</div>`;
|
||||
}
|
||||
body.innerHTML = lines;
|
||||
|
||||
// update footer
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(":scope > div").length;
|
||||
biomesFooterCells.innerHTML = pack.cells.h.filter(h => h >= 20).length;
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(':scope > div').length;
|
||||
biomesFooterCells.innerHTML = pack.cells.h.filter((h) => h >= 20).length;
|
||||
biomesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
biomesFooterPopulation.innerHTML = si(totalPopulation);
|
||||
biomesFooterArea.dataset.area = totalArea;
|
||||
biomesFooterPopulation.dataset.population = totalPopulation;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseenter", ev => biomeHighlightOn(ev)));
|
||||
body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseleave", ev => biomeHighlightOff(ev)));
|
||||
body.querySelectorAll('div.biomes').forEach((el) => el.addEventListener('mouseenter', (ev) => biomeHighlightOn(ev)));
|
||||
body.querySelectorAll('div.biomes').forEach((el) => el.addEventListener('mouseleave', (ev) => biomeHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(biomesHeader);
|
||||
$("#biomesEditor").dialog({width: fitContent()});
|
||||
$('#biomesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function biomeHighlightOn(event) {
|
||||
if (customization === 6) return;
|
||||
const biome = +event.target.dataset.id;
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.select('#biome' + biome)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr("stroke-width", 2)
|
||||
.attr("stroke", "#cd4c11");
|
||||
.attr('stroke-width', 2)
|
||||
.attr('stroke', '#cd4c11');
|
||||
}
|
||||
|
||||
function biomeHighlightOff(event) {
|
||||
|
|
@ -146,23 +148,23 @@ function editBiomes() {
|
|||
const biome = +event.target.dataset.id;
|
||||
const color = biomesData.color[biome];
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.select('#biome' + biome)
|
||||
.transition()
|
||||
.attr("stroke-width", 0.7)
|
||||
.attr("stroke", color);
|
||||
.attr('stroke-width', 0.7)
|
||||
.attr('stroke', color);
|
||||
}
|
||||
|
||||
function biomeChangeColor(el) {
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const currentFill = el.getAttribute('fill');
|
||||
const biome = +el.parentNode.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
el.setAttribute('fill', fill);
|
||||
biomesData.color[biome] = fill;
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.attr("fill", fill)
|
||||
.attr("stroke", fill);
|
||||
.select('#biome' + biome)
|
||||
.attr('fill', fill)
|
||||
.attr('stroke', fill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
|
|
@ -179,7 +181,7 @@ function editBiomes() {
|
|||
const failed = isNaN(+el.value) || +el.value < 0 || +el.value > 9999;
|
||||
if (failed) {
|
||||
el.value = biomesData.habitability[biome];
|
||||
tip("Please provide a valid number in range 0-9999", false, "error");
|
||||
tip('Please provide a valid number in range 0-9999', false, 'error');
|
||||
return;
|
||||
}
|
||||
biomesData.habitability[biome] = +el.value;
|
||||
|
|
@ -190,69 +192,69 @@ function editBiomes() {
|
|||
|
||||
function openWiki(el) {
|
||||
const name = el.parentNode.dataset.name;
|
||||
if (name === "Custom" || !name) {
|
||||
tip("Please provide a biome name", false, "error");
|
||||
if (name === 'Custom' || !name) {
|
||||
tip('Please provide a biome name', false, 'error');
|
||||
return;
|
||||
}
|
||||
const wiki = "https://en.wikipedia.org/wiki/";
|
||||
const wiki = 'https://en.wikipedia.org/wiki/';
|
||||
|
||||
switch (name) {
|
||||
case "Hot desert":
|
||||
openURL(wiki + "Desert_climate#Hot_desert_climates");
|
||||
case "Cold desert":
|
||||
openURL(wiki + "Desert_climate#Cold_desert_climates");
|
||||
case "Savanna":
|
||||
openURL(wiki + "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands");
|
||||
case "Grassland":
|
||||
openURL(wiki + "Temperate_grasslands,_savannas,_and_shrublands");
|
||||
case "Tropical seasonal forest":
|
||||
openURL(wiki + "Seasonal_tropical_forest");
|
||||
case "Temperate deciduous forest":
|
||||
openURL(wiki + "Temperate_deciduous_forest");
|
||||
case "Tropical rainforest":
|
||||
openURL(wiki + "Tropical_rainforest");
|
||||
case "Temperate rainforest":
|
||||
openURL(wiki + "Temperate_rainforest");
|
||||
case "Taiga":
|
||||
openURL(wiki + "Taiga");
|
||||
case "Tundra":
|
||||
openURL(wiki + "Tundra");
|
||||
case "Glacier":
|
||||
openURL(wiki + "Glacier");
|
||||
case "Wetland":
|
||||
openURL(wiki + "Wetland");
|
||||
case 'Hot desert':
|
||||
openURL(wiki + 'Desert_climate#Hot_desert_climates');
|
||||
case 'Cold desert':
|
||||
openURL(wiki + 'Desert_climate#Cold_desert_climates');
|
||||
case 'Savanna':
|
||||
openURL(wiki + 'Tropical_and_subtropical_grasslands,_savannas,_and_shrublands');
|
||||
case 'Grassland':
|
||||
openURL(wiki + 'Temperate_grasslands,_savannas,_and_shrublands');
|
||||
case 'Tropical seasonal forest':
|
||||
openURL(wiki + 'Seasonal_tropical_forest');
|
||||
case 'Temperate deciduous forest':
|
||||
openURL(wiki + 'Temperate_deciduous_forest');
|
||||
case 'Tropical rainforest':
|
||||
openURL(wiki + 'Tropical_rainforest');
|
||||
case 'Temperate rainforest':
|
||||
openURL(wiki + 'Temperate_rainforest');
|
||||
case 'Taiga':
|
||||
openURL(wiki + 'Taiga');
|
||||
case 'Tundra':
|
||||
openURL(wiki + 'Tundra');
|
||||
case 'Glacier':
|
||||
openURL(wiki + 'Glacier');
|
||||
case 'Wetland':
|
||||
openURL(wiki + 'Wetland');
|
||||
default:
|
||||
openURL(`https://en.wikipedia.org/w/index.php?search=${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll("*").size()) {
|
||||
if (legend.selectAll('*').size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const d = biomesData;
|
||||
const data = Array.from(d.i)
|
||||
.filter(i => d.cells[i])
|
||||
.filter((i) => d.cells[i])
|
||||
.sort((a, b) => d.area[b] - d.area[a])
|
||||
.map(i => [i, d.color[i], d.name[i]]);
|
||||
drawLegend("Biomes", data);
|
||||
.map((i) => [i, d.color[i], d.name[i]]);
|
||||
drawLegend('Biomes', data);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const totalCells = +biomesFooterCells.innerHTML;
|
||||
const totalArea = +biomesFooterArea.dataset.area;
|
||||
const totalPopulation = +biomesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(":scope> div").forEach(function (el) {
|
||||
el.querySelector(".biomeCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%";
|
||||
el.querySelector(".biomePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%";
|
||||
body.querySelectorAll(':scope> div').forEach(function (el) {
|
||||
el.querySelector('.biomeCells').innerHTML = rn((+el.dataset.cells / totalCells) * 100) + '%';
|
||||
el.querySelector('.biomeArea').innerHTML = rn((+el.dataset.area / totalArea) * 100) + '%';
|
||||
el.querySelector('.biomePopulation').innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + '%';
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
body.dataset.type = 'absolute';
|
||||
biomesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
|
@ -261,14 +263,14 @@ function editBiomes() {
|
|||
const b = biomesData,
|
||||
i = biomesData.i.length;
|
||||
if (i > 254) {
|
||||
tip("Maximum number of biomes reached (255), data cleansing is required", false, "error");
|
||||
tip('Maximum number of biomes reached (255), data cleansing is required', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
b.i.push(i);
|
||||
b.color.push(getRandomColor());
|
||||
b.habitability.push(50);
|
||||
b.name.push("Custom");
|
||||
b.name.push('Custom');
|
||||
b.iconsDensity.push(0);
|
||||
b.icons.push([]);
|
||||
b.cost.push(50);
|
||||
|
|
@ -278,7 +280,7 @@ function editBiomes() {
|
|||
b.cells.push(0);
|
||||
b.area.push(0);
|
||||
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
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>
|
||||
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
|
||||
|
|
@ -293,84 +295,84 @@ function editBiomes() {
|
|||
<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", line);
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(":scope > div").length;
|
||||
$("#biomesEditor").dialog({width: fitContent()});
|
||||
body.insertAdjacentHTML('beforeend', line);
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(':scope > div').length;
|
||||
$('#biomesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function removeCustomBiome(el) {
|
||||
const biome = +el.parentNode.dataset.id;
|
||||
el.parentNode.remove();
|
||||
biomesData.name[biome] = "removed";
|
||||
biomesData.name[biome] = 'removed';
|
||||
biomesFooterBiomes.innerHTML = +biomesFooterBiomes.innerHTML - 1;
|
||||
}
|
||||
|
||||
function regenerateIcons() {
|
||||
ReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
if (!layerIsOn('toggleRelief')) toggleRelief();
|
||||
}
|
||||
|
||||
function downloadBiomesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,Biome,Color,Habitability,Cells,Area " + unit + ",Population\n"; // headers
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,Biome,Color,Habitability,Cells,Area ' + unit + ',Population\n'; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += el.dataset.color + ",";
|
||||
data += el.dataset.habitability + "%,";
|
||||
data += el.dataset.cells + ",";
|
||||
data += el.dataset.area + ",";
|
||||
data += el.dataset.population + "\n";
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.habitability + '%,';
|
||||
data += el.dataset.cells + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Biomes") + ".csv";
|
||||
const name = getFileName('Biomes') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function enterBiomesCustomizationMode() {
|
||||
if (!layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (!layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
customization = 6;
|
||||
biomes.append("g").attr("id", "temp");
|
||||
biomes.append('g').attr('id', 'temp');
|
||||
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "block"));
|
||||
body.querySelector("div.biomes").classList.add("selected");
|
||||
document.querySelectorAll('#biomesBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.querySelectorAll('#biomesBottom > div').forEach((el) => (el.style.display = 'block'));
|
||||
body.querySelector('div.biomes').classList.add('selected');
|
||||
|
||||
biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none"));
|
||||
biomesFooter.style.display = "none";
|
||||
$("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
biomesEditor.querySelectorAll('.hide').forEach((el) => el.classList.add('hidden'));
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'none'));
|
||||
biomesFooter.style.display = 'none';
|
||||
$('#biomesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
|
||||
|
||||
tip("Click on biome to select, drag the circle to change biome", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectBiomeOnMapClick).call(d3.drag().on("start", dragBiomeBrush)).on("touchmove mousemove", moveBiomeBrush);
|
||||
tip('Click on biome to select, drag the circle to change biome', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectBiomeOnMapClick).call(d3.drag().on('start', dragBiomeBrush)).on('touchmove mousemove', moveBiomeBrush);
|
||||
}
|
||||
|
||||
function selectBiomeOnLineClick(line) {
|
||||
const selected = body.querySelector("div.selected");
|
||||
if (selected) selected.classList.remove("selected");
|
||||
line.classList.add("selected");
|
||||
const selected = body.querySelector('div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
line.classList.add('selected');
|
||||
}
|
||||
|
||||
function selectBiomeOnMapClick() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[i] < 20) {
|
||||
tip("You cannot reassign water via biomes. Please edit the Heightmap to change water", false, "error");
|
||||
tip('You cannot reassign water via biomes. Please edit the Heightmap to change water', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const assigned = biomes.select("#temp").select("polygon[data-cell='" + i + "']");
|
||||
const biome = assigned.size() ? +assigned.attr("data-biome") : pack.cells.biome[i];
|
||||
const assigned = biomes.select('#temp').select("polygon[data-cell='" + i + "']");
|
||||
const biome = assigned.size() ? +assigned.attr('data-biome') : pack.cells.biome[i];
|
||||
|
||||
body.querySelector("div.selected").classList.remove("selected");
|
||||
body.querySelector("div[data-id='" + biome + "']").classList.add("selected");
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
body.querySelector("div[data-id='" + biome + "']").classList.add('selected');
|
||||
}
|
||||
|
||||
function dragBiomeBrush() {
|
||||
const r = +biomesManuallyBrush.value;
|
||||
|
||||
d3.event.on("drag", () => {
|
||||
d3.event.on('drag', () => {
|
||||
if (!d3.event.dx && !d3.event.dy) return;
|
||||
const p = d3.mouse(this);
|
||||
moveCircle(p[0], p[1], r);
|
||||
|
|
@ -383,20 +385,20 @@ function editBiomes() {
|
|||
|
||||
// change region within selection
|
||||
function changeBiomeForSelection(selection) {
|
||||
const temp = biomes.select("#temp");
|
||||
const selected = body.querySelector("div.selected");
|
||||
const temp = biomes.select('#temp');
|
||||
const selected = body.querySelector('div.selected');
|
||||
|
||||
const biomeNew = selected.dataset.id;
|
||||
const color = biomesData.color[biomeNew];
|
||||
|
||||
selection.forEach(function (i) {
|
||||
const exists = temp.select("polygon[data-cell='" + i + "']");
|
||||
const biomeOld = exists.size() ? +exists.attr("data-biome") : pack.cells.biome[i];
|
||||
const biomeOld = exists.size() ? +exists.attr('data-biome') : pack.cells.biome[i];
|
||||
if (biomeNew === biomeOld) return;
|
||||
|
||||
// change of append new element
|
||||
if (exists.size()) exists.attr("data-biome", biomeNew).attr("fill", color).attr("stroke", color);
|
||||
else temp.append("polygon").attr("data-cell", i).attr("data-biome", biomeNew).attr("points", getPackPolygon(i)).attr("fill", color).attr("stroke", color);
|
||||
if (exists.size()) exists.attr('data-biome', biomeNew).attr('fill', color).attr('stroke', color);
|
||||
else temp.append('polygon').attr('data-cell', i).attr('data-biome', biomeNew).attr('points', getPackPolygon(i)).attr('fill', color).attr('stroke', color);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -408,7 +410,7 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function applyBiomesChange() {
|
||||
const changed = biomes.select("#temp").selectAll("polygon");
|
||||
const changed = biomes.select('#temp').selectAll('polygon');
|
||||
changed.each(function () {
|
||||
const i = +this.dataset.cell;
|
||||
const b = +this.dataset.biome;
|
||||
|
|
@ -424,21 +426,21 @@ function editBiomes() {
|
|||
|
||||
function exitBiomesCustomizationMode(close) {
|
||||
customization = 0;
|
||||
biomes.select("#temp").remove();
|
||||
biomes.select('#temp').remove();
|
||||
removeCircle();
|
||||
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "none"));
|
||||
document.querySelectorAll('#biomesBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.querySelectorAll('#biomesBottom > div').forEach((el) => (el.style.display = 'none'));
|
||||
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all"));
|
||||
biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden"));
|
||||
biomesFooter.style.display = "block";
|
||||
if (!close) $("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'all'));
|
||||
biomesEditor.querySelectorAll('.hide').forEach((el) => el.classList.remove('hidden'));
|
||||
biomesFooter.style.display = 'block';
|
||||
if (!close) $('#biomesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
|
||||
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
const selected = document.querySelector("#biomesBody > div.selected");
|
||||
if (selected) selected.classList.remove("selected");
|
||||
const selected = document.querySelector('#biomesBody > div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
}
|
||||
|
||||
function restoreInitialBiomes() {
|
||||
|
|
@ -450,6 +452,6 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function closeBiomesEditor() {
|
||||
exitBiomesCustomizationMode("close");
|
||||
exitBiomesCustomizationMode('close');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,14 @@ function editBurg(id) {
|
|||
burgLabels.selectAll('text').call(d3.drag().on('start', dragBurgLabel)).classed('draggable', true);
|
||||
updateBurgValues();
|
||||
|
||||
const my = id || d3.event.target.tagName === 'text' ? 'center bottom-40' : 'center top+40';
|
||||
const at = id ? 'center' : d3.event.target.tagName === 'text' ? 'top' : 'bottom';
|
||||
const of = id ? 'svg' : d3.event.target;
|
||||
|
||||
const my = id || d3.event.target.tagName === "text" ? "center bottom-20" : "center top+20";
|
||||
const at = id ? "center" : d3.event.target.tagName === "text" ? "top" : "bottom";
|
||||
const of = id ? "svg" : d3.event.target;
|
||||
$('#burgEditor').dialog({
|
||||
title: 'Edit Burg',
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my, at, of, collision: 'fit'}
|
||||
position: {my, at, of, collision: "fit"}
|
||||
});
|
||||
|
||||
if (modules.editBurg) return;
|
||||
|
|
@ -39,6 +38,8 @@ function editBurg(id) {
|
|||
document.getElementById('burgNameReCulture').addEventListener('click', generateNameCulture);
|
||||
document.getElementById('burgPopulation').addEventListener('change', changePopulation);
|
||||
burgBody.querySelectorAll('.burgFeature').forEach((el) => el.addEventListener('click', toggleFeature));
|
||||
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
|
||||
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
|
||||
|
||||
document.getElementById('burgStyleShow').addEventListener('click', showStyleSection);
|
||||
document.getElementById('burgStyleHide').addEventListener('click', hideStyleSection);
|
||||
|
|
@ -48,6 +49,8 @@ function editBurg(id) {
|
|||
|
||||
document.getElementById('burgSeeInMFCG').addEventListener('click', openInMFCG);
|
||||
document.getElementById('burgEditEmblem').addEventListener('click', openEmblemEdit);
|
||||
document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgToggleMFCGMap").addEventListener("click", toggleMFCGMap);
|
||||
document.getElementById('burgRelocate').addEventListener('click', toggleRelocateBurg);
|
||||
document.getElementById('burglLegend').addEventListener('click', editBurgLegend);
|
||||
document.getElementById('burgLock').addEventListener('click', toggleBurgLockButton);
|
||||
|
|
@ -68,7 +71,14 @@ function editBurg(id) {
|
|||
document.getElementById('burgState').innerHTML = stateName;
|
||||
document.getElementById('burgProvince').innerHTML = provinceName;
|
||||
|
||||
document.getElementById('burgEditAnchorStyle').style.display = +b.port ? 'inline-block' : 'none';
|
||||
document.getElementById("burgName").value = b.name;
|
||||
document.getElementById("burgType").value = b.type || "Generic";
|
||||
document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization);
|
||||
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
|
||||
document.getElementById("burgName").value = b.name;
|
||||
document.getElementById("burgType").value = b.type || "Generic";
|
||||
document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization);
|
||||
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
|
||||
|
||||
// update list and select culture
|
||||
const cultureSelect = document.getElementById('burgCulture');
|
||||
|
|
@ -119,6 +129,14 @@ function editBurg(id) {
|
|||
const coaID = 'burgCOA' + id;
|
||||
COArenderer.trigger(coaID, b.coa);
|
||||
document.getElementById('burgEmblem').setAttribute('href', '#' + coaID);
|
||||
|
||||
if (options.showMFCGMap) {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "block";
|
||||
updateMFCGFrame(b);
|
||||
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
|
||||
} else {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function getProduction(pool) {
|
||||
|
|
@ -411,11 +429,7 @@ function editBurg(id) {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgELockTip() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
showBurgLockTip(id);
|
||||
}
|
||||
|
||||
function showStyleSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('burgStyleSection').style.display = 'inline-block';
|
||||
|
|
@ -443,57 +457,62 @@ function editBurg(id) {
|
|||
|
||||
function openInMFCG(event) {
|
||||
const id = elSelected.attr('data-id');
|
||||
const burg = pack.burgs[id];
|
||||
const defSeed = +(seed + id.padStart(4, 0));
|
||||
if (isCtrlClick(event)) {
|
||||
prompt(
|
||||
`Please provide a Medieval Fantasy City Generator seed.
|
||||
Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}).
|
||||
Please note that if seed is custom, "Overworld" button from MFCG will open a different map`,
|
||||
{default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1},
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
(v) => {
|
||||
burg.MFCG = v;
|
||||
openMFCG(v);
|
||||
}
|
||||
);
|
||||
} else openMFCG();
|
||||
}
|
||||
function getBurgSeed(burg) {
|
||||
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
|
||||
}
|
||||
|
||||
function openMFCG(seed) {
|
||||
if (!seed && burg.MFCGlink) {
|
||||
openURL(burg.MFCGlink);
|
||||
return;
|
||||
}
|
||||
const cells = pack.cells;
|
||||
const name = elSelected.text();
|
||||
const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
function getMFCGlink(burg) {
|
||||
const {cells} = pack;
|
||||
const {name, population, cell} = burg;
|
||||
const burgSeed = getBurgSeed(burg);
|
||||
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
|
||||
const size = minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
const people = rn(population * populationRate * urbanization);
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : '';
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return '&sea=' + norm;
|
||||
}
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
}
|
||||
|
||||
const site = 'http://fantasycities.watabou.ru/?random=0&continuous=0';
|
||||
const url = `${site}&name=${name}&population=${population}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
openURL(url);
|
||||
}
|
||||
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
function changeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = +this.value;
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
}
|
||||
|
||||
function randomizeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = rand(1e9 - 1);
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
document.getElementById("mfcgBurgSeed").value = burgSeed;
|
||||
}
|
||||
|
||||
function openEmblemEdit() {
|
||||
|
|
@ -502,6 +521,12 @@ function editBurg(id) {
|
|||
editEmblem('burg', 'burgCOA' + id, burg);
|
||||
}
|
||||
|
||||
function toggleMFCGMap() {
|
||||
options.showMFCGMap = !options.showMFCGMap;
|
||||
document.getElementById("mfcgPreviewSection").style.display = options.showMFCGMap ? "block" : "none";
|
||||
document.getElementById("burgToggleMFCGMap").className = options.showMFCGMap ? "icon-map" : "icon-map-o";
|
||||
}
|
||||
|
||||
function toggleRelocateBurg() {
|
||||
const toggler = document.getElementById('toggleCells');
|
||||
document.getElementById('burgRelocate').classList.toggle('pressed');
|
||||
|
|
|
|||
739
modules/ui/burg-editor.js.orig
Normal file
739
modules/ui/burg-editor.js.orig
Normal file
|
|
@ -0,0 +1,739 @@
|
|||
'use strict';
|
||||
function editBurg(id) {
|
||||
if (customization) return;
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleIcons')) toggleIcons();
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
|
||||
const burg = id || d3.event.target.dataset.id;
|
||||
elSelected = burgLabels.select("[data-id='" + burg + "']");
|
||||
burgLabels.selectAll('text').call(d3.drag().on('start', dragBurgLabel)).classed('draggable', true);
|
||||
updateBurgValues();
|
||||
|
||||
<<<<<<< HEAD
|
||||
const my = id || d3.event.target.tagName === 'text' ? 'center bottom-40' : 'center top+40';
|
||||
const at = id ? 'center' : d3.event.target.tagName === 'text' ? 'top' : 'bottom';
|
||||
const of = id ? 'svg' : d3.event.target;
|
||||
|
||||
$('#burgEditor').dialog({
|
||||
title: 'Edit Burg',
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my, at, of, collision: 'fit'}
|
||||
=======
|
||||
$("#burgEditor").dialog({
|
||||
title: "Edit Burg",
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}
|
||||
>>>>>>> master
|
||||
});
|
||||
|
||||
if (modules.editBurg) return;
|
||||
modules.editBurg = true;
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
document.getElementById('burgGroupShow').addEventListener('click', showGroupSection);
|
||||
document.getElementById('burgGroupHide').addEventListener('click', hideGroupSection);
|
||||
document.getElementById('burgSelectGroup').addEventListener('change', changeGroup);
|
||||
document.getElementById('burgInputGroup').addEventListener('change', createNewGroup);
|
||||
document.getElementById('burgAddGroup').addEventListener('click', toggleNewGroupInput);
|
||||
document.getElementById('burgRemoveGroup').addEventListener('click', removeBurgsGroup);
|
||||
|
||||
document.getElementById('burgName').addEventListener('input', changeName);
|
||||
document.getElementById('burgNameReRandom').addEventListener('click', generateNameRandom);
|
||||
document.getElementById('burgType').addEventListener('input', changeType);
|
||||
document.getElementById('burgCulture').addEventListener('input', changeCulture);
|
||||
document.getElementById('burgNameReCulture').addEventListener('click', generateNameCulture);
|
||||
document.getElementById('burgPopulation').addEventListener('change', changePopulation);
|
||||
burgBody.querySelectorAll('.burgFeature').forEach((el) => el.addEventListener('click', toggleFeature));
|
||||
|
||||
document.getElementById('burgStyleShow').addEventListener('click', showStyleSection);
|
||||
document.getElementById('burgStyleHide').addEventListener('click', hideStyleSection);
|
||||
document.getElementById('burgEditLabelStyle').addEventListener('click', editGroupLabelStyle);
|
||||
document.getElementById('burgEditIconStyle').addEventListener('click', editGroupIconStyle);
|
||||
document.getElementById('burgEditAnchorStyle').addEventListener('click', editGroupAnchorStyle);
|
||||
|
||||
document.getElementById('burgSeeInMFCG').addEventListener('click', openInMFCG);
|
||||
document.getElementById('burgEditEmblem').addEventListener('click', openEmblemEdit);
|
||||
document.getElementById('burgRelocate').addEventListener('click', toggleRelocateBurg);
|
||||
document.getElementById('burglLegend').addEventListener('click', editBurgLegend);
|
||||
document.getElementById('burgLock').addEventListener('click', toggleBurgLockButton);
|
||||
document.getElementById('burgLock').addEventListener('mouseover', showBurgELockTip);
|
||||
document.getElementById('burgRemove').addEventListener('click', removeSelectedBurg);
|
||||
=======
|
||||
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection);
|
||||
document.getElementById("burgGroupHide").addEventListener("click", hideGroupSection);
|
||||
document.getElementById("burgSelectGroup").addEventListener("change", changeGroup);
|
||||
document.getElementById("burgInputGroup").addEventListener("change", createNewGroup);
|
||||
document.getElementById("burgAddGroup").addEventListener("click", toggleNewGroupInput);
|
||||
document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup);
|
||||
|
||||
document.getElementById("burgName").addEventListener("input", changeName);
|
||||
document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom);
|
||||
document.getElementById("burgType").addEventListener("input", changeType);
|
||||
document.getElementById("burgCulture").addEventListener("input", changeCulture);
|
||||
document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
|
||||
document.getElementById("burgPopulation").addEventListener("change", changePopulation);
|
||||
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
|
||||
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
|
||||
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
|
||||
|
||||
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
|
||||
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
|
||||
document.getElementById("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle);
|
||||
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
|
||||
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
|
||||
|
||||
document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgToggleMFCGMap").addEventListener("click", toggleMFCGMap);
|
||||
document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg);
|
||||
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
|
||||
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
|
||||
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
|
||||
>>>>>>> master
|
||||
|
||||
function updateBurgValues() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
const b = pack.burgs[id];
|
||||
|
||||
document.getElementById('burgName').value = b.name;
|
||||
document.getElementById('burgType').value = b.type || 'Generic';
|
||||
document.getElementById('burgPopulation').value = rn(b.population * populationRate * urbanization);
|
||||
|
||||
const stateName = pack.states[b.state].fullName || pack.states[b.state].name;
|
||||
const province = pack.cells.province[b.cell];
|
||||
const provinceName = province ? pack.provinces[province].fullName : '';
|
||||
document.getElementById('burgState').innerHTML = stateName;
|
||||
document.getElementById('burgProvince').innerHTML = provinceName;
|
||||
|
||||
document.getElementById('burgEditAnchorStyle').style.display = +b.port ? 'inline-block' : 'none';
|
||||
|
||||
// update list and select culture
|
||||
const cultureSelect = document.getElementById('burgCulture');
|
||||
cultureSelect.options.length = 0;
|
||||
const cultures = pack.cultures.filter((c) => !c.removed);
|
||||
cultures.forEach((c) => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture)));
|
||||
|
||||
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
|
||||
document.getElementById('burgTemperature').innerHTML = convertTemperature(temperature);
|
||||
document.getElementById('burgTemperatureLike').innerHTML = getTemperatureLikeness(temperature);
|
||||
document.getElementById('burgElevation').innerHTML = getHeight(pack.cells.h[b.cell]);
|
||||
|
||||
// toggle features
|
||||
if (b.capital) document.getElementById('burgCapital').classList.remove('inactive');
|
||||
else document.getElementById('burgCapital').classList.add('inactive');
|
||||
if (b.port) document.getElementById('burgPort').classList.remove('inactive');
|
||||
else document.getElementById('burgPort').classList.add('inactive');
|
||||
if (b.citadel) document.getElementById('burgCitadel').classList.remove('inactive');
|
||||
else document.getElementById('burgCitadel').classList.add('inactive');
|
||||
if (b.walls) document.getElementById('burgWalls').classList.remove('inactive');
|
||||
else document.getElementById('burgWalls').classList.add('inactive');
|
||||
if (b.plaza) document.getElementById('burgPlaza').classList.remove('inactive');
|
||||
else document.getElementById('burgPlaza').classList.add('inactive');
|
||||
if (b.temple) document.getElementById('burgTemple').classList.remove('inactive');
|
||||
else document.getElementById('burgTemple').classList.add('inactive');
|
||||
if (b.shanty) document.getElementById('burgShanty').classList.remove('inactive');
|
||||
else document.getElementById('burgShanty').classList.add('inactive');
|
||||
|
||||
// economics block
|
||||
document.getElementById('burgProduction').innerHTML = getProduction(b.produced);
|
||||
const deals = pack.trade.deals;
|
||||
document.getElementById('burgExport').innerHTML = getExport(deals.filter((deal) => deal.exporter === b.i));
|
||||
document.getElementById('burgImport').innerHTML = '';
|
||||
|
||||
//toggle lock
|
||||
updateBurgLockIcon();
|
||||
|
||||
// select group
|
||||
const group = elSelected.node().parentNode.id;
|
||||
const select = document.getElementById('burgSelectGroup');
|
||||
select.options.length = 0; // remove all options
|
||||
|
||||
burgLabels.selectAll('g').each(function () {
|
||||
select.options.add(new Option(this.id, this.id, false, this.id === group));
|
||||
});
|
||||
|
||||
// set emlem image
|
||||
const coaID = 'burgCOA' + id;
|
||||
COArenderer.trigger(coaID, b.coa);
|
||||
<<<<<<< HEAD
|
||||
document.getElementById('burgEmblem').setAttribute('href', '#' + coaID);
|
||||
=======
|
||||
document.getElementById("burgEmblem").setAttribute("href", "#" + coaID);
|
||||
|
||||
if (options.showMFCGMap) {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "block";
|
||||
updateMFCGFrame(b);
|
||||
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
|
||||
} else {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "none";
|
||||
}
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function getProduction(pool) {
|
||||
let html = '';
|
||||
|
||||
for (const resourceId in pool) {
|
||||
const {name, unit, icon} = Resources.get(+resourceId);
|
||||
const production = pool[resourceId];
|
||||
const unitName = production > 1 ? unit + 's' : unit;
|
||||
|
||||
html += `<span data-tip="${name}: ${production} ${unitName}">
|
||||
<svg class="resIcon"><use href="#${icon}"></svg>
|
||||
<span style="margin: 0 0.2em 0 -0.2em">${production}</span>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function getExport(dealsArray) {
|
||||
if (!dealsArray.length) return 'no';
|
||||
|
||||
const totalIncome = rn(d3.sum(dealsArray.map((deal) => deal.burgIncome)));
|
||||
const exported = dealsArray.map((deal) => {
|
||||
const {resourceId, quantity, burgIncome} = deal;
|
||||
const {name, unit, icon} = Resources.get(resourceId);
|
||||
const unitName = quantity > 1 ? unit + 's' : unit;
|
||||
|
||||
return `<span data-tip="${name}: ${quantity} ${unitName}. Income: ${rn(burgIncome)}">
|
||||
<svg class="resIcon"><use href="#${icon}"></svg>
|
||||
<span style="margin: 0 0.2em 0 -0.2em">${quantity}</span>
|
||||
</span>`;
|
||||
});
|
||||
|
||||
return `${totalIncome}: ${exported.join('')}`;
|
||||
}
|
||||
|
||||
// [-1; 31] °C, source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
|
||||
function getTemperatureLikeness(temperature) {
|
||||
if (temperature < -15) return 'nowhere in the real-world';
|
||||
if (temperature < -5) return 'in Yakutsk';
|
||||
if (temperature > 31) return 'nowhere in the real-world';
|
||||
const cities = [
|
||||
'Snag (Yukon)',
|
||||
'Yellowknife (Canada)',
|
||||
'Okhotsk (Russia)',
|
||||
'Fairbanks (Alaska)',
|
||||
'Nuuk (Greenland)',
|
||||
'Murmansk', // -5 - 0
|
||||
'Arkhangelsk',
|
||||
'Anchorage',
|
||||
'Tromsø',
|
||||
'Reykjavik',
|
||||
'Riga',
|
||||
'Stockholm',
|
||||
'Halifax',
|
||||
'Prague',
|
||||
'Copenhagen',
|
||||
'London', // 1 - 10
|
||||
'Antwerp',
|
||||
'Paris',
|
||||
'Milan',
|
||||
'Batumi',
|
||||
'Rome',
|
||||
'Dubrovnik',
|
||||
'Lisbon',
|
||||
'Barcelona',
|
||||
'Marrakesh',
|
||||
'Alexandria', // 11 - 20
|
||||
'Tegucigalpa',
|
||||
'Guangzhou',
|
||||
'Rio de Janeiro',
|
||||
'Dakar',
|
||||
'Miami',
|
||||
'Jakarta',
|
||||
'Mogadishu',
|
||||
'Bangkok',
|
||||
'Aden',
|
||||
'Khartoum'
|
||||
]; // 21 - 30
|
||||
if (temperature > 30) return 'Mecca';
|
||||
return cities[temperature + 5] || null;
|
||||
}
|
||||
|
||||
function dragBurgLabel() {
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const x = d3.event.x,
|
||||
y = d3.event.y;
|
||||
this.setAttribute('transform', `translate(${dx + x},${dy + y})`);
|
||||
tip('Use dragging for fine-tuning only, to actually move burg use "Relocate" button', false, 'warning');
|
||||
});
|
||||
}
|
||||
|
||||
function showGroupSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('burgGroupSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('burgGroupSection').style.display = 'none';
|
||||
document.getElementById('burgInputGroup').style.display = 'none';
|
||||
document.getElementById('burgInputGroup').value = '';
|
||||
document.getElementById('burgSelectGroup').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
moveBurgToGroup(id, this.value);
|
||||
}
|
||||
|
||||
function toggleNewGroupInput() {
|
||||
if (burgInputGroup.style.display === 'none') {
|
||||
burgInputGroup.style.display = 'inline-block';
|
||||
burgInputGroup.focus();
|
||||
burgSelectGroup.style.display = 'none';
|
||||
} else {
|
||||
burgInputGroup.style.display = 'none';
|
||||
burgSelectGroup.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {
|
||||
tip('Please provide a valid group name', false, 'error');
|
||||
return;
|
||||
}
|
||||
const group = this.value
|
||||
.toLowerCase()
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
|
||||
if (document.getElementById(group)) {
|
||||
tip('Element with this id already exists. Please provide a unique name', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isFinite(+group.charAt(0))) {
|
||||
tip('Group name should start with a letter', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = +elSelected.attr('data-id');
|
||||
const oldGroup = elSelected.node().parentNode.id;
|
||||
|
||||
const label = document.querySelector("#burgLabels [data-id='" + id + "']");
|
||||
const icon = document.querySelector("#burgIcons [data-id='" + id + "']");
|
||||
const anchor = document.querySelector("#anchors [data-id='" + id + "']");
|
||||
if (!label || !icon) {
|
||||
ERROR && console.error('Cannot find label or icon elements');
|
||||
return;
|
||||
}
|
||||
|
||||
const labelG = document.querySelector('#burgLabels > #' + oldGroup);
|
||||
const iconG = document.querySelector('#burgIcons > #' + oldGroup);
|
||||
const anchorG = document.querySelector('#anchors > #' + oldGroup);
|
||||
|
||||
// just rename if only 1 element left
|
||||
const count = elSelected.node().parentNode.childElementCount;
|
||||
if (oldGroup !== 'cities' && oldGroup !== 'towns' && count === 1) {
|
||||
document.getElementById('burgSelectGroup').selectedOptions[0].remove();
|
||||
document.getElementById('burgSelectGroup').options.add(new Option(group, group, false, true));
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('burgInputGroup').value = '';
|
||||
labelG.id = group;
|
||||
iconG.id = group;
|
||||
if (anchor) anchorG.id = group;
|
||||
return;
|
||||
}
|
||||
|
||||
// create new groups
|
||||
document.getElementById('burgSelectGroup').options.add(new Option(group, group, false, true));
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('burgInputGroup').value = '';
|
||||
|
||||
const newLabelG = document.querySelector('#burgLabels').appendChild(labelG.cloneNode(false));
|
||||
newLabelG.id = group;
|
||||
const newIconG = document.querySelector('#burgIcons').appendChild(iconG.cloneNode(false));
|
||||
newIconG.id = group;
|
||||
if (anchor) {
|
||||
const newAnchorG = document.querySelector('#anchors').appendChild(anchorG.cloneNode(false));
|
||||
newAnchorG.id = group;
|
||||
}
|
||||
moveBurgToGroup(id, group);
|
||||
}
|
||||
|
||||
function removeBurgsGroup() {
|
||||
const group = elSelected.node().parentNode;
|
||||
const basic = group.id === 'cities' || group.id === 'towns';
|
||||
|
||||
const burgsInGroup = [];
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
burgsInGroup.push(+group.children[i].dataset.id);
|
||||
}
|
||||
const burgsToRemove = burgsInGroup.filter((b) => !(pack.burgs[b].capital || pack.burgs[b].lock));
|
||||
const capital = burgsToRemove.length < burgsInGroup.length;
|
||||
|
||||
const message = `Are you sure you want to remove
|
||||
${basic || capital ? 'all unlocked elements in the group' : 'the entire burg group'}?
|
||||
<br>Please note that capital or locked burgs will not be deleted.
|
||||
<br><br>Burgs to be removed: ${burgsToRemove.length}`;
|
||||
confirmationDialog({title: 'Remove burg group', message, confirm: 'Remove', onConfirm: removeGroup});
|
||||
|
||||
function removeGroup() {
|
||||
$(this).dialog('close');
|
||||
$('#burgEditor').dialog('close');
|
||||
hideGroupSection();
|
||||
burgsToRemove.forEach((b) => removeBurg(b));
|
||||
|
||||
if (!basic && !capital) {
|
||||
// entirely remove group
|
||||
const labelG = document.querySelector('#burgLabels > #' + group.id);
|
||||
const iconG = document.querySelector('#burgIcons > #' + group.id);
|
||||
const anchorG = document.querySelector('#anchors > #' + group.id);
|
||||
if (labelG) labelG.remove();
|
||||
if (iconG) iconG.remove();
|
||||
if (anchorG) anchorG.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
pack.burgs[id].name = burgName.value;
|
||||
elSelected.text(burgName.value);
|
||||
}
|
||||
|
||||
function generateNameRandom() {
|
||||
const base = rand(nameBases.length - 1);
|
||||
burgName.value = Names.getBase(base);
|
||||
changeName();
|
||||
}
|
||||
|
||||
function changeType() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
pack.burgs[id].type = this.value;
|
||||
}
|
||||
|
||||
function changeCulture() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
pack.burgs[id].culture = +this.value;
|
||||
}
|
||||
|
||||
function generateNameCulture() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
const culture = pack.burgs[id].culture;
|
||||
burgName.value = Names.getCulture(culture);
|
||||
changeName();
|
||||
}
|
||||
|
||||
function changePopulation() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4);
|
||||
}
|
||||
|
||||
function toggleFeature() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
const b = pack.burgs[id];
|
||||
const feature = this.dataset.feature;
|
||||
const turnOn = this.classList.contains('inactive');
|
||||
if (feature === 'port') togglePort(id);
|
||||
else if (feature === 'capital') toggleCapital(id);
|
||||
else b[feature] = +turnOn;
|
||||
if (b[feature]) this.classList.remove('inactive');
|
||||
else if (!b[feature]) this.classList.add('inactive');
|
||||
|
||||
if (b.port) document.getElementById('burgEditAnchorStyle').style.display = 'inline-block';
|
||||
else document.getElementById('burgEditAnchorStyle').style.display = 'none';
|
||||
}
|
||||
|
||||
function toggleBurgLockButton() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
toggleBurgLock(id);
|
||||
updateBurgLockIcon();
|
||||
}
|
||||
|
||||
function updateBurgLockIcon() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
const b = pack.burgs[id];
|
||||
if (b.lock) {
|
||||
document.getElementById('burgLock').classList.remove('icon-lock-open');
|
||||
document.getElementById('burgLock').classList.add('icon-lock');
|
||||
} else {
|
||||
document.getElementById('burgLock').classList.remove('icon-lock');
|
||||
document.getElementById('burgLock').classList.add('icon-lock-open');
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
function showBurgELockTip() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
showBurgLockTip(id);
|
||||
}
|
||||
|
||||
=======
|
||||
>>>>>>> master
|
||||
function showStyleSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('burgStyleSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideStyleSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('burgStyleSection').style.display = 'none';
|
||||
}
|
||||
|
||||
function editGroupLabelStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('labels', g);
|
||||
}
|
||||
|
||||
function editGroupIconStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('burgIcons', g);
|
||||
}
|
||||
|
||||
function editGroupAnchorStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('anchors', g);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
function openInMFCG(event) {
|
||||
const id = elSelected.attr('data-id');
|
||||
const burg = pack.burgs[id];
|
||||
const defSeed = +(seed + id.padStart(4, 0));
|
||||
if (isCtrlClick(event)) {
|
||||
prompt(
|
||||
`Please provide a Medieval Fantasy City Generator seed.
|
||||
Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}).
|
||||
Please note that if seed is custom, "Overworld" button from MFCG will open a different map`,
|
||||
{default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1},
|
||||
(v) => {
|
||||
burg.MFCG = v;
|
||||
openMFCG(v);
|
||||
}
|
||||
);
|
||||
} else openMFCG();
|
||||
|
||||
function openMFCG(seed) {
|
||||
if (!seed && burg.MFCGlink) {
|
||||
openURL(burg.MFCGlink);
|
||||
return;
|
||||
}
|
||||
const cells = pack.cells;
|
||||
const name = elSelected.text();
|
||||
const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : '';
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return '&sea=' + norm;
|
||||
}
|
||||
|
||||
const site = 'http://fantasycities.watabou.ru/?random=0&continuous=0';
|
||||
const url = `${site}&name=${name}&population=${population}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
openURL(url);
|
||||
=======
|
||||
function updateMFCGFrame(burg) {
|
||||
const mfcgURL = getMFCGlink(burg);
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
}
|
||||
function getBurgSeed(burg) {
|
||||
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
|
||||
}
|
||||
|
||||
function getMFCGlink(burg) {
|
||||
const {cells} = pack;
|
||||
const {name, population, cell} = burg;
|
||||
const burgSeed = getBurgSeed(burg);
|
||||
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
|
||||
const size = minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
const people = rn(population * populationRate * urbanization);
|
||||
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
const baseURL = "https://watabou.github.io/city-generator/?random=0&continuous=0";
|
||||
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
function changeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = +this.value;
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
}
|
||||
|
||||
function randomizeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = rand(1e9 - 1);
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
document.getElementById("mfcgBurgSeed").value = burgSeed;
|
||||
}
|
||||
|
||||
function openEmblemEdit() {
|
||||
const id = +elSelected.attr('data-id'),
|
||||
burg = pack.burgs[id];
|
||||
editEmblem('burg', 'burgCOA' + id, burg);
|
||||
}
|
||||
|
||||
function toggleMFCGMap() {
|
||||
options.showMFCGMap = !options.showMFCGMap;
|
||||
document.getElementById("mfcgPreviewSection").style.display = options.showMFCGMap ? "block" : "none";
|
||||
document.getElementById("burgToggleMFCGMap").className = options.showMFCGMap ? "icon-map" : "icon-map-o";
|
||||
}
|
||||
|
||||
function toggleRelocateBurg() {
|
||||
const toggler = document.getElementById('toggleCells');
|
||||
document.getElementById('burgRelocate').classList.toggle('pressed');
|
||||
if (document.getElementById('burgRelocate').classList.contains('pressed')) {
|
||||
viewbox.style('cursor', 'crosshair').on('click', relocateBurgOnClick);
|
||||
tip('Click on map to relocate burg. Hold Shift for continuous move', true);
|
||||
if (!layerIsOn('toggleCells')) {
|
||||
toggleCells();
|
||||
toggler.dataset.forced = true;
|
||||
}
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on('click', clicked).style('cursor', 'default');
|
||||
if (layerIsOn('toggleCells') && toggler.dataset.forced) {
|
||||
toggleCells();
|
||||
toggler.dataset.forced = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function relocateBurgOnClick() {
|
||||
const cells = pack.cells;
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const id = +elSelected.attr('data-id');
|
||||
const burg = pack.burgs[id];
|
||||
|
||||
if (cells.h[cell] < 20) {
|
||||
tip('Cannot place burg into the water! Select a land cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (cells.burg[cell] && cells.burg[cell] !== id) {
|
||||
tip('There is already a burg in this cell. Please select a free cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const newState = cells.state[cell];
|
||||
const oldState = burg.state;
|
||||
|
||||
if (newState !== oldState && burg.capital) {
|
||||
tip('Capital cannot be relocated into another state!', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// change UI
|
||||
const x = rn(point[0], 2),
|
||||
y = rn(point[1], 2);
|
||||
burgIcons
|
||||
.select("[data-id='" + id + "']")
|
||||
.attr('transform', null)
|
||||
.attr('cx', x)
|
||||
.attr('cy', y);
|
||||
burgLabels
|
||||
.select("text[data-id='" + id + "']")
|
||||
.attr('transform', null)
|
||||
.attr('x', x)
|
||||
.attr('y', y);
|
||||
const anchor = anchors.select("use[data-id='" + id + "']");
|
||||
if (anchor.size()) {
|
||||
const size = anchor.attr('width');
|
||||
const xa = rn(x - size * 0.47, 2);
|
||||
const ya = rn(y - size * 0.47, 2);
|
||||
anchor.attr('transform', null).attr('x', xa).attr('y', ya);
|
||||
}
|
||||
|
||||
// change data
|
||||
cells.burg[burg.cell] = 0;
|
||||
cells.burg[cell] = id;
|
||||
burg.cell = cell;
|
||||
burg.state = newState;
|
||||
burg.x = x;
|
||||
burg.y = y;
|
||||
if (burg.capital) pack.states[newState].center = burg.cell;
|
||||
|
||||
if (d3.event.shiftKey === false) toggleRelocateBurg();
|
||||
}
|
||||
|
||||
function editBurgLegend() {
|
||||
const id = elSelected.attr('data-id');
|
||||
const name = elSelected.text();
|
||||
editNotes('burg' + id, name);
|
||||
}
|
||||
|
||||
function removeSelectedBurg() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
if (pack.burgs[id].capital) {
|
||||
alertMessage.innerHTML = `You cannot remove the burg as it is a state capital.<br><br>
|
||||
You can change the capital using Burgs Editor (shift + T)`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove burg',
|
||||
buttons: {
|
||||
Ok: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const message = 'Are you sure you want to remove the burg? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
removeBurg(id);
|
||||
$('#burgEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove burg', message, confirm: 'Remove', onConfirm});
|
||||
}
|
||||
}
|
||||
|
||||
function closeBurgEditor() {
|
||||
document.getElementById('burgRelocate').classList.remove('pressed');
|
||||
burgLabels.selectAll('text').call(d3.drag().on('drag', null)).classed('draggable', false);
|
||||
unselect();
|
||||
}
|
||||
}
|
||||
|
|
@ -79,17 +79,20 @@ function overviewBurgs() {
|
|||
const province = prov ? pack.provinces[prov].name : '';
|
||||
const culture = pack.cultures[b.culture].name;
|
||||
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${
|
||||
b.name
|
||||
}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled>
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(b.culture)}</select>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
b.culture
|
||||
)}</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)}>
|
||||
<div class="burgType">
|
||||
<span data-tip="${b.capital ? ' This burg is a state capital' : 'Click to assign a capital status'}" class="icon-star-empty${b.capital ? '' : ' inactive pointer'}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? '' : ' inactive'}" style="font-size:.9em"></span>
|
||||
}"></span>
|
||||
</div>
|
||||
<span data-tip="Zoom to burg" class="icon-dot-circled pointer"></span>
|
||||
<span class="locks pointer ${b.lock ? 'icon-lock' : 'icon-lock-open inactive'}"></span>
|
||||
|
|
@ -202,11 +205,6 @@ function overviewBurgs() {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgOLockTip() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
showBurgLockTip(burg);
|
||||
}
|
||||
|
||||
function openBurgEditor() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
editBurg(burg);
|
||||
|
|
@ -281,6 +279,7 @@ function overviewBurgs() {
|
|||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const burgs = pack.burgs
|
||||
.filter((b) => b.i && !b.removed)
|
||||
.map((b) => {
|
||||
|
|
@ -292,6 +291,7 @@ function overviewBurgs() {
|
|||
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
|
||||
});
|
||||
const data = states.concat(burgs);
|
||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
|
|
@ -401,6 +401,12 @@ function overviewBurgs() {
|
|||
|
||||
const base = this.value === 'states' ? getStatesData() : this.value === 'cultures' ? getCulturesData() : this.value === 'parent' ? getParentData() : getProvincesData();
|
||||
burgs.forEach((b) => (b.id = b.i + base.length - 1));
|
||||
? getStatesData()
|
||||
: this.value === "cultures"
|
||||
? getCulturesData()
|
||||
: this.value === "parent"
|
||||
? getParentData()
|
||||
: getProvincesData();
|
||||
|
||||
const data = base.concat(burgs);
|
||||
|
||||
|
|
@ -435,6 +441,8 @@ function overviewBurgs() {
|
|||
function downloadBurgsData() {
|
||||
let data = 'Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation (' + heightUnit.value + '),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n'; // headers
|
||||
const valid = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
|
||||
heightUnit.value +
|
||||
"),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||
|
||||
valid.forEach((b) => {
|
||||
data += b.i + ',';
|
||||
|
|
|
|||
603
modules/ui/burgs-overview.js.orig
Normal file
603
modules/ui/burgs-overview.js.orig
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
'use strict';
|
||||
function overviewBurgs() {
|
||||
if (customization) return;
|
||||
closeDialogs('#burgsOverview, .stable');
|
||||
if (!layerIsOn('toggleIcons')) toggleIcons();
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
|
||||
const body = document.getElementById('burgsBody');
|
||||
updateFilter();
|
||||
burgsOverviewAddLines();
|
||||
$('#burgsOverview').dialog();
|
||||
|
||||
if (modules.overviewBurgs) return;
|
||||
modules.overviewBurgs = true;
|
||||
|
||||
$('#burgsOverview').dialog({
|
||||
title: 'Burgs Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: exitAddBurgMode,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('burgsOverviewRefresh').addEventListener('click', refreshBurgsEditor);
|
||||
document.getElementById('burgsChart').addEventListener('click', showBurgsChart);
|
||||
document.getElementById('burgsFilterState').addEventListener('change', burgsOverviewAddLines);
|
||||
document.getElementById('burgsFilterCulture').addEventListener('change', burgsOverviewAddLines);
|
||||
document.getElementById('regenerateBurgNames').addEventListener('click', regenerateNames);
|
||||
document.getElementById('addNewBurg').addEventListener('click', enterAddBurgMode);
|
||||
document.getElementById('burgsExport').addEventListener('click', downloadBurgsData);
|
||||
document.getElementById('burgNamesImport').addEventListener('click', renameBurgsInBulk);
|
||||
document.getElementById('burgsListToLoad').addEventListener('change', function () {
|
||||
uploadFile(this, importBurgNames);
|
||||
});
|
||||
document.getElementById('burgsRemoveAll').addEventListener('click', triggerAllBurgsRemove);
|
||||
|
||||
function refreshBurgsEditor() {
|
||||
updateFilter();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function updateFilter() {
|
||||
const stateFilter = document.getElementById('burgsFilterState');
|
||||
const selectedState = stateFilter.value || 1;
|
||||
stateFilter.options.length = 0; // remove all options
|
||||
stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1));
|
||||
stateFilter.options.add(new Option(pack.states[0].name, 0, false, !selectedState));
|
||||
const statesSorted = pack.states.filter((s) => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
statesSorted.forEach((s) => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
|
||||
|
||||
const cultureFilter = document.getElementById('burgsFilterCulture');
|
||||
const selectedCulture = cultureFilter.value || -1;
|
||||
cultureFilter.options.length = 0; // remove all options
|
||||
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture == -1));
|
||||
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, !selectedCulture));
|
||||
const culturesSorted = pack.cultures.filter((c) => c.i && !c.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
culturesSorted.forEach((c) => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture)));
|
||||
}
|
||||
|
||||
// add line for each burg
|
||||
function burgsOverviewAddLines() {
|
||||
const selectedState = +document.getElementById('burgsFilterState').value;
|
||||
const selectedCulture = +document.getElementById('burgsFilterCulture').value;
|
||||
let filtered = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
|
||||
if (selectedState != -1) filtered = filtered.filter((b) => b.state === selectedState); // filtered by state
|
||||
if (selectedCulture != -1) filtered = filtered.filter((b) => b.culture === selectedCulture); // filtered by culture
|
||||
|
||||
body.innerHTML = '';
|
||||
let lines = '',
|
||||
totalPopulation = 0;
|
||||
|
||||
for (const b of filtered) {
|
||||
const population = b.population * populationRate * urbanization;
|
||||
totalPopulation += population;
|
||||
const type = b.capital && b.port ? 'a-capital-port' : b.capital ? 'c-capital' : b.port ? 'p-port' : 'z-burg';
|
||||
const state = pack.states[b.state].name;
|
||||
const prov = pack.cells.province[b.cell];
|
||||
const province = prov ? pack.provinces[prov].name : '';
|
||||
const culture = pack.cultures[b.culture].name;
|
||||
|
||||
<<<<<<< HEAD
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
=======
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${
|
||||
b.name
|
||||
}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
||||
>>>>>>> master
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled>
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
b.culture
|
||||
)}</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)}>
|
||||
<div class="burgType">
|
||||
<<<<<<< HEAD
|
||||
<span data-tip="${b.capital ? ' This burg is a state capital' : 'Click to assign a capital status'}" class="icon-star-empty${b.capital ? '' : ' inactive pointer'}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? '' : ' inactive'}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Zoom to burg" class="icon-dot-circled pointer"></span>
|
||||
<span class="locks pointer ${b.lock ? 'icon-lock' : 'icon-lock-open inactive'}"></span>
|
||||
=======
|
||||
<span data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}" class="icon-star-empty${
|
||||
b.capital ? "" : " inactive pointer"
|
||||
}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
>>>>>>> master
|
||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
|
||||
// update footer
|
||||
burgsFooterBurgs.innerHTML = filtered.length;
|
||||
burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => burgHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => burgHighlightOff(ev)));
|
||||
body.querySelectorAll('div > input.burgName').forEach((el) => el.addEventListener('input', changeBurgName));
|
||||
body.querySelectorAll('div > span.icon-dot-circled').forEach((el) => el.addEventListener('click', zoomIntoBurg));
|
||||
body.querySelectorAll('div > select.stateCulture').forEach((el) => el.addEventListener('change', changeBurgCulture));
|
||||
body.querySelectorAll('div > input.burgPopulation').forEach((el) => el.addEventListener('change', changeBurgPopulation));
|
||||
body.querySelectorAll('div > span.icon-star-empty').forEach((el) => el.addEventListener('click', toggleCapitalStatus));
|
||||
body.querySelectorAll('div > span.icon-anchor').forEach((el) => el.addEventListener('click', togglePortStatus));
|
||||
body.querySelectorAll('div > span.locks').forEach((el) => el.addEventListener('click', toggleBurgLockStatus));
|
||||
body.querySelectorAll('div > span.locks').forEach((el) => el.addEventListener('mouseover', showBurgOLockTip));
|
||||
body.querySelectorAll('div > span.icon-pencil').forEach((el) => el.addEventListener('click', openBurgEditor));
|
||||
body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', triggerBurgRemove));
|
||||
=======
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => burgHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => burgHighlightOff(ev)));
|
||||
body.querySelectorAll("div > input.burgName").forEach(el => el.addEventListener("input", changeBurgName));
|
||||
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomIntoBurg));
|
||||
body.querySelectorAll("div > select.stateCulture").forEach(el => el.addEventListener("change", changeBurgCulture));
|
||||
body.querySelectorAll("div > input.burgPopulation").forEach(el => el.addEventListener("change", changeBurgPopulation));
|
||||
body.querySelectorAll("div > span.icon-star-empty").forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerBurgRemove));
|
||||
>>>>>>> master
|
||||
|
||||
applySorting(burgsHeader);
|
||||
}
|
||||
|
||||
function getCultureOptions(culture) {
|
||||
let options = '';
|
||||
pack.cultures.filter((c) => !c.removed).forEach((c) => (options += `<option ${c.i === culture ? 'selected' : ''} value="${c.i}">${c.name}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
function burgHighlightOn(event) {
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
const burg = +event.target.dataset.id;
|
||||
burgLabels.select("[data-id='" + burg + "']").classed('drag', true);
|
||||
}
|
||||
|
||||
function burgHighlightOff() {
|
||||
burgLabels.selectAll('text.drag').classed('drag', false);
|
||||
}
|
||||
|
||||
function changeBurgName() {
|
||||
if (this.value == '') tip('Please provide a name', false, 'error');
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
pack.burgs[burg].name = this.value;
|
||||
this.parentNode.dataset.name = this.value;
|
||||
const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
|
||||
if (label) label.innerHTML = this.value;
|
||||
}
|
||||
|
||||
function zoomIntoBurg() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
|
||||
const x = +label.getAttribute('x'),
|
||||
y = +label.getAttribute('y');
|
||||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function changeBurgCulture() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
const v = +this.value;
|
||||
pack.burgs[burg].culture = v;
|
||||
this.parentNode.dataset.culture = pack.cultures[v].name;
|
||||
}
|
||||
|
||||
function changeBurgPopulation() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (this.value == '' || isNaN(+this.value)) {
|
||||
tip('Please provide an integer number (like 10000, not 10K)', false, 'error');
|
||||
this.value = si(pack.burgs[burg].population * populationRate * urbanization);
|
||||
return;
|
||||
}
|
||||
pack.burgs[burg].population = this.value / populationRate / urbanization;
|
||||
this.parentNode.dataset.population = this.value;
|
||||
this.value = si(this.value);
|
||||
|
||||
const population = [];
|
||||
body.querySelectorAll(':scope > div').forEach((el) => population.push(+getInteger(el.dataset.population)));
|
||||
burgsFooterPopulation.innerHTML = si(d3.mean(population));
|
||||
}
|
||||
|
||||
function toggleCapitalStatus() {
|
||||
const burg = +this.parentNode.parentNode.dataset.id;
|
||||
toggleCapital(burg);
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function togglePortStatus() {
|
||||
const burg = +this.parentNode.parentNode.dataset.id;
|
||||
togglePort(burg);
|
||||
if (this.classList.contains('inactive')) this.classList.remove('inactive');
|
||||
else this.classList.add('inactive');
|
||||
}
|
||||
|
||||
function toggleBurgLockStatus() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
toggleBurgLock(burg);
|
||||
if (this.classList.contains('icon-lock')) {
|
||||
this.classList.remove('icon-lock');
|
||||
this.classList.add('icon-lock-open');
|
||||
this.classList.add('inactive');
|
||||
} else {
|
||||
this.classList.remove('icon-lock-open');
|
||||
this.classList.add('icon-lock');
|
||||
this.classList.remove('inactive');
|
||||
}
|
||||
}
|
||||
|
||||
function openBurgEditor() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
editBurg(burg);
|
||||
}
|
||||
|
||||
function triggerBurgRemove() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (pack.burgs[burg].capital) {
|
||||
tip('You cannot remove the capital. Please change the capital first', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = 'Are you sure you want to remove the burg? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
removeBurg(burg);
|
||||
burgsOverviewAddLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove burg', message, confirm: 'Remove', onConfirm});
|
||||
}
|
||||
|
||||
function regenerateNames() {
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
const burg = +el.dataset.id;
|
||||
//if (pack.burgs[burg].lock) return;
|
||||
const culture = pack.burgs[burg].culture;
|
||||
const name = Names.getCulture(culture);
|
||||
if (!pack.burgs[burg].lock) {
|
||||
el.querySelector('.burgName').value = name;
|
||||
pack.burgs[burg].name = el.dataset.name = name;
|
||||
burgLabels.select("[data-id='" + burg + "']").text(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enterAddBurgMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
exitAddBurgMode();
|
||||
return;
|
||||
}
|
||||
customization = 3;
|
||||
this.classList.add('pressed');
|
||||
tip('Click on the map to create a new burg. Hold Shift to add multiple', true, 'warn');
|
||||
viewbox.style('cursor', 'crosshair').on('click', addBurgOnClick);
|
||||
}
|
||||
|
||||
function addBurgOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[cell] < 20) return tip('You cannot place state into the water. Please click on a land cell', false, 'error');
|
||||
if (pack.cells.burg[cell]) return tip('There is already a burg in this cell. Please select a free cell', false, 'error');
|
||||
|
||||
addBurg(point); // add new burg
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
exitAddBurgMode();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
function exitAddBurgMode() {
|
||||
customization = 0;
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
if (addBurgTool.classList.contains('pressed')) addBurgTool.classList.remove('pressed');
|
||||
if (addNewBurg.classList.contains('pressed')) addNewBurg.classList.remove('pressed');
|
||||
}
|
||||
|
||||
function showBurgsChart() {
|
||||
// build hierarchy tree
|
||||
const states = pack.states.map((s) => {
|
||||
const color = s.color ? s.color : '#ccc';
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const burgs = pack.burgs
|
||||
.filter((b) => b.i && !b.removed)
|
||||
.map((b) => {
|
||||
const id = b.i + states.length - 1;
|
||||
const population = b.population;
|
||||
const capital = b.capital;
|
||||
const province = pack.cells.province[b.cell];
|
||||
const parent = province ? province + states.length - 1 : b.state;
|
||||
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
|
||||
});
|
||||
const data = states.concat(burgs);
|
||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.parentId((d) => d.state)(data)
|
||||
.sum((d) => d.population)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const width = 150 + 200 * uiSizeOutput.value,
|
||||
height = 150 + 200 * uiSizeOutput.value;
|
||||
const margin = {top: 0, right: -50, bottom: -10, left: -50};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height - margin.top - margin.bottom;
|
||||
const treeLayout = d3.pack().size([w, h]).padding(3);
|
||||
|
||||
// prepare svg
|
||||
alertMessage.innerHTML = `<select id="burgsTreeType" style="display:block; margin-left:13px; font-size:11px">
|
||||
<option value="states" selected>Group by state</option>
|
||||
<option value="cultures">Group by culture</option>
|
||||
<option value="parent">Group by province and state</option>
|
||||
<option value="provinces">Group by province</option></select>`;
|
||||
alertMessage.innerHTML += `<div id='burgsInfo' class='chartInfo'>‍</div>`;
|
||||
const svg = d3
|
||||
.select('#alertMessage')
|
||||
.insert('svg', '#burgsInfo')
|
||||
.attr('id', 'burgsTree')
|
||||
.attr('width', width)
|
||||
.attr('height', height - 10)
|
||||
.attr('stroke-width', 2);
|
||||
const graph = svg.append('g').attr('transform', `translate(-50, -10)`);
|
||||
document.getElementById('burgsTreeType').addEventListener('change', updateChart);
|
||||
|
||||
treeLayout(root);
|
||||
|
||||
const node = graph
|
||||
.selectAll('circle')
|
||||
.data(root.leaves())
|
||||
.join('circle')
|
||||
.attr('data-id', (d) => d.data.i)
|
||||
.attr('r', (d) => d.r)
|
||||
.attr('fill', (d) => d.parent.data.color)
|
||||
.attr('cx', (d) => d.x)
|
||||
.attr('cy', (d) => d.y)
|
||||
.on('mouseenter', (d) => showInfo(event, d))
|
||||
.on('mouseleave', (d) => hideInfo(event, d))
|
||||
.on('click', (d) => zoomTo(d.data.x, d.data.y, 8, 2000));
|
||||
|
||||
function showInfo(ev, d) {
|
||||
d3.select(ev.target).transition().duration(1500).attr('stroke', '#c13119');
|
||||
const name = d.data.name;
|
||||
const parent = d.parent.data.name;
|
||||
const population = si(d.value * populationRate * urbanization);
|
||||
|
||||
burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`;
|
||||
burgHighlightOn(ev);
|
||||
tip('Click to zoom into view');
|
||||
}
|
||||
|
||||
function hideInfo(ev) {
|
||||
burgHighlightOff(ev);
|
||||
if (!document.getElementById('burgsInfo')) return;
|
||||
burgsInfo.innerHTML = '‍';
|
||||
d3.select(ev.target).transition().attr('stroke', null);
|
||||
tip('');
|
||||
}
|
||||
|
||||
function updateChart() {
|
||||
const getStatesData = () =>
|
||||
pack.states.map((s) => {
|
||||
const color = s.color ? s.color : '#ccc';
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const getCulturesData = () =>
|
||||
pack.cultures.map((c) => {
|
||||
const color = c.color ? c.color : '#ccc';
|
||||
return {id: c.i, culture: c.i ? 0 : null, color, name: c.name};
|
||||
});
|
||||
|
||||
const getParentData = () => {
|
||||
const states = pack.states.map((s) => {
|
||||
const color = s.color ? s.color : '#ccc';
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, parent: s.i ? 0 : null, color, name};
|
||||
});
|
||||
const provinces = pack.provinces
|
||||
.filter((p) => p.i && !p.removed)
|
||||
.map((p) => {
|
||||
return {id: p.i + states.length - 1, parent: p.state, color: p.color, name: p.fullName};
|
||||
});
|
||||
return states.concat(provinces);
|
||||
};
|
||||
|
||||
const getProvincesData = () =>
|
||||
pack.provinces.map((p) => {
|
||||
const color = p.color ? p.color : '#ccc';
|
||||
const name = p.fullName ? p.fullName : p.name;
|
||||
return {id: p.i ? p.i : 0, province: p.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const value = (d) => {
|
||||
if (this.value === 'states') return d.state;
|
||||
if (this.value === 'cultures') return d.culture;
|
||||
if (this.value === 'parent') return d.parent;
|
||||
if (this.value === 'provinces') return d.province;
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
const base = this.value === 'states' ? getStatesData() : this.value === 'cultures' ? getCulturesData() : this.value === 'parent' ? getParentData() : getProvincesData();
|
||||
burgs.forEach((b) => (b.id = b.i + base.length - 1));
|
||||
=======
|
||||
const base =
|
||||
this.value === "states"
|
||||
? getStatesData()
|
||||
: this.value === "cultures"
|
||||
? getCulturesData()
|
||||
: this.value === "parent"
|
||||
? getParentData()
|
||||
: getProvincesData();
|
||||
burgs.forEach(b => (b.id = b.i + base.length - 1));
|
||||
>>>>>>> master
|
||||
|
||||
const data = base.concat(burgs);
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.parentId((d) => value(d))(data)
|
||||
.sum((d) => d.population)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
node
|
||||
.data(treeLayout(root).leaves())
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.attr('data-id', (d) => d.data.i)
|
||||
.attr('fill', (d) => d.parent.data.color)
|
||||
.attr('cx', (d) => d.x)
|
||||
.attr('cy', (d) => d.y)
|
||||
.attr('r', (d) => d.r);
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Burgs bubble chart',
|
||||
width: fitContent(),
|
||||
position: {my: 'left bottom', at: 'left+10 bottom-10', of: 'svg'},
|
||||
buttons: {},
|
||||
close: () => {
|
||||
alertMessage.innerHTML = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadBurgsData() {
|
||||
<<<<<<< HEAD
|
||||
let data = 'Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation (' + heightUnit.value + '),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n'; // headers
|
||||
const valid = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
|
||||
=======
|
||||
let data =
|
||||
"Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Longitude,Latitude,Elevation (" +
|
||||
heightUnit.value +
|
||||
"),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
>>>>>>> master
|
||||
|
||||
valid.forEach((b) => {
|
||||
data += b.i + ',';
|
||||
data += b.name + ',';
|
||||
const province = pack.cells.province[b.cell];
|
||||
<<<<<<< HEAD
|
||||
data += province ? pack.provinces[province].fullName + ',' : ',';
|
||||
data += b.state ? pack.states[b.state].fullName + ',' : pack.states[b.state].name + ',';
|
||||
data += pack.cultures[b.culture].name + ',';
|
||||
data += pack.religions[pack.cells.religion[b.cell]].name + ',';
|
||||
data += rn(b.population * populationRate * urbanization) + ',';
|
||||
=======
|
||||
data += province ? pack.provinces[province].name + "," : ",";
|
||||
data += province ? pack.provinces[province].fullName + "," : ",";
|
||||
data += pack.states[b.state].name + ",";
|
||||
data += pack.states[b.state].fullName + ",";
|
||||
data += pack.cultures[b.culture].name + ",";
|
||||
data += pack.religions[pack.cells.religion[b.cell]].name + ",";
|
||||
data += rn(b.population * populationRate * urbanization) + ",";
|
||||
>>>>>>> master
|
||||
|
||||
// add geography data
|
||||
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ',';
|
||||
data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ','; // this is inverted in QGIS otherwise
|
||||
data += parseInt(getHeight(pack.cells.h[b.cell])) + ',';
|
||||
|
||||
// add status data
|
||||
data += b.capital ? 'capital,' : ',';
|
||||
data += b.port ? 'port,' : ',';
|
||||
data += b.citadel ? 'citadel,' : ',';
|
||||
data += b.walls ? 'walls,' : ',';
|
||||
data += b.plaza ? 'plaza,' : ',';
|
||||
data += b.temple ? 'temple,' : ',';
|
||||
data += b.shanty ? 'shanty town\n' : '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Burgs') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function renameBurgsInBulk() {
|
||||
const message = `Download burgs list as a text file, make changes and re-upload the file.
|
||||
If you do not want to change the name, just leave it as is`;
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Burgs bulk renaming',
|
||||
width: '22em',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Download: function () {
|
||||
const data = pack.burgs
|
||||
.filter((b) => b.i && !b.removed)
|
||||
.map((b) => b.name)
|
||||
.join('\r\n');
|
||||
const name = getFileName('Burg names') + '.txt';
|
||||
downloadFile(data, name);
|
||||
},
|
||||
Upload: () => burgsListToLoad.click(),
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importBurgNames(dataLoaded) {
|
||||
if (!dataLoaded) return tip('Cannot load the file, please check the format', false, 'error');
|
||||
const data = dataLoaded.split('\r\n');
|
||||
if (!data.length) return tip('Cannot parse the list, please check the file format', false, 'error');
|
||||
|
||||
let change = [],
|
||||
message = `Burgs will be renamed as below. Please confirm`;
|
||||
message += `<table class="overflow-table"><tr><th>Id</th><th>Current name</th><th>New Name</th></tr>`;
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
for (let i = 0; i < data.length && i <= burgs.length; i++) {
|
||||
const v = data[i];
|
||||
if (!v || !burgs[i] || v == burgs[i].name) continue;
|
||||
change.push({id: burgs[i].i, name: v});
|
||||
message += `<tr><td style="width:20%">${burgs[i].i}</td><td style="width:40%">${burgs[i].name}</td><td style="width:40%">${v}</td></tr>`;
|
||||
}
|
||||
message += `</tr></table>`;
|
||||
if (!change.length) message = 'No changes found in the file. Please change some names to get a result';
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Burgs bulk renaming',
|
||||
width: '22em',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Confirm: function () {
|
||||
for (let i = 0; i < change.length; i++) {
|
||||
const id = change[i].id;
|
||||
pack.burgs[id].name = change[i].name;
|
||||
burgLabels.select("[data-id='" + id + "']").text(change[i].name);
|
||||
}
|
||||
$(this).dialog('close');
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllBurgsRemove() {
|
||||
const message = 'Are you sure you want to remove all unlocked burgs except for capitals?<br><i>To remove a capital you have to remove the state first</i>';
|
||||
confirmationDialog({title: 'Remove all burgs', message, confirm: 'Remove', onConfirm: removeAllBurgs});
|
||||
}
|
||||
|
||||
function removeAllBurgs() {
|
||||
pack.burgs.filter((b) => b.i && !(b.capital || b.lock)).forEach((b) => removeBurg(b.i));
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
|
@ -63,8 +63,8 @@ function editCultures() {
|
|||
function culturesEditorAddLines() {
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
let lines = '',
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
let totalArea = 0;
|
||||
let totalPopulation = 0;
|
||||
|
||||
const emblemShapeGroup = document.getElementById('emblemShape').selectedOptions[0].parentNode.label;
|
||||
const selectShape = emblemShapeGroup === 'Diversiform';
|
||||
|
|
@ -84,7 +84,8 @@ function editCultures() {
|
|||
lines += `<div class="states" data-id=${c.i} data-name="${c.name}" data-color="" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Neutral culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span class="icon-cw placeholder"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span class="icon-resize-full placeholder hide"></span>
|
||||
|
|
@ -96,17 +97,22 @@ function editCultures() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
</div>`;
|
||||
continue;
|
||||
}
|
||||
|
||||
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>
|
||||
<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>
|
||||
<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>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span>
|
||||
|
|
@ -120,7 +126,11 @@ function editCultures() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
<span data-tip="Remove culture" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -141,6 +151,7 @@ function editCultures() {
|
|||
body.querySelectorAll('rect.fillRect').forEach((el) => el.addEventListener('click', cultureChangeColor));
|
||||
body.querySelectorAll('div > input.cultureName').forEach((el) => el.addEventListener('input', cultureChangeName));
|
||||
body.querySelectorAll('div > input.statePower').forEach((el) => el.addEventListener('input', cultureChangeExpansionism));
|
||||
body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName));
|
||||
body.querySelectorAll('div > select.cultureType').forEach((el) => el.addEventListener('change', cultureChangeType));
|
||||
body.querySelectorAll('div > select.cultureBase').forEach((el) => el.addEventListener('change', cultureChangeBase));
|
||||
body.querySelectorAll('div > select.cultureShape').forEach((el) => el.addEventListener('change', cultureChangeShape));
|
||||
|
|
@ -262,6 +273,13 @@ function editCultures() {
|
|||
);
|
||||
}
|
||||
|
||||
function cultureRegenerateName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const name = Names.getCultureShort(culture);
|
||||
this.parentNode.querySelector("input.cultureName").value = name;
|
||||
pack.cultures[culture].name = name;
|
||||
}
|
||||
|
||||
function cultureChangeExpansionism() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.expansionism = this.value;
|
||||
|
|
@ -534,6 +552,9 @@ function editCultures() {
|
|||
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');
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.style("text-anchor", "middle");
|
||||
|
||||
renderTree();
|
||||
function renderTree() {
|
||||
|
|
@ -683,6 +704,10 @@ function editCultures() {
|
|||
|
||||
tip('Click on culture to select, drag the circle to change culture', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectCultureOnMapClick).call(d3.drag().on('start', dragCultureBrush)).on('touchmove mousemove', moveCultureBrush);
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectCultureOnMapClick)
|
||||
.call(d3.drag().on("start", dragCultureBrush))
|
||||
.on("touchmove mousemove", moveCultureBrush);
|
||||
|
||||
body.querySelector('div').classList.add('selected');
|
||||
}
|
||||
|
|
@ -733,7 +758,14 @@ function editCultures() {
|
|||
|
||||
// change of append new element
|
||||
if (exists.size()) exists.attr('data-culture', cultureNew).attr('fill', color).attr('stroke', color);
|
||||
else temp.append('polygon').attr('data-cell', i).attr('data-culture', cultureNew).attr('points', getPackPolygon(i)).attr('fill', color).attr('stroke', color);
|
||||
else
|
||||
temp
|
||||
.append("polygon")
|
||||
.attr("data-cell", i)
|
||||
.attr("data-culture", cultureNew)
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("fill", color)
|
||||
.attr("stroke", color);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
965
modules/ui/cultures-editor.js.orig
Normal file
965
modules/ui/cultures-editor.js.orig
Normal file
|
|
@ -0,0 +1,965 @@
|
|||
'use strict';
|
||||
function editCultures() {
|
||||
if (customization) return;
|
||||
closeDialogs('#culturesEditor, .stable');
|
||||
if (!layerIsOn('toggleCultures')) toggleCultures();
|
||||
if (layerIsOn('toggleStates')) toggleStates();
|
||||
if (layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
if (layerIsOn('toggleReligions')) toggleReligions();
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
|
||||
const body = document.getElementById('culturesBody');
|
||||
drawCultureCenters();
|
||||
refreshCulturesEditor();
|
||||
|
||||
if (modules.editCultures) return;
|
||||
modules.editCultures = true;
|
||||
|
||||
$('#culturesEditor').dialog({
|
||||
title: 'Cultures Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: closeCulturesEditor,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}
|
||||
});
|
||||
body.focus();
|
||||
|
||||
// add listeners
|
||||
document.getElementById('culturesEditorRefresh').addEventListener('click', refreshCulturesEditor);
|
||||
document.getElementById('culturesEditStyle').addEventListener('click', () => editStyle('cults'));
|
||||
document.getElementById('culturesLegend').addEventListener('click', toggleLegend);
|
||||
document.getElementById('culturesPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('culturesHeirarchy').addEventListener('click', showHierarchy);
|
||||
document.getElementById('culturesRecalculate').addEventListener('click', () => recalculateCultures(true));
|
||||
document.getElementById('culturesManually').addEventListener('click', enterCultureManualAssignent);
|
||||
document.getElementById('culturesManuallyApply').addEventListener('click', applyCultureManualAssignent);
|
||||
document.getElementById('culturesManuallyCancel').addEventListener('click', () => exitCulturesManualAssignment());
|
||||
document.getElementById('culturesEditNamesBase').addEventListener('click', editNamesbase);
|
||||
document.getElementById('culturesAdd').addEventListener('click', enterAddCulturesMode);
|
||||
document.getElementById('culturesExport').addEventListener('click', downloadCulturesData);
|
||||
|
||||
function refreshCulturesEditor() {
|
||||
culturesCollectStatistics();
|
||||
culturesEditorAddLines();
|
||||
drawCultureCenters();
|
||||
}
|
||||
|
||||
function culturesCollectStatistics() {
|
||||
const cells = pack.cells,
|
||||
cultures = pack.cultures;
|
||||
cultures.forEach((c) => (c.cells = c.area = c.rural = c.urban = 0));
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (cells.h[i] < 20) continue;
|
||||
const c = cells.culture[i];
|
||||
cultures[c].cells += 1;
|
||||
cultures[c].area += cells.area[i];
|
||||
cultures[c].rural += cells.pop[i];
|
||||
if (cells.burg[i]) cultures[c].urban += pack.burgs[cells.burg[i]].population;
|
||||
}
|
||||
}
|
||||
|
||||
// add line for each culture
|
||||
function culturesEditorAddLines() {
|
||||
<<<<<<< HEAD
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
let lines = '',
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
=======
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
let lines = "";
|
||||
let totalArea = 0;
|
||||
let totalPopulation = 0;
|
||||
>>>>>>> master
|
||||
|
||||
const emblemShapeGroup = document.getElementById('emblemShape').selectedOptions[0].parentNode.label;
|
||||
const selectShape = emblemShapeGroup === 'Diversiform';
|
||||
|
||||
for (const c of pack.cultures) {
|
||||
if (c.removed) continue;
|
||||
const area = c.area * distanceScaleInput.value ** 2;
|
||||
const rural = c.rural * populationRate;
|
||||
const urban = c.urban * populationRate * urbanization;
|
||||
const population = rn(rural + urban);
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to edit`;
|
||||
totalArea += area;
|
||||
totalPopulation += population;
|
||||
|
||||
if (!c.i) {
|
||||
// Uncultured (neutral) line
|
||||
lines += `<div class="states" data-id=${c.i} data-name="${c.name}" data-color="" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Neutral culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span class="icon-cw placeholder"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span class="icon-resize-full placeholder hide"></span>
|
||||
<input class="statePower placeholder hide" type="number">
|
||||
<select class="cultureType placeholder">${getTypeOptions(c.type)}</select>
|
||||
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
<<<<<<< HEAD
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
=======
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
>>>>>>> master
|
||||
</div>`;
|
||||
continue;
|
||||
}
|
||||
|
||||
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}">
|
||||
<<<<<<< HEAD
|
||||
<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>
|
||||
=======
|
||||
<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>
|
||||
>>>>>>> master
|
||||
<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>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span>
|
||||
<input data-tip="Culture expansionism. Defines competitive size. Click to change, then click Recalculate to apply change" class="statePower hide" type="number" min=0 max=99 step=.1 value=${
|
||||
c.expansionism
|
||||
}>
|
||||
<select data-tip="Culture type. Defines growth model. Click to change" class="cultureType">${getTypeOptions(c.type)}</select>
|
||||
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
<<<<<<< HEAD
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
=======
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
>>>>>>> master
|
||||
<span data-tip="Remove culture" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.innerHTML = lines;
|
||||
|
||||
// update footer
|
||||
culturesFooterCultures.innerHTML = pack.cultures.filter((c) => c.i && !c.removed).length;
|
||||
culturesFooterCells.innerHTML = pack.cells.h.filter((h) => h >= 20).length;
|
||||
culturesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
culturesFooterPopulation.innerHTML = si(totalPopulation);
|
||||
culturesFooterArea.dataset.area = totalArea;
|
||||
culturesFooterPopulation.dataset.population = totalPopulation;
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
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('div > input.cultureName').forEach((el) => el.addEventListener('input', cultureChangeName));
|
||||
body.querySelectorAll('div > input.statePower').forEach((el) => el.addEventListener('input', cultureChangeExpansionism));
|
||||
body.querySelectorAll('div > select.cultureType').forEach((el) => el.addEventListener('change', cultureChangeType));
|
||||
body.querySelectorAll('div > select.cultureBase').forEach((el) => el.addEventListener('change', cultureChangeBase));
|
||||
body.querySelectorAll('div > select.cultureShape').forEach((el) => el.addEventListener('change', cultureChangeShape));
|
||||
body.querySelectorAll('div > div.culturePopulation').forEach((el) => el.addEventListener('click', changePopulation));
|
||||
body.querySelectorAll('div > span.icon-arrows-cw').forEach((el) => el.addEventListener('click', cultureRegenerateBurgs));
|
||||
body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', cultureRemove));
|
||||
|
||||
culturesHeader.querySelector("div[data-sortby='emblems']").style.display = selectShape ? 'inline-block' : 'none';
|
||||
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
=======
|
||||
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("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));
|
||||
body.querySelectorAll("div > select.cultureType").forEach(el => el.addEventListener("change", cultureChangeType));
|
||||
body.querySelectorAll("div > select.cultureBase").forEach(el => el.addEventListener("change", cultureChangeBase));
|
||||
body.querySelectorAll("div > select.cultureShape").forEach(el => el.addEventListener("change", cultureChangeShape));
|
||||
body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation));
|
||||
body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", cultureRegenerateBurgs));
|
||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", cultureRemove));
|
||||
|
||||
culturesHeader.querySelector("div[data-sortby='emblems']").style.display = selectShape ? "inline-block" : "none";
|
||||
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
>>>>>>> master
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(culturesHeader);
|
||||
$('#culturesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function getTypeOptions(type) {
|
||||
let options = '';
|
||||
const types = ['Generic', 'River', 'Lake', 'Naval', 'Nomadic', 'Hunting', 'Highland'];
|
||||
types.forEach((t) => (options += `<option ${type === t ? 'selected' : ''} value="${t}">${t}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
function getBaseOptions(base) {
|
||||
let options = '';
|
||||
nameBases.forEach((n, i) => (options += `<option ${base === i ? 'selected' : ''} value="${i}">${n.name}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
function getShapeOptions(selected) {
|
||||
const shapes = Object.keys(COA.shields.types)
|
||||
.map((type) => Object.keys(COA.shields[type]))
|
||||
.flat();
|
||||
return shapes.map((shape) => `<option ${shape === selected ? 'selected' : ''} value="${shape}">${capitalize(shape)}</option>`);
|
||||
}
|
||||
|
||||
function cultureHighlightOn(event) {
|
||||
const culture = +event.target.dataset.id;
|
||||
const info = document.getElementById('cultureInfo');
|
||||
if (info) {
|
||||
d3.select('#hierarchy')
|
||||
.select("g[data-id='" + culture + "'] > path")
|
||||
.classed('selected', 1);
|
||||
const c = pack.cultures[culture];
|
||||
const rural = c.rural * populationRate;
|
||||
const urban = c.urban * populationRate * urbanization;
|
||||
const population = rural + urban > 0 ? si(rn(rural + urban)) + ' people' : 'Extinct';
|
||||
info.innerHTML = `${c.name} culture. ${c.type}. ${population}`;
|
||||
tip('Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation');
|
||||
}
|
||||
|
||||
if (!layerIsOn('toggleCultures')) return;
|
||||
if (customization) return;
|
||||
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||
cults
|
||||
.select('#culture' + culture)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr('stroke-width', 2.5)
|
||||
.attr('stroke', '#d0240f');
|
||||
debug
|
||||
.select('#cultureCenter' + culture)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr('r', 8)
|
||||
.attr('stroke', '#d0240f');
|
||||
}
|
||||
|
||||
function cultureHighlightOff(event) {
|
||||
const culture = +event.target.dataset.id;
|
||||
const info = document.getElementById('cultureInfo');
|
||||
if (info) {
|
||||
d3.select('#hierarchy')
|
||||
.select("g[data-id='" + culture + "'] > path")
|
||||
.classed('selected', 0);
|
||||
info.innerHTML = '‍';
|
||||
tip('');
|
||||
}
|
||||
|
||||
if (!layerIsOn('toggleCultures')) return;
|
||||
cults
|
||||
.select('#culture' + culture)
|
||||
.transition()
|
||||
.attr('stroke-width', null)
|
||||
.attr('stroke', null);
|
||||
debug
|
||||
.select('#cultureCenter' + culture)
|
||||
.transition()
|
||||
.attr('r', 6)
|
||||
.attr('stroke', null);
|
||||
}
|
||||
|
||||
function cultureChangeColor() {
|
||||
const el = this;
|
||||
const currentFill = el.getAttribute('fill');
|
||||
const culture = +el.parentNode.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute('fill', fill);
|
||||
pack.cultures[culture].color = fill;
|
||||
cults
|
||||
.select('#culture' + culture)
|
||||
.attr('fill', fill)
|
||||
.attr('stroke', fill);
|
||||
debug.select('#cultureCenter' + culture).attr('fill', fill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
}
|
||||
|
||||
function cultureChangeName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.name = this.value;
|
||||
pack.cultures[culture].name = this.value;
|
||||
pack.cultures[culture].code = abbreviate(
|
||||
this.value,
|
||||
pack.cultures.map((c) => c.code)
|
||||
);
|
||||
}
|
||||
|
||||
function cultureRegenerateName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const name = Names.getCultureShort(culture);
|
||||
this.parentNode.querySelector("input.cultureName").value = name;
|
||||
pack.cultures[culture].name = name;
|
||||
}
|
||||
|
||||
function cultureChangeExpansionism() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.expansionism = this.value;
|
||||
pack.cultures[culture].expansionism = +this.value;
|
||||
recalculateCultures();
|
||||
}
|
||||
|
||||
function cultureChangeType() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.type = this.value;
|
||||
pack.cultures[culture].type = this.value;
|
||||
recalculateCultures();
|
||||
}
|
||||
|
||||
function cultureChangeBase() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const v = +this.value;
|
||||
this.parentNode.dataset.base = pack.cultures[culture].base = v;
|
||||
}
|
||||
|
||||
function cultureChangeShape() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const shape = this.value;
|
||||
this.parentNode.dataset.emblems = pack.cultures[culture].shield = shape;
|
||||
|
||||
const rerenderCOA = (id, coa) => {
|
||||
const coaEl = document.getElementById(id);
|
||||
if (!coaEl) return; // not rendered
|
||||
coaEl.remove();
|
||||
COArenderer.trigger(id, coa);
|
||||
};
|
||||
|
||||
pack.states.forEach((state) => {
|
||||
if (state.culture !== culture || !state.i || state.removed || !state.coa || state.coa === 'custom') return;
|
||||
if (shape === state.coa.shield) return;
|
||||
state.coa.shield = shape;
|
||||
rerenderCOA('stateCOA' + state.i, state.coa);
|
||||
});
|
||||
|
||||
pack.provinces.forEach((province) => {
|
||||
if (pack.cells.culture[province.center] !== culture || !province.i || province.removed || !province.coa || province.coa === 'custom') return;
|
||||
if (shape === province.coa.shield) return;
|
||||
province.coa.shield = shape;
|
||||
rerenderCOA('provinceCOA' + province.i, province.coa);
|
||||
});
|
||||
|
||||
pack.burgs.forEach((burg) => {
|
||||
if (burg.culture !== culture || !burg.i || burg.removed || !burg.coa || burg.coa === 'custom') return;
|
||||
if (shape === burg.coa.shield) return;
|
||||
burg.coa.shield = shape;
|
||||
rerenderCOA('burgCOA' + burg.i, burg.coa);
|
||||
});
|
||||
}
|
||||
|
||||
function changePopulation() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const c = pack.cultures[culture];
|
||||
if (!c.cells) {
|
||||
tip('Culture does not have any cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const rural = rn(c.rural * populationRate);
|
||||
const urban = rn(c.urban * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
const burgs = pack.burgs.filter((b) => !b.removed && b.culture === culture);
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? '' : 'disabled'}>
|
||||
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
||||
|
||||
const update = function () {
|
||||
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
|
||||
if (isNaN(totalNew)) return;
|
||||
totalPop.innerHTML = l(totalNew);
|
||||
totalPopPerc.innerHTML = rn((totalNew / total) * 100);
|
||||
};
|
||||
|
||||
ruralPop.oninput = () => update();
|
||||
urbanPop.oninput = () => update();
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Change culture population',
|
||||
width: '24em',
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
applyPopulationChange();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
|
||||
function applyPopulationChange() {
|
||||
const ruralChange = ruralPop.value / rural;
|
||||
if (isFinite(ruralChange) && ruralChange !== 1) {
|
||||
const cells = pack.cells.i.filter((i) => pack.cells.culture[i] === culture);
|
||||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate;
|
||||
const cells = pack.cells.i.filter((i) => pack.cells.culture[i] === culture);
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
}
|
||||
|
||||
const urbanChange = urbanPop.value / urban;
|
||||
if (isFinite(urbanChange) && urbanChange !== 1) {
|
||||
burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach((b) => (b.population = population));
|
||||
}
|
||||
|
||||
refreshCulturesEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function cultureRegenerateBurgs() {
|
||||
if (customization === 4) return;
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const cBurgs = pack.burgs.filter((b) => b.culture === culture && !b.lock);
|
||||
cBurgs.forEach((b) => {
|
||||
b.name = Names.getCulture(culture);
|
||||
labels.select("[data-id='" + b.i + "']").text(b.name);
|
||||
});
|
||||
tip(`Names for ${cBurgs.length} burgs are regenerated`, false, 'success');
|
||||
}
|
||||
|
||||
function cultureRemove() {
|
||||
if (customization === 4) return;
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the culture? <br>This action cannot be reverted';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove culture',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
cults.select('#culture' + culture).remove();
|
||||
debug.select('#cultureCenter' + culture).remove();
|
||||
|
||||
pack.burgs.filter((b) => b.culture == culture).forEach((b) => (b.culture = 0));
|
||||
pack.states.forEach((s, i) => {
|
||||
if (s.culture === culture) s.culture = 0;
|
||||
});
|
||||
pack.cells.culture.forEach((c, i) => {
|
||||
if (c === culture) pack.cells.culture[i] = 0;
|
||||
});
|
||||
pack.cultures[culture].removed = true;
|
||||
|
||||
const origin = pack.cultures[culture].origin;
|
||||
pack.cultures.forEach((c) => {
|
||||
if (c.origin === culture) c.origin = origin;
|
||||
});
|
||||
refreshCulturesEditor();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawCultureCenters() {
|
||||
const tooltip = 'Drag to move the culture center (ancestral home)';
|
||||
debug.select('#cultureCenters').remove();
|
||||
const cultureCenters = debug.append('g').attr('id', 'cultureCenters').attr('stroke-width', 2).attr('stroke', '#444444').style('cursor', 'move');
|
||||
|
||||
const data = pack.cultures.filter((c) => c.i && !c.removed);
|
||||
cultureCenters
|
||||
.selectAll('circle')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('id', (d) => 'cultureCenter' + d.i)
|
||||
.attr('data-id', (d) => d.i)
|
||||
.attr('r', 6)
|
||||
.attr('fill', (d) => d.color)
|
||||
.attr('cx', (d) => pack.cells.p[d.center][0])
|
||||
.attr('cy', (d) => pack.cells.p[d.center][1])
|
||||
.on('mouseenter', (d) => {
|
||||
tip(tooltip, true);
|
||||
body.querySelector(`div[data-id='${d.i}']`).classList.add('selected');
|
||||
cultureHighlightOn(event);
|
||||
})
|
||||
.on('mouseleave', (d) => {
|
||||
tip('', true);
|
||||
body.querySelector(`div[data-id='${d.i}']`).classList.remove('selected');
|
||||
cultureHighlightOff(event);
|
||||
})
|
||||
.call(d3.drag().on('start', cultureCenterDrag));
|
||||
}
|
||||
|
||||
function cultureCenterDrag() {
|
||||
const el = d3.select(this);
|
||||
const c = +this.id.slice(13);
|
||||
d3.event.on('drag', () => {
|
||||
el.attr('cx', d3.event.x).attr('cy', d3.event.y);
|
||||
const cell = findCell(d3.event.x, d3.event.y);
|
||||
if (pack.cells.h[cell] < 20) return; // ignore dragging on water
|
||||
pack.cultures[c].center = cell;
|
||||
recalculateCultures();
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll('*').size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const data = pack.cultures
|
||||
.filter((c) => c.i && !c.removed && c.cells)
|
||||
.sort((a, b) => b.area - a.area)
|
||||
.map((c) => [c.i, c.color, c.name]);
|
||||
drawLegend('Cultures', data);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const totalCells = +culturesFooterCells.innerHTML;
|
||||
const totalArea = +culturesFooterArea.dataset.area;
|
||||
const totalPopulation = +culturesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
el.querySelector('.stateCells').innerHTML = rn((+el.dataset.cells / totalCells) * 100) + '%';
|
||||
el.querySelector('.biomeArea').innerHTML = rn((+el.dataset.area / totalArea) * 100) + '%';
|
||||
el.querySelector('.culturePopulation').innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + '%';
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = 'absolute';
|
||||
culturesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
function showHierarchy() {
|
||||
// build hierarchy tree
|
||||
pack.cultures[0].origin = null;
|
||||
const cultures = pack.cultures.filter((c) => !c.removed);
|
||||
if (cultures.length < 3) {
|
||||
tip('Not enough cultures to show hierarchy', false, 'error');
|
||||
return;
|
||||
}
|
||||
const root = d3
|
||||
.stratify()
|
||||
.id((d) => d.i)
|
||||
.parentId((d) => d.origin)(cultures);
|
||||
const treeWidth = root.leaves().length;
|
||||
const treeHeight = root.height;
|
||||
const width = treeWidth * 40,
|
||||
height = treeHeight * 60;
|
||||
|
||||
const margin = {top: 10, right: 10, bottom: -5, left: 10};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height + 30 - margin.top - margin.bottom;
|
||||
const treeLayout = d3.tree().size([w, h]);
|
||||
|
||||
// prepare svg
|
||||
alertMessage.innerHTML = "<div id='cultureInfo' class='chartInfo'>‍</div>";
|
||||
<<<<<<< HEAD
|
||||
const svg = d3.select('#alertMessage').insert('svg', '#cultureInfo').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');
|
||||
=======
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#cultureInfo")
|
||||
.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");
|
||||
>>>>>>> master
|
||||
|
||||
renderTree();
|
||||
function renderTree() {
|
||||
treeLayout(root);
|
||||
links
|
||||
.selectAll('path')
|
||||
.data(root.links())
|
||||
.enter()
|
||||
<<<<<<< HEAD
|
||||
.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 +
|
||||
',' +
|
||||
=======
|
||||
.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 +
|
||||
"," +
|
||||
>>>>>>> master
|
||||
d.target.y
|
||||
);
|
||||
});
|
||||
|
||||
const node = nodes
|
||||
.selectAll('g')
|
||||
.data(root.descendants())
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('data-id', (d) => d.data.i)
|
||||
.attr('stroke', '#333333')
|
||||
.attr('transform', (d) => `translate(${d.x}, ${d.y})`)
|
||||
.on('mouseenter', () => cultureHighlightOn(event))
|
||||
.on('mouseleave', () => cultureHighlightOff(event))
|
||||
.call(d3.drag().on('start', (d) => dragToReorigin(d)));
|
||||
|
||||
node
|
||||
.append('path')
|
||||
.attr('d', (d) => {
|
||||
if (!d.data.i) return 'M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0';
|
||||
// small circle
|
||||
else if (d.data.type === 'Generic') return 'M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0';
|
||||
// circle
|
||||
else if (d.data.type === 'River') return 'M0,-14L14,0L0,14L-14,0Z';
|
||||
// diamond
|
||||
else if (d.data.type === 'Lake') return 'M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z';
|
||||
// hexagon
|
||||
else if (d.data.type === 'Naval') return 'M-11,-11h22v22h-22Z'; // square
|
||||
if (d.data.type === 'Highland') return 'M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z'; // concave square
|
||||
if (d.data.type === 'Nomadic') return 'M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z'; // octagon
|
||||
if (d.data.type === 'Hunting') return 'M0,-14l14,11l-6,14h-16l-6,-14Z'; // pentagon
|
||||
return 'M-11,-11h22v22h-22Z'; // square
|
||||
})
|
||||
.attr('fill', (d) => (d.data.i ? d.data.color : '#ffffff'))
|
||||
.attr('stroke-dasharray', (d) => (d.data.cells ? 'null' : '1'));
|
||||
|
||||
node
|
||||
.append('text')
|
||||
.attr('dy', '.35em')
|
||||
.text((d) => (d.data.i ? d.data.code : ''));
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Cultures tree',
|
||||
width: fitContent(),
|
||||
resizable: false,
|
||||
position: {my: 'left center', at: 'left+10 center', of: 'svg'},
|
||||
buttons: {},
|
||||
close: () => {
|
||||
alertMessage.innerHTML = '';
|
||||
}
|
||||
});
|
||||
|
||||
function dragToReorigin(d) {
|
||||
if (isCtrlClick(d3.event.sourceEvent)) {
|
||||
changeCode(d);
|
||||
return;
|
||||
}
|
||||
|
||||
const originLine = graph.append('path').attr('class', 'dragLine').attr('d', `M${d.x},${d.y}L${d.x},${d.y}`);
|
||||
|
||||
d3.event.on('drag', () => {
|
||||
originLine.attr('d', `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`);
|
||||
});
|
||||
|
||||
d3.event.on('end', () => {
|
||||
originLine.remove();
|
||||
const selected = graph.select('path.selected');
|
||||
if (!selected.size()) return;
|
||||
const culture = d.data.i;
|
||||
const oldOrigin = d.data.origin;
|
||||
let newOrigin = selected.datum().data.i;
|
||||
if (newOrigin == oldOrigin) return; // already a child of the selected node
|
||||
if (newOrigin == culture) newOrigin = 0; // move to top
|
||||
if (newOrigin && d.descendants().some((node) => node.id == newOrigin)) return; // cannot be a child of its own child
|
||||
pack.cultures[culture].origin = d.data.origin = newOrigin; // change data
|
||||
showHierarchy(); // update hierarchy
|
||||
});
|
||||
}
|
||||
|
||||
function changeCode(d) {
|
||||
prompt(`Please provide an abbreviation for culture: ${d.data.name}`, {default: d.data.code}, (v) => {
|
||||
pack.cultures[d.data.i].code = v;
|
||||
nodes
|
||||
.select("g[data-id='" + d.data.i + "']")
|
||||
.select('text')
|
||||
.text(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function recalculateCultures(must) {
|
||||
if (!must && !culturesAutoChange.checked) return;
|
||||
|
||||
pack.cells.culture = new Uint16Array(pack.cells.i.length);
|
||||
pack.cultures.forEach(function (c) {
|
||||
if (!c.i || c.removed) return;
|
||||
pack.cells.culture[c.center] = c.i;
|
||||
});
|
||||
Cultures.expand();
|
||||
drawCultures();
|
||||
pack.burgs.forEach((b) => (b.culture = pack.cells.culture[b.cell]));
|
||||
refreshCulturesEditor();
|
||||
document.querySelector('input.statePower').focus(); // to not trigger hotkeys
|
||||
}
|
||||
|
||||
function enterCultureManualAssignent() {
|
||||
if (!layerIsOn('toggleCultures')) toggleCultures();
|
||||
customization = 4;
|
||||
cults.append('g').attr('id', 'temp');
|
||||
document.querySelectorAll('#culturesBottom > *').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('culturesManuallyButtons').style.display = 'inline-block';
|
||||
debug.select('#cultureCenters').style('display', 'none');
|
||||
|
||||
culturesEditor.querySelectorAll('.hide').forEach((el) => el.classList.add('hidden'));
|
||||
culturesHeader.querySelector("div[data-sortby='type']").style.left = '8.8em';
|
||||
culturesHeader.querySelector("div[data-sortby='base']").style.left = '13.6em';
|
||||
culturesFooter.style.display = 'none';
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'none'));
|
||||
$('#culturesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
|
||||
|
||||
<<<<<<< HEAD
|
||||
tip('Click on culture to select, drag the circle to change culture', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectCultureOnMapClick).call(d3.drag().on('start', dragCultureBrush)).on('touchmove mousemove', moveCultureBrush);
|
||||
=======
|
||||
tip("Click on culture to select, drag the circle to change culture", true);
|
||||
viewbox
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectCultureOnMapClick)
|
||||
.call(d3.drag().on("start", dragCultureBrush))
|
||||
.on("touchmove mousemove", moveCultureBrush);
|
||||
>>>>>>> master
|
||||
|
||||
body.querySelector('div').classList.add('selected');
|
||||
}
|
||||
|
||||
function selectCultureOnLineClick(i) {
|
||||
if (customization !== 4) return;
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
this.classList.add('selected');
|
||||
}
|
||||
|
||||
function selectCultureOnMapClick() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[i] < 20) return;
|
||||
|
||||
const assigned = cults.select('#temp').select("polygon[data-cell='" + i + "']");
|
||||
const culture = assigned.size() ? +assigned.attr('data-culture') : pack.cells.culture[i];
|
||||
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
body.querySelector("div[data-id='" + culture + "']").classList.add('selected');
|
||||
}
|
||||
|
||||
function dragCultureBrush() {
|
||||
const r = +culturesManuallyBrush.value;
|
||||
|
||||
d3.event.on('drag', () => {
|
||||
if (!d3.event.dx && !d3.event.dy) return;
|
||||
const p = d3.mouse(this);
|
||||
moveCircle(p[0], p[1], r);
|
||||
|
||||
const found = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)];
|
||||
const selection = found.filter(isLand);
|
||||
if (selection) changeCultureForSelection(selection);
|
||||
});
|
||||
}
|
||||
|
||||
function changeCultureForSelection(selection) {
|
||||
const temp = cults.select('#temp');
|
||||
const selected = body.querySelector('div.selected');
|
||||
|
||||
const cultureNew = +selected.dataset.id;
|
||||
const color = pack.cultures[cultureNew].color || '#ffffff';
|
||||
|
||||
selection.forEach(function (i) {
|
||||
const exists = temp.select("polygon[data-cell='" + i + "']");
|
||||
const cultureOld = exists.size() ? +exists.attr('data-culture') : pack.cells.culture[i];
|
||||
if (cultureNew === cultureOld) return;
|
||||
|
||||
// change of append new element
|
||||
<<<<<<< HEAD
|
||||
if (exists.size()) exists.attr('data-culture', cultureNew).attr('fill', color).attr('stroke', color);
|
||||
else temp.append('polygon').attr('data-cell', i).attr('data-culture', cultureNew).attr('points', getPackPolygon(i)).attr('fill', color).attr('stroke', color);
|
||||
=======
|
||||
if (exists.size()) exists.attr("data-culture", cultureNew).attr("fill", color).attr("stroke", color);
|
||||
else
|
||||
temp
|
||||
.append("polygon")
|
||||
.attr("data-cell", i)
|
||||
.attr("data-culture", cultureNew)
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("fill", color)
|
||||
.attr("stroke", color);
|
||||
>>>>>>> master
|
||||
});
|
||||
}
|
||||
|
||||
function moveCultureBrush() {
|
||||
showMainTip();
|
||||
const point = d3.mouse(this);
|
||||
const radius = +culturesManuallyBrush.value;
|
||||
moveCircle(point[0], point[1], radius);
|
||||
}
|
||||
|
||||
function applyCultureManualAssignent() {
|
||||
const changed = cults.select('#temp').selectAll('polygon');
|
||||
changed.each(function () {
|
||||
const i = +this.dataset.cell;
|
||||
const c = +this.dataset.culture;
|
||||
pack.cells.culture[i] = c;
|
||||
if (pack.cells.burg[i]) pack.burgs[pack.cells.burg[i]].culture = c;
|
||||
});
|
||||
|
||||
if (changed.size()) {
|
||||
drawCultures();
|
||||
refreshCulturesEditor();
|
||||
}
|
||||
exitCulturesManualAssignment();
|
||||
}
|
||||
|
||||
function exitCulturesManualAssignment(close) {
|
||||
customization = 0;
|
||||
cults.select('#temp').remove();
|
||||
removeCircle();
|
||||
document.querySelectorAll('#culturesBottom > *').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('culturesManuallyButtons').style.display = 'none';
|
||||
|
||||
culturesEditor.querySelectorAll('.hide').forEach((el) => el.classList.remove('hidden'));
|
||||
culturesHeader.querySelector("div[data-sortby='type']").style.left = '18.6em';
|
||||
culturesHeader.querySelector("div[data-sortby='base']").style.left = '35.8em';
|
||||
culturesFooter.style.display = 'block';
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'all'));
|
||||
if (!close) $('#culturesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
|
||||
|
||||
debug.select('#cultureCenters').style('display', null);
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
const selected = body.querySelector('div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
}
|
||||
|
||||
function enterAddCulturesMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
exitAddCultureMode();
|
||||
return;
|
||||
}
|
||||
customization = 9;
|
||||
this.classList.add('pressed');
|
||||
tip('Click on the map to add a new culture', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', addCulture);
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'none'));
|
||||
}
|
||||
|
||||
function exitAddCultureMode() {
|
||||
customization = 0;
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'all'));
|
||||
if (culturesAdd.classList.contains('pressed')) culturesAdd.classList.remove('pressed');
|
||||
}
|
||||
|
||||
function addCulture() {
|
||||
const point = d3.mouse(this);
|
||||
const center = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[center] < 20) {
|
||||
tip('You cannot place culture center into the water. Please click on a land cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
const occupied = pack.cultures.some((c) => !c.removed && c.center === center);
|
||||
if (occupied) {
|
||||
tip('This cell is already a culture center. Please select a different cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (d3.event.shiftKey === false) exitAddCultureMode();
|
||||
Cultures.add(center);
|
||||
|
||||
drawCultureCenters();
|
||||
culturesEditorAddLines();
|
||||
}
|
||||
|
||||
function downloadCulturesData() {
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,Culture,Color,Cells,Expansionism,Type,Area ' + unit + ',Population,Namesbase,Emblems Shape\n'; // headers
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.cells + ',';
|
||||
data += el.dataset.expansionism + ',';
|
||||
data += el.dataset.type + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
const base = +el.dataset.base;
|
||||
data += nameBases[base].name + ',';
|
||||
data += el.dataset.emblems + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Cultures') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function closeCulturesEditor() {
|
||||
debug.select('#cultureCenters').remove();
|
||||
exitCulturesManualAssignment('close');
|
||||
exitAddCultureMode();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +1,57 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function editDiplomacy() {
|
||||
if (customization) return;
|
||||
if (pack.states.filter(s => s.i && !s.removed).length < 2) {
|
||||
tip("There should be at least 2 states to edit the diplomacy", false, "error");
|
||||
if (pack.states.filter((s) => s.i && !s.removed).length < 2) {
|
||||
tip('There should be at least 2 states to edit the diplomacy', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
closeDialogs("#diplomacyEditor, .stable");
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
if (layerIsOn("toggleCultures")) toggleCultures();
|
||||
if (layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (layerIsOn("toggleReligions")) toggleReligions();
|
||||
closeDialogs('#diplomacyEditor, .stable');
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
if (layerIsOn('toggleCultures')) toggleCultures();
|
||||
if (layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
if (layerIsOn('toggleReligions')) toggleReligions();
|
||||
|
||||
const body = document.getElementById("diplomacyBodySection");
|
||||
const statuses = ["Ally", "Friendly", "Neutral", "Suspicion", "Enemy", "Unknown", "Rival", "Vassal", "Suzerain"];
|
||||
const description = [" is an ally of ", " is friendly to ", " is neutral to ", " is suspicious of ",
|
||||
" is at war with ", " does not know about ", " is a rival of ", " is a vassal of ", " is suzerain to "];
|
||||
const colors = ["#00b300", "#d4f8aa", "#edeee8", "#eeafaa", "#e64b40", "#a9a9a9", "#ad5a1f", "#87CEFA", "#00008B"];
|
||||
const body = document.getElementById('diplomacyBodySection');
|
||||
const statuses = ['Ally', 'Friendly', 'Neutral', 'Suspicion', 'Enemy', 'Unknown', 'Rival', 'Vassal', 'Suzerain'];
|
||||
const description = [
|
||||
' is an ally of ',
|
||||
' is friendly to ',
|
||||
' is neutral to ',
|
||||
' is suspicious of ',
|
||||
' is at war with ',
|
||||
' does not know about ',
|
||||
' is a rival of ',
|
||||
' is a vassal of ',
|
||||
' is suzerain to '
|
||||
];
|
||||
const colors = ['#00b300', '#d4f8aa', '#edeee8', '#eeafaa', '#e64b40', '#a9a9a9', '#ad5a1f', '#87CEFA', '#00008B'];
|
||||
refreshDiplomacyEditor();
|
||||
|
||||
tip("Click on a state to see its diplomatic relations", false, "warning");
|
||||
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick);
|
||||
tip('Click on a state to see its diplomatic relations', false, 'warning');
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectStateOnMapClick);
|
||||
|
||||
if (modules.editDiplomacy) return;
|
||||
modules.editDiplomacy = true;
|
||||
|
||||
$("#diplomacyEditor").dialog({
|
||||
title: "Diplomacy Editor", resizable: false, width: fitContent(), close: closeDiplomacyEditor,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
$('#diplomacyEditor').dialog({
|
||||
title: 'Diplomacy Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: closeDiplomacyEditor,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
|
||||
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
|
||||
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
|
||||
document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix);
|
||||
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);
|
||||
document.getElementById("diplomacyExport").addEventListener("click", downloadDiplomacyData);
|
||||
document.getElementById("diplomacySelect").addEventListener("mouseup", diplomacyChangeRelations);
|
||||
document.getElementById('diplomacyEditorRefresh').addEventListener('click', refreshDiplomacyEditor);
|
||||
document.getElementById('diplomacyEditStyle').addEventListener('click', () => editStyle('regions'));
|
||||
document.getElementById('diplomacyRegenerate').addEventListener('click', regenerateRelations);
|
||||
document.getElementById('diplomacyMatrix').addEventListener('click', showRelationsMatrix);
|
||||
document.getElementById('diplomacyHistory').addEventListener('click', showRelationsHistory);
|
||||
document.getElementById('diplomacyExport').addEventListener('click', downloadDiplomacyData);
|
||||
document.getElementById('diplomacySelect').addEventListener('mouseup', diplomacyChangeRelations);
|
||||
|
||||
function refreshDiplomacyEditor() {
|
||||
diplomacyEditorAddLines();
|
||||
|
|
@ -49,12 +61,12 @@ function editDiplomacy() {
|
|||
// add line for each state
|
||||
function diplomacyEditorAddLines() {
|
||||
const states = pack.states;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : states.find((s) => s.i && !s.removed).i;
|
||||
const selName = states[sel].fullName;
|
||||
diplomacySelect.style.display = "none";
|
||||
diplomacySelect.style.display = 'none';
|
||||
|
||||
COArenderer.trigger("stateCOA"+sel, states[sel].coa);
|
||||
COArenderer.trigger('stateCOA' + sel, states[sel].coa);
|
||||
let lines = `<div class="states Self" data-id=${sel} data-tip="List below shows relations to ${selName}">
|
||||
<div style="width: max-content">${selName}</div>
|
||||
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${sel}"></use></svg>
|
||||
|
|
@ -68,7 +80,7 @@ function editDiplomacy() {
|
|||
const tip = s.fullName + description[index] + selName;
|
||||
const tipSelect = `${tip}. Click to see relations to ${s.name}`;
|
||||
const tipChange = `${tip}. Click to change relations to ${selName}`;
|
||||
COArenderer.trigger("stateCOA"+s.i, s.coa);
|
||||
COArenderer.trigger('stateCOA' + s.i, s.coa);
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.fullName}" data-relations="${relation}">
|
||||
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
|
||||
|
|
@ -82,57 +94,61 @@ function editDiplomacy() {
|
|||
body.innerHTML = lines;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectStateOnLineClick));
|
||||
body.querySelectorAll(".changeRelations").forEach(el => el.addEventListener("click", toggleDiplomacySelect));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => stateHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => stateHighlightOff(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('click', selectStateOnLineClick));
|
||||
body.querySelectorAll('.changeRelations').forEach((el) => el.addEventListener('click', toggleDiplomacySelect));
|
||||
|
||||
applySorting(diplomacyHeader);
|
||||
$("#diplomacyEditor").dialog();
|
||||
$('#diplomacyEditor').dialog();
|
||||
}
|
||||
|
||||
function stateHighlightOn(event) {
|
||||
if (!layerIsOn("toggleStates")) return;
|
||||
if (!layerIsOn('toggleStates')) return;
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
const d = regions.select("#state"+state).attr("d");
|
||||
const d = regions.select('#state' + state).attr('d');
|
||||
|
||||
const path = debug.append("path").attr("class", "highlight").attr("d", d)
|
||||
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
const path = debug.append('path').attr('class', 'highlight').attr('d', d).attr('fill', 'none').attr('stroke', 'red').attr('stroke-width', 1).attr('opacity', 1).attr('filter', 'url(#blur1)');
|
||||
|
||||
const l = path.node().getTotalLength(), dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString("0," + l, l + "," + l);
|
||||
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
|
||||
const l = path.node().getTotalLength(),
|
||||
dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString('0,' + l, l + ',' + l);
|
||||
path
|
||||
.transition()
|
||||
.duration(dur)
|
||||
.attrTween('stroke-dasharray', function () {
|
||||
return (t) => i(t);
|
||||
});
|
||||
}
|
||||
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll(".highlight").each(function() {
|
||||
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
|
||||
debug.selectAll('.highlight').each(function () {
|
||||
d3.select(this).transition().duration(1000).attr('opacity', 0).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function showStateRelations() {
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : pack.states.find(s => s.i && !s.removed).i;
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : pack.states.find((s) => s.i && !s.removed).i;
|
||||
if (!sel) return;
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
|
||||
statesBody.selectAll("path").each(function() {
|
||||
if (this.id.slice(0, 9) === "state-gap") return; // exclude state gap element
|
||||
statesBody.selectAll('path').each(function () {
|
||||
if (this.id.slice(0, 9) === 'state-gap') return; // exclude state gap element
|
||||
const id = +this.id.slice(5); // state id
|
||||
const index = statuses.indexOf(pack.states[id].diplomacy[sel]); // status index
|
||||
const clr = index !== -1 ? colors[index] : "#4682b4"; // Self (bluish)
|
||||
this.setAttribute("fill", clr);
|
||||
statesBody.select("#state-gap"+id).attr("stroke", clr);
|
||||
statesHalo.select("#state-border"+id).attr("stroke", d3.color(clr).darker().hex());
|
||||
const clr = index !== -1 ? colors[index] : '#4682b4'; // Self (bluish)
|
||||
this.setAttribute('fill', clr);
|
||||
statesBody.select('#state-gap' + id).attr('stroke', clr);
|
||||
statesHalo.select('#state-border' + id).attr('stroke', d3.color(clr).darker().hex());
|
||||
});
|
||||
}
|
||||
|
||||
function selectStateOnLineClick() {
|
||||
if (this.classList.contains("Self")) return;
|
||||
body.querySelector("div.Self").classList.remove("Self");
|
||||
this.classList.add("Self");
|
||||
if (this.classList.contains('Self')) return;
|
||||
body.querySelector('div.Self').classList.remove('Self');
|
||||
this.classList.add('Self');
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
|
|
@ -141,35 +157,39 @@ function editDiplomacy() {
|
|||
const i = findCell(point[0], point[1]);
|
||||
const state = pack.cells.state[i];
|
||||
if (!state) return;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
if (+selectedLine.dataset.id === state) return;
|
||||
|
||||
selectedLine.classList.remove("Self");
|
||||
body.querySelector("div[data-id='"+state+"']").classList.add("Self");
|
||||
selectedLine.classList.remove('Self');
|
||||
body.querySelector("div[data-id='" + state + "']").classList.add('Self');
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
function toggleDiplomacySelect(event) {
|
||||
event.stopPropagation();
|
||||
const select = document.getElementById("diplomacySelect");
|
||||
const show = select.style.display === "none";
|
||||
if (!show) {select.style.display = "none"; return;}
|
||||
select.style.display = "block";
|
||||
const input = event.target.closest("div").querySelector("input");
|
||||
select.style.left = input.getBoundingClientRect().left + "px";
|
||||
select.style.top = input.getBoundingClientRect().bottom + "px";
|
||||
body.dataset.state = event.target.closest("div.states").dataset.id;
|
||||
const select = document.getElementById('diplomacySelect');
|
||||
const show = select.style.display === 'none';
|
||||
if (!show) {
|
||||
select.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
select.style.display = 'block';
|
||||
const input = event.target.closest('div').querySelector('input');
|
||||
select.style.left = input.getBoundingClientRect().left + 'px';
|
||||
select.style.top = input.getBoundingClientRect().bottom + 'px';
|
||||
body.dataset.state = event.target.closest('div.states').dataset.id;
|
||||
}
|
||||
|
||||
function diplomacyChangeRelations(event) {
|
||||
event.stopPropagation();
|
||||
diplomacySelect.style.display = "none";
|
||||
diplomacySelect.style.display = 'none';
|
||||
const subject = +body.dataset.state;
|
||||
const rel = event.target.innerHTML;
|
||||
|
||||
const states = pack.states, chronicle = states[0].diplomacy;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const object = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
|
||||
const states = pack.states,
|
||||
chronicle = states[0].diplomacy;
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
const object = selectedLine ? +selectedLine.dataset.id : states.find((s) => s.i && !s.removed).i;
|
||||
if (!object) return;
|
||||
const objectName = states[object].name; // object of relations change
|
||||
const subjectName = states[subject].name; // subject of relations change - actor
|
||||
|
|
@ -177,7 +197,7 @@ function editDiplomacy() {
|
|||
const oldRel = states[subject].diplomacy[object];
|
||||
if (rel === oldRel) return;
|
||||
states[subject].diplomacy[object] = rel;
|
||||
states[object].diplomacy[subject] = rel === "Vassal" ? "Suzerain" : rel === "Suzerain" ? "Vassal" : rel;
|
||||
states[object].diplomacy[subject] = rel === 'Vassal' ? 'Suzerain' : rel === 'Suzerain' ? 'Vassal' : rel;
|
||||
|
||||
// update relation history
|
||||
const change = () => [`Relations change`, `${subjectName}-${getAdjective(objectName)} relations changed to ${rel.toLowerCase()}`];
|
||||
|
|
@ -189,22 +209,18 @@ function editDiplomacy() {
|
|||
const war = () => [`War declaration`, `${subjectName} declared a war on its enemy ${objectName}`];
|
||||
const peace = () => {
|
||||
const treaty = `${subjectName} and ${objectName} agreed to cease fire and signed a peace treaty`;
|
||||
const changed = rel === "Ally" ? ally()
|
||||
: rel === "Vassal" ? vassal()
|
||||
: rel === "Suzerain" ? suzerain()
|
||||
: rel === "Unknown" ? unknown()
|
||||
: change();
|
||||
const changed = rel === 'Ally' ? ally() : rel === 'Vassal' ? vassal() : rel === 'Suzerain' ? suzerain() : rel === 'Unknown' ? unknown() : change();
|
||||
return [`War termination`, treaty, changed[1]];
|
||||
}
|
||||
};
|
||||
|
||||
if (oldRel === "Enemy") chronicle.push(peace()); else
|
||||
if (rel === "Enemy") chronicle.push(war()); else
|
||||
if (rel === "Vassal") chronicle.push(vassal()); else
|
||||
if (rel === "Suzerain") chronicle.push(suzerain()); else
|
||||
if (rel === "Ally") chronicle.push(ally()); else
|
||||
if (rel === "Unknown") chronicle.push(unknown()); else
|
||||
if (rel === "Rival") chronicle.push(rival()); else
|
||||
chronicle.push(change());
|
||||
if (oldRel === 'Enemy') chronicle.push(peace());
|
||||
else if (rel === 'Enemy') chronicle.push(war());
|
||||
else if (rel === 'Vassal') chronicle.push(vassal());
|
||||
else if (rel === 'Suzerain') chronicle.push(suzerain());
|
||||
else if (rel === 'Ally') chronicle.push(ally());
|
||||
else if (rel === 'Unknown') chronicle.push(unknown());
|
||||
else if (rel === 'Rival') chronicle.push(rival());
|
||||
else chronicle.push(change());
|
||||
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
|
@ -216,79 +232,95 @@ function editDiplomacy() {
|
|||
|
||||
function showRelationsHistory() {
|
||||
const chronicle = pack.states[0].diplomacy;
|
||||
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;}
|
||||
if (!chronicle.length) {
|
||||
tip('Relations history is blank', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
let message = `<div autocorrect="off" spellcheck="false">`;
|
||||
chronicle.forEach((e, d) => {
|
||||
message += `<div>`;
|
||||
e.forEach((l, i) => message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
|
||||
e.forEach((l, i) => (message += `<div contenteditable="true" data-id="${d}-${i}"${i ? '' : " style='font-weight:bold'"}>${l}</div>`));
|
||||
message += `‍</div>`;
|
||||
});
|
||||
alertMessage.innerHTML = message + `</div><i id="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</i>`;
|
||||
alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory));
|
||||
alertMessage.querySelectorAll("div[contenteditable='true']").forEach((el) => el.addEventListener('input', changeReliationsHistory));
|
||||
|
||||
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"},
|
||||
$('#alert').dialog({
|
||||
title: 'Relations history',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Save: function() {
|
||||
const data = this.querySelector("div").innerText.split("\n").join("\r\n");
|
||||
const name = getFileName("Relations history") + ".txt";
|
||||
Save: function () {
|
||||
const data = this.querySelector('div').innerText.split('\n').join('\r\n');
|
||||
const name = getFileName('Relations history') + '.txt';
|
||||
downloadFile(data, name);
|
||||
},
|
||||
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
|
||||
Close: function() {$(this).dialog("close");}
|
||||
Clear: function () {
|
||||
pack.states[0].diplomacy = [];
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeReliationsHistory() {
|
||||
const i = this.dataset.id.split("-");
|
||||
const i = this.dataset.id.split('-');
|
||||
const group = pack.states[0].diplomacy[i[0]];
|
||||
if (this.innerHTML === "") {
|
||||
if (this.innerHTML === '') {
|
||||
group.splice(i[1], 1);
|
||||
this.remove();
|
||||
} else group[i[1]] = this.innerHTML;
|
||||
}
|
||||
|
||||
function showRelationsMatrix() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const valid = states.map(s => s.i);
|
||||
const states = pack.states.filter((s) => s.i && !s.removed);
|
||||
const valid = states.map((s) => s.i);
|
||||
|
||||
let message = `<table class="matrix-table"><tr><th data-tip='‍'></th>`;
|
||||
message += states.map(s => `<th data-tip='See relations to ${s.fullName}'>${s.name}</th>`).join("") + `</tr>`; // headers
|
||||
states.forEach(s => {
|
||||
message += `<tr><th data-tip='See relations of ${s.fullName}'>${s.name}</th>` + s.diplomacy
|
||||
.filter((v, i) => valid.includes(i)).map((r, i) => {
|
||||
const desc = description[statuses.indexOf(r)];
|
||||
const tip = desc ? s.fullName + desc + pack.states[valid[i]].fullName : '‍';
|
||||
return `<td data-tip='${tip}' class='${r}'>${r}</td>`
|
||||
}).join("") + "</tr>";
|
||||
message += states.map((s) => `<th data-tip='See relations to ${s.fullName}'>${s.name}</th>`).join('') + `</tr>`; // headers
|
||||
states.forEach((s) => {
|
||||
message +=
|
||||
`<tr><th data-tip='See relations of ${s.fullName}'>${s.name}</th>` +
|
||||
s.diplomacy
|
||||
.filter((v, i) => valid.includes(i))
|
||||
.map((r, i) => {
|
||||
const desc = description[statuses.indexOf(r)];
|
||||
const tip = desc ? s.fullName + desc + pack.states[valid[i]].fullName : '‍';
|
||||
return `<td data-tip='${tip}' class='${r}'>${r}</td>`;
|
||||
})
|
||||
.join('') +
|
||||
'</tr>';
|
||||
});
|
||||
message += `</table>`;
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$("#alert").dialog({title: "Relations matrix", width: fitContent(), position: {my: "center", at: "center", of: "svg"}, buttons: {}});
|
||||
|
||||
$('#alert').dialog({title: 'Relations matrix', width: fitContent(), position: {my: 'center', at: 'center', of: 'svg'}, buttons: {}});
|
||||
}
|
||||
|
||||
function downloadDiplomacyData() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const valid = states.map(s => s.i);
|
||||
const states = pack.states.filter((s) => s.i && !s.removed);
|
||||
const valid = states.map((s) => s.i);
|
||||
|
||||
let data = "," + states.map(s => s.name).join(",") + "\n"; // headers
|
||||
states.forEach(s => {
|
||||
let data = ',' + states.map((s) => s.name).join(',') + '\n'; // headers
|
||||
states.forEach((s) => {
|
||||
const rels = s.diplomacy.filter((v, i) => valid.includes(i));
|
||||
data += s.name + "," + rels.join(",") + "\n";
|
||||
data += s.name + ',' + rels.join(',') + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Relations") + ".csv";
|
||||
const name = getFileName('Relations') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function closeDiplomacyEditor() {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
const selected = body.querySelector("div.Self");
|
||||
if (selected) selected.classList.remove("Self");
|
||||
if (layerIsOn("toggleStates")) drawStates(); else toggleStates();
|
||||
debug.selectAll(".highlight").remove();
|
||||
const selected = body.querySelector('div.Self');
|
||||
if (selected) selected.classList.remove('Self');
|
||||
if (layerIsOn('toggleStates')) drawStates();
|
||||
else toggleStates();
|
||||
debug.selectAll('.highlight').remove();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// module stub to store common functions for ui editors
|
||||
'use strict';
|
||||
|
||||
modules.editors = true;
|
||||
restoreDefaultEvents(); // apply default viewbox events on load
|
||||
|
||||
// restore default viewbox events
|
||||
|
|
@ -264,15 +265,8 @@ function toggleBurgLock(burg) {
|
|||
b.lock = b.lock ? 0 : 1;
|
||||
}
|
||||
|
||||
function showBurgLockTip(burg) {
|
||||
const b = pack.burgs[burg];
|
||||
if (b.lock) {
|
||||
tip('Click to unlock burg and allow it to be change by regeneration tools');
|
||||
} else {
|
||||
tip('Click to lock burg and prevent changes by regeneration tools');
|
||||
}
|
||||
}
|
||||
|
||||
// draw legend box
|
||||
function drawLegend(name, data) {
|
||||
legend.selectAll('*').remove(); // fully redraw every time
|
||||
|
|
@ -385,6 +379,14 @@ function createPicker() {
|
|||
|
||||
const contaiter = d3.select('body').append('svg').attr('id', 'pickerContainer').attr('width', '100%').attr('height', '100%');
|
||||
contaiter.append('rect').attr('x', 0).attr('y', 0).attr('width', '100%').attr('height', '100%').attr('opacity', 0.2).on('mousemove', cl).on('click', closePicker);
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("opacity", 0.2)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
const picker = contaiter
|
||||
.append('g')
|
||||
.attr('id', 'picker')
|
||||
|
|
@ -489,6 +491,17 @@ function createPicker() {
|
|||
picker.insert('text', ':first-child').attr('x', 12).attr('y', -10).attr('id', 'pickerLabel').text('Color Picker').on('mousemove', pos);
|
||||
picker.insert('rect', ':first-child').attr('x', 0).attr('y', -30).attr('width', width).attr('height', 30).attr('id', 'pickerHeader').on('mousemove', pos);
|
||||
picker.attr('transform', `translate(${(svgWidth - width) / 2},${(svgHeight - height) / 2})`);
|
||||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#5d4651")
|
||||
.on("mousemove", pos);
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", 288)
|
||||
.attr("y", -21)
|
||||
.attr("id", "pickerCloseRect")
|
||||
.attr("width", 14)
|
||||
.attr("height", 14)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
}
|
||||
|
||||
function updateSelectedRect(fill) {
|
||||
|
|
@ -693,23 +706,32 @@ function uploadFile(el, callback) {
|
|||
fileReader.onload = (loaded) => callback(loaded.target.result);
|
||||
}
|
||||
|
||||
function highlightElement(element) {
|
||||
function getBBox(element) {
|
||||
if (debug.select('.highlighted').size()) return; // allow only 1 highlight element simultaniosly
|
||||
const box = element.getBBox();
|
||||
const y = +element.getAttribute("y");
|
||||
const transform = element.getAttribute('transform') || null;
|
||||
const height = +element.getAttribute("height");
|
||||
return {x, y, width, height};
|
||||
}
|
||||
|
||||
function highlightElement(element, zoom) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaneously
|
||||
const box = element.tagName === "svg" ? getBBox(element) : element.getBBox();
|
||||
const enter = d3.transition().duration(1000).ease(d3.easeBounceOut);
|
||||
const exit = d3.transition().duration(500).ease(d3.easeLinear);
|
||||
|
||||
const highlight = debug.append('rect').attr('x', box.x).attr('y', box.y).attr('width', box.width).attr('height', box.height).attr('transform', transform);
|
||||
|
||||
highlight.classed("highlighted", 1).attr("transform", transform);
|
||||
highlight.classed('highlighted', 1).transition(enter).style('outline-offset', '0px').transition(exit).style('outline-color', 'transparent').delay(1000).remove();
|
||||
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : 3, 1600);
|
||||
if (zoom) {
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : zoom, 1600);
|
||||
}
|
||||
}
|
||||
|
||||
function selectIcon(initial, callback) {
|
||||
|
|
@ -945,6 +967,37 @@ function selectIcon(initial, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
function confirmationDialog(options) {
|
||||
const {
|
||||
title = "Confirm action",
|
||||
message = "Are you sure you want to continue? <br>The action cannot be reverted",
|
||||
cancel = "Cancel",
|
||||
confirm = "Continue",
|
||||
onCancel,
|
||||
onConfirm
|
||||
} = options;
|
||||
|
||||
const buttons = {
|
||||
[confirm]: function () {
|
||||
if (onConfirm) onConfirm();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
[cancel]: function () {
|
||||
if (onCancel) onCancel();
|
||||
$(this).dialog("close");
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById("alertMessage").innerHTML = message;
|
||||
$("#alert").dialog({resizable: false, title, buttons});
|
||||
}
|
||||
|
||||
// add and register event listeners to clean up on editor closure
|
||||
function listen(element, event, handler) {
|
||||
element.addEventListener(event, handler);
|
||||
return () => element.removeEventListener(event, handler);
|
||||
}
|
||||
|
||||
// Calls the refresh for all currently open editors
|
||||
function refreshAllEditors() {
|
||||
TIME && console.time('refreshAllEditors');
|
||||
|
|
|
|||
1098
modules/ui/editors.js.orig
Normal file
1098
modules/ui/editors.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,12 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
function showEPForRoute(node) {
|
||||
const points = [];
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
const i = findCell(this.getAttribute('cx'), this.getAttribute('cy'));
|
||||
points.push(i);
|
||||
});
|
||||
|
||||
|
|
@ -17,10 +17,10 @@ function showEPForRoute(node) {
|
|||
function showEPForRiver(node) {
|
||||
const points = [];
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
const i = findCell(this.getAttribute('cx'), this.getAttribute('cy'));
|
||||
points.push(i);
|
||||
});
|
||||
|
||||
|
|
@ -30,16 +30,16 @@ function showEPForRiver(node) {
|
|||
|
||||
function showElevationProfile(data, routeLen, isRiver) {
|
||||
// data is an array of cell indexes, routeLen is the distance (in actual metres/feet), isRiver should be true for rivers, false otherwise
|
||||
document.getElementById("epScaleRange").addEventListener("change", draw);
|
||||
document.getElementById("epCurve").addEventListener("change", draw);
|
||||
document.getElementById("epSave").addEventListener("click", downloadCSV);
|
||||
document.getElementById('epScaleRange').addEventListener('change', draw);
|
||||
document.getElementById('epCurve').addEventListener('change', draw);
|
||||
document.getElementById('epSave').addEventListener('click', downloadCSV);
|
||||
|
||||
$("#elevationProfile").dialog({
|
||||
title: "Elevation profile",
|
||||
$('#elevationProfile').dialog({
|
||||
title: 'Elevation profile',
|
||||
resizable: false,
|
||||
width: window.width,
|
||||
close: closeElevationProfile,
|
||||
position: {my: "left top", at: "left+20 bottom-500", of: window, collision: "fit"}
|
||||
position: {my: 'left top', at: 'left+20 bottom-500', of: window, collision: 'fit'}
|
||||
});
|
||||
|
||||
// prevent river graphs from showing rivers as flowing uphill - remember the general slope
|
||||
|
|
@ -67,7 +67,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let h = pack.cells.h[cell];
|
||||
if (h < 20) {
|
||||
const f = pack.features[pack.cells.f[cell]];
|
||||
if (f.type === "lake") h = f.height;
|
||||
if (f.type === 'lake') h = f.height;
|
||||
else h = 20;
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
chartData.burg[i] = b;
|
||||
chartData.cell[i] = cell;
|
||||
let sh = getHeight(h);
|
||||
chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(" ")));
|
||||
chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(' ')));
|
||||
chartData.mih = Math.min(chartData.mih, h);
|
||||
chartData.mah = Math.max(chartData.mah, h);
|
||||
chartData.mi = Math.min(chartData.mi, chartData.height[i]);
|
||||
|
|
@ -109,7 +109,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
draw();
|
||||
|
||||
function downloadCSV() {
|
||||
let data = "Point,X,Y,Cell,Height,Height value,Population,Burg,Burg population,Biome,Biome color,Culture,Culture color,Religion,Religion color,Province,Province color,State,State color\n"; // headers
|
||||
let data = 'Point,X,Y,Cell,Height,Height value,Population,Burg,Burg population,Biome,Biome color,Culture,Culture color,Religion,Religion color,Province,Province color,State,State color\n'; // headers
|
||||
|
||||
for (let k = 0; k < chartData.points.length; k++) {
|
||||
let cell = chartData.cell[k];
|
||||
|
|
@ -122,34 +122,34 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let pop = pack.cells.pop[cell];
|
||||
let h = pack.cells.h[cell];
|
||||
|
||||
data += k + 1 + ",";
|
||||
data += chartData.points[k][0] + ",";
|
||||
data += chartData.points[k][1] + ",";
|
||||
data += cell + ",";
|
||||
data += getHeight(h) + ",";
|
||||
data += h + ",";
|
||||
data += rn(pop * populationRate) + ",";
|
||||
data += k + 1 + ',';
|
||||
data += chartData.points[k][0] + ',';
|
||||
data += chartData.points[k][1] + ',';
|
||||
data += cell + ',';
|
||||
data += getHeight(h) + ',';
|
||||
data += h + ',';
|
||||
data += rn(pop * populationRate) + ',';
|
||||
if (burg) {
|
||||
data += pack.burgs[burg].name + ",";
|
||||
data += pack.burgs[burg].population * populationRate * urbanization + ",";
|
||||
data += pack.burgs[burg].name + ',';
|
||||
data += pack.burgs[burg].population * populationRate * urbanization + ',';
|
||||
} else {
|
||||
data += ",0,";
|
||||
data += ',0,';
|
||||
}
|
||||
data += biomesData.name[biome] + ",";
|
||||
data += biomesData.color[biome] + ",";
|
||||
data += pack.cultures[culture].name + ",";
|
||||
data += pack.cultures[culture].color + ",";
|
||||
data += pack.religions[religion].name + ",";
|
||||
data += pack.religions[religion].color + ",";
|
||||
data += pack.provinces[province].name + ",";
|
||||
data += pack.provinces[province].color + ",";
|
||||
data += pack.states[state].name + ",";
|
||||
data += pack.states[state].color + ",";
|
||||
data += biomesData.name[biome] + ',';
|
||||
data += biomesData.color[biome] + ',';
|
||||
data += pack.cultures[culture].name + ',';
|
||||
data += pack.cultures[culture].color + ',';
|
||||
data += pack.religions[religion].name + ',';
|
||||
data += pack.religions[religion].color + ',';
|
||||
data += pack.provinces[province].name + ',';
|
||||
data += pack.provinces[province].color + ',';
|
||||
data += pack.states[state].name + ',';
|
||||
data += pack.states[state].color + ',';
|
||||
|
||||
data = data + "\n";
|
||||
data = data + '\n';
|
||||
}
|
||||
|
||||
const name = getFileName("elevation profile") + ".csv";
|
||||
const name = getFileName('elevation profile') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
|
|
@ -169,37 +169,48 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
chartData.points.push([xscale(i) + xOffset, yscale(chartData.height[i]) + yOffset]);
|
||||
}
|
||||
|
||||
document.getElementById("elevationGraph").innerHTML = "";
|
||||
document.getElementById('elevationGraph').innerHTML = '';
|
||||
|
||||
const chart = d3
|
||||
.select("#elevationGraph")
|
||||
.append("svg")
|
||||
.attr("width", chartWidth + 120)
|
||||
.attr("height", chartHeight + yOffset + biomesHeight)
|
||||
.attr("id", "elevationSVG")
|
||||
.attr("class", "epbackground");
|
||||
.select('#elevationGraph')
|
||||
.append('svg')
|
||||
.attr('width', chartWidth + 120)
|
||||
.attr('height', chartHeight + yOffset + biomesHeight)
|
||||
.attr('id', 'elevationSVG')
|
||||
.attr('class', 'epbackground');
|
||||
// arrow-head definition
|
||||
chart.append("defs").append("marker").attr("id", "arrowhead").attr("orient", "auto").attr("markerWidth", "2").attr("markerHeight", "4").attr("refX", "0.1").attr("refY", "2").append("path").attr("d", "M0,0 V4 L2,2 Z").attr("fill", "darkgray");
|
||||
chart
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('orient', 'auto')
|
||||
.attr('markerWidth', '2')
|
||||
.attr('markerHeight', '4')
|
||||
.attr('refX', '0.1')
|
||||
.attr('refY', '2')
|
||||
.append('path')
|
||||
.attr('d', 'M0,0 V4 L2,2 Z')
|
||||
.attr('fill', 'darkgray');
|
||||
|
||||
let colors = getColorScheme();
|
||||
const landdef = chart.select("defs").append("linearGradient").attr("id", "landdef").attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
||||
const landdef = chart.select('defs').append('linearGradient').attr('id', 'landdef').attr('x1', '0%').attr('y1', '0%').attr('x2', '0%').attr('y2', '100%');
|
||||
|
||||
if (chartData.mah == chartData.mih) {
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", "0%")
|
||||
.attr("style", "stop-color:" + getColor(chartData.mih, colors) + ";stop-opacity:1");
|
||||
.append('stop')
|
||||
.attr('offset', '0%')
|
||||
.attr('style', 'stop-color:' + getColor(chartData.mih, colors) + ';stop-opacity:1');
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("style", "stop-color:" + getColor(chartData.mah, colors) + ";stop-opacity:1");
|
||||
.append('stop')
|
||||
.attr('offset', '100%')
|
||||
.attr('style', 'stop-color:' + getColor(chartData.mah, colors) + ';stop-opacity:1');
|
||||
} else {
|
||||
for (let k = chartData.mah; k >= chartData.mih; k--) {
|
||||
let perc = 1 - (k - chartData.mih) / (chartData.mah - chartData.mih);
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", perc * 100 + "%")
|
||||
.attr("style", "stop-color:" + getColor(k, colors) + ";stop-opacity:1");
|
||||
.append('stop')
|
||||
.attr('offset', perc * 100 + '%')
|
||||
.attr('style', 'stop-color:' + getColor(k, colors) + ';stop-opacity:1');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,14 +242,14 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let extra = chartData.points.slice();
|
||||
let path = curve(extra);
|
||||
// this completes the right-hand side and bottom of our land "polygon"
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(extra[extra.length - 1][1]);
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += "Z";
|
||||
chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)");
|
||||
path += ' L' + parseInt(xscale(extra.length) + +xOffset) + ',' + parseInt(extra[extra.length - 1][1]);
|
||||
path += ' L' + parseInt(xscale(extra.length) + +xOffset) + ',' + parseInt(yscale(0) + +yOffset);
|
||||
path += ' L' + parseInt(xscale(0) + +xOffset) + ',' + parseInt(yscale(0) + +yOffset);
|
||||
path += 'Z';
|
||||
chart.append('g').attr('id', 'epland').append('path').attr('d', path).attr('stroke', 'purple').attr('stroke-width', '0').attr('fill', 'url(#landdef)');
|
||||
|
||||
// biome / heights
|
||||
let g = chart.append("g").attr("id", "epbiomes");
|
||||
let g = chart.append('g').attr('id', 'epbiomes');
|
||||
const hu = heightUnit.value;
|
||||
for (let k = 0; k < chartData.points.length; k++) {
|
||||
const x = chartData.points[k][0];
|
||||
|
|
@ -257,65 +268,82 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
|
||||
const populationDesc = rn(pop * populationRate);
|
||||
|
||||
const provinceDesc = province ? ", " + pack.provinces[province].name : "";
|
||||
const dataTip = biomesData.name[chartData.biome[k]] + provinceDesc + ", " + pack.states[state].name + ", " + pack.religions[religion].name + ", " + pack.cultures[culture].name + " (height: " + chartData.height[k] + " " + hu + ", population " + populationDesc + ", cell " + chartData.cell[k] + ")";
|
||||
const provinceDesc = province ? ', ' + pack.provinces[province].name : '';
|
||||
const dataTip =
|
||||
biomesData.name[chartData.biome[k]] +
|
||||
provinceDesc +
|
||||
', ' +
|
||||
pack.states[state].name +
|
||||
', ' +
|
||||
pack.religions[religion].name +
|
||||
', ' +
|
||||
pack.cultures[culture].name +
|
||||
' (height: ' +
|
||||
chartData.height[k] +
|
||||
' ' +
|
||||
hu +
|
||||
', population ' +
|
||||
populationDesc +
|
||||
', cell ' +
|
||||
chartData.cell[k] +
|
||||
')';
|
||||
|
||||
g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip);
|
||||
g.append('rect').attr('stroke', c).attr('fill', c).attr('x', x).attr('y', y).attr('width', xscale(1)).attr('height', 15).attr('data-tip', dataTip);
|
||||
}
|
||||
|
||||
const xAxis = d3
|
||||
.axisBottom(xscale)
|
||||
.ticks(10)
|
||||
.tickFormat(function (d) {
|
||||
return rn((d / chartData.points.length) * routeLen) + " " + distanceUnitInput.value;
|
||||
return rn((d / chartData.points.length) * routeLen) + ' ' + distanceUnitInput.value;
|
||||
});
|
||||
const yAxis = d3
|
||||
.axisLeft(yscale)
|
||||
.ticks(5)
|
||||
.tickFormat(function (d) {
|
||||
return d + " " + hu;
|
||||
return d + ' ' + hu;
|
||||
});
|
||||
|
||||
const xGrid = d3.axisBottom(xscale).ticks(10).tickSize(-chartHeight).tickFormat("");
|
||||
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth).tickFormat("");
|
||||
const xGrid = d3.axisBottom(xscale).ticks(10).tickSize(-chartHeight).tickFormat('');
|
||||
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth).tickFormat('');
|
||||
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epxaxis")
|
||||
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset + 20) + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epxaxis')
|
||||
.attr('transform', 'translate(' + xOffset + ',' + parseInt(chartHeight + +yOffset + 20) + ')')
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "center")
|
||||
.attr("transform", function (d) {
|
||||
return "rotate(0)"; // used to rotate labels, - anti-clockwise, + clockwise
|
||||
.selectAll('text')
|
||||
.style('text-anchor', 'center')
|
||||
.attr('transform', function (d) {
|
||||
return 'rotate(0)'; // used to rotate labels, - anti-clockwise, + clockwise
|
||||
});
|
||||
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epyaxis")
|
||||
.attr("transform", "translate(" + parseInt(+xOffset - 10) + "," + parseInt(+yOffset) + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epyaxis')
|
||||
.attr('transform', 'translate(' + parseInt(+xOffset - 10) + ',' + parseInt(+yOffset) + ')')
|
||||
.call(yAxis);
|
||||
|
||||
// add the X gridlines
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epxgrid")
|
||||
.attr("class", "epgrid")
|
||||
.attr("stroke-dasharray", "4 1")
|
||||
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset) + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epxgrid')
|
||||
.attr('class', 'epgrid')
|
||||
.attr('stroke-dasharray', '4 1')
|
||||
.attr('transform', 'translate(' + xOffset + ',' + parseInt(chartHeight + +yOffset) + ')')
|
||||
.call(xGrid);
|
||||
|
||||
// add the Y gridlines
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epygrid")
|
||||
.attr("class", "epgrid")
|
||||
.attr("stroke-dasharray", "4 1")
|
||||
.attr("transform", "translate(" + xOffset + "," + yOffset + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epygrid')
|
||||
.attr('class', 'epgrid')
|
||||
.attr('stroke-dasharray', '4 1')
|
||||
.attr('transform', 'translate(' + xOffset + ',' + yOffset + ')')
|
||||
.call(yGrid);
|
||||
|
||||
// draw city labels - try to avoid putting labels over one another
|
||||
g = chart.append("g").attr("id", "epburglabels");
|
||||
g = chart.append('g').attr('id', 'epburglabels');
|
||||
let y1 = 0;
|
||||
const add = 15;
|
||||
|
||||
|
|
@ -331,31 +359,31 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
if (y1 >= yOffset) y1 = add;
|
||||
|
||||
// burg name
|
||||
g.append("text")
|
||||
.attr("id", "ep" + b)
|
||||
.attr("class", "epburglabel")
|
||||
.attr("x", x1)
|
||||
.attr("y", y1)
|
||||
.attr("text-anchor", "middle");
|
||||
document.getElementById("ep" + b).innerHTML = pack.burgs[b].name;
|
||||
g.append('text')
|
||||
.attr('id', 'ep' + b)
|
||||
.attr('class', 'epburglabel')
|
||||
.attr('x', x1)
|
||||
.attr('y', y1)
|
||||
.attr('text-anchor', 'middle');
|
||||
document.getElementById('ep' + b).innerHTML = pack.burgs[b].name;
|
||||
|
||||
// arrow from burg name to graph line
|
||||
g.append("path")
|
||||
.attr("id", "eparrow" + b)
|
||||
.attr("d", "M" + x1.toString() + "," + (y1 + 3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1] - 3).toString())
|
||||
.attr("stroke", "darkgray")
|
||||
.attr("fill", "lightgray")
|
||||
.attr("stroke-width", "1")
|
||||
.attr("marker-end", "url(#arrowhead)");
|
||||
g.append('path')
|
||||
.attr('id', 'eparrow' + b)
|
||||
.attr('d', 'M' + x1.toString() + ',' + (y1 + 3).toString() + 'L' + x1.toString() + ',' + parseInt(chartData.points[k][1] - 3).toString())
|
||||
.attr('stroke', 'darkgray')
|
||||
.attr('fill', 'lightgray')
|
||||
.attr('stroke-width', '1')
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeElevationProfile() {
|
||||
document.getElementById("epScaleRange").removeEventListener("change", draw);
|
||||
document.getElementById("epCurve").removeEventListener("change", draw);
|
||||
document.getElementById("epSave").removeEventListener("click", downloadCSV);
|
||||
document.getElementById("elevationGraph").innerHTML = "";
|
||||
document.getElementById('epScaleRange').removeEventListener('change', draw);
|
||||
document.getElementById('epCurve').removeEventListener('change', draw);
|
||||
document.getElementById('epSave').removeEventListener('click', downloadCSV);
|
||||
document.getElementById('elevationGraph').innerHTML = '';
|
||||
modules.elevation = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Module to store general UI functions
|
||||
'use strict';
|
||||
// Module to store general UI functions
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
$(window).resize(function (e) {
|
||||
window.addEventListener("resize", function (e) {
|
||||
if (localStorage.getItem('mapWidth') && localStorage.getItem('mapHeight')) return;
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
|
|
@ -10,6 +10,7 @@ $(window).resize(function (e) {
|
|||
});
|
||||
|
||||
window.onbeforeunload = () => 'Are you sure you want to navigate away?';
|
||||
}
|
||||
|
||||
// Tooltips
|
||||
const tooltip = document.getElementById('tooltip');
|
||||
|
|
@ -19,12 +20,6 @@ document.getElementById('dialogs').addEventListener('mousemove', showDataTip);
|
|||
document.getElementById('optionsContainer').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('exitCustomization').addEventListener('mousemove', showDataTip);
|
||||
|
||||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
* @param {boolean} main Show above other tooltips
|
||||
* @param {string} type Message type (color): error / warn / success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = 'Tip is undefined', main, type, time) {
|
||||
tooltip.innerHTML = tip;
|
||||
tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)';
|
||||
|
|
@ -32,11 +27,15 @@ function tip(tip = 'Tip is undefined', main, type, time) {
|
|||
else if (type === 'warn') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)';
|
||||
else if (type === 'success') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)';
|
||||
|
||||
if (main) tooltip.dataset.main = tip; // set main tip
|
||||
if (main) {
|
||||
if (time) setTimeout(() => (tooltip.dataset.main = ''), time); // clear main in some time
|
||||
tooltip.dataset.color = tooltip.style.background;
|
||||
}
|
||||
if (time) setTimeout(() => clearMainTip(), time);
|
||||
}
|
||||
|
||||
function showMainTip() {
|
||||
tooltip.style.background = tooltip.dataset.color;
|
||||
tooltip.innerHTML = tooltip.dataset.main;
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +54,15 @@ function showDataTip(e) {
|
|||
tip(dataTip);
|
||||
}
|
||||
|
||||
function showElementLockTip(event) {
|
||||
const locked = event?.target?.classList?.contains("icon-lock");
|
||||
if (locked) {
|
||||
tip("Click to unlock the element and allow it to be changed by regeneration tools");
|
||||
} else {
|
||||
tip("Click to lock the element and prevent changes to it by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
const moved = debounce(mouseMove, 100);
|
||||
function mouseMove() {
|
||||
const point = d3.mouse(this);
|
||||
|
|
@ -79,7 +87,7 @@ function showNotes(e, i) {
|
|||
document.getElementById('notes').style.display = 'block';
|
||||
document.getElementById('notesHeader').innerHTML = note.name;
|
||||
document.getElementById('notesBody').innerHTML = note.legend;
|
||||
} else if (!options.pinNotes) {
|
||||
} else if (!options.pinNotes && !markerEditor.offsetParent) {
|
||||
document.getElementById('notes').style.display = 'none';
|
||||
document.getElementById('notesHeader').innerHTML = '';
|
||||
document.getElementById('notesBody').innerHTML = '';
|
||||
|
|
@ -101,6 +109,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (group === 'emblems' && e.target.tagName === 'use') {
|
||||
const parent = e.target.parentNode;
|
||||
const [g, type] = parent.id === 'burgEmblems' ? [pack.burgs, 'burg'] : parent.id === 'provinceEmblems' ? [pack.provinces, 'province'] : [pack.states, 'state'];
|
||||
parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
const i = +e.target.dataset.i;
|
||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||
|
||||
|
|
@ -497,229 +506,7 @@ function showInfo() {
|
|||
});
|
||||
}
|
||||
|
||||
// prevent default browser behavior for FMG-used hotkeys
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallow alt key combinations
|
||||
if (event.ctrlKey && event.code === 'KeyS') event.preventDefault(); // disallow CTRL + C
|
||||
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
|
||||
});
|
||||
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener('keyup', (event) => {
|
||||
if (!window.closeDialogs) return; // not all modules are loaded
|
||||
const canvas3d = document.getElementById('canvas3d'); // check if 3d mode is active
|
||||
const active = document.activeElement.tagName;
|
||||
if (active === 'INPUT' || active === 'SELECT' || active === 'TEXTAREA') return; // don't trigger if user inputs a text
|
||||
if (active === 'DIV' && document.activeElement.contentEditable === 'true') return; // don't trigger if user inputs a text
|
||||
event.stopPropagation();
|
||||
|
||||
const key = event.keyCode;
|
||||
const ctrl = event.ctrlKey || event.metaKey || key === 17;
|
||||
const shift = event.shiftKey || key === 16;
|
||||
const alt = event.altKey || key === 18;
|
||||
|
||||
if (key === 112) showInfo();
|
||||
// "F1" to show info
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for new map
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for a new map
|
||||
else if (key === 117) quickSave();
|
||||
// "F6" for quick save
|
||||
else if (key === 120) quickLoad();
|
||||
// "F9" for quick load
|
||||
else if (key === 9) toggleOptions(event);
|
||||
// Tab to toggle options
|
||||
else if (key === 27) {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
} // Escape to close all dialogs
|
||||
else if (key === 46) removeElementOnKey();
|
||||
// "Delete" to remove the selected element
|
||||
else if (key === 79 && canvas3d) toggle3dOptions();
|
||||
// "O" to toggle 3d options
|
||||
else if (ctrl && key === 81) toggleSaveReminder();
|
||||
// Ctrl + "Q" to toggle save reminder
|
||||
else if (ctrl && key === 83) saveMap();
|
||||
// Ctrl + "S" to save .map file
|
||||
else if (undo.offsetParent && ctrl && key === 90) undo.click();
|
||||
// Ctrl + "Z" to undo
|
||||
else if (redo.offsetParent && ctrl && key === 89) redo.click();
|
||||
// Ctrl + "Y" to redo
|
||||
else if (shift && key === 72) editHeightmap();
|
||||
// Shift + "H" to edit Heightmap
|
||||
else if (shift && key === 66) editBiomes();
|
||||
// Shift + "B" to edit Biomes
|
||||
else if (shift && key === 83) editStates();
|
||||
// Shift + "S" to edit States
|
||||
else if (shift && key === 80) editProvinces();
|
||||
// Shift + "P" to edit Provinces
|
||||
else if (shift && key === 68) editDiplomacy();
|
||||
// Shift + "D" to edit Diplomacy
|
||||
else if (shift && key === 67) editCultures();
|
||||
// Shift + "C" to edit Cultures
|
||||
else if (shift && key === 78) editNamesbase();
|
||||
// Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones();
|
||||
// Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions();
|
||||
// Shift + "R" to edit Religions
|
||||
else if (shift && key === 81) editResources();
|
||||
// Shift + "Q" to edit Resources
|
||||
else if (shift && key === 89) openEmblemEditor();
|
||||
// Shift + "Y" to edit Emblems
|
||||
else if (shift && key === 87) editUnits();
|
||||
// Shift + "W" to edit Units
|
||||
else if (shift && key === 79) editNotes();
|
||||
// Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs();
|
||||
// Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers();
|
||||
// Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 77) overviewMilitary();
|
||||
// Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails();
|
||||
// Shift + "E" to open Cell Details
|
||||
else if (shift && key === 49) toggleAddBurg();
|
||||
// Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel();
|
||||
// Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver();
|
||||
// Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute();
|
||||
// Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker();
|
||||
// Shift + "5" to click to add Marker
|
||||
else if (alt && key === 66) console.table(pack.burgs);
|
||||
// Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states);
|
||||
// Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures);
|
||||
// Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions);
|
||||
// Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features);
|
||||
// Alt + "F" to log features data
|
||||
else if (key === 88) toggleTexture();
|
||||
// "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight();
|
||||
// "H" to toggle Heightmap layer
|
||||
else if (key === 66) toggleBiomes();
|
||||
// "B" to toggle Biomes layer
|
||||
else if (key === 69) toggleCells();
|
||||
// "E" to toggle Cells layer
|
||||
else if (key === 71) toggleGrid();
|
||||
// "G" to toggle Grid layer
|
||||
else if (key === 79) toggleCoordinates();
|
||||
// "O" to toggle Coordinates layer
|
||||
else if (key === 87) toggleCompass();
|
||||
// "W" to toggle Compass Rose layer
|
||||
else if (key === 86) toggleRivers();
|
||||
// "V" to toggle Rivers layer
|
||||
else if (key === 70) toggleRelief();
|
||||
// "F" to toggle Relief icons layer
|
||||
else if (key === 67) toggleCultures();
|
||||
// "C" to toggle Cultures layer
|
||||
else if (key === 83) toggleStates();
|
||||
// "S" to toggle States layer
|
||||
else if (key === 80) toggleProvinces();
|
||||
// "P" to toggle Provinces layer
|
||||
else if (key === 90) toggleZones();
|
||||
// "Z" to toggle Zones
|
||||
else if (key === 68) toggleBorders();
|
||||
// "D" to toggle Borders layer
|
||||
else if (key === 82) toggleReligions();
|
||||
// "R" to toggle Religions layer
|
||||
else if (key === 85) toggleRoutes();
|
||||
// "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp();
|
||||
// "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation();
|
||||
// "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce();
|
||||
// "J" to toggle Ice layer
|
||||
else if (key === 65) togglePrec();
|
||||
// "A" to toggle Precipitation layer
|
||||
else if (key === 81) toggleResources();
|
||||
// "Q" to toggle Resources layer
|
||||
else if (key === 89) toggleEmblems();
|
||||
// "Y" to toggle Emblems layer
|
||||
else if (key === 76) toggleLabels();
|
||||
// "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons();
|
||||
// "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMilitary();
|
||||
// "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers();
|
||||
// "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers();
|
||||
// Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar();
|
||||
// Minus (-) to toggle Scale bar
|
||||
else if (key === 37) zoom.translateBy(svg, 10, 0);
|
||||
// Left to scroll map left
|
||||
else if (key === 39) zoom.translateBy(svg, -10, 0);
|
||||
// Right to scroll map right
|
||||
else if (key === 38) zoom.translateBy(svg, 0, 10);
|
||||
// Up to scroll map up
|
||||
else if (key === 40) zoom.translateBy(svg, 0, -10);
|
||||
// Up to scroll map up
|
||||
else if (key === 107 || key === 109) pressNumpadSign(key);
|
||||
// Numpad Plus/Minus to zoom map or change brush size
|
||||
else if (key === 48 || key === 96) resetZoom(1000);
|
||||
// 0 to reset zoom
|
||||
else if (key === 49 || key === 97) zoom.scaleTo(svg, 1);
|
||||
// 1 to zoom to 1
|
||||
else if (key === 50 || key === 98) zoom.scaleTo(svg, 2);
|
||||
// 2 to zoom to 2
|
||||
else if (key === 51 || key === 99) zoom.scaleTo(svg, 3);
|
||||
// 3 to zoom to 3
|
||||
else if (key === 52 || key === 100) zoom.scaleTo(svg, 4);
|
||||
// 4 to zoom to 4
|
||||
else if (key === 53 || key === 101) zoom.scaleTo(svg, 5);
|
||||
// 5 to zoom to 5
|
||||
else if (key === 54 || key === 102) zoom.scaleTo(svg, 6);
|
||||
// 6 to zoom to 6
|
||||
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7);
|
||||
// 7 to zoom to 7
|
||||
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8);
|
||||
// 8 to zoom to 8
|
||||
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9);
|
||||
// 9 to zoom to 9
|
||||
else if (ctrl) pressControl(); // Control to toggle mode
|
||||
});
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
// if brush sliders are displayed, decrease brush size
|
||||
let brush = null;
|
||||
const d = key === 107 ? 1 : -1;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById('brushRadius');
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById('biomesManuallyBrush');
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById('statesManuallyBrush');
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById('provincesManuallyBrush');
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById('culturesManuallyBrush');
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById('zonesBrush');
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById('religionsManuallyBrush');
|
||||
|
||||
if (brush) {
|
||||
const value = Math.max(Math.min(+brush.value + d, +brush.max), +brush.min);
|
||||
brush.value = document.getElementById(brush.id + 'Number').value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === 107 ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no, zoom map
|
||||
}
|
||||
|
||||
function pressControl() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains('pressed') ? zonesRemove.classList.remove('pressed') : zonesRemove.classList.add('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$('.dialog:visible .fastDelete').click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
// "Q" to toggle Resources layer
|
||||
810
modules/ui/general.js.orig
Normal file
810
modules/ui/general.js.orig
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
<<<<<<< HEAD
|
||||
// Module to store general UI functions
|
||||
'use strict';
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
$(window).resize(function (e) {
|
||||
if (localStorage.getItem('mapWidth') && localStorage.getItem('mapHeight')) return;
|
||||
=======
|
||||
"use strict";
|
||||
// Module to store general UI functions
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
window.addEventListener("resize", function (e) {
|
||||
if (localStorage.getItem("mapWidth") && localStorage.getItem("mapHeight")) return;
|
||||
>>>>>>> master
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
changeMapSize();
|
||||
});
|
||||
|
||||
<<<<<<< HEAD
|
||||
window.onbeforeunload = () => 'Are you sure you want to navigate away?';
|
||||
=======
|
||||
if (location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1") {
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
}
|
||||
>>>>>>> master
|
||||
|
||||
// Tooltips
|
||||
const tooltip = document.getElementById('tooltip');
|
||||
|
||||
// show tip for non-svg elemets with data-tip
|
||||
document.getElementById('dialogs').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('optionsContainer').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('exitCustomization').addEventListener('mousemove', showDataTip);
|
||||
|
||||
<<<<<<< HEAD
|
||||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
* @param {boolean} main Show above other tooltips
|
||||
* @param {string} type Message type (color): error / warn / success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = 'Tip is undefined', main, type, time) {
|
||||
=======
|
||||
function tip(tip = "Tip is undefined", main, type, time) {
|
||||
>>>>>>> master
|
||||
tooltip.innerHTML = tip;
|
||||
tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)';
|
||||
if (type === 'error') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)';
|
||||
else if (type === 'warn') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)';
|
||||
else if (type === 'success') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)';
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (main) tooltip.dataset.main = tip; // set main tip
|
||||
if (time) setTimeout(() => (tooltip.dataset.main = ''), time); // clear main in some time
|
||||
=======
|
||||
if (main) {
|
||||
tooltip.dataset.main = tip;
|
||||
tooltip.dataset.color = tooltip.style.background;
|
||||
}
|
||||
if (time) setTimeout(() => clearMainTip(), time);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function showMainTip() {
|
||||
tooltip.style.background = tooltip.dataset.color;
|
||||
tooltip.innerHTML = tooltip.dataset.main;
|
||||
}
|
||||
|
||||
function clearMainTip() {
|
||||
<<<<<<< HEAD
|
||||
tooltip.dataset.main = '';
|
||||
tooltip.innerHTML = '';
|
||||
=======
|
||||
tooltip.dataset.color = "";
|
||||
tooltip.dataset.main = "";
|
||||
tooltip.innerHTML = "";
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
// show tip at the bottom of the screen, consider possible translation
|
||||
function showDataTip(e) {
|
||||
if (!e.target) return;
|
||||
let dataTip = e.target.dataset.tip;
|
||||
if (!dataTip && e.target.parentNode.dataset.tip) dataTip = e.target.parentNode.dataset.tip;
|
||||
if (!dataTip) return;
|
||||
//const tooltip = lang === "en" ? dataTip : translate(e.target.dataset.t || e.target.parentNode.dataset.t, dataTip);
|
||||
tip(dataTip);
|
||||
}
|
||||
|
||||
function showElementLockTip(event) {
|
||||
const locked = event?.target?.classList?.contains("icon-lock");
|
||||
if (locked) {
|
||||
tip("Click to unlock the element and allow it to be changed by regeneration tools");
|
||||
} else {
|
||||
tip("Click to lock the element and prevent changes to it by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
const moved = debounce(mouseMove, 100);
|
||||
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);
|
||||
const g = findGridCell(point[0], point[1]); // grid cell id
|
||||
if (tooltip.dataset.main) showMainTip();
|
||||
else showMapTooltip(point, d3.event, i, g);
|
||||
if (cellInfo.offsetParent) updateCellInfo(point, i, g);
|
||||
}
|
||||
|
||||
// show note box on hover (if any)
|
||||
function showNotes(e, i) {
|
||||
if (notesEditor.offsetParent) return;
|
||||
let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id;
|
||||
<<<<<<< HEAD
|
||||
if (e.target.parentNode.parentNode.id === 'burgLabels') id = 'burg' + e.target.dataset.id;
|
||||
else if (e.target.parentNode.parentNode.id === 'burgIcons') id = 'burg' + e.target.dataset.id;
|
||||
|
||||
const note = notes.find((note) => note.id === id);
|
||||
if (note !== undefined && note.legend !== '') {
|
||||
document.getElementById('notes').style.display = 'block';
|
||||
document.getElementById('notesHeader').innerHTML = note.name;
|
||||
document.getElementById('notesBody').innerHTML = note.legend;
|
||||
} else if (!options.pinNotes) {
|
||||
document.getElementById('notes').style.display = 'none';
|
||||
document.getElementById('notesHeader').innerHTML = '';
|
||||
document.getElementById('notesBody').innerHTML = '';
|
||||
=======
|
||||
if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id;
|
||||
else if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id;
|
||||
|
||||
const note = notes.find(note => note.id === id);
|
||||
if (note !== undefined && note.legend !== "") {
|
||||
document.getElementById("notes").style.display = "block";
|
||||
document.getElementById("notesHeader").innerHTML = note.name;
|
||||
document.getElementById("notesBody").innerHTML = note.legend;
|
||||
} else if (!options.pinNotes && !markerEditor.offsetParent) {
|
||||
document.getElementById("notes").style.display = "none";
|
||||
document.getElementById("notesHeader").innerHTML = "";
|
||||
document.getElementById("notesBody").innerHTML = "";
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
// show viewbox tooltip if main tooltip is blank
|
||||
function showMapTooltip(point, e, i, g) {
|
||||
tip(''); // clear tip
|
||||
const path = e.composedPath ? e.composedPath() : getComposedPath(e.target); // apply polyfill
|
||||
if (!path[path.length - 8]) return;
|
||||
const group = path[path.length - 7].id;
|
||||
const subgroup = path[path.length - 8].id;
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// specific elements
|
||||
if (group === 'armies') return tip(e.target.parentNode.dataset.name + '. Click to edit');
|
||||
|
||||
if (group === 'emblems' && e.target.tagName === 'use') {
|
||||
const parent = e.target.parentNode;
|
||||
<<<<<<< HEAD
|
||||
const [g, type] = parent.id === 'burgEmblems' ? [pack.burgs, 'burg'] : parent.id === 'provinceEmblems' ? [pack.provinces, 'province'] : [pack.states, 'state'];
|
||||
=======
|
||||
const [g, type] =
|
||||
parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
>>>>>>> master
|
||||
const i = +e.target.dataset.i;
|
||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||
|
||||
d3.select(e.target).raise();
|
||||
d3.select(parent).raise();
|
||||
|
||||
const name = g[i].fullName || g[i].name;
|
||||
tip(`${name} ${type} emblem. Click to edit. Hold Shift to show associated area or place`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'goods') {
|
||||
const id = +e.target.dataset.i;
|
||||
const resource = pack.resources.find((resource) => resource.i === id);
|
||||
tip('Resource: ' + resource.name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'rivers') {
|
||||
const river = +e.target.id.slice(5);
|
||||
const r = pack.rivers.find((r) => r.i === river);
|
||||
const name = r ? r.name + ' ' + r.type : '';
|
||||
tip(name + '. Click to edit');
|
||||
if (riversOverview.offsetParent) highlightEditorLine(riversOverview, river, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'routes') return tip('Click to edit the Route');
|
||||
|
||||
if (group === 'terrain') return tip('Click to edit the Relief Icon');
|
||||
|
||||
if (subgroup === 'burgLabels' || subgroup === 'burgIcons') {
|
||||
const burg = +path[path.length - 10].dataset.id;
|
||||
const b = pack.burgs[burg];
|
||||
const population = si(b.population * populationRate * urbanization);
|
||||
tip(`${b.name}. Population: ${population}. Click to edit`);
|
||||
if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'labels') return tip('Click to edit the Label');
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (group === 'markers') return tip('Click to edit the Marker');
|
||||
=======
|
||||
if (group === "markers") return tip("Click to edit the Marker and pin the marker note");
|
||||
>>>>>>> master
|
||||
|
||||
if (group === 'ruler') {
|
||||
const tag = e.target.tagName;
|
||||
const className = e.target.getAttribute('class');
|
||||
if (tag === 'circle' && className === 'edge') return tip('Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point');
|
||||
if (tag === 'circle' && className === 'control') return tip('Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point');
|
||||
if (tag === 'circle') return tip('Drag to adjust the measurer');
|
||||
if (tag === 'polyline') return tip('Click on drag to add a control point');
|
||||
if (tag === 'path') return tip('Drag to move the measurer');
|
||||
if (tag === 'text') return tip('Drag to move, click to remove the measurer');
|
||||
}
|
||||
|
||||
if (subgroup === 'burgIcons') return tip('Click to edit the Burg');
|
||||
|
||||
if (subgroup === 'burgLabels') return tip('Click to edit the Burg');
|
||||
|
||||
if (group === 'lakes' && !land) {
|
||||
const lakeId = +e.target.dataset.f;
|
||||
const name = pack.features[lakeId]?.name;
|
||||
const fullName = subgroup === 'freshwater' ? name : name + ' ' + subgroup;
|
||||
tip(`${fullName} lake. Click to edit`);
|
||||
return;
|
||||
}
|
||||
if (group === 'coastline') return tip('Click to edit the coastline');
|
||||
|
||||
if (group === 'zones') {
|
||||
const zone = path[path.length - 8];
|
||||
tip(zone.dataset.description);
|
||||
if (zonesEditor.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'ice') return tip('Click to edit the Ice');
|
||||
|
||||
// covering elements
|
||||
if (layerIsOn('togglePrec') && land) tip('Annual Precipitation: ' + getFriendlyPrecipitation(i));
|
||||
else if (layerIsOn('togglePopulation')) tip(getPopulationTip(i));
|
||||
else if (layerIsOn('toggleTemp')) tip('Temperature: ' + convertTemperature(grid.cells.temp[g]));
|
||||
else if (layerIsOn('toggleBiomes') && pack.cells.biome[i]) {
|
||||
const biome = pack.cells.biome[i];
|
||||
tip('Biome: ' + biomesData.name[biome]);
|
||||
if (biomesEditor.offsetParent) highlightEditorLine(biomesEditor, biome);
|
||||
} else if (layerIsOn('toggleReligions') && pack.cells.religion[i]) {
|
||||
const religion = pack.cells.religion[i];
|
||||
const r = pack.religions[religion];
|
||||
const type = r.type === 'Cult' || r.type == 'Heresy' ? r.type : r.type + ' religion';
|
||||
tip(type + ': ' + r.name);
|
||||
if (religionsEditor.offsetParent) highlightEditorLine(religionsEditor, religion);
|
||||
} else if (pack.cells.state[i] && (layerIsOn('toggleProvinces') || layerIsOn('toggleStates'))) {
|
||||
const state = pack.cells.state[i];
|
||||
const stateName = pack.states[state].fullName;
|
||||
const province = pack.cells.province[i];
|
||||
const prov = province ? pack.provinces[province].fullName + ', ' : '';
|
||||
tip(prov + stateName);
|
||||
if (statesEditor.offsetParent) highlightEditorLine(statesEditor, state);
|
||||
if (diplomacyEditor.offsetParent) highlightEditorLine(diplomacyEditor, state);
|
||||
if (militaryOverview.offsetParent) highlightEditorLine(militaryOverview, state);
|
||||
if (provincesEditor.offsetParent) highlightEditorLine(provincesEditor, province);
|
||||
} else if (layerIsOn('toggleCultures') && pack.cells.culture[i]) {
|
||||
const culture = pack.cells.culture[i];
|
||||
tip('Culture: ' + pack.cultures[culture].name);
|
||||
if (culturesEditor.offsetParent) highlightEditorLine(culturesEditor, culture);
|
||||
} else if (layerIsOn('toggleHeight')) tip('Height: ' + getFriendlyHeight(point));
|
||||
}
|
||||
|
||||
function highlightEditorLine(editor, id, timeout = 15000) {
|
||||
Array.from(editor.getElementsByClassName('states hovered')).forEach((el) => el.classList.remove('hovered')); // clear all hovered
|
||||
const hovered = Array.from(editor.querySelectorAll('div')).find((el) => el.dataset.id == id);
|
||||
if (hovered) hovered.classList.add('hovered'); // add hovered class
|
||||
if (timeout)
|
||||
setTimeout(() => {
|
||||
hovered && hovered.classList.remove('hovered');
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// get cell info on mouse move
|
||||
function updateCellInfo(point, i, g) {
|
||||
const cells = pack.cells;
|
||||
const x = (infoX.innerHTML = rn(point[0]));
|
||||
const y = (infoY.innerHTML = rn(point[1]));
|
||||
const f = cells.f[i];
|
||||
infoLat.innerHTML = toDMS(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, 'lat');
|
||||
infoLon.innerHTML = toDMS(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, 'lon');
|
||||
|
||||
infoCell.innerHTML = i;
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
infoArea.innerHTML = cells.area[i] ? si(cells.area[i] * distanceScaleInput.value ** 2) + unit : 'n/a';
|
||||
infoEvelation.innerHTML = getElevation(pack.features[f], pack.cells.h[i]);
|
||||
infoDepth.innerHTML = getDepth(pack.features[f], pack.cells.h[i], point);
|
||||
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : 'n/a';
|
||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : 'no';
|
||||
infoState.innerHTML = cells.h[i] >= 20 ? (cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : 'neutral lands (0)') : 'no';
|
||||
infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : 'no';
|
||||
infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : 'no';
|
||||
infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : 'no';
|
||||
infoPopulation.innerHTML = getFriendlyPopulation(i);
|
||||
infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + ' (' + cells.burg[i] + ')' : 'no';
|
||||
infoFeature.innerHTML = f ? pack.features[f].group + ' (' + f + ')' : 'n/a';
|
||||
infoBiome.innerHTML = biomesData.name[cells.biome[i]];
|
||||
}
|
||||
|
||||
// convert coordinate to DMS format
|
||||
function toDMS(coord, c) {
|
||||
const degrees = Math.floor(Math.abs(coord));
|
||||
const minutesNotTruncated = (Math.abs(coord) - degrees) * 60;
|
||||
const minutes = Math.floor(minutesNotTruncated);
|
||||
const seconds = Math.floor((minutesNotTruncated - minutes) * 60);
|
||||
const cardinal = c === 'lat' ? (coord >= 0 ? 'N' : 'S') : coord >= 0 ? 'E' : 'W';
|
||||
return degrees + '° ' + minutes + '′ ' + seconds + '″ ' + cardinal;
|
||||
}
|
||||
|
||||
// get surface elevation
|
||||
function getElevation(f, h) {
|
||||
if (f.land) return getHeight(h) + ' (' + h + ')'; // land: usual height
|
||||
if (f.border) return '0 ' + heightUnit.value; // ocean: 0
|
||||
if (f.type === 'lake') return getHeight(f.height) + ' (' + f.height + ')'; // lake: defined on river generation
|
||||
}
|
||||
|
||||
// get water depth
|
||||
function getDepth(f, h, p) {
|
||||
if (f.land) return '0 ' + heightUnit.value; // land: 0
|
||||
|
||||
// lake: difference between surface and bottom
|
||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
||||
if (f.type === 'lake') {
|
||||
const depth = gridH === 19 ? f.height / 2 : gridH;
|
||||
return getHeight(depth, 'abs');
|
||||
}
|
||||
|
||||
return getHeight(gridH, 'abs'); // ocean: grid height
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) height value from map data
|
||||
function getFriendlyHeight(p) {
|
||||
const packH = pack.cells.h[findCell(p[0], p[1])];
|
||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
||||
const h = packH < 20 ? gridH : packH;
|
||||
return getHeight(h);
|
||||
}
|
||||
|
||||
function getHeight(h, abs) {
|
||||
const unit = heightUnit.value;
|
||||
let unitRatio = 3.281; // default calculations are in feet
|
||||
if (unit === 'm') unitRatio = 1;
|
||||
// if meter
|
||||
else if (unit === 'f') unitRatio = 0.5468; // if fathom
|
||||
|
||||
let height = -990;
|
||||
if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value);
|
||||
else if (h < 20 && h > 0) height = ((h - 20) / h) * 50;
|
||||
|
||||
if (abs) height = Math.abs(height);
|
||||
return rn(height * unitRatio) + ' ' + unit;
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) precipitation value from map data
|
||||
function getFriendlyPrecipitation(i) {
|
||||
const prec = grid.cells.prec[pack.cells.g[i]];
|
||||
return prec * 100 + ' mm';
|
||||
}
|
||||
|
||||
function getRiverInfo(id) {
|
||||
const r = pack.rivers.find((r) => r.i == id);
|
||||
return r ? `${r.name} ${r.type} (${id})` : 'n/a';
|
||||
}
|
||||
|
||||
function getCellPopulation(i) {
|
||||
const rural = pack.cells.pop[i] * populationRate;
|
||||
const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate * urbanization : 0;
|
||||
return [rural, urban];
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) population value from map data
|
||||
function getFriendlyPopulation(i) {
|
||||
const [rural, urban] = getCellPopulation(i);
|
||||
return `${si(rural + urban)} (${si(rural)} rural, urban ${si(urban)})`;
|
||||
}
|
||||
|
||||
function getPopulationTip(i) {
|
||||
const [rural, urban] = getCellPopulation(i);
|
||||
return `Cell population: ${si(rural + urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`;
|
||||
}
|
||||
|
||||
function highlightEmblemElement(type, el) {
|
||||
const i = el.i,
|
||||
cells = pack.cells;
|
||||
const animation = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||
|
||||
if (type === 'burg') {
|
||||
const {x, y} = el;
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.append('circle')
|
||||
.attr('cx', x)
|
||||
.attr('cy', y)
|
||||
.attr('r', 0)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#d0240f')
|
||||
.attr('stroke-width', 1)
|
||||
.attr('opacity', 1)
|
||||
.transition(animation)
|
||||
.attr('r', 20)
|
||||
.attr('opacity', 0.1)
|
||||
.attr('stroke-width', 0)
|
||||
=======
|
||||
.append("circle")
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("r", 0)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "#d0240f")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.transition(animation)
|
||||
.attr("r", 20)
|
||||
.attr("opacity", 0.1)
|
||||
.attr("stroke-width", 0)
|
||||
>>>>>>> master
|
||||
.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const [x, y] = el.pole || pack.cells.p[el.center];
|
||||
const obj = type === 'state' ? cells.state : cells.province;
|
||||
const borderCells = cells.i.filter((id) => obj[id] === i && cells.c[id].some((n) => obj[n] !== i));
|
||||
const data = Array.from(borderCells)
|
||||
.filter((c, i) => !(i % 2))
|
||||
.map((i) => cells.p[i])
|
||||
.map((i) => [i[0], i[1], Math.hypot(i[0] - x, i[1] - y)]);
|
||||
|
||||
debug
|
||||
.selectAll('line')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('x1', x)
|
||||
.attr('y1', y)
|
||||
.attr('x2', (d) => d[0])
|
||||
.attr('y2', (d) => d[1])
|
||||
.attr('stroke', '#d0240f')
|
||||
.attr('stroke-width', 0.5)
|
||||
.attr('opacity', 0.2)
|
||||
.attr('stroke-dashoffset', (d) => d[2])
|
||||
.attr('stroke-dasharray', (d) => d[2])
|
||||
.transition(animation)
|
||||
.attr('stroke-dashoffset', 0)
|
||||
.attr('opacity', 1)
|
||||
.transition(animation)
|
||||
.delay(1000)
|
||||
.attr('stroke-dashoffset', (d) => d[2])
|
||||
.attr('opacity', 0)
|
||||
.remove();
|
||||
}
|
||||
|
||||
// assign lock behavior
|
||||
document.querySelectorAll('[data-locked]').forEach(function (e) {
|
||||
e.addEventListener('mouseover', function (event) {
|
||||
if (this.className === 'icon-lock') tip('Click to unlock the option and allow it to be randomized on new map generation');
|
||||
else tip('Click to lock the option and always use the current value on new map generation');
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
e.addEventListener('click', function () {
|
||||
const id = this.id.slice(5);
|
||||
if (this.className === 'icon-lock') unlock(id);
|
||||
else lock(id);
|
||||
});
|
||||
});
|
||||
|
||||
// lock option
|
||||
function lock(id) {
|
||||
const input = document.querySelector("[data-stored='" + id + "']");
|
||||
if (input) localStorage.setItem(id, input.value);
|
||||
const el = document.getElementById('lock_' + id);
|
||||
if (!el) return;
|
||||
el.dataset.locked = 1;
|
||||
el.className = 'icon-lock';
|
||||
}
|
||||
|
||||
// unlock option
|
||||
function unlock(id) {
|
||||
localStorage.removeItem(id);
|
||||
const el = document.getElementById('lock_' + id);
|
||||
if (!el) return;
|
||||
el.dataset.locked = 0;
|
||||
el.className = 'icon-lock-open';
|
||||
}
|
||||
|
||||
// check if option is locked
|
||||
function locked(id) {
|
||||
const lockEl = document.getElementById('lock_' + id);
|
||||
return lockEl.dataset.locked == 1;
|
||||
}
|
||||
|
||||
// check if option is stored in localStorage
|
||||
function stored(option) {
|
||||
return localStorage.getItem(option);
|
||||
}
|
||||
|
||||
// assign skeaker behaviour
|
||||
Array.from(document.getElementsByClassName('speaker')).forEach((el) => {
|
||||
const input = el.previousElementSibling;
|
||||
el.addEventListener('click', () => speak(input.value));
|
||||
});
|
||||
|
||||
function speak(text) {
|
||||
const speaker = new SpeechSynthesisUtterance(text);
|
||||
const voices = speechSynthesis.getVoices();
|
||||
if (voices.length) {
|
||||
const voiceId = +document.getElementById('speakerVoice').value;
|
||||
speaker.voice = voices[voiceId];
|
||||
}
|
||||
speechSynthesis.speak(speaker);
|
||||
}
|
||||
|
||||
// apply drop-down menu option. If the value is not in options, add it
|
||||
function applyOption(select, id, name = id) {
|
||||
const custom = !Array.from(select.options).some((o) => o.value == id);
|
||||
if (custom) select.options.add(new Option(name, id));
|
||||
select.value = id;
|
||||
}
|
||||
|
||||
// show info about the generator in a popup
|
||||
function showInfo() {
|
||||
const Discord = link('https://discordapp.com/invite/X7E84HU', 'Discord');
|
||||
const Reddit = link('https://www.reddit.com/r/FantasyMapGenerator', 'Reddit');
|
||||
const Patreon = link('https://www.patreon.com/azgaar', 'Patreon');
|
||||
const Trello = link('https://trello.com/b/7x832DG4/fantasy-map-generator', 'Trello');
|
||||
const Armoria = link('https://azgaar.github.io/Armoria', 'Armoria');
|
||||
|
||||
const QuickStart = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial', 'Quick start tutorial');
|
||||
const QAA = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A', 'Q&A page');
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
<b>Fantasy Map Generator</b> (FMG) is an open-source application, it means the code is published an anyone can use it.
|
||||
In case of FMG is also means that you own all created maps and can use them as you wish, you can even sell them.
|
||||
|
||||
<p>The development is supported by community, you can donate on ${Patreon}.
|
||||
You can also help creating overviews, tutorials and spreding the word about the Generator.</p>
|
||||
|
||||
<p>The best way to get help is to contact the community on ${Discord} and ${Reddit}.
|
||||
Before asking questions, please check out the ${QuickStart} and the ${QAA}.</p>
|
||||
|
||||
<p>Track the development process on ${Trello}.</p>
|
||||
|
||||
<p>Check out our new project: ${Armoria}, heraldry generator and editor.</p>
|
||||
|
||||
<b>Links:</b>
|
||||
<ul style="columns:2">
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator', 'GitHub repository')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE', 'License')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog', 'Changelog')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys', 'Hotkeys')}</li>
|
||||
</ul>`;
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: document.title,
|
||||
width: '28em',
|
||||
buttons: {
|
||||
OK: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
|
||||
// prevent default browser behavior for FMG-used hotkeys
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallow alt key combinations
|
||||
if (event.ctrlKey && event.code === 'KeyS') event.preventDefault(); // disallow CTRL + C
|
||||
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
|
||||
});
|
||||
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener('keyup', (event) => {
|
||||
if (!window.closeDialogs) return; // not all modules are loaded
|
||||
const canvas3d = document.getElementById('canvas3d'); // check if 3d mode is active
|
||||
const active = document.activeElement.tagName;
|
||||
if (active === 'INPUT' || active === 'SELECT' || active === 'TEXTAREA') return; // don't trigger if user inputs a text
|
||||
if (active === 'DIV' && document.activeElement.contentEditable === 'true') return; // don't trigger if user inputs a text
|
||||
event.stopPropagation();
|
||||
|
||||
const key = event.keyCode;
|
||||
const ctrl = event.ctrlKey || event.metaKey || key === 17;
|
||||
const shift = event.shiftKey || key === 16;
|
||||
const alt = event.altKey || key === 18;
|
||||
|
||||
if (key === 112) showInfo();
|
||||
// "F1" to show info
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for new map
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for a new map
|
||||
else if (key === 117) quickSave();
|
||||
// "F6" for quick save
|
||||
else if (key === 120) quickLoad();
|
||||
// "F9" for quick load
|
||||
else if (key === 9) toggleOptions(event);
|
||||
// Tab to toggle options
|
||||
else if (key === 27) {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
} // Escape to close all dialogs
|
||||
else if (key === 46) removeElementOnKey();
|
||||
// "Delete" to remove the selected element
|
||||
else if (key === 79 && canvas3d) toggle3dOptions();
|
||||
// "O" to toggle 3d options
|
||||
else if (ctrl && key === 81) toggleSaveReminder();
|
||||
// Ctrl + "Q" to toggle save reminder
|
||||
else if (ctrl && key === 83) saveMap();
|
||||
// Ctrl + "S" to save .map file
|
||||
else if (undo.offsetParent && ctrl && key === 90) undo.click();
|
||||
// Ctrl + "Z" to undo
|
||||
else if (redo.offsetParent && ctrl && key === 89) redo.click();
|
||||
// Ctrl + "Y" to redo
|
||||
else if (shift && key === 72) editHeightmap();
|
||||
// Shift + "H" to edit Heightmap
|
||||
else if (shift && key === 66) editBiomes();
|
||||
// Shift + "B" to edit Biomes
|
||||
else if (shift && key === 83) editStates();
|
||||
// Shift + "S" to edit States
|
||||
else if (shift && key === 80) editProvinces();
|
||||
// Shift + "P" to edit Provinces
|
||||
else if (shift && key === 68) editDiplomacy();
|
||||
// Shift + "D" to edit Diplomacy
|
||||
else if (shift && key === 67) editCultures();
|
||||
// Shift + "C" to edit Cultures
|
||||
else if (shift && key === 78) editNamesbase();
|
||||
// Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones();
|
||||
// Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions();
|
||||
// Shift + "R" to edit Religions
|
||||
else if (shift && key === 81) editResources();
|
||||
// Shift + "Q" to edit Resources
|
||||
else if (shift && key === 89) openEmblemEditor();
|
||||
// Shift + "Y" to edit Emblems
|
||||
else if (shift && key === 87) editUnits();
|
||||
// Shift + "W" to edit Units
|
||||
else if (shift && key === 79) editNotes();
|
||||
// Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs();
|
||||
// Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers();
|
||||
// Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 77) overviewMilitary();
|
||||
// Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails();
|
||||
// Shift + "E" to open Cell Details
|
||||
else if (shift && key === 49) toggleAddBurg();
|
||||
// Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel();
|
||||
// Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver();
|
||||
// Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute();
|
||||
// Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker();
|
||||
// Shift + "5" to click to add Marker
|
||||
else if (alt && key === 66) console.table(pack.burgs);
|
||||
// Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states);
|
||||
// Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures);
|
||||
// Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions);
|
||||
// Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features);
|
||||
// Alt + "F" to log features data
|
||||
else if (key === 88) toggleTexture();
|
||||
// "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight();
|
||||
// "H" to toggle Heightmap layer
|
||||
else if (key === 66) toggleBiomes();
|
||||
// "B" to toggle Biomes layer
|
||||
else if (key === 69) toggleCells();
|
||||
// "E" to toggle Cells layer
|
||||
else if (key === 71) toggleGrid();
|
||||
// "G" to toggle Grid layer
|
||||
else if (key === 79) toggleCoordinates();
|
||||
// "O" to toggle Coordinates layer
|
||||
else if (key === 87) toggleCompass();
|
||||
// "W" to toggle Compass Rose layer
|
||||
else if (key === 86) toggleRivers();
|
||||
// "V" to toggle Rivers layer
|
||||
else if (key === 70) toggleRelief();
|
||||
// "F" to toggle Relief icons layer
|
||||
else if (key === 67) toggleCultures();
|
||||
// "C" to toggle Cultures layer
|
||||
else if (key === 83) toggleStates();
|
||||
// "S" to toggle States layer
|
||||
else if (key === 80) toggleProvinces();
|
||||
// "P" to toggle Provinces layer
|
||||
else if (key === 90) toggleZones();
|
||||
// "Z" to toggle Zones
|
||||
else if (key === 68) toggleBorders();
|
||||
// "D" to toggle Borders layer
|
||||
else if (key === 82) toggleReligions();
|
||||
// "R" to toggle Religions layer
|
||||
else if (key === 85) toggleRoutes();
|
||||
// "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp();
|
||||
// "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation();
|
||||
// "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce();
|
||||
// "J" to toggle Ice layer
|
||||
else if (key === 65) togglePrec();
|
||||
// "A" to toggle Precipitation layer
|
||||
else if (key === 81) toggleResources();
|
||||
// "Q" to toggle Resources layer
|
||||
else if (key === 89) toggleEmblems();
|
||||
// "Y" to toggle Emblems layer
|
||||
else if (key === 76) toggleLabels();
|
||||
// "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons();
|
||||
// "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMilitary();
|
||||
// "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers();
|
||||
// "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers();
|
||||
// Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar();
|
||||
// Minus (-) to toggle Scale bar
|
||||
else if (key === 37) zoom.translateBy(svg, 10, 0);
|
||||
// Left to scroll map left
|
||||
else if (key === 39) zoom.translateBy(svg, -10, 0);
|
||||
// Right to scroll map right
|
||||
else if (key === 38) zoom.translateBy(svg, 0, 10);
|
||||
// Up to scroll map up
|
||||
else if (key === 40) zoom.translateBy(svg, 0, -10);
|
||||
// Up to scroll map up
|
||||
else if (key === 107 || key === 109) pressNumpadSign(key);
|
||||
// Numpad Plus/Minus to zoom map or change brush size
|
||||
else if (key === 48 || key === 96) resetZoom(1000);
|
||||
// 0 to reset zoom
|
||||
else if (key === 49 || key === 97) zoom.scaleTo(svg, 1);
|
||||
// 1 to zoom to 1
|
||||
else if (key === 50 || key === 98) zoom.scaleTo(svg, 2);
|
||||
// 2 to zoom to 2
|
||||
else if (key === 51 || key === 99) zoom.scaleTo(svg, 3);
|
||||
// 3 to zoom to 3
|
||||
else if (key === 52 || key === 100) zoom.scaleTo(svg, 4);
|
||||
// 4 to zoom to 4
|
||||
else if (key === 53 || key === 101) zoom.scaleTo(svg, 5);
|
||||
// 5 to zoom to 5
|
||||
else if (key === 54 || key === 102) zoom.scaleTo(svg, 6);
|
||||
// 6 to zoom to 6
|
||||
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7);
|
||||
// 7 to zoom to 7
|
||||
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8);
|
||||
// 8 to zoom to 8
|
||||
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9);
|
||||
// 9 to zoom to 9
|
||||
else if (ctrl) pressControl(); // Control to toggle mode
|
||||
});
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
// if brush sliders are displayed, decrease brush size
|
||||
let brush = null;
|
||||
const d = key === 107 ? 1 : -1;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById('brushRadius');
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById('biomesManuallyBrush');
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById('statesManuallyBrush');
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById('provincesManuallyBrush');
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById('culturesManuallyBrush');
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById('zonesBrush');
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById('religionsManuallyBrush');
|
||||
|
||||
if (brush) {
|
||||
const value = Math.max(Math.min(+brush.value + d, +brush.max), +brush.min);
|
||||
brush.value = document.getElementById(brush.id + 'Number').value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === 107 ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no, zoom map
|
||||
}
|
||||
|
||||
function pressControl() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains('pressed') ? zonesRemove.classList.remove('pressed') : zonesRemove.classList.add('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$('.dialog:visible .fastDelete').click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
=======
|
||||
>>>>>>> master
|
||||
|
|
@ -7,8 +7,8 @@ function editHeightmap() {
|
|||
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
||||
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
||||
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
||||
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
||||
<p>Check out ${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization', 'wiki')} for guidance.</p>`;
|
||||
<p>Please <span class="pseudoLink" onclick=dowloadMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
||||
<p style="margin-bottom: 0">Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
|
|
@ -222,7 +222,7 @@ function editHeightmap() {
|
|||
Lakes.generateName();
|
||||
|
||||
Military.generate();
|
||||
addMarkers();
|
||||
Markers.generate();
|
||||
addZones();
|
||||
TIME && console.timeEnd('regenerateErasedData');
|
||||
INFO && console.groupEnd('Edit Heightmap');
|
||||
|
|
@ -334,10 +334,10 @@ function editHeightmap() {
|
|||
|
||||
for (const i of pack.cells.i) {
|
||||
const g = pack.cells.g[i];
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
const isLand = pack.cells.h[i] >= 20;
|
||||
|
||||
// check biome
|
||||
pack.cells.biome[i] = land && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], pack.cells.h[i]);
|
||||
pack.cells.biome[i] = isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
|
||||
|
||||
// rivers data
|
||||
if (!erosionAllowed) {
|
||||
|
|
@ -346,7 +346,7 @@ function editHeightmap() {
|
|||
pack.cells.fl[i] = fl[g];
|
||||
}
|
||||
|
||||
if (!land) continue;
|
||||
if (!isLand) continue;
|
||||
pack.cells.culture[i] = culture[g];
|
||||
pack.cells.pop[i] = pop[g];
|
||||
pack.cells.road[i] = road[g];
|
||||
|
|
@ -614,7 +614,7 @@ function editHeightmap() {
|
|||
const interpolate = d3.interpolateRound(power, 1);
|
||||
const land = changeOnlyLand.checked;
|
||||
function lim(v) {
|
||||
return Math.max(Math.min(v, 100), land ? 20 : 0);
|
||||
return minmax(v, land ? 20 : 0, 100);
|
||||
}
|
||||
const h = grid.cells.h;
|
||||
|
||||
|
|
@ -626,6 +626,8 @@ function editHeightmap() {
|
|||
else if (brush === 'brushAlign') s.forEach((i) => (h[i] = lim(h[start])));
|
||||
else if (brush === 'brushSmooth') s.forEach((i) => (h[i] = rn((d3.mean(grid.cells.c[i].filter((i) => (land ? h[i] >= 20 : 1)).map((c) => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1)));
|
||||
else if (brush === 'brushDisrupt') s.forEach((i) => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
|
||||
i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1))
|
||||
);
|
||||
|
||||
mockHeightmapSelection(s);
|
||||
// updateHistory(); uncomment to update history every step
|
||||
|
|
@ -775,6 +777,7 @@ function editHeightmap() {
|
|||
const TempX = `<span>x:<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || '15-85'}></span>`;
|
||||
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3 || '40-50'}></span>`;
|
||||
const Count = `<span>n:<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || '1-2'}></span>`;
|
||||
}></span>`;
|
||||
const blob = `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
||||
|
||||
if (type === 'Hill' || type === 'Pit' || type === 'Range' || type === 'Trough') return blob;
|
||||
|
|
@ -792,6 +795,8 @@ function editHeightmap() {
|
|||
} min=0 max=10 step=.1></span></div>`;
|
||||
if (type === 'Smooth')
|
||||
return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${count || 2}></span></div>`;
|
||||
count || 2
|
||||
}></span></div>`;
|
||||
}
|
||||
|
||||
function setRange(event) {
|
||||
|
|
@ -853,31 +858,27 @@ function editHeightmap() {
|
|||
const steps = body.querySelectorAll('#templateBody > div');
|
||||
if (!steps.length) return;
|
||||
|
||||
const {addHill, addPit, addRange, addTrough, addStrait, modify, smooth} = HeightmapGenerator;
|
||||
grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights
|
||||
|
||||
for (const s of steps) {
|
||||
if (s.style.opacity == 0.5) continue;
|
||||
const type = s.dataset.type;
|
||||
for (const step of steps) {
|
||||
if (step.style.opacity === "0.5") continue;
|
||||
const type = step.dataset.type;
|
||||
|
||||
const elCount = s.querySelector('.templateCount') || '';
|
||||
const elHeight = s.querySelector('.templateHeight') || '';
|
||||
const count = step.querySelector(".templateCount")?.value || "";
|
||||
const height = step.querySelector(".templateHeight")?.value || "";
|
||||
const dist = step.querySelector(".templateDist")?.value || null;
|
||||
const x = step.querySelector(".templateX")?.value || null;
|
||||
const y = step.querySelector(".templateY")?.value || null;
|
||||
|
||||
const elDist = s.querySelector('.templateDist');
|
||||
const dist = elDist ? elDist.value : null;
|
||||
|
||||
const templateX = s.querySelector('.templateX');
|
||||
const x = templateX ? templateX.value : null;
|
||||
const templateY = s.querySelector('.templateY');
|
||||
const y = templateY ? templateY.value : null;
|
||||
|
||||
if (type === 'Hill') HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Pit') HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Range') HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Trough') HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Strait') HeightmapGenerator.addStrait(elCount.value, dist);
|
||||
else if (type === 'Add') HeightmapGenerator.modify(dist, +elCount.value, 1);
|
||||
else if (type === 'Multiply') HeightmapGenerator.modify(dist, 0, +elCount.value);
|
||||
else if (type === 'Smooth') HeightmapGenerator.smooth(+elCount.value);
|
||||
if (type === "Hill") addHill(count, height, x, y);
|
||||
else if (type === "Pit") addPit(count, height, x, y);
|
||||
else if (type === "Range") addRange(count, height, x, y);
|
||||
else if (type === "Trough") addTrough(count, height, x, y);
|
||||
else if (type === "Strait") addStrait(count, dist);
|
||||
else if (type === "Add") modify(dist, +count, 1);
|
||||
else if (type === "Multiply") modify(dist, 0, +count);
|
||||
else if (type === "Smooth") smooth(+count);
|
||||
|
||||
updateHistory('noStat'); // update history every step
|
||||
}
|
||||
|
|
@ -896,17 +897,13 @@ function editHeightmap() {
|
|||
|
||||
let data = '';
|
||||
for (const s of steps) {
|
||||
if (s.style.opacity == 0.5) continue;
|
||||
const type = s.getAttribute('data-type');
|
||||
if (s.style.opacity === "0.5") continue;
|
||||
|
||||
const elCount = s.querySelector('.templateCount');
|
||||
const count = elCount ? elCount.value : '0';
|
||||
const elHeight = s.querySelector('.templateHeight');
|
||||
const elDist = s.querySelector('.templateDist');
|
||||
const arg3 = elHeight ? elHeight.value : elDist ? elDist.value : '0';
|
||||
const templateX = s.querySelector('.templateX');
|
||||
const x = templateX ? templateX.value : '0';
|
||||
const templateY = s.querySelector('.templateY');
|
||||
const y = templateY ? templateY.value : '0';
|
||||
const count = s.querySelector(".templateCount")?.value || "0";
|
||||
const arg3 = s.querySelector(".templateHeight")?.value || s.querySelector(".templateDist")?.value || "0";
|
||||
const x = s.querySelector(".templateX")?.value || "0";
|
||||
const y = s.querySelector(".templateY")?.value || "0";
|
||||
data += `${type} ${count} ${arg3} ${x} ${y}\r\n`;
|
||||
}
|
||||
|
||||
|
|
@ -1194,10 +1191,14 @@ function editHeightmap() {
|
|||
}
|
||||
|
||||
function setConvertColorsNumber() {
|
||||
prompt(`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`, {default: +convertColors.value, step: 1, min: 3, max: 255}, (number) => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
});
|
||||
prompt(
|
||||
`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`,
|
||||
{default: +convertColors.value, step: 1, min: 3, max: 255},
|
||||
number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function setOverlayOpacity(v) {
|
||||
|
|
|
|||
1452
modules/ui/heightmap-editor.js.orig
Normal file
1452
modules/ui/heightmap-editor.js.orig
Normal file
File diff suppressed because it is too large
Load diff
153
modules/ui/hotkeys.js
Normal file
153
modules/ui/hotkeys.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
'use strict';
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
document.addEventListener('keyup', handleKeyup);
|
||||
|
||||
function handleKeydown(event) {
|
||||
const {code, ctrlKey, altKey} = event;
|
||||
if (altKey && !ctrlKey) event.preventDefault(); // disallow alt key combinations
|
||||
if (ctrlKey && ['KeyS', 'KeyC'].includes(code)) event.preventDefault(); // disallow CTRL + S and CTRL + C
|
||||
if (['F1', 'F2', 'F6', 'F9', 'Tab'].includes(code)) event.preventDefault(); // disallow default Fn and Tab
|
||||
}
|
||||
|
||||
function handleKeyup(event) {
|
||||
if (!modules.editors) return; // if editors are not loaded, do nothing
|
||||
|
||||
const {tagName, contentEditable} = document.activeElement;
|
||||
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(tagName)) return; // don't trigger if user inputs text
|
||||
if (tagName === 'DIV' && contentEditable === 'true') return; // don't trigger if user inputs a text
|
||||
if (document.getSelection().toString()) return; // don't trigger if user selects text
|
||||
event.stopPropagation();
|
||||
|
||||
const {code, key, ctrlKey, metaKey, shiftKey, altKey} = event;
|
||||
const ctrl = ctrlKey || metaKey || key === 'Control';
|
||||
const shift = shiftKey || key === 'Shift';
|
||||
const alt = altKey || key === 'Alt';
|
||||
|
||||
if (code === 'F1') showInfo();
|
||||
else if (code === 'F2') regeneratePrompt('hotkey');
|
||||
else if (code === 'F6') quickSave();
|
||||
else if (code === 'F9') quickLoad();
|
||||
else if (code === 'Tab') toggleOptions(event);
|
||||
else if (code === 'Escape') closeAllDialogs();
|
||||
else if (code === 'Delete') removeElementOnKey();
|
||||
else if (code === 'KeyO' && document.getElementById('canvas3d')) toggle3dOptions();
|
||||
else if (ctrl && code === 'KeyQ') toggleSaveReminder();
|
||||
else if (ctrl && code === 'KeyS') dowloadMap();
|
||||
else if (ctrl && code === 'KeyC') saveToDropbox();
|
||||
else if (ctrl && code === 'KeyZ' && undo.offsetParent) undo.click();
|
||||
else if (ctrl && code === 'KeyY' && redo.offsetParent) redo.click();
|
||||
else if (shift && code === 'KeyH') editHeightmap();
|
||||
else if (shift && code === 'KeyB') editBiomes();
|
||||
else if (shift && code === 'KeyS') editStates();
|
||||
else if (shift && code === 'KeyP') editProvinces();
|
||||
else if (shift && code === 'KeyD') editDiplomacy();
|
||||
else if (shift && code === 'KeyC') editCultures();
|
||||
else if (shift && code === 'KeyN') editNamesbase();
|
||||
else if (shift && code === 'KeyZ') editZones();
|
||||
else if (shift && code === 'KeyR') editReligions();
|
||||
else if (shift && code === 'KeyY') openEmblemEditor();
|
||||
else if (shift && code === 'KeyQ') editUnits();
|
||||
else if (shift && code === 'KeyO') editNotes();
|
||||
else if (shift && code === 'KeyT') overviewBurgs();
|
||||
else if (shift && code === 'KeyV') overviewRivers();
|
||||
else if (shift && code === 'KeyM') overviewMilitary();
|
||||
else if (shift && code === 'KeyK') overviewMarkers();
|
||||
else if (shift && code === 'KeyE') viewCellDetails();
|
||||
else if (key === '!') toggleAddBurg();
|
||||
else if (key === '@') toggleAddLabel();
|
||||
else if (key === '#') toggleAddRiver();
|
||||
else if (key === '$') toggleAddRoute();
|
||||
else if (key === '%') toggleAddMarker();
|
||||
else if (alt && code === 'KeyB') console.table(pack.burgs);
|
||||
else if (alt && code === 'KeyS') console.table(pack.states);
|
||||
else if (alt && code === 'KeyC') console.table(pack.cultures);
|
||||
else if (alt && code === 'KeyR') console.table(pack.religions);
|
||||
else if (alt && code === 'KeyF') console.table(pack.features);
|
||||
else if (code === 'KeyX') toggleTexture();
|
||||
else if (code === 'KeyH') toggleHeight();
|
||||
else if (code === 'KeyB') toggleBiomes();
|
||||
else if (code === 'KeyE') toggleCells();
|
||||
else if (code === 'KeyG') toggleGrid();
|
||||
else if (code === 'KeyO') toggleCoordinates();
|
||||
else if (code === 'KeyW') toggleCompass();
|
||||
else if (code === 'KeyV') toggleRivers();
|
||||
else if (code === 'KeyF') toggleRelief();
|
||||
else if (code === 'KeyC') toggleCultures();
|
||||
else if (code === 'KeyS') toggleStates();
|
||||
else if (code === 'KeyP') toggleProvinces();
|
||||
else if (code === 'KeyZ') toggleZones();
|
||||
else if (code === 'KeyD') toggleBorders();
|
||||
else if (code === 'KeyR') toggleReligions();
|
||||
else if (code === 'KeyU') toggleRoutes();
|
||||
else if (code === 'KeyT') toggleTemp();
|
||||
else if (code === 'KeyN') togglePopulation();
|
||||
else if (code === 'KeyJ') toggleIce();
|
||||
else if (code === 'KeyA') togglePrec();
|
||||
else if (code === 'KeyY') toggleEmblems();
|
||||
else if (code === 'KeyL') toggleLabels();
|
||||
else if (code === 'KeyI') toggleIcons();
|
||||
else if (code === 'KeyM') toggleMilitary();
|
||||
else if (code === 'KeyK') toggleMarkers();
|
||||
else if (code === 'Equal') toggleRulers();
|
||||
else if (code === 'Slash') toggleScaleBar();
|
||||
else if (code === 'ArrowLeft') zoom.translateBy(svg, 10, 0);
|
||||
else if (code === 'ArrowRight') zoom.translateBy(svg, -10, 0);
|
||||
else if (code === 'ArrowUp') zoom.translateBy(svg, 0, 10);
|
||||
else if (code === 'ArrowDown') zoom.translateBy(svg, 0, -10);
|
||||
else if (key === '+' || key === '-') pressNumpadSign(key);
|
||||
else if (key === '0') resetZoom(1000);
|
||||
else if (key === '1') zoom.scaleTo(svg, 1);
|
||||
else if (key === '2') zoom.scaleTo(svg, 2);
|
||||
else if (key === '3') zoom.scaleTo(svg, 3);
|
||||
else if (key === '4') zoom.scaleTo(svg, 4);
|
||||
else if (key === '5') zoom.scaleTo(svg, 5);
|
||||
else if (key === '6') zoom.scaleTo(svg, 6);
|
||||
else if (key === '7') zoom.scaleTo(svg, 7);
|
||||
else if (key === '8') zoom.scaleTo(svg, 8);
|
||||
else if (key === '9') zoom.scaleTo(svg, 9);
|
||||
else if (ctrl) toggleMode();
|
||||
}
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
const change = key === '+' ? 1 : -1;
|
||||
let brush = null;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById('brushRadius');
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById('biomesManuallyBrush');
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById('statesManuallyBrush');
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById('provincesManuallyBrush');
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById('culturesManuallyBrush');
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById('zonesBrush');
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById('religionsManuallyBrush');
|
||||
|
||||
if (brush) {
|
||||
const value = minmax(+brush.value + change, +brush.min, +brush.max);
|
||||
brush.value = document.getElementById(brush.id + 'Number').value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === '+' ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no brush elements displayed, zoom map
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains('pressed') ? zonesRemove.classList.remove('pressed') : zonesRemove.classList.add('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
function removeElementOnKey() {
|
||||
const fastDelete = Array.from(document.querySelectorAll("[role='dialog'] .fastDelete")).find((dialog) => dialog.style.display !== 'none');
|
||||
if (fastDelete) fastDelete.click();
|
||||
|
||||
const visibleDialogs = Array.from(document.querySelectorAll("[role='dialog']")).filter((dialog) => dialog.style.display !== 'none');
|
||||
if (!visibleDialogs.length) return;
|
||||
|
||||
visibleDialogs.forEach((dialog) => dialog.querySelectorAll('button').forEach((button) => button.textContent === 'Remove' && button.click()));
|
||||
}
|
||||
|
||||
function closeAllDialogs() {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
}
|
||||
|
|
@ -51,8 +51,8 @@ function editLabel() {
|
|||
|
||||
function showEditorTips() {
|
||||
showMainTip();
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr('id')) tip('Drag to shift the label');
|
||||
else if (d3.event.target.parentNode.id === 'controlPoints') {
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr("id")) tip("Drag to shift the label");
|
||||
else if (d3.event.target.parentNode.id === "controlPoints") {
|
||||
if (d3.event.target.tagName === 'circle') tip('Drag to move, click to delete the control point');
|
||||
if (d3.event.target.tagName === 'path') tip('Click to add a control point');
|
||||
}
|
||||
|
|
@ -253,7 +253,13 @@ function editLabel() {
|
|||
const message = `Are you sure you want to remove ${basic ? 'all elements in the group' : 'the entire label group'}?<br><br>Labels to be removed: ${count}`;
|
||||
const onConfirm = () => {
|
||||
$('#labelEditor').dialog('close');
|
||||
hideGroupSection();
|
||||
resizable: false,
|
||||
title: "Remove route group",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
$("#labelEditor").dialog("close");
|
||||
hideGroupSection();
|
||||
labels
|
||||
.select('#' + group)
|
||||
.selectAll('text')
|
||||
|
|
@ -357,10 +363,13 @@ function editLabel() {
|
|||
const message = 'Are you sure you want to remove the label? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
defs.select('#textPath_' + elSelected.attr('id')).remove();
|
||||
title: "Remove label",
|
||||
elSelected.remove();
|
||||
$('#labelEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove label', message, confirm: 'Remove', onConfirm});
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
|
||||
function closeLabelEditor() {
|
||||
|
|
|
|||
549
modules/ui/labels-editor.js.orig
Normal file
549
modules/ui/labels-editor.js.orig
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
'use strict';
|
||||
function editLabel() {
|
||||
if (customization) return;
|
||||
closeDialogs();
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
|
||||
const tspan = d3.event.target;
|
||||
const textPath = tspan.parentNode;
|
||||
const text = textPath.parentNode;
|
||||
<<<<<<< HEAD
|
||||
elSelected = d3.select(text).call(d3.drag().on('start', dragLabel)).classed('draggable', true);
|
||||
viewbox.on('touchmove mousemove', showEditorTips);
|
||||
|
||||
$('#labelEditor').dialog({
|
||||
title: 'Edit Label',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'center top+10', at: 'bottom', of: text, collision: 'fit'},
|
||||
=======
|
||||
elSelected = d3.select(text).call(d3.drag().on("start", dragLabel)).classed("draggable", true);
|
||||
viewbox.on("touchmove mousemove", showEditorTips);
|
||||
|
||||
$("#labelEditor").dialog({
|
||||
title: "Edit Label",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: "center top+10", at: "bottom", of: text, collision: "fit"},
|
||||
>>>>>>> master
|
||||
close: closeLabelEditor
|
||||
});
|
||||
|
||||
drawControlPointsAndLine();
|
||||
selectLabelGroup(text);
|
||||
updateValues(textPath);
|
||||
|
||||
if (modules.editLabel) return;
|
||||
modules.editLabel = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('labelGroupShow').addEventListener('click', showGroupSection);
|
||||
document.getElementById('labelGroupHide').addEventListener('click', hideGroupSection);
|
||||
document.getElementById('labelGroupSelect').addEventListener('click', changeGroup);
|
||||
document.getElementById('labelGroupInput').addEventListener('change', createNewGroup);
|
||||
document.getElementById('labelGroupNew').addEventListener('click', toggleNewGroupInput);
|
||||
document.getElementById('labelGroupRemove').addEventListener('click', removeLabelsGroup);
|
||||
|
||||
document.getElementById('labelTextShow').addEventListener('click', showTextSection);
|
||||
document.getElementById('labelTextHide').addEventListener('click', hideTextSection);
|
||||
document.getElementById('labelText').addEventListener('input', changeText);
|
||||
document.getElementById('labelTextRandom').addEventListener('click', generateRandomName);
|
||||
|
||||
document.getElementById('labelEditStyle').addEventListener('click', editGroupStyle);
|
||||
|
||||
document.getElementById('labelSizeShow').addEventListener('click', showSizeSection);
|
||||
document.getElementById('labelSizeHide').addEventListener('click', hideSizeSection);
|
||||
document.getElementById('labelStartOffset').addEventListener('input', changeStartOffset);
|
||||
document.getElementById('labelRelativeSize').addEventListener('input', changeRelativeSize);
|
||||
|
||||
document.getElementById('labelAlign').addEventListener('click', editLabelAlign);
|
||||
document.getElementById('labelLegend').addEventListener('click', editLabelLegend);
|
||||
document.getElementById('labelRemoveSingle').addEventListener('click', removeLabel);
|
||||
|
||||
function showEditorTips() {
|
||||
showMainTip();
|
||||
<<<<<<< HEAD
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr('id')) tip('Drag to shift the label');
|
||||
else if (d3.event.target.parentNode.id === 'controlPoints') {
|
||||
if (d3.event.target.tagName === 'circle') tip('Drag to move, click to delete the control point');
|
||||
if (d3.event.target.tagName === 'path') tip('Click to add a control point');
|
||||
=======
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr("id")) tip("Drag to shift the label");
|
||||
else if (d3.event.target.parentNode.id === "controlPoints") {
|
||||
if (d3.event.target.tagName === "circle") tip("Drag to move, click to delete the control point");
|
||||
if (d3.event.target.tagName === "path") tip("Click to add a control point");
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
function selectLabelGroup(text) {
|
||||
const group = text.parentNode.id;
|
||||
const select = document.getElementById('labelGroupSelect');
|
||||
select.options.length = 0; // remove all options
|
||||
|
||||
<<<<<<< HEAD
|
||||
labels.selectAll(':scope > g').each(function () {
|
||||
if (this.id === 'burgLabels') return;
|
||||
=======
|
||||
labels.selectAll(":scope > g").each(function () {
|
||||
if (this.id === "burgLabels") return;
|
||||
>>>>>>> master
|
||||
select.options.add(new Option(this.id, this.id, false, this.id === group));
|
||||
});
|
||||
}
|
||||
|
||||
function updateValues(textPath) {
|
||||
document.getElementById('labelText').value = [...textPath.querySelectorAll('tspan')].map((tspan) => tspan.textContent).join('|');
|
||||
document.getElementById('labelStartOffset').value = parseFloat(textPath.getAttribute('startOffset'));
|
||||
document.getElementById('labelRelativeSize').value = parseFloat(textPath.getAttribute('font-size'));
|
||||
}
|
||||
|
||||
function drawControlPointsAndLine() {
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.append('g').attr('id', 'controlPoints').attr('transform', elSelected.attr('transform'));
|
||||
const path = document.getElementById('textPath_' + elSelected.attr('id'));
|
||||
debug.select('#controlPoints').append('path').attr('d', path.getAttribute('d')).on('click', addInterimControlPoint);
|
||||
const l = path.getTotalLength();
|
||||
if (!l) return;
|
||||
const increment = l / Math.max(Math.ceil(l / 200), 2);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
addControlPoint(path.getPointAtLength(i));
|
||||
}
|
||||
}
|
||||
|
||||
function addControlPoint(point) {
|
||||
<<<<<<< HEAD
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.append('circle')
|
||||
.attr('cx', point.x)
|
||||
.attr('cy', point.y)
|
||||
.attr('r', 2.5)
|
||||
.attr('stroke-width', 0.8)
|
||||
.call(d3.drag().on('drag', dragControlPoint))
|
||||
.on('click', clickControlPoint);
|
||||
=======
|
||||
debug.select("#controlPoints").append("circle").attr("cx", point.x).attr("cy", point.y).attr("r", 2.5).attr("stroke-width", 0.8).call(d3.drag().on("drag", dragControlPoint)).on("click", clickControlPoint);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
this.setAttribute('cx', d3.event.x);
|
||||
this.setAttribute('cy', d3.event.y);
|
||||
redrawLabelPath();
|
||||
}
|
||||
|
||||
function redrawLabelPath() {
|
||||
const path = document.getElementById('textPath_' + elSelected.attr('id'));
|
||||
lineGen.curve(d3.curveBundle.beta(1));
|
||||
const points = [];
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
points.push([this.getAttribute('cx'), this.getAttribute('cy')]);
|
||||
=======
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
|
||||
>>>>>>> master
|
||||
});
|
||||
const d = round(lineGen(points));
|
||||
path.setAttribute('d', d);
|
||||
debug.select('#controlPoints > path').attr('d', d);
|
||||
}
|
||||
|
||||
function clickControlPoint() {
|
||||
this.remove();
|
||||
redrawLabelPath();
|
||||
}
|
||||
|
||||
function addInterimControlPoint() {
|
||||
const point = d3.mouse(this);
|
||||
|
||||
const dists = [];
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
const x = +this.getAttribute('cx');
|
||||
const y = +this.getAttribute('cy');
|
||||
=======
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
const x = +this.getAttribute("cx");
|
||||
const y = +this.getAttribute("cy");
|
||||
>>>>>>> master
|
||||
dists.push((point[0] - x) ** 2 + (point[1] - y) ** 2);
|
||||
});
|
||||
|
||||
let index = dists.length;
|
||||
if (dists.length > 1) {
|
||||
const sorted = dists.slice(0).sort((a, b) => a - b);
|
||||
const closest = dists.indexOf(sorted[0]);
|
||||
const next = dists.indexOf(sorted[1]);
|
||||
if (closest <= next) index = closest + 1;
|
||||
else index = next + 1;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
const before = ':nth-child(' + (index + 2) + ')';
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.insert('circle', before)
|
||||
.attr('cx', point[0])
|
||||
.attr('cy', point[1])
|
||||
.attr('r', 2.5)
|
||||
.attr('stroke-width', 0.8)
|
||||
.call(d3.drag().on('drag', dragControlPoint))
|
||||
.on('click', clickControlPoint);
|
||||
=======
|
||||
const before = ":nth-child(" + (index + 2) + ")";
|
||||
debug.select("#controlPoints").insert("circle", before).attr("cx", point[0]).attr("cy", point[1]).attr("r", 2.5).attr("stroke-width", 0.8).call(d3.drag().on("drag", dragControlPoint)).on("click", clickControlPoint);
|
||||
>>>>>>> master
|
||||
|
||||
redrawLabelPath();
|
||||
}
|
||||
|
||||
function dragLabel() {
|
||||
<<<<<<< HEAD
|
||||
const tr = parseTransform(elSelected.attr('transform'));
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const x = d3.event.x,
|
||||
y = d3.event.y;
|
||||
const transform = `translate(${dx + x},${dy + y})`;
|
||||
elSelected.attr('transform', transform);
|
||||
debug.select('#controlPoints').attr('transform', transform);
|
||||
=======
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
const x = d3.event.x,
|
||||
y = d3.event.y;
|
||||
const transform = `translate(${dx + x},${dy + y})`;
|
||||
elSelected.attr("transform", transform);
|
||||
debug.select("#controlPoints").attr("transform", transform);
|
||||
>>>>>>> master
|
||||
});
|
||||
}
|
||||
|
||||
function showGroupSection() {
|
||||
<<<<<<< HEAD
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('labelGroupSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('labelGroupSection').style.display = 'none';
|
||||
document.getElementById('labelGroupInput').style.display = 'none';
|
||||
document.getElementById('labelGroupInput').value = '';
|
||||
document.getElementById('labelGroupSelect').style.display = 'inline-block';
|
||||
=======
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelGroupSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelGroupSection").style.display = "none";
|
||||
document.getElementById("labelGroupInput").style.display = "none";
|
||||
document.getElementById("labelGroupInput").value = "";
|
||||
document.getElementById("labelGroupSelect").style.display = "inline-block";
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
document.getElementById(this.value).appendChild(elSelected.node());
|
||||
}
|
||||
|
||||
function toggleNewGroupInput() {
|
||||
if (labelGroupInput.style.display === 'none') {
|
||||
labelGroupInput.style.display = 'inline-block';
|
||||
labelGroupInput.focus();
|
||||
labelGroupSelect.style.display = 'none';
|
||||
} else {
|
||||
<<<<<<< HEAD
|
||||
labelGroupInput.style.display = 'none';
|
||||
labelGroupSelect.style.display = 'inline-block';
|
||||
=======
|
||||
labelGroupInput.style.display = "none";
|
||||
labelGroupSelect.style.display = "inline-block";
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {
|
||||
<<<<<<< HEAD
|
||||
tip('Please provide a valid group name');
|
||||
=======
|
||||
tip("Please provide a valid group name");
|
||||
>>>>>>> master
|
||||
return;
|
||||
}
|
||||
const group = this.value
|
||||
.toLowerCase()
|
||||
<<<<<<< HEAD
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
=======
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
>>>>>>> master
|
||||
|
||||
if (document.getElementById(group)) {
|
||||
tip('Element with this id already exists. Please provide a unique name', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isFinite(+group.charAt(0))) {
|
||||
tip('Group name should start with a letter', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// just rename if only 1 element left
|
||||
const oldGroup = elSelected.node().parentNode;
|
||||
if (oldGroup !== 'states' && oldGroup !== 'addedLabels' && oldGroup.childElementCount === 1) {
|
||||
document.getElementById('labelGroupSelect').selectedOptions[0].remove();
|
||||
document.getElementById('labelGroupSelect').options.add(new Option(group, group, false, true));
|
||||
oldGroup.id = group;
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('labelGroupInput').value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const newGroup = elSelected.node().parentNode.cloneNode(false);
|
||||
document.getElementById('labels').appendChild(newGroup);
|
||||
newGroup.id = group;
|
||||
document.getElementById('labelGroupSelect').options.add(new Option(group, group, false, true));
|
||||
document.getElementById(group).appendChild(elSelected.node());
|
||||
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('labelGroupInput').value = '';
|
||||
}
|
||||
|
||||
function removeLabelsGroup() {
|
||||
const group = elSelected.node().parentNode.id;
|
||||
const basic = group === 'states' || group === 'addedLabels';
|
||||
const count = elSelected.node().parentNode.childElementCount;
|
||||
<<<<<<< HEAD
|
||||
|
||||
const message = `Are you sure you want to remove ${basic ? 'all elements in the group' : 'the entire label group'}?<br><br>Labels to be removed: ${count}`;
|
||||
const onConfirm = () => {
|
||||
$('#labelEditor').dialog('close');
|
||||
hideGroupSection();
|
||||
labels
|
||||
.select('#' + group)
|
||||
.selectAll('text')
|
||||
.each(function () {
|
||||
document.getElementById('textPath_' + this.id).remove();
|
||||
this.remove();
|
||||
});
|
||||
if (!basic) labels.select('#' + group).remove();
|
||||
};
|
||||
confirmationDialog({title: 'Remove label group', message, confirm: 'Remove', onConfirm});
|
||||
}
|
||||
|
||||
function showTextSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('labelTextSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideTextSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('labelTextSection').style.display = 'none';
|
||||
}
|
||||
|
||||
function changeText() {
|
||||
const input = document.getElementById('labelText').value;
|
||||
const el = elSelected.select('textPath').node();
|
||||
const example = d3.select(elSelected.node().parentNode).append('text').attr('x', 0).attr('x', 0).attr('font-size', el.getAttribute('font-size')).node();
|
||||
=======
|
||||
alertMessage.innerHTML = `Are you sure you want to remove
|
||||
${basic ? "all elements in the group" : "the entire label group"}?
|
||||
<br><br>Labels to be removed: ${count}`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove route group",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
$("#labelEditor").dialog("close");
|
||||
hideGroupSection();
|
||||
labels
|
||||
.select("#" + group)
|
||||
.selectAll("text")
|
||||
.each(function () {
|
||||
document.getElementById("textPath_" + this.id).remove();
|
||||
this.remove();
|
||||
});
|
||||
if (!basic) labels.select("#" + group).remove();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showTextSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelTextSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideTextSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelTextSection").style.display = "none";
|
||||
}
|
||||
|
||||
function changeText() {
|
||||
const input = document.getElementById("labelText").value;
|
||||
const el = elSelected.select("textPath").node();
|
||||
const example = d3.select(elSelected.node().parentNode).append("text").attr("x", 0).attr("x", 0).attr("font-size", el.getAttribute("font-size")).node();
|
||||
>>>>>>> master
|
||||
|
||||
const lines = input.split('|');
|
||||
const top = (lines.length - 1) / -2; // y offset
|
||||
const inner = lines
|
||||
.map((l, d) => {
|
||||
example.innerHTML = l;
|
||||
const left = example.getBBox().width / -2; // x offset
|
||||
return `<tspan x="${left}px" dy="${d ? 1 : top}em">${l}</tspan>`;
|
||||
})
|
||||
<<<<<<< HEAD
|
||||
.join('');
|
||||
=======
|
||||
.join("");
|
||||
>>>>>>> master
|
||||
|
||||
el.innerHTML = inner;
|
||||
example.remove();
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (elSelected.attr('id').slice(0, 10) === 'stateLabel') tip('Use States Editor to change an actual state name, not just a label', false, 'warning');
|
||||
}
|
||||
|
||||
function generateRandomName() {
|
||||
let name = '';
|
||||
if (elSelected.attr('id').slice(0, 10) === 'stateLabel') {
|
||||
const id = +elSelected.attr('id').slice(10);
|
||||
=======
|
||||
if (elSelected.attr("id").slice(0, 10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning");
|
||||
}
|
||||
|
||||
function generateRandomName() {
|
||||
let name = "";
|
||||
if (elSelected.attr("id").slice(0, 10) === "stateLabel") {
|
||||
const id = +elSelected.attr("id").slice(10);
|
||||
>>>>>>> master
|
||||
const culture = pack.states[id].culture;
|
||||
name = Names.getState(Names.getCulture(culture, 4, 7, ''), culture);
|
||||
} else {
|
||||
const box = elSelected.node().getBBox();
|
||||
const cell = findCell((box.x + box.width) / 2, (box.y + box.height) / 2);
|
||||
const culture = pack.cells.culture[cell];
|
||||
name = Names.getCulture(culture);
|
||||
}
|
||||
document.getElementById('labelText').value = name;
|
||||
changeText();
|
||||
}
|
||||
|
||||
function editGroupStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('labels', g);
|
||||
}
|
||||
|
||||
function showSizeSection() {
|
||||
<<<<<<< HEAD
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('labelSizeSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideSizeSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('labelSizeSection').style.display = 'none';
|
||||
=======
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelSizeSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideSizeSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelSizeSection").style.display = "none";
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function changeStartOffset() {
|
||||
elSelected.select('textPath').attr('startOffset', this.value + '%');
|
||||
tip('Label offset: ' + this.value + '%');
|
||||
}
|
||||
|
||||
function changeRelativeSize() {
|
||||
elSelected.select('textPath').attr('font-size', this.value + '%');
|
||||
tip('Label relative size: ' + this.value + '%');
|
||||
changeText();
|
||||
}
|
||||
|
||||
function editLabelAlign() {
|
||||
const bbox = elSelected.node().getBBox();
|
||||
const c = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
|
||||
<<<<<<< HEAD
|
||||
const path = defs.select('#textPath_' + elSelected.attr('id'));
|
||||
path.attr('d', `M${c[0] - bbox.width},${c[1]}h${bbox.width * 2}`);
|
||||
=======
|
||||
const path = defs.select("#textPath_" + elSelected.attr("id"));
|
||||
path.attr("d", `M${c[0] - bbox.width},${c[1]}h${bbox.width * 2}`);
|
||||
>>>>>>> master
|
||||
drawControlPointsAndLine();
|
||||
}
|
||||
|
||||
function editLabelLegend() {
|
||||
const id = elSelected.attr('id');
|
||||
const name = elSelected.text();
|
||||
editNotes(id, name);
|
||||
}
|
||||
|
||||
function removeLabel() {
|
||||
<<<<<<< HEAD
|
||||
const message = 'Are you sure you want to remove the label? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
defs.select('#textPath_' + elSelected.attr('id')).remove();
|
||||
elSelected.remove();
|
||||
$('#labelEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove label', message, confirm: 'Remove', onConfirm});
|
||||
=======
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the label?";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove label",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
defs.select("#textPath_" + elSelected.attr("id")).remove();
|
||||
elSelected.remove();
|
||||
$("#labelEditor").dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function closeLabelEditor() {
|
||||
debug.select('#controlPoints').remove();
|
||||
unselect();
|
||||
}
|
||||
}
|
||||
|
|
@ -48,8 +48,7 @@ function changePreset(preset) {
|
|||
.querySelectorAll('li')
|
||||
.forEach(function (e) {
|
||||
if (layers.includes(e.id) && !layerIsOn(e.id)) e.click();
|
||||
// turn on
|
||||
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off
|
||||
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click();
|
||||
});
|
||||
layersPreset.value = preset;
|
||||
localStorage.setItem('preset', preset);
|
||||
|
|
@ -122,6 +121,7 @@ function restoreLayers() {
|
|||
if (layerIsOn('toggleReligions')) drawReligions();
|
||||
if (layerIsOn('toggleIce')) drawIce();
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems();
|
||||
if (layerIsOn('toggleMarkers')) drawMarkers();
|
||||
|
||||
// some layers are rendered each time, remove them if they are not on
|
||||
if (!layerIsOn('toggleBorders')) borders.selectAll('path').remove();
|
||||
|
|
@ -1419,8 +1419,8 @@ function toggleTexture(event) {
|
|||
turnButtonOn('toggleTexture');
|
||||
// append default texture image selected by default. Don't append on load to not harm performance
|
||||
if (!texture.selectAll('*').size()) {
|
||||
const x = +styleTextureShiftX.value,
|
||||
y = +styleTextureShiftY.value;
|
||||
const x = +styleTextureShiftX.value;
|
||||
const y = +styleTextureShiftY.value;
|
||||
const image = texture
|
||||
.append('image')
|
||||
.attr('id', 'textureImage')
|
||||
|
|
@ -1430,16 +1430,13 @@ function toggleTexture(event) {
|
|||
.attr('height', graphHeight - y)
|
||||
.attr('xlink:href', getDefaultTexture())
|
||||
.attr('preserveAspectRatio', 'xMidYMid slice');
|
||||
if (styleTextureInput.value !== 'default') getBase64(styleTextureInput.value, (base64) => image.attr('xlink:href', base64));
|
||||
getBase64(styleTextureInput.value, (base64) => image.attr('xlink:href', base64));
|
||||
}
|
||||
$('#texture').fadeIn();
|
||||
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
|
||||
if (event && isCtrlClick(event)) editStyle('texture');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('texture');
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle('texture');
|
||||
$('#texture').fadeOut();
|
||||
turnButtonOff('toggleTexture');
|
||||
}
|
||||
|
|
@ -1459,14 +1456,16 @@ function toggleRivers(event) {
|
|||
|
||||
function drawRivers() {
|
||||
TIME && console.time('drawRivers');
|
||||
rivers.selectAll('*').remove();
|
||||
|
||||
const {addMeandering, getRiverPath} = Rivers;
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const riverPaths = pack.rivers.map((river) => {
|
||||
const meanderedPoints = addMeandering(river.cells, river.points);
|
||||
const widthFactor = river.widthFactor || 1;
|
||||
const startingWidth = river.sourceWidth || 0;
|
||||
const path = getRiverPath(meanderedPoints, widthFactor, startingWidth);
|
||||
return `<path id="river${river.i}" d="${path}"/>`;
|
||||
|
||||
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
|
||||
if (!cells || cells.length < 2) return;
|
||||
const meanderedPoints = addMeandering(cells, points);
|
||||
const path = getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
return `<path id="river${i}" d="${path}"/>`;
|
||||
});
|
||||
rivers.html(riverPaths.join(''));
|
||||
TIME && console.timeEnd('drawRivers');
|
||||
|
|
@ -1505,18 +1504,51 @@ function toggleMilitary() {
|
|||
function toggleMarkers(event) {
|
||||
if (!layerIsOn('toggleMarkers')) {
|
||||
turnButtonOn('toggleMarkers');
|
||||
$('#markers').fadeIn();
|
||||
drawMarkers();
|
||||
if (event && isCtrlClick(event)) editStyle('markers');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('markers');
|
||||
return;
|
||||
}
|
||||
$('#markers').fadeOut();
|
||||
if (event && isCtrlClick(event)) return editStyle('markers');
|
||||
markers.selectAll('*').remove();
|
||||
turnButtonOff('toggleMarkers');
|
||||
}
|
||||
}
|
||||
|
||||
function drawMarkers() {
|
||||
const rescale = +markers.attr('rescale');
|
||||
const pinned = +markers.attr('pinned');
|
||||
|
||||
const markersData = pinned ? pack.markers.filter(({pinned}) => pinned) : pack.markers;
|
||||
const html = markersData.map((marker) => drawMarker(marker, rescale));
|
||||
markers.html(html.join(''));
|
||||
}
|
||||
|
||||
const getPin = (shape = 'bubble', fill = '#fff', stroke = '#000') => {
|
||||
if (shape === 'bubble') return `<path d="M6,19 l9,10 L24,19" fill="${stroke}" stroke="none" /><circle cx="15" cy="15" r="10" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === 'pin') return `<path d="m 15,3 c -5.5,0 -9.7,4.09 -9.7,9.3 0,6.8 9.7,17 9.7,17 0,0 9.7,-10.2 9.7,-17 C 24.7,7.09 20.5,3 15,3 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === 'square') return `<path d="m 20,25 -5,4 -5,-4 z" fill="${stroke}"/><path d="M 5,5 H 25 V 25 H 5 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === 'squarish') return `<path d="m 5,5 h 20 v 20 h -6 l -4,4 -4,-4 H 5 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'diamond') return `<path d="M 2,15 15,1 28,15 15,29 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'hex') return `<path d="M 15,29 4.61,21 V 9 L 15,3 25.4,9 v 12 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'hexy') return `<path d="M 15,29 6,21 5,8 15,4 25,8 24,21 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'shieldy') return `<path d="M 15,29 6,21 5,7 c 0,0 5,-3 10,-3 5,0 10,3 10,3 l -1,14 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'shield') return `<path d="M 4.6,5.2 H 25 v 6.7 A 20.3,20.4 0 0 1 15,29 20.3,20.4 0 0 1 4.6,11.9 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'pentagon') return `<path d="M 4,16 9,4 h 12 l 5,12 -11,13 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'heptagon') return `<path d="M 15,29 6,22 4,12 10,4 h 10 l 6,8 -2,10 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'circle') return `<circle cx="15" cy="15" r="11" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'no') return '';
|
||||
};
|
||||
|
||||
function drawMarker(marker, rescale = 1) {
|
||||
const {i, icon, x, y, dx = 50, dy = 50, px = 12, size = 30, pin, fill, stroke} = marker;
|
||||
const id = `marker${i}`;
|
||||
const zoomSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
const viewX = rn(x - zoomSize / 2, 1);
|
||||
const viewY = rn(y - zoomSize, 1);
|
||||
const pinHTML = getPin(pin, fill, stroke);
|
||||
|
||||
return `<svg id="${id}" viewbox="0 0 30 30" width="${zoomSize}" height="${zoomSize}" x="${viewX}" y="${viewY}"><g>${pinHTML}</g><text x="${dx}%" y="${dy}%" font-size="${px}px" >${icon}</text></svg>`;
|
||||
}
|
||||
|
||||
function toggleLabels(event) {
|
||||
if (!layerIsOn('toggleLabels')) {
|
||||
turnButtonOn('toggleLabels');
|
||||
|
|
@ -1620,21 +1652,21 @@ function drawEmblems() {
|
|||
const validBurgs = burgs.filter((b) => b.i && !b.removed && b.coa && b.coaSize != 0);
|
||||
|
||||
const getStateEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 40, 10, 100);
|
||||
const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier
|
||||
const sizeMod = +document.getElementById('emblemsStateSizeInput').value || 1;
|
||||
return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states
|
||||
};
|
||||
|
||||
const getProvinceEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 100, 5), 70);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 100, 5, 70);
|
||||
const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('emblemsProvinceSizeInput').value || 1;
|
||||
return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces
|
||||
};
|
||||
|
||||
const getBurgEmblemSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 185, 2), 50);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 185, 2, 50);
|
||||
const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('emblemsBurgSizeInput').value || 1;
|
||||
return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs
|
||||
|
|
@ -1685,11 +1717,9 @@ function drawEmblems() {
|
|||
|
||||
const burgNodes = nodes.filter((node) => node.type === 'burg');
|
||||
const burgString = burgNodes.map((d) => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join('');
|
||||
emblems.select('#burgEmblems').attr('font-size', sizeBurgs).html(burgString);
|
||||
|
||||
const provinceNodes = nodes.filter((node) => node.type === 'province');
|
||||
const provinceString = provinceNodes.map((d) => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join('');
|
||||
emblems.select('#provinceEmblems').attr('font-size', sizeProvinces).html(provinceString);
|
||||
|
||||
const stateNodes = nodes.filter((node) => node.type === 'state');
|
||||
const stateString = stateNodes.map((d) => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join('');
|
||||
|
|
|
|||
1970
modules/ui/layers.js.orig
Normal file
1970
modules/ui/layers.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,291 +1,261 @@
|
|||
'use strict';
|
||||
function editMarker() {
|
||||
function editMarker(markerI) {
|
||||
if (customization) return;
|
||||
closeDialogs('#markerEditor, .stable');
|
||||
$('#markerEditor').dialog();
|
||||
closeDialogs(".stable");
|
||||
|
||||
const [element, marker] = getElement(markerI, d3.event);
|
||||
if (!marker || !element) return;
|
||||
|
||||
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
|
||||
if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
|
||||
|
||||
// dom elements
|
||||
const markerType = document.getElementById("markerType");
|
||||
const markerIcon = document.getElementById("markerIcon");
|
||||
const markerIconSelect = document.getElementById("markerIconSelect");
|
||||
const markerIconSize = document.getElementById("markerIconSize");
|
||||
const markerIconShiftX = document.getElementById("markerIconShiftX");
|
||||
const markerIconShiftY = document.getElementById("markerIconShiftY");
|
||||
const markerSize = document.getElementById("markerSize");
|
||||
const markerPin = document.getElementById("markerPin");
|
||||
const markerFill = document.getElementById("markerFill");
|
||||
const markerStroke = document.getElementById("markerStroke");
|
||||
|
||||
const markerNotes = document.getElementById("markerNotes");
|
||||
const markerLock = document.getElementById("markerLock");
|
||||
const addMarker = document.getElementById("addMarker");
|
||||
const markerAdd = document.getElementById("markerAdd");
|
||||
const markerRemove = document.getElementById("markerRemove");
|
||||
|
||||
elSelected = d3.select(d3.event.target).call(d3.drag().on('start', dragMarker)).classed('draggable', true);
|
||||
updateInputs();
|
||||
|
||||
if (modules.editMarker) return;
|
||||
modules.editMarker = true;
|
||||
|
||||
$('#markerEditor').dialog({
|
||||
title: 'Edit Marker',
|
||||
title: "Edit Marker",
|
||||
resizable: false,
|
||||
position: {my: 'center top+30', at: 'bottom', of: d3.event, collision: 'fit'},
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
close: closeMarkerEditor
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('markerGroup').addEventListener('click', toggleGroupSection);
|
||||
document.getElementById('markerAddGroup').addEventListener('click', toggleGroupInput);
|
||||
document.getElementById('markerSelectGroup').addEventListener('change', changeGroup);
|
||||
document.getElementById('markerInputGroup').addEventListener('change', createGroup);
|
||||
document.getElementById('markerRemoveGroup').addEventListener('click', removeGroup);
|
||||
const listeners = [
|
||||
listen(markerType, "change", changeMarkerType),
|
||||
listen(markerIcon, "input", changeMarkerIcon),
|
||||
listen(markerIconSelect, "click", selectMarkerIcon),
|
||||
listen(markerIconSize, "input", changeIconSize),
|
||||
listen(markerIconShiftX, "input", changeIconShiftX),
|
||||
listen(markerIconShiftY, "input", changeIconShiftY),
|
||||
listen(markerSize, "input", changeMarkerSize),
|
||||
listen(markerPin, "change", changeMarkerPin),
|
||||
listen(markerFill, "input", changePinFill),
|
||||
listen(markerStroke, "input", changePinStroke),
|
||||
listen(markerNotes, "click", editMarkerLegend),
|
||||
listen(markerLock, "click", toggleMarkerLock),
|
||||
listen(markerAdd, "click", toggleAddMarker),
|
||||
listen(markerRemove, "click", confirmMarkerDeletion)
|
||||
];
|
||||
|
||||
document.getElementById('markerIcon').addEventListener('click', toggleIconSection);
|
||||
document.getElementById('markerIconSize').addEventListener('input', changeIconSize);
|
||||
document.getElementById('markerIconShiftX').addEventListener('input', changeIconShiftX);
|
||||
document.getElementById('markerIconShiftY').addEventListener('input', changeIconShiftY);
|
||||
document.getElementById('markerIconSelect').addEventListener('click', selectMarkerIcon);
|
||||
function getElement(markerI, event) {
|
||||
if (event) {
|
||||
const element = event.target?.closest("svg");
|
||||
const marker = pack.markers.find(({i}) => Number(element.id.slice(6)) === i);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById('markerStyle').addEventListener('click', toggleStyleSection);
|
||||
document.getElementById('markerSize').addEventListener('input', changeMarkerSize);
|
||||
document.getElementById('markerBaseStroke').addEventListener('input', changePinStroke);
|
||||
document.getElementById('markerBaseFill').addEventListener('input', changePinFill);
|
||||
document.getElementById('markerIconStrokeWidth').addEventListener('input', changeIconStrokeWidth);
|
||||
document.getElementById('markerIconStroke').addEventListener('input', changeIconStroke);
|
||||
document.getElementById('markerIconFill').addEventListener('input', changeIconFill);
|
||||
const element = document.getElementById(`marker${markerI}`);
|
||||
const marker = pack.markers.find(({i}) => i === markerI);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById('markerToggleBubble').addEventListener('click', togglePinVisibility);
|
||||
document.getElementById('markerLegendButton').addEventListener('click', editMarkerLegend);
|
||||
document.getElementById('markerAdd').addEventListener('click', toggleAddMarker);
|
||||
document.getElementById('markerRemove').addEventListener('click', removeMarker);
|
||||
|
||||
updateGroupOptions();
|
||||
function getSameTypeMarkers() {
|
||||
const currentType = marker.type;
|
||||
if (!currentType) return [marker];
|
||||
return pack.markers.filter(({type}) => type === currentType);
|
||||
}
|
||||
|
||||
function dragMarker() {
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
const x = +tr[0] - d3.event.x,
|
||||
y = +tr[1] - d3.event.y;
|
||||
const dx = +this.getAttribute("x") - d3.event.x;
|
||||
const dy = +this.getAttribute("y") - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
|
||||
this.setAttribute('transform', transform);
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", dx + x);
|
||||
this.setAttribute("y", dy + y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", rn(dx + x, 2));
|
||||
this.setAttribute("y", rn(dy + y, 2));
|
||||
|
||||
const size = marker.size || 30;
|
||||
const zoomSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
|
||||
marker.x = rn(x + dx + zoomSize / 2, 1);
|
||||
marker.y = rn(y + dy + zoomSize, 1);
|
||||
marker.cell = findCell(marker.x, marker.y);
|
||||
.selectAll('symbol')
|
||||
.each(function () {
|
||||
});
|
||||
}
|
||||
|
||||
function updateInputs() {
|
||||
const id = elSelected.attr('data-id');
|
||||
const symbol = d3.select('#defs-markers').select(id);
|
||||
const icon = symbol.select('text');
|
||||
const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
|
||||
|
||||
markerSelectGroup.value = id.slice(1);
|
||||
markerIconSize.value = parseFloat(icon.attr('font-size'));
|
||||
markerIconShiftX.value = parseFloat(icon.attr('x'));
|
||||
markerIconShiftY.value = parseFloat(icon.attr('y'));
|
||||
markerType.value = type;
|
||||
markerIcon.value = icon;
|
||||
markerIconSize.value = px;
|
||||
markerIconShiftX.value = dx;
|
||||
markerIconShiftY.value = dy;
|
||||
markerSize.value = size;
|
||||
markerPin.value = pin;
|
||||
markerFill.value = fill;
|
||||
markerStroke.value = stroke;
|
||||
|
||||
markerSize.value = elSelected.attr('data-size');
|
||||
markerBaseStroke.value = symbol.select('path').attr('fill');
|
||||
markerBaseFill.value = symbol.select('circle').attr('fill');
|
||||
|
||||
markerIconStrokeWidth.value = icon.attr('stroke-width');
|
||||
markerIconStroke.value = icon.attr('stroke');
|
||||
markerIconFill.value = icon.attr('fill');
|
||||
|
||||
markerToggleBubble.className = symbol.select('circle').attr('opacity') === '0' ? 'icon-info' : 'icon-info-circled';
|
||||
markerIconSelect.innerHTML = icon.text();
|
||||
}
|
||||
|
||||
function toggleGroupSection() {
|
||||
if (markerGroupSection.style.display === 'inline-block') {
|
||||
markerEditor.querySelectorAll('button:not(#markerGroup)').forEach((b) => (b.style.display = 'inline-block'));
|
||||
markerGroupSection.style.display = 'none';
|
||||
} else {
|
||||
markerEditor.querySelectorAll('button:not(#markerGroup)').forEach((b) => (b.style.display = 'none'));
|
||||
markerGroupSection.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
function updateGroupOptions() {
|
||||
markerSelectGroup.innerHTML = '';
|
||||
d3.select('#defs-markers')
|
||||
.selectAll('symbol')
|
||||
.each(function () {
|
||||
markerSelectGroup.options.add(new Option(this.id, this.id));
|
||||
});
|
||||
markerSelectGroup.value = elSelected.attr('data-id').slice(1);
|
||||
}
|
||||
|
||||
function toggleGroupInput() {
|
||||
if (markerInputGroup.style.display === 'inline-block') {
|
||||
markerSelectGroup.style.display = 'inline-block';
|
||||
markerInputGroup.style.display = 'none';
|
||||
} else {
|
||||
markerSelectGroup.style.display = 'none';
|
||||
markerInputGroup.style.display = 'inline-block';
|
||||
markerInputGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
elSelected.attr('xlink:href', '#' + this.value);
|
||||
elSelected.attr('data-id', '#' + this.value);
|
||||
}
|
||||
|
||||
function createGroup() {
|
||||
let newGroup = this.value
|
||||
.toLowerCase()
|
||||
markerLock.className = lock ? "icon-lock" : "icon-lock-open";
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
if (Number.isFinite(+newGroup.charAt(0))) newGroup = 'm' + newGroup;
|
||||
if (document.getElementById(newGroup)) {
|
||||
tip('Element with this id already exists. Please provide a unique name', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
markerInputGroup.value = '';
|
||||
// clone old group assigning new id
|
||||
const id = elSelected.attr('data-id');
|
||||
const clone = d3.select('#defs-markers').select(id).node().cloneNode(true);
|
||||
clone.id = newGroup;
|
||||
document.getElementById('defs-markers').insertBefore(clone, null);
|
||||
elSelected.attr('xlink:href', '#' + newGroup).attr('data-id', '#' + newGroup);
|
||||
|
||||
// select new group
|
||||
markerSelectGroup.options.add(new Option(newGroup, newGroup, false, true));
|
||||
toggleGroupInput();
|
||||
}
|
||||
|
||||
function removeGroup() {
|
||||
const id = elSelected.attr('data-id');
|
||||
const used = document.querySelectorAll("use[data-id='" + id + "']");
|
||||
|
||||
const count = used.length === 1 ? '1 element' : used.length + ' elements';
|
||||
const message = `Are you sure you want to remove all markers of that type (${count})? <br>This action cannot be reverted`;
|
||||
const onConfirm = () => {
|
||||
if (id !== '#marker0') d3.select('#defs-markers').select(id).remove();
|
||||
used.forEach((e) => {
|
||||
const index = notes.findIndex((n) => n.id === e.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
e.remove();
|
||||
});
|
||||
updateGroupOptions();
|
||||
updateGroupOptions();
|
||||
$('#markerEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove marker type', message, confirm: 'Remove', onConfirm});
|
||||
function changeMarkerType() {
|
||||
marker.type = this.value;
|
||||
}
|
||||
|
||||
function toggleIconSection() {
|
||||
if (markerIconSection.style.display === 'inline-block') {
|
||||
markerEditor.querySelectorAll('button:not(#markerIcon)').forEach((b) => (b.style.display = 'inline-block'));
|
||||
markerIconSection.style.display = 'none';
|
||||
markerIconSelect.style.display = 'none';
|
||||
} else {
|
||||
markerEditor.querySelectorAll('button:not(#markerIcon)').forEach((b) => (b.style.display = 'none'));
|
||||
markerIconSection.style.display = 'inline-block';
|
||||
markerIconSelect.style.display = 'inline-block';
|
||||
}
|
||||
function changeMarkerIcon() {
|
||||
const icon = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
}
|
||||
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(this.innerHTML, (v) => {
|
||||
this.innerHTML = v;
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').text(v);
|
||||
selectIcon(marker.icon, icon => {
|
||||
markerIcon.value = icon;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconSize() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers')
|
||||
.select(id)
|
||||
.select('text')
|
||||
.attr('font-size', this.value + 'px');
|
||||
const px = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.px = px;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftX() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers')
|
||||
.select(id)
|
||||
.select('text')
|
||||
.attr('x', this.value + '%');
|
||||
const dx = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dx = dx;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftY() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers')
|
||||
.select(id)
|
||||
.select('text')
|
||||
.attr('y', this.value + '%');
|
||||
}
|
||||
|
||||
function toggleStyleSection() {
|
||||
if (markerStyleSection.style.display === 'inline-block') {
|
||||
markerEditor.querySelectorAll('button:not(#markerStyle)').forEach((b) => (b.style.display = 'inline-block'));
|
||||
markerStyleSection.style.display = 'none';
|
||||
} else {
|
||||
markerEditor.querySelectorAll('button:not(#markerStyle)').forEach((b) => (b.style.display = 'none'));
|
||||
markerStyleSection.style.display = 'inline-block';
|
||||
}
|
||||
const dy = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dy = dy;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerSize() {
|
||||
const id = elSelected.attr('data-id');
|
||||
document.querySelectorAll("use[data-id='" + id + "']").forEach((e) => {
|
||||
const x = +e.dataset.x,
|
||||
y = +e.dataset.y;
|
||||
const size = +this.value;
|
||||
const rescale = +markers.attr("rescale");
|
||||
const desired = (e.dataset.size = +markerSize.value);
|
||||
const size = Math.max(desired * 5 + 25 / scale, 1);
|
||||
|
||||
e.setAttribute('x', x - size / 2);
|
||||
e.setAttribute('y', y - size / 2);
|
||||
e.setAttribute('width', size);
|
||||
e.setAttribute('height', size);
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.size = size;
|
||||
const {i, x, y, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
invokeActiveZooming();
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select(id).select('path').attr('fill', this.value);
|
||||
d3.select(id).select('circle').attr('stroke', this.value);
|
||||
function changeMarkerPin() {
|
||||
const pin = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.pin = pin;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinFill() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select(id).select('circle').attr('fill', this.value);
|
||||
const fill = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.fill = fill;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconStrokeWidth() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').attr('stroke-width', this.value);
|
||||
function changePinStroke() {
|
||||
const stroke = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.stroke = stroke;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconStroke() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').attr('stroke', this.value);
|
||||
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
|
||||
const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconElement) {
|
||||
iconElement.innerHTML = icon;
|
||||
iconElement.setAttribute("x", dx + "%");
|
||||
iconElement.setAttribute("y", dy + "%");
|
||||
iconElement.setAttribute("font-size", px + "px");
|
||||
}
|
||||
}
|
||||
|
||||
function changeIconFill() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').attr('fill', this.value);
|
||||
}
|
||||
|
||||
function togglePinVisibility() {
|
||||
const id = elSelected.attr('data-id');
|
||||
let show = 1;
|
||||
if (this.className === 'icon-info-circled') {
|
||||
this.className = 'icon-info';
|
||||
show = 0;
|
||||
} else this.className = 'icon-info-circled';
|
||||
function redrawPin({i, hidden, pin = "bubble", fill = "#fff", stroke = "#000"}) {
|
||||
const pinGroup = !hidden && document.querySelector(`#marker${i} > g`);
|
||||
if (pinGroup) pinGroup.innerHTML = getPin(pin, fill, stroke);
|
||||
d3.select(id).select('circle').attr('opacity', show);
|
||||
d3.select(id).select('path').attr('opacity', show);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = elSelected.attr('id');
|
||||
const id = element.id;
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
document.getElementById('addMarker').click();
|
||||
function toggleMarkerLock() {
|
||||
marker.lock = !marker.lock;
|
||||
markerLock.classList.toggle("icon-lock-open");
|
||||
markerLock.classList.toggle("icon-lock");
|
||||
}
|
||||
|
||||
function removeMarker() {
|
||||
const message = 'Are you sure you want to remove the marker? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
const index = notes.findIndex((n) => n.id === elSelected.attr('id'));
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
elSelected.remove();
|
||||
$('#markerEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove marker', message, confirm: 'Remove', onConfirm});
|
||||
function toggleAddMarker() {
|
||||
markerAdd.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function confirmMarkerDeletion() {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: deleteMarker
|
||||
|
||||
function deleteMarker() {
|
||||
notes = notes.filter(note => note.id !== element.id);
|
||||
pack.markers = pack.markers.filter(m => m.i !== marker.i);
|
||||
element.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function closeMarkerEditor() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
unselect();
|
||||
if (addMarker.classList.contains('pressed')) addMarker.classList.remove('pressed');
|
||||
if (markerAdd.classList.contains('pressed')) markerAdd.classList.remove('pressed');
|
||||
|
|
|
|||
265
modules/ui/markers-editor.js.orig
Normal file
265
modules/ui/markers-editor.js.orig
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
'use strict';
|
||||
function editMarker(markerI) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
|
||||
const [element, marker] = getElement(markerI, d3.event);
|
||||
if (!marker || !element) return;
|
||||
|
||||
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
|
||||
if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
|
||||
|
||||
// dom elements
|
||||
const markerType = document.getElementById("markerType");
|
||||
const markerIcon = document.getElementById("markerIcon");
|
||||
const markerIconSelect = document.getElementById("markerIconSelect");
|
||||
const markerIconSize = document.getElementById("markerIconSize");
|
||||
const markerIconShiftX = document.getElementById("markerIconShiftX");
|
||||
const markerIconShiftY = document.getElementById("markerIconShiftY");
|
||||
const markerSize = document.getElementById("markerSize");
|
||||
const markerPin = document.getElementById("markerPin");
|
||||
const markerFill = document.getElementById("markerFill");
|
||||
const markerStroke = document.getElementById("markerStroke");
|
||||
|
||||
const markerNotes = document.getElementById("markerNotes");
|
||||
const markerLock = document.getElementById("markerLock");
|
||||
const addMarker = document.getElementById("addMarker");
|
||||
const markerAdd = document.getElementById("markerAdd");
|
||||
const markerRemove = document.getElementById("markerRemove");
|
||||
|
||||
updateInputs();
|
||||
|
||||
$('#markerEditor').dialog({
|
||||
title: "Edit Marker",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
close: closeMarkerEditor
|
||||
});
|
||||
|
||||
const listeners = [
|
||||
listen(markerType, "change", changeMarkerType),
|
||||
listen(markerIcon, "input", changeMarkerIcon),
|
||||
listen(markerIconSelect, "click", selectMarkerIcon),
|
||||
listen(markerIconSize, "input", changeIconSize),
|
||||
listen(markerIconShiftX, "input", changeIconShiftX),
|
||||
listen(markerIconShiftY, "input", changeIconShiftY),
|
||||
listen(markerSize, "input", changeMarkerSize),
|
||||
listen(markerPin, "change", changeMarkerPin),
|
||||
listen(markerFill, "input", changePinFill),
|
||||
listen(markerStroke, "input", changePinStroke),
|
||||
listen(markerNotes, "click", editMarkerLegend),
|
||||
listen(markerLock, "click", toggleMarkerLock),
|
||||
listen(markerAdd, "click", toggleAddMarker),
|
||||
listen(markerRemove, "click", confirmMarkerDeletion)
|
||||
];
|
||||
|
||||
function getElement(markerI, event) {
|
||||
if (event) {
|
||||
const element = event.target?.closest("svg");
|
||||
const marker = pack.markers.find(({i}) => Number(element.id.slice(6)) === i);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
const element = document.getElementById(`marker${markerI}`);
|
||||
const marker = pack.markers.find(({i}) => i === markerI);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
function getSameTypeMarkers() {
|
||||
const currentType = marker.type;
|
||||
if (!currentType) return [marker];
|
||||
return pack.markers.filter(({type}) => type === currentType);
|
||||
}
|
||||
|
||||
function dragMarker() {
|
||||
const dx = +this.getAttribute("x") - d3.event.x;
|
||||
const dy = +this.getAttribute("y") - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", dx + x);
|
||||
this.setAttribute("y", dy + y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", rn(dx + x, 2));
|
||||
this.setAttribute("y", rn(dy + y, 2));
|
||||
|
||||
const size = marker.size || 30;
|
||||
const zoomSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
|
||||
marker.x = rn(x + dx + zoomSize / 2, 1);
|
||||
marker.y = rn(y + dy + zoomSize, 1);
|
||||
marker.cell = findCell(marker.x, marker.y);
|
||||
.selectAll('symbol')
|
||||
.each(function () {
|
||||
});
|
||||
}
|
||||
|
||||
function updateInputs() {
|
||||
const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
|
||||
|
||||
markerType.value = type;
|
||||
markerIcon.value = icon;
|
||||
markerIconSize.value = px;
|
||||
markerIconShiftX.value = dx;
|
||||
markerIconShiftY.value = dy;
|
||||
markerSize.value = size;
|
||||
markerPin.value = pin;
|
||||
markerFill.value = fill;
|
||||
markerStroke.value = stroke;
|
||||
|
||||
markerLock.className = lock ? "icon-lock" : "icon-lock-open";
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
if (Number.isFinite(+newGroup.charAt(0))) newGroup = 'm' + newGroup;
|
||||
}
|
||||
|
||||
function changeMarkerType() {
|
||||
marker.type = this.value;
|
||||
}
|
||||
|
||||
function changeMarkerIcon() {
|
||||
const icon = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
}
|
||||
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(marker.icon, icon => {
|
||||
markerIcon.value = icon;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconSize() {
|
||||
const px = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.px = px;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftX() {
|
||||
const dx = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dx = dx;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftY() {
|
||||
const dy = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dy = dy;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerSize() {
|
||||
const size = +this.value;
|
||||
const rescale = +markers.attr("rescale");
|
||||
const desired = (e.dataset.size = +markerSize.value);
|
||||
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.size = size;
|
||||
const {i, x, y, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerPin() {
|
||||
const pin = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.pin = pin;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinFill() {
|
||||
const fill = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.fill = fill;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const stroke = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.stroke = stroke;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
|
||||
const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconElement) {
|
||||
iconElement.innerHTML = icon;
|
||||
iconElement.setAttribute("x", dx + "%");
|
||||
iconElement.setAttribute("y", dy + "%");
|
||||
iconElement.setAttribute("font-size", px + "px");
|
||||
}
|
||||
}
|
||||
|
||||
function redrawPin({i, hidden, pin = "bubble", fill = "#fff", stroke = "#000"}) {
|
||||
const pinGroup = !hidden && document.querySelector(`#marker${i} > g`);
|
||||
if (pinGroup) pinGroup.innerHTML = getPin(pin, fill, stroke);
|
||||
d3.select(id).select('circle').attr('opacity', show);
|
||||
d3.select(id).select('path').attr('opacity', show);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = element.id;
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleMarkerLock() {
|
||||
marker.lock = !marker.lock;
|
||||
markerLock.classList.toggle("icon-lock-open");
|
||||
markerLock.classList.toggle("icon-lock");
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markerAdd.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function confirmMarkerDeletion() {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: deleteMarker
|
||||
|
||||
function deleteMarker() {
|
||||
notes = notes.filter(note => note.id !== element.id);
|
||||
pack.markers = pack.markers.filter(m => m.i !== marker.i);
|
||||
element.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function closeMarkerEditor() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
unselect();
|
||||
addMarker.classList.remove("pressed");
|
||||
markerAdd.classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
196
modules/ui/markers-overview.js
Normal file
196
modules/ui/markers-overview.js
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
'use strict';
|
||||
function overviewMarkers() {
|
||||
if (customization) return;
|
||||
closeDialogs('#markersOverview, .stable');
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
|
||||
const markerGroup = document.getElementById('markers');
|
||||
const body = document.getElementById('markersBody');
|
||||
const markersInverPin = document.getElementById('markersInverPin');
|
||||
const markersInverLock = document.getElementById('markersInverLock');
|
||||
const markersFooterNumber = document.getElementById('markersFooterNumber');
|
||||
const markersOverviewRefresh = document.getElementById('markersOverviewRefresh');
|
||||
const markersAddFromOverview = document.getElementById('markersAddFromOverview');
|
||||
const markersGenerationConfig = document.getElementById('markersGenerationConfig');
|
||||
const markersRemoveAll = document.getElementById('markersRemoveAll');
|
||||
const markersExport = document.getElementById('markersExport');
|
||||
|
||||
addLines();
|
||||
|
||||
$('#markersOverview').dialog({
|
||||
title: 'Markers Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: close,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
const listeners = [
|
||||
listen(body, 'click', handleLineClick),
|
||||
listen(markersInverPin, 'click', invertPin),
|
||||
listen(markersInverLock, 'click', invertLock),
|
||||
listen(markersOverviewRefresh, 'click', addLines),
|
||||
listen(markersAddFromOverview, 'click', toggleAddMarker),
|
||||
listen(markersGenerationConfig, 'click', configMarkersGeneration),
|
||||
listen(markersRemoveAll, 'click', triggerRemoveAll),
|
||||
listen(markersExport, 'click', exportMarkers)
|
||||
];
|
||||
|
||||
function handleLineClick(ev) {
|
||||
const el = ev.target;
|
||||
const i = +el.parentNode.dataset.i;
|
||||
|
||||
if (el.classList.contains('icon-pencil')) return openEditor(i);
|
||||
if (el.classList.contains('icon-dot-circled')) return focusOnMarker(i);
|
||||
if (el.classList.contains('icon-pin')) return pinMarker(el, i);
|
||||
if (el.classList.contains('locks')) return toggleLockStatus(el, i);
|
||||
if (el.classList.contains('icon-trash-empty')) return triggerRemove(i);
|
||||
}
|
||||
|
||||
function addLines() {
|
||||
const lines = pack.markers
|
||||
.map(({i, type, icon, pinned, lock}) => {
|
||||
return `<div class="states" data-i=${i} data-type="${type}">
|
||||
<div data-tip="Marker icon and type" style="width:12em">${icon} ${type}</div>
|
||||
<span style="padding-right:.1em" data-tip="Edit marker" class="icon-pencil"></span>
|
||||
<span style="padding-right:.1em" data-tip="Focus on marker position" class="icon-dot-circled pointer"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin ${pinned ? '' : 'inactive'}" pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${lock ? 'icon-lock' : 'icon-lock-open inactive'}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove marker" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
body.innerHTML = lines;
|
||||
markersFooterNumber.innerText = pack.markers.length;
|
||||
|
||||
applySorting(markersHeader);
|
||||
}
|
||||
|
||||
function invertPin() {
|
||||
let anyPinned = false;
|
||||
|
||||
pack.markers.forEach((marker) => {
|
||||
const pinned = !marker.pinned;
|
||||
if (pinned) {
|
||||
marker.pinned = true;
|
||||
anyPinned = true;
|
||||
} else delete marker.pinned;
|
||||
});
|
||||
|
||||
markerGroup.setAttribute('pinned', anyPinned ? 1 : null);
|
||||
drawMarkers();
|
||||
addLines();
|
||||
}
|
||||
|
||||
function invertLock() {
|
||||
pack.markers = pack.markers.map((marker) => ({...marker, lock: !marker.lock}));
|
||||
addLines();
|
||||
}
|
||||
|
||||
function openEditor(i) {
|
||||
const marker = pack.markers.find((marker) => marker.i === i);
|
||||
if (!marker) return;
|
||||
|
||||
const {x, y} = marker;
|
||||
zoomTo(x, y, 8, 2000);
|
||||
editMarker(i);
|
||||
}
|
||||
|
||||
function focusOnMarker(i) {
|
||||
highlightElement(document.getElementById(`marker${i}`), 2);
|
||||
}
|
||||
|
||||
function pinMarker(el, i) {
|
||||
const marker = pack.markers.find((marker) => marker.i === i);
|
||||
if (marker.pinned) {
|
||||
delete marker.pinned;
|
||||
const anyPinned = pack.markers.some((marker) => marker.pinned);
|
||||
if (!anyPinned) markerGroup.removeAttribute('pinned');
|
||||
} else {
|
||||
marker.pinned = true;
|
||||
markerGroup.setAttribute('pinned', 1);
|
||||
}
|
||||
el.classList.toggle('inactive');
|
||||
drawMarkers();
|
||||
}
|
||||
|
||||
function toggleLockStatus(el, i) {
|
||||
const marker = pack.markers.find((marker) => marker.i === i);
|
||||
if (marker.lock) {
|
||||
delete marker.lock;
|
||||
el.className = 'locks pointer icon-lock-open inactive';
|
||||
} else {
|
||||
marker.lock = true;
|
||||
el.className = 'locks pointer icon-lock';
|
||||
}
|
||||
}
|
||||
|
||||
function triggerRemove(i) {
|
||||
confirmationDialog({
|
||||
title: 'Remove marker',
|
||||
message: 'Are you sure you want to remove this marker? The action cannot be reverted',
|
||||
confirm: 'Remove',
|
||||
onConfirm: () => removeMarker(i)
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markersAddFromOverview.classList.toggle('pressed');
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function removeMarker(i) {
|
||||
notes = notes.filter((note) => note.id !== `marker${i}`);
|
||||
pack.markers = pack.markers.filter((marker) => marker.i !== i);
|
||||
document.getElementById(`marker${i}`)?.remove();
|
||||
addLines();
|
||||
}
|
||||
|
||||
function triggerRemoveAll() {
|
||||
confirmationDialog({
|
||||
title: 'Remove all markers',
|
||||
message: 'Are you sure you want to remove all non-locked markers? The action cannot be reverted',
|
||||
confirm: 'Remove all',
|
||||
onConfirm: removeAllMarkers
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllMarkers() {
|
||||
pack.markers = pack.markers.filter(({i, lock}) => {
|
||||
if (lock) return true;
|
||||
|
||||
const id = `marker${i}`;
|
||||
document.getElementById(id)?.remove();
|
||||
notes = notes.filter((note) => note.id !== id);
|
||||
return false;
|
||||
});
|
||||
|
||||
addLines();
|
||||
}
|
||||
|
||||
function exportMarkers() {
|
||||
const headers = 'Id,Type,Icon,Name,Note,X,Y\n';
|
||||
|
||||
const body = pack.markers.map((marker) => {
|
||||
const {i, type, icon, x, y} = marker;
|
||||
const id = `marker${i}`;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
const legend = escape(note.legend);
|
||||
return [id, type, icon, note.name, legend, x, y].join(',');
|
||||
});
|
||||
|
||||
const data = headers + body.join('\n');
|
||||
const fileName = getFileName('Markers') + '.csv';
|
||||
downloadFile(data, fileName);
|
||||
}
|
||||
|
||||
function close() {
|
||||
listeners.forEach((removeListener) => removeListener());
|
||||
|
||||
addMarker.classList.remove('pressed');
|
||||
markerAdd.classList.remove('pressed');
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,33 +10,33 @@ class Rulers {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return this.data.map(ruler => ruler.toString()).join("; ");
|
||||
return this.data.map((ruler) => ruler.toString()).join('; ');
|
||||
}
|
||||
|
||||
fromString(string) {
|
||||
this.data = [];
|
||||
|
||||
const rulers = string.split("; ");
|
||||
const rulers = string.split('; ');
|
||||
for (const rulerString of rulers) {
|
||||
const [type, pointsString] = rulerString.split(": ");
|
||||
const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
|
||||
const Type = type === "Ruler" ? Ruler : type === "Opisometer" ? Opisometer : type === "RouteOpisometer" ? RouteOpisometer : type === "Planimeter" ? Planimeter : null;
|
||||
const [type, pointsString] = rulerString.split(': ');
|
||||
const points = pointsString.split(' ').map((el) => el.split(',').map((n) => +n));
|
||||
const Type = type === 'Ruler' ? Ruler : type === 'Opisometer' ? Opisometer : type === 'RouteOpisometer' ? RouteOpisometer : type === 'Planimeter' ? Planimeter : null;
|
||||
this.create(Type, points);
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.data.forEach(ruler => ruler.draw());
|
||||
this.data.forEach((ruler) => ruler.draw());
|
||||
}
|
||||
|
||||
undraw() {
|
||||
this.data.forEach(ruler => ruler.undraw());
|
||||
this.data.forEach((ruler) => ruler.undraw());
|
||||
}
|
||||
|
||||
remove(id) {
|
||||
if (id === undefined) return;
|
||||
|
||||
const ruler = this.data.find(ruler => ruler.id === id);
|
||||
const ruler = this.data.find((ruler) => ruler.id === id);
|
||||
ruler.undraw();
|
||||
const rulerIndex = this.data.indexOf(ruler);
|
||||
rulers.data.splice(rulerIndex, 1);
|
||||
|
|
@ -50,7 +50,7 @@ class Measurer {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return this.constructor.name + ": " + this.points.join(" ");
|
||||
return this.constructor.name + ': ' + this.points.join(' ');
|
||||
}
|
||||
|
||||
getSize() {
|
||||
|
|
@ -62,13 +62,13 @@ class Measurer {
|
|||
}
|
||||
|
||||
drag() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
const x = +tr[0] - d3.event.x,
|
||||
y = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
|
||||
this.setAttribute("transform", transform);
|
||||
this.setAttribute('transform', transform);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
getPointsString() {
|
||||
return this.points.join(" ");
|
||||
return this.points.join(' ');
|
||||
}
|
||||
|
||||
updatePoint(index, x, y) {
|
||||
|
|
@ -119,7 +119,7 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
getPointId(x, y) {
|
||||
return this.points.findIndex(el => el[0] == x && el[1] == y);
|
||||
return this.points.findIndex((el) => el[0] == x && el[1] == y);
|
||||
}
|
||||
|
||||
pushPoint(i) {
|
||||
|
|
@ -128,42 +128,42 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const points = this.getPointsString();
|
||||
const size = this.getSize();
|
||||
const dash = this.getDash();
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "ruler")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("polyline")
|
||||
.attr("points", points)
|
||||
.attr("class", "white")
|
||||
.attr("stroke-width", size)
|
||||
.call(d3.drag().on("start", () => this.addControl(this)));
|
||||
el.append("polyline")
|
||||
.attr("points", points)
|
||||
.attr("class", "gray")
|
||||
.attr("stroke-width", rn(size * 1.2, 2))
|
||||
.attr("stroke-dasharray", dash);
|
||||
el.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
.append('g')
|
||||
.attr('class', 'ruler')
|
||||
.call(d3.drag().on('start', this.drag))
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('polyline')
|
||||
.attr('points', points)
|
||||
.attr('class', 'white')
|
||||
.attr('stroke-width', size)
|
||||
.call(d3.drag().on('start', () => this.addControl(this)));
|
||||
el.append('polyline')
|
||||
.attr('points', points)
|
||||
.attr('class', 'gray')
|
||||
.attr('stroke-width', rn(size * 1.2, 2))
|
||||
.attr('stroke-dasharray', dash);
|
||||
el.append('g')
|
||||
.attr('class', 'rulerPoints')
|
||||
.attr('stroke-width', 0.5 * size)
|
||||
.attr('font-size', 2 * size);
|
||||
el.append('text')
|
||||
.attr('dx', '.35em')
|
||||
.attr('dy', '-.45em')
|
||||
.on('click', () => rulers.remove(this.id));
|
||||
this.drawPoints(el);
|
||||
this.updateLabel();
|
||||
return this;
|
||||
}
|
||||
|
||||
drawPoints(el) {
|
||||
const g = el.select(".rulerPoints");
|
||||
g.selectAll("circle").remove();
|
||||
const g = el.select('.rulerPoints');
|
||||
g.selectAll('circle').remove();
|
||||
|
||||
for (let i = 0; i < this.points.length; i++) {
|
||||
const [x, y] = this.points[i];
|
||||
|
|
@ -173,19 +173,19 @@ class Ruler extends Measurer {
|
|||
|
||||
drawPoint(el, x, y, i) {
|
||||
const context = this;
|
||||
el.append("circle")
|
||||
.attr("r", "1em")
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("class", this.isEdge(i) ? "edge" : "control")
|
||||
.on("click", function () {
|
||||
el.append('circle')
|
||||
.attr('r', '1em')
|
||||
.attr('cx', x)
|
||||
.attr('cy', y)
|
||||
.attr('class', this.isEdge(i) ? 'edge' : 'control')
|
||||
.on('click', function () {
|
||||
context.removePoint(context, i);
|
||||
})
|
||||
.call(
|
||||
d3
|
||||
.drag()
|
||||
.clickDistance(3)
|
||||
.on("start", function () {
|
||||
.on('start', function () {
|
||||
context.dragControl(context, i);
|
||||
})
|
||||
);
|
||||
|
|
@ -197,9 +197,9 @@ class Ruler extends Measurer {
|
|||
|
||||
updateLabel() {
|
||||
const length = this.getLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const text = rn(length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
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);
|
||||
}
|
||||
|
||||
getLength() {
|
||||
|
|
@ -215,13 +215,13 @@ class Ruler extends Measurer {
|
|||
dragControl(context, pointId) {
|
||||
let addPoint = context.isEdge(pointId) && d3.event.sourceEvent.ctrlKey;
|
||||
let circle = context.el.select(`circle:nth-child(${pointId + 1})`);
|
||||
const line = context.el.selectAll("polyline");
|
||||
const line = context.el.selectAll('polyline');
|
||||
|
||||
let x0 = rn(d3.event.x, 1);
|
||||
let y0 = rn(d3.event.y, 1);
|
||||
let axis;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
if (addPoint) {
|
||||
if (d3.event.dx < 0.1 && d3.event.dy < 0.1) return;
|
||||
context.pushPoint(pointId);
|
||||
|
|
@ -232,10 +232,10 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
const shiftPressed = d3.event.sourceEvent.shiftKey;
|
||||
if (shiftPressed && !axis) axis = Math.abs(d3.event.dx) > Math.abs(d3.event.dy) ? "x" : "y";
|
||||
if (shiftPressed && !axis) axis = Math.abs(d3.event.dx) > Math.abs(d3.event.dy) ? 'x' : 'y';
|
||||
|
||||
const x = axis === "y" ? x0 : rn(d3.event.x, 1);
|
||||
const y = axis === "x" ? y0 : rn(d3.event.y, 1);
|
||||
const x = axis === 'y' ? x0 : rn(d3.event.x, 1);
|
||||
const y = axis === 'x' ? y0 : rn(d3.event.y, 1);
|
||||
|
||||
if (!shiftPressed) {
|
||||
axis = null;
|
||||
|
|
@ -244,8 +244,8 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
context.updatePoint(pointId, x, y);
|
||||
line.attr("points", context.getPointsString());
|
||||
circle.attr("cx", x).attr("cy", y);
|
||||
line.attr('points', context.getPointsString());
|
||||
circle.attr('cx', x).attr('cy', y);
|
||||
context.updateLabel();
|
||||
});
|
||||
}
|
||||
|
|
@ -273,43 +273,43 @@ class Opisometer extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const size = this.getSize();
|
||||
const dash = this.getDash();
|
||||
const context = this;
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "opisometer")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
.append('g')
|
||||
.attr('class', 'opisometer')
|
||||
.call(d3.drag().on('start', this.drag))
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('path').attr('class', 'white').attr('stroke-width', size);
|
||||
el.append('path').attr('class', 'gray').attr('stroke-width', size).attr('stroke-dasharray', dash);
|
||||
const rulerPoints = el
|
||||
.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
.append('g')
|
||||
.attr('class', 'rulerPoints')
|
||||
.attr('stroke-width', 0.5 * size)
|
||||
.attr('font-size', 2 * size);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 0);
|
||||
})
|
||||
);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 1);
|
||||
})
|
||||
);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
el.append('text')
|
||||
.attr('dx', '.35em')
|
||||
.attr('dy', '-.45em')
|
||||
.on('click', () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -319,26 +319,26 @@ class Opisometer extends Measurer {
|
|||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
this.el.selectAll('path').attr('d', path);
|
||||
|
||||
const left = this.points[0];
|
||||
const right = last(this.points);
|
||||
this.el.select(".rulerPoints > circle:first-child").attr("cx", left[0]).attr("cy", left[1]);
|
||||
this.el.select(".rulerPoints > circle:last-child").attr("cx", right[0]).attr("cy", right[1]);
|
||||
this.el.select('.rulerPoints > circle:first-child').attr('cx', left[0]).attr('cy', left[1]);
|
||||
this.el.select('.rulerPoints > circle:last-child').attr('cx', right[0]).attr('cy', right[1]);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
const length = this.el.select("path").node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const length = this.el.select('path').node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
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);
|
||||
}
|
||||
|
||||
dragControl(context, rigth) {
|
||||
const MIN_DIST = d3.event.sourceEvent.shiftKey ? 9 : 100;
|
||||
let prev = rigth ? last(context.points) : context.points[0];
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
const point = [d3.event.x | 0, d3.event.y | 0];
|
||||
|
||||
const dist2 = (prev[0] - point[0]) ** 2 + (prev[1] - point[1]) ** 2;
|
||||
|
|
@ -351,7 +351,7 @@ class Opisometer extends Measurer {
|
|||
context.updateLabel();
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
d3.event.on('end', function () {
|
||||
if (!d3.event.sourceEvent.shiftKey) context.optimize();
|
||||
});
|
||||
}
|
||||
|
|
@ -361,7 +361,7 @@ class RouteOpisometer extends Measurer {
|
|||
constructor(points) {
|
||||
super(points);
|
||||
if (pack.cells) {
|
||||
this.cellStops = points.map(p => findCell(p[0], p[1]));
|
||||
this.cellStops = points.map((p) => findCell(p[0], p[1]));
|
||||
} else {
|
||||
this.cellStops = null;
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ class RouteOpisometer extends Measurer {
|
|||
|
||||
checkCellStops() {
|
||||
if (!this.cellStops) {
|
||||
this.cellStops = this.points.map(p => findCell(p[0], p[1]));
|
||||
this.cellStops = this.points.map((p) => findCell(p[0], p[1]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -412,42 +412,42 @@ class RouteOpisometer extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const size = this.getSize();
|
||||
const dash = this.getDash();
|
||||
const context = this;
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "opisometer")
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
.append('g')
|
||||
.attr('class', 'opisometer')
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('path').attr('class', 'white').attr('stroke-width', size);
|
||||
el.append('path').attr('class', 'gray').attr('stroke-width', size).attr('stroke-dasharray', dash);
|
||||
const rulerPoints = el
|
||||
.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
.append('g')
|
||||
.attr('class', 'rulerPoints')
|
||||
.attr('stroke-width', 0.5 * size)
|
||||
.attr('font-size', 2 * size);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 0);
|
||||
})
|
||||
);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 1);
|
||||
})
|
||||
);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
el.append('text')
|
||||
.attr('dx', '.35em')
|
||||
.attr('dy', '-.45em')
|
||||
.on('click', () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -457,23 +457,23 @@ class RouteOpisometer extends Measurer {
|
|||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
this.el.selectAll('path').attr('d', path);
|
||||
|
||||
const left = this.points[0];
|
||||
const right = last(this.points);
|
||||
this.el.select(".rulerPoints > circle:first-child").attr("cx", left[0]).attr("cy", left[1]);
|
||||
this.el.select(".rulerPoints > circle:last-child").attr("cx", right[0]).attr("cy", right[1]);
|
||||
this.el.select('.rulerPoints > circle:first-child').attr('cx', left[0]).attr('cy', left[1]);
|
||||
this.el.select('.rulerPoints > circle:last-child').attr('cx', right[0]).attr('cy', right[1]);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
const length = this.el.select("path").node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const length = this.el.select('path').node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
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);
|
||||
}
|
||||
|
||||
dragControl(context, rigth) {
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
const mousePoint = [d3.event.x | 0, d3.event.y | 0];
|
||||
const cells = pack.cells;
|
||||
|
||||
|
|
@ -493,16 +493,16 @@ class Planimeter extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const size = this.getSize();
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "planimeter")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "planimeter").attr("stroke-width", size);
|
||||
el.append("text").on("click", () => rulers.remove(this.id));
|
||||
.append('g')
|
||||
.attr('class', 'planimeter')
|
||||
.call(d3.drag().on('start', this.drag))
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('path').attr('class', 'planimeter').attr('stroke-width', size);
|
||||
el.append('text').on('click', () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -512,32 +512,33 @@ class Planimeter extends Measurer {
|
|||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRomClosed.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
this.el.selectAll('path').attr('d', path);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
if (this.points.length < 3) return;
|
||||
|
||||
const polygonArea = rn(Math.abs(d3.polygonArea(this.points)));
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const area = si(polygonArea * distanceScaleInput.value ** 2) + " " + unit;
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
const area = si(polygonArea * distanceScaleInput.value ** 2) + ' ' + unit;
|
||||
const c = polylabel([this.points], 1.0);
|
||||
this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area);
|
||||
this.el.select('text').attr('x', c[0]).attr('y', c[1]).text(area);
|
||||
}
|
||||
}
|
||||
|
||||
// Scale bar
|
||||
function drawScaleBar() {
|
||||
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
|
||||
scaleBar.selectAll("*").remove(); // fully redraw every time
|
||||
function drawScaleBar(requestedScale) {
|
||||
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 dScale = distanceScaleInput.value;
|
||||
const distanceScale = distanceScaleInput.value;
|
||||
const unit = distanceUnitInput.value;
|
||||
const size = +barSizeInput.value;
|
||||
|
||||
// calculate size
|
||||
const init = 100; // actual length in pixels if scale, dScale and size = 1;
|
||||
const size = +barSizeInput.value;
|
||||
let val = (init * size * dScale) / scale; // bar length in distance unit
|
||||
const init = 100;
|
||||
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
|
||||
if (val > 900) val = rn(val, -3);
|
||||
// round to 1000
|
||||
else if (val > 90) val = rn(val, -2);
|
||||
|
|
@ -545,81 +546,81 @@ function drawScaleBar() {
|
|||
else if (val > 9) val = rn(val, -1);
|
||||
// round to 10
|
||||
else val = rn(val); // round to 1
|
||||
const l = (val * scale) / dScale; // actual length in pixels on this scale
|
||||
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
|
||||
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0.5)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size - 0.5)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "white");
|
||||
.append('line')
|
||||
.attr('x1', 0.5)
|
||||
.attr('y1', 0)
|
||||
.attr('x2', length + size - 0.5)
|
||||
.attr('y2', 0)
|
||||
.attr('stroke-width', size)
|
||||
.attr('stroke', 'white');
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", size)
|
||||
.attr("x2", l + size)
|
||||
.attr("y2", size)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
const dash = size + " " + rn(l / 5 - size, 2);
|
||||
.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', size)
|
||||
.attr('x2', length + size)
|
||||
.attr('y2', size)
|
||||
.attr('stroke-width', size)
|
||||
.attr('stroke', '#3d3d3d');
|
||||
const dash = size + ' ' + rn(length / 5 - size, 2);
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", rn(size * 3, 2))
|
||||
.attr("stroke-dasharray", dash)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', 0)
|
||||
.attr('x2', length + size)
|
||||
.attr('y2', 0)
|
||||
.attr('stroke-width', rn(size * 3, 2))
|
||||
.attr('stroke-dasharray', dash)
|
||||
.attr('stroke', '#3d3d3d');
|
||||
|
||||
const fontSize = rn(5 * size, 1);
|
||||
scaleBar
|
||||
.selectAll("text")
|
||||
.selectAll('text')
|
||||
.data(d3.range(0, 6))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", d => rn((d * l) / 5, 2))
|
||||
.attr("y", 0)
|
||||
.attr("dy", "-.5em")
|
||||
.attr("font-size", fontSize)
|
||||
.text(d => rn((((d * l) / 5) * dScale) / scale) + (d < 5 ? "" : " " + unit));
|
||||
.append('text')
|
||||
.attr('x', (d) => rn((d * length) / 5, 2))
|
||||
.attr('y', 0)
|
||||
.attr('dy', '-.5em')
|
||||
.attr('font-size', fontSize)
|
||||
.text((d) => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? '' : ' ' + unit));
|
||||
|
||||
if (barLabel.value !== "") {
|
||||
if (barLabel.value !== '') {
|
||||
scaleBar
|
||||
.append("text")
|
||||
.attr("x", (l + 1) / 2)
|
||||
.attr("y", 2 * size)
|
||||
.attr("dominant-baseline", "text-before-edge")
|
||||
.attr("font-size", fontSize)
|
||||
.append('text')
|
||||
.attr('x', (length + 1) / 2)
|
||||
.attr('y', 2 * size)
|
||||
.attr('dominant-baseline', 'text-before-edge')
|
||||
.attr('font-size', fontSize)
|
||||
.text(barLabel.value);
|
||||
}
|
||||
|
||||
const bbox = scaleBar.node().getBBox();
|
||||
// append backbround rectangle
|
||||
scaleBar
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", -10)
|
||||
.attr("y", -20)
|
||||
.attr("width", bbox.width + 10)
|
||||
.attr("height", bbox.height + 15)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "none")
|
||||
.attr("filter", "url(#blur5)")
|
||||
.attr("fill", barBackColor.value)
|
||||
.attr("opacity", +barBackOpacity.value);
|
||||
.insert('rect', ':first-child')
|
||||
.attr('x', -10)
|
||||
.attr('y', -20)
|
||||
.attr('width', bbox.width + 10)
|
||||
.attr('height', bbox.height + 15)
|
||||
.attr('stroke-width', size)
|
||||
.attr('stroke', 'none')
|
||||
.attr('filter', 'url(#blur5)')
|
||||
.attr('fill', barBackColor.value)
|
||||
.attr('opacity', +barBackOpacity.value);
|
||||
|
||||
fitScaleBar();
|
||||
}
|
||||
|
||||
// fit ScaleBar to canvas size
|
||||
function fitScaleBar() {
|
||||
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
|
||||
if (!scaleBar.select('rect').size() || scaleBar.style('display') === 'none') return;
|
||||
const px = isNaN(+barPosX.value) ? 0.99 : barPosX.value / 100;
|
||||
const py = isNaN(+barPosY.value) ? 0.99 : barPosY.value / 100;
|
||||
const bbox = scaleBar.select("rect").node().getBBox();
|
||||
const bbox = scaleBar.select('rect').node().getBBox();
|
||||
const x = rn(svgWidth * px - bbox.width + 10),
|
||||
y = rn(svgHeight * py - bbox.height + 20);
|
||||
scaleBar.attr("transform", `translate(${x},${y})`);
|
||||
scaleBar.attr('transform', `translate(${x},${y})`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,9 +199,9 @@ function overviewMilitary() {
|
|||
|
||||
function militaryCustomize() {
|
||||
const types = ['melee', 'ranged', 'mounted', 'machinery', 'naval', 'armored', 'aviation', 'magical'];
|
||||
const table = document.getElementById('militaryOptions').querySelector('tbody');
|
||||
const tableBody = document.getElementById('militaryOptions').querySelector('tbody');
|
||||
removeUnitLines();
|
||||
options.military.map((u) => addUnitLine(u));
|
||||
options.military.map((unit) => addUnitLine(unit));
|
||||
|
||||
$('#militaryOptions').dialog({
|
||||
title: 'Edit Military Units',
|
||||
|
|
@ -219,44 +219,127 @@ function overviewMilitary() {
|
|||
open: function () {
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Add new military unit to the table'));
|
||||
buttons[2].addEventListener('mousemove', () => tip('Restore default military units and settings'));
|
||||
buttons[3].addEventListener('mousemove', () => tip('Close the window without saving the changes'));
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.overviewMilitaryCustomize) return;
|
||||
modules.overviewMilitaryCustomize = true;
|
||||
|
||||
tableBody.addEventListener('click', (event) => {
|
||||
const el = event.target;
|
||||
if (el.tagName !== 'BUTTON') return;
|
||||
const type = el.dataset.type;
|
||||
|
||||
if (type === 'icon') return selectIcon(el.innerHTML, (v) => (el.innerHTML = v));
|
||||
if (type === 'biomes') {
|
||||
const {i, name, color} = biomesData;
|
||||
const biomesArray = Array(i.length).fill(null);
|
||||
const biomes = biomesArray.map((_, i) => ({i, name: name[i], color: color[i]}));
|
||||
return selectLimitation(el, biomes);
|
||||
}
|
||||
if (type === 'states') return selectLimitation(el, pack.states);
|
||||
if (type === 'cultures') return selectLimitation(el, pack.cultures);
|
||||
if (type === 'religions') return selectLimitation(el, pack.religions);
|
||||
});
|
||||
|
||||
function removeUnitLines() {
|
||||
table.querySelectorAll('tr').forEach((el) => el.remove());
|
||||
tableBody.querySelectorAll('tr').forEach((el) => el.remove());
|
||||
}
|
||||
|
||||
function addUnitLine(u) {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon || ' '}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types
|
||||
.map((t) => `<option ${u.type === t ? 'selected' : ''} value="${t}">${t}</option>`)
|
||||
.join(' ')}</select></td>
|
||||
function getLimitValue(attr) {
|
||||
return attr?.join(',') || '';
|
||||
}
|
||||
|
||||
function getLimitText(attr) {
|
||||
return attr?.length ? 'some' : 'all';
|
||||
}
|
||||
|
||||
function getLimitTip(attr, data) {
|
||||
if (!attr || !attr.length) return '';
|
||||
return attr.map((i) => data?.[i]?.name || '').join(', ');
|
||||
}
|
||||
|
||||
function addUnitLine(unit) {
|
||||
const typeOptions = types.map((t) => `<option ${unit.type === t ? 'selected' : ''} value="${t}">${t}</option>`).join(' ');
|
||||
const getLimitButton = (attr) =>
|
||||
`<button
|
||||
data-tip="Select allowed ${attr}"
|
||||
data-type="${attr}"
|
||||
title="${getLimitTip(unit[attr], pack[attr])}"
|
||||
data-value="${getLimitValue(unit[attr])}">
|
||||
${getLimitText(unit[attr])}
|
||||
</button>`;
|
||||
|
||||
row.innerHTML = `<td><button data-type="icon" data-tip="Click to select unit icon">${unit.icon || ' '}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${unit.name}"></td>
|
||||
<td>${getLimitButton('biomes')}</td>
|
||||
<td>${getLimitButton('states')}</td>
|
||||
<td>${getLimitButton('cultures')}</td>
|
||||
<td>${getLimitButton('religions')}</td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${unit.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${unit.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min=1 step=1 value="${unit.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${unit.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${typeOptions}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? 'checked' : ''}>
|
||||
<label for="${u.name}Separate" class="checkbox-label"></label></td>
|
||||
<input id="${unit.name}Separate" type="checkbox" class="checkbox" ${unit.separate ? 'checked' : ''}>
|
||||
<label for="${unit.name}Separate" class="checkbox-label"></label></td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
|
||||
row.querySelector('button').addEventListener('click', function (e) {
|
||||
selectIcon(this.innerHTML, (v) => (this.innerHTML = v));
|
||||
});
|
||||
table.appendChild(row);
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
removeUnitLines();
|
||||
Military.getDefaultOptions().map((u) => addUnitLine(u));
|
||||
Military.getDefaultOptions().map((unit) => addUnitLine(unit));
|
||||
}
|
||||
|
||||
function selectLimitation(el, data) {
|
||||
const type = el.dataset.type;
|
||||
const value = el.dataset.value;
|
||||
const initial = value ? value.split(',').map((v) => +v) : [];
|
||||
|
||||
const filtered = data.filter((datum) => datum.i && !datum.removed);
|
||||
const lines = filtered.map(
|
||||
({i, name, fullName, color}) =>
|
||||
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
|
||||
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? 'checked' : ''} >
|
||||
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
||||
</td></tr>`
|
||||
);
|
||||
alertMessage.innerHTML = `<b>Limit unit by ${type}:</b><table style="margin-top:.3em"><tbody>${lines.join('')}</tbody></table>`;
|
||||
|
||||
$('#alert').dialog({
|
||||
width: fitContent(),
|
||||
title: `Limit unit`,
|
||||
buttons: {
|
||||
Invert: function () {
|
||||
alertMessage.querySelectorAll('input').forEach((el) => (el.checked = !el.checked));
|
||||
},
|
||||
Apply: function () {
|
||||
const inputs = Array.from(alertMessage.querySelectorAll('input'));
|
||||
const selected = inputs.reduce((acc, input) => {
|
||||
if (input.checked) acc.push(input.dataset.i);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (!selected.length) return tip('Select at least one element', false, 'error');
|
||||
|
||||
const allAreSelected = selected.length === inputs.length;
|
||||
el.dataset.value = allAreSelected ? '' : selected.join(',');
|
||||
el.innerHTML = allAreSelected ? 'all' : 'some';
|
||||
el.setAttribute('title', getLimitTip(selected, data));
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(table.querySelectorAll('tr'));
|
||||
const unitLines = Array.from(tableBody.querySelectorAll('tr'));
|
||||
const names = unitLines.map((r) => r.querySelector('input').value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'));
|
||||
if (new Set(names).size !== names.length) {
|
||||
tip('All units should have unique names', false, 'error');
|
||||
|
|
@ -265,14 +348,22 @@ function overviewMilitary() {
|
|||
|
||||
$('#militaryOptions').dialog('close');
|
||||
options.military = unitLines.map((r, i) => {
|
||||
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll('input, select, button')).map((d) => {
|
||||
let value = d.value;
|
||||
if (d.type === 'number') value = +d.value || 0;
|
||||
if (d.type === 'checkbox') value = +d.checked || 0;
|
||||
if (d.type === 'button') value = d.innerHTML || '⠀';
|
||||
return value;
|
||||
const elements = Array.from(r.querySelectorAll('input, button, select'));
|
||||
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map((el) => {
|
||||
const {type, value} = el.dataset || {};
|
||||
if (type === 'icon') return el.innerHTML || '⠀';
|
||||
if (type) return value ? value.split(',').map((v) => parseInt(v)) : null;
|
||||
if (el.type === 'number') return +el.value || 0;
|
||||
if (el.type === 'checkbox') return +el.checked || 0;
|
||||
return el.value;
|
||||
});
|
||||
return {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
|
||||
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
if (biomes) unit.biomes = biomes;
|
||||
if (states) unit.states = states;
|
||||
if (cultures) unit.cultures = cultures;
|
||||
if (religions) unit.religions = religions;
|
||||
return unit;
|
||||
});
|
||||
localStorage.setItem('military', JSON.stringify(options.military));
|
||||
Military.generate();
|
||||
|
|
|
|||
492
modules/ui/military-overview.js.orig
Normal file
492
modules/ui/military-overview.js.orig
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
'use strict';
|
||||
function overviewMilitary() {
|
||||
if (customization) return;
|
||||
closeDialogs('#militaryOverview, .stable');
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
if (!layerIsOn('toggleMilitary')) toggleMilitary();
|
||||
|
||||
const body = document.getElementById('militaryBody');
|
||||
addLines();
|
||||
$('#militaryOverview').dialog();
|
||||
|
||||
if (modules.overviewMilitary) return;
|
||||
modules.overviewMilitary = true;
|
||||
updateHeaders();
|
||||
|
||||
$('#militaryOverview').dialog({
|
||||
title: 'Military Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('militaryOverviewRefresh').addEventListener('click', addLines);
|
||||
document.getElementById('militaryPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('militaryOptionsButton').addEventListener('click', militaryCustomize);
|
||||
document.getElementById('militaryRegimentsList').addEventListener('click', () => overviewRegiments(-1));
|
||||
document.getElementById('militaryOverviewRecalculate').addEventListener('click', militaryRecalculate);
|
||||
document.getElementById('militaryExport').addEventListener('click', downloadMilitaryData);
|
||||
document.getElementById('militaryWiki').addEventListener('click', () => wiki('Military-Forces'));
|
||||
|
||||
body.addEventListener('change', function (ev) {
|
||||
const el = ev.target,
|
||||
line = el.parentNode,
|
||||
state = +line.dataset.id;
|
||||
changeAlert(state, line, +el.value);
|
||||
});
|
||||
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target,
|
||||
line = el.parentNode,
|
||||
state = +line.dataset.id;
|
||||
if (el.tagName === 'SPAN') overviewRegiments(state);
|
||||
});
|
||||
|
||||
// update military types in header and tooltips
|
||||
function updateHeaders() {
|
||||
const header = document.getElementById('militaryHeader');
|
||||
header.querySelectorAll('.removable').forEach((el) => el.remove());
|
||||
const insert = (html) => document.getElementById('militaryTotal').insertAdjacentHTML('beforebegin', html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
insert(`<div data-tip="State ${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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function addLines() {
|
||||
body.innerHTML = '';
|
||||
let lines = '';
|
||||
const states = pack.states.filter((s) => s.i && !s.removed);
|
||||
|
||||
for (const s of states) {
|
||||
const population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||
const getForces = (u) => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
|
||||
const total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0);
|
||||
const rate = (total / population) * 100;
|
||||
|
||||
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>
|
||||
<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>
|
||||
<div data-type="population" data-tip="State population">${si(population)}</div>
|
||||
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
|
||||
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(
|
||||
s.alert,
|
||||
2
|
||||
)}">
|
||||
<span data-tip="Show regiments list" class="icon-list-bullet pointer"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
updateFooter();
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => stateHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => stateHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(militaryHeader);
|
||||
}
|
||||
|
||||
function changeAlert(state, line, alert) {
|
||||
const s = pack.states[state];
|
||||
const dif = s.alert || alert ? alert / s.alert : 0; // modifier
|
||||
s.alert = line.dataset.alert = alert;
|
||||
|
||||
s.military.forEach((r) => {
|
||||
Object.keys(r.u).forEach((u) => (r.u[u] = rn(r.u[u] * dif))); // change units value
|
||||
r.a = d3.sum(Object.values(r.u)); // change total
|
||||
armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text
|
||||
});
|
||||
|
||||
const getForces = (u) => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
|
||||
options.military.forEach((u) => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)));
|
||||
|
||||
const population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||
const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0));
|
||||
const rate = (line.dataset.rate = (total / population) * 100);
|
||||
line.querySelector("div[data-type='total']").innerHTML = si(total);
|
||||
line.querySelector("div[data-type='rate']").innerHTML = rn(rate, 2) + '%';
|
||||
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
function updateFooter() {
|
||||
const lines = Array.from(body.querySelectorAll(':scope > div'));
|
||||
const statesNumber = (militaryFooterStates.innerHTML = pack.states.filter((s) => s.i && !s.removed).length);
|
||||
const total = d3.sum(lines.map((el) => el.dataset.total));
|
||||
militaryFooterForcesTotal.innerHTML = si(total);
|
||||
militaryFooterForces.innerHTML = si(total / statesNumber);
|
||||
militaryFooterRate.innerHTML = rn(d3.sum(lines.map((el) => el.dataset.rate)) / statesNumber, 2) + '%';
|
||||
militaryFooterAlert.innerHTML = rn(d3.sum(lines.map((el) => el.dataset.alert)) / statesNumber, 2);
|
||||
}
|
||||
|
||||
function stateHighlightOn(event) {
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
armies
|
||||
.select('#army' + state)
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.style('fill', '#ff0000');
|
||||
|
||||
if (!layerIsOn('toggleStates')) return;
|
||||
const d = regions.select('#state' + state).attr('d');
|
||||
|
||||
<<<<<<< HEAD
|
||||
const path = debug.append('path').attr('class', 'highlight').attr('d', d).attr('fill', 'none').attr('stroke', 'red').attr('stroke-width', 1).attr('opacity', 1).attr('filter', 'url(#blur1)');
|
||||
=======
|
||||
const path = debug
|
||||
.append("path")
|
||||
.attr("class", "highlight")
|
||||
.attr("d", d)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
>>>>>>> master
|
||||
|
||||
const l = path.node().getTotalLength(),
|
||||
dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString('0,' + l, l + ',' + l);
|
||||
path
|
||||
.transition()
|
||||
.duration(dur)
|
||||
.attrTween('stroke-dasharray', function () {
|
||||
return (t) => i(t);
|
||||
});
|
||||
}
|
||||
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll('.highlight').each(function () {
|
||||
d3.select(this).transition().duration(1000).attr('opacity', 0).remove();
|
||||
});
|
||||
|
||||
const state = +event.target.dataset.id;
|
||||
armies
|
||||
.select('#army' + state)
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.style('fill', null);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const lines = body.querySelectorAll(':scope > div');
|
||||
const array = Array.from(lines),
|
||||
cache = [];
|
||||
|
||||
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) {
|
||||
const type = div.dataset.type;
|
||||
if (type === 'rate') return;
|
||||
div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + '%' : '0%';
|
||||
});
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = 'absolute';
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function militaryCustomize() {
|
||||
<<<<<<< HEAD
|
||||
const types = ['melee', 'ranged', 'mounted', 'machinery', 'naval', 'armored', 'aviation', 'magical'];
|
||||
const table = document.getElementById('militaryOptions').querySelector('tbody');
|
||||
removeUnitLines();
|
||||
options.military.map((u) => addUnitLine(u));
|
||||
=======
|
||||
const types = ["melee", "ranged", "mounted", "machinery", "naval", "armored", "aviation", "magical"];
|
||||
const tableBody = document.getElementById("militaryOptions").querySelector("tbody");
|
||||
removeUnitLines();
|
||||
options.military.map(unit => addUnitLine(unit));
|
||||
>>>>>>> master
|
||||
|
||||
$('#militaryOptions').dialog({
|
||||
title: 'Edit Military Units',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Apply: applyMilitaryOptions,
|
||||
Add: () => addUnitLine({icon: '🛡️', name: 'custom' + militaryOptionsTable.rows.length, rural: 0.2, urban: 0.5, crew: 1, power: 1, type: 'melee'}),
|
||||
Restore: restoreDefaultUnits,
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
<<<<<<< HEAD
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Add new military unit to the table'));
|
||||
buttons[2].addEventListener('mousemove', () => tip('Restore default military units and settings'));
|
||||
buttons[3].addEventListener('mousemove', () => tip('Close the window without saving the changes'));
|
||||
=======
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () =>
|
||||
tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>")
|
||||
);
|
||||
buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table"));
|
||||
buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes"));
|
||||
>>>>>>> master
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.overviewMilitaryCustomize) return;
|
||||
modules.overviewMilitaryCustomize = true;
|
||||
|
||||
tableBody.addEventListener("click", event => {
|
||||
const el = event.target;
|
||||
if (el.tagName !== "BUTTON") return;
|
||||
const type = el.dataset.type;
|
||||
|
||||
if (type === "icon") return selectIcon(el.innerHTML, v => (el.innerHTML = v));
|
||||
if (type === "biomes") {
|
||||
const {i, name, color} = biomesData;
|
||||
const biomesArray = Array(i.length).fill(null);
|
||||
const biomes = biomesArray.map((_, i) => ({i, name: name[i], color: color[i]}));
|
||||
return selectLimitation(el, biomes);
|
||||
}
|
||||
if (type === "states") return selectLimitation(el, pack.states);
|
||||
if (type === "cultures") return selectLimitation(el, pack.cultures);
|
||||
if (type === "religions") return selectLimitation(el, pack.religions);
|
||||
});
|
||||
|
||||
function removeUnitLines() {
|
||||
<<<<<<< HEAD
|
||||
table.querySelectorAll('tr').forEach((el) => el.remove());
|
||||
}
|
||||
|
||||
function addUnitLine(u) {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon || ' '}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types
|
||||
.map((t) => `<option ${u.type === t ? 'selected' : ''} value="${t}">${t}</option>`)
|
||||
.join(' ')}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? 'checked' : ''}>
|
||||
<label for="${u.name}Separate" class="checkbox-label"></label></td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
|
||||
row.querySelector('button').addEventListener('click', function (e) {
|
||||
selectIcon(this.innerHTML, (v) => (this.innerHTML = v));
|
||||
});
|
||||
table.appendChild(row);
|
||||
=======
|
||||
tableBody.querySelectorAll("tr").forEach(el => el.remove());
|
||||
}
|
||||
|
||||
function getLimitValue(attr) {
|
||||
return attr?.join(",") || "";
|
||||
}
|
||||
|
||||
function getLimitText(attr) {
|
||||
return attr?.length ? "some" : "all";
|
||||
}
|
||||
|
||||
function getLimitTip(attr, data) {
|
||||
if (!attr || !attr.length) return "";
|
||||
return attr.map(i => data?.[i]?.name || "").join(", ");
|
||||
}
|
||||
|
||||
function addUnitLine(unit) {
|
||||
const row = document.createElement("tr");
|
||||
const typeOptions = types.map(t => `<option ${unit.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ");
|
||||
const getLimitButton = attr =>
|
||||
`<button
|
||||
data-tip="Select allowed ${attr}"
|
||||
data-type="${attr}"
|
||||
title="${getLimitTip(unit[attr], pack[attr])}"
|
||||
data-value="${getLimitValue(unit[attr])}">
|
||||
${getLimitText(unit[attr])}
|
||||
</button>`;
|
||||
|
||||
row.innerHTML = `<td><button data-type="icon" data-tip="Click to select unit icon">${unit.icon || " "}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${unit.name}"></td>
|
||||
<td>${getLimitButton("biomes")}</td>
|
||||
<td>${getLimitButton("states")}</td>
|
||||
<td>${getLimitButton("cultures")}</td>
|
||||
<td>${getLimitButton("religions")}</td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${unit.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${unit.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min=1 step=1 value="${unit.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${unit.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${typeOptions}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${unit.name}Separate" type="checkbox" class="checkbox" ${unit.separate ? "checked" : ""}>
|
||||
<label for="${unit.name}Separate" class="checkbox-label"></label></td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
|
||||
tableBody.appendChild(row);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
removeUnitLines();
|
||||
<<<<<<< HEAD
|
||||
Military.getDefaultOptions().map((u) => addUnitLine(u));
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(table.querySelectorAll('tr'));
|
||||
const names = unitLines.map((r) => r.querySelector('input').value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'));
|
||||
=======
|
||||
Military.getDefaultOptions().map(unit => addUnitLine(unit));
|
||||
}
|
||||
|
||||
function selectLimitation(el, data) {
|
||||
const type = el.dataset.type;
|
||||
const value = el.dataset.value;
|
||||
const initial = value ? value.split(",").map(v => +v) : [];
|
||||
|
||||
const filtered = data.filter(datum => datum.i && !datum.removed);
|
||||
const lines = filtered.map(
|
||||
({i, name, fullName, color}) =>
|
||||
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
|
||||
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? "checked" : ""} >
|
||||
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
||||
</td></tr>`
|
||||
);
|
||||
alertMessage.innerHTML = `<b>Limit unit by ${type}:</b><table style="margin-top:.3em"><tbody>${lines.join("")}</tbody></table>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
width: fitContent(),
|
||||
title: `Limit unit`,
|
||||
buttons: {
|
||||
Invert: function () {
|
||||
alertMessage.querySelectorAll("input").forEach(el => (el.checked = !el.checked));
|
||||
},
|
||||
Apply: function () {
|
||||
const inputs = Array.from(alertMessage.querySelectorAll("input"));
|
||||
const selected = inputs.reduce((acc, input) => {
|
||||
if (input.checked) acc.push(input.dataset.i);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (!selected.length) return tip("Select at least one element", false, "error");
|
||||
|
||||
const allAreSelected = selected.length === inputs.length;
|
||||
el.dataset.value = allAreSelected ? "" : selected.join(",");
|
||||
el.innerHTML = allAreSelected ? "all" : "some";
|
||||
el.setAttribute("title", getLimitTip(selected, data));
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(tableBody.querySelectorAll("tr"));
|
||||
const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_"));
|
||||
>>>>>>> master
|
||||
if (new Set(names).size !== names.length) {
|
||||
tip('All units should have unique names', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#militaryOptions').dialog('close');
|
||||
options.military = unitLines.map((r, i) => {
|
||||
<<<<<<< HEAD
|
||||
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll('input, select, button')).map((d) => {
|
||||
let value = d.value;
|
||||
if (d.type === 'number') value = +d.value || 0;
|
||||
if (d.type === 'checkbox') value = +d.checked || 0;
|
||||
if (d.type === 'button') value = d.innerHTML || '⠀';
|
||||
return value;
|
||||
=======
|
||||
const elements = Array.from(r.querySelectorAll("input, button, select"));
|
||||
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map(el => {
|
||||
const {type, value} = el.dataset || {};
|
||||
if (type === "icon") return el.innerHTML || "⠀";
|
||||
if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
|
||||
if (el.type === "number") return +el.value || 0;
|
||||
if (el.type === "checkbox") return +el.checked || 0;
|
||||
return el.value;
|
||||
>>>>>>> master
|
||||
});
|
||||
|
||||
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
if (biomes) unit.biomes = biomes;
|
||||
if (states) unit.states = states;
|
||||
if (cultures) unit.cultures = cultures;
|
||||
if (religions) unit.religions = religions;
|
||||
return unit;
|
||||
});
|
||||
localStorage.setItem('military', JSON.stringify(options.military));
|
||||
Military.generate();
|
||||
updateHeaders();
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function militaryRecalculate() {
|
||||
alertMessage.innerHTML = 'Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove regiment',
|
||||
buttons: {
|
||||
Recalculate: function () {
|
||||
$(this).dialog('close');
|
||||
Military.generate();
|
||||
addLines();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadMilitaryData() {
|
||||
const units = options.military.map((u) => u.name);
|
||||
let data = 'Id,State,' + units.map((u) => capitalize(u)).join(',') + ',Total,Population,Rate,War Alert\n'; // headers
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.state + ',';
|
||||
data += units.map((u) => el.dataset[u]).join(',') + ',';
|
||||
data += el.dataset.total + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += rn(el.dataset.rate, 2) + '%,';
|
||||
data += el.dataset.alert + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Military') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
function editNotes(id, name) {
|
||||
// update list of objects
|
||||
const select = document.getElementById('notesSelect');
|
||||
|
|
@ -8,11 +9,12 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
// initiate pell (html editor)
|
||||
const notesText = document.getElementById('notesText');
|
||||
notesText.innerHTML = '';
|
||||
const editor = Pell.init({
|
||||
element: document.getElementById('notesText'),
|
||||
element: notesText,
|
||||
onChange: (html) => {
|
||||
const id = document.getElementById('notesSelect').value;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
const note = notes.find((note) => note.id === select.value);
|
||||
if (!note) return;
|
||||
note.legend = html;
|
||||
showNote(note);
|
||||
|
|
@ -43,8 +45,7 @@ function editNotes(id, name) {
|
|||
title: 'Notes Editor',
|
||||
minWidth: '40em',
|
||||
width: '50vw',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
close: () => (notesText.innerHTML = '')
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
|
||||
if (modules.editNotes) return;
|
||||
|
|
@ -107,7 +108,7 @@ function editNotes(id, name) {
|
|||
return;
|
||||
}
|
||||
|
||||
highlightElement(element); // if element is found
|
||||
highlightElement(element, 3); // if element is found
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
|
|
|
|||
183
modules/ui/notes-editor.js.orig
Normal file
183
modules/ui/notes-editor.js.orig
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<<<<<<< HEAD
|
||||
'use strict';
|
||||
=======
|
||||
"use strict";
|
||||
|
||||
>>>>>>> master
|
||||
function editNotes(id, name) {
|
||||
// update list of objects
|
||||
const select = document.getElementById('notesSelect');
|
||||
select.options.length = 0;
|
||||
for (const note of notes) {
|
||||
select.options.add(new Option(note.id, note.id));
|
||||
}
|
||||
|
||||
// initiate pell (html editor)
|
||||
const notesText = document.getElementById("notesText");
|
||||
notesText.innerHTML = "";
|
||||
const editor = Pell.init({
|
||||
<<<<<<< HEAD
|
||||
element: document.getElementById('notesText'),
|
||||
onChange: (html) => {
|
||||
const id = document.getElementById('notesSelect').value;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
=======
|
||||
element: notesText,
|
||||
onChange: html => {
|
||||
const note = notes.find(note => note.id === select.value);
|
||||
>>>>>>> master
|
||||
if (!note) return;
|
||||
note.legend = html;
|
||||
showNote(note);
|
||||
}
|
||||
});
|
||||
|
||||
// select an object
|
||||
if (notes.length || id) {
|
||||
if (!id) id = notes[0].id;
|
||||
let note = notes.find((note) => note.id === id);
|
||||
if (note === undefined) {
|
||||
if (!name) name = id;
|
||||
note = {id, name, legend: ''};
|
||||
notes.push(note);
|
||||
select.options.add(new Option(id, id));
|
||||
}
|
||||
select.value = id;
|
||||
notesName.value = note.name;
|
||||
editor.content.innerHTML = note.legend;
|
||||
showNote(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 = '';
|
||||
}
|
||||
|
||||
// open a dialog
|
||||
<<<<<<< HEAD
|
||||
$('#notesEditor').dialog({
|
||||
title: 'Notes Editor',
|
||||
minWidth: '40em',
|
||||
width: '50vw',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
close: () => (notesText.innerHTML = '')
|
||||
=======
|
||||
$("#notesEditor").dialog({
|
||||
title: "Notes Editor",
|
||||
minWidth: "40em",
|
||||
width: "50vw",
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
>>>>>>> master
|
||||
});
|
||||
|
||||
if (modules.editNotes) return;
|
||||
modules.editNotes = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('notesSelect').addEventListener('change', changeObject);
|
||||
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('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';
|
||||
document.getElementById('notesHeader').innerHTML = note.name;
|
||||
document.getElementById('notesBody').innerHTML = note.legend;
|
||||
}
|
||||
|
||||
function changeObject() {
|
||||
const note = notes.find((note) => note.id === this.value);
|
||||
if (!note) return;
|
||||
notesName.value = note.name;
|
||||
editor.content.innerHTML = note.legend;
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
const id = document.getElementById('notesSelect').value;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
if (!note) return;
|
||||
note.name = this.value;
|
||||
showNote(note);
|
||||
}
|
||||
|
||||
function validateHighlightElement() {
|
||||
const select = document.getElementById('notesSelect');
|
||||
const element = document.getElementById(select.value);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
const data = JSON.stringify(notes);
|
||||
const name = getFileName('Notes') + '.txt';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function uploadLegends(dataLoaded) {
|
||||
if (!dataLoaded) {
|
||||
tip('Cannot load the file. Please check the data format', false, 'error');
|
||||
return;
|
||||
}
|
||||
notes = JSON.parse(dataLoaded);
|
||||
document.getElementById('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,
|
||||
title: 'Remove note',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeLegend();
|
||||
},
|
||||
Keep: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeLegend() {
|
||||
const select = document.getElementById('notesSelect');
|
||||
const index = notes.findIndex((n) => n.id === select.value);
|
||||
notes.splice(index, 1);
|
||||
select.options.length = 0;
|
||||
if (!notes.length) {
|
||||
$('#notesEditor').dialog('close');
|
||||
return;
|
||||
}
|
||||
notesText.innerHTML = '';
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
}
|
||||
|
|
@ -89,17 +89,19 @@ function showSupporters() {
|
|||
Maxwell Hill,Drunken_Legends,rob bee,Jesse Holmes,YYako,Detocroix,Anoplexian,Hannah,Paul,Sandra Krohn,Lucid,Richard Keating,Allen Varney,Rick Falkvinge,
|
||||
Seth Fusion,Adam Butler,Gus,StroboWolf,Sadie Blackthorne,Zewen Senpai,Dell McKnight,Oneiris,Darinius Dragonclaw Studios,Christopher Whitney,Rhodes HvZ,
|
||||
Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,
|
||||
Thirty-OneR ,ThatGuyGW ,Dee Chiu,MontyBoosh ,Achillain ,Jaden ,SashaTK,Steve Johnson,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR,
|
||||
ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill,
|
||||
Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,Alex Debus,Joshua Vaught,
|
||||
Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,Radovan Zapletal,Jmmat6,
|
||||
Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen,
|
||||
Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone,
|
||||
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram,
|
||||
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
||||
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,
|
||||
"Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas"`;
|
||||
Thirty-OneR,ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,
|
||||
Andrew Rostaing,Daniel Gill,Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,
|
||||
Alex Debus,Joshua Vaught,Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,
|
||||
Radovan Zapletal,Jmmat6,Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,
|
||||
Guilherme Aguiar,Jarno Hallikainen,Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,
|
||||
Cooper Counts,Patrick Jones,Clonetone,PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,
|
||||
Page One Project,Spencer Morris,Paul Ingram,Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,
|
||||
Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,
|
||||
PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,
|
||||
Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,
|
||||
Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,
|
||||
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`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, '')
|
||||
|
|
@ -148,7 +150,9 @@ optionsContent.addEventListener('input', function (event) {
|
|||
else if (id === 'regionsInput' || id === 'regionsOutput') changeStatesNumber(value);
|
||||
else if (id === 'emblemShape') changeEmblemShape(value);
|
||||
else if (id === 'tooltipSizeInput' || id === 'tooltipSizeOutput') changeTooltipSize(value);
|
||||
else if (id === 'transparencyInput') changeDialogsTransparency(value);
|
||||
else if (id === "themeHueInput") changeThemeHue(value);
|
||||
else if (id === "themeColorInput") changeDialogsTheme(themeColorInput.value, transparencyInput.value);
|
||||
else if (id === "transparencyInput") changeDialogsTheme(themeColorInput.value, value);
|
||||
});
|
||||
|
||||
optionsContent.addEventListener('change', function (event) {
|
||||
|
|
@ -156,23 +160,24 @@ optionsContent.addEventListener('change', function (event) {
|
|||
const value = event.target.value;
|
||||
|
||||
if (id === 'zoomExtentMin' || id === 'zoomExtentMax') changeZoomExtent(value);
|
||||
else if (id === 'optionsSeed') generateMapWithSeed();
|
||||
else if (id === "optionsSeed") generateMapWithSeed("seed change");
|
||||
else if (id === 'uiSizeInput' || id === 'uiSizeOutput') changeUIsize(value);
|
||||
if (id === 'shapeRendering') viewbox.attr('shape-rendering', value);
|
||||
else if (id === 'yearInput') changeYear();
|
||||
else if (id === 'eraInput') changeEra();
|
||||
else if (id === "stateLabelsModeInput") options.stateLabelsMode = value;
|
||||
});
|
||||
|
||||
optionsContent.addEventListener('click', function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === 'toggleFullscreen') toggleFullscreen();
|
||||
else if (id === 'optionsSeedGenerate') generateMapWithSeed();
|
||||
else if (id === 'optionsMapHistory') showSeedHistoryDialog();
|
||||
else if (id === 'optionsCopySeed') copyMapURL();
|
||||
else if (id === 'optionsEraRegenerate') regenerateEra();
|
||||
else if (id === 'zoomExtentDefault') restoreDefaultZoomExtent();
|
||||
else if (id === 'translateExtent') toggleTranslateExtent(event.target);
|
||||
else if (id === 'speakerTest') testSpeaker();
|
||||
else if (id === "themeColorRestore") restoreDefaultThemeColor();
|
||||
});
|
||||
|
||||
function mapSizeInputChange() {
|
||||
|
|
@ -206,8 +211,8 @@ function changeMapSize() {
|
|||
|
||||
// just apply canvas size that was already set
|
||||
function applyMapSize() {
|
||||
const zoomMin = +zoomExtentMin.value,
|
||||
zoomMax = +zoomExtentMax.value;
|
||||
const zoomMin = +zoomExtentMin.value;
|
||||
const zoomMax = +zoomExtentMax.value;
|
||||
graphWidth = +mapWidthInput.value;
|
||||
graphHeight = +mapHeightInput.value;
|
||||
svgWidth = Math.min(graphWidth, window.innerWidth);
|
||||
|
|
@ -275,12 +280,9 @@ function testSpeaker() {
|
|||
speechSynthesis.speak(speaker);
|
||||
}
|
||||
|
||||
function generateMapWithSeed() {
|
||||
if (optionsSeed.value == seed) {
|
||||
tip('The current map already has this seed', false, 'error');
|
||||
return;
|
||||
}
|
||||
regeneratePrompt();
|
||||
function generateMapWithSeed(source) {
|
||||
if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error");
|
||||
regeneratePrompt(source);
|
||||
}
|
||||
|
||||
function showSeedHistoryDialog() {
|
||||
|
|
@ -311,7 +313,7 @@ function restoreSeed(id) {
|
|||
mapHeightInput.value = mapHistory[id].height;
|
||||
templateInput.value = mapHistory[id].template;
|
||||
if (locked('template')) unlock('template');
|
||||
regeneratePrompt();
|
||||
regeneratePrompt("seed history");
|
||||
}
|
||||
|
||||
function restoreDefaultZoomExtent() {
|
||||
|
|
@ -415,7 +417,7 @@ function changeUIsize(value) {
|
|||
if (+value > max) value = max;
|
||||
|
||||
uiSizeInput.value = uiSizeOutput.value = value;
|
||||
document.getElementsByTagName('body')[0].style.fontSize = value * 11 + 'px';
|
||||
document.getElementsByTagName("body")[0].style.fontSize = rn(value * 10, 2) + "px";
|
||||
document.getElementById('options').style.width = value * 300 + 'px';
|
||||
}
|
||||
|
||||
|
|
@ -427,26 +429,56 @@ function changeTooltipSize(value) {
|
|||
tooltip.style.fontSize = `calc(${value}px + 0.5vw)`;
|
||||
}
|
||||
|
||||
// change transparency for modal windows
|
||||
function changeDialogsTransparency(value) {
|
||||
transparencyInput.value = transparencyOutput.value = value;
|
||||
const alpha = (100 - +value) / 100;
|
||||
const optionsColor = 'rgba(164, 139, 149, ' + alpha + ')';
|
||||
const dialogsColor = 'rgba(255, 255, 255, ' + alpha + ')';
|
||||
const optionButtonsColor = 'rgba(145, 110, 127, ' + Math.min(alpha + 0.3, 1) + ')';
|
||||
const optionLiColor = 'rgba(153, 123, 137, ' + Math.min(alpha + 0.3, 1) + ')';
|
||||
document.getElementById('options').style.backgroundColor = optionsColor;
|
||||
document.getElementById('dialogs').style.backgroundColor = dialogsColor;
|
||||
document.querySelectorAll('.tabcontent button').forEach((el) => (el.style.backgroundColor = optionButtonsColor));
|
||||
document.querySelectorAll('.tabcontent li').forEach((el) => (el.style.backgroundColor = optionLiColor));
|
||||
document.querySelectorAll('button.options').forEach((el) => (el.style.backgroundColor = optionLiColor));
|
||||
const THEME_COLOR = "#997787";
|
||||
function restoreDefaultThemeColor() {
|
||||
localStorage.removeItem("themeColor");
|
||||
changeDialogsTheme(THEME_COLOR, transparencyInput.value);
|
||||
}
|
||||
|
||||
function changeThemeHue(hue) {
|
||||
const {s, l} = d3.hsl(themeColorInput.value);
|
||||
const newColor = d3.hsl(+hue, s, l).hex();
|
||||
changeDialogsTheme(newColor, transparencyInput.value);
|
||||
}
|
||||
|
||||
// change color and transparency for modal windows
|
||||
function changeDialogsTheme(themeColor, transparency) {
|
||||
transparencyInput.value = transparencyOutput.value = transparency;
|
||||
const alpha = (100 - +transparency) / 100;
|
||||
const alphaReduced = Math.min(alpha + 0.3, 1);
|
||||
|
||||
const {h, s, l} = d3.hsl(themeColor || THEME_COLOR);
|
||||
themeColorInput.value = themeColor || THEME_COLOR;
|
||||
themeHueInput.value = h;
|
||||
|
||||
const getRGBA = (hue, saturation, lightness, alpha) => {
|
||||
const color = d3.hsl(hue, saturation, lightness, alpha);
|
||||
return color.toString();
|
||||
};
|
||||
|
||||
const theme = [
|
||||
{name: "--bg-main", h, s, l, alpha},
|
||||
{name: "--bg-lighter", h, s, l: l + 0.02, alpha},
|
||||
{name: "--bg-light", h, s: s - 0.02, l: l + 0.06, alpha},
|
||||
{name: "--light-solid", h, s: s + 0.01, l: l + 0.05, alpha: 1},
|
||||
{name: "--dark-solid", h, s, l: l - 0.2, alpha: 1},
|
||||
{name: "--header", h, s: s, l: l - 0.03, alpha: alphaReduced},
|
||||
{name: "--header-active", h, s: s, l: l - 0.09, alpha: alphaReduced},
|
||||
{name: "--bg-disabled", h, s: s - 0.04, l: l + 0.09, alphaReduced},
|
||||
{name: "--bg-dialogs", h: 0, s: 0, l: 0.98, alpha}
|
||||
];
|
||||
|
||||
const sx = document.documentElement.style;
|
||||
theme.forEach(({name, h, s, l, alpha}) => {
|
||||
sx.setProperty(name, getRGBA(h, s, l, alpha));
|
||||
});
|
||||
}
|
||||
|
||||
function changeZoomExtent(value) {
|
||||
const min = Math.max(+zoomExtentMin.value, 0.01),
|
||||
max = Math.min(+zoomExtentMax.value, 200);
|
||||
const min = Math.max(+zoomExtentMin.value, 0.01);
|
||||
const max = Math.min(+zoomExtentMax.value, 200);
|
||||
zoom.scaleExtent([min, max]);
|
||||
const scale = Math.max(Math.min(+value, 200), 0.01);
|
||||
const scale = minmax(+value, 0.01, 200);
|
||||
zoom.scaleTo(svg, scale);
|
||||
}
|
||||
|
||||
|
|
@ -482,13 +514,12 @@ function applyStoredOptions() {
|
|||
.map((w) => +w);
|
||||
if (localStorage.getItem('military')) options.military = JSON.parse(localStorage.getItem('military'));
|
||||
|
||||
changeDialogsTransparency(localStorage.getItem('transparency') || 5);
|
||||
if (localStorage.getItem('tooltipSize')) changeTooltipSize(localStorage.getItem('tooltipSize'));
|
||||
if (localStorage.getItem('regions')) changeStatesNumber(localStorage.getItem('regions'));
|
||||
|
||||
uiSizeInput.max = uiSizeOutput.max = getUImaxSize();
|
||||
if (localStorage.getItem('uiSize')) changeUIsize(localStorage.getItem('uiSize'));
|
||||
else changeUIsize(Math.max(Math.min(rn(mapWidthInput.value / 1280, 1), 2.5), 1));
|
||||
else changeUIsize(minmax(rn(mapWidthInput.value / 1280, 1), 1, 2.5));
|
||||
|
||||
// search params overwrite stored and default options
|
||||
const params = new URL(window.location.href).searchParams;
|
||||
|
|
@ -497,8 +528,14 @@ function applyStoredOptions() {
|
|||
if (width) mapWidthInput.value = width;
|
||||
if (height) mapHeightInput.value = height;
|
||||
|
||||
const transparency = localStorage.getItem("transparency") || 5;
|
||||
const themeColor = localStorage.getItem("themeColor");
|
||||
changeDialogsTheme(themeColor, transparency);
|
||||
|
||||
// set shape rendering
|
||||
viewbox.attr('shape-rendering', shapeRendering.value);
|
||||
|
||||
options.stateLabelsMode = stateLabelsModeInput.value;
|
||||
}
|
||||
|
||||
// randomize options if randomization is allowed (not locked or options='default')
|
||||
|
|
@ -529,10 +566,9 @@ function randomizeOptions() {
|
|||
|
||||
// 'Units Editor' settings
|
||||
const US = navigator.language === 'en-US';
|
||||
const UK = navigator.language === 'en-GB';
|
||||
if (randomize || !locked('distanceScale')) distanceScaleOutput.value = distanceScaleInput.value = gauss(3, 1, 1, 5);
|
||||
if (!stored('distanceUnit')) distanceUnitInput.value = US || UK ? 'mi' : 'km';
|
||||
if (!stored('heightUnit')) heightUnit.value = US || UK ? 'ft' : 'm';
|
||||
if (!stored("distanceUnit")) distanceUnitInput.value = US ? "mi" : "km";
|
||||
if (!stored("heightUnit")) heightUnit.value = US ? "ft" : "m";
|
||||
if (!stored('temperatureScale')) temperatureScale.value = US ? '°F' : '°C';
|
||||
|
||||
// World settings
|
||||
|
|
@ -619,22 +655,16 @@ function restoreDefaultOptions() {
|
|||
// Sticked menu Options listeners
|
||||
document.getElementById('sticked').addEventListener('click', function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === 'newMapButton') regeneratePrompt();
|
||||
if (id === "newMapButton") regeneratePrompt("sticky button");
|
||||
else if (id === 'saveButton') showSavePane();
|
||||
else if (id === 'loadButton') showLoadPane();
|
||||
else if (id === "exportButton") showExportPane();
|
||||
else if (id === 'zoomReset') resetZoom(1000);
|
||||
});
|
||||
|
||||
function regeneratePrompt() {
|
||||
if (customization) {
|
||||
tip('New map cannot be generated when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
return;
|
||||
}
|
||||
function regeneratePrompt(source) {
|
||||
if (customization) return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 5) {
|
||||
regenerateMap();
|
||||
return;
|
||||
}
|
||||
if (workingTime < 5) return regenerateMap(source);
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
||||
All unsaved changes made to the current map will be lost`;
|
||||
|
|
@ -647,19 +677,20 @@ function regeneratePrompt() {
|
|||
},
|
||||
Generate: function () {
|
||||
closeDialogs();
|
||||
regenerateMap();
|
||||
regenerateMap(source);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showSavePane() {
|
||||
document.getElementById('showLabels').checked = !hideLabels.checked;
|
||||
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||
sharableLinkContainer.style.display = "none";
|
||||
|
||||
$('#saveMapData').dialog({
|
||||
title: 'Save map',
|
||||
resizable: false,
|
||||
width: '30em',
|
||||
width: "25em",
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Close: function () {
|
||||
|
|
@ -669,21 +700,21 @@ function showSavePane() {
|
|||
});
|
||||
}
|
||||
|
||||
// download map data as GeoJSON
|
||||
function saveGeoJSON() {
|
||||
alertMessage.innerHTML = `You can export map data in GeoJSON format used in GIS tools such as QGIS.
|
||||
Check out ${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export', 'wiki-page')} for guidance`;
|
||||
function copyLinkToClickboard() {
|
||||
const shrableLink = document.getElementById("sharableLink");
|
||||
const link = shrableLink.getAttribute("href");
|
||||
navigator.clipboard.writeText(link).then(() => tip("Link is copied to the clipboard", true, "success", 8000));
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'GIS data export',
|
||||
function showExportPane() {
|
||||
document.getElementById("showLabels").checked = !hideLabels.checked;
|
||||
|
||||
$("#exportMapData").dialog({
|
||||
title: "Export map data",
|
||||
resizable: false,
|
||||
width: '35em',
|
||||
width: "26em",
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Cells: saveGeoJSON_Cells,
|
||||
Routes: saveGeoJSON_Routes,
|
||||
Rivers: saveGeoJSON_Rivers,
|
||||
Markers: saveGeoJSON_Markers,
|
||||
Close: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
|
|
@ -691,11 +722,11 @@ function saveGeoJSON() {
|
|||
});
|
||||
}
|
||||
|
||||
function showLoadPane() {
|
||||
async function showLoadPane() {
|
||||
$('#loadMapData').dialog({
|
||||
title: 'Load map',
|
||||
resizable: false,
|
||||
width: '17em',
|
||||
width: "24em",
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Close: function () {
|
||||
|
|
@ -703,6 +734,25 @@ function showLoadPane() {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
|
||||
const fileSelect = document.getElementById("loadFromDropboxSelect");
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function loadURL() {
|
||||
|
|
@ -747,7 +797,9 @@ function openSaveTiles() {
|
|||
status.innerHTML = '';
|
||||
let loading = null;
|
||||
|
||||
$('#saveTilesScreen').dialog({
|
||||
const inputs = document.getElementById("saveTilesScreen").querySelectorAll("input");
|
||||
inputs.forEach(input => input.addEventListener("input", updateTilesOptions));
|
||||
|
||||
resizable: false,
|
||||
title: 'Download tiles',
|
||||
width: '23em',
|
||||
|
|
@ -767,17 +819,12 @@ function openSaveTiles() {
|
|||
}
|
||||
},
|
||||
close: () => {
|
||||
debug.selectAll('*').remove();
|
||||
inputs.forEach(input => input.removeEventListener("input", updateTilesOptions));
|
||||
clearInterval(loading);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById('saveTilesScreen')
|
||||
.querySelectorAll('input')
|
||||
.forEach((el) => el.addEventListener('input', updateTilesOptions));
|
||||
|
||||
function updateTilesOptions() {
|
||||
if (this?.tagName === 'INPUT') {
|
||||
const {nextElementSibling: next, previousElementSibling: prev} = this;
|
||||
|
|
|
|||
1187
modules/ui/options.js.orig
Normal file
1187
modules/ui/options.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -34,7 +34,7 @@ function editProvinces() {
|
|||
document.getElementById('provincesManually').addEventListener('click', enterProvincesManualAssignent);
|
||||
document.getElementById('provincesManuallyApply').addEventListener('click', applyProvincesManualAssignent);
|
||||
document.getElementById('provincesManuallyCancel').addEventListener('click', () => exitProvincesManualAssignment());
|
||||
document.getElementById('provincesAdd').addEventListener('click', enterAddProvinceMode);
|
||||
document.getElementById('provincesRelease').addEventListener('click', triggerProvincesRelease);
|
||||
document.getElementById('provincesRecolor').addEventListener('click', recolorProvinces);
|
||||
|
||||
body.addEventListener('click', function (ev) {
|
||||
|
|
@ -148,7 +148,6 @@ function editProvinces() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Declare province independence (turn non-capital province with burgs into a new state)" class="icon-flag-empty ${separable ? '' : 'placeholder'} hide"></span>
|
||||
<span data-tip="Toggle province focus" class="icon-pin ${focused ? '' : ' inactive'} hide"></span>
|
||||
<span data-tip="Remove the province" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -228,74 +227,63 @@ function editProvinces() {
|
|||
function capitalZoomIn(p) {
|
||||
const capital = pack.provinces[p].burg;
|
||||
const l = burgLabels.select("[data-id='" + capital + "']");
|
||||
const x = +l.attr('x'),
|
||||
y = +l.attr('y');
|
||||
const x = +l.attr('x');
|
||||
const y = +l.attr('y');
|
||||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function triggerIndependencePromps(p) {
|
||||
alertMessage.innerHTML = 'Are you sure you want to declare province independence? <br>It will turn province into a new state';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
confirmationDialog({
|
||||
title: 'Declare independence',
|
||||
buttons: {
|
||||
Declare: function () {
|
||||
declareProvinceIndependence(p);
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
message: 'Are you sure you want to declare province independence? <br>It will turn province into a new state',
|
||||
confirm: 'Declare',
|
||||
onConfirm: () => {
|
||||
const [oldStateId, newStateId] = declareProvinceIndependence(p);
|
||||
updateStatesPostRelease([oldStateId], [newStateId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function declareProvinceIndependence(p) {
|
||||
const states = pack.states,
|
||||
provinces = pack.provinces,
|
||||
cells = pack.cells;
|
||||
if (provinces[p].burgs.some((b) => pack.burgs[b].capital)) {
|
||||
tip('Cannot declare independence of a province having capital burg. Please change capital first', false, 'error');
|
||||
return;
|
||||
}
|
||||
function declareProvinceIndependence(provinceId) {
|
||||
const {states, provinces, cells, burgs} = pack;
|
||||
const province = provinces[provinceId];
|
||||
const {name, burg: burgId, burgs: provinceBurgs} = province;
|
||||
|
||||
const oldState = pack.provinces[p].state;
|
||||
const newState = pack.states.length;
|
||||
if (provinceBurgs.some((b) => burgs[b].capital)) return tip('Cannot declare independence of a province having capital burg. Please change capital first', false, 'error');
|
||||
if (!burgId) return tip('Cannot declare independence of a province without burg', false, 'error');
|
||||
|
||||
const oldStateId = province.state;
|
||||
const newStateId = states.length;
|
||||
|
||||
// turn province burg into a capital
|
||||
const burg = provinces[p].burg;
|
||||
if (!burg) return;
|
||||
pack.burgs[burg].capital = 1;
|
||||
moveBurgToGroup(burg, 'cities');
|
||||
burgs[burgId].capital = 1;
|
||||
moveBurgToGroup(burgId, 'cities');
|
||||
|
||||
// move all burgs to a new state
|
||||
provinces[p].burgs.forEach((b) => (pack.burgs[b].state = newState));
|
||||
province.burgs.forEach((b) => (burgs[b].state = newStateId));
|
||||
|
||||
// difine new state attributes
|
||||
const center = pack.burgs[burg].cell;
|
||||
const culture = pack.burgs[burg].culture;
|
||||
const name = provinces[p].name;
|
||||
const {cell: center, culture} = burgs[burgId];
|
||||
const color = getRandomColor();
|
||||
|
||||
const coa = provinces[p].coa;
|
||||
const coaEl = document.getElementById('provinceCOA' + p);
|
||||
if (coaEl) coaEl.id = 'stateCOA' + newState;
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
const coa = province.coa;
|
||||
const coaEl = document.getElementById('provinceCOA' + provinceId);
|
||||
if (coaEl) coaEl.id = 'stateCOA' + newStateId;
|
||||
emblems.select(`#provinceEmblems > use[data-i='${provinceId}']`).remove();
|
||||
|
||||
// update cells
|
||||
cells.i
|
||||
.filter((i) => cells.province[i] === p)
|
||||
.filter((i) => cells.province[i] === provinceId)
|
||||
.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
cells.state[i] = newState;
|
||||
cells.state[i] = newStateId;
|
||||
});
|
||||
|
||||
// update diplomacy and reverse relations
|
||||
const diplomacy = states.map((s) => {
|
||||
if (!s.i || s.removed) return 'x';
|
||||
let relations = states[oldState].diplomacy[s.i]; // relations between Nth state and old overlord
|
||||
if (s.i === oldState) relations = 'Enemy';
|
||||
// new state is Enemy to its old overlord
|
||||
let relations = states[oldStateId].diplomacy[s.i]; // relations between Nth state and old overlord
|
||||
// new state is Enemy to its old owner
|
||||
if (s.i === oldStateId) relations = 'Enemy';
|
||||
else if (relations === 'Ally') relations = 'Suspicion';
|
||||
else if (relations === 'Friendly') relations = 'Suspicion';
|
||||
else if (relations === 'Suspicion') relations = 'Neutral';
|
||||
|
|
@ -307,28 +295,51 @@ function editProvinces() {
|
|||
return relations;
|
||||
});
|
||||
diplomacy.push('x');
|
||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]);
|
||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldStateId].name}`]);
|
||||
|
||||
// create new state
|
||||
states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: 'Generic', center, culture, military: [], alert: 1, coa});
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms([newState]);
|
||||
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
else drawStates();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
BurgsAndStates.drawStateLabels([newState, oldState]);
|
||||
states.push({
|
||||
i: newStateId,
|
||||
name,
|
||||
diplomacy,
|
||||
provinces: [],
|
||||
color,
|
||||
expansionism: 0.5,
|
||||
capital: burgId,
|
||||
type: 'Generic',
|
||||
center,
|
||||
culture,
|
||||
military: [],
|
||||
alert: 1,
|
||||
coa
|
||||
});
|
||||
|
||||
// remove old province
|
||||
unfog('focusProvince' + p);
|
||||
if (states[oldState].provinces.includes(p)) states[oldState].provinces.splice(states[oldState].provinces.indexOf(p), 1);
|
||||
provinces[p] = {i: p, removed: true};
|
||||
states[oldStateId].provinces = states[oldStateId].provinces.filter((p) => p !== provinceId);
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
|
||||
// draw emblem
|
||||
COArenderer.add('state', newState, coa, pack.states[newState].pole[0], pack.states[newState].pole[1]);
|
||||
return [oldStateId, newStateId];
|
||||
}
|
||||
|
||||
function updateStatesPostRelease(oldStates, newStates) {
|
||||
const allStates = unique([...oldStates, ...newStates]);
|
||||
|
||||
layerIsOn('toggleProvinces') && toggleProvinces();
|
||||
layerIsOn('toggleStates') ? drawStates() : toggleStates();
|
||||
layerIsOn('toggleBorders') ? drawBorders() : toggleBorders();
|
||||
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms(newStates);
|
||||
BurgsAndStates.drawStateLabels(allStates);
|
||||
|
||||
// redraw emblems
|
||||
allStates.forEach((stateId) => {
|
||||
emblems.select(`#stateEmblems > use[data-i='${stateId}']`)?.remove();
|
||||
const {coa, pole} = pack.states[stateId];
|
||||
COArenderer.add('state', stateId, coa, ...pole);
|
||||
});
|
||||
|
||||
unfog();
|
||||
closeDialogs();
|
||||
editStates();
|
||||
}
|
||||
|
|
@ -547,7 +558,17 @@ function editProvinces() {
|
|||
const provinces = pack.provinces
|
||||
.filter((p) => p.i && !p.removed)
|
||||
.map((p) => {
|
||||
return {id: p.i + states.length - 1, i: p.i, state: p.state, color: p.color, name: p.name, fullName: p.fullName, area: p.area, urban: p.urban, rural: p.rural};
|
||||
return {
|
||||
id: p.i + states.length - 1,
|
||||
i: p.i,
|
||||
state: p.state,
|
||||
color: p.color,
|
||||
name: p.name,
|
||||
fullName: p.fullName,
|
||||
area: p.area,
|
||||
urban: p.urban,
|
||||
rural: p.rural
|
||||
};
|
||||
});
|
||||
const data = states.concat(provinces);
|
||||
const root = d3
|
||||
|
|
@ -571,8 +592,6 @@ function editProvinces() {
|
|||
</select>`;
|
||||
alertMessage.innerHTML += `<div id='provinceInfo' class='chartInfo'>‍</div>`;
|
||||
const svg = d3.select('#alertMessage').insert('svg', '#provinceInfo').attr('id', 'provincesTree').attr('width', width).attr('height', height).attr('font-size', '10px');
|
||||
const graph = svg.append('g').attr('transform', `translate(10, 0)`);
|
||||
document.getElementById('provincesTreeType').addEventListener('change', updateChart);
|
||||
|
||||
treeLayout(root);
|
||||
|
||||
|
|
@ -694,6 +713,34 @@ function editProvinces() {
|
|||
provs.selectAll('text').call(d3.drag().on('drag', dragLabel)).classed('draggable', true);
|
||||
}
|
||||
|
||||
function triggerProvincesRelease() {
|
||||
confirmationDialog({
|
||||
title: 'Release provinces',
|
||||
message: `Are you sure you want to release all provinces?
|
||||
</br>It will turn all separable provinces into independent states.
|
||||
</br>Capital province and provinces without any burgs will state as they are`,
|
||||
confirm: 'Release',
|
||||
onConfirm: () => {
|
||||
const oldStateIds = [];
|
||||
const newStateIds = [];
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach((el) => {
|
||||
const provinceId = +el.dataset.id;
|
||||
const province = pack.provinces[provinceId];
|
||||
if (!province.burg) return;
|
||||
if (province.burg === pack.states[province.state].capital) return;
|
||||
if (province.burgs.some((burgId) => pack.burgs[burgId].capital)) return;
|
||||
|
||||
const [oldStateId, newStateId] = declareProvinceIndependence(provinceId);
|
||||
oldStateIds.push(oldStateId);
|
||||
newStateIds.push(newStateId);
|
||||
});
|
||||
|
||||
updateStatesPostRelease(unique(oldStateIds), newStateIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enterProvincesManualAssignent() {
|
||||
if (!layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
|
|
@ -852,10 +899,8 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function enterAddProvinceMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
exitAddProvinceMode();
|
||||
return;
|
||||
}
|
||||
if (this.classList.contains('pressed')) return exitAddProvinceMode();
|
||||
|
||||
customization = 12;
|
||||
this.classList.add('pressed');
|
||||
tip('Click on the map to place a new province center', true);
|
||||
|
|
@ -864,24 +909,16 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function addProvince() {
|
||||
const cells = pack.cells,
|
||||
provinces = pack.provinces;
|
||||
const {cells, provinces} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const center = findCell(point[0], point[1]);
|
||||
if (cells.h[center] < 20) {
|
||||
tip('You cannot place province into the water. Please click on a land cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (cells.h[center] < 20) return tip('You cannot place province into the water. Please click on a land cell', false, 'error');
|
||||
|
||||
const oldProvince = cells.province[center];
|
||||
if (oldProvince && provinces[oldProvince].center === center) {
|
||||
tip('The cell is already a center of a different province. Select other cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (oldProvince && provinces[oldProvince].center === center) return tip('The cell is already a center of a different province. Select other cell', false, 'error');
|
||||
|
||||
const state = cells.state[center];
|
||||
if (!state) {
|
||||
tip('You cannot create a province in neutral lands. Please assign this land to a state first', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (!state) return tip('You cannot create a province in neutral lands. Please assign this land to a state first', false, 'error');
|
||||
|
||||
if (d3.event.shiftKey === false) exitAddProvinceMode();
|
||||
|
||||
|
|
@ -892,8 +929,8 @@ function editProvinces() {
|
|||
const name = burg ? pack.burgs[burg].name : Names.getState(Names.getCultureShort(c), c);
|
||||
const formName = oldProvince ? provinces[oldProvince].formName : 'Province';
|
||||
const fullName = name + ' ' + formName;
|
||||
const stateColor = pack.states[state].color,
|
||||
rndColor = getRandomColor();
|
||||
const stateColor = pack.states[state].color;
|
||||
const rndColor = getRandomColor();
|
||||
const color = stateColor[0] === '#' ? d3.color(d3.interpolate(stateColor, rndColor)(0.2)).hex() : rndColor;
|
||||
|
||||
// generate emblem
|
||||
|
|
@ -947,20 +984,20 @@ function editProvinces() {
|
|||
|
||||
function downloadProvincesData() {
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,Province,Form,State,Color,Capital,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
let data = 'Id,Province,Full Name,Form,State,Color,Capital,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
let key = parseInt(el.dataset.id);
|
||||
data += el.dataset.id + ',';
|
||||
const key = parseInt(el.dataset.id);
|
||||
const provincePack = pack.provinces[key];
|
||||
data += el.dataset.name + ',';
|
||||
data += el.dataset.form + ',';
|
||||
data += el.dataset.state + ',';
|
||||
data += provincePack.fullName + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.capital + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += `${Math.round(pack.provinces[key].rural * populationRate)},`;
|
||||
data += `${Math.round(pack.provinces[key].urban * populationRate * urbanization)}\n`;
|
||||
data += `${Math.round(provincePack.rural * populationRate)},`;
|
||||
data += `${Math.round(provincePack.urban * populationRate * urbanization)}\n`;
|
||||
});
|
||||
|
||||
const name = getFileName('Provinces') + '.csv';
|
||||
|
|
|
|||
1288
modules/ui/provinces-editor.js.orig
Normal file
1288
modules/ui/provinces-editor.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,49 +1,53 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function overviewRegiments(state) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
if (!layerIsOn("toggleMilitary")) toggleMilitary();
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleMilitary')) toggleMilitary();
|
||||
|
||||
const body = document.getElementById("regimentsBody");
|
||||
const body = document.getElementById('regimentsBody');
|
||||
updateFilter(state);
|
||||
addLines();
|
||||
$("#regimentsOverview").dialog();
|
||||
$('#regimentsOverview').dialog();
|
||||
|
||||
if (modules.overviewRegiments) return;
|
||||
modules.overviewRegiments = true;
|
||||
updateHeaders();
|
||||
|
||||
$("#regimentsOverview").dialog({
|
||||
title: "Regiments Overview", resizable: false, width: fitContent(),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
$('#regimentsOverview').dialog({
|
||||
title: 'Regiments Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("regimentsOverviewRefresh").addEventListener("click", addLines);
|
||||
document.getElementById("regimentsPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("regimentsAddNew").addEventListener("click", toggleAdd);
|
||||
document.getElementById("regimentsExport").addEventListener("click", downloadRegimentsData);
|
||||
document.getElementById("regimentsFilter").addEventListener("change", addLines);
|
||||
document.getElementById('regimentsOverviewRefresh').addEventListener('click', addLines);
|
||||
document.getElementById('regimentsPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('regimentsAddNew').addEventListener('click', toggleAdd);
|
||||
document.getElementById('regimentsExport').addEventListener('click', downloadRegimentsData);
|
||||
document.getElementById('regimentsFilter').addEventListener('change', addLines);
|
||||
|
||||
// update military types in header and tooltips
|
||||
function updateHeaders() {
|
||||
const header = document.getElementById("regimentsHeader");
|
||||
header.querySelectorAll(".removable").forEach(el => el.remove());
|
||||
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
|
||||
const header = document.getElementById('regimentsHeader');
|
||||
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, ' '));
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function addLines() {
|
||||
const state = +regimentsFilter.value;
|
||||
body.innerHTML = "";
|
||||
let lines = "";
|
||||
body.innerHTML = '';
|
||||
let lines = '';
|
||||
const regiments = [];
|
||||
|
||||
for (const s of pack.states) {
|
||||
|
|
@ -51,8 +55,8 @@ 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>
|
||||
|
|
@ -70,90 +74,98 @@ 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(" ")}
|
||||
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
|
||||
${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();}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(regimentsHeader);
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => regimentHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => regimentHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => regimentHighlightOff(ev)));
|
||||
}
|
||||
|
||||
function updateFilter(state) {
|
||||
const filter = document.getElementById("regimentsFilter");
|
||||
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);
|
||||
statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
|
||||
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)));
|
||||
}
|
||||
|
||||
function regimentHighlightOn(event) {
|
||||
const state = +event.target.dataset.s;
|
||||
const id = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(2000).style("fill", "#ff0000");
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(2000).style('fill', '#ff0000');
|
||||
}
|
||||
|
||||
function regimentHighlightOff(event) {
|
||||
const state = +event.target.dataset.s;
|
||||
const id = +event.target.dataset.id;
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(1000).style("fill", null);
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(1000).style('fill', null);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
const lines = body.querySelectorAll(":scope > div:not(.totalLine)");
|
||||
const array = Array.from(lines), cache = [];
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const lines = body.querySelectorAll(':scope > div:not(.totalLine)');
|
||||
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]));
|
||||
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%";
|
||||
if (type === 'rate') return;
|
||||
div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + '%' : '0%';
|
||||
});
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
body.dataset.type = 'absolute';
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAdd() {
|
||||
document.getElementById("regimentsAddNew").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentsAddNew").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", addRegimentOnClick);
|
||||
tip("Click on map to create new regiment or fleet", true);
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.add("pressed");
|
||||
document.getElementById('regimentsAddNew').classList.toggle('pressed');
|
||||
if (document.getElementById('regimentsAddNew').classList.contains('pressed')) {
|
||||
viewbox.style('cursor', 'crosshair').on('click', addRegimentOnClick);
|
||||
tip('Click on map to create new regiment or fleet', true);
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.add('pressed');
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
viewbox.on('click', clicked).style('cursor', 'default');
|
||||
addLines();
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.remove("pressed");
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.remove('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -162,19 +174,18 @@ 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
|
||||
const units = options.military.map((u) => u.name);
|
||||
let data = 'State,Id,Name,' + units.map((u) => capitalize(u)).join(',') + ',Total\n'; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) {
|
||||
data += el.dataset.state + ",";
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += units.map(u => el.dataset[u]).join(",") + ",";
|
||||
data += el.dataset.total + "\n";
|
||||
body.querySelectorAll(':scope > div:not(.totalLine)').forEach(function (el) {
|
||||
data += el.dataset.state + ',';
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += units.map((u) => el.dataset[u]).join(',') + ',';
|
||||
data += el.dataset.total + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Regiments") + ".csv";
|
||||
const name = getFileName('Regiments') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function createRiver() {
|
||||
if (customization) return;
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
document.getElementById("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
||||
if (!layerIsOn("toggleCells")) toggleCells();
|
||||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
tip("Click to add river point, click again to remove", true);
|
||||
debug.append("g").attr("id", "controlCells");
|
||||
viewbox.style("cursor", "crosshair").on("click", onCellClick);
|
||||
tip('Click to add river point, click again to remove', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
viewbox.style('cursor', 'crosshair').on('click', onCellClick);
|
||||
|
||||
createRiver.cells = [];
|
||||
const body = document.getElementById("riverCreatorBody");
|
||||
const body = document.getElementById('riverCreatorBody');
|
||||
|
||||
$("#riverCreator").dialog({
|
||||
title: "Create River",
|
||||
$('#riverCreator').dialog({
|
||||
title: 'Create River',
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: '#map'},
|
||||
close: closeRiverCreator
|
||||
});
|
||||
|
||||
|
|
@ -25,14 +25,14 @@ function createRiver() {
|
|||
modules.createRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("riverCreatorComplete").addEventListener("click", addRiver);
|
||||
document.getElementById("riverCreatorCancel").addEventListener("click", () => $("#riverCreator").dialog("close"));
|
||||
body.addEventListener("click", function (ev) {
|
||||
document.getElementById('riverCreatorComplete').addEventListener('click', addRiver);
|
||||
document.getElementById('riverCreatorCancel').addEventListener('click', () => $('#riverCreator').dialog('close'));
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target;
|
||||
const cl = el.classList;
|
||||
const cell = +el.parentNode.dataset.cell;
|
||||
if (cl.contains("editFlux")) pack.cells.fl[cell] = +el.value;
|
||||
else if (cl.contains("icon-trash-empty")) removeCell(cell);
|
||||
if (cl.contains('editFlux')) pack.cells.fl[cell] = +el.value;
|
||||
else if (cl.contains('icon-trash-empty')) removeCell(cell);
|
||||
});
|
||||
|
||||
function onCellClick() {
|
||||
|
|
@ -57,19 +57,19 @@ function createRiver() {
|
|||
}
|
||||
|
||||
function removeCell(cell) {
|
||||
createRiver.cells = createRiver.cells.filter(c => c !== cell);
|
||||
createRiver.cells = createRiver.cells.filter((c) => c !== cell);
|
||||
drawCells(createRiver.cells);
|
||||
body.querySelector(`div[data-cell='${cell}']`)?.remove();
|
||||
}
|
||||
|
||||
function drawCells(cells) {
|
||||
debug
|
||||
.select("#controlCells")
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon`)
|
||||
.data(cells)
|
||||
.join("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("class", "current");
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', 'current');
|
||||
}
|
||||
|
||||
function addRiver() {
|
||||
|
|
@ -77,12 +77,12 @@ function createRiver() {
|
|||
const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin} = Rivers;
|
||||
|
||||
const riverCells = createRiver.cells;
|
||||
if (riverCells.length < 2) return tip("Add at least 2 cells", false, "error");
|
||||
if (riverCells.length < 2) return tip('Add at least 2 cells', false, 'error');
|
||||
|
||||
const riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
const parent = cells.r[last(riverCells)] || riverId;
|
||||
|
||||
riverCells.forEach(cell => {
|
||||
riverCells.forEach((cell) => {
|
||||
if (!cells.r[cell]) cells.r[cell] = riverId;
|
||||
});
|
||||
|
||||
|
|
@ -99,27 +99,24 @@ function createRiver() {
|
|||
const name = getName(mouth);
|
||||
const basin = getBasin(parent);
|
||||
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: 'River'});
|
||||
const id = 'river' + riverId;
|
||||
|
||||
// render river
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
viewbox
|
||||
.select("#rivers")
|
||||
.append("path")
|
||||
.attr("id", "river" + riverId)
|
||||
.attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
viewbox.select('#rivers').append('path').attr('id', id).attr('d', getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
|
||||
editRiver(riverId);
|
||||
editRiver(id);
|
||||
}
|
||||
|
||||
function closeRiverCreator() {
|
||||
body.innerHTML = "";
|
||||
debug.select("#controlCells").remove();
|
||||
body.innerHTML = '';
|
||||
debug.select('#controlCells').remove();
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById("toggleCells").dataset.forced;
|
||||
document.getElementById("toggleCells").dataset.forced = 0;
|
||||
if (forced && layerIsOn("toggleCells")) toggleCells();
|
||||
const forced = +document.getElementById('toggleCells').dataset.forced;
|
||||
document.getElementById('toggleCells').dataset.forced = 0;
|
||||
if (forced && layerIsOn('toggleCells')) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ function editRiver(id) {
|
|||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
elSelected = d3.select('#' + id);
|
||||
elSelected = d3.select('#' + id).on('click', addControlPoint);
|
||||
|
||||
tip('Drag control points to change the river course. For major changes please create a new river instead', true);
|
||||
tip('Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
debug.append('g').attr('id', 'controlPoints');
|
||||
|
||||
|
|
@ -19,8 +19,8 @@ function editRiver(id) {
|
|||
const river = getRiver();
|
||||
const {cells, points} = river;
|
||||
const riverPoints = Rivers.getRiverPoints(cells, points);
|
||||
drawControlPoints(riverPoints, cells);
|
||||
drawCells(cells, 'current');
|
||||
drawControlPoints(riverPoints);
|
||||
drawCells(cells);
|
||||
|
||||
$('#riverEditor').dialog({
|
||||
title: 'Edit River',
|
||||
|
|
@ -92,37 +92,35 @@ function editRiver(id) {
|
|||
document.getElementById('riverWidth').value = width;
|
||||
}
|
||||
|
||||
function drawControlPoints(points, cells) {
|
||||
function drawControlPoints(points) {
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.data(points)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.join('circle')
|
||||
.attr('cx', (d) => d[0])
|
||||
.attr('cy', (d) => d[1])
|
||||
.attr('r', 0.6)
|
||||
.attr('data-cell', (d, i) => cells[i])
|
||||
.attr('data-i', (d, i) => i)
|
||||
.call(d3.drag().on('start', dragControlPoint));
|
||||
.call(d3.drag().on('start', dragControlPoint))
|
||||
.on('click', removeControlPoint);
|
||||
}
|
||||
|
||||
function drawCells(cells, type) {
|
||||
function drawCells(cells) {
|
||||
const validCells = [...new Set(cells)].filter((i) => pack.cells.i[i]);
|
||||
debug
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon.${type}`)
|
||||
.data(cells.filter((i) => pack.cells.i[i]))
|
||||
.selectAll(`polygon`)
|
||||
.data(validCells)
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', type);
|
||||
.attr('points', (d) => getPackPolygon(d));
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
const {i, r, fl} = pack.cells;
|
||||
const {r, fl} = pack.cells;
|
||||
const river = getRiver();
|
||||
|
||||
const initCell = +this.dataset.cell;
|
||||
const index = +this.dataset.i;
|
||||
const {x: x0, y: y0} = d3.event;
|
||||
const initCell = findCell(x0, y0);
|
||||
|
||||
let movedToCell = null;
|
||||
|
||||
|
|
@ -136,22 +134,18 @@ function editRiver(id) {
|
|||
this.setAttribute('cy', y);
|
||||
this.__data__ = [rn(x, 1), rn(y, 1)];
|
||||
redrawRiver();
|
||||
drawCells(river.cells);
|
||||
});
|
||||
|
||||
d3.event.on('end', () => {
|
||||
if (movedToCell) {
|
||||
this.dataset.cell = movedToCell;
|
||||
river.cells[index] = movedToCell;
|
||||
drawCells(river.cells, 'current');
|
||||
|
||||
if (!r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
}
|
||||
if (movedToCell && !r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
redrawRiver();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -159,8 +153,10 @@ function editRiver(id) {
|
|||
function redrawRiver() {
|
||||
const river = getRiver();
|
||||
river.points = debug.selectAll('#controlPoints > *').data();
|
||||
const {cells, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(cells, river.points);
|
||||
river.cells = river.points.map(([x, y]) => findCell(x, y));
|
||||
|
||||
const {widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
||||
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
|
|
@ -170,6 +166,27 @@ function editRiver(id) {
|
|||
if (modules.elevation) showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function addControlPoint() {
|
||||
const [x, y] = d3.mouse(this);
|
||||
const point = [rn(x, 1), rn(y, 1)];
|
||||
|
||||
const river = getRiver();
|
||||
if (!river.points) river.points = debug.selectAll('#controlPoints > *').data();
|
||||
|
||||
const index = getSegmentId(river.points, point, 2);
|
||||
river.points.splice(index, 0, point);
|
||||
drawControlPoints(river.points);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function removeControlPoint() {
|
||||
this.remove();
|
||||
redrawRiver();
|
||||
|
||||
const {cells} = getRiver();
|
||||
drawCells(cells);
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
getRiver().name = this.value;
|
||||
}
|
||||
|
|
@ -244,6 +261,8 @@ function editRiver(id) {
|
|||
function closeRiverEditor() {
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.select('#controlCells').remove();
|
||||
|
||||
elSelected.on('click', null);
|
||||
unselect();
|
||||
clearMainTip();
|
||||
|
||||
|
|
|
|||
334
modules/ui/rivers-editor.js.orig
Normal file
334
modules/ui/rivers-editor.js.orig
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
'use strict';
|
||||
function editRiver(id) {
|
||||
if (customization) return;
|
||||
if (elSelected && id === elSelected.attr('id')) return;
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
<<<<<<< HEAD
|
||||
elSelected = d3.select('#' + id);
|
||||
|
||||
tip('Drag control points to change the river course. For major changes please create a new river instead', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
debug.append('g').attr('id', 'controlPoints');
|
||||
=======
|
||||
elSelected = d3.select("#" + id).on("click", addControlPoint);
|
||||
|
||||
tip("Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead", true);
|
||||
debug.append("g").attr("id", "controlCells");
|
||||
debug.append("g").attr("id", "controlPoints");
|
||||
>>>>>>> master
|
||||
|
||||
updateRiverData();
|
||||
|
||||
const river = getRiver();
|
||||
const {cells, points} = river;
|
||||
const riverPoints = Rivers.getRiverPoints(cells, points);
|
||||
<<<<<<< HEAD
|
||||
drawControlPoints(riverPoints, cells);
|
||||
drawCells(cells, 'current');
|
||||
=======
|
||||
drawControlPoints(riverPoints);
|
||||
drawCells(cells);
|
||||
>>>>>>> master
|
||||
|
||||
$('#riverEditor').dialog({
|
||||
title: 'Edit River',
|
||||
resizable: false,
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: '#map'},
|
||||
close: closeRiverEditor
|
||||
});
|
||||
|
||||
if (modules.editRiver) return;
|
||||
modules.editRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('riverCreateSelectingCells').addEventListener('click', createRiver);
|
||||
document.getElementById('riverEditStyle').addEventListener('click', () => editStyle('rivers'));
|
||||
document.getElementById('riverElevationProfile').addEventListener('click', showElevationProfile);
|
||||
document.getElementById('riverLegend').addEventListener('click', editRiverLegend);
|
||||
document.getElementById('riverRemove').addEventListener('click', removeRiver);
|
||||
document.getElementById('riverName').addEventListener('input', changeName);
|
||||
document.getElementById('riverType').addEventListener('input', changeType);
|
||||
document.getElementById('riverNameCulture').addEventListener('click', generateNameCulture);
|
||||
document.getElementById('riverNameRandom').addEventListener('click', generateNameRandom);
|
||||
document.getElementById('riverMainstem').addEventListener('change', changeParent);
|
||||
document.getElementById('riverSourceWidth').addEventListener('input', changeSourceWidth);
|
||||
document.getElementById('riverWidthFactor').addEventListener('input', changeWidthFactor);
|
||||
|
||||
function getRiver() {
|
||||
const riverId = +elSelected.attr('id').slice(5);
|
||||
const river = pack.rivers.find((r) => r.i === riverId);
|
||||
return river;
|
||||
}
|
||||
|
||||
function updateRiverData() {
|
||||
const r = getRiver();
|
||||
|
||||
document.getElementById('riverName').value = r.name;
|
||||
document.getElementById('riverType').value = r.type;
|
||||
|
||||
const parentSelect = document.getElementById('riverMainstem');
|
||||
parentSelect.options.length = 0;
|
||||
const parent = r.parent || r.i;
|
||||
const sortedRivers = pack.rivers.slice().sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
sortedRivers.forEach((river) => {
|
||||
const opt = new Option(river.name, river.i, false, river.i === parent);
|
||||
parentSelect.options.add(opt);
|
||||
});
|
||||
document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
|
||||
|
||||
document.getElementById('riverDischarge').value = r.discharge + ' m³/s';
|
||||
document.getElementById('riverSourceWidth').value = r.sourceWidth;
|
||||
document.getElementById('riverWidthFactor').value = r.widthFactor;
|
||||
|
||||
updateRiverLength(r);
|
||||
updateRiverWidth(r);
|
||||
}
|
||||
|
||||
function updateRiverLength(river) {
|
||||
river.length = rn(elSelected.node().getTotalLength() / 2, 2);
|
||||
const lengthUI = `${rn(river.length * distanceScaleInput.value)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverLength').value = lengthUI;
|
||||
}
|
||||
|
||||
function updateRiverWidth(river) {
|
||||
const {addMeandering, getWidth, getOffset} = Rivers;
|
||||
const {cells, discharge, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = addMeandering(cells);
|
||||
river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth));
|
||||
|
||||
const width = `${rn(river.width * distanceScaleInput.value, 3)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverWidth').value = width;
|
||||
}
|
||||
|
||||
function drawControlPoints(points) {
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.data(points)
|
||||
<<<<<<< HEAD
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('cx', (d) => d[0])
|
||||
.attr('cy', (d) => d[1])
|
||||
.attr('r', 0.6)
|
||||
.attr('data-cell', (d, i) => cells[i])
|
||||
.attr('data-i', (d, i) => i)
|
||||
.call(d3.drag().on('start', dragControlPoint));
|
||||
=======
|
||||
.join("circle")
|
||||
.attr("cx", d => d[0])
|
||||
.attr("cy", d => d[1])
|
||||
.attr("r", 0.6)
|
||||
.call(d3.drag().on("start", dragControlPoint))
|
||||
.on("click", removeControlPoint);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function drawCells(cells) {
|
||||
const validCells = [...new Set(cells)].filter(i => pack.cells.i[i]);
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon.${type}`)
|
||||
.data(cells.filter((i) => pack.cells.i[i]))
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', type);
|
||||
=======
|
||||
.select("#controlCells")
|
||||
.selectAll(`polygon`)
|
||||
.data(validCells)
|
||||
.join("polygon")
|
||||
.attr("points", d => getPackPolygon(d));
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
const {r, fl} = pack.cells;
|
||||
const river = getRiver();
|
||||
|
||||
const {x: x0, y: y0} = d3.event;
|
||||
const initCell = findCell(x0, y0);
|
||||
|
||||
let movedToCell = null;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const {x, y} = d3.event;
|
||||
const currentCell = findCell(x, y);
|
||||
|
||||
movedToCell = initCell !== currentCell ? currentCell : null;
|
||||
|
||||
this.setAttribute('cx', x);
|
||||
this.setAttribute('cy', y);
|
||||
this.__data__ = [rn(x, 1), rn(y, 1)];
|
||||
redrawRiver();
|
||||
drawCells(river.cells);
|
||||
});
|
||||
|
||||
<<<<<<< HEAD
|
||||
d3.event.on('end', () => {
|
||||
if (movedToCell) {
|
||||
this.dataset.cell = movedToCell;
|
||||
river.cells[index] = movedToCell;
|
||||
drawCells(river.cells, 'current');
|
||||
|
||||
if (!r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
}
|
||||
=======
|
||||
d3.event.on("end", () => {
|
||||
if (movedToCell && !r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
redrawRiver();
|
||||
>>>>>>> master
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function redrawRiver() {
|
||||
const river = getRiver();
|
||||
<<<<<<< HEAD
|
||||
river.points = debug.selectAll('#controlPoints > *').data();
|
||||
const {cells, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(cells, river.points);
|
||||
=======
|
||||
river.points = debug.selectAll("#controlPoints > *").data();
|
||||
river.cells = river.points.map(([x, y]) => findCell(x, y));
|
||||
|
||||
const {widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
||||
>>>>>>> master
|
||||
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
elSelected.attr('d', path);
|
||||
|
||||
updateRiverLength(river);
|
||||
if (modules.elevation) showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function addControlPoint() {
|
||||
const [x, y] = d3.mouse(this);
|
||||
const point = [rn(x, 1), rn(y, 1)];
|
||||
|
||||
const river = getRiver();
|
||||
if (!river.points) river.points = debug.selectAll("#controlPoints > *").data();
|
||||
|
||||
const index = getSegmentId(river.points, point, 2);
|
||||
river.points.splice(index, 0, point);
|
||||
drawControlPoints(river.points);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function removeControlPoint() {
|
||||
this.remove();
|
||||
redrawRiver();
|
||||
|
||||
const {cells} = getRiver();
|
||||
drawCells(cells);
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
getRiver().name = this.value;
|
||||
}
|
||||
|
||||
function changeType() {
|
||||
getRiver().type = this.value;
|
||||
}
|
||||
|
||||
function generateNameCulture() {
|
||||
const r = getRiver();
|
||||
r.name = riverName.value = Rivers.getName(r.mouth);
|
||||
}
|
||||
|
||||
function generateNameRandom() {
|
||||
const r = getRiver();
|
||||
if (r) r.name = riverName.value = Names.getBase(rand(nameBases.length - 1));
|
||||
}
|
||||
|
||||
function changeParent() {
|
||||
const r = getRiver();
|
||||
r.parent = +this.value;
|
||||
r.basin = pack.rivers.find((river) => river.i === r.parent).basin;
|
||||
document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
|
||||
}
|
||||
|
||||
function changeSourceWidth() {
|
||||
const river = getRiver();
|
||||
river.sourceWidth = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function changeWidthFactor() {
|
||||
const river = getRiver();
|
||||
river.widthFactor = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function showElevationProfile() {
|
||||
modules.elevation = true;
|
||||
showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function editRiverLegend() {
|
||||
const id = elSelected.attr('id');
|
||||
const river = getRiver();
|
||||
editNotes(id, river.name + ' ' + river.type);
|
||||
}
|
||||
|
||||
function removeRiver() {
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the river and all its tributaries';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river and tributaries',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
const river = +elSelected.attr('id').slice(5);
|
||||
Rivers.remove(river);
|
||||
elSelected.remove();
|
||||
$('#riverEditor').dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRiverEditor() {
|
||||
<<<<<<< HEAD
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.select('#controlCells').remove();
|
||||
=======
|
||||
debug.select("#controlPoints").remove();
|
||||
debug.select("#controlCells").remove();
|
||||
|
||||
elSelected.on("click", null);
|
||||
>>>>>>> master
|
||||
unselect();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById('toggleCells').dataset.forced;
|
||||
document.getElementById('toggleCells').dataset.forced = 0;
|
||||
if (forced && layerIsOn('toggleCells')) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ function overviewRivers() {
|
|||
function zoomToRiver() {
|
||||
const r = +this.parentNode.dataset.id;
|
||||
const river = rivers.select('#river' + r).node();
|
||||
highlightElement(river);
|
||||
highlightElement(river, 3);
|
||||
}
|
||||
|
||||
function toggleBasinsHightlight() {
|
||||
|
|
|
|||
187
modules/ui/rivers-overview.js.orig
Normal file
187
modules/ui/rivers-overview.js.orig
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
'use strict';
|
||||
function overviewRivers() {
|
||||
if (customization) return;
|
||||
closeDialogs('#riversOverview, .stable');
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
const body = document.getElementById('riversBody');
|
||||
riversOverviewAddLines();
|
||||
$('#riversOverview').dialog();
|
||||
|
||||
if (modules.overviewRivers) return;
|
||||
modules.overviewRivers = true;
|
||||
|
||||
$('#riversOverview').dialog({
|
||||
title: 'Rivers Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('riversOverviewRefresh').addEventListener('click', riversOverviewAddLines);
|
||||
document.getElementById('addNewRiver').addEventListener('click', toggleAddRiver);
|
||||
document.getElementById('riverCreateNew').addEventListener('click', createRiver);
|
||||
document.getElementById('riversBasinHighlight').addEventListener('click', toggleBasinsHightlight);
|
||||
document.getElementById('riversExport').addEventListener('click', downloadRiversData);
|
||||
document.getElementById('riversRemoveAll').addEventListener('click', triggerAllRiversRemove);
|
||||
|
||||
// add line for each river
|
||||
function riversOverviewAddLines() {
|
||||
body.innerHTML = '';
|
||||
let lines = '';
|
||||
const unit = distanceUnitInput.value;
|
||||
|
||||
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 basin = pack.rivers.find((river) => river.i === r.basin)?.name;
|
||||
|
||||
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-type="${r.type}" data-discharge="${r.discharge}" data-length="${r.length}" data-width="${r.width}" data-basin="${basin}">
|
||||
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
|
||||
<div data-tip="River name" class="riverName">${r.name}</div>
|
||||
<div data-tip="River type name" class="riverType">${r.type}</div>
|
||||
<div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div>
|
||||
<div data-tip="River length from source to mouth" class="biomeArea">${length}</div>
|
||||
<div data-tip="River mouth width" class="biomeArea">${width}</div>
|
||||
<input data-tip="River basin (name of the main stem)" class="stateName" value="${basin}" disabled>
|
||||
<span data-tip="Edit river" class="icon-pencil"></span>
|
||||
<span data-tip="Remove river" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
|
||||
// update footer
|
||||
riversFooterNumber.innerHTML = pack.rivers.length;
|
||||
const averageDischarge = rn(d3.mean(pack.rivers.map((r) => r.discharge)));
|
||||
riversFooterDischarge.innerHTML = averageDischarge + ' m³/s';
|
||||
const averageLength = rn(d3.mean(pack.rivers.map((r) => r.length)));
|
||||
riversFooterLength.innerHTML = averageLength * distanceScaleInput.value + ' ' + unit;
|
||||
const averageWidth = rn(d3.mean(pack.rivers.map((r) => r.width)), 3);
|
||||
riversFooterWidth.innerHTML = rn(averageWidth * distanceScaleInput.value, 3) + ' ' + unit;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => riverHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => riverHighlightOff(ev)));
|
||||
body.querySelectorAll('div > span.icon-dot-circled').forEach((el) => el.addEventListener('click', zoomToRiver));
|
||||
body.querySelectorAll('div > span.icon-pencil').forEach((el) => el.addEventListener('click', openRiverEditor));
|
||||
body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', triggerRiverRemove));
|
||||
|
||||
applySorting(riversHeader);
|
||||
}
|
||||
|
||||
function riverHighlightOn(event) {
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
const r = +event.target.dataset.id;
|
||||
rivers
|
||||
.select('#river' + r)
|
||||
.attr('stroke', 'red')
|
||||
.attr('stroke-width', 1);
|
||||
}
|
||||
|
||||
function riverHighlightOff(e) {
|
||||
const r = +e.target.dataset.id;
|
||||
rivers
|
||||
.select('#river' + r)
|
||||
.attr('stroke', null)
|
||||
.attr('stroke-width', null);
|
||||
}
|
||||
|
||||
function zoomToRiver() {
|
||||
const r = +this.parentNode.dataset.id;
|
||||
<<<<<<< HEAD
|
||||
const river = rivers.select('#river' + r).node();
|
||||
highlightElement(river);
|
||||
=======
|
||||
const river = rivers.select("#river" + r).node();
|
||||
highlightElement(river, 3);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function toggleBasinsHightlight() {
|
||||
if (rivers.attr('data-basin') === 'hightlighted') {
|
||||
rivers.selectAll('*').attr('fill', null);
|
||||
rivers.attr('data-basin', null);
|
||||
} else {
|
||||
rivers.attr('data-basin', 'hightlighted');
|
||||
const basins = [...new Set(pack.rivers.map((r) => r.basin))];
|
||||
const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'];
|
||||
|
||||
basins.forEach((b, i) => {
|
||||
const color = colors[i % colors.length];
|
||||
pack.rivers
|
||||
.filter((r) => r.basin === b)
|
||||
.forEach((r) => {
|
||||
rivers.select('#river' + r.i).attr('fill', color);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function downloadRiversData() {
|
||||
let data = 'Id,River,Type,Discharge,Length,Width,Basin\n'; // headers
|
||||
|
||||
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;
|
||||
data += [d.id, d.name, d.type, discharge, length, width, d.basin].join(',') + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Rivers') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function openRiverEditor() {
|
||||
const id = 'river' + this.parentNode.dataset.id;
|
||||
editRiver(id);
|
||||
}
|
||||
|
||||
function triggerRiverRemove() {
|
||||
const river = +this.parentNode.dataset.id;
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the river?
|
||||
All tributaries will be auto-removed`;
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
Rivers.remove(river);
|
||||
riversOverviewAddLines();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllRiversRemove() {
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all rivers?`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove all rivers',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeAllRivers();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllRivers() {
|
||||
pack.rivers = [];
|
||||
pack.cells.r = new Uint16Array(pack.cells.i.length);
|
||||
rivers.selectAll('*').remove();
|
||||
riversOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
|
@ -502,6 +502,7 @@ function editStates() {
|
|||
pack.cells.province.forEach((pr, i) => {
|
||||
if (pr === p) pack.cells.province[i] = 0;
|
||||
});
|
||||
|
||||
const coaId = 'provinceCOA' + p;
|
||||
if (document.getElementById(coaId)) document.getElementById(coaId).remove();
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
|
|
@ -568,19 +569,20 @@ function editStates() {
|
|||
|
||||
function showStatesChart() {
|
||||
// build hierarchy tree
|
||||
const data = pack.states.filter((s) => !s.removed);
|
||||
const statesData = pack.states.filter((s) => !s.removed);
|
||||
if (statesData.length < 2) return tip('There are no states to show', false, 'error');
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.id((d) => d.i)
|
||||
.parentId((d) => (d.i ? 0 : null))(data)
|
||||
.parentId((d) => (d.i ? 0 : null))(statesData)
|
||||
.sum((d) => d.area)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const width = 150 + 200 * uiSizeOutput.value,
|
||||
height = 150 + 200 * uiSizeOutput.value;
|
||||
const size = 150 + 200 * uiSizeOutput.value;
|
||||
const margin = {top: 0, right: -50, bottom: 0, left: -50};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height - margin.top - margin.bottom;
|
||||
const w = size - margin.left - margin.right;
|
||||
const h = size - margin.top - margin.bottom;
|
||||
const treeLayout = d3.pack().size([w, h]).padding(3);
|
||||
|
||||
// prepare svg
|
||||
|
|
@ -592,12 +594,13 @@ function editStates() {
|
|||
<option value="burgs">Burgs number</option>
|
||||
</select>`;
|
||||
alertMessage.innerHTML += `<div id='statesInfo' class='chartInfo'>‍</div>`;
|
||||
|
||||
const svg = d3
|
||||
.select('#alertMessage')
|
||||
.insert('svg', '#statesInfo')
|
||||
.attr('id', 'statesTree')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('width', size)
|
||||
.attr('height', size)
|
||||
.style('font-family', 'Almendra SC')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'central');
|
||||
|
|
@ -819,9 +822,9 @@ function editStates() {
|
|||
}
|
||||
|
||||
function applyStatesManualAssignent() {
|
||||
const cells = pack.cells,
|
||||
affectedStates = [],
|
||||
affectedProvinces = [];
|
||||
const {cells} = pack;
|
||||
const affectedStates = [];
|
||||
const affectedProvinces = [];
|
||||
|
||||
statesBody
|
||||
.select('#temp')
|
||||
|
|
@ -837,77 +840,143 @@ function editStates() {
|
|||
|
||||
if (affectedStates.length) {
|
||||
refreshStatesEditor();
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
else drawStates();
|
||||
layerIsOn('toggleStates') ? drawStates() : toggleStates();
|
||||
if (adjustLabels.checked) BurgsAndStates.drawStateLabels([...new Set(affectedStates)]);
|
||||
adjustProvinces([...new Set(affectedProvinces)]);
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
layerIsOn('toggleBorders') ? drawBorders() : toggleBorders();
|
||||
if (layerIsOn('toggleProvinces')) drawProvinces();
|
||||
}
|
||||
|
||||
exitStatesManualAssignment();
|
||||
}
|
||||
|
||||
function adjustProvinces(affectedProvinces) {
|
||||
const {cells, provinces, states} = pack;
|
||||
const form = {Zone: 1, Area: 1, Territory: 2, Province: 1};
|
||||
|
||||
affectedProvinces.forEach((p) => {
|
||||
if (!p) return; // do nothing if neutral lands are captured
|
||||
const old = provinces[p].state;
|
||||
|
||||
// remove province from state provinces list
|
||||
if (states[old]?.provinces?.includes(p)) states[old].provinces.splice(states[old].provinces.indexOf(p), 1);
|
||||
const {cells, provinces, states, burgs} = pack;
|
||||
|
||||
affectedProvinces.forEach((provinceId) => {
|
||||
// find states owning at least 1 province cell
|
||||
const provCells = cells.i.filter((i) => cells.province[i] === p);
|
||||
const provCells = cells.i.filter((i) => cells.state[i] && cells.province[i] === provinceId);
|
||||
const provStates = [...new Set(provCells.map((i) => cells.state[i]))];
|
||||
|
||||
// assign province to its center owner; if center is neutral, remove province
|
||||
const owner = cells.state[provinces[p].center];
|
||||
if (owner) {
|
||||
const name = provinces[p].name;
|
||||
// province is captured completely => change owner or remove
|
||||
if (provinceId && provStates.length === 1) return changeProvinceOwner(provinceId, provStates[0], provCells);
|
||||
|
||||
// if province is a historical part of another state's province, unite with old province
|
||||
const part = states[owner].provinces.find((n) => name.includes(provinces[n].name));
|
||||
if (part) {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter((i) => cells.state[i] === owner).forEach((i) => (cells.province[i] = part));
|
||||
} else {
|
||||
provinces[p].state = owner;
|
||||
states[owner].provinces.push(p);
|
||||
provinces[p].color = getMixedColor(states[owner].color);
|
||||
}
|
||||
} else {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter((i) => !cells.state[i]).forEach((i) => (cells.province[i] = 0));
|
||||
}
|
||||
|
||||
// create new provinces for non-main part
|
||||
provStates
|
||||
.filter((s) => s && s !== owner)
|
||||
.forEach((s) =>
|
||||
createProvince(
|
||||
p,
|
||||
s,
|
||||
provCells.filter((i) => cells.state[i] === s)
|
||||
)
|
||||
);
|
||||
// province is captured partially => split province
|
||||
splitProvince(provinceId, provStates, provCells);
|
||||
});
|
||||
|
||||
function createProvince(initProv, state, provCells) {
|
||||
const province = provinces.length;
|
||||
provCells.forEach((i) => (cells.province[i] = province));
|
||||
function changeProvinceOwner(provinceId, newOwnerId, provinceCells) {
|
||||
const province = provinces[provinceId];
|
||||
const prevOwner = states[province.state];
|
||||
|
||||
const burgCell = provCells.find((i) => cells.burg[i]);
|
||||
const center = burgCell ? burgCell : provCells[0];
|
||||
const burg = burgCell ? cells.burg[burgCell] : 0;
|
||||
// remove province from old owner list
|
||||
prevOwner.provinces = prevOwner.provinces.filter((province) => province !== provinceId);
|
||||
|
||||
const name = burgCell && P(0.7) ? getAdjective(pack.burgs[burg].name) : getAdjective(states[state].name) + ' ' + provinces[initProv].name.split(' ').slice(-1)[0];
|
||||
const formName = name.split(' ').length > 1 ? provinces[initProv].formName : rw(form);
|
||||
const fullName = name + ' ' + formName;
|
||||
const color = getMixedColor(states[state].color);
|
||||
provinces.push({i: province, state, center, burg, name, formName, fullName, color});
|
||||
if (newOwnerId) {
|
||||
// new owner is a state => change owner
|
||||
province.state = newOwnerId;
|
||||
states[newOwnerId].provinces.push(provinceId);
|
||||
} else {
|
||||
// new owner is neutral => remove province
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
provinceCells.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function splitProvince(provinceId, provinceStates, provinceCells) {
|
||||
const province = provinces[provinceId];
|
||||
const prevOwner = states[province.state];
|
||||
const provinceCenterOwner = cells.state[province.center];
|
||||
|
||||
provinceStates.forEach((stateId) => {
|
||||
const stateProvinceCells = provinceCells.filter((i) => cells.state[i] === stateId);
|
||||
|
||||
if (stateId === provinceCenterOwner) {
|
||||
// province center is owned by the same state => do nothing for this state
|
||||
if (stateId === prevOwner.i) return;
|
||||
|
||||
// province center is captured by neutrals => remove state
|
||||
if (!stateId) {
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
stateProvinceCells.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// reassign province ownership to province center owner
|
||||
prevOwner.provinces = prevOwner.provinces.filter((province) => province !== provinceId);
|
||||
province.state = stateId;
|
||||
province.color = getMixedColor(states[stateId].color);
|
||||
states[stateId].provinces.push(provinceId);
|
||||
return;
|
||||
}
|
||||
|
||||
// province cells captured by neutrals => clear province
|
||||
if (!stateId) {
|
||||
stateProvinceCells.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// a few province cells owned by state => add to closes province
|
||||
if (stateProvinceCells.length < 20) {
|
||||
const closestProvince = findClosestProvince(provinceId, stateId, stateProvinceCells);
|
||||
if (closestProvince) {
|
||||
stateProvinceCells.forEach((i) => {
|
||||
cells.province[i] = closestProvince;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// some province cells owned by state => create new province
|
||||
createProvince(province, stateId, stateProvinceCells);
|
||||
});
|
||||
}
|
||||
|
||||
function createProvince(oldProvince, stateId, provinceCells) {
|
||||
const newProvinceId = provinces.length;
|
||||
const burgCell = provinceCells.find((i) => cells.burg[i]);
|
||||
const center = burgCell ? burgCell : provinceCells[0];
|
||||
const burgId = burgCell ? cells.burg[burgCell] : 0;
|
||||
const burg = burgId ? burgs[burgId] : null;
|
||||
const culture = cells.culture[center];
|
||||
|
||||
const nameByBurg = burgCell && P(0.5);
|
||||
const name = nameByBurg ? burg.name : oldProvince.name || Names.getState(Names.getCultureShort(culture), culture);
|
||||
|
||||
const formOptions = ['Zone', 'Area', 'Territory', 'Province'];
|
||||
const formName = burgCell && oldProvince.formName ? oldProvince.formName : ra(formOptions);
|
||||
|
||||
const color = getMixedColor(states[stateId].color);
|
||||
|
||||
const kinship = nameByBurg ? 0.8 : 0.4;
|
||||
const type = BurgsAndStates.getType(center, burg?.port);
|
||||
const coa = COA.generate(burg?.coa || states[stateId].coa, kinship, burg ? null : 0.9, type);
|
||||
coa.shield = COA.getShield(culture, stateId);
|
||||
|
||||
provinces.push({i: newProvinceId, state: stateId, center, burg: burgId, name, formName, fullName: `${name} ${formName}`, color, coa});
|
||||
|
||||
provinceCells.forEach((i) => {
|
||||
cells.province[i] = newProvinceId;
|
||||
});
|
||||
|
||||
states[stateId].provinces.push(newProvinceId);
|
||||
}
|
||||
|
||||
function findClosestProvince(provinceId, stateId, sourceCells) {
|
||||
const borderCell = sourceCells.find((i) =>
|
||||
cells.c[i].some((c) => {
|
||||
return cells.state[c] === stateId && cells.province[c] && cells.province[c] !== provinceId;
|
||||
})
|
||||
);
|
||||
|
||||
const closesProvince = borderCell && cells.c[borderCell].map((c) => cells.province[c]).find((province) => province && province !== provinceId);
|
||||
return closesProvince;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1007,7 +1076,22 @@ function editStates() {
|
|||
cells.state[center] = newState;
|
||||
cells.province[center] = 0;
|
||||
|
||||
states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: 'Generic', center, culture, military: [], alert: 1, coa, pole});
|
||||
states.push({
|
||||
i: newState,
|
||||
name,
|
||||
diplomacy,
|
||||
provinces: [],
|
||||
color,
|
||||
expansionism: 0.5,
|
||||
capital: burg,
|
||||
type: 'Generic',
|
||||
center,
|
||||
culture,
|
||||
military: [],
|
||||
alert: 1,
|
||||
coa,
|
||||
pole
|
||||
});
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms([newState]);
|
||||
adjustProvinces([cells.province[center]]);
|
||||
|
|
@ -1050,12 +1134,13 @@ function editStates() {
|
|||
|
||||
function downloadStatesData() {
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,State,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
|
||||
let data = 'Id,State,Full Name,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
const key = parseInt(el.dataset.id);
|
||||
const statePack = pack.states[key];
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += (statePack.fullName ? statePack.fullName : '') + ',';
|
||||
data += el.dataset.form + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.capital + ',';
|
||||
|
|
@ -1066,8 +1151,8 @@ function editStates() {
|
|||
data += el.dataset.burgs + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += `${Math.round(pack.states[key].rural * populationRate)},`;
|
||||
data += `${Math.round(pack.states[key].urban * populationRate * urbanization)}\n`;
|
||||
data += `${Math.round(statePack.rural * populationRate)},`;
|
||||
data += `${Math.round(statePack.urban * populationRate * urbanization)}\n`;
|
||||
});
|
||||
|
||||
const name = getFileName('States') + '.csv';
|
||||
|
|
|
|||
1351
modules/ui/states-editor.js.orig
Normal file
1351
modules/ui/states-editor.js.orig
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
1701
modules/ui/style.js.orig
Normal file
1701
modules/ui/style.js.orig
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,15 +1,15 @@
|
|||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
'use strict';
|
||||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
|
||||
toolsContent.addEventListener('click', function (event) {
|
||||
if (customization) {
|
||||
tip('Please exit the customization mode first', false, 'warning');
|
||||
return;
|
||||
}
|
||||
if (event.target.tagName !== 'BUTTON') return;
|
||||
if (!['BUTTON', 'I'].includes(event.target.tagName)) return;
|
||||
const button = event.target.id;
|
||||
|
||||
// Click to open Editor buttons
|
||||
// click on open Editor buttons
|
||||
if (button === 'editHeightmapButton') editHeightmap();
|
||||
else if (button === 'editBiomesButton') editBiomes();
|
||||
else if (button === 'editStatesButton') editStates();
|
||||
|
|
@ -17,7 +17,6 @@ toolsContent.addEventListener('click', function (event) {
|
|||
else if (button === 'editDiplomacyButton') editDiplomacy();
|
||||
else if (button === 'editCulturesButton') editCultures();
|
||||
else if (button === 'editReligions') editReligions();
|
||||
else if (button === 'editResources') editResources();
|
||||
else if (button === 'editEmblemButton') openEmblemEditor();
|
||||
else if (button === 'editNamesBaseButton') editNamesbase();
|
||||
else if (button === 'editUnitsButton') editUnits();
|
||||
|
|
@ -26,9 +25,10 @@ toolsContent.addEventListener('click', function (event) {
|
|||
else if (button === 'overviewBurgsButton') overviewBurgs();
|
||||
else if (button === 'overviewRiversButton') overviewRivers();
|
||||
else if (button === 'overviewMilitaryButton') overviewMilitary();
|
||||
else if (button === 'overviewMarkersButton') overviewMarkers();
|
||||
else if (button === 'overviewCellsButton') viewCellDetails();
|
||||
|
||||
// Click to Regenerate buttons
|
||||
// click on Regenerate buttons
|
||||
if (event.target.parentNode.id === 'regenerateFeature') {
|
||||
if (sessionStorage.getItem('regenerateFeatureDontAsk')) {
|
||||
processFeatureRegeneration(event, button);
|
||||
|
|
@ -61,7 +61,10 @@ toolsContent.addEventListener('click', function (event) {
|
|||
});
|
||||
}
|
||||
|
||||
// Click to Add buttons
|
||||
// click on Configure regenerate buttons
|
||||
if (button === 'configRegenerateMarkers') configMarkersGeneration();
|
||||
|
||||
// click on Add buttons
|
||||
if (button === 'addLabel') toggleAddLabel();
|
||||
else if (button === 'addBurgTool') toggleAddBurg();
|
||||
else if (button === 'addRiver') toggleAddRiver();
|
||||
|
|
@ -84,13 +87,12 @@ function processFeatureRegeneration(event, button) {
|
|||
else if (button === 'regenerateStates') regenerateStates();
|
||||
else if (button === 'regenerateProvinces') regenerateProvinces();
|
||||
else if (button === 'regenerateBurgs') regenerateBurgs();
|
||||
else if (button === 'regenerateResources') regenerateResources();
|
||||
else if (button === 'regenerateEmblems') regenerateEmblems();
|
||||
else if (button === 'regenerateReligions') regenerateReligions();
|
||||
else if (button === 'regenerateCultures') regenerateCultures();
|
||||
else if (button === 'regenerateMilitary') regenerateMilitary();
|
||||
else if (button === 'regenerateIce') regenerateIce();
|
||||
else if (button === 'regenerateMarkers') regenerateMarkers(event);
|
||||
else if (button === 'regenerateMarkers') regenerateMarkers();
|
||||
else if (button === 'regenerateZones') regenerateZones(event);
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +121,7 @@ function regenerateRivers() {
|
|||
Lakes.defineGroup();
|
||||
Rivers.specify();
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
else drawRivers();
|
||||
}
|
||||
|
||||
function recalculatePopulation() {
|
||||
|
|
@ -137,21 +140,11 @@ function recalculatePopulation() {
|
|||
function regenerateStates() {
|
||||
const localSeed = Math.floor(Math.random() * 1e9); // new random seed
|
||||
Math.random = aleaPRNG(localSeed);
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
if (!burgs.length) {
|
||||
tip('No burgs to generate states. Please create burgs first', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (burgs.length < +regionsInput.value) {
|
||||
tip(`Not enough burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, 'warn');
|
||||
}
|
||||
|
||||
// burg local ids sorted by a bit randomized population:
|
||||
const sorted = burgs
|
||||
.map((b, i) => [i, b.population * Math.random()])
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map((b) => b[0]);
|
||||
const capitalsTree = d3.quadtree();
|
||||
const statesCount = +regionsInput.value;
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
if (!burgs.length) return tip('There are no any burgs to generate states. Please create burgs first', false, 'error');
|
||||
if (burgs.length < statesCount) tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, 'warn');
|
||||
|
||||
// turn all old capitals into towns
|
||||
burgs
|
||||
|
|
@ -168,8 +161,7 @@ function regenerateStates() {
|
|||
|
||||
unfog();
|
||||
|
||||
// if desired states number is 0
|
||||
if (regionsInput.value == 0) {
|
||||
if (!statesCount) {
|
||||
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, 'warn');
|
||||
pack.states = pack.states.slice(0, 1); // remove all except of neutrals
|
||||
pack.states[0].diplomacy = []; // clear diplomacy
|
||||
|
|
@ -185,26 +177,34 @@ function regenerateStates() {
|
|||
return;
|
||||
}
|
||||
|
||||
const neutral = pack.states[0].name;
|
||||
const count = Math.min(+regionsInput.value, burgs.length);
|
||||
// burg local ids sorted by a bit randomized population:
|
||||
const sortedBurgs = burgs
|
||||
.map((b, i) => [b, b.population * Math.random()])
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map((b) => b[0]);
|
||||
const capitalsTree = d3.quadtree();
|
||||
|
||||
const neutral = pack.states[0].name; // neutrals name
|
||||
const count = Math.min(statesCount, burgs.length) + 1; // +1 for neutral
|
||||
let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals
|
||||
|
||||
pack.states = d3.range(count).map((i) => {
|
||||
if (!i) return {i, name: neutral};
|
||||
|
||||
let capital = null,
|
||||
x = 0,
|
||||
y = 0;
|
||||
for (const i of sorted) {
|
||||
capital = burgs[i];
|
||||
(x = capital.x), (y = capital.y);
|
||||
if (capitalsTree.find(x, y, spacing) === undefined) break;
|
||||
let capital = null;
|
||||
for (const burg of sortedBurgs) {
|
||||
const {x, y} = burg;
|
||||
if (capitalsTree.find(x, y, spacing) === undefined) {
|
||||
burg.capital = 1;
|
||||
capital = burg;
|
||||
capitalsTree.add([x, y]);
|
||||
moveBurgToGroup(burg.i, 'cities');
|
||||
break;
|
||||
}
|
||||
|
||||
spacing = Math.max(spacing - 1, 1);
|
||||
}
|
||||
|
||||
capitalsTree.add([x, y]);
|
||||
capital.capital = 1;
|
||||
moveBurgToGroup(capital.i, 'cities');
|
||||
|
||||
const culture = capital.culture;
|
||||
const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, '', 0);
|
||||
const name = Names.getState(basename, culture);
|
||||
|
|
@ -337,13 +337,6 @@ function regenerateBurgs() {
|
|||
if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateResources() {
|
||||
Resources.generate();
|
||||
goods.selectAll('*').remove();
|
||||
if (layerIsOn('toggleResources')) drawResources();
|
||||
refreshAllEditors();
|
||||
}
|
||||
|
||||
function regenerateEmblems() {
|
||||
// remove old emblems
|
||||
document.querySelectorAll('[id^=stateCOA]').forEach((el) => el.remove());
|
||||
|
|
@ -424,23 +417,11 @@ function regenerateIce() {
|
|||
drawIce();
|
||||
}
|
||||
|
||||
function regenerateMarkers(event) {
|
||||
if (isCtrlClick(event)) prompt('Please provide markers number multiplier', {default: 1, step: 0.01, min: 0, max: 100}, (v) => addNumberOfMarkers(v));
|
||||
else addNumberOfMarkers(gauss(1, 0.5, 0.3, 5, 2));
|
||||
|
||||
function addNumberOfMarkers(number) {
|
||||
// remove existing markers and assigned notes
|
||||
markers
|
||||
.selectAll('use')
|
||||
.each(function () {
|
||||
const index = notes.findIndex((n) => n.id === this.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
})
|
||||
.remove();
|
||||
|
||||
addMarkers(number);
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
}
|
||||
function regenerateMarkers() {
|
||||
Markers.regenerate();
|
||||
turnButtonOn('toggleMarkers');
|
||||
drawMarkers();
|
||||
if (document.getElementById('markersOverviewRefresh').offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateZones(event) {
|
||||
|
|
@ -485,7 +466,10 @@ function addLabelOnClick() {
|
|||
const name = Names.getCulture(culture);
|
||||
const id = getNextId('label');
|
||||
|
||||
let group = labels.select('#addedLabels');
|
||||
// use most recently selected label group
|
||||
let selected = labelGroupSelect.value;
|
||||
const symbol = selected ? '#' + selected : '#addedLabels';
|
||||
let group = labels.select(symbol);
|
||||
if (!group.size())
|
||||
group = labels
|
||||
.append('g')
|
||||
|
|
@ -495,7 +479,6 @@ function addLabelOnClick() {
|
|||
.attr('stroke', '#3a3a3a')
|
||||
.attr('stroke-width', 0)
|
||||
.attr('font-family', 'Almendra SC')
|
||||
.attr('data-font', 'Almendra+SC')
|
||||
.attr('font-size', 18)
|
||||
.attr('data-size', 18)
|
||||
.attr('filter', null);
|
||||
|
|
@ -697,7 +680,7 @@ function addRouteOnClick() {
|
|||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
const pressed = document.getElementById('addMarker').classList.contains('pressed');
|
||||
const pressed = document.getElementById('addMarker')?.classList.contains('pressed');
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
|
|
@ -705,45 +688,115 @@ function toggleAddMarker() {
|
|||
|
||||
addFeature.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
|
||||
addMarker.classList.add('pressed');
|
||||
closeDialogs('.stable');
|
||||
markersAddFromOverview.classList.add('pressed');
|
||||
|
||||
viewbox.style('cursor', 'crosshair').on('click', addMarkerOnClick);
|
||||
tip('Click on map to add a marker. Hold Shift to add multiple', true);
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
}
|
||||
|
||||
function addMarkerOnClick() {
|
||||
const {markers} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const x = rn(point[0], 2),
|
||||
y = rn(point[1], 2);
|
||||
const id = getNextId('markerElement');
|
||||
const x = rn(point[0], 2);
|
||||
const y = rn(point[1], 2);
|
||||
const i = last(markers).i + 1;
|
||||
|
||||
const selected = markerSelectGroup.value;
|
||||
const valid =
|
||||
selected &&
|
||||
d3
|
||||
.select('#defs-markers')
|
||||
.select('#' + selected)
|
||||
.size();
|
||||
const symbol = valid ? '#' + selected : '#marker0';
|
||||
const added = markers.select("[data-id='" + symbol + "']").size();
|
||||
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr('data-size') : 1;
|
||||
if (isNaN(desired)) desired = 1;
|
||||
const size = desired * 5 + 25 / scale;
|
||||
const isMarkerSelected = elSelected?.node()?.parentElement?.id === 'markers';
|
||||
const selectedMarker = isMarkerSelected ? markers.find((marker) => marker.i === +elSelected.attr('id').slice(6)) : null;
|
||||
const baseMarker = selectedMarker || {icon: '❓'};
|
||||
const marker = {...baseMarker, i, x, y};
|
||||
|
||||
markers
|
||||
.append('use')
|
||||
.attr('id', id)
|
||||
.attr('xlink:href', symbol)
|
||||
.attr('data-id', symbol)
|
||||
.attr('data-x', x)
|
||||
.attr('data-y', y)
|
||||
.attr('x', x - size / 2)
|
||||
.attr('y', y - size)
|
||||
.attr('data-size', desired)
|
||||
.attr('width', size)
|
||||
.attr('height', size);
|
||||
markers.push(marker);
|
||||
const markersElement = document.getElementById('markers');
|
||||
const rescale = +markersElement.getAttribute('rescale');
|
||||
markersElement.insertAdjacentHTML('beforeend', drawMarker(marker, rescale));
|
||||
|
||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||
if (d3.event.shiftKey === false) {
|
||||
document.getElementById('markerAdd').classList.remove('pressed');
|
||||
document.getElementById('markersAddFromOverview').classList.remove('pressed');
|
||||
unpressClickToAddButton();
|
||||
}
|
||||
}
|
||||
|
||||
function configMarkersGeneration() {
|
||||
drawConfigTable();
|
||||
|
||||
function drawConfigTable() {
|
||||
const {markers} = pack;
|
||||
const config = Markers.getConfig();
|
||||
const headers = `<thead style='font-weight:bold'><tr>
|
||||
<td data-tip="Marker type name">Type</td>
|
||||
<td data-tip="Marker icon">Icon</td>
|
||||
<td data-tip="Marker number multiplier">Multiplier</td>
|
||||
<td data-tip="Number of markers of that type on the current map">Number</td>
|
||||
</tr></thead>`;
|
||||
const lines = config.map(({type, icon, multiplier}, index) => {
|
||||
const inputId = `markerIconInput${index}`;
|
||||
return `<tr>
|
||||
<td><input value="${type}" /></td>
|
||||
<td>
|
||||
<input id="${inputId}" style="width: 5em" value="${icon}" />
|
||||
<i class="icon-edit pointer" style="position: absolute; margin:.4em 0 0 -1.4em; font-size:.85em"></i>
|
||||
</td>
|
||||
<td><input type="number" min="0" max="100" step="0.1" value="${multiplier}" /></td>
|
||||
<td style="text-align:center">${markers.filter((marker) => marker.type === type).length}</td>
|
||||
</tr>`;
|
||||
});
|
||||
const table = `<table class="table">${headers}<tbody>${lines.join('')}</tbody></table>`;
|
||||
alertMessage.innerHTML = table;
|
||||
|
||||
alertMessage.querySelectorAll('i').forEach((selectIconButton) => {
|
||||
selectIconButton.addEventListener('click', function () {
|
||||
const input = this.previousElementSibling;
|
||||
selectIcon(input.value, (icon) => (input.value = icon));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const applyChanges = () => {
|
||||
const rows = alertMessage.querySelectorAll('tbody > tr');
|
||||
const rowsData = Array.from(rows).map((row) => {
|
||||
const inputs = row.querySelectorAll('input');
|
||||
return {
|
||||
type: inputs[0].value,
|
||||
icon: inputs[1].value,
|
||||
multiplier: parseFloat(inputs[2].value)
|
||||
};
|
||||
});
|
||||
|
||||
const config = Markers.getConfig();
|
||||
const newConfig = config.map((markerType, index) => {
|
||||
const {type, icon, multiplier} = rowsData[index];
|
||||
return {...markerType, type, icon, multiplier};
|
||||
});
|
||||
|
||||
Markers.setConfig(newConfig);
|
||||
};
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Markers generation settings',
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: 'svg', collision: 'fit'},
|
||||
buttons: {
|
||||
Regenerate: () => {
|
||||
applyChanges();
|
||||
regenerateMarkers();
|
||||
drawConfigTable();
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip('Apply changes and regenerate markers'));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Close the window'));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog('destroy');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function viewCellDetails() {
|
||||
|
|
|
|||
1014
modules/ui/tools.js.orig
Normal file
1014
modules/ui/tools.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -31,6 +31,8 @@ function editUnits() {
|
|||
document.getElementById('populationRateInput').addEventListener('change', changePopulationRate);
|
||||
document.getElementById('urbanizationOutput').addEventListener('input', changeUrbanizationRate);
|
||||
document.getElementById('urbanizationInput').addEventListener('change', changeUrbanizationRate);
|
||||
document.getElementById('urbanDensityOutput').addEventListener('input', changeUrbanDensity);
|
||||
document.getElementById('urbanDensityInput').addEventListener('change', changeUrbanDensity);
|
||||
|
||||
document.getElementById('addLinearRuler').addEventListener('click', addRuler);
|
||||
document.getElementById('addOpisometer').addEventListener('click', toggleOpisometerMode);
|
||||
|
|
@ -93,6 +95,10 @@ function editUnits() {
|
|||
urbanization = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanDensity() {
|
||||
urbanDensity = +this.value;
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
// distanceScale
|
||||
document.getElementById('distanceScaleOutput').value = 3;
|
||||
|
|
@ -135,8 +141,9 @@ function editUnits() {
|
|||
// population
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
urbanization = urbanizationOutput.value = urbanizationInput.value = 1;
|
||||
localStorage.removeItem('populationRate');
|
||||
urbanDensity = urbanDensityOutput.value = urbanDensityInput.value = 10;
|
||||
localStorage.removeItem('urbanization');
|
||||
localStorage.removeItem('urbanDensity');
|
||||
}
|
||||
|
||||
function addRuler() {
|
||||
|
|
|
|||
329
modules/ui/units-editor.js.orig
Normal file
329
modules/ui/units-editor.js.orig
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
'use strict';
|
||||
function editUnits() {
|
||||
closeDialogs('#unitsEditor, .stable');
|
||||
$('#unitsEditor').dialog();
|
||||
|
||||
if (modules.editUnits) return;
|
||||
modules.editUnits = true;
|
||||
|
||||
$('#unitsEditor').dialog({
|
||||
title: 'Units Editor',
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
document.getElementById('distanceUnitInput').addEventListener('change', changeDistanceUnit);
|
||||
document.getElementById('distanceScaleOutput').addEventListener('input', changeDistanceScale);
|
||||
document.getElementById('distanceScaleInput').addEventListener('change', changeDistanceScale);
|
||||
document.getElementById('heightUnit').addEventListener('change', changeHeightUnit);
|
||||
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('barPosX').addEventListener('input', fitScaleBar);
|
||||
document.getElementById('barPosY').addEventListener('input', fitScaleBar);
|
||||
document.getElementById('barBackOpacity').addEventListener('input', changeScaleBarOpacity);
|
||||
document.getElementById('barBackColor').addEventListener('input', changeScaleBarColor);
|
||||
|
||||
document.getElementById('populationRateOutput').addEventListener('input', changePopulationRate);
|
||||
document.getElementById('populationRateInput').addEventListener('change', changePopulationRate);
|
||||
document.getElementById('urbanizationOutput').addEventListener('input', changeUrbanizationRate);
|
||||
document.getElementById('urbanizationInput').addEventListener('change', changeUrbanizationRate);
|
||||
|
||||
document.getElementById('addLinearRuler').addEventListener('click', addRuler);
|
||||
document.getElementById('addOpisometer').addEventListener('click', toggleOpisometerMode);
|
||||
document.getElementById('addRouteOpisometer').addEventListener('click', toggleRouteOpisometerMode);
|
||||
document.getElementById('addPlanimeter').addEventListener('click', togglePlanimeterMode);
|
||||
document.getElementById('removeRulers').addEventListener('click', removeAllRulers);
|
||||
document.getElementById('unitsRestore').addEventListener('click', restoreDefaultUnits);
|
||||
=======
|
||||
document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit);
|
||||
document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale);
|
||||
document.getElementById("distanceScaleInput").addEventListener("change", changeDistanceScale);
|
||||
document.getElementById("heightUnit").addEventListener("change", changeHeightUnit);
|
||||
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("barPosX").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barPosY").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity);
|
||||
document.getElementById("barBackColor").addEventListener("input", changeScaleBarColor);
|
||||
|
||||
document.getElementById("populationRateOutput").addEventListener("input", changePopulationRate);
|
||||
document.getElementById("populationRateInput").addEventListener("change", changePopulationRate);
|
||||
document.getElementById("urbanizationOutput").addEventListener("input", changeUrbanizationRate);
|
||||
document.getElementById("urbanizationInput").addEventListener("change", changeUrbanizationRate);
|
||||
document.getElementById("urbanDensityOutput").addEventListener("input", changeUrbanDensity);
|
||||
document.getElementById("urbanDensityInput").addEventListener("change", changeUrbanDensity);
|
||||
|
||||
document.getElementById("addLinearRuler").addEventListener("click", addRuler);
|
||||
document.getElementById("addOpisometer").addEventListener("click", toggleOpisometerMode);
|
||||
document.getElementById("addRouteOpisometer").addEventListener("click", toggleRouteOpisometerMode);
|
||||
document.getElementById("addPlanimeter").addEventListener("click", togglePlanimeterMode);
|
||||
document.getElementById("removeRulers").addEventListener("click", removeAllRulers);
|
||||
document.getElementById("unitsRestore").addEventListener("click", restoreDefaultUnits);
|
||||
>>>>>>> master
|
||||
|
||||
function changeDistanceUnit() {
|
||||
if (this.value === 'custom_name') {
|
||||
prompt('Provide a custom name for a distance unit', {default: ''}, (custom) => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock('distanceUnit');
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeDistanceScale() {
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeHeightUnit() {
|
||||
if (this.value !== 'custom_name') return;
|
||||
|
||||
prompt('Provide a custom name for a height unit', {default: ''}, (custom) => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock('heightUnit');
|
||||
});
|
||||
}
|
||||
|
||||
function changeHeightExponent() {
|
||||
calculateTemperatures();
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
}
|
||||
|
||||
function changeTemperatureScale() {
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
}
|
||||
|
||||
function changeScaleBarOpacity() {
|
||||
scaleBar.select('rect').attr('opacity', this.value);
|
||||
}
|
||||
|
||||
function changeScaleBarColor() {
|
||||
scaleBar.select('rect').attr('fill', this.value);
|
||||
}
|
||||
|
||||
function changePopulationRate() {
|
||||
populationRate = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanizationRate() {
|
||||
urbanization = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanDensity() {
|
||||
urbanDensity = +this.value;
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
// distanceScale
|
||||
document.getElementById('distanceScaleOutput').value = 3;
|
||||
document.getElementById('distanceScaleInput').value = 3;
|
||||
unlock('distanceScale');
|
||||
|
||||
// units
|
||||
const US = navigator.language === 'en-US';
|
||||
const UK = navigator.language === 'en-GB';
|
||||
distanceUnitInput.value = US || UK ? 'mi' : 'km';
|
||||
heightUnit.value = US || UK ? 'ft' : 'm';
|
||||
temperatureScale.value = US ? '°F' : '°C';
|
||||
areaUnit.value = 'square';
|
||||
localStorage.removeItem('distanceUnit');
|
||||
localStorage.removeItem('heightUnit');
|
||||
localStorage.removeItem('temperatureScale');
|
||||
localStorage.removeItem('areaUnit');
|
||||
calculateFriendlyGridSize();
|
||||
|
||||
// height exponent
|
||||
heightExponentInput.value = heightExponentOutput.value = 1.8;
|
||||
localStorage.removeItem('heightExponent');
|
||||
calculateTemperatures();
|
||||
|
||||
// scale bar
|
||||
barSizeOutput.value = barSizeInput.value = 2;
|
||||
barLabel.value = '';
|
||||
barBackOpacity.value = 0.2;
|
||||
barBackColor.value = '#ffffff';
|
||||
barPosX.value = barPosY.value = 99;
|
||||
|
||||
localStorage.removeItem('barSize');
|
||||
localStorage.removeItem('barLabel');
|
||||
localStorage.removeItem('barBackOpacity');
|
||||
localStorage.removeItem('barBackColor');
|
||||
localStorage.removeItem('barPosX');
|
||||
localStorage.removeItem('barPosY');
|
||||
drawScaleBar();
|
||||
|
||||
// population
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
urbanization = urbanizationOutput.value = urbanizationInput.value = 1;
|
||||
<<<<<<< HEAD
|
||||
localStorage.removeItem('populationRate');
|
||||
localStorage.removeItem('urbanization');
|
||||
=======
|
||||
urbanDensity = urbanDensityOutput.value = urbanDensityInput.value = 10;
|
||||
localStorage.removeItem("populationRate");
|
||||
localStorage.removeItem("urbanization");
|
||||
localStorage.removeItem("urbanDensity");
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function addRuler() {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
const pt = document.getElementById('map').createSVGPoint();
|
||||
(pt.x = graphWidth / 2), (pt.y = graphHeight / 4);
|
||||
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
|
||||
const dx = graphWidth / 4 / scale;
|
||||
const dy = (rulers.data.length * 40) % (graphHeight / 2);
|
||||
const from = [(p.x - dx) | 0, (p.y + dy) | 0];
|
||||
const to = [(p.x + dx) | 0, (p.y + dy) | 0];
|
||||
rulers.create(Ruler, [from, to]).draw();
|
||||
}
|
||||
|
||||
function toggleOpisometerMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
this.classList.remove('pressed');
|
||||
} else {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
tip('Draw a curve to measure length. Hold Shift to disallow path optimization', true);
|
||||
unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
|
||||
this.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(
|
||||
d3.drag().on('start', function () {
|
||||
const point = d3.mouse(this);
|
||||
const opisometer = rulers.create(Opisometer, [point]).draw();
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const point = d3.mouse(this);
|
||||
opisometer.addPoint(point);
|
||||
});
|
||||
|
||||
d3.event.on('end', function () {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addOpisometer.classList.remove('pressed');
|
||||
if (opisometer.points.length < 2) rulers.remove(opisometer.id);
|
||||
if (!d3.event.sourceEvent.shiftKey) opisometer.optimize();
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRouteOpisometerMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
this.classList.remove('pressed');
|
||||
} else {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
tip('Draw a curve along routes to measure length. Hold Shift to measure away from roads.', true);
|
||||
unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
|
||||
this.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(
|
||||
d3.drag().on('start', function () {
|
||||
const cells = pack.cells;
|
||||
const burgs = pack.burgs;
|
||||
const point = d3.mouse(this);
|
||||
const c = findCell(point[0], point[1]);
|
||||
if (cells.road[c] || d3.event.sourceEvent.shiftKey) {
|
||||
const b = cells.burg[c];
|
||||
const x = b ? burgs[b].x : cells.p[c][0];
|
||||
const y = b ? burgs[b].y : cells.p[c][1];
|
||||
const routeOpisometer = rulers.create(RouteOpisometer, [[x, y]]).draw();
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const point = d3.mouse(this);
|
||||
const c = findCell(point[0], point[1]);
|
||||
if (cells.road[c] || d3.event.sourceEvent.shiftKey) {
|
||||
routeOpisometer.trackCell(c, true);
|
||||
}
|
||||
});
|
||||
|
||||
d3.event.on('end', function () {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addRouteOpisometer.classList.remove('pressed');
|
||||
if (routeOpisometer.points.length < 2) {
|
||||
rulers.remove(routeOpisometer.id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addRouteOpisometer.classList.remove('pressed');
|
||||
tip('Must start in a cell with a route in it', false, 'error');
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function togglePlanimeterMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
this.classList.remove('pressed');
|
||||
} else {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
tip('Draw a curve to measure its area. Hold Shift to disallow path optimization', true);
|
||||
unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
|
||||
this.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(
|
||||
d3.drag().on('start', function () {
|
||||
const point = d3.mouse(this);
|
||||
const planimeter = rulers.create(Planimeter, [point]).draw();
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const point = d3.mouse(this);
|
||||
planimeter.addPoint(point);
|
||||
});
|
||||
|
||||
d3.event.on('end', function () {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addPlanimeter.classList.remove('pressed');
|
||||
if (planimeter.points.length < 3) rulers.remove(planimeter.id);
|
||||
else if (!d3.event.sourceEvent.shiftKey) planimeter.optimize();
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function removeAllRulers() {
|
||||
if (!rulers.data.length) return;
|
||||
alertMessage.innerHTML = `
|
||||
Are you sure you want to remove all placed rulers?
|
||||
<br>If you just want to hide rulers, toggle the Rulers layer off in Menu`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove all rulers',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
rulers.undraw();
|
||||
rulers = new Rulers();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,33 @@
|
|||
function editWorld() {
|
||||
if (customization) return;
|
||||
$("#worldConfigurator").dialog({title: "Configure World", resizable: false, width: "42em",
|
||||
$('#worldConfigurator').dialog({
|
||||
title: 'Configure World',
|
||||
resizable: false,
|
||||
width: '42em',
|
||||
buttons: {
|
||||
"Whole World": () => applyWorldPreset(100, 50),
|
||||
"Northern": () => applyWorldPreset(33, 25),
|
||||
"Tropical": () => applyWorldPreset(33, 50),
|
||||
"Southern": () => applyWorldPreset(33, 75),
|
||||
"Restore Winds": restoreDefaultWinds
|
||||
}, open: function() {
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () => tip("Click to set map size to cover the whole World"));
|
||||
buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
|
||||
buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
|
||||
buttons[4].addEventListener("mousemove", () => tip("Click to restore default wind directions"));
|
||||
'Whole World': () => applyWorldPreset(100, 50),
|
||||
Northern: () => applyWorldPreset(33, 25),
|
||||
Tropical: () => applyWorldPreset(33, 50),
|
||||
Southern: () => applyWorldPreset(33, 75),
|
||||
'Restore Winds': restoreDefaultWinds
|
||||
},
|
||||
open: function () {
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip('Click to set map size to cover the whole World'));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Click to set map size to cover the Northern latitudes'));
|
||||
buttons[2].addEventListener('mousemove', () => tip('Click to set map size to cover the Tropical latitudes'));
|
||||
buttons[3].addEventListener('mousemove', () => tip('Click to set map size to cover the Southern latitudes'));
|
||||
buttons[4].addEventListener('mousemove', () => tip('Click to restore default wind directions'));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog('destroy');
|
||||
}
|
||||
});
|
||||
|
||||
const globe = d3.select("#globe");
|
||||
const globe = d3.select('#globe');
|
||||
const clr = d3.scaleSequential(d3.interpolateSpectral);
|
||||
const tMax = 30, tMin = -25; // temperature extremes
|
||||
const tMax = 30,
|
||||
tMin = -25; // temperature extremes
|
||||
const projection = d3.geoOrthographic().translate([100, 100]).scale(100);
|
||||
const path = d3.geoPath(projection);
|
||||
|
||||
|
|
@ -29,15 +37,15 @@ function editWorld() {
|
|||
if (modules.editWorld) return;
|
||||
modules.editWorld = true;
|
||||
|
||||
document.getElementById("worldControls").addEventListener("input", (e) => updateWorld(e.target));
|
||||
globe.select("#globeWindArrows").on("click", changeWind);
|
||||
globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
|
||||
document.getElementById('worldControls').addEventListener('input', (e) => updateWorld(e.target));
|
||||
globe.select('#globeWindArrows').on('click', changeWind);
|
||||
globe.select('#globeGraticule').attr('d', round(path(d3.geoGraticule()()))); // globe graticule
|
||||
updateWindDirections();
|
||||
|
||||
function updateWorld(el) {
|
||||
if (el) {
|
||||
document.getElementById(el.dataset.stored+"Input").value = el.value;
|
||||
document.getElementById(el.dataset.stored+"Output").value = el.value;
|
||||
document.getElementById(el.dataset.stored + 'Input').value = el.value;
|
||||
document.getElementById(el.dataset.stored + 'Output').value = el.value;
|
||||
if (el.dataset.stored) lock(el.dataset.stored);
|
||||
}
|
||||
|
||||
|
|
@ -52,84 +60,94 @@ function editWorld() {
|
|||
pack.cells.h = new Float32Array(heights);
|
||||
defineBiomes();
|
||||
|
||||
if (layerIsOn("toggleTemp")) drawTemp();
|
||||
if (layerIsOn("togglePrec")) drawPrec();
|
||||
if (layerIsOn("toggleBiomes")) drawBiomes();
|
||||
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||
if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500);
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
if (layerIsOn('togglePrec')) drawPrec();
|
||||
if (layerIsOn('toggleBiomes')) drawBiomes();
|
||||
if (layerIsOn('toggleCoordinates')) drawCoordinates();
|
||||
if (layerIsOn('toggleRivers')) drawRivers();
|
||||
if (document.getElementById('canvas3d')) setTimeout(ThreeD.update(), 500);
|
||||
}
|
||||
|
||||
function updateGlobePosition() {
|
||||
const size = +document.getElementById("mapSizeOutput").value;
|
||||
const eqD = graphHeight / 2 * 100 / size;
|
||||
const size = +document.getElementById('mapSizeOutput').value;
|
||||
const eqD = ((graphHeight / 2) * 100) / size;
|
||||
|
||||
calculateMapCoordinates();
|
||||
const mc = mapCoordinates; // shortcut
|
||||
const scale = +distanceScaleInput.value, unit = distanceUnitInput.value;
|
||||
const scale = +distanceScaleInput.value,
|
||||
unit = distanceUnitInput.value;
|
||||
const meridian = toKilometer(eqD * 2 * scale);
|
||||
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
|
||||
document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
|
||||
document.getElementById("meridianLength").innerHTML = rn(eqD * 2);
|
||||
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
|
||||
document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
|
||||
document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
|
||||
document.getElementById('mapSize').innerHTML = `${graphWidth}x${graphHeight}`;
|
||||
document.getElementById('mapSizeFriendly').innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
|
||||
document.getElementById('meridianLength').innerHTML = rn(eqD * 2);
|
||||
document.getElementById('meridianLengthFriendly').innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
|
||||
document.getElementById('meridianLengthEarth').innerHTML = meridian ? ' = ' + rn(meridian / 200) + '%🌏' : '';
|
||||
document.getElementById('mapCoordinates').innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
|
||||
|
||||
function toKilometer(v) {
|
||||
if (unit === "km") return v;
|
||||
else if (unit === "mi") return v * 1.60934;
|
||||
else if (unit === "lg") return v * 5.556;
|
||||
else if (unit === "vr") return v * 1.0668;
|
||||
if (unit === 'km') return v;
|
||||
else if (unit === 'mi') return v * 1.60934;
|
||||
else if (unit === 'lg') return v * 5.556;
|
||||
else if (unit === 'vr') return v * 1.0668;
|
||||
return 0; // 0 if distanceUnitInput is a custom unit
|
||||
}
|
||||
|
||||
function lat(lat) {return lat > 0 ? Math.abs(rn(lat)) + "°N" : Math.abs(rn(lat)) + "°S";} // parse latitude value
|
||||
const area = d3.geoGraticule().extent([[mc.lonW, mc.latN], [mc.lonE, mc.latS]]);
|
||||
globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
|
||||
function lat(lat) {
|
||||
return lat > 0 ? Math.abs(rn(lat)) + '°N' : Math.abs(rn(lat)) + '°S';
|
||||
} // parse latitude value
|
||||
const area = d3.geoGraticule().extent([
|
||||
[mc.lonW, mc.latN],
|
||||
[mc.lonE, mc.latS]
|
||||
]);
|
||||
globe.select('#globeArea').attr('d', round(path(area.outline()))); // map area
|
||||
}
|
||||
|
||||
function updateGlobeTemperature() {
|
||||
const tEq = +document.getElementById("temperatureEquatorOutput").value;
|
||||
document.getElementById("temperatureEquatorF").innerHTML = rn(tEq * 9/5 + 32);
|
||||
const tPole = +document.getElementById("temperaturePoleOutput").value;
|
||||
document.getElementById("temperaturePoleF").innerHTML = rn(tPole * 9/5 + 32);
|
||||
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
|
||||
globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 2/3 - tMin) / (tMax - tMin)));
|
||||
globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 1/3 - tMin) / (tMax - tMin)));
|
||||
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
|
||||
const tEq = +document.getElementById('temperatureEquatorOutput').value;
|
||||
document.getElementById('temperatureEquatorF').innerHTML = rn((tEq * 9) / 5 + 32);
|
||||
const tPole = +document.getElementById('temperaturePoleOutput').value;
|
||||
document.getElementById('temperaturePoleF').innerHTML = rn((tPole * 9) / 5 + 32);
|
||||
globe.selectAll('.tempGradient90').attr('stop-color', clr(1 - (tPole - tMin) / (tMax - tMin)));
|
||||
globe.selectAll('.tempGradient60').attr('stop-color', clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin)));
|
||||
globe.selectAll('.tempGradient30').attr('stop-color', clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin)));
|
||||
globe.select('.tempGradient0').attr('stop-color', clr(1 - (tEq - tMin) / (tMax - tMin)));
|
||||
}
|
||||
|
||||
function updateWindDirections() {
|
||||
globe.select("#globeWindArrows").selectAll("path").each(function(d, i) {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
|
||||
});
|
||||
globe
|
||||
.select('#globeWindArrows')
|
||||
.selectAll('path')
|
||||
.each(function (d, i) {
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
this.setAttribute('transform', `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
|
||||
});
|
||||
}
|
||||
|
||||
function changeWind() {
|
||||
const arrow = d3.event.target.nextElementSibling;
|
||||
const tier = +arrow.dataset.tier;
|
||||
options.winds[tier] = (options.winds[tier] + 45) % 360;
|
||||
const tr = parseTransform(arrow.getAttribute("transform"));
|
||||
arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
|
||||
localStorage.setItem("winds", options.winds);
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
||||
const tr = parseTransform(arrow.getAttribute('transform'));
|
||||
arrow.setAttribute('transform', `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
|
||||
localStorage.setItem('winds', options.winds);
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map((c) => ((90 - c) / 30) | 0);
|
||||
if (mapTiers.includes(tier)) updateWorld();
|
||||
}
|
||||
|
||||
function restoreDefaultWinds() {
|
||||
const defaultWinds = [225, 45, 225, 315, 135, 315];
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
||||
const update = mapTiers.some(t => options.winds[t] != defaultWinds[t]);
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map((c) => ((90 - c) / 30) | 0);
|
||||
const update = mapTiers.some((t) => options.winds[t] != defaultWinds[t]);
|
||||
options.winds = defaultWinds;
|
||||
updateWindDirections();
|
||||
if (update) updateWorld();
|
||||
}
|
||||
|
||||
function applyWorldPreset(size, lat) {
|
||||
document.getElementById("mapSizeInput").value = document.getElementById("mapSizeOutput").value = size;
|
||||
document.getElementById("latitudeInput").value = document.getElementById("latitudeOutput").value = lat;
|
||||
lock("mapSize");
|
||||
lock("latitude");
|
||||
document.getElementById('mapSizeInput').value = document.getElementById('mapSizeOutput').value = size;
|
||||
document.getElementById('latitudeInput').value = document.getElementById('latitudeOutput').value = lat;
|
||||
lock('mapSize');
|
||||
lock('latitude');
|
||||
updateWorld();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,83 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
function editZones() {
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
const body = document.getElementById("zonesBodySection");
|
||||
if (!layerIsOn('toggleZones')) toggleZones();
|
||||
const body = document.getElementById('zonesBodySection');
|
||||
zonesEditorAddLines();
|
||||
|
||||
if (modules.editZones) return;
|
||||
modules.editZones = true;
|
||||
|
||||
$("#zonesEditor").dialog({
|
||||
title: "Zones Editor",
|
||||
$('#zonesEditor').dialog({
|
||||
title: 'Zones Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: () => exitZonesManualAssignment("close"),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
close: () => exitZonesManualAssignment('close'),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
|
||||
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
|
||||
document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("zonesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("zonesManually").addEventListener("click", enterZonesManualAssignent);
|
||||
document.getElementById("zonesManuallyApply").addEventListener("click", applyZonesManualAssignent);
|
||||
document.getElementById("zonesManuallyCancel").addEventListener("click", cancelZonesManualAssignent);
|
||||
document.getElementById("zonesAdd").addEventListener("click", addZonesLayer);
|
||||
document.getElementById("zonesExport").addEventListener("click", downloadZonesData);
|
||||
document.getElementById("zonesRemove").addEventListener("click", toggleEraseMode);
|
||||
document.getElementById('zonesEditorRefresh').addEventListener('click', zonesEditorAddLines);
|
||||
document.getElementById('zonesEditStyle').addEventListener('click', () => editStyle('zones'));
|
||||
document.getElementById('zonesLegend').addEventListener('click', toggleLegend);
|
||||
document.getElementById('zonesPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('zonesManually').addEventListener('click', enterZonesManualAssignent);
|
||||
document.getElementById('zonesManuallyApply').addEventListener('click', applyZonesManualAssignent);
|
||||
document.getElementById('zonesManuallyCancel').addEventListener('click', cancelZonesManualAssignent);
|
||||
document.getElementById('zonesAdd').addEventListener('click', addZonesLayer);
|
||||
document.getElementById('zonesExport').addEventListener('click', downloadZonesData);
|
||||
document.getElementById('zonesRemove').addEventListener('click', toggleEraseMode);
|
||||
|
||||
body.addEventListener("click", function (ev) {
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList,
|
||||
zone = el.parentNode.dataset.id;
|
||||
if (cl.contains("culturePopulation")) {
|
||||
if (cl.contains('culturePopulation')) {
|
||||
changePopulation(zone);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-trash-empty")) {
|
||||
if (cl.contains('icon-trash-empty')) {
|
||||
zoneRemove(zone);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-eye")) {
|
||||
if (cl.contains('icon-eye')) {
|
||||
toggleVisibility(el);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-pin")) {
|
||||
if (cl.contains('icon-pin')) {
|
||||
toggleFog(zone, cl);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("fillRect")) {
|
||||
if (cl.contains('fillRect')) {
|
||||
changeFill(el);
|
||||
return;
|
||||
}
|
||||
if (customization) selectZone(el);
|
||||
});
|
||||
|
||||
body.addEventListener("input", function (ev) {
|
||||
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);
|
||||
if (el.classList.contains('religionName')) zones.select('#' + zone).attr('data-description', el.value);
|
||||
});
|
||||
|
||||
// add line for each zone
|
||||
function zonesEditorAddLines() {
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
let lines = "";
|
||||
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) : [];
|
||||
zones.selectAll('g').each(function () {
|
||||
const c = this.dataset.cells ? this.dataset.cells.split(',').map((c) => +c) : [];
|
||||
const description = this.dataset.description;
|
||||
const fill = this.getAttribute("fill");
|
||||
const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2;
|
||||
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
|
||||
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
|
||||
const fill = this.getAttribute('fill');
|
||||
const area = d3.sum(c.map((i) => pack.cells.area[i])) * distanceScaleInput.value ** 2;
|
||||
const rural = d3.sum(c.map((i) => pack.cells.pop[i])) * populationRate;
|
||||
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 = this.style.display === 'none';
|
||||
const focused = defs.select('#fog #focus' + this.id).size();
|
||||
|
||||
lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}" data-cells=${c.length} data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
|
||||
|
|
@ -89,8 +89,8 @@ function editZones() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Drag to raise or lower the zone" class="icon-resize-vertical hide"></span>
|
||||
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? "" : " inactive"} hide ${c.length ? "" : " placeholder"}"></span>
|
||||
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? " inactive" : ""} hide ${c.length ? "" : " placeholder"}"></span>
|
||||
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? '' : ' inactive'} hide ${c.length ? '' : ' placeholder'}"></span>
|
||||
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? ' inactive' : ''} hide ${c.length ? '' : ' placeholder'}"></span>
|
||||
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
});
|
||||
|
|
@ -99,73 +99,73 @@ function editZones() {
|
|||
|
||||
// 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;
|
||||
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 = zones.selectAll('g').size();
|
||||
zonesFooterCells.innerHTML = pack.cells.i.length;
|
||||
zonesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
zonesFooterPopulation.innerHTML = si(totalPop);
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => zoneHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => zoneHighlightOff(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => zoneHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => zoneHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
$("#zonesEditor").dialog({width: fitContent()});
|
||||
$('#zonesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function zoneHighlightOn(event) {
|
||||
const zone = event.target.dataset.id;
|
||||
zones.select("#" + zone).style("outline", "1px solid red");
|
||||
zones.select('#' + zone).style('outline', '1px solid red');
|
||||
}
|
||||
|
||||
function zoneHighlightOff(event) {
|
||||
const zone = event.target.dataset.id;
|
||||
zones.select("#" + zone).style("outline", null);
|
||||
zones.select('#' + zone).style('outline', null);
|
||||
}
|
||||
|
||||
$(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone});
|
||||
$(body).sortable({items: 'div.states', handle: '.icon-resize-vertical', containment: 'parent', axis: 'y', update: movezone});
|
||||
function movezone(ev, ui) {
|
||||
const zone = $("#" + ui.item.attr("data-id"));
|
||||
const prev = $("#" + ui.item.prev().attr("data-id"));
|
||||
const zone = $('#' + ui.item.attr('data-id'));
|
||||
const prev = $('#' + ui.item.prev().attr('data-id'));
|
||||
if (prev) {
|
||||
zone.insertAfter(prev);
|
||||
return;
|
||||
}
|
||||
const next = $("#" + ui.item.next().attr("data-id"));
|
||||
const next = $('#' + ui.item.next().attr('data-id'));
|
||||
if (next) zone.insertBefore(next);
|
||||
}
|
||||
|
||||
function enterZonesManualAssignent() {
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
if (!layerIsOn('toggleZones')) toggleZones();
|
||||
customization = 10;
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "inline-block";
|
||||
document.querySelectorAll('#zonesBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('zonesManuallyButtons').style.display = 'inline-block';
|
||||
|
||||
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
zonesFooter.style.display = "none";
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "none"));
|
||||
$("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
zonesEditor.querySelectorAll('.hide').forEach((el) => el.classList.add('hidden'));
|
||||
zonesFooter.style.display = 'none';
|
||||
body.querySelectorAll('div > input, select, svg').forEach((e) => (e.style.pointerEvents = 'none'));
|
||||
$('#zonesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}});
|
||||
|
||||
tip("Click to select a zone, drag to paint a zone", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectZoneOnMapClick).call(d3.drag().on("start", dragZoneBrush)).on("touchmove mousemove", moveZoneBrush);
|
||||
tip('Click to select a zone, drag to paint a zone', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectZoneOnMapClick).call(d3.drag().on('start', dragZoneBrush)).on('touchmove mousemove', moveZoneBrush);
|
||||
|
||||
body.querySelector("div").classList.add("selected");
|
||||
zones.selectAll("g").each(function () {
|
||||
this.setAttribute("data-init", this.getAttribute("data-cells"));
|
||||
body.querySelector('div').classList.add('selected');
|
||||
zones.selectAll('g').each(function () {
|
||||
this.setAttribute('data-init', this.getAttribute('data-cells'));
|
||||
});
|
||||
}
|
||||
|
||||
function selectZone(el) {
|
||||
body.querySelector("div.selected").classList.remove("selected");
|
||||
el.classList.add("selected");
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
el.classList.add('selected');
|
||||
}
|
||||
|
||||
function selectZoneOnMapClick() {
|
||||
if (d3.event.target.parentElement.parentElement.id !== "zones") return;
|
||||
if (d3.event.target.parentElement.parentElement.id !== 'zones') return;
|
||||
const zone = d3.event.target.parentElement.id;
|
||||
const el = body.querySelector("div[data-id='" + zone + "']");
|
||||
selectZone(el);
|
||||
|
|
@ -174,7 +174,7 @@ function editZones() {
|
|||
function dragZoneBrush() {
|
||||
const r = +zonesBrush.value;
|
||||
|
||||
d3.event.on("drag", () => {
|
||||
d3.event.on('drag', () => {
|
||||
if (!d3.event.dx && !d3.event.dy) return;
|
||||
const p = d3.mouse(this);
|
||||
moveCircle(p[0], p[1], r);
|
||||
|
|
@ -182,34 +182,34 @@ function editZones() {
|
|||
const selection = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)];
|
||||
if (!selection) return;
|
||||
|
||||
const selected = body.querySelector("div.selected");
|
||||
const zone = zones.select("#" + selected.dataset.id);
|
||||
const base = zone.attr("id") + "_"; // id generic part
|
||||
const dataCells = zone.attr("data-cells");
|
||||
let cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||
const selected = body.querySelector('div.selected');
|
||||
const zone = zones.select('#' + selected.dataset.id);
|
||||
const base = zone.attr('id') + '_'; // id generic part
|
||||
const dataCells = zone.attr('data-cells');
|
||||
let cells = dataCells ? dataCells.split(',').map((i) => +i) : [];
|
||||
|
||||
const erase = document.getElementById("zonesRemove").classList.contains("pressed");
|
||||
const erase = document.getElementById('zonesRemove').classList.contains('pressed');
|
||||
if (erase) {
|
||||
// remove
|
||||
selection.forEach(i => {
|
||||
selection.forEach((i) => {
|
||||
const index = cells.indexOf(i);
|
||||
if (index === -1) return;
|
||||
zone.select("polygon#" + base + i).remove();
|
||||
zone.select('polygon#' + base + i).remove();
|
||||
cells.splice(index, 1);
|
||||
});
|
||||
} else {
|
||||
// add
|
||||
selection.forEach(i => {
|
||||
selection.forEach((i) => {
|
||||
if (cells.includes(i)) return;
|
||||
cells.push(i);
|
||||
zone
|
||||
.append("polygon")
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("id", base + i);
|
||||
.append('polygon')
|
||||
.attr('points', getPackPolygon(i))
|
||||
.attr('id', base + i);
|
||||
});
|
||||
}
|
||||
|
||||
zone.attr("data-cells", cells);
|
||||
zone.attr('data-cells', cells);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -221,11 +221,11 @@ function editZones() {
|
|||
}
|
||||
|
||||
function applyZonesManualAssignent() {
|
||||
zones.selectAll("g").each(function () {
|
||||
zones.selectAll('g').each(function () {
|
||||
if (this.dataset.cells) return;
|
||||
// all zone cells are removed
|
||||
unfog("focusZone" + this.id);
|
||||
this.style.display = "block";
|
||||
unfog('focusZone' + this.id);
|
||||
this.style.display = 'block';
|
||||
});
|
||||
|
||||
zonesEditorAddLines();
|
||||
|
|
@ -234,20 +234,20 @@ function editZones() {
|
|||
|
||||
// restore initial zone cells
|
||||
function cancelZonesManualAssignent() {
|
||||
zones.selectAll("g").each(function () {
|
||||
zones.selectAll('g').each(function () {
|
||||
const zone = d3.select(this);
|
||||
const dataCells = zone.attr("data-init");
|
||||
const cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||
zone.attr("data-cells", cells);
|
||||
zone.selectAll("*").remove();
|
||||
const base = zone.attr("id") + "_"; // id generic part
|
||||
const dataCells = zone.attr('data-init');
|
||||
const cells = dataCells ? dataCells.split(',').map((i) => +i) : [];
|
||||
zone.attr('data-cells', cells);
|
||||
zone.selectAll('*').remove();
|
||||
const base = zone.attr('id') + '_'; // id generic part
|
||||
zone
|
||||
.selectAll("*")
|
||||
.selectAll('*')
|
||||
.data(cells)
|
||||
.enter()
|
||||
.append("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("id", d => base + d);
|
||||
.append('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('id', (d) => base + d);
|
||||
});
|
||||
|
||||
exitZonesManualAssignment();
|
||||
|
|
@ -256,97 +256,97 @@ function editZones() {
|
|||
function exitZonesManualAssignment(close) {
|
||||
customization = 0;
|
||||
removeCircle();
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "none";
|
||||
document.querySelectorAll('#zonesBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('zonesManuallyButtons').style.display = 'none';
|
||||
|
||||
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
|
||||
zonesFooter.style.display = "block";
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "all"));
|
||||
if (!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
zonesEditor.querySelectorAll('.hide:not(.show)').forEach((el) => el.classList.remove('hidden'));
|
||||
zonesFooter.style.display = 'block';
|
||||
body.querySelectorAll('div > input, select, svg').forEach((e) => (e.style.pointerEvents = 'all'));
|
||||
if (!close) $('#zonesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}});
|
||||
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
zones.selectAll("g").each(function () {
|
||||
this.removeAttribute("data-init");
|
||||
zones.selectAll('g').each(function () {
|
||||
this.removeAttribute('data-init');
|
||||
});
|
||||
const selected = body.querySelector("div.selected");
|
||||
if (selected) selected.classList.remove("selected");
|
||||
const selected = body.querySelector('div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
}
|
||||
|
||||
function changeFill(el) {
|
||||
const fill = el.getAttribute("fill");
|
||||
const fill = el.getAttribute('fill');
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill);
|
||||
el.setAttribute('fill', fill);
|
||||
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute('fill', fill);
|
||||
};
|
||||
|
||||
openPicker(fill, callback);
|
||||
}
|
||||
|
||||
function toggleVisibility(el) {
|
||||
const zone = zones.select("#" + el.parentNode.dataset.id);
|
||||
const inactive = zone.style("display") === "none";
|
||||
inactive ? zone.style("display", "block") : zone.style("display", "none");
|
||||
el.classList.toggle("inactive");
|
||||
const zone = zones.select('#' + el.parentNode.dataset.id);
|
||||
const inactive = zone.style('display') === 'none';
|
||||
inactive ? zone.style('display', 'block') : zone.style('display', 'none');
|
||||
el.classList.toggle('inactive');
|
||||
}
|
||||
|
||||
function toggleFog(z, cl) {
|
||||
const dataCells = zones.select("#" + z).attr("data-cells");
|
||||
const dataCells = zones.select('#' + z).attr('data-cells');
|
||||
if (!dataCells) return;
|
||||
|
||||
const path =
|
||||
"M" +
|
||||
'M' +
|
||||
dataCells
|
||||
.split(",")
|
||||
.map(c => getPackPolygon(+c))
|
||||
.join("M") +
|
||||
"Z",
|
||||
id = "focusZone" + z;
|
||||
cl.contains("inactive") ? fog(id, path) : unfog(id);
|
||||
cl.toggle("inactive");
|
||||
.split(',')
|
||||
.map((c) => getPackPolygon(+c))
|
||||
.join('M') +
|
||||
'Z',
|
||||
id = 'focusZone' + z;
|
||||
cl.contains('inactive') ? fog(id, path) : unfog(id);
|
||||
cl.toggle('inactive');
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll("*").size()) {
|
||||
if (legend.selectAll('*').size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const data = [];
|
||||
|
||||
zones.selectAll("g").each(function () {
|
||||
zones.selectAll('g').each(function () {
|
||||
const id = this.dataset.id;
|
||||
const description = this.dataset.description;
|
||||
const fill = this.getAttribute("fill");
|
||||
const fill = this.getAttribute('fill');
|
||||
data.push([id, fill, description]);
|
||||
});
|
||||
|
||||
drawLegend("Zones", data);
|
||||
drawLegend('Zones', data);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const totalCells = +zonesFooterCells.innerHTML;
|
||||
const totalArea = +zonesFooterArea.dataset.area;
|
||||
const totalPopulation = +zonesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + "%";
|
||||
el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + "%";
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
el.querySelector('.stateCells').innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + '%';
|
||||
el.querySelector('.biomeArea').innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + '%';
|
||||
el.querySelector('.culturePopulation').innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + '%';
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
body.dataset.type = 'absolute';
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
function addZonesLayer() {
|
||||
const id = getNextId("zone");
|
||||
const description = "Unknown zone";
|
||||
const fill = "url(#hatch" + (id.slice(4) % 14) + ")";
|
||||
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill);
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const id = getNextId('zone');
|
||||
const description = 'Unknown zone';
|
||||
const fill = 'url(#hatch' + (id.slice(4) % 14) + ')';
|
||||
zones.append('g').attr('id', id).attr('data-description', description).attr('data-cells', '').attr('fill', fill);
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
|
||||
const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0>
|
||||
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
|
||||
|
|
@ -363,53 +363,53 @@ function editZones() {
|
|||
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", line);
|
||||
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
||||
body.insertAdjacentHTML('beforeend', line);
|
||||
zonesFooterNumber.innerHTML = zones.selectAll('g').size();
|
||||
}
|
||||
|
||||
function downloadZonesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,Fill,Description,Cells,Area " + unit + ",Population\n"; // headers
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,Fill,Description,Cells,Area ' + unit + ',Population\n'; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.fill + ",";
|
||||
data += el.dataset.description + ",";
|
||||
data += el.dataset.cells + ",";
|
||||
data += el.dataset.area + ",";
|
||||
data += el.dataset.population + "\n";
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.fill + ',';
|
||||
data += el.dataset.description + ',';
|
||||
data += el.dataset.cells + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Zones") + ".csv";
|
||||
const name = getFileName('Zones') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function toggleEraseMode() {
|
||||
this.classList.toggle("pressed");
|
||||
this.classList.toggle('pressed');
|
||||
}
|
||||
|
||||
function changePopulation(zone) {
|
||||
const dataCells = zones.select("#" + zone).attr("data-cells");
|
||||
const dataCells = zones.select('#' + zone).attr('data-cells');
|
||||
const cells = dataCells
|
||||
? dataCells
|
||||
.split(",")
|
||||
.map(i => +i)
|
||||
.filter(i => pack.cells.h[i] >= 20)
|
||||
.split(',')
|
||||
.map((i) => +i)
|
||||
.filter((i) => pack.cells.h[i] >= 20)
|
||||
: [];
|
||||
if (!cells.length) {
|
||||
tip("Zone does not have any land cells, cannot change population", false, "error");
|
||||
tip('Zone does not have any land cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
|
||||
const burgs = pack.burgs.filter((b) => !b.removed && cells.includes(b.cell));
|
||||
|
||||
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate);
|
||||
const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization);
|
||||
const rural = rn(d3.sum(cells.map((i) => pack.cells.pop[i])) * populationRate);
|
||||
const urban = rn(d3.sum(cells.map((i) => pack.cells.burg[i]).map((b) => pack.burgs[b].population)) * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = n => Number(n).toLocaleString();
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"}>
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? '' : 'disabled'}>
|
||||
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
||||
|
||||
const update = function () {
|
||||
|
|
@ -422,41 +422,41 @@ function editZones() {
|
|||
ruralPop.oninput = () => update();
|
||||
urbanPop.oninput = () => update();
|
||||
|
||||
$("#alert").dialog({
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: "Change zone population",
|
||||
width: "24em",
|
||||
title: 'Change zone population',
|
||||
width: '24em',
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
applyPopulationChange();
|
||||
$(this).dialog("close");
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
|
||||
function applyPopulationChange() {
|
||||
const ruralChange = ruralPop.value / rural;
|
||||
if (isFinite(ruralChange) && ruralChange !== 1) {
|
||||
cells.forEach(i => (pack.cells.pop[i] *= ruralChange));
|
||||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate;
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach(i => (pack.cells.pop[i] = pop));
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
}
|
||||
|
||||
const urbanChange = urbanPop.value / urban;
|
||||
if (isFinite(urbanChange) && urbanChange !== 1) {
|
||||
burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4)));
|
||||
burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach(b => (b.population = population));
|
||||
burgs.forEach((b) => (b.population = population));
|
||||
}
|
||||
|
||||
zonesEditorAddLines();
|
||||
|
|
@ -464,8 +464,8 @@ function editZones() {
|
|||
}
|
||||
|
||||
function zoneRemove(zone) {
|
||||
zones.select("#" + zone).remove();
|
||||
unfog("focusZone" + zone);
|
||||
zones.select('#' + zone).remove();
|
||||
unfog('focusZone' + zone);
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue