Merge pull request #734 from Azgaar/burg-temperature-graph

Burg temperature graph refactoring
This commit is contained in:
Azgaar 2022-02-05 17:15:05 +03:00 committed by GitHub
commit 69a26c37d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 230 additions and 61 deletions

View file

@ -2310,7 +2310,7 @@ svg.button {
.epgrid line {
stroke: lightgrey;
stroke-opacity: 0.7;
stroke-opacity: 0.5;
shape-rendering: crispEdges;
}

View file

@ -226,7 +226,7 @@
<div id="loading">
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
<div id="version"><t data-t="version">v. </t>1.71</div>
<div id="version"><t data-t="version">v. </t>1.72</div>
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
</div>
@ -1799,7 +1799,7 @@
<span data-tip="Set curve profile">Curve:
<select id="epCurve">
<option>Linear</option>
<option selected>Basis spline</option>
<option selected>Basis spline</option>
<option>Bundle</option>
<option>Cubic Catmull-Rom</option>
<option>Monotone X</option>
@ -1990,61 +1990,62 @@
<div id="burgBody" style="padding-bottom: .3em">
<div style="display: flex; align-items: center">
<svg data-tip="Burg emblem. Click to edit" class="pointer" viewBox="0 0 200 200" width="13em" height="13em"><use id="burgEmblem"></use></svg>
<div>
<div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div>
<div style="display: grid; grid-auto-rows: minmax(1.6em, auto)">
<div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div>
<div>
<div class="label">Name:</div>
<input id="burgName" data-tip="Type to rename the burg" autocorrect="off" spellcheck="false" style="width: 8em">
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
<span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
</div>
<div>
<div class="label">Name:</div>
<input id="burgName" data-tip="Type to rename the burg" autocorrect="off" spellcheck="false" style="width: 8em">
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
<span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
</div>
<div data-tip="Select burg type. Type slightly affects emblem generation">
<div class="label">Type:</div>
<select id="burgType" style="width: 8em">
<option value="Generic">Generic</option>
<option value="River">River</option>
<option value="Lake">Lake</option>
<option value="Naval">Naval</option>
<option value="Nomadic">Nomadic</option>
<option value="Hunting">Hunting</option>
<option value="Highland">Highland</option>
</select>
</div>
<div data-tip="Select burg type. Type slightly affects emblem generation">
<div class="label">Type:</div>
<select id="burgType" style="width: 8em">
<option value="Generic">Generic</option>
<option value="River">River</option>
<option value="Lake">Lake</option>
<option value="Naval">Naval</option>
<option value="Nomadic">Nomadic</option>
<option value="Hunting">Hunting</option>
<option value="Highland">Highland</option>
</select>
</div>
<div data-tip="Select dominant culture">
<div class="label">Culture:</div>
<select id="burgCulture" style="width: 8em"></select>
<span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span>
</div>
<div data-tip="Select dominant culture">
<div class="label">Culture:</div>
<select id="burgCulture" style="width: 8em"></select>
<span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span>
</div>
<div data-tip="Set burg population">
<div class="label">Population:</div>
<input id="burgPopulation" type="number" min=0 step=1 style="width: 8em">
</div>
<div data-tip="Set burg population">
<div class="label">Population:</div>
<input id="burgPopulation" type="number" min=0 step=1 style="width: 8em">
</div>
<div>
<div class="label">Features:</div>
<span id="burgCapital" data-tip="Shows whether the burg is a state capital. Click to toggle" data-feature="capital" class="burgFeature icon-star"></span>
<span id="burgPort" data-tip="Shows whether the burg is a port. Click to toggle" data-feature="port" class="burgFeature icon-anchor"></span>
<span id="burgCitadel" data-tip="Shows whether the burg has a citadel (castle). Click to toggle" data-feature="citadel" class="burgFeature icon-chess-rook" style="font-size: 1.1em"></span>
<span id="burgWalls" data-tip="Shows whether the burg is walled. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span>
<span id="burgPlaza" data-tip="Shows whether the burg is a trade center (has big marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span>
<span id="burgTemple" data-tip="Shows whether the burg is a religious center. Click to toggle" data-feature="temple" class="burgFeature icon-chess-bishop" style="font-size: 1.1em; margin-left: 3px"></span>
<span id="burgShanty" data-tip="Shows whether the burg has a shanty town. Click to toggle" data-feature="shanty" class="burgFeature icon-campground" style="font-size: 1em"></span>
</div>
<div>
<div class="label">Features:</div>
<span id="burgCapital" data-tip="Shows whether the burg is a state capital. Click to toggle" data-feature="capital" class="burgFeature icon-star"></span>
<span id="burgPort" data-tip="Shows whether the burg is a port. Click to toggle" data-feature="port" class="burgFeature icon-anchor"></span>
<span id="burgCitadel" data-tip="Shows whether the burg has a citadel (castle). Click to toggle" data-feature="citadel" class="burgFeature icon-chess-rook" style="font-size: 1.1em"></span>
<span id="burgWalls" data-tip="Shows whether the burg is walled. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span>
<span id="burgPlaza" data-tip="Shows whether the burg is a trade center (has big marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span>
<span id="burgTemple" data-tip="Shows whether the burg is a religious center. Click to toggle" data-feature="temple" class="burgFeature icon-chess-bishop" style="font-size: 1.1em; margin-left: 3px"></span>
<span id="burgShanty" data-tip="Shows whether the burg has a shanty town. Click to toggle" data-feature="shanty" class="burgFeature icon-campground" style="font-size: 1em"></span>
</div>
<div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span>
</div>
<div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span>
<i id="burgTemperatureGraph" data-tip="Show temperature graph for the burg" class="icon-chart-area pointer"></i>
</div>
<div data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level
</div>
<div data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level
</div>
</div>
</div>
@ -4426,6 +4427,7 @@
<script defer src="modules/ui/cultures-editor.js"></script>
<script defer src="modules/ui/namesbase-editor.js"></script>
<script defer src="modules/ui/elevation-profile.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/ice-editor.js"></script>
<script defer src="modules/ui/lakes-editor.js"></script>

16
main.js
View file

@ -2,7 +2,7 @@
// https://github.com/Azgaar/Fantasy-Map-Generator
"use strict";
const version = "1.71"; // generator version
const version = "1.72"; // generator version
document.title += " v" + version;
// Switches to disable/enable logging features
@ -411,20 +411,20 @@ function showWelcomeMessage() {
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
<ul>Main changes:
<li>Ability to limit military units by biome, state, culture and religion</li>
<ul><b>Latest changes:</b>
<li>Burg temperature graph</li>
<li>4 new textures</li>
<li>Province capture logic rework</li>
<li>Button to release all provinces</li>
<li>Limit military units by biome, state, culture and religion</li>
<li>New marker types</li>
<li>New markers editor</li>
<li>Markers overview screen</li>
<li>Markers regeneration menu</li>
<li>Burg editor update</li>
<li>Editable theme color</li>
<li>Add font dialog</li>
<li>Save to Dropbox</li>
</ul>
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
<span>Thanks for all supporters on <a href="https://www.patreon.com/azgaar" target="_blank">Patreon</a>!</i></span>`;
<span><i>Thanks for all supporters on ${patreon}!</i></span>`;
$("#alert").dialog({
resizable: false,

View file

@ -52,6 +52,7 @@ function editBurg(id) {
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
function updateBurgValues() {
const id = +elSelected.attr("data-id");
@ -543,6 +544,11 @@ function editBurg(id) {
editNotes("burg" + id, name);
}
function showTemperatureGraph() {
const id = elSelected.attr("data-id");
showBurgTemperatureGraph(id);
}
function removeSelectedBurg() {
const id = +elSelected.attr("data-id");
if (pack.burgs[id].capital) {

View file

@ -0,0 +1,161 @@
"use strict";
function showBurgTemperatureGraph(id) {
const b = pack.burgs[id];
const lat = mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT;
const burgTemp = grid.cells.temp[pack.cells.g[b.cell]];
const prec = grid.cells.prec[pack.cells.g[b.cell]];
// prettier-ignore
const weights = [
[
[10.782752257744338, 2.7100404240962126], [-2.8226802110591462, 51.62920138583541], [-6.6250956268643835, 4.427939197315455], [-59.64690518541339, 41.89084162654791], [-1.3302059550553835, -3.6964487738450913],
[-2.5844898544535497, 0.09879268612455298], [-5.58528252533573, -0.23426224364501905], [26.94531337690372, 20.898158905988907], [3.816397481634785, -0.19045424064580757], [-4.835697931609101, -10.748232783636434]
],
[
[-2.478952081870123, 0.6405800134306895, -7.136785640930911, -0.2186529024764509, 3.6568435212735424, 31.446026153530838, -19.91005187482281, 0.2543395274783306, -7.036924569659988, -0.7721371621651565],
[-197.10583739743538, 6.889921141533474, 0.5058941504631129, 7.7667203434606416, -53.74180550086929, -15.717331715167001, -61.32068414155791, -2.259728220978728, 35.84049189540032, 94.6157364730977],
[-5.312011591880851, -0.09923148954215096, -1.7132477487917586, -22.55559652066422, 0.4806107280554336, -26.5583974109492, 2.0558257347014863, 25.815645234787432, -18.569029876991156, -2.6792003366730035],
[20.706518520569514, 18.344297403881875, 99.52244671131733, -58.53124969563653, -60.74384321042212, -80.57540534651835, 7.884792406540866, -144.33871131678563, 80.134199744324, 20.50745285622448],
[-52.88299538575159, -15.782505343805528, 16.63316001054924, 88.09475330556671, -17.619552086641818, -19.943999528182427, -120.46286026828177, 19.354752020806302, 43.49422099308949, 28.733924806541363],
[-2.4621368711159897, -1.2074759925679757, -1.5133898639835084, 2.173715352424188, -5.988707597991683, 3.0234147182203843, 3.3284199340000797, -1.8805161326360575, 5.151910934121654, -1.2540553911612116]
],
[
[-0.3357437479474717, 0.01430651794222215, -0.7927524256670906, 0.2121636229648523, 1.0587803023358318, -3.759288325505095],
[-1.1988028704442968, 1.3768997508052783, -3.8480086358278816, 0.5289387340947143, 0.5769459339961177, -1.2528318145750772],
[1.0074966649240946, 1.155301164699459, -2.974254371052421, 0.47408176553219467, 0.5939042688615264, -0.7631976947131744]
]
];
// From (-∞, ∞) to ~[-1, 1]
const In1 = [(Math.abs(lat) - 26.950680212887473) / 48.378128506956, (prec - 12.229929140832644) / 29.94402033696607];
let lastIn = In1;
let lstOut = [];
for (let levelN = 0; levelN < weights.length; levelN++) {
const layerN = weights[levelN];
for (let i = 0; i < layerN.length; i++) {
lstOut[i] = 0;
for (let j = 0; j < layerN[i].length; j++) {
lstOut[i] = lstOut[i] + lastIn[j] * layerN[i][j];
}
// sigmoid
lstOut[i] = 1 / (1 + Math.exp(-lstOut[i]));
}
lastIn = lstOut.slice(0);
}
// Standard deviation for average temperature for the year from [0, 1] to [min, max]
const yearSig = lstOut[0] * 62.9466411977018 + 0.28613807855649165;
// Standard deviation for the difference between the minimum and maximum temperatures for the year
const yearDelTmpSig = lstOut[1] * 13.541688670361175 + 0.1414213562373084 > yearSig ? yearSig : lstOut[1] * 13.541688670361175 + 0.1414213562373084;
// Expected value for the difference between the minimum and maximum temperatures for the year
const yearDelTmpMu = lstOut[2] * 15.266666666666667 + 0.6416666666666663;
// Temperature change shape
const delT = yearDelTmpMu / 2 + (0.5 * yearDelTmpSig) / 2;
const minT = burgTemp - Math.max(yearSig + delT, 15);
const maxT = burgTemp + (burgTemp - minT);
const chartWidth = Math.max(window.innerWidth / 2, 580);
const chartHeight = 300;
// drawing starting point from top-left (y = 0) of SVG
const xOffset = 60;
const yOffset = 10;
const year = new Date().getFullYear(); // use current year
const startDate = new Date(year, 0, 1);
const endDate = new Date(year, 11, 31);
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const xscale = d3.scaleTime().domain([startDate, endDate]).range([0, chartWidth]);
const yscale = d3.scaleLinear().domain([minT, maxT]).range([chartHeight, 0]);
const tempMean = [];
const tempMin = [];
const tempMax = [];
months.forEach((month, index) => {
const rate = index / 11;
let formTmp = Math.cos(rate * 2 * Math.PI) / 2;
if (lat > 0) formTmp = -formTmp;
const x = rate * chartWidth + xOffset;
const tempAverage = formTmp * yearSig + burgTemp;
const tempDelta = yearDelTmpMu / 2 + (formTmp * yearDelTmpSig) / 2;
tempMean.push([x, yscale(tempAverage) + yOffset]);
tempMin.push([x, yscale(tempAverage - tempDelta) + yOffset]);
tempMax.push([x, yscale(tempAverage + tempDelta) + yOffset]);
});
drawGraph();
$("#alert").dialog({title: "Annual temperature in " + b.name, width: "auto", position: {my: "center", at: "center", of: "svg"}});
function drawGraph() {
alertMessage.innerHTML = "";
const getCurve = data => round(d3.line().curve(d3.curveBasis)(data), 2);
const legendSize = 60;
const chart = d3
.select("#alertMessage")
.append("svg")
.attr("width", chartWidth + 120)
.attr("height", chartHeight + yOffset + legendSize);
const legend = chart.append("g");
const legendY = chartHeight + yOffset + legendSize * 0.8;
const legendX = n => (chartWidth * n) / 4;
const legendTextX = n => legendX(n) + 10;
legend.append("circle").attr("cx", legendX(1)).attr("cy", legendY).attr("r", 4).style("fill", "red");
legend.append("text").attr("x", legendTextX(1)).attr("y", legendY).attr("alignment-baseline", "central").text("Day temperature");
legend.append("circle").attr("cx", legendX(2)).attr("cy", legendY).attr("r", 4).style("fill", "orange");
legend.append("text").attr("x", legendTextX(2)).attr("y", legendY).attr("alignment-baseline", "central").text("Mean temperature");
legend.append("circle").attr("cx", legendX(3)).attr("cy", legendY).attr("r", 4).style("fill", "blue");
legend.append("text").attr("x", legendTextX(3)).attr("y", legendY).attr("alignment-baseline", "central").text("Night temperature");
const xGrid = d3.axisBottom(xscale).ticks().tickSize(-chartHeight);
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth);
const grid = chart.append("g").attr("class", "epgrid").attr("stroke-dasharray", "4 1");
grid.append("g").attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`).call(xGrid); // prettier-ignore
grid.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yGrid);
grid.selectAll("text").remove();
// add zero degree line
if (minT < 0 && maxT > 0) {
grid
.append("line")
.attr("x1", xOffset)
.attr("y1", yscale(0) + yOffset)
.attr("x2", chartWidth + xOffset)
.attr("y2", yscale(0) + yOffset)
.attr("stroke", "gray");
}
const xAxis = d3.axisBottom(xscale).ticks().tickFormat(d3.timeFormat("%B"));
const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(convertTemperature);
const axis = chart.append("g");
axis
.append("g")
.attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`)
.call(xAxis);
axis.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yAxis);
axis.select("path.domain").attr("d", `M0.5,0.5 H${chartWidth + 0.5}`);
const curves = chart.append("g").attr("fill", "none").style("stroke-width", 2.5);
curves.append("path").attr("d", getCurve(tempMean)).attr("data-type", "daily").attr("stroke", "orange").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMin)).attr("data-type", "night").attr("stroke", "blue").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMax)).attr("data-type", "day").attr("stroke", "red").on("mousemove", printVal);
function printVal() {
const [x, y] = d3.mouse(this);
const type = this.getAttribute("data-type");
const temp = convertTemperature(yscale.invert(y - yOffset));
const month = months[rn(((x - xOffset) / chartWidth) * 12)] || months[0];
tip(`Average ${type} temperature in ${month}: ${temp}`);
}
}
}

View file

@ -5,7 +5,7 @@
function convertTemperature(temp) {
switch (temperatureScale.value) {
case "°C":
return temp + "°C";
return rn(temp) + "°C";
case "°F":
return rn((temp * 9) / 5 + 32) + "°F";
case "K":
@ -21,7 +21,7 @@ function convertTemperature(temp) {
case "°Rø":
return rn((temp * 21) / 40 + 7.5) + "°Rø";
default:
return temp + "°C";
return rn(temp) + "°C";
}
}