mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor(es modules): split layers to modules
This commit is contained in:
parent
05564ef5d9
commit
7755d8b588
37 changed files with 2035 additions and 1998 deletions
|
|
@ -7671,7 +7671,7 @@
|
||||||
<script type="module" src="/src/modules/names-generator.js"></script>
|
<script type="module" src="/src/modules/names-generator.js"></script>
|
||||||
<script type="module" src="/src/modules/biomes.js"></script>
|
<script type="module" src="/src/modules/biomes.js"></script>
|
||||||
<script type="module" src="/src/modules/cultures-generator.js"></script>
|
<script type="module" src="/src/modules/cultures-generator.js"></script>
|
||||||
<script type="module" src="/src/modules/burgs-and-states.js?v=1.87.04"></script>
|
<script type="module" src="/src/modules/burgs-and-states.js"></script>
|
||||||
<script type="module" src="/src/modules/routes-generator.js"></script>
|
<script type="module" src="/src/modules/routes-generator.js"></script>
|
||||||
<script type="module" src="/src/modules/religions-generator.js"></script>
|
<script type="module" src="/src/modules/religions-generator.js"></script>
|
||||||
<script type="module" src="/src/modules/military-generator.js"></script>
|
<script type="module" src="/src/modules/military-generator.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export {initLayers, restoreLayers, updatePresetInput} from "./init";
|
||||||
|
export {renderLayer} from "./renderers";
|
||||||
|
export {toggleLayer} from "./toggles";
|
||||||
|
export {layerIsOn, turnLayerButtonOff, turnLayerButtonOn} from "./utils";
|
||||||
246
src/layers/init.ts
Normal file
246
src/layers/init.ts
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
import {prompt} from "/src/scripts/prompt";
|
||||||
|
import {byId, store, stored} from "/src/utils/shorthands";
|
||||||
|
import {layerIsOn} from "./utils";
|
||||||
|
|
||||||
|
export function initLayers() {
|
||||||
|
restoreCustomPresets();
|
||||||
|
applyPreset();
|
||||||
|
addLayerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
let presets = {};
|
||||||
|
|
||||||
|
const defaultPresets = {
|
||||||
|
political: [
|
||||||
|
"toggleBorders",
|
||||||
|
"toggleIcons",
|
||||||
|
"toggleIce",
|
||||||
|
"toggleLabels",
|
||||||
|
"toggleRivers",
|
||||||
|
"toggleRoutes",
|
||||||
|
"toggleScaleBar",
|
||||||
|
"toggleStates"
|
||||||
|
],
|
||||||
|
cultural: [
|
||||||
|
"toggleBorders",
|
||||||
|
"toggleCultures",
|
||||||
|
"toggleIcons",
|
||||||
|
"toggleLabels",
|
||||||
|
"toggleRivers",
|
||||||
|
"toggleRoutes",
|
||||||
|
"toggleScaleBar"
|
||||||
|
],
|
||||||
|
religions: [
|
||||||
|
"toggleBorders",
|
||||||
|
"toggleIcons",
|
||||||
|
"toggleLabels",
|
||||||
|
"toggleReligions",
|
||||||
|
"toggleRivers",
|
||||||
|
"toggleRoutes",
|
||||||
|
"toggleScaleBar"
|
||||||
|
],
|
||||||
|
provinces: ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"],
|
||||||
|
biomes: ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar"],
|
||||||
|
heightmap: ["toggleHeight", "toggleRivers"],
|
||||||
|
physical: ["toggleCoordinates", "toggleHeight", "toggleIce", "toggleRivers", "toggleScaleBar"],
|
||||||
|
poi: [
|
||||||
|
"toggleBorders",
|
||||||
|
"toggleHeight",
|
||||||
|
"toggleIce",
|
||||||
|
"toggleIcons",
|
||||||
|
"toggleMarkers",
|
||||||
|
"toggleRivers",
|
||||||
|
"toggleRoutes",
|
||||||
|
"toggleScaleBar"
|
||||||
|
],
|
||||||
|
military: [
|
||||||
|
"toggleBorders",
|
||||||
|
"toggleIcons",
|
||||||
|
"toggleLabels",
|
||||||
|
"toggleMilitary",
|
||||||
|
"toggleRivers",
|
||||||
|
"toggleRoutes",
|
||||||
|
"toggleScaleBar",
|
||||||
|
"toggleStates"
|
||||||
|
],
|
||||||
|
emblems: [
|
||||||
|
"toggleBorders",
|
||||||
|
"toggleIcons",
|
||||||
|
"toggleIce",
|
||||||
|
"toggleEmblems",
|
||||||
|
"toggleRivers",
|
||||||
|
"toggleRoutes",
|
||||||
|
"toggleScaleBar",
|
||||||
|
"toggleStates"
|
||||||
|
],
|
||||||
|
landmass: ["toggleScaleBar"]
|
||||||
|
};
|
||||||
|
|
||||||
|
function restoreCustomPresets() {
|
||||||
|
const storedPresets = JSON.parse(stored("presets"));
|
||||||
|
if (!storedPresets) {
|
||||||
|
presets = structuredClone(defaultPresets);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const preset in storedPresets) {
|
||||||
|
if (presets[preset]) continue;
|
||||||
|
byId("layersPreset").add(new Option(preset, preset));
|
||||||
|
}
|
||||||
|
|
||||||
|
presets = storedPresets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLayerListeners() {
|
||||||
|
byId("mapLayers").on("click", toggleLayerOnClick);
|
||||||
|
byId("savePresetButton").on("click", savePreset);
|
||||||
|
byId("removePresetButton").on("click", removePreset);
|
||||||
|
|
||||||
|
// allow to move layers by dragging layer button (jquery)
|
||||||
|
$("#mapLayers").sortable({items: "li:not(.solid)", containment: "parent", cancel: ".solid", update: moveLayer});
|
||||||
|
}
|
||||||
|
|
||||||
|
// connection between option layer buttons and actual svg groups to move the element
|
||||||
|
const layerButtonToElementMap = {
|
||||||
|
toggleBiomes: "biomes",
|
||||||
|
toggleBorders: "borders",
|
||||||
|
toggleCells: "cells",
|
||||||
|
toggleCompass: "compass",
|
||||||
|
toggleCoordinates: "coordinates",
|
||||||
|
toggleCultures: "cults",
|
||||||
|
toggleEmblems: "emblems",
|
||||||
|
toggleGrid: "gridOverlay",
|
||||||
|
toggleHeight: "terrs",
|
||||||
|
toggleIce: "ice",
|
||||||
|
toggleIcons: "icons",
|
||||||
|
toggleLabels: "labels",
|
||||||
|
toggleMarkers: "markers",
|
||||||
|
toggleMilitary: "armies",
|
||||||
|
togglePopulation: "population",
|
||||||
|
togglePrec: "prec",
|
||||||
|
toggleProvinces: "provs",
|
||||||
|
toggleRelief: "terrain",
|
||||||
|
toggleReligions: "relig",
|
||||||
|
toggleRivers: "rivers",
|
||||||
|
toggleRoutes: "routes",
|
||||||
|
toggleRulers: "ruler",
|
||||||
|
toggleStates: "regions",
|
||||||
|
toggleTemp: "temperature",
|
||||||
|
toggleTexture: "texture",
|
||||||
|
toggleZones: "zones"
|
||||||
|
};
|
||||||
|
|
||||||
|
function moveLayer(event, $layerButton) {
|
||||||
|
const getLayer = buttonId => $("#" + layerButtonToElementMap[buttonId]);
|
||||||
|
const layer = getLayer($layerButton.item.attr("id"));
|
||||||
|
if (!layer) return;
|
||||||
|
|
||||||
|
const prev = getLayer($layerButton.item.prev().attr("id"));
|
||||||
|
const next = getLayer($layerButton.item.next().attr("id"));
|
||||||
|
|
||||||
|
if (prev) layer.insertAfter(prev);
|
||||||
|
else if (next) layer.insertBefore(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLayerOnClick(event) {
|
||||||
|
const targetId = event.target.id;
|
||||||
|
if (!targetId || targetId === "mapLayers" || !layerTogglesMap[targetId]) return;
|
||||||
|
layerTogglesMap[targetId]();
|
||||||
|
}
|
||||||
|
|
||||||
|
// run on map generation
|
||||||
|
function applyPreset() {
|
||||||
|
const preset = stored("preset") || byId("layersPreset")?.value || "political";
|
||||||
|
changePreset(preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggle layers on preset change
|
||||||
|
function changePreset(preset) {
|
||||||
|
const layers = presets[preset]; // layers to be turned on
|
||||||
|
const $layerButtons = byId("mapLayers").querySelectorAll("li");
|
||||||
|
|
||||||
|
$layerButtons.forEach(function ($layerButton) {
|
||||||
|
const {id} = $layerButton;
|
||||||
|
if (layers.includes(id) && !layerIsOn(id)) $layerButton.click();
|
||||||
|
else if (!layers.includes(id) && layerIsOn(id)) $layerButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
byId("layersPreset").value = preset;
|
||||||
|
store("preset", preset);
|
||||||
|
|
||||||
|
const isDefault = defaultPresets[preset];
|
||||||
|
byId("removePresetButton").style.display = isDefault ? "none" : "inline-block";
|
||||||
|
byId("savePresetButton").style.display = "none";
|
||||||
|
if (byId("canvas3d")) setTimeout(ThreeD.update(), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePreset() {
|
||||||
|
prompt("Please provide a preset name", {default: ""}, preset => {
|
||||||
|
presets[preset] = Array.from(byId("mapLayers").querySelectorAll("li:not(.buttonoff)"))
|
||||||
|
.map(node => node.id)
|
||||||
|
.sort();
|
||||||
|
layersPreset.add(new Option(preset, preset, false, true));
|
||||||
|
localStorage.setItem("presets", JSON.stringify(presets));
|
||||||
|
localStorage.setItem("preset", preset);
|
||||||
|
removePresetButton.style.display = "inline-block";
|
||||||
|
savePresetButton.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePreset() {
|
||||||
|
const preset = layersPreset.value;
|
||||||
|
delete presets[preset];
|
||||||
|
const index = Array.from(layersPreset.options).findIndex(o => o.value === preset);
|
||||||
|
layersPreset.options.remove(index);
|
||||||
|
layersPreset.value = "custom";
|
||||||
|
removePresetButton.style.display = "none";
|
||||||
|
savePresetButton.style.display = "inline-block";
|
||||||
|
|
||||||
|
store("presets", JSON.stringify(presets));
|
||||||
|
localStorage.removeItem("preset");
|
||||||
|
}
|
||||||
|
|
||||||
|
// run on map regeneration
|
||||||
|
export function restoreLayers() {
|
||||||
|
if (layerIsOn("toggleHeight")) drawHeightmap();
|
||||||
|
if (layerIsOn("toggleCells")) drawCells();
|
||||||
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||||
|
if (layerIsOn("toggleCompass")) compass.style("display", "block");
|
||||||
|
if (layerIsOn("toggleTemp")) drawTemp();
|
||||||
|
if (layerIsOn("togglePrec")) drawPrec();
|
||||||
|
if (layerIsOn("togglePopulation")) drawPopulation();
|
||||||
|
if (layerIsOn("toggleBiomes")) drawBiomes();
|
||||||
|
if (layerIsOn("toggleRelief")) ReliefIcons();
|
||||||
|
if (layerIsOn("toggleCultures")) drawCultures();
|
||||||
|
if (layerIsOn("toggleProvinces")) drawProvinces();
|
||||||
|
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();
|
||||||
|
if (!layerIsOn("toggleStates")) regions.selectAll("path").remove();
|
||||||
|
if (!layerIsOn("toggleRivers")) rivers.selectAll("*").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePresetInput() {
|
||||||
|
const $toggledOnLayers = byId("mapLayers").querySelectorAll("li:not(.buttonoff)");
|
||||||
|
const currentLayers = Array.from($toggledOnLayers)
|
||||||
|
.map(node => node.id)
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
for (const preset in presets) {
|
||||||
|
if (JSON.stringify(presets[preset].sort()) !== JSON.stringify(currentLayers)) continue;
|
||||||
|
|
||||||
|
byId("layersPreset").value = preset;
|
||||||
|
byId("removePresetButton").style.display = defaultPresets[preset] ? "none" : "inline-block";
|
||||||
|
byId("savePresetButton").style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byId("layersPreset").value = "custom";
|
||||||
|
byId("removePresetButton").style.display = "none";
|
||||||
|
byId("savePresetButton").style.display = "inline-block";
|
||||||
|
}
|
||||||
62
src/layers/renderers/drawBiomes.js
Normal file
62
src/layers/renderers/drawBiomes.js
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {clipPoly} from "/src/utils/lineUtils";
|
||||||
|
|
||||||
|
export function drawBiomes() {
|
||||||
|
TIME && console.time("drawBiomes");
|
||||||
|
biomes.selectAll("path").remove();
|
||||||
|
|
||||||
|
const {cells, vertices} = pack;
|
||||||
|
const n = cells.i.length;
|
||||||
|
const used = new Uint8Array(cells.i.length);
|
||||||
|
const paths = new Array(biomesData.i.length).fill("");
|
||||||
|
|
||||||
|
for (const i of cells.i) {
|
||||||
|
if (!cells.biome[i]) continue; // no need to mark marine biome (liquid water)
|
||||||
|
if (used[i]) continue; // already marked
|
||||||
|
const b = cells.biome[i];
|
||||||
|
const onborder = cells.c[i].some(n => cells.biome[n] !== b);
|
||||||
|
if (!onborder) continue;
|
||||||
|
const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b));
|
||||||
|
const chain = connectVertices(edgeVerticle, b);
|
||||||
|
if (chain.length < 3) continue;
|
||||||
|
const points = clipPoly(
|
||||||
|
chain.map(v => vertices.p[v]),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
paths[b] += "M" + points.join("L") + "Z";
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.forEach(function (d, i) {
|
||||||
|
if (d.length < 10) return;
|
||||||
|
biomes
|
||||||
|
.append("path")
|
||||||
|
.attr("d", d)
|
||||||
|
.attr("fill", biomesData.color[i])
|
||||||
|
.attr("stroke", biomesData.color[i])
|
||||||
|
.attr("id", "biome" + i);
|
||||||
|
});
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start, b) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = chain[chain.length - 1]; // previous vertex in chain
|
||||||
|
chain.push(current); // add current vertex to sequence
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
c.filter(c => cells.biome[c] === b).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || cells.biome[c[0]] !== b;
|
||||||
|
const c1 = c[1] >= n || cells.biome[c[1]] !== b;
|
||||||
|
const c2 = c[2] >= n || cells.biome[c[2]] !== b;
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||||
|
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||||
|
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||||
|
if (current === chain[chain.length - 1]) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
TIME && console.timeEnd("drawBiomes");
|
||||||
|
}
|
||||||
102
src/layers/renderers/drawBorders.js
Normal file
102
src/layers/renderers/drawBorders.js
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
export function drawBorders() {
|
||||||
|
borders.selectAll("path").remove();
|
||||||
|
|
||||||
|
const {cells, vertices} = pack;
|
||||||
|
const n = cells.i.length;
|
||||||
|
|
||||||
|
const sPath = [];
|
||||||
|
const pPath = [];
|
||||||
|
|
||||||
|
const sUsed = new Array(pack.states.length).fill("").map(_ => []);
|
||||||
|
const pUsed = new Array(pack.provinces.length).fill("").map(_ => []);
|
||||||
|
|
||||||
|
for (let i = 0; i < cells.i.length; i++) {
|
||||||
|
if (!cells.state[i]) continue;
|
||||||
|
const p = cells.province[i];
|
||||||
|
const s = cells.state[i];
|
||||||
|
|
||||||
|
// if cell is on province border
|
||||||
|
const provToCell = cells.c[i].find(
|
||||||
|
n => cells.state[n] === s && p > cells.province[n] && pUsed[p][n] !== cells.province[n]
|
||||||
|
);
|
||||||
|
if (provToCell) {
|
||||||
|
const provTo = cells.province[provToCell];
|
||||||
|
pUsed[p][provToCell] = provTo;
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.province[i] === provTo));
|
||||||
|
const chain = connectVertices(vertex, p, cells.province, provTo, pUsed);
|
||||||
|
|
||||||
|
if (chain.length > 1) {
|
||||||
|
pPath.push("M" + chain.map(c => vertices.p[c]).join(" "));
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if cell is on state border
|
||||||
|
const stateToCell = cells.c[i].find(n => cells.h[n] >= 20 && s > cells.state[n] && sUsed[s][n] !== cells.state[n]);
|
||||||
|
if (stateToCell !== undefined) {
|
||||||
|
const stateTo = cells.state[stateToCell];
|
||||||
|
sUsed[s][stateToCell] = stateTo;
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] >= 20 && cells.state[i] === stateTo));
|
||||||
|
const chain = connectVertices(vertex, s, cells.state, stateTo, sUsed);
|
||||||
|
|
||||||
|
if (chain.length > 1) {
|
||||||
|
sPath.push("M" + chain.map(c => vertices.p[c]).join(" "));
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBorders.append("path").attr("d", sPath.join(" "));
|
||||||
|
provinceBorders.append("path").attr("d", pPath.join(" "));
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(current, f, array, t, used) {
|
||||||
|
let chain = [];
|
||||||
|
const checkCell = c => c >= n || array[c] !== f;
|
||||||
|
const checkVertex = v =>
|
||||||
|
vertices.c[v].some(c => array[c] === f) && vertices.c[v].some(c => array[c] === t && cells.h[c] >= 20);
|
||||||
|
|
||||||
|
// find starting vertex
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
if (i === 999) ERROR && console.error("Find starting vertex: limit is reached", current, f, t);
|
||||||
|
const p = chain[chain.length - 2] || -1; // previous vertex
|
||||||
|
const v = vertices.v[current],
|
||||||
|
c = vertices.c[current];
|
||||||
|
|
||||||
|
const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]);
|
||||||
|
const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]);
|
||||||
|
const v2 = checkCell(c[0]) !== checkCell(c[2]) && checkVertex(v[2]);
|
||||||
|
if (v0 + v1 + v2 === 1) break;
|
||||||
|
current = v0 && p !== v[0] ? v[0] : v1 && p !== v[1] ? v[1] : v[2];
|
||||||
|
|
||||||
|
if (current === chain[0]) break;
|
||||||
|
if (current === p) return [];
|
||||||
|
chain.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
chain = [current]; // vertices chain to form a path
|
||||||
|
// find path
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
if (i === 999) ERROR && console.error("Find path: limit is reached", current, f, t);
|
||||||
|
const p = chain[chain.length - 2] || -1; // previous vertex
|
||||||
|
const v = vertices.v[current],
|
||||||
|
c = vertices.c[current];
|
||||||
|
c.filter(c => array[c] === t).forEach(c => (used[f][c] = t));
|
||||||
|
|
||||||
|
const v0 = checkCell(c[0]) !== checkCell(c[1]) && checkVertex(v[0]);
|
||||||
|
const v1 = checkCell(c[1]) !== checkCell(c[2]) && checkVertex(v[1]);
|
||||||
|
const v2 = checkCell(c[0]) !== checkCell(c[2]) && checkVertex(v[2]);
|
||||||
|
current = v0 && p !== v[0] ? v[0] : v1 && p !== v[1] ? v[1] : v[2];
|
||||||
|
|
||||||
|
if (current === p) break;
|
||||||
|
if (current === chain[chain.length - 1]) break;
|
||||||
|
if (chain.length > 1 && v0 + v1 + v2 < 2) break;
|
||||||
|
chain.push(current);
|
||||||
|
if (current === chain[0]) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/layers/renderers/drawCells.js
Normal file
11
src/layers/renderers/drawCells.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import {getGridPolygon} from "/src/utils/graphUtils";
|
||||||
|
|
||||||
|
export function drawCells() {
|
||||||
|
cells.selectAll("path").remove();
|
||||||
|
|
||||||
|
const cellIds = customization === 1 ? grid.cells.i : pack.cells.i;
|
||||||
|
const getPolygon = customization === 1 ? getGridPolygon : getPackPolygon;
|
||||||
|
|
||||||
|
const paths = cellIds.map(getPolygon);
|
||||||
|
cells.append("path").attr("d", "M" + paths.join("M"));
|
||||||
|
}
|
||||||
69
src/layers/renderers/drawCoordinates.js
Normal file
69
src/layers/renderers/drawCoordinates.js
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {rn} from "/src/utils/numberUtils";
|
||||||
|
import {round} from "/src/utils/stringUtils";
|
||||||
|
import {byId} from "/src/utils/shorthands";
|
||||||
|
|
||||||
|
export function drawCoordinates() {
|
||||||
|
coordinates.selectAll("*").remove(); // remove every time
|
||||||
|
const steps = [0.5, 1, 2, 5, 10, 15, 30]; // possible steps
|
||||||
|
const goal = mapCoordinates.lonT / scale / 10;
|
||||||
|
const step = steps.reduce((p, c) => (Math.abs(c - goal) < Math.abs(p - goal) ? c : p));
|
||||||
|
|
||||||
|
const desired = +coordinates.attr("data-size"); // desired label size
|
||||||
|
coordinates.attr("font-size", Math.max(rn(desired / scale ** 0.8, 2), 0.1)); // actual label size
|
||||||
|
const graticule = d3
|
||||||
|
.geoGraticule()
|
||||||
|
.extent([
|
||||||
|
[mapCoordinates.lonW, mapCoordinates.latN],
|
||||||
|
[mapCoordinates.lonE + 0.1, mapCoordinates.latS + 0.1]
|
||||||
|
])
|
||||||
|
.stepMajor([400, 400])
|
||||||
|
.stepMinor([step, step]);
|
||||||
|
const projection = d3.geoEquirectangular().fitSize([graphWidth, graphHeight], graticule());
|
||||||
|
|
||||||
|
const grid = coordinates.append("g").attr("id", "coordinateGrid");
|
||||||
|
const labels = coordinates.append("g").attr("id", "coordinateLabels");
|
||||||
|
|
||||||
|
const p = getViewPoint(scale + desired + 2, scale + desired / 2); // on border point on viexBox
|
||||||
|
const data = graticule.lines().map(d => {
|
||||||
|
const lat = d.coordinates[0][1] === d.coordinates[1][1]; // check if line is latitude or longitude
|
||||||
|
const c = d.coordinates[0];
|
||||||
|
const pos = projection(c); // map coordinates
|
||||||
|
const [x, y] = lat ? [rn(p.x, 2), rn(pos[1], 2)] : [rn(pos[0], 2), rn(p.y, 2)]; // labels position
|
||||||
|
const v = lat ? c[1] : c[0]; // label
|
||||||
|
|
||||||
|
const text = !v
|
||||||
|
? v
|
||||||
|
: Number.isInteger(v)
|
||||||
|
? lat
|
||||||
|
? c[1] < 0
|
||||||
|
? -c[1] + "°S"
|
||||||
|
: c[1] + "°N"
|
||||||
|
: c[0] < 0
|
||||||
|
? -c[0] + "°W"
|
||||||
|
: c[0] + "°E"
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return {lat, x, y, text};
|
||||||
|
});
|
||||||
|
|
||||||
|
const d = round(d3.geoPath(projection)(graticule()));
|
||||||
|
grid.append("path").attr("d", d).attr("vector-effect", "non-scaling-stroke");
|
||||||
|
labels
|
||||||
|
.selectAll("text")
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("x", d => d.x)
|
||||||
|
.attr("y", d => d.y)
|
||||||
|
.text(d => d.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// conver svg point into viewBox point
|
||||||
|
function getViewPoint(x, y) {
|
||||||
|
const view = byId("viewbox");
|
||||||
|
const svg = byId("map");
|
||||||
|
const pt = svg.createSVGPoint();
|
||||||
|
pt.x = x;
|
||||||
|
pt.y = y;
|
||||||
|
return pt.matrixTransform(view.getScreenCTM().inverse());
|
||||||
|
}
|
||||||
54
src/layers/renderers/drawCultures.js
Normal file
54
src/layers/renderers/drawCultures.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
export function drawCultures() {
|
||||||
|
cults.selectAll("path").remove();
|
||||||
|
const {cells, vertices, cultures} = pack;
|
||||||
|
const n = cells.i.length;
|
||||||
|
const used = new Uint8Array(cells.i.length);
|
||||||
|
const paths = new Array(cultures.length).fill("");
|
||||||
|
|
||||||
|
for (const i of cells.i) {
|
||||||
|
if (!cells.culture[i]) continue;
|
||||||
|
if (used[i]) continue;
|
||||||
|
used[i] = 1;
|
||||||
|
const c = cells.culture[i];
|
||||||
|
const onborder = cells.c[i].some(n => cells.culture[n] !== c);
|
||||||
|
if (!onborder) continue;
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.culture[i] !== c));
|
||||||
|
const chain = connectVertices(vertex, c);
|
||||||
|
if (chain.length < 3) continue;
|
||||||
|
const points = chain.map(v => vertices.p[v]);
|
||||||
|
paths[c] += "M" + points.join("L") + "Z";
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = paths.map((p, i) => [p, i]).filter(d => d[0].length > 10);
|
||||||
|
cults
|
||||||
|
.selectAll("path")
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.attr("d", d => d[0])
|
||||||
|
.attr("fill", d => cultures[d[1]].color)
|
||||||
|
.attr("id", d => "culture" + d[1]);
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start, t) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = chain[chain.length - 1]; // previous vertex in chain
|
||||||
|
chain.push(current); // add current vertex to sequence
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
c.filter(c => cells.culture[c] === t).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || cells.culture[c[0]] !== t;
|
||||||
|
const c1 = c[1] >= n || cells.culture[c[1]] !== t;
|
||||||
|
const c2 = c[2] >= n || cells.culture[c[2]] !== t;
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||||
|
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||||
|
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||||
|
if (current === chain[chain.length - 1]) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/layers/renderers/drawEmblems.js
Normal file
110
src/layers/renderers/drawEmblems.js
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import {getProvincesVertices} from "./drawProvinces";
|
||||||
|
import {minmax, rn} from "/src/utils/numberUtils";
|
||||||
|
import {byId} from "/src/utils/shorthands";
|
||||||
|
|
||||||
|
export function drawEmblems() {
|
||||||
|
const {states, provinces, burgs} = pack;
|
||||||
|
|
||||||
|
const validStates = states.filter(s => s.i && !s.removed && s.coa && s.coaSize != 0);
|
||||||
|
const validProvinces = provinces.filter(p => p.i && !p.removed && p.coa && p.coaSize != 0);
|
||||||
|
const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa && b.coaSize != 0);
|
||||||
|
|
||||||
|
const getStateEmblemsSize = () => {
|
||||||
|
const startSize = minmax((graphHeight + graphWidth) / 40, 10, 100);
|
||||||
|
const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier
|
||||||
|
const sizeMod = +byId("emblemsStateSizeInput").value || 1;
|
||||||
|
return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProvinceEmblemsSize = () => {
|
||||||
|
const startSize = minmax((graphHeight + graphWidth) / 100, 5, 70);
|
||||||
|
const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier
|
||||||
|
const sizeMod = +byId("emblemsProvinceSizeInput").value || 1;
|
||||||
|
return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBurgEmblemSize = () => {
|
||||||
|
const startSize = minmax((graphHeight + graphWidth) / 185, 2, 50);
|
||||||
|
const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier
|
||||||
|
const sizeMod = +byId("emblemsBurgSizeInput").value || 1;
|
||||||
|
return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeBurgs = getBurgEmblemSize();
|
||||||
|
const burgCOAs = validBurgs.map(burg => {
|
||||||
|
const {x, y} = burg;
|
||||||
|
const size = burg.coaSize || 1;
|
||||||
|
const shift = (sizeBurgs * size) / 2;
|
||||||
|
return {type: "burg", i: burg.i, x, y, size, shift};
|
||||||
|
});
|
||||||
|
|
||||||
|
const sizeProvinces = getProvinceEmblemsSize();
|
||||||
|
const provinceCOAs = validProvinces.map(province => {
|
||||||
|
if (!province.pole) getProvincesVertices();
|
||||||
|
const [x, y] = province.pole || pack.cells.p[province.center];
|
||||||
|
const size = province.coaSize || 1;
|
||||||
|
const shift = (sizeProvinces * size) / 2;
|
||||||
|
return {type: "province", i: province.i, x, y, size, shift};
|
||||||
|
});
|
||||||
|
|
||||||
|
const sizeStates = getStateEmblemsSize();
|
||||||
|
const stateCOAs = validStates.map(state => {
|
||||||
|
const [x, y] = state.pole || pack.cells.p[state.center];
|
||||||
|
const size = state.coaSize || 1;
|
||||||
|
const shift = (sizeStates * size) / 2;
|
||||||
|
return {type: "state", i: state.i, x, y, size, shift};
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodes = burgCOAs.concat(provinceCOAs).concat(stateCOAs);
|
||||||
|
const simulation = d3
|
||||||
|
.forceSimulation(nodes)
|
||||||
|
.alphaMin(0.6)
|
||||||
|
.alphaDecay(0.2)
|
||||||
|
.velocityDecay(0.6)
|
||||||
|
.force(
|
||||||
|
"collision",
|
||||||
|
d3.forceCollide().radius(d => d.shift)
|
||||||
|
)
|
||||||
|
.stop();
|
||||||
|
|
||||||
|
d3.timeout(function () {
|
||||||
|
const n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()));
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
simulation.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
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("");
|
||||||
|
emblems.select("#stateEmblems").attr("font-size", sizeStates).html(stateString);
|
||||||
|
|
||||||
|
Zoom.invoke();
|
||||||
|
});
|
||||||
|
}
|
||||||
29
src/layers/renderers/drawGrid.js
Normal file
29
src/layers/renderers/drawGrid.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export function drawGrid() {
|
||||||
|
gridOverlay.selectAll("*").remove();
|
||||||
|
const pattern = "#pattern_" + (gridOverlay.attr("type") || "pointyHex");
|
||||||
|
const stroke = gridOverlay.attr("stroke") || "#808080";
|
||||||
|
const width = gridOverlay.attr("stroke-width") || 0.5;
|
||||||
|
const dasharray = gridOverlay.attr("stroke-dasharray") || null;
|
||||||
|
const linecap = gridOverlay.attr("stroke-linecap") || null;
|
||||||
|
const scale = gridOverlay.attr("scale") || 1;
|
||||||
|
const dx = gridOverlay.attr("dx") || 0;
|
||||||
|
const dy = gridOverlay.attr("dy") || 0;
|
||||||
|
const tr = `scale(${scale}) translate(${dx} ${dy})`;
|
||||||
|
|
||||||
|
const maxWidth = Math.max(+mapWidthInput.value, graphWidth);
|
||||||
|
const maxHeight = Math.max(+mapHeightInput.value, graphHeight);
|
||||||
|
|
||||||
|
d3.select(pattern)
|
||||||
|
.attr("stroke", stroke)
|
||||||
|
.attr("stroke-width", width)
|
||||||
|
.attr("stroke-dasharray", dasharray)
|
||||||
|
.attr("stroke-linecap", linecap)
|
||||||
|
.attr("patternTransform", tr);
|
||||||
|
|
||||||
|
gridOverlay
|
||||||
|
.append("rect")
|
||||||
|
.attr("width", maxWidth)
|
||||||
|
.attr("height", maxHeight)
|
||||||
|
.attr("fill", "url(" + pattern + ")")
|
||||||
|
.attr("stroke", "none");
|
||||||
|
}
|
||||||
87
src/layers/renderers/drawHeightmap.js
Normal file
87
src/layers/renderers/drawHeightmap.js
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import {getColorScheme, getHeightColor} from "/src/utils/colorUtils";
|
||||||
|
|
||||||
|
export function drawHeightmap() {
|
||||||
|
terrs.selectAll("*").remove();
|
||||||
|
|
||||||
|
const {cells, vertices} = pack;
|
||||||
|
const n = cells.i.length;
|
||||||
|
const used = new Uint8Array(cells.i.length);
|
||||||
|
const paths = new Array(101).fill("");
|
||||||
|
|
||||||
|
const scheme = getColorScheme(terrs.attr("scheme"));
|
||||||
|
const terracing = terrs.attr("terracing") / 10; // add additional shifted darker layer for pseudo-3d effect
|
||||||
|
const skip = +terrs.attr("skip") + 1;
|
||||||
|
const simplification = +terrs.attr("relax");
|
||||||
|
|
||||||
|
const curveMap = {0: d3.curveBasisClosed, 1: d3.curveLinear, 2: d3.curveStep};
|
||||||
|
const curve = curveMap[+terrs.attr("curve") || 0];
|
||||||
|
const lineGen = d3.line().curve(curve);
|
||||||
|
|
||||||
|
let currentLayer = 20;
|
||||||
|
const heights = cells.i.sort((a, b) => cells.h[a] - cells.h[b]);
|
||||||
|
for (const i of heights) {
|
||||||
|
const h = cells.h[i];
|
||||||
|
if (h > currentLayer) currentLayer += skip;
|
||||||
|
if (currentLayer > 100) break; // no layers possible with height > 100
|
||||||
|
if (h < currentLayer) continue;
|
||||||
|
if (used[i]) continue; // already marked
|
||||||
|
const onborder = cells.c[i].some(n => cells.h[n] < h);
|
||||||
|
if (!onborder) continue;
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h));
|
||||||
|
const chain = connectVertices(vertex, h);
|
||||||
|
if (chain.length < 3) continue;
|
||||||
|
const points = simplifyLine(chain).map(v => vertices.p[v]);
|
||||||
|
paths[h] += round(lineGen(points));
|
||||||
|
}
|
||||||
|
|
||||||
|
terrs
|
||||||
|
.append("rect")
|
||||||
|
.attr("x", 0)
|
||||||
|
.attr("y", 0)
|
||||||
|
.attr("width", graphWidth)
|
||||||
|
.attr("height", graphHeight)
|
||||||
|
.attr("fill", scheme(0.8)); // draw base layer
|
||||||
|
|
||||||
|
for (const i of d3.range(20, 101)) {
|
||||||
|
if (paths[i].length < 10) continue;
|
||||||
|
const color = getHeightColor(i, scheme);
|
||||||
|
|
||||||
|
if (terracing)
|
||||||
|
terrs
|
||||||
|
.append("path")
|
||||||
|
.attr("d", paths[i])
|
||||||
|
.attr("transform", "translate(.7,1.4)")
|
||||||
|
.attr("fill", d3.color(color).darker(terracing))
|
||||||
|
.attr("data-height", i);
|
||||||
|
terrs.append("path").attr("d", paths[i]).attr("fill", color).attr("data-height", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start, h) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = chain[chain.length - 1]; // previous vertex in chain
|
||||||
|
chain.push(current); // add current vertex to sequence
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
c.filter(c => cells.h[c] === h).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || cells.h[c[0]] < h;
|
||||||
|
const c1 = c[1] >= n || cells.h[c[1]] < h;
|
||||||
|
const c2 = c[2] >= n || cells.h[c[2]] < h;
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||||
|
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||||
|
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||||
|
if (current === chain[chain.length - 1]) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
function simplifyLine(chain) {
|
||||||
|
if (!simplification) return chain;
|
||||||
|
const n = simplification + 1; // filter each nth element
|
||||||
|
return chain.filter((d, i) => i % n === 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/layers/renderers/drawIce.js
Normal file
73
src/layers/renderers/drawIce.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import {getGridPolygon} from "/src/utils/graphUtils";
|
||||||
|
|
||||||
|
export function drawIce() {
|
||||||
|
const {cells, vertices} = grid;
|
||||||
|
const {temp, h} = cells;
|
||||||
|
const n = cells.i.length;
|
||||||
|
|
||||||
|
const used = new Uint8Array(cells.i.length);
|
||||||
|
Math.random = aleaPRNG(seed);
|
||||||
|
|
||||||
|
const shieldMin = -8; // max temp to form ice shield (glacier)
|
||||||
|
const icebergMax = 1; // max temp to form an iceberg
|
||||||
|
|
||||||
|
for (const i of grid.cells.i) {
|
||||||
|
const t = temp[i];
|
||||||
|
if (t > icebergMax) continue; // too warm: no ice
|
||||||
|
if (t > shieldMin && h[i] >= 20) continue; // non-glacier land: no ice
|
||||||
|
|
||||||
|
if (t <= shieldMin) {
|
||||||
|
// very cold: ice shield
|
||||||
|
if (used[i]) continue; // already rendered
|
||||||
|
const onborder = cells.c[i].some(n => temp[n] > shieldMin);
|
||||||
|
if (!onborder) continue; // need to start from onborder cell
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => temp[i] > shieldMin));
|
||||||
|
const chain = connectVertices(vertex);
|
||||||
|
if (chain.length < 3) continue;
|
||||||
|
const points = clipPoly(chain.map(v => vertices.p[v]));
|
||||||
|
ice.append("polygon").attr("points", points).attr("type", "iceShield");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mildly cold: iceberd
|
||||||
|
if (P(normalize(t, -7, 2.5))) continue; // t[-5; 2] cold: skip some cells
|
||||||
|
if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers
|
||||||
|
let size = (6.5 + t) / 10; // iceberg size: 0 = full size, 1 = zero size
|
||||||
|
if (cells.t[i] === -1) size *= 1.3; // coasline: smaller icebers
|
||||||
|
size = Math.min(size * (0.4 + rand() * 1.2), 0.95); // randomize iceberg size
|
||||||
|
resizePolygon(i, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizePolygon(i, s) {
|
||||||
|
const c = grid.points[i];
|
||||||
|
const points = getGridPolygon(i).map(p => [(p[0] + (c[0] - p[0]) * s) | 0, (p[1] + (c[1] - p[1]) * s) | 0]);
|
||||||
|
ice
|
||||||
|
.append("polygon")
|
||||||
|
.attr("points", points)
|
||||||
|
.attr("cell", i)
|
||||||
|
.attr("size", rn(1 - s, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = last(chain); // previous vertex in chain
|
||||||
|
chain.push(current); // add current vertex to sequence
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
c.filter(c => temp[c] <= shieldMin).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || temp[c[0]] > shieldMin;
|
||||||
|
const c1 = c[1] >= n || temp[c[1]] > shieldMin;
|
||||||
|
const c2 = c[2] >= n || temp[c[2]] > shieldMin;
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||||
|
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||||
|
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||||
|
if (current === chain[chain.length - 1]) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/layers/renderers/drawMarkers.js
Normal file
48
src/layers/renderers/drawMarkers.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {rn} from "/src/utils/numberUtils";
|
||||||
|
|
||||||
|
const pinShapeMap = {
|
||||||
|
bubble: (stroke, fill) =>
|
||||||
|
`<path d="M6,19 l9,10 L24,19" fill="${stroke}" stroke="none" /><circle cx="15" cy="15" r="10" fill="${fill}" stroke="${stroke}"/>`,
|
||||||
|
pin: (stroke, fill) =>
|
||||||
|
`<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}"/>`,
|
||||||
|
square: (stroke, fill) =>
|
||||||
|
`<path d="m 20,25 -5,4 -5,-4 z" fill="${fill}"/><path d="M 5,5 H 25 V 25 H 5 Z" fill="${fill}" stroke="${stroke}"/>`,
|
||||||
|
squarish: (stroke, fill) => `<path d="m 5,5 h 20 v 20 h -6 l -4,4 -4,-4 H 5 Z" fill="${fill}" stroke="${stroke}" />`,
|
||||||
|
diamond: (stroke, fill) => `<path d="M 2,15 15,1 28,15 15,29 Z" fill="${fill}" stroke="${stroke}" />`,
|
||||||
|
hex: (stroke, fill) => `<path d="M 15,29 4.61,21 V 9 L 15,3 25.4,9 v 12 z" fill="${fill}" stroke="${stroke}" />`,
|
||||||
|
hexy: (stroke, fill) => `<path d="M 15,29 6,21 5,8 15,4 25,8 24,21 Z" fill="${fill}" stroke="${stroke}" />`,
|
||||||
|
shieldy: (stroke, fill) =>
|
||||||
|
`<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}" />`,
|
||||||
|
shield: (stroke, fill) =>
|
||||||
|
`<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}" />`,
|
||||||
|
pentagon: (stroke, fill) => `<path d="M 4,16 9,4 h 12 l 5,12 -11,13 z" fill="${fill}" stroke="${stroke}" />`,
|
||||||
|
heptagon: (stroke, fill) =>
|
||||||
|
`<path d="M 15,29 6,22 4,12 10,4 h 10 l 6,8 -2,10 z" fill="${fill}" stroke="${stroke}" />`,
|
||||||
|
circle: (stroke, fill) => `<circle cx="15" cy="15" r="11" fill="${fill}" stroke="${stroke}" />`,
|
||||||
|
no: (stroke, fill) => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function drawMarker(marker, rescale = 1) {
|
||||||
|
const {i, icon, x, y, dx = 50, dy = 50, px = 12, size = 30, pin = "bubble", fill = "#fff", stroke = "#000"} = 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 = pinShapeMap[pin](fill, stroke);
|
||||||
|
|
||||||
|
return /* html */ `
|
||||||
|
<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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
41
src/layers/renderers/drawPopulation.js
Normal file
41
src/layers/renderers/drawPopulation.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
export function drawPopulation(event) {
|
||||||
|
population.selectAll("line").remove();
|
||||||
|
|
||||||
|
const {cells, burgs} = pack;
|
||||||
|
const p = {cells};
|
||||||
|
const show = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||||
|
|
||||||
|
const rural = Array.from(
|
||||||
|
cells.i.filter(i => cells.pop[i] > 0),
|
||||||
|
i => [p[i][0], p[i][1], p[i][1] - cells.pop[i] / 8]
|
||||||
|
);
|
||||||
|
|
||||||
|
population
|
||||||
|
.select("#rural")
|
||||||
|
.selectAll("line")
|
||||||
|
.data(rural)
|
||||||
|
.enter()
|
||||||
|
.append("line")
|
||||||
|
.attr("x1", d => d[0])
|
||||||
|
.attr("y1", d => d[1])
|
||||||
|
.attr("x2", d => d[0])
|
||||||
|
.attr("y2", d => d[1])
|
||||||
|
.transition(show)
|
||||||
|
.attr("y2", d => d[2]);
|
||||||
|
|
||||||
|
const urban = burgs.filter(b => b.i && !b.removed).map(b => [b.x, b.y, b.y - (b.population / 8) * urbanization]);
|
||||||
|
|
||||||
|
population
|
||||||
|
.select("#urban")
|
||||||
|
.selectAll("line")
|
||||||
|
.data(urban)
|
||||||
|
.enter()
|
||||||
|
.append("line")
|
||||||
|
.attr("x1", d => d[0])
|
||||||
|
.attr("y1", d => d[1])
|
||||||
|
.attr("x2", d => d[0])
|
||||||
|
.attr("y2", d => d[1])
|
||||||
|
.transition(show)
|
||||||
|
.delay(500)
|
||||||
|
.attr("y2", d => d[2]);
|
||||||
|
}
|
||||||
23
src/layers/renderers/drawPrecipitation.js
Normal file
23
src/layers/renderers/drawPrecipitation.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
export function drawPrecipitation() {
|
||||||
|
prec.selectAll("circle").remove();
|
||||||
|
const {cells, points} = grid;
|
||||||
|
|
||||||
|
prec.style("display", "block");
|
||||||
|
const show = d3.transition().duration(800).ease(d3.easeSinIn);
|
||||||
|
prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1);
|
||||||
|
|
||||||
|
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
|
||||||
|
const data = cells.i.filter(i => cells.h[i] >= 20 && cells.prec[i]);
|
||||||
|
const getRadius = prec => rn(Math.sqrt(prec / 4) / cellsNumberModifier, 2);
|
||||||
|
|
||||||
|
prec
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append("circle")
|
||||||
|
.attr("cx", d => points[d][0])
|
||||||
|
.attr("cy", d => points[d][1])
|
||||||
|
.attr("r", 0)
|
||||||
|
.transition(show)
|
||||||
|
.attr("r", d => getRadius(cells.prec[d]));
|
||||||
|
}
|
||||||
120
src/layers/renderers/drawProvinces.js
Normal file
120
src/layers/renderers/drawProvinces.js
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
export function drawProvinces() {
|
||||||
|
const labelsOn = provs.attr("data-labels") == 1;
|
||||||
|
provs.selectAll("*").remove();
|
||||||
|
|
||||||
|
const provinces = pack.provinces;
|
||||||
|
const {body, gap} = getProvincesVertices();
|
||||||
|
|
||||||
|
const g = provs.append("g").attr("id", "provincesBody");
|
||||||
|
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]);
|
||||||
|
g.selectAll("path")
|
||||||
|
.data(bodyData)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.attr("d", d => d[0])
|
||||||
|
.attr("fill", d => d[2])
|
||||||
|
.attr("stroke", "none")
|
||||||
|
.attr("id", d => "province" + d[1]);
|
||||||
|
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]);
|
||||||
|
g.selectAll(".path")
|
||||||
|
.data(gapData)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.attr("d", d => d[0])
|
||||||
|
.attr("fill", "none")
|
||||||
|
.attr("stroke", d => d[2])
|
||||||
|
.attr("id", d => "province-gap" + d[1]);
|
||||||
|
|
||||||
|
const labels = provs.append("g").attr("id", "provinceLabels");
|
||||||
|
labels.style("display", `${labelsOn ? "block" : "none"}`);
|
||||||
|
const labelData = provinces.filter(p => p.i && !p.removed && p.pole);
|
||||||
|
labels
|
||||||
|
.selectAll(".path")
|
||||||
|
.data(labelData)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("x", d => d.pole[0])
|
||||||
|
.attr("y", d => d.pole[1])
|
||||||
|
.attr("id", d => "provinceLabel" + d.i)
|
||||||
|
.text(d => d.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProvincesVertices() {
|
||||||
|
const cells = pack.cells,
|
||||||
|
vertices = pack.vertices,
|
||||||
|
provinces = pack.provinces,
|
||||||
|
n = cells.i.length;
|
||||||
|
const used = new Uint8Array(cells.i.length);
|
||||||
|
const vArray = new Array(provinces.length); // store vertices array
|
||||||
|
const body = new Array(provinces.length).fill(""); // store path around each province
|
||||||
|
const gap = new Array(provinces.length).fill(""); // store path along water for each province to fill the gaps
|
||||||
|
|
||||||
|
for (const i of cells.i) {
|
||||||
|
if (!cells.province[i] || used[i]) continue;
|
||||||
|
const p = cells.province[i];
|
||||||
|
const onborder = cells.c[i].some(n => cells.province[n] !== p);
|
||||||
|
if (!onborder) continue;
|
||||||
|
|
||||||
|
const borderWith = cells.c[i].map(c => cells.province[c]).find(n => n !== p);
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.province[i] === borderWith));
|
||||||
|
const chain = connectVertices(vertex, p, borderWith);
|
||||||
|
if (chain.length < 3) continue;
|
||||||
|
const points = chain.map(v => vertices.p[v[0]]);
|
||||||
|
if (!vArray[p]) vArray[p] = [];
|
||||||
|
vArray[p].push(points);
|
||||||
|
body[p] += "M" + points.join("L");
|
||||||
|
gap[p] +=
|
||||||
|
"M" +
|
||||||
|
vertices.p[chain[0][0]] +
|
||||||
|
chain.reduce(
|
||||||
|
(r, v, i, d) =>
|
||||||
|
!i ? r : !v[2] ? r + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + "M" + vertices.p[v[0]] : r,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find province visual center
|
||||||
|
vArray.forEach((ar, i) => {
|
||||||
|
const sorted = ar.sort((a, b) => b.length - a.length); // sort by points number
|
||||||
|
provinces[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility
|
||||||
|
});
|
||||||
|
|
||||||
|
return {body, gap};
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start, t, province) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.province[c] !== t);
|
||||||
|
function check(i) {
|
||||||
|
province = cells.province[i];
|
||||||
|
land = cells.h[i] >= 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||||
|
chain.push([current, province, land]); // add current vertex to sequence
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
c.filter(c => cells.province[c] === t).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || cells.province[c[0]] !== t;
|
||||||
|
const c1 = c[1] >= n || cells.province[c[1]] !== t;
|
||||||
|
const c2 = c[2] >= n || cells.province[c[2]] !== t;
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
if (v[0] !== prev && c0 !== c1) {
|
||||||
|
current = v[0];
|
||||||
|
check(c0 ? c[0] : c[1]);
|
||||||
|
} else if (v[1] !== prev && c1 !== c2) {
|
||||||
|
current = v[1];
|
||||||
|
check(c1 ? c[1] : c[2]);
|
||||||
|
} else if (v[2] !== prev && c0 !== c2) {
|
||||||
|
current = v[2];
|
||||||
|
check(c2 ? c[2] : c[0]);
|
||||||
|
}
|
||||||
|
if (current === chain[chain.length - 1][0]) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain.push([start, province, land]); // add starting vertex to sequence to close the path
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/layers/renderers/drawReligions.js
Normal file
93
src/layers/renderers/drawReligions.js
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
export function drawReligions() {
|
||||||
|
relig.selectAll("path").remove();
|
||||||
|
const {cells, vertices, religions} = pack;
|
||||||
|
const n = cells.i.length;
|
||||||
|
|
||||||
|
const used = new Uint8Array(cells.i.length);
|
||||||
|
const vArray = new Array(religions.length); // store vertices array
|
||||||
|
const body = new Array(religions.length).fill(""); // store path around each religion
|
||||||
|
const gap = new Array(religions.length).fill(""); // store path along water for each religion to fill the gaps
|
||||||
|
|
||||||
|
for (const i of cells.i) {
|
||||||
|
if (!cells.religion[i]) continue;
|
||||||
|
if (used[i]) continue;
|
||||||
|
used[i] = 1;
|
||||||
|
const r = cells.religion[i];
|
||||||
|
const onborder = cells.c[i].filter(n => cells.religion[n] !== r);
|
||||||
|
if (!onborder.length) continue;
|
||||||
|
const borderWith = cells.c[i].map(c => cells.religion[c]).find(n => n !== r);
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.religion[i] === borderWith));
|
||||||
|
const chain = connectVertices(vertex, r, borderWith);
|
||||||
|
if (chain.length < 3) continue;
|
||||||
|
const points = chain.map(v => vertices.p[v[0]]);
|
||||||
|
if (!vArray[r]) vArray[r] = [];
|
||||||
|
vArray[r].push(points);
|
||||||
|
body[r] += "M" + points.join("L") + "Z";
|
||||||
|
gap[r] +=
|
||||||
|
"M" +
|
||||||
|
vertices.p[chain[0][0]] +
|
||||||
|
chain.reduce(
|
||||||
|
(r2, v, i, d) =>
|
||||||
|
!i ? r2 : !v[2] ? r2 + "L" + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r2 + "M" + vertices.p[v[0]] : r2,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
||||||
|
relig
|
||||||
|
.selectAll("path")
|
||||||
|
.data(bodyData)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.attr("d", d => d[0])
|
||||||
|
.attr("fill", d => d[2])
|
||||||
|
.attr("id", d => "religion" + d[1]);
|
||||||
|
|
||||||
|
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, religions[i].color]).filter(d => d[0]);
|
||||||
|
relig
|
||||||
|
.selectAll(".path")
|
||||||
|
.data(gapData)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.attr("d", d => d[0])
|
||||||
|
.attr("fill", "none")
|
||||||
|
.attr("stroke", d => d[2])
|
||||||
|
.attr("id", d => "religion-gap" + d[1])
|
||||||
|
.attr("stroke-width", "10px");
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start, t, religion) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
let land = vertices.c[start].some(c => cells.h[c] >= 20 && cells.religion[c] !== t);
|
||||||
|
function check(i) {
|
||||||
|
religion = cells.religion[i];
|
||||||
|
land = cells.h[i] >= 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||||
|
chain.push([current, religion, land]); // add current vertex to sequence
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
c.filter(c => cells.religion[c] === t).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || cells.religion[c[0]] !== t;
|
||||||
|
const c1 = c[1] >= n || cells.religion[c[1]] !== t;
|
||||||
|
const c2 = c[2] >= n || cells.religion[c[2]] !== t;
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
if (v[0] !== prev && c0 !== c1) {
|
||||||
|
current = v[0];
|
||||||
|
check(c0 ? c[0] : c[1]);
|
||||||
|
} else if (v[1] !== prev && c1 !== c2) {
|
||||||
|
current = v[1];
|
||||||
|
check(c1 ? c[1] : c[2]);
|
||||||
|
} else if (v[2] !== prev && c0 !== c2) {
|
||||||
|
current = v[2];
|
||||||
|
check(c2 ? c[2] : c[0]);
|
||||||
|
}
|
||||||
|
if (current === chain[chain.length - 1][0]) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/layers/renderers/drawRivers.js
Normal file
21
src/layers/renderers/drawRivers.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
export function drawRivers() {
|
||||||
|
rivers.selectAll("*").remove();
|
||||||
|
|
||||||
|
const {addMeandering, getRiverPath} = Rivers;
|
||||||
|
|
||||||
|
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
|
||||||
|
if (!cells || cells.length < 2) return;
|
||||||
|
|
||||||
|
if (points && points.length !== cells.length) {
|
||||||
|
const error = `River ${i} has ${cells.length} cells, but only ${points.length} points defined. Resetting points data`;
|
||||||
|
console.error(error);
|
||||||
|
points = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meanderedPoints = addMeandering(cells, points);
|
||||||
|
const path = getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||||
|
return `<path id="river${i}" d="${path}"/>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
rivers.html(riverPaths.join(""));
|
||||||
|
}
|
||||||
145
src/layers/renderers/drawStates.js
Normal file
145
src/layers/renderers/drawStates.js
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
export function drawStates() {
|
||||||
|
regions.selectAll("path").remove();
|
||||||
|
|
||||||
|
const {cells, vertices, features} = pack;
|
||||||
|
const states = pack.states;
|
||||||
|
const n = cells.i.length;
|
||||||
|
|
||||||
|
const used = new Uint8Array(cells.i.length);
|
||||||
|
const vArray = new Array(states.length); // store vertices array
|
||||||
|
const body = new Array(states.length).fill(""); // path around each state
|
||||||
|
const gap = new Array(states.length).fill(""); // path along water for each state to fill the gaps
|
||||||
|
const halo = new Array(states.length).fill(""); // path around states, but not lakes
|
||||||
|
|
||||||
|
const getStringPoint = v => vertices.p[v[0]].join(",");
|
||||||
|
|
||||||
|
// define inner-state lakes to omit on border render
|
||||||
|
const innerLakes = features.map(feature => {
|
||||||
|
if (feature.type !== "lake") return false;
|
||||||
|
if (!feature.shoreline) Lakes.getShoreline(feature);
|
||||||
|
|
||||||
|
const states = feature.shoreline.map(i => cells.state[i]);
|
||||||
|
return new Set(states).size > 1 ? false : true;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const i of cells.i) {
|
||||||
|
if (!cells.state[i] || used[i]) continue;
|
||||||
|
const state = cells.state[i];
|
||||||
|
|
||||||
|
const onborder = cells.c[i].some(n => cells.state[n] !== state);
|
||||||
|
if (!onborder) continue;
|
||||||
|
|
||||||
|
const borderWith = cells.c[i].map(c => cells.state[c]).find(n => n !== state);
|
||||||
|
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.state[i] === borderWith));
|
||||||
|
const chain = connectVertices(vertex, state);
|
||||||
|
|
||||||
|
const noInnerLakes = chain.filter(v => v[1] !== "innerLake");
|
||||||
|
if (noInnerLakes.length < 3) continue;
|
||||||
|
|
||||||
|
// get path around the state
|
||||||
|
if (!vArray[state]) vArray[state] = [];
|
||||||
|
const points = noInnerLakes.map(v => vertices.p[v[0]]);
|
||||||
|
vArray[state].push(points);
|
||||||
|
body[state] += "M" + points.join("L");
|
||||||
|
|
||||||
|
// connect path for halo
|
||||||
|
let discontinued = true;
|
||||||
|
halo[state] += noInnerLakes
|
||||||
|
.map(v => {
|
||||||
|
if (v[1] === "border") {
|
||||||
|
discontinued = true;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const operation = discontinued ? "M" : "L";
|
||||||
|
discontinued = false;
|
||||||
|
return `${operation}${getStringPoint(v)}`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
// connect gaps between state and water into a single path
|
||||||
|
discontinued = true;
|
||||||
|
gap[state] += chain
|
||||||
|
.map(v => {
|
||||||
|
if (v[1] === "land") {
|
||||||
|
discontinued = true;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const operation = discontinued ? "M" : "L";
|
||||||
|
discontinued = false;
|
||||||
|
return `${operation}${getStringPoint(v)}`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// find state visual center
|
||||||
|
vArray.forEach((ar, i) => {
|
||||||
|
const sorted = ar.sort((a, b) => b.length - a.length); // sort by points number
|
||||||
|
states[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility
|
||||||
|
});
|
||||||
|
|
||||||
|
const bodyData = body.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
|
||||||
|
const gapData = gap.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
|
||||||
|
const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]);
|
||||||
|
|
||||||
|
const bodyString = bodyData.map(d => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join("");
|
||||||
|
const gapString = gapData.map(d => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join("");
|
||||||
|
const clipString = bodyData
|
||||||
|
.map(d => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`)
|
||||||
|
.join("");
|
||||||
|
const haloString = haloData
|
||||||
|
.map(
|
||||||
|
d =>
|
||||||
|
`<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${
|
||||||
|
d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"
|
||||||
|
}"/>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
statesBody.html(bodyString + gapString);
|
||||||
|
defs.select("#statePaths").html(clipString);
|
||||||
|
statesHalo.html(haloString);
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start, state) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
const getType = c => {
|
||||||
|
const borderCell = c.find(i => cells.b[i]);
|
||||||
|
if (borderCell) return "border";
|
||||||
|
|
||||||
|
const waterCell = c.find(i => cells.h[i] < 20);
|
||||||
|
if (!waterCell) return "land";
|
||||||
|
if (innerLakes[cells.f[waterCell]]) return "innerLake";
|
||||||
|
return features[cells.f[waterCell]].type;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = chain.length ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||||
|
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
chain.push([current, getType(c)]); // add current vertex to sequence
|
||||||
|
|
||||||
|
c.filter(c => cells.state[c] === state).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || cells.state[c[0]] !== state;
|
||||||
|
const c1 = c[1] >= n || cells.state[c[1]] !== state;
|
||||||
|
const c2 = c[2] >= n || cells.state[c[2]] !== state;
|
||||||
|
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
|
||||||
|
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||||
|
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||||
|
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||||
|
|
||||||
|
if (current === prev) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chain.length) chain.push(chain[0]);
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
Zoom.invoke();
|
||||||
|
}
|
||||||
116
src/layers/renderers/drawTemperature.js
Normal file
116
src/layers/renderers/drawTemperature.js
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import {convertTemperature} from "/src/utils/unitUtils";
|
||||||
|
|
||||||
|
export function drawTemperature() {
|
||||||
|
temperature.selectAll("*").remove();
|
||||||
|
|
||||||
|
const lineGen = d3.line().curve(d3.curveBasisClosed);
|
||||||
|
const scheme = d3.scaleSequential(d3.interpolateSpectral);
|
||||||
|
const tMax = +temperatureEquatorOutput.max,
|
||||||
|
tMin = +temperatureEquatorOutput.min,
|
||||||
|
delta = tMax - tMin;
|
||||||
|
|
||||||
|
const cells = grid.cells,
|
||||||
|
vertices = grid.vertices,
|
||||||
|
n = cells.i.length;
|
||||||
|
const used = new Uint8Array(n); // to detect already passed cells
|
||||||
|
const min = d3.min(cells.temp),
|
||||||
|
max = d3.max(cells.temp);
|
||||||
|
const step = Math.max(Math.round(Math.abs(min - max) / 5), 1);
|
||||||
|
const isolines = d3.range(min + step, max, step);
|
||||||
|
const chains = [],
|
||||||
|
labels = []; // store label coordinates
|
||||||
|
|
||||||
|
for (const i of cells.i) {
|
||||||
|
const t = cells.temp[i];
|
||||||
|
if (used[i] || !isolines.includes(t)) continue;
|
||||||
|
const start = findStart(i, t);
|
||||||
|
if (!start) continue;
|
||||||
|
used[i] = 1;
|
||||||
|
//debug.append("circle").attr("r", 3).attr("cx", vertices.p[start][0]).attr("cy", vertices.p[start][1]).attr("fill", "red").attr("stroke", "black").attr("stroke-width", .3);
|
||||||
|
|
||||||
|
const chain = connectVertices(start, t); // vertices chain to form a path
|
||||||
|
const relaxed = chain.filter((v, i) => i % 4 === 0 || vertices.c[v].some(c => c >= n));
|
||||||
|
if (relaxed.length < 6) continue;
|
||||||
|
const points = relaxed.map(v => vertices.p[v]);
|
||||||
|
chains.push([t, points]);
|
||||||
|
addLabel(points, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// min temp isoline covers all graph
|
||||||
|
temperature
|
||||||
|
.append("path")
|
||||||
|
.attr("d", `M0,0 h${graphWidth} v${graphHeight} h${-graphWidth} Z`)
|
||||||
|
.attr("fill", scheme(1 - (min - tMin) / delta))
|
||||||
|
.attr("stroke", "none");
|
||||||
|
|
||||||
|
for (const t of isolines) {
|
||||||
|
const path = chains
|
||||||
|
.filter(c => c[0] === t)
|
||||||
|
.map(c => round(lineGen(c[1])))
|
||||||
|
.join("");
|
||||||
|
if (!path) continue;
|
||||||
|
const fill = scheme(1 - (t - tMin) / delta),
|
||||||
|
stroke = d3.color(fill).darker(0.2);
|
||||||
|
temperature.append("path").attr("d", path).attr("fill", fill).attr("stroke", stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempLabels = temperature.append("g").attr("id", "tempLabels").attr("fill-opacity", 1);
|
||||||
|
tempLabels
|
||||||
|
.selectAll("text")
|
||||||
|
.data(labels)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("x", d => d[0])
|
||||||
|
.attr("y", d => d[1])
|
||||||
|
.text(d => convertTemperature(d[2]));
|
||||||
|
|
||||||
|
// find cell with temp < isotherm and find vertex to start path detection
|
||||||
|
function findStart(i, t) {
|
||||||
|
if (cells.b[i]) return cells.v[i].find(v => vertices.c[v].some(c => c >= n)); // map border cell
|
||||||
|
return cells.v[i][cells.c[i].findIndex(c => cells.temp[c] < t || !cells.temp[c])];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLabel(points, t) {
|
||||||
|
const c = svgWidth / 2; // map center x coordinate
|
||||||
|
// add label on isoline top center
|
||||||
|
const tc = points[d3.scan(points, (a, b) => a[1] - b[1] + (Math.abs(a[0] - c) - Math.abs(b[0] - c)) / 2)];
|
||||||
|
pushLabel(tc[0], tc[1], t);
|
||||||
|
|
||||||
|
// add label on isoline bottom center
|
||||||
|
if (points.length > 20) {
|
||||||
|
const bc = points[d3.scan(points, (a, b) => b[1] - a[1] + (Math.abs(a[0] - c) - Math.abs(b[0] - c)) / 2)];
|
||||||
|
const dist2 = (tc[1] - bc[1]) ** 2 + (tc[0] - bc[0]) ** 2; // square distance between this and top point
|
||||||
|
if (dist2 > 100) pushLabel(bc[0], bc[1], t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushLabel(x, y, t) {
|
||||||
|
if (x < 20 || x > svgWidth - 20) return;
|
||||||
|
if (y < 20 || y > svgHeight - 20) return;
|
||||||
|
labels.push([x, y, t]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect vertices to chain
|
||||||
|
function connectVertices(start, t) {
|
||||||
|
const chain = []; // vertices chain to form a path
|
||||||
|
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||||
|
const prev = chain[chain.length - 1]; // previous vertex in chain
|
||||||
|
chain.push(current); // add current vertex to sequence
|
||||||
|
const c = vertices.c[current]; // cells adjacent to vertex
|
||||||
|
c.filter(c => cells.temp[c] === t).forEach(c => (used[c] = 1));
|
||||||
|
const c0 = c[0] >= n || cells.temp[c[0]] < t;
|
||||||
|
const c1 = c[1] >= n || cells.temp[c[1]] < t;
|
||||||
|
const c2 = c[2] >= n || cells.temp[c[2]] < t;
|
||||||
|
const v = vertices.v[current]; // neighboring vertices
|
||||||
|
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||||
|
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||||
|
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||||
|
if (current === chain[chain.length - 1]) {
|
||||||
|
ERROR && console.error("Next vertex is not found");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain.push(start);
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {TIME} from "/src/config/logging";
|
||||||
|
import {drawBiomes} from "./drawBiomes";
|
||||||
|
import {drawBorders} from "./drawBorders";
|
||||||
|
import {drawCells} from "./drawCells";
|
||||||
|
import {drawCoordinates} from "./drawCoordinates";
|
||||||
|
import {drawCultures} from "./drawCultures";
|
||||||
|
import {drawEmblems} from "./drawEmblems";
|
||||||
|
import {drawGrid} from "./drawGrid";
|
||||||
|
import {drawHeightmap} from "./drawHeightmap";
|
||||||
|
import {drawIce} from "./drawIce";
|
||||||
|
import {drawMarkers} from "./drawMarkers";
|
||||||
|
import {drawPopulation} from "./drawPopulation";
|
||||||
|
import {drawPrecipitation} from "./drawPrecipitation";
|
||||||
|
import {drawProvinces} from "./drawProvinces";
|
||||||
|
import {drawReligions} from "./drawReligions";
|
||||||
|
import {drawRivers} from "./drawRivers";
|
||||||
|
import {drawStates} from "./drawStates";
|
||||||
|
import {drawTemperature} from "./drawTemperature";
|
||||||
|
|
||||||
|
// Note: missed renderers are in toggle functions
|
||||||
|
const layerRenderersMap = {
|
||||||
|
biomes: drawBiomes,
|
||||||
|
borders: drawBorders,
|
||||||
|
cells: drawCells,
|
||||||
|
coordinates: drawCoordinates,
|
||||||
|
cultures: drawCultures,
|
||||||
|
emblems: drawEmblems,
|
||||||
|
grid: drawGrid,
|
||||||
|
heightmap: drawHeightmap,
|
||||||
|
ice: drawIce,
|
||||||
|
markers: drawMarkers,
|
||||||
|
population: drawPopulation,
|
||||||
|
precipitation: drawPrecipitation,
|
||||||
|
provinces: drawProvinces,
|
||||||
|
religions: drawReligions,
|
||||||
|
rivers: drawRivers,
|
||||||
|
states: drawStates,
|
||||||
|
temperature: drawTemperature
|
||||||
|
};
|
||||||
|
|
||||||
|
export function renderLayer(layerName) {
|
||||||
|
const rendered = layerRenderersMap[layerName];
|
||||||
|
TIME && console.time(rendered.name);
|
||||||
|
rendered();
|
||||||
|
TIME && console.timeEnd(rendered.name);
|
||||||
|
}
|
||||||
494
src/layers/toggles.js
Normal file
494
src/layers/toggles.js
Normal file
|
|
@ -0,0 +1,494 @@
|
||||||
|
import {tip} from "/src/scripts/tooltips";
|
||||||
|
import {getBase64} from "/src/utils/functionUtils";
|
||||||
|
import {isCtrlClick} from "/src/utils/keyboardUtils";
|
||||||
|
import {turnLayerButtonOn, turnLayerButtonOff, layerIsOn} from "./utils";
|
||||||
|
import {renderLayer} from "./renderers";
|
||||||
|
|
||||||
|
const layerTogglesMap = {
|
||||||
|
toggleBiomes,
|
||||||
|
toggleBorders,
|
||||||
|
toggleCells,
|
||||||
|
toggleCompass,
|
||||||
|
toggleCoordinates,
|
||||||
|
toggleCultures,
|
||||||
|
toggleEmblems,
|
||||||
|
toggleGrid,
|
||||||
|
toggleHeight,
|
||||||
|
toggleIce,
|
||||||
|
toggleIcons,
|
||||||
|
toggleLabels,
|
||||||
|
toggleMarkers,
|
||||||
|
toggleMilitary,
|
||||||
|
togglePopulation,
|
||||||
|
togglePrec,
|
||||||
|
toggleProvinces,
|
||||||
|
toggleRelief,
|
||||||
|
toggleReligions,
|
||||||
|
toggleRivers,
|
||||||
|
toggleRoutes,
|
||||||
|
toggleRulers,
|
||||||
|
toggleScaleBar,
|
||||||
|
toggleStates,
|
||||||
|
toggleTemp,
|
||||||
|
toggleTexture,
|
||||||
|
toggleZones
|
||||||
|
};
|
||||||
|
|
||||||
|
export function toggleLayer(toggleId) {
|
||||||
|
layerTogglesMap[toggleId]();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleHeight(event) {
|
||||||
|
if (customization === 1) {
|
||||||
|
tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!terrs.selectAll("*").size()) {
|
||||||
|
turnLayerButtonOn("toggleHeight");
|
||||||
|
renderLayer("heightmap");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("terrs");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("terrs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleHeight");
|
||||||
|
terrs.selectAll("*").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTemp(event) {
|
||||||
|
if (!temperature.selectAll("*").size()) {
|
||||||
|
turnLayerButtonOn("toggleTemp");
|
||||||
|
renderLayer("temperature");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("temperature");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("temperature");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleTemp");
|
||||||
|
temperature.selectAll("*").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBiomes(event) {
|
||||||
|
if (!biomes.selectAll("path").size()) {
|
||||||
|
turnLayerButtonOn("toggleBiomes");
|
||||||
|
renderLayer("biomes");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("biomes");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("biomes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
biomes.selectAll("path").remove();
|
||||||
|
turnLayerButtonOff("toggleBiomes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePrec(event) {
|
||||||
|
if (!prec.selectAll("circle").size()) {
|
||||||
|
turnLayerButtonOn("togglePrec");
|
||||||
|
renderLayer("precipitation");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("prec");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("prec");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("togglePrec");
|
||||||
|
const hide = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||||
|
prec.selectAll("text").attr("opacity", 1).transition(hide).attr("opacity", 0);
|
||||||
|
prec.selectAll("circle").transition(hide).attr("r", 0).remove();
|
||||||
|
prec.transition().delay(1000).style("display", "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePopulation(event) {
|
||||||
|
if (!population.selectAll("line").size()) {
|
||||||
|
turnLayerButtonOn("togglePopulation");
|
||||||
|
renderLayer("population");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("population");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("population");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("togglePopulation");
|
||||||
|
const isD3data = population.select("line").datum();
|
||||||
|
if (!isD3data) {
|
||||||
|
// just remove
|
||||||
|
population.selectAll("line").remove();
|
||||||
|
} else {
|
||||||
|
// remove with animation
|
||||||
|
const hide = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||||
|
population
|
||||||
|
.select("#rural")
|
||||||
|
.selectAll("line")
|
||||||
|
.transition(hide)
|
||||||
|
.attr("y2", d => d[1])
|
||||||
|
.remove();
|
||||||
|
population
|
||||||
|
.select("#urban")
|
||||||
|
.selectAll("line")
|
||||||
|
.transition(hide)
|
||||||
|
.delay(1000)
|
||||||
|
.attr("y2", d => d[1])
|
||||||
|
.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCells(event) {
|
||||||
|
if (!cells.selectAll("path").size()) {
|
||||||
|
turnLayerButtonOn("toggleCells");
|
||||||
|
renderLayer("cells");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("cells");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("cells");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cells.selectAll("path").remove();
|
||||||
|
turnLayerButtonOff("toggleCells");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleIce(event) {
|
||||||
|
if (!layerIsOn("toggleIce")) {
|
||||||
|
turnLayerButtonOn("toggleIce");
|
||||||
|
$("#ice").fadeIn();
|
||||||
|
if (!ice.selectAll("*").size()) renderLayer("ice");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("ice");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("ice");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#ice").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleIce");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCultures(event) {
|
||||||
|
const cultures = pack.cultures.filter(c => c.i && !c.removed);
|
||||||
|
const empty = !cults.selectAll("path").size();
|
||||||
|
if (empty && cultures.length) {
|
||||||
|
turnLayerButtonOn("toggleCultures");
|
||||||
|
renderLayer("cultures");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("cults");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("cults");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cults.selectAll("path").remove();
|
||||||
|
turnLayerButtonOff("toggleCultures");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleReligions(event) {
|
||||||
|
const religions = pack.religions.filter(r => r.i && !r.removed);
|
||||||
|
if (!relig.selectAll("path").size() && religions.length) {
|
||||||
|
turnLayerButtonOn("toggleReligions");
|
||||||
|
renderLayer("religions");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("relig");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("relig");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
relig.selectAll("path").remove();
|
||||||
|
turnLayerButtonOff("toggleReligions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleStates(event) {
|
||||||
|
if (!layerIsOn("toggleStates")) {
|
||||||
|
turnLayerButtonOn("toggleStates");
|
||||||
|
regions.style("display", null);
|
||||||
|
renderLayer("states");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("regions");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("regions");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
regions.style("display", "none").selectAll("path").remove();
|
||||||
|
turnLayerButtonOff("toggleStates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBorders(event) {
|
||||||
|
if (!layerIsOn("toggleBorders")) {
|
||||||
|
turnLayerButtonOn("toggleBorders");
|
||||||
|
renderLayer("borders");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("borders");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("borders");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleBorders");
|
||||||
|
borders.selectAll("path").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleProvinces(event) {
|
||||||
|
if (!layerIsOn("toggleProvinces")) {
|
||||||
|
turnLayerButtonOn("toggleProvinces");
|
||||||
|
renderLayer("provinces");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("provs");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("provs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
provs.selectAll("*").remove();
|
||||||
|
turnLayerButtonOff("toggleProvinces");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleGrid(event) {
|
||||||
|
if (!gridOverlay.selectAll("*").size()) {
|
||||||
|
turnLayerButtonOn("toggleGrid");
|
||||||
|
renderLayer("grid");
|
||||||
|
calculateFriendlyGridSize();
|
||||||
|
|
||||||
|
if (event && isCtrlClick(event)) editStyle("gridOverlay");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("gridOverlay");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleGrid");
|
||||||
|
gridOverlay.selectAll("*").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCoordinates(event) {
|
||||||
|
if (!coordinates.selectAll("*").size()) {
|
||||||
|
turnLayerButtonOn("toggleCoordinates");
|
||||||
|
renderLayer("coordinates");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("coordinates");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("coordinates");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleCoordinates");
|
||||||
|
coordinates.selectAll("*").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCompass(event) {
|
||||||
|
if (!layerIsOn("toggleCompass")) {
|
||||||
|
turnLayerButtonOn("toggleCompass");
|
||||||
|
$("#compass").fadeIn();
|
||||||
|
if (!compass.selectAll("*").size()) {
|
||||||
|
compass.append("use").attr("xlink:href", "#rose");
|
||||||
|
shiftCompass();
|
||||||
|
}
|
||||||
|
if (event && isCtrlClick(event)) editStyle("compass");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("compass");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#compass").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleCompass");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRelief(event) {
|
||||||
|
if (!layerIsOn("toggleRelief")) {
|
||||||
|
turnLayerButtonOn("toggleRelief");
|
||||||
|
if (!terrain.selectAll("*").size()) ReliefIcons();
|
||||||
|
$("#terrain").fadeIn();
|
||||||
|
if (event && isCtrlClick(event)) editStyle("terrain");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("terrain");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#terrain").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleRelief");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTexture(event) {
|
||||||
|
if (!layerIsOn("toggleTexture")) {
|
||||||
|
turnLayerButtonOn("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;
|
||||||
|
const y = +styleTextureShiftY.value;
|
||||||
|
const image = texture
|
||||||
|
.append("image")
|
||||||
|
.attr("id", "textureImage")
|
||||||
|
.attr("x", x)
|
||||||
|
.attr("y", y)
|
||||||
|
.attr("width", graphWidth - x)
|
||||||
|
.attr("height", graphHeight - y)
|
||||||
|
.attr("preserveAspectRatio", "xMidYMid slice");
|
||||||
|
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)) return editStyle("texture");
|
||||||
|
$("#texture").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleTexture");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRivers(event) {
|
||||||
|
if (!layerIsOn("toggleRivers")) {
|
||||||
|
turnLayerButtonOn("toggleRivers");
|
||||||
|
renderLayer("rivers");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("rivers");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) return editStyle("rivers");
|
||||||
|
rivers.selectAll("*").remove();
|
||||||
|
turnLayerButtonOff("toggleRivers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRoutes(event) {
|
||||||
|
if (!layerIsOn("toggleRoutes")) {
|
||||||
|
turnLayerButtonOn("toggleRoutes");
|
||||||
|
$("#routes").fadeIn();
|
||||||
|
if (event && isCtrlClick(event)) editStyle("routes");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("routes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#routes").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleRoutes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMilitary(event) {
|
||||||
|
if (!layerIsOn("toggleMilitary")) {
|
||||||
|
turnLayerButtonOn("toggleMilitary");
|
||||||
|
$("#armies").fadeIn();
|
||||||
|
if (event && isCtrlClick(event)) editStyle("armies");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("armies");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#armies").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleMilitary");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMarkers(event) {
|
||||||
|
if (!layerIsOn("toggleMarkers")) {
|
||||||
|
turnLayerButtonOn("toggleMarkers");
|
||||||
|
renderLayer("markers");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("markers");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) return editStyle("markers");
|
||||||
|
markers.selectAll("*").remove();
|
||||||
|
turnLayerButtonOff("toggleMarkers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLabels(event) {
|
||||||
|
if (!layerIsOn("toggleLabels")) {
|
||||||
|
turnLayerButtonOn("toggleLabels");
|
||||||
|
labels.style("display", null);
|
||||||
|
Zoom.invoke();
|
||||||
|
if (event && isCtrlClick(event)) editStyle("labels");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("labels");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleLabels");
|
||||||
|
labels.style("display", "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleIcons(event) {
|
||||||
|
if (!layerIsOn("toggleIcons")) {
|
||||||
|
turnLayerButtonOn("toggleIcons");
|
||||||
|
$("#icons").fadeIn();
|
||||||
|
if (event && isCtrlClick(event)) editStyle("burgIcons");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("burgIcons");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleIcons");
|
||||||
|
$("#icons").fadeOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRulers(event) {
|
||||||
|
if (!layerIsOn("toggleRulers")) {
|
||||||
|
turnLayerButtonOn("toggleRulers");
|
||||||
|
if (event && isCtrlClick(event)) editStyle("ruler");
|
||||||
|
rulers.draw();
|
||||||
|
ruler.style("display", null);
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("ruler");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleRulers");
|
||||||
|
ruler.selectAll("*").remove();
|
||||||
|
ruler.style("display", "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleScaleBar(event) {
|
||||||
|
if (!layerIsOn("toggleScaleBar")) {
|
||||||
|
turnLayerButtonOn("toggleScaleBar");
|
||||||
|
$("#scaleBar").fadeIn();
|
||||||
|
if (event && isCtrlClick(event)) editUnits();
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editUnits();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#scaleBar").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleScaleBar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleZones(event) {
|
||||||
|
if (!layerIsOn("toggleZones")) {
|
||||||
|
turnLayerButtonOn("toggleZones");
|
||||||
|
$("#zones").fadeIn();
|
||||||
|
if (event && isCtrlClick(event)) editStyle("zones");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("zones");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turnLayerButtonOff("toggleZones");
|
||||||
|
$("#zones").fadeOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEmblems(event) {
|
||||||
|
if (!layerIsOn("toggleEmblems")) {
|
||||||
|
turnLayerButtonOn("toggleEmblems");
|
||||||
|
if (!emblems.selectAll("use").size()) renderLayer("emblems");
|
||||||
|
$("#emblems").fadeIn();
|
||||||
|
if (event && isCtrlClick(event)) editStyle("emblems");
|
||||||
|
} else {
|
||||||
|
if (event && isCtrlClick(event)) {
|
||||||
|
editStyle("emblems");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#emblems").fadeOut();
|
||||||
|
turnLayerButtonOff("toggleEmblems");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/layers/utils.ts
Normal file
17
src/layers/utils.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {byId} from "/src/utils/shorthands";
|
||||||
|
import {updatePresetInput} from "./init";
|
||||||
|
|
||||||
|
export function layerIsOn(toggleId: string) {
|
||||||
|
const buttonoff = byId(toggleId)?.classList.contains("buttonoff");
|
||||||
|
return !buttonoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function turnLayerButtonOn(toggleId: string) {
|
||||||
|
byId(toggleId)?.classList.remove("buttonoff");
|
||||||
|
updatePresetInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function turnLayerButtonOff(toggleId: string) {
|
||||||
|
byId(toggleId)?.classList.add("buttonoff");
|
||||||
|
updatePresetInput();
|
||||||
|
}
|
||||||
11
src/main.js
11
src/main.js
|
|
@ -6,7 +6,7 @@ import {ERROR, INFO, TIME, WARN} from "./config/logging";
|
||||||
import {UINT16_MAX} from "./constants";
|
import {UINT16_MAX} from "./constants";
|
||||||
import {clearLegend} from "./modules/legend";
|
import {clearLegend} from "./modules/legend";
|
||||||
import {drawScaleBar, Ruler, Rulers} from "./modules/measurers";
|
import {drawScaleBar, Ruler, Rulers} from "./modules/measurers";
|
||||||
import {applyPreset, drawBorders, drawRivers, drawStates} from "./modules/ui/layers";
|
import {initLayers, restoreLayers, renderLayer} from "./layers";
|
||||||
import {applyMapSize, applyStoredOptions, randomizeOptions} from "./modules/ui/options";
|
import {applyMapSize, applyStoredOptions, randomizeOptions} from "./modules/ui/options";
|
||||||
import {applyStyleOnLoad} from "./modules/ui/stylePresets";
|
import {applyStyleOnLoad} from "./modules/ui/stylePresets";
|
||||||
import {restoreDefaultEvents} from "./scripts/events";
|
import {restoreDefaultEvents} from "./scripts/events";
|
||||||
|
|
@ -30,7 +30,6 @@ import {minmax, normalize, rn} from "./utils/numberUtils";
|
||||||
import {gauss, generateSeed, P, ra, rand, rw} from "./utils/probabilityUtils";
|
import {gauss, generateSeed, P, ra, rand, rw} from "./utils/probabilityUtils";
|
||||||
import {byId} from "./utils/shorthands";
|
import {byId} from "./utils/shorthands";
|
||||||
import {round} from "./utils/stringUtils";
|
import {round} from "./utils/stringUtils";
|
||||||
import {restoreLayers} from "./modules/ui/layers";
|
|
||||||
|
|
||||||
addGlobalListeners();
|
addGlobalListeners();
|
||||||
|
|
||||||
|
|
@ -174,7 +173,7 @@ async function generateMapOnLoad() {
|
||||||
await applyStyleOnLoad(); // apply previously selected default or custom style
|
await applyStyleOnLoad(); // apply previously selected default or custom style
|
||||||
await generate(); // generate map
|
await generate(); // generate map
|
||||||
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
||||||
applyPreset(); // apply saved layers preset
|
initLayers(); // apply saved layers data
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus on coordinates, cell or burg provided in searchParams
|
// focus on coordinates, cell or burg provided in searchParams
|
||||||
|
|
@ -381,7 +380,7 @@ async function generate(options) {
|
||||||
drawCoastline();
|
drawCoastline();
|
||||||
|
|
||||||
Rivers.generate();
|
Rivers.generate();
|
||||||
drawRivers();
|
renderLayer("rivers");
|
||||||
Lakes.defineGroup();
|
Lakes.defineGroup();
|
||||||
defineBiomes();
|
defineBiomes();
|
||||||
|
|
||||||
|
|
@ -394,8 +393,8 @@ async function generate(options) {
|
||||||
BurgsAndStates.generateProvinces();
|
BurgsAndStates.generateProvinces();
|
||||||
BurgsAndStates.defineBurgFeatures();
|
BurgsAndStates.defineBurgFeatures();
|
||||||
|
|
||||||
drawStates();
|
renderLayer("states");
|
||||||
drawBorders();
|
renderLayer("borders");
|
||||||
BurgsAndStates.drawStateLabels();
|
BurgsAndStates.drawStateLabels();
|
||||||
|
|
||||||
Rivers.specify();
|
Rivers.specify();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import {rn} from "/src/utils/numberUtils";
|
import {rn} from "/src/utils/numberUtils";
|
||||||
import {drawCoordinates} from "/src/modules/ui/layers";
|
import {layerIsOn, renderLayer} from "/src/layers";
|
||||||
import {drawScaleBar} from "/src/modules/measurers";
|
import {drawScaleBar} from "/src/modules/measurers";
|
||||||
|
|
||||||
export function handleZoom(isScaleChanged, isPositionChanged) {
|
export function handleZoom(isScaleChanged, isPositionChanged) {
|
||||||
viewbox.attr("transform", `translate(${viewX} ${viewY}) scale(${scale})`);
|
viewbox.attr("transform", `translate(${viewX} ${viewY}) scale(${scale})`);
|
||||||
|
|
||||||
if (isPositionChanged) drawCoordinates();
|
if (isPositionChanged && layerIsOn("toggleCoordinates")) renderLayer("coordinates");
|
||||||
|
|
||||||
if (isScaleChanged) {
|
if (isScaleChanged) {
|
||||||
invokeActiveZooming();
|
invokeActiveZooming();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import {TIME} from "/src/config/logging";
|
import {TIME} from "/src/config/logging";
|
||||||
import {findCell} from "/src/utils/graphUtils";
|
import {layerIsOn} from "/src/layers";
|
||||||
import {layerIsOn} from "./ui/layers";
|
|
||||||
import {getColors, getRandomColor, getMixedColor} from "/src/utils/colorUtils";
|
|
||||||
import {getMiddlePoint} from "/src/utils/lineUtils";
|
|
||||||
import {rn, minmax} from "/src/utils/numberUtils";
|
|
||||||
import {rand, P, each, gauss, ra, rw, generateSeed} from "/src/utils/probabilityUtils";
|
|
||||||
import {round, splitInTwo} from "/src/utils/stringUtils";
|
|
||||||
import {trimVowels, getAdjective} from "/src/utils/languageUtils";
|
|
||||||
import {Voronoi} from "/src/modules/voronoi";
|
import {Voronoi} from "/src/modules/voronoi";
|
||||||
|
import {getColors, getMixedColor, getRandomColor} from "/src/utils/colorUtils";
|
||||||
|
import {findCell} from "/src/utils/graphUtils";
|
||||||
|
import {getAdjective, trimVowels} from "/src/utils/languageUtils";
|
||||||
|
import {getMiddlePoint} from "/src/utils/lineUtils";
|
||||||
|
import {minmax, rn} from "/src/utils/numberUtils";
|
||||||
|
import {each, gauss, generateSeed, P, ra, rand, rw} from "/src/utils/probabilityUtils";
|
||||||
|
import {round, splitInTwo} from "/src/utils/stringUtils";
|
||||||
|
|
||||||
window.BurgsAndStates = (function () {
|
window.BurgsAndStates = (function () {
|
||||||
const generate = function () {
|
const generate = function () {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import {findCell} from "/src/utils/graphUtils";
|
||||||
import {rn} from "/src/utils/numberUtils";
|
import {rn} from "/src/utils/numberUtils";
|
||||||
import {rand, P, rw} from "/src/utils/probabilityUtils";
|
import {rand, P, rw} from "/src/utils/probabilityUtils";
|
||||||
import {parseTransform} from "/src/utils/stringUtils";
|
import {parseTransform} from "/src/utils/stringUtils";
|
||||||
import {turnLayerButtonOn, turnLayerButtonOff} from "/src/modules/ui/layers";
|
import {turnLayerButtonOn, turnLayerButtonOff} from "/src/layers";
|
||||||
|
|
||||||
// update old .map version to the current one
|
// update old .map version to the current one
|
||||||
export function resolveVersionConflicts(version) {
|
export function resolveVersionConflicts(version) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {updatePresetInput} from "/src/modules/ui/layers";
|
import {updatePresetInput} from "/src/layers";
|
||||||
import {restoreDefaultEvents} from "/src/scripts/events";
|
import {restoreDefaultEvents} from "/src/scripts/events";
|
||||||
import {ldb} from "/src/scripts/indexedDB";
|
import {ldb} from "/src/scripts/indexedDB";
|
||||||
import {tip} from "/src/scripts/tooltips";
|
import {tip} from "/src/scripts/tooltips";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {turnLayerButtonOff, turnLayerButtonOn, updatePresetInput} from "/src/modules/ui/layers";
|
import {turnLayerButtonOff, turnLayerButtonOn, updatePresetInput} from "/src/layers";
|
||||||
import {restoreDefaultEvents} from "/src/scripts/events";
|
import {restoreDefaultEvents} from "/src/scripts/events";
|
||||||
import {prompt} from "/src/scripts/prompt";
|
import {prompt} from "/src/scripts/prompt";
|
||||||
import {clearMainTip, showMainTip, tip} from "/src/scripts/tooltips";
|
import {clearMainTip, showMainTip, tip} from "/src/scripts/tooltips";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {byId} from "/src/utils/shorthands";
|
import {byId} from "/src/utils/shorthands";
|
||||||
import {toggleLayer} from "/src/modules/ui/layers";
|
import {toggleLayer} from "/src/layers";
|
||||||
|
|
||||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||||
document.on("keydown", handleKeydown);
|
document.on("keydown", handleKeydown);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -7,7 +7,7 @@ import {rn} from "/src/utils/numberUtils";
|
||||||
import {rand, P} from "/src/utils/probabilityUtils";
|
import {rand, P} from "/src/utils/probabilityUtils";
|
||||||
import {parseTransform} from "/src/utils/stringUtils";
|
import {parseTransform} from "/src/utils/stringUtils";
|
||||||
import {si} from "/src/utils/unitUtils";
|
import {si} from "/src/utils/unitUtils";
|
||||||
import {turnLayerButtonOff} from "/src/modules/ui/layers";
|
import {turnLayerButtonOff} from "/src/layers";
|
||||||
|
|
||||||
export function editProvinces() {
|
export function editProvinces() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {clearMainTip} from "/src/scripts/tooltips";
|
||||||
import {parseError} from "/src/utils/errorUtils";
|
import {parseError} from "/src/utils/errorUtils";
|
||||||
import {rn, minmax} from "/src/utils/numberUtils";
|
import {rn, minmax} from "/src/utils/numberUtils";
|
||||||
import {debounce} from "/src/utils/functionUtils";
|
import {debounce} from "/src/utils/functionUtils";
|
||||||
import {restoreLayers} from "/src/modules/ui/layers";
|
import {restoreLayers} from "/src/layers";
|
||||||
|
|
||||||
window.UISubmap = (function () {
|
window.UISubmap = (function () {
|
||||||
byId("submapPointsInput").addEventListener("input", function () {
|
byId("submapPointsInput").addEventListener("input", function () {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {isCtrlClick} from "/src/utils/keyboardUtils";
|
||||||
import {prompt} from "/src/scripts/prompt";
|
import {prompt} from "/src/scripts/prompt";
|
||||||
import {getNextId} from "/src/utils/nodeUtils";
|
import {getNextId} from "/src/utils/nodeUtils";
|
||||||
import {P, generateSeed} from "/src/utils/probabilityUtils";
|
import {P, generateSeed} from "/src/utils/probabilityUtils";
|
||||||
import {turnLayerButtonOn} from "/src/modules/ui/layers";
|
import {turnLayerButtonOn} from "/src/layers";
|
||||||
|
|
||||||
toolsContent.addEventListener("click", function (event) {
|
toolsContent.addEventListener("click", function (event) {
|
||||||
if (customization) return tip("Please exit the customization mode first", false, "warning");
|
if (customization) return tip("Please exit the customization mode first", false, "warning");
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": "src"
|
"baseUrl": "."
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue