mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
commit
eded296b47
14 changed files with 809 additions and 100 deletions
102
index.html
102
index.html
|
|
@ -108,14 +108,9 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<link rel="preload" href="index.css?v=12062022" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
<link rel="preload" href="index.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||||
<link rel="preload" href="icons.css?v=04062022" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
<link rel="preload" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||||
<link
|
<link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
|
||||||
rel="preload"
|
|
||||||
href="libs/jquery-ui.css?v=04062022"
|
|
||||||
as="style"
|
|
||||||
onload="this.onload=null; this.rel='stylesheet'"
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -1967,6 +1962,13 @@
|
||||||
|
|
||||||
<p>Click to overview:</p>
|
<p>Click to overview:</p>
|
||||||
<div>
|
<div>
|
||||||
|
<button
|
||||||
|
id="overviewChartsButton"
|
||||||
|
data-tip="Click to open Charts to overview cells data"
|
||||||
|
data-shortcut="Shift + A"
|
||||||
|
>
|
||||||
|
Charts
|
||||||
|
</button>
|
||||||
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T">
|
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T">
|
||||||
Burgs
|
Burgs
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -7779,89 +7781,89 @@
|
||||||
|
|
||||||
<script src="utils/shorthands.js"></script>
|
<script src="utils/shorthands.js"></script>
|
||||||
<script src="utils/commonUtils.js"></script>
|
<script src="utils/commonUtils.js"></script>
|
||||||
<script src="utils/arrayUtils.js?v=02062022"></script>
|
<script src="utils/arrayUtils.js"></script>
|
||||||
<script src="utils/colorUtils.js"></script>
|
<script src="utils/colorUtils.js"></script>
|
||||||
<script src="utils/graphUtils.js?v=02062022"></script>
|
<script src="utils/graphUtils.js"></script>
|
||||||
<script src="utils/nodeUtils.js"></script>
|
<script src="utils/nodeUtils.js"></script>
|
||||||
<script src="utils/numberUtils.js"></script>
|
<script src="utils/numberUtils.js"></script>
|
||||||
<script src="utils/polyfills.js"></script>
|
<script src="utils/polyfills.js"></script>
|
||||||
<script src="utils/probabilityUtils.js?v=29052022"></script>
|
<script src="utils/probabilityUtils.js"></script>
|
||||||
<script src="utils/stringUtils.js?v=04062022"></script>
|
<script src="utils/stringUtils.js"></script>
|
||||||
<script src="utils/languageUtils.js"></script>
|
<script src="utils/languageUtils.js"></script>
|
||||||
<script src="utils/unitUtils.js"></script>
|
<script src="utils/unitUtils.js?v=1.87.00"></script>
|
||||||
|
|
||||||
<script src="modules/voronoi.js"></script>
|
<script src="modules/voronoi.js"></script>
|
||||||
<script src="config/heightmap-templates.js"></script>
|
<script src="config/heightmap-templates.js"></script>
|
||||||
<script src="config/precreated-heightmaps.js"></script>
|
<script src="config/precreated-heightmaps.js"></script>
|
||||||
<script src="modules/heightmap-generator.js?v=29052020"></script>
|
<script src="modules/heightmap-generator.js"></script>
|
||||||
<script src="modules/ocean-layers.js?v=29052022"></script>
|
<script src="modules/ocean-layers.js"></script>
|
||||||
<script src="modules/river-generator.js"></script>
|
<script src="modules/river-generator.js"></script>
|
||||||
<script src="modules/lakes.js"></script>
|
<script src="modules/lakes.js"></script>
|
||||||
<script src="modules/names-generator.js"></script>
|
<script src="modules/names-generator.js"></script>
|
||||||
<script src="modules/cultures-generator.js?v=16062022"></script>
|
<script src="modules/cultures-generator.js"></script>
|
||||||
<script src="modules/burgs-and-states.js?v=29052022"></script>
|
<script src="modules/burgs-and-states.js"></script>
|
||||||
<script src="modules/routes-generator.js"></script>
|
<script src="modules/routes-generator.js"></script>
|
||||||
<script src="modules/religions-generator.js?v=16062022"></script>
|
<script src="modules/religions-generator.js"></script>
|
||||||
<script src="modules/military-generator.js"></script>
|
<script src="modules/military-generator.js"></script>
|
||||||
<script src="modules/markers-generator.js"></script>
|
<script src="modules/markers-generator.js"></script>
|
||||||
<script src="modules/coa-generator.js"></script>
|
<script src="modules/coa-generator.js"></script>
|
||||||
<script src="modules/submap.js?v=02062022"></script>
|
<script src="modules/submap.js"></script>
|
||||||
<script src="libs/polylabel.min.js"></script>
|
<script src="libs/polylabel.min.js"></script>
|
||||||
<script src="libs/lineclip.min.js"></script>
|
<script src="libs/lineclip.min.js"></script>
|
||||||
<script src="libs/alea.min.js"></script>
|
<script src="libs/alea.min.js"></script>
|
||||||
<script src="modules/fonts.js?v=11052022"></script>
|
<script src="modules/fonts.js"></script>
|
||||||
<script src="modules/ui/layers.js?v=06062022"></script>
|
<script src="modules/ui/layers.js"></script>
|
||||||
<script src="modules/ui/measurers.js?v=18052022"></script>
|
<script src="modules/ui/measurers.js"></script>
|
||||||
<script src="modules/ui/stylePresets.js"></script>
|
<script src="modules/ui/stylePresets.js"></script>
|
||||||
|
|
||||||
<script src="modules/ui/general.js?v=02062022"></script>
|
<script src="modules/ui/general.js?v=1.87.00"></script>
|
||||||
<script src="modules/ui/options.js?v=190620222"></script>
|
<script src="modules/ui/options.js?v=1.87.00"></script>
|
||||||
<script src="main.js?v=020620222"></script>
|
<script src="main.js"></script>
|
||||||
|
|
||||||
<script defer src="modules/relief-icons.js"></script>
|
<script defer src="modules/relief-icons.js"></script>
|
||||||
<script defer src="modules/ui/style.js"></script>
|
<script defer src="modules/ui/style.js"></script>
|
||||||
<script defer src="modules/ui/editors.js?v=19062022"></script>
|
<script defer src="modules/ui/editors.js?v=1.87.00"></script>
|
||||||
<script defer src="modules/ui/tools.js?v=12062022"></script>
|
<script defer src="modules/ui/tools.js?v=1.87.00"></script>
|
||||||
<script defer src="modules/ui/world-configurator.js?v=29052022"></script>
|
<script defer src="modules/ui/world-configurator.js"></script>
|
||||||
<script defer src="modules/ui/heightmap-editor.js?v=29052020"></script>
|
<script defer src="modules/ui/heightmap-editor.js"></script>
|
||||||
<script defer src="modules/ui/provinces-editor.js?v=29052022"></script>
|
<script defer src="modules/ui/provinces-editor.js"></script>
|
||||||
<script defer src="modules/ui/biomes-editor.js?v=29052022"></script>
|
<script defer src="modules/ui/biomes-editor.js"></script>
|
||||||
<script defer src="modules/ui/namesbase-editor.js"></script>
|
<script defer src="modules/ui/namesbase-editor.js"></script>
|
||||||
<script defer src="modules/ui/elevation-profile.js?v=29052022"></script>
|
<script defer src="modules/ui/elevation-profile.js"></script>
|
||||||
<script defer src="modules/ui/temperature-graph.js"></script>
|
<script defer src="modules/ui/temperature-graph.js"></script>
|
||||||
<script defer src="modules/ui/routes-editor.js"></script>
|
<script defer src="modules/ui/routes-editor.js"></script>
|
||||||
<script defer src="modules/ui/ice-editor.js?v=29052022"></script>
|
<script defer src="modules/ui/ice-editor.js"></script>
|
||||||
<script defer src="modules/ui/lakes-editor.js?v=18052022"></script>
|
<script defer src="modules/ui/lakes-editor.js"></script>
|
||||||
<script defer src="modules/ui/coastline-editor.js?v=18052022"></script>
|
<script defer src="modules/ui/coastline-editor.js"></script>
|
||||||
<script defer src="modules/ui/labels-editor.js"></script>
|
<script defer src="modules/ui/labels-editor.js"></script>
|
||||||
<script defer src="modules/ui/rivers-editor.js"></script>
|
<script defer src="modules/ui/rivers-editor.js"></script>
|
||||||
<script defer src="modules/ui/rivers-creator.js"></script>
|
<script defer src="modules/ui/rivers-creator.js"></script>
|
||||||
<script defer src="modules/ui/relief-editor.js"></script>
|
<script defer src="modules/ui/relief-editor.js"></script>
|
||||||
<script defer src="modules/ui/burg-editor.js"></script>
|
<script defer src="modules/ui/burg-editor.js"></script>
|
||||||
<script defer src="modules/ui/units-editor.js"></script>
|
<script defer src="modules/ui/units-editor.js"></script>
|
||||||
<script defer src="modules/ui/notes-editor.js?v=29052022"></script>
|
<script defer src="modules/ui/notes-editor.js"></script>
|
||||||
<script defer src="modules/ui/diplomacy-editor.js?v=04062022"></script>
|
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
||||||
<script defer src="modules/ui/zones-editor.js?v=18052022"></script>
|
<script defer src="modules/ui/zones-editor.js"></script>
|
||||||
<script defer src="modules/ui/burgs-overview.js?v=29052022"></script>
|
<script defer src="modules/ui/burgs-overview.js"></script>
|
||||||
<script defer src="modules/ui/rivers-overview.js?v=29052022"></script>
|
<script defer src="modules/ui/rivers-overview.js"></script>
|
||||||
<script defer src="modules/ui/military-overview.js?v=29052022"></script>
|
<script defer src="modules/ui/military-overview.js"></script>
|
||||||
<script defer src="modules/ui/regiments-overview.js?v=29052022"></script>
|
<script defer src="modules/ui/regiments-overview.js"></script>
|
||||||
<script defer src="modules/ui/markers-overview.js?v=29052022"></script>
|
<script defer src="modules/ui/markers-overview.js"></script>
|
||||||
<script defer src="modules/ui/regiment-editor.js?v=12062022"></script>
|
<script defer src="modules/ui/regiment-editor.js"></script>
|
||||||
<script defer src="modules/ui/battle-screen.js"></script>
|
<script defer src="modules/ui/battle-screen.js"></script>
|
||||||
<script defer src="modules/ui/emblems-editor.js"></script>
|
<script defer src="modules/ui/emblems-editor.js"></script>
|
||||||
<script defer src="modules/ui/markers-editor.js"></script>
|
<script defer src="modules/ui/markers-editor.js"></script>
|
||||||
<script defer src="modules/ui/3d.js"></script>
|
<script defer src="modules/ui/3d.js"></script>
|
||||||
<script defer src="modules/ui/submap.js?v=29052022"></script>
|
<script defer src="modules/ui/submap.js"></script>
|
||||||
<script defer src="modules/ui/hotkeys.js?v=17062022"></script>
|
<script defer src="modules/ui/hotkeys.js?v=1.87.00"></script>
|
||||||
<script defer src="modules/coa-renderer.js"></script>
|
<script defer src="modules/coa-renderer.js"></script>
|
||||||
<script defer src="libs/rgbquant.min.js"></script>
|
<script defer src="libs/rgbquant.min.js"></script>
|
||||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||||
|
|
||||||
<script defer src="modules/io/save.js?v=29052022"></script>
|
<script defer src="modules/io/save.js"></script>
|
||||||
<script defer src="modules/io/load.js?v=12062022"></script>
|
<script defer src="modules/io/load.js?v=1.87.00"></script>
|
||||||
<script defer src="modules/io/cloud.js?v=04062022"></script>
|
<script defer src="modules/io/cloud.js"></script>
|
||||||
<script defer src="modules/io/export.js?v=04062022"></script>
|
<script defer src="modules/io/export.js"></script>
|
||||||
<script defer src="modules/io/formats.js"></script>
|
<script defer src="modules/io/formats.js"></script>
|
||||||
|
|
||||||
<!-- Web Components -->
|
<!-- Web Components -->
|
||||||
|
|
|
||||||
|
|
@ -630,7 +630,7 @@ function togglePercentageMode() {
|
||||||
|
|
||||||
async function showHierarchy() {
|
async function showHierarchy() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
const HeirarchyTree = await import("../hierarchy-tree.js?v=19062022");
|
const HeirarchyTree = await import("../hierarchy-tree.js?v=1.87.00");
|
||||||
|
|
||||||
const getDescription = culture => {
|
const getDescription = culture => {
|
||||||
const {name, type, rural, urban} = culture;
|
const {name, type, rural, urban} = culture;
|
||||||
|
|
|
||||||
|
|
@ -533,7 +533,7 @@ function togglePercentageMode() {
|
||||||
|
|
||||||
async function showHierarchy() {
|
async function showHierarchy() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
const HeirarchyTree = await import("../hierarchy-tree.js?v=19062022");
|
const HeirarchyTree = await import("../hierarchy-tree.js?v=1.87.00");
|
||||||
|
|
||||||
const getDescription = religion => {
|
const getDescription = religion => {
|
||||||
const {name, type, form, rural, urban} = religion;
|
const {name, type, form, rural, urban} = religion;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ export function open() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendStyleSheet() {
|
function appendStyleSheet() {
|
||||||
const styles = /* css */ `
|
const style = document.createElement("style");
|
||||||
|
style.textContent = /* css */ `
|
||||||
div.dialog > div.heightmap-selection {
|
div.dialog > div.heightmap-selection {
|
||||||
width: 70vw;
|
width: 70vw;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
|
|
@ -145,8 +146,6 @@ function appendStyleSheet() {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.appendChild(document.createTextNode(styles));
|
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,11 @@ export function open(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendStyleSheet() {
|
function appendStyleSheet() {
|
||||||
const styles = /* css */ `
|
const style = document.createElement("style");
|
||||||
|
style.textContent = /* css */ `
|
||||||
|
#hierarchyTree_selectedOrigins > button {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
#hierarchyTree {
|
#hierarchyTree {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -74,10 +78,6 @@ function appendStyleSheet() {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hierarchyTree_selectedOrigins > button {
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchyTree_selectedOrigins {
|
.hierarchyTree_selectedOrigins {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
@ -141,8 +141,6 @@ function appendStyleSheet() {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.appendChild(document.createTextNode(styles));
|
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
665
modules/dynamic/overview/charts-overview.js
Normal file
665
modules/dynamic/overview/charts-overview.js
Normal file
|
|
@ -0,0 +1,665 @@
|
||||||
|
import {rollups} from "../../../utils/functionUtils.js";
|
||||||
|
|
||||||
|
const entitiesMap = {
|
||||||
|
states: {
|
||||||
|
label: "State",
|
||||||
|
cellsData: pack.cells.state,
|
||||||
|
getName: nameGetter("states"),
|
||||||
|
getColors: colorsGetter("states"),
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
cultures: {
|
||||||
|
label: "Culture",
|
||||||
|
cellsData: pack.cells.culture,
|
||||||
|
getName: nameGetter("cultures"),
|
||||||
|
getColors: colorsGetter("cultures"),
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
religions: {
|
||||||
|
label: "Religion",
|
||||||
|
cellsData: pack.cells.religion,
|
||||||
|
getName: nameGetter("religions"),
|
||||||
|
getColors: colorsGetter("religions"),
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
provinces: {
|
||||||
|
label: "Province",
|
||||||
|
cellsData: pack.cells.province,
|
||||||
|
getName: nameGetter("provinces"),
|
||||||
|
getColors: colorsGetter("provinces"),
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
biomes: {
|
||||||
|
label: "Biome",
|
||||||
|
cellsData: pack.cells.biome,
|
||||||
|
getName: biomeNameGetter,
|
||||||
|
getColors: biomeColorsGetter,
|
||||||
|
landOnly: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const quantizationMap = {
|
||||||
|
total_population: {
|
||||||
|
label: "Total population",
|
||||||
|
quantize: cellId => getUrbanPopulation(cellId) + getRuralPopulation(cellId),
|
||||||
|
aggregate: values => rn(d3.sum(values)),
|
||||||
|
formatTicks: value => si(value),
|
||||||
|
stringify: value => value.toLocaleString(),
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
urban_population: {
|
||||||
|
label: "Urban population",
|
||||||
|
quantize: getUrbanPopulation,
|
||||||
|
aggregate: values => rn(d3.sum(values)),
|
||||||
|
formatTicks: value => si(value),
|
||||||
|
stringify: value => value.toLocaleString(),
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
rural_population: {
|
||||||
|
label: "Rural population",
|
||||||
|
quantize: getRuralPopulation,
|
||||||
|
aggregate: values => rn(d3.sum(values)),
|
||||||
|
formatTicks: value => si(value),
|
||||||
|
stringify: value => value.toLocaleString(),
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
area: {
|
||||||
|
label: "Land area",
|
||||||
|
quantize: cellId => getArea(pack.cells.area[cellId]),
|
||||||
|
aggregate: values => rn(d3.sum(values)),
|
||||||
|
formatTicks: value => `${si(value)} ${getAreaUnit()}`,
|
||||||
|
stringify: value => `${value.toLocaleString()} ${getAreaUnit()}`,
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
cells: {
|
||||||
|
label: "Number of cells",
|
||||||
|
quantize: () => 1,
|
||||||
|
aggregate: values => d3.sum(values),
|
||||||
|
formatTicks: value => value,
|
||||||
|
stringify: value => value.toLocaleString(),
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
burgs_number: {
|
||||||
|
label: "Number of burgs",
|
||||||
|
quantize: cellId => (pack.cells.burg[cellId] ? 1 : 0),
|
||||||
|
aggregate: values => d3.sum(values),
|
||||||
|
formatTicks: value => value,
|
||||||
|
stringify: value => value.toLocaleString(),
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
average_elevation: {
|
||||||
|
label: "Average elevation",
|
||||||
|
quantize: cellId => pack.cells.h[cellId],
|
||||||
|
aggregate: values => d3.mean(values),
|
||||||
|
formatTicks: value => getHeight(value),
|
||||||
|
stringify: value => getHeight(value),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: false
|
||||||
|
},
|
||||||
|
max_elevation: {
|
||||||
|
label: "Maximum mean elevation",
|
||||||
|
quantize: cellId => pack.cells.h[cellId],
|
||||||
|
aggregate: values => d3.max(values),
|
||||||
|
formatTicks: value => getHeight(value),
|
||||||
|
stringify: value => getHeight(value),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: false
|
||||||
|
},
|
||||||
|
min_elevation: {
|
||||||
|
label: "Minimum mean elevation",
|
||||||
|
quantize: cellId => pack.cells.h[cellId],
|
||||||
|
aggregate: values => d3.min(values),
|
||||||
|
formatTicks: value => getHeight(value),
|
||||||
|
stringify: value => getHeight(value),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: false
|
||||||
|
},
|
||||||
|
average_temperature: {
|
||||||
|
label: "Annual mean temperature",
|
||||||
|
quantize: cellId => grid.cells.temp[pack.cells.g[cellId]],
|
||||||
|
aggregate: values => d3.mean(values),
|
||||||
|
formatTicks: value => convertTemperature(value),
|
||||||
|
stringify: value => convertTemperature(value),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: false
|
||||||
|
},
|
||||||
|
max_temperature: {
|
||||||
|
label: "Mean annual maximum temperature",
|
||||||
|
quantize: cellId => grid.cells.temp[pack.cells.g[cellId]],
|
||||||
|
aggregate: values => d3.max(values),
|
||||||
|
formatTicks: value => convertTemperature(value),
|
||||||
|
stringify: value => convertTemperature(value),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: false
|
||||||
|
},
|
||||||
|
min_temperature: {
|
||||||
|
label: "Mean annual minimum temperature",
|
||||||
|
quantize: cellId => grid.cells.temp[pack.cells.g[cellId]],
|
||||||
|
aggregate: values => d3.min(values),
|
||||||
|
formatTicks: value => convertTemperature(value),
|
||||||
|
stringify: value => convertTemperature(value),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: false
|
||||||
|
},
|
||||||
|
average_precipitation: {
|
||||||
|
label: "Annual mean precipitation",
|
||||||
|
quantize: cellId => grid.cells.prec[pack.cells.g[cellId]],
|
||||||
|
aggregate: values => rn(d3.mean(values)),
|
||||||
|
formatTicks: value => getPrecipitation(rn(value)),
|
||||||
|
stringify: value => getPrecipitation(rn(value)),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
max_precipitation: {
|
||||||
|
label: "Mean annual maximum precipitation",
|
||||||
|
quantize: cellId => grid.cells.prec[pack.cells.g[cellId]],
|
||||||
|
aggregate: values => rn(d3.max(values)),
|
||||||
|
formatTicks: value => getPrecipitation(rn(value)),
|
||||||
|
stringify: value => getPrecipitation(rn(value)),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
min_precipitation: {
|
||||||
|
label: "Mean annual minimum precipitation",
|
||||||
|
quantize: cellId => grid.cells.prec[pack.cells.g[cellId]],
|
||||||
|
aggregate: values => rn(d3.min(values)),
|
||||||
|
formatTicks: value => getPrecipitation(rn(value)),
|
||||||
|
stringify: value => getPrecipitation(rn(value)),
|
||||||
|
stackable: false,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
coastal_cells: {
|
||||||
|
label: "Number of coastal cells",
|
||||||
|
quantize: cellId => (pack.cells.t[cellId] === 1 ? 1 : 0),
|
||||||
|
aggregate: values => d3.sum(values),
|
||||||
|
formatTicks: value => value,
|
||||||
|
stringify: value => value.toLocaleString(),
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
},
|
||||||
|
river_cells: {
|
||||||
|
label: "Number of river cells",
|
||||||
|
quantize: cellId => (pack.cells.r[cellId] ? 1 : 0),
|
||||||
|
aggregate: values => d3.sum(values),
|
||||||
|
formatTicks: value => value,
|
||||||
|
stringify: value => value.toLocaleString(),
|
||||||
|
stackable: true,
|
||||||
|
landOnly: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const plotTypeMap = {
|
||||||
|
stackedBar: {offset: d3.stackOffsetDiverging},
|
||||||
|
normalizedStackedBar: {offset: d3.stackOffsetExpand, formatX: value => rn(value * 100) + "%"}
|
||||||
|
};
|
||||||
|
|
||||||
|
appendStyleSheet();
|
||||||
|
insertHtml();
|
||||||
|
addListeners();
|
||||||
|
changeViewColumns();
|
||||||
|
|
||||||
|
export function open() {
|
||||||
|
const charts = byId("chartsOverview__charts").childElementCount;
|
||||||
|
if (!charts) renderChart();
|
||||||
|
$("#chartsOverview").dialog({title: "Data Charts"});
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendStyleSheet() {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = /* css */ `
|
||||||
|
#chartsOverview {
|
||||||
|
max-width: 90vw !important;
|
||||||
|
max-height: 90vh !important;
|
||||||
|
overflow: hidden;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chartsOverview__form {
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin: 0.3em 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-gap: 0.3em;
|
||||||
|
align-items: start;
|
||||||
|
justify-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
#chartsOverview__form {
|
||||||
|
font-size: 1em;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
justify-items: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#chartsOverview__charts {
|
||||||
|
overflow: auto;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chartsOverview__charts figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chartsOverview__charts figcaption {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0 1% 0 4%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertHtml() {
|
||||||
|
const entities = Object.entries(entitiesMap).map(([entity, {label}]) => [entity, label]);
|
||||||
|
const plotBy = Object.entries(quantizationMap).map(([plotBy, {label}]) => [plotBy, label]);
|
||||||
|
|
||||||
|
const createOption = ([value, label]) => `<option value="${value}">${label}</option>`;
|
||||||
|
const createOptions = values => values.map(createOption).join("");
|
||||||
|
|
||||||
|
const html = /* html */ `<div id="chartsOverview">
|
||||||
|
<form id="chartsOverview__form">
|
||||||
|
<div>
|
||||||
|
<button data-tip="Add a chart" type="submit">Plot</button>
|
||||||
|
|
||||||
|
<select data-tip="Select entity (y axis)" id="chartsOverview__entitiesSelect">${createOptions(
|
||||||
|
entities
|
||||||
|
)}</select>
|
||||||
|
|
||||||
|
<label>by
|
||||||
|
<select data-tip="Select value to plot by (x axis)" id="chartsOverview__plotBySelect">${createOptions(
|
||||||
|
plotBy
|
||||||
|
)}</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>grouped by
|
||||||
|
<select data-tip="Select entoty to group by. If you don't need grouping, set it the same as the entity" id="chartsOverview__groupBySelect">${createOptions(
|
||||||
|
entities
|
||||||
|
)}</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label data-tip="Sorting type">sorted
|
||||||
|
<select id="chartsOverview__sortingSelect">
|
||||||
|
<option value="value">by value</option>
|
||||||
|
<option value="name">by name</option>
|
||||||
|
<option value="natural">naturally</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span data-tip="Chart type">Type</span>
|
||||||
|
<select id="chartsOverview__chartType">
|
||||||
|
<option value="stackedBar" selected>Stacked Bar</option>
|
||||||
|
<option value="normalizedStackedBar">Normalized Stacked Bar</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<span data-tip="Columns to display">Columns</span>
|
||||||
|
<select id="chartsOverview__viewColumns">
|
||||||
|
<option value="1" selected>1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<section id="chartsOverview__charts"></section>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
byId("dialogs").insertAdjacentHTML("beforeend", html);
|
||||||
|
|
||||||
|
// set defaults
|
||||||
|
byId("chartsOverview__entitiesSelect").value = "states";
|
||||||
|
byId("chartsOverview__plotBySelect").value = "total_population";
|
||||||
|
byId("chartsOverview__groupBySelect").value = "cultures";
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListeners() {
|
||||||
|
byId("chartsOverview__form").on("submit", renderChart);
|
||||||
|
byId("chartsOverview__viewColumns").on("change", changeViewColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderChart(event) {
|
||||||
|
if (event) event.preventDefault();
|
||||||
|
|
||||||
|
const entity = byId("chartsOverview__entitiesSelect").value;
|
||||||
|
const plotBy = byId("chartsOverview__plotBySelect").value;
|
||||||
|
let groupBy = byId("chartsOverview__groupBySelect").value;
|
||||||
|
const sorting = byId("chartsOverview__sortingSelect").value;
|
||||||
|
const type = byId("chartsOverview__chartType").value;
|
||||||
|
|
||||||
|
const {
|
||||||
|
label: plotByLabel,
|
||||||
|
stringify,
|
||||||
|
quantize,
|
||||||
|
aggregate,
|
||||||
|
formatTicks,
|
||||||
|
stackable,
|
||||||
|
landOnly: plotByLandOnly
|
||||||
|
} = quantizationMap[plotBy];
|
||||||
|
|
||||||
|
if (!stackable && groupBy !== entity) {
|
||||||
|
tip(`Grouping is not supported for ${plotByLabel}`, false, "warn", 4000);
|
||||||
|
groupBy = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const noGrouping = groupBy === entity;
|
||||||
|
|
||||||
|
const {
|
||||||
|
label: entityLabel,
|
||||||
|
getName: getEntityName,
|
||||||
|
cellsData: entityCells,
|
||||||
|
landOnly: entityLandOnly
|
||||||
|
} = entitiesMap[entity];
|
||||||
|
const {label: groupLabel, getName: getGroupName, cellsData: groupCells, getColors} = entitiesMap[groupBy];
|
||||||
|
|
||||||
|
const title = `${capitalize(entity)} by ${plotByLabel}${noGrouping ? "" : " grouped by " + groupLabel}`;
|
||||||
|
|
||||||
|
const tooltip = (entity, group, value, percentage) => {
|
||||||
|
const entityTip = `${entityLabel}: ${entity}`;
|
||||||
|
const groupTip = noGrouping ? "" : `${groupLabel}: ${group}`;
|
||||||
|
let valueTip = `${plotByLabel}: ${stringify(value)}`;
|
||||||
|
if (!noGrouping) valueTip += ` (${rn(percentage * 100)}%)`;
|
||||||
|
return [entityTip, groupTip, valueTip].filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataCollection = {};
|
||||||
|
const groups = new Set();
|
||||||
|
|
||||||
|
for (const cellId of pack.cells.i) {
|
||||||
|
if ((entityLandOnly || plotByLandOnly) && isWater(cellId)) continue;
|
||||||
|
const entityId = entityCells[cellId];
|
||||||
|
const groupId = groupCells[cellId];
|
||||||
|
const value = quantize(cellId);
|
||||||
|
|
||||||
|
if (!dataCollection[entityId]) dataCollection[entityId] = {[groupId]: [value]};
|
||||||
|
else if (!dataCollection[entityId][groupId]) dataCollection[entityId][groupId] = [value];
|
||||||
|
else dataCollection[entityId][groupId].push(value);
|
||||||
|
|
||||||
|
groups.add(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartData = Object.entries(dataCollection)
|
||||||
|
.map(([entityId, groupData]) => {
|
||||||
|
const name = getEntityName(entityId);
|
||||||
|
return Object.entries(groupData).map(([groupId, values]) => {
|
||||||
|
const group = getGroupName(groupId);
|
||||||
|
const value = aggregate(values);
|
||||||
|
return {name, group, value};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
const colors = getColors();
|
||||||
|
const {offset, formatX = formatTicks} = plotTypeMap[type];
|
||||||
|
|
||||||
|
const chart = createStackedBarChart(chartData, {sorting, colors, tooltip, offset, formatX});
|
||||||
|
insertChart(chart, title);
|
||||||
|
|
||||||
|
byId("chartsOverview__charts").lastChild.scrollIntoView();
|
||||||
|
updateDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on observablehq.com/@d3/stacked-horizontal-bar-chart
|
||||||
|
function createStackedBarChart(data, {sorting, colors, tooltip, offset, formatX}) {
|
||||||
|
const sortedData = sortData(data, sorting);
|
||||||
|
|
||||||
|
const X = sortedData.map(d => d.value);
|
||||||
|
const Y = sortedData.map(d => d.name);
|
||||||
|
const Z = sortedData.map(d => d.group);
|
||||||
|
|
||||||
|
const yDomain = new Set(Y);
|
||||||
|
const zDomain = new Set(Z);
|
||||||
|
const I = d3.range(X.length).filter(i => yDomain.has(Y[i]) && zDomain.has(Z[i]));
|
||||||
|
|
||||||
|
const entities = Array.from(yDomain);
|
||||||
|
const groups = Array.from(zDomain);
|
||||||
|
|
||||||
|
const yScaleMinWidth = getTextMinWidth(entities);
|
||||||
|
const legendRows = calculateLegendRows(groups, WIDTH - yScaleMinWidth - 15);
|
||||||
|
|
||||||
|
const margin = {top: 30, right: 15, bottom: legendRows * 20 + 10, left: yScaleMinWidth};
|
||||||
|
const xRange = [margin.left, WIDTH - margin.right];
|
||||||
|
const height = yDomain.size * 25 + margin.top + margin.bottom;
|
||||||
|
const yRange = [height - margin.bottom, margin.top];
|
||||||
|
|
||||||
|
const rolled = rollups(...[I, ([i]) => i, i => Y[i], i => Z[i]]);
|
||||||
|
|
||||||
|
const series = d3
|
||||||
|
.stack()
|
||||||
|
.keys(groups)
|
||||||
|
.value(([, I], z) => X[new Map(I).get(z)])
|
||||||
|
.order(d3.stackOrderNone)
|
||||||
|
.offset(offset)(rolled)
|
||||||
|
.map(s => {
|
||||||
|
const defined = s.filter(d => !isNaN(d[1]));
|
||||||
|
const data = defined.map(d => Object.assign(d, {i: new Map(d.data[1]).get(s.key)}));
|
||||||
|
return {key: s.key, data};
|
||||||
|
});
|
||||||
|
|
||||||
|
const xDomain = d3.extent(series.map(d => d.data).flat(2));
|
||||||
|
|
||||||
|
const xScale = d3.scaleLinear(xDomain, xRange);
|
||||||
|
const yScale = d3.scaleBand(entities, yRange).paddingInner(Y_PADDING);
|
||||||
|
|
||||||
|
const xAxis = d3.axisTop(xScale).ticks(WIDTH / 80, null);
|
||||||
|
const yAxis = d3.axisLeft(yScale).tickSizeOuter(0);
|
||||||
|
|
||||||
|
const svg = d3
|
||||||
|
.create("svg")
|
||||||
|
.attr("version", "1.1")
|
||||||
|
.attr("xmlns", "http://www.w3.org/2000/svg")
|
||||||
|
.attr("viewBox", [0, 0, WIDTH, height])
|
||||||
|
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
|
||||||
|
|
||||||
|
svg
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", `translate(0,${margin.top})`)
|
||||||
|
.call(xAxis)
|
||||||
|
.call(g => g.select(".domain").remove())
|
||||||
|
.call(g => g.selectAll("text").text(d => formatX(d)))
|
||||||
|
.call(g =>
|
||||||
|
g
|
||||||
|
.selectAll(".tick line")
|
||||||
|
.clone()
|
||||||
|
.attr("y2", height - margin.top - margin.bottom)
|
||||||
|
.attr("stroke-opacity", 0.1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const bar = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("stroke", "#666")
|
||||||
|
.attr("stroke-width", 0.5)
|
||||||
|
.selectAll("g")
|
||||||
|
.data(series)
|
||||||
|
.join("g")
|
||||||
|
.attr("fill", d => colors[d.key])
|
||||||
|
.selectAll("rect")
|
||||||
|
.data(d => d.data.filter(([x1, x2]) => x1 !== x2))
|
||||||
|
.join("rect")
|
||||||
|
.attr("x", ([x1, x2]) => Math.min(xScale(x1), xScale(x2)))
|
||||||
|
.attr("y", ({i}) => yScale(Y[i]))
|
||||||
|
.attr("width", ([x1, x2]) => Math.abs(xScale(x1) - xScale(x2)))
|
||||||
|
.attr("height", yScale.bandwidth());
|
||||||
|
|
||||||
|
const totalZ = Object.fromEntries(
|
||||||
|
rollups(...[I, ([i]) => i, i => Y[i], i => X[i]]).map(([y, yz]) => [y, d3.sum(yz, yz => yz[0])])
|
||||||
|
);
|
||||||
|
const getTooltip = ({i}) => tooltip(Y[i], Z[i], X[i], X[i] / totalZ[Y[i]]);
|
||||||
|
|
||||||
|
bar.append("title").text(d => getTooltip(d).join("\r\n"));
|
||||||
|
bar.on("mouseover", d => tip(getTooltip(d).join(". ")));
|
||||||
|
|
||||||
|
svg
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", `translate(${xScale(0)},0)`)
|
||||||
|
.call(yAxis);
|
||||||
|
|
||||||
|
const rowElements = Math.ceil(groups.length / legendRows);
|
||||||
|
const columnWidth = WIDTH / (rowElements + 0.5);
|
||||||
|
|
||||||
|
const ROW_HEIGHT = 20;
|
||||||
|
|
||||||
|
const getLegendX = (d, i) => (i % rowElements) * columnWidth;
|
||||||
|
const getLegendLabelX = (d, i) => getLegendX(d, i) + LABEL_GAP;
|
||||||
|
const getLegendY = (d, i) => Math.floor(i / rowElements) * ROW_HEIGHT;
|
||||||
|
|
||||||
|
const legend = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("stroke", "#666")
|
||||||
|
.attr("stroke-width", 0.5)
|
||||||
|
.attr("dominant-baseline", "central")
|
||||||
|
.attr("transform", `translate(${margin.left},${height - margin.bottom + 15})`);
|
||||||
|
|
||||||
|
legend
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(groups)
|
||||||
|
.join("rect")
|
||||||
|
.attr("x", getLegendX)
|
||||||
|
.attr("y", getLegendY)
|
||||||
|
.attr("width", 10)
|
||||||
|
.attr("height", 10)
|
||||||
|
.attr("transform", "translate(-5, -5)")
|
||||||
|
.attr("fill", d => colors[d]);
|
||||||
|
|
||||||
|
legend
|
||||||
|
.selectAll("text")
|
||||||
|
.data(groups)
|
||||||
|
.join("text")
|
||||||
|
.attr("x", getLegendLabelX)
|
||||||
|
.attr("y", getLegendY)
|
||||||
|
.text(d => d);
|
||||||
|
|
||||||
|
return svg.node();
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertChart(chart, title) {
|
||||||
|
const $chartContainer = byId("chartsOverview__charts");
|
||||||
|
|
||||||
|
const $figure = document.createElement("figure");
|
||||||
|
const $caption = document.createElement("figcaption");
|
||||||
|
|
||||||
|
const figureNo = $chartContainer.childElementCount + 1;
|
||||||
|
$caption.innerHTML = /* html */ `
|
||||||
|
<div>
|
||||||
|
<strong>Figure ${figureNo}</strong>. ${title}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button data-tip="Download the chart in svg format (can open in browser or Inkscape)" class="icon-download"></button>
|
||||||
|
<button data-tip="Remove the chart" class="icon-trash"></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$figure.appendChild(chart);
|
||||||
|
$figure.appendChild($caption);
|
||||||
|
$chartContainer.appendChild($figure);
|
||||||
|
|
||||||
|
const downloadChart = () => {
|
||||||
|
const name = `${getFileName(title)}.svg`;
|
||||||
|
downloadFile(chart.outerHTML, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeChart = () => {
|
||||||
|
$figure.remove();
|
||||||
|
updateDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
$figure.querySelector("button.icon-download").on("click", downloadChart);
|
||||||
|
$figure.querySelector("button.icon-trash").on("click", removeChart);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeViewColumns() {
|
||||||
|
const columns = byId("chartsOverview__viewColumns").value;
|
||||||
|
const $charts = byId("chartsOverview__charts");
|
||||||
|
$charts.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
|
||||||
|
updateDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDialog() {
|
||||||
|
$("#chartsOverview").dialog({position: {my: "center", at: "center", of: "svg"}});
|
||||||
|
}
|
||||||
|
|
||||||
|
// config
|
||||||
|
const NEUTRAL_COLOR = "#ccc";
|
||||||
|
const EMPTY_NAME = "no";
|
||||||
|
|
||||||
|
const WIDTH = 800;
|
||||||
|
const Y_PADDING = 0.2;
|
||||||
|
|
||||||
|
const RESERVED_PX_PER_CHAR = 7;
|
||||||
|
const LABEL_GAP = 10;
|
||||||
|
|
||||||
|
function getTextMinWidth(entities) {
|
||||||
|
return d3.max(entities.map(name => name.length)) * RESERVED_PX_PER_CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateLegendRows(groups, availableWidth) {
|
||||||
|
const minWidth = LABEL_GAP + getTextMinWidth(groups);
|
||||||
|
const maxInRow = Math.floor(availableWidth / minWidth);
|
||||||
|
const legendRows = Math.ceil(groups.length / maxInRow);
|
||||||
|
return legendRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nameGetter(entity) {
|
||||||
|
return i => pack[entity][i].name || EMPTY_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorsGetter(entity) {
|
||||||
|
return () => Object.fromEntries(pack[entity].map(({name, color}) => [name || EMPTY_NAME, color || NEUTRAL_COLOR]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function biomeNameGetter(i) {
|
||||||
|
return biomesData.name[i] || EMPTY_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
function biomeColorsGetter() {
|
||||||
|
return Object.fromEntries(biomesData.i.map(i => [biomesData.name[i], biomesData.color[i]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUrbanPopulation(cellId) {
|
||||||
|
const burgId = pack.cells.burg[cellId];
|
||||||
|
if (!burgId) return 0;
|
||||||
|
const populationPoints = pack.burgs[burgId].population;
|
||||||
|
return populationPoints * populationRate * urbanization;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuralPopulation(cellId) {
|
||||||
|
return pack.cells.pop[cellId] * populationRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortData(data, sorting) {
|
||||||
|
if (sorting === "natural") return data;
|
||||||
|
|
||||||
|
if (sorting === "name") {
|
||||||
|
return data.sort((a, b) => {
|
||||||
|
if (a.name !== b.name) return b.name.localeCompare(a.name); // reversed as 1st element is the bottom
|
||||||
|
return a.group.localeCompare(b.group);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sorting === "value") {
|
||||||
|
const entitySum = {};
|
||||||
|
const groupSum = {};
|
||||||
|
for (const {name, group, value} of data) {
|
||||||
|
entitySum[name] = (entitySum[name] || 0) + value;
|
||||||
|
groupSum[group] = (groupSum[group] || 0) + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.sort((a, b) => {
|
||||||
|
if (a.name !== b.name) return entitySum[a.name] - entitySum[b.name]; // reversed as 1st element is the bottom
|
||||||
|
return groupSum[b.group] - groupSum[a.group];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
@ -1180,12 +1180,12 @@ async function editStates() {
|
||||||
|
|
||||||
async function editCultures() {
|
async function editCultures() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=19062022");
|
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.87.00");
|
||||||
Editor.open();
|
Editor.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function editReligions() {
|
async function editReligions() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
const Editor = await import("../dynamic/editors/religions-editor.js?v=19062022");
|
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.87.00");
|
||||||
Editor.open();
|
Editor.open();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,11 @@ function showMapTooltip(point, e, i, g) {
|
||||||
if (group === "emblems" && e.target.tagName === "use") {
|
if (group === "emblems" && e.target.tagName === "use") {
|
||||||
const parent = e.target.parentNode;
|
const parent = e.target.parentNode;
|
||||||
const [g, type] =
|
const [g, type] =
|
||||||
parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
parent.id === "burgEmblems"
|
||||||
|
? [pack.burgs, "burg"]
|
||||||
|
: parent.id === "provinceEmblems"
|
||||||
|
? [pack.provinces, "province"]
|
||||||
|
: [pack.states, "state"];
|
||||||
const i = +e.target.dataset.i;
|
const i = +e.target.dataset.i;
|
||||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||||
|
|
||||||
|
|
@ -161,8 +165,10 @@ function showMapTooltip(point, e, i, g) {
|
||||||
if (group === "ruler") {
|
if (group === "ruler") {
|
||||||
const tag = e.target.tagName;
|
const tag = e.target.tagName;
|
||||||
const className = e.target.getAttribute("class");
|
const className = e.target.getAttribute("class");
|
||||||
if (tag === "circle" && className === "edge") return tip("Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point");
|
if (tag === "circle" && className === "edge")
|
||||||
if (tag === "circle" && className === "control") return tip("Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point");
|
return tip("Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point");
|
||||||
|
if (tag === "circle" && className === "control")
|
||||||
|
return tip("Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point");
|
||||||
if (tag === "circle") return tip("Drag to adjust the measurer");
|
if (tag === "circle") return tip("Drag to adjust the measurer");
|
||||||
if (tag === "polyline") return tip("Click on drag to add a control point");
|
if (tag === "polyline") return tip("Click on drag to add a control point");
|
||||||
if (tag === "path") return tip("Drag to move the measurer");
|
if (tag === "path") return tip("Drag to move the measurer");
|
||||||
|
|
@ -248,10 +254,19 @@ function updateCellInfo(point, i, g) {
|
||||||
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
||||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a";
|
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : "n/a";
|
||||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no";
|
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : "no";
|
||||||
infoState.innerHTML = cells.h[i] >= 20 ? (cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : "neutral lands (0)") : "no";
|
infoState.innerHTML =
|
||||||
infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : "no";
|
cells.h[i] >= 20
|
||||||
|
? cells.state[i]
|
||||||
|
? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})`
|
||||||
|
: "neutral lands (0)"
|
||||||
|
: "no";
|
||||||
|
infoProvince.innerHTML = cells.province[i]
|
||||||
|
? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})`
|
||||||
|
: "no";
|
||||||
infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : "no";
|
infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : "no";
|
||||||
infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : "no";
|
infoReligion.innerHTML = cells.religion[i]
|
||||||
|
? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})`
|
||||||
|
: "no";
|
||||||
infoPopulation.innerHTML = getFriendlyPopulation(i);
|
infoPopulation.innerHTML = getFriendlyPopulation(i);
|
||||||
infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + " (" + cells.burg[i] + ")" : "no";
|
infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + " (" + cells.burg[i] + ")" : "no";
|
||||||
infoFeature.innerHTML = f ? pack.features[f].group + " (" + f + ")" : "n/a";
|
infoFeature.innerHTML = f ? pack.features[f].group + " (" + f + ")" : "n/a";
|
||||||
|
|
@ -300,8 +315,7 @@ function getFriendlyHeight([x, y]) {
|
||||||
function getHeight(h, abs) {
|
function getHeight(h, abs) {
|
||||||
const unit = heightUnit.value;
|
const unit = heightUnit.value;
|
||||||
let unitRatio = 3.281; // default calculations are in feet
|
let unitRatio = 3.281; // default calculations are in feet
|
||||||
if (unit === "m") unitRatio = 1;
|
if (unit === "m") unitRatio = 1; // if meter
|
||||||
// if meter
|
|
||||||
else if (unit === "f") unitRatio = 0.5468; // if fathom
|
else if (unit === "f") unitRatio = 0.5468; // if fathom
|
||||||
|
|
||||||
let height = -990;
|
let height = -990;
|
||||||
|
|
@ -312,10 +326,14 @@ function getHeight(h, abs) {
|
||||||
return rn(height * unitRatio) + " " + unit;
|
return rn(height * unitRatio) + " " + unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPrecipitation(prec) {
|
||||||
|
return prec * 100 + " mm";
|
||||||
|
}
|
||||||
|
|
||||||
// get user-friendly (real-world) precipitation value from map data
|
// get user-friendly (real-world) precipitation value from map data
|
||||||
function getFriendlyPrecipitation(i) {
|
function getFriendlyPrecipitation(i) {
|
||||||
const prec = grid.cells.prec[pack.cells.g[i]];
|
const prec = grid.cells.prec[pack.cells.g[i]];
|
||||||
return prec * 100 + " mm";
|
return getPrecipitation(prec);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRiverInfo(id) {
|
function getRiverInfo(id) {
|
||||||
|
|
@ -399,7 +417,8 @@ function highlightEmblemElement(type, el) {
|
||||||
// assign lock behavior
|
// assign lock behavior
|
||||||
document.querySelectorAll("[data-locked]").forEach(function (e) {
|
document.querySelectorAll("[data-locked]").forEach(function (e) {
|
||||||
e.addEventListener("mouseover", function (event) {
|
e.addEventListener("mouseover", function (event) {
|
||||||
if (this.className === "icon-lock") tip("Click to unlock the option and allow it to be randomized on new map generation");
|
if (this.className === "icon-lock")
|
||||||
|
tip("Click to unlock the option and allow it to be randomized on new map generation");
|
||||||
else tip("Click to lock the option and always use the current value on new map generation");
|
else tip("Click to lock the option and always use the current value on new map generation");
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
@ -476,7 +495,10 @@ function showInfo() {
|
||||||
const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
|
const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
|
||||||
const Armoria = link("https://azgaar.github.io/Armoria", "Armoria");
|
const Armoria = link("https://azgaar.github.io/Armoria", "Armoria");
|
||||||
|
|
||||||
const QuickStart = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial");
|
const QuickStart = link(
|
||||||
|
"https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial",
|
||||||
|
"Quick start tutorial"
|
||||||
|
);
|
||||||
const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page");
|
const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page");
|
||||||
const VideoTutorial = link("https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C", "Video tutorial");
|
const VideoTutorial = link("https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C", "Video tutorial");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ function handleKeyup(event) {
|
||||||
else if (shift && code === "KeyY") openEmblemEditor();
|
else if (shift && code === "KeyY") openEmblemEditor();
|
||||||
else if (shift && code === "KeyQ") editUnits();
|
else if (shift && code === "KeyQ") editUnits();
|
||||||
else if (shift && code === "KeyO") editNotes();
|
else if (shift && code === "KeyO") editNotes();
|
||||||
|
else if (shift && code === "KeyA") overviewCharts();
|
||||||
else if (shift && code === "KeyT") overviewBurgs();
|
else if (shift && code === "KeyT") overviewBurgs();
|
||||||
else if (shift && code === "KeyV") overviewRivers();
|
else if (shift && code === "KeyV") overviewRivers();
|
||||||
else if (shift && code === "KeyM") overviewMilitary();
|
else if (shift && code === "KeyM") overviewMilitary();
|
||||||
|
|
|
||||||
|
|
@ -635,7 +635,7 @@ function changeEra() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openTemplateSelectionDialog() {
|
async function openTemplateSelectionDialog() {
|
||||||
const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=290520222");
|
const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.87.00");
|
||||||
HeightmapSelectionDialog.open();
|
HeightmapSelectionDialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ toolsContent.addEventListener("click", function (event) {
|
||||||
else if (button === "editUnitsButton") editUnits();
|
else if (button === "editUnitsButton") editUnits();
|
||||||
else if (button === "editNotesButton") editNotes();
|
else if (button === "editNotesButton") editNotes();
|
||||||
else if (button === "editZonesButton") editZones();
|
else if (button === "editZonesButton") editZones();
|
||||||
|
else if (button === "overviewChartsButton") overviewCharts();
|
||||||
else if (button === "overviewBurgsButton") overviewBurgs();
|
else if (button === "overviewBurgsButton") overviewBurgs();
|
||||||
else if (button === "overviewRiversButton") overviewRivers();
|
else if (button === "overviewRiversButton") overviewRivers();
|
||||||
else if (button === "overviewMilitaryButton") overviewMilitary();
|
else if (button === "overviewMilitaryButton") overviewMilitary();
|
||||||
|
|
@ -855,3 +856,8 @@ function viewCellDetails() {
|
||||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function overviewCharts() {
|
||||||
|
const Overview = await import("../dynamic/overview/charts-overview.js");
|
||||||
|
Overview.open();
|
||||||
|
}
|
||||||
|
|
|
||||||
25
utils/functionUtils.js
Normal file
25
utils/functionUtils.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// extracted d3 code to bypass version conflicts
|
||||||
|
// https://github.com/d3/d3-array/blob/main/src/group.js
|
||||||
|
|
||||||
|
export function rollups(values, reduce, ...keys) {
|
||||||
|
return nest(values, Array.from, reduce, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nest(values, map, reduce, keys) {
|
||||||
|
return (function regroup(values, i) {
|
||||||
|
if (i >= keys.length) return reduce(values);
|
||||||
|
const groups = new Map();
|
||||||
|
const keyof = keys[i++];
|
||||||
|
let index = -1;
|
||||||
|
for (const value of values) {
|
||||||
|
const key = keyof(value, ++index, values);
|
||||||
|
const group = groups.get(key);
|
||||||
|
if (group) group.push(value);
|
||||||
|
else groups.set(key, [value]);
|
||||||
|
}
|
||||||
|
for (const [key, values] of groups) {
|
||||||
|
groups.set(key, regroup(values, i));
|
||||||
|
}
|
||||||
|
return map(groups);
|
||||||
|
})(values, 0);
|
||||||
|
}
|
||||||
|
|
@ -2,27 +2,20 @@
|
||||||
// FMG utils related to units
|
// FMG utils related to units
|
||||||
|
|
||||||
// conver temperature from °C to other scales
|
// conver temperature from °C to other scales
|
||||||
|
const temperatureConversionMap = {
|
||||||
|
"°C": temp => rn(temp) + "°C",
|
||||||
|
"°F": temp => rn((temp * 9) / 5 + 32) + "°F",
|
||||||
|
K: temp => rn(temp + 273.15) + "K",
|
||||||
|
"°R": temp => rn(((temp + 273.15) * 9) / 5) + "°R",
|
||||||
|
"°De": temp => rn(((100 - temp) * 3) / 2) + "°De",
|
||||||
|
"°N": temp => rn((temp * 33) / 100) + "°N",
|
||||||
|
"°Ré": temp => rn((temp * 4) / 5) + "°Ré",
|
||||||
|
"°Rø": temp => rn((temp * 21) / 40 + 7.5) + "°Rø"
|
||||||
|
};
|
||||||
|
|
||||||
function convertTemperature(temp) {
|
function convertTemperature(temp) {
|
||||||
switch (temperatureScale.value) {
|
const scale = temperatureScale.value || "°C";
|
||||||
case "°C":
|
return temperatureConversionMap[scale](temp);
|
||||||
return rn(temp) + "°C";
|
|
||||||
case "°F":
|
|
||||||
return rn((temp * 9) / 5 + 32) + "°F";
|
|
||||||
case "K":
|
|
||||||
return rn(temp + 273.15) + "K";
|
|
||||||
case "°R":
|
|
||||||
return rn(((temp + 273.15) * 9) / 5) + "°R";
|
|
||||||
case "°De":
|
|
||||||
return rn(((100 - temp) * 3) / 2) + "°De";
|
|
||||||
case "°N":
|
|
||||||
return rn((temp * 33) / 100) + "°N";
|
|
||||||
case "°Ré":
|
|
||||||
return rn((temp * 4) / 5) + "°Ré";
|
|
||||||
case "°Rø":
|
|
||||||
return rn((temp * 21) / 40 + 7.5) + "°Rø";
|
|
||||||
default:
|
|
||||||
return rn(temp) + "°C";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// corvent number to short string with SI postfix
|
// corvent number to short string with SI postfix
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
// version and caching control
|
// version and caching control
|
||||||
|
|
||||||
const version = "1.86.10"; // generator version, update each time
|
const version = "1.87.00"; // generator version, update each time
|
||||||
|
|
||||||
{
|
{
|
||||||
document.title += " v" + version;
|
document.title += " v" + version;
|
||||||
|
|
@ -28,6 +28,7 @@ const version = "1.86.10"; // generator version, update each time
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<strong>Latest changes:</strong>
|
<strong>Latest changes:</strong>
|
||||||
|
<li>Data Charts screen</li>
|
||||||
<li>Сultures and religions can have multiple parents in hierarchy tree</li>
|
<li>Сultures and religions can have multiple parents in hierarchy tree</li>
|
||||||
<li>Heightmap selection screen</li>
|
<li>Heightmap selection screen</li>
|
||||||
<li>Dialogs optimization for mobile</li>
|
<li>Dialogs optimization for mobile</li>
|
||||||
|
|
@ -36,9 +37,6 @@ const version = "1.86.10"; // generator version, update each time
|
||||||
<li>Ability to install the App</li>
|
<li>Ability to install the App</li>
|
||||||
<li>14 new default fonts</li>
|
<li>14 new default fonts</li>
|
||||||
<li>Caching for faster startup</li>
|
<li>Caching for faster startup</li>
|
||||||
<li>Submap tool by Goteguru</li>
|
|
||||||
<li>Resample tool by Goteguru</li>
|
|
||||||
<li>Pre-defined heightmaps</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue