Ocean heightmap and Scale bar styling change [v1.96] (#1045)

* Scale bar styling (#1025)

* feat: style scale bar

* feat: style scale bar - style presets

---------

Co-authored-by: Azgaar <azgaar.fmg@yandex.com>

* Ocean heightmap to v1.96 (#1044)

* feat: allow to render ocean heightmap

* feat: allow to render ocean heightmap - test

* feat: allow to render ocean heightmap - fix issue

* feat: allow to render ocean heightmap - cleanup

---------

Co-authored-by: Azgaar <azgaar.fmg@yandex.com>

* fix: scale bar size

* fix: remove mask on terrs lavel

* fix: regenerate heigtmap preview to use current graph size

* Add the name of culture and namesbase in the name editor dialog (#1033)

* Add the name of culture and namesbase in the name editor dialog

Added the name of the culture and  namesbase in the dialog "name editor".
This tells information on the "click to generate a culture-specific name"
It tells you the culture before changing name.

* cultureName into cultureId + cultureName

And deleted the incomplete code of showing culture name on datatip

* refactor: leave culture name only

---------

Co-authored-by: Azgaar <azgaar.fmg@yandex.com>

* Added Burgs column to province editor (#1031)

* Added Burgs column to province editor

Added to province editor:
+ Burgs column
+ the number of Burgs, p.burgs.length
+ "icon-dot-circled" to go to overviewBurgs.
+ overviewBurgs Filtered by state id.
+ Fixed some typos.

* fixed code as Azgaar suggested

+ Corrected provincesHeader distance in em.
+ const stateId = pack.provinces[p].state;
- Deleted cell count.

* deleted HTML code for provincesFooter cells

- Deleted Total land cells number HTML from provincesFooter.

* deleting totalCells in the code, maybe i will add provinceCells in the future.

Deleted lines for const totalCells and for (+cells / totalCells) * 100 + "%";

* refactor: cleanup

* refactor: cleanup

---------

Co-authored-by: Azgaar <azgaar.fmg@yandex.com>

* fix: burgs overview - add MFCG link back

* feat: add more details to burgs export

* feat: don't show auto-update dialog

* feat: pump version

* fix: #1041

* feat: update style presets

---------

Co-authored-by: Azgaar <azgaar.fmg@yandex.com>
Co-authored-by: Ángel Montero Lamas <angel.montero1@gmail.com>
This commit is contained in:
Azgaar 2024-02-24 19:12:48 +04:00 committed by GitHub
parent 845dc893d2
commit 374c21b3d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 1304 additions and 608 deletions

View file

@ -1876,12 +1876,6 @@ div.editorLine {
margin: 0.4em 0 0 -0.9em; margin: 0.4em 0 0 -0.9em;
} }
#barBackColor {
width: 3.5em;
padding: 0px;
height: 1.2em;
}
#ruler { #ruler {
cursor: move; cursor: move;
fill: none; fill: none;
@ -1921,18 +1915,6 @@ div.editorLine {
stroke: #737373; stroke: #737373;
} }
#scaleBar {
stroke: none;
fill: none;
cursor: pointer;
}
#scaleBar text {
fill: #353540;
text-anchor: middle;
font-family: var(--serif);
}
#militaryOptionsTable select { #militaryOptionsTable select {
border: 1px solid #d4d4d4; border: 1px solid #d4d4d4;
} }

View file

@ -138,7 +138,7 @@
} }
</style> </style>
<link rel="preload" href="index.css?v=1.95.00" as="style" onload="this.onload=null; this.rel='stylesheet'" /> <link rel="preload" href="index.css?v=1.96.00" 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 rel="preload" href="icons.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
<link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" /> <link rel="preload" href="libs/jquery-ui.css" as="style" onload="this.onload=null; this.rel='stylesheet'" />
</head> </head>
@ -366,7 +366,9 @@
</mask> </mask>
</defs> </defs>
<g id="viewbox"></g> <g id="viewbox"></g>
<g id="scaleBar"></g> <g id="scaleBar">
<rect id="scaleBarBack"></rect>
</g>
<g id="vignette" mask="url(#vignette-mask)"> <g id="vignette" mask="url(#vignette-mask)">
<rect x="0" y="0" width="100%" height="100%" /> <rect x="0" y="0" width="100%" height="100%" />
</g> </g>
@ -799,6 +801,7 @@
<option value="rivers">Rivers</option> <option value="rivers">Rivers</option>
<option value="routes">Routes</option> <option value="routes">Routes</option>
<option value="ruler">Rulers</option> <option value="ruler">Rulers</option>
<option value="scaleBar">Scale Bar</option>
<option value="regions" selected>States</option> <option value="regions" selected>States</option>
<option value="temperature">Temperature</option> <option value="temperature">Temperature</option>
<option value="texture">Texture</option> <option value="texture">Texture</option>
@ -824,6 +827,63 @@
</tr> </tr>
</tbody> </tbody>
<tbody id="styleHeightmap">
<tr id="styleHeightmapRenderOceanOption" data-tip="Check to render ocean heights">
<td colspan="2">
<input id="styleHeightmapRenderOcean" class="checkbox" type="checkbox" />
<label for="styleHeightmapRenderOcean" class="checkbox-label">Render ocean heights</label>
</td>
</tr>
<tr data-tip="Terracing rate. Set to 0 (toggle off) to improve performance">
<td>Terracing</td>
<td>
<input id="styleHeightmapTerracingInput" type="range" min="0" max="20" step="1" />
<output id="styleHeightmapTerracingOutput">0</output>
</td>
</tr>
<tr data-tip="Layers reduction rate. Increase to improve performance">
<td>Reduce layers</td>
<td>
<input id="styleHeightmapSkipInput" type="range" min="0" max="10" step="1" value="5" />
<output id="styleHeightmapSkipOutput">5</output>
</td>
</tr>
<tr data-tip="Line simplification rate. Increase to slightly improve performance">
<td>Simplify line</td>
<td>
<input id="styleHeightmapSimplificationInput" type="range" min="0" max="10" step="1" value="0" />
<output id="styleHeightmapSimplificationOutput">0</output>
</td>
</tr>
<tr data-tip="Select line interpolation type">
<td>Line style</td>
<td>
<select id="styleHeightmapCurve">
<option value="curveBasisClosed" selected>Curved</option>
<option value="curveLinear">Linear</option>
<option value="curveStep">Rectangular</option>
</select>
</td>
</tr>
<tr data-tip="Select color scheme for the element">
<td>Color scheme</td>
<td>
<select id="styleHeightmapScheme"></select>
<button
id="openCreateHeightmapSchemeButton"
data-tip="Click to add a custom heightmap color scheme"
data-stops="#ffffff,#EEEECC,#D2B48C,#008000,#008080"
class="icon-plus sideButton"
></button>
</td>
</tr>
</tbody>
<tbody id="styleOpacity" style="display: none"> <tbody id="styleOpacity" style="display: none">
<tr data-tip="Set opacity. 0: transparent, 1: solid"> <tr data-tip="Set opacity. 0: transparent, 1: solid">
<td>Opacity</td> <td>Opacity</td>
@ -1281,56 +1341,6 @@
</tr> </tr>
</tbody> </tbody>
<tbody id="styleHeightmap">
<tr data-tip="Terracing rate. Set to 0 (toggle off) to improve performance">
<td>Terracing</td>
<td>
<input id="styleHeightmapTerracingInput" type="range" min="0" max="20" step="1" />
<output id="styleHeightmapTerracingOutput">0</output>
</td>
</tr>
<tr data-tip="Layers reduction rate. Increase to improve performance">
<td>Reduce layers</td>
<td>
<input id="styleHeightmapSkipInput" type="range" min="0" max="10" step="1" value="5" />
<output id="styleHeightmapSkipOutput">5</output>
</td>
</tr>
<tr data-tip="Line simplification rate. Increase to slightly improve performance">
<td>Simplify line</td>
<td>
<input id="styleHeightmapSimplificationInput" type="range" min="0" max="10" step="1" value="0" />
<output id="styleHeightmapSimplificationOutput">0</output>
</td>
</tr>
<tr data-tip="Select line interpolation type">
<td>Line style</td>
<td>
<select id="styleHeightmapCurve">
<option value="0" selected>Curved</option>
<option value="1">Linear</option>
<option value="2">Rectangular</option>
</select>
</td>
</tr>
<tr data-tip="Select color scheme for the element">
<td>Color scheme</td>
<td>
<select id="styleHeightmapScheme"></select>
<button
id="openCreateHeightmapSchemeButton"
data-tip="Click to add a custom heightmap color scheme"
data-stops="#ffffff,#EEEECC,#D2B48C,#008000,#008080"
class="icon-plus sideButton"
></button>
</td>
</tr>
</tbody>
<tbody id="styleArmies"> <tbody id="styleArmies">
<tr data-tip="Set fill transparency. Set to 0 to make it fully transparent"> <tr data-tip="Set fill transparency. Set to 0 to make it fully transparent">
<td>Fill opacity</td> <td>Fill opacity</td>
@ -1446,6 +1456,84 @@
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tbody id="styleScaleBar">
<tr data-tip="Set bar and font size">
<td>Size</td>
<td>
<span>Bar </span>
<input id="styleScaleBarSize" type="number" min=".5" max="5" step=".1" />
<span>Font </span>
<input id="styleScaleBarFontSize" type="number" min="1" max="100" step=".1" />
</td>
</tr>
<tr data-tip="Set position of the Scale bar bottom right corner (in percents)">
<td>Position</td>
<td>
<span>x </span>
<input id="styleScaleBarPositionX" type="number" min="0" max="100" step="0.1" style="width: 5em" />
<span>y </span>
<input id="styleScaleBarPositionY" type="number" min="0" max="100" step="0.1" style="width: 5em" />
</td>
</tr>
<tr data-tip="Type scale bar label, leave blank to hide label">
<td>Label</td>
<td>
<input id="styleScaleBarLabel" type="text" />
</td>
</tr>
<tr data-tip="Set background opacity. 0: transparent, 1: solid">
<td>Back opacity</td>
<td>
<input id="styleScaleBarBackgroundOpacityInput" type="range" min="0" max="1" step="0.01" />
<output id="styleScaleBarBackgroundOpacityOutput"></output>
</td>
</tr>
<tr data-tip="Set background fill color">
<td>Back fill</td>
<td>
<input id="styleScaleBarBackgroundFillInput" type="color" />
<output id="styleScaleBarBackgroundFillOutput"></output>
</td>
</tr>
<tr data-tip="Set background stroke color and width">
<td>Back stroke</td>
<td>
<input id="styleScaleBarBackgroundStrokeInput" type="color" />
<output id="styleScaleBarBackgroundStrokeOutput"></output>
<span>Width </span>
<input
id="styleScaleBarBackgroundStrokeWidth"
type="number"
min="0"
max="10"
step="0.1"
style="width: 5em"
/>
</td>
</tr>
<tr data-tip="Set background element padding: top, right, bottom, left (in pixels)">
<td>Back padding</td>
<td style="display: flex; gap: 4px">
<input id="styleScaleBarBackgroundPaddingTop" type="number" min="0" max="100" style="width: 5em" />
<input id="styleScaleBarBackgroundPaddingRight" type="number" min="0" max="100" style="width: 5em" />
<input id="styleScaleBarBackgroundPaddingBottom" type="number" min="0" max="100" style="width: 5em" />
<input id="styleScaleBarBackgroundPaddingLeft" type="number" min="0" max="100" style="width: 5em" />
</td>
</tr>
<tr data-tip="Select background filter">
<td>Back filter</td>
<td><select id="styleScaleBarBackgroundFilter" /></td>
</tr>
</tbody>
</table> </table>
<div id="mapFilters" data-tip="Set a filter to be applied to the map in general"> <div id="mapFilters" data-tip="Set a filter to be applied to the map in general">
@ -4450,7 +4538,7 @@
</div> </div>
<div id="provincesEditor" class="dialog stable" style="display: none"> <div id="provincesEditor" class="dialog stable" style="display: none">
<div id="provincesHeader" class="header" style="grid-template-columns: 11em 8em 8em 8em 5em 8em"> <div id="provincesHeader" class="header" style="grid-template-columns: 11em 8em 8em 6em 6em 6em 8em">
<div data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="name"> <div data-tip="Click to sort by province name" class="sortable alphabetically" data-sortby="name">
Province&nbsp; Province&nbsp;
</div> </div>
@ -4463,6 +4551,9 @@
<div data-tip="Click to sort by province owner" class="sortable alphabetically" data-sortby="state"> <div data-tip="Click to sort by province owner" class="sortable alphabetically" data-sortby="state">
State&nbsp; State&nbsp;
</div> </div>
<div data-tip="Click to sort by province burgs count" class="sortable hide" data-sortby="burgs">
Burgs&nbsp;
</div>
<div data-tip="Click to sort by province area" class="sortable hide" data-sortby="area">Area&nbsp;</div> <div data-tip="Click to sort by province area" class="sortable hide" data-sortby="area">Area&nbsp;</div>
<div data-tip="Click to sort by province population" class="sortable hide" data-sortby="population"> <div data-tip="Click to sort by province population" class="sortable hide" data-sortby="population">
Population&nbsp; Population&nbsp;
@ -4475,6 +4566,9 @@
<div data-tip="Provinces displayed" style="margin-left: 4px"> <div data-tip="Provinces displayed" style="margin-left: 4px">
Provinces:&nbsp;<span id="provincesFooterNumber">0</span> Provinces:&nbsp;<span id="provincesFooterNumber">0</span>
</div> </div>
<div data-tip="Total burgs number" style="margin-left: 12px">
Burgs:&nbsp;<span id="provincesFooterBurgs">0</span>
</div>
<div data-tip="Average area" style="margin-left: 14px"> <div data-tip="Average area" style="margin-left: 14px">
Mean area:&nbsp;<span id="provincesFooterArea">0</span> Mean area:&nbsp;<span id="provincesFooterArea">0</span>
</div> </div>
@ -4612,7 +4706,7 @@
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span> <span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
<span <span
id="provinceNameEditorShortCulture" id="provinceNameEditorShortCulture"
data-tip="Generate culture-specific name" data-tip="Generate culture-specific name for the province"
class="icon-book pointer" class="icon-book pointer"
></span> ></span>
<span id="provinceNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span> <span id="provinceNameEditorShortRandom" data-tip="Generate random name" class="icon-globe pointer"></span>
@ -4688,6 +4782,14 @@
class="icon-arrows-cw pointer" class="icon-arrows-cw pointer"
></span> ></span>
</div> </div>
<div
id="provinceCultureName"
data-tip="Dominant culture in the province. This defines culture-based naming. Can be changed via the Cultures Editor"
style="margin-top: 0.2em"
>
Dominant culture:&nbsp;<span id="provinceCultureDisplay"></span>
</div>
</div> </div>
<div id="namesbaseEditor" class="dialog stable textual" style="display: none"> <div id="namesbaseEditor" class="dialog stable textual" style="display: none">
@ -5132,49 +5234,6 @@
</select> </select>
</div> </div>
<div class="unitsHeader">
<span class="icon-minus"></span>
<div>Scale bar:</div>
</div>
<div data-tip="Set scale bar size">
<div>Bar size:</div>
<input id="barSizeOutput" data-stored="barSize" type="range" min=".5" max="5" value="2" step=".1" />
<input id="barSizeInput" data-stored="barSize" type="number" min=".5" max="5" value="2" step=".1" />
</div>
<div data-tip="Type scale bar label, leave blank to hide label">
<div>Bar label:</div>
<input id="barLabel" data-stored="barLabel" type="text" placeholder="hidden" value="" />
</div>
<div data-tip="Set background for Scale bar">
<div>Bar background:</div>
<input
id="barBackOpacity"
data-stored="barBackOpacity"
type="range"
min="0"
max="1"
value=".2"
step=".01"
/>
<input id="barBackColor" data-stored="barBackColor" type="color" value="#ffffff" />
</div>
<div data-tip="Set position of the Scale bar bottom right corner in percents">
<div>Bar position:</div>
x:<input id="barPosX" data-stored="barPosX" type="number" min="0" max="100" step=".1" value="99" /> y:<input
id="barPosY"
data-stored="barPosY"
type="number"
min="0"
max="100"
step=".1"
value="99"
/>
</div>
<div class="unitsHeader"> <div class="unitsHeader">
<span class="icon-male"></span> <span class="icon-male"></span>
<div>Population:</div> <div>Population:</div>
@ -7963,7 +8022,7 @@
<script src="utils/commonUtils.js?v=1.89.29"></script> <script src="utils/commonUtils.js?v=1.89.29"></script>
<script src="utils/arrayUtils.js"></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=1.93.12"></script> <script src="utils/graphUtils.js?v=1.96.00"></script>
<script src="utils/nodeUtils.js"></script> <script src="utils/nodeUtils.js"></script>
<script src="utils/numberUtils.js?v=1.89.08"></script> <script src="utils/numberUtils.js?v=1.89.08"></script>
<script src="utils/polyfills.js?v=1.95.03"></script> <script src="utils/polyfills.js?v=1.95.03"></script>
@ -7976,39 +8035,39 @@
<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=1.88.00"></script> <script src="modules/heightmap-generator.js?v=1.88.00"></script>
<script src="modules/ocean-layers.js?v=1.95.00"></script> <script src="modules/ocean-layers.js?v=1.96.00"></script>
<script src="modules/river-generator.js?v=1.89.13"></script> <script src="modules/river-generator.js?v=1.89.13"></script>
<script src="modules/lakes.js"></script> <script src="modules/lakes.js"></script>
<script src="modules/biomes.js"></script> <script src="modules/biomes.js"></script>
<script src="modules/names-generator.js?v=1.87.14"></script> <script src="modules/names-generator.js?v=1.87.14"></script>
<script src="modules/cultures-generator.js?v=1.89.10"></script> <script src="modules/cultures-generator.js?v=1.96.00"></script>
<script src="modules/renderers/state-labels.js"></script> <script src="modules/renderers/state-labels.js"></script>
<script src="modules/burgs-and-states.js?v=1.92.00"></script> <script src="modules/burgs-and-states.js?v=1.92.00"></script>
<script src="modules/routes-generator.js"></script> <script src="modules/routes-generator.js"></script>
<script src="modules/religions-generator.js?v=1.93.08"></script> <script src="modules/religions-generator.js?v=1.93.08"></script>
<script src="modules/military-generator.js"></script> <script src="modules/military-generator.js?v=1.96.00"></script>
<script src="modules/markers-generator.js?v=1.93.04"></script> <script src="modules/markers-generator.js?v=1.93.04"></script>
<script src="modules/coa-generator.js?v=1.91.05"></script> <script src="modules/coa-generator.js?v=1.91.05"></script>
<script src="modules/submap.js?v=1.94.01"></script> <script src="modules/submap.js?v=1.96.00"></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=1.89.18"></script> <script src="modules/fonts.js?v=1.89.18"></script>
<script src="modules/ui/layers.js?v=1.94.00"></script> <script src="modules/ui/layers.js?v=1.96.00"></script>
<script src="modules/ui/measurers.js?v=1.94.03"></script> <script src="modules/ui/measurers.js?v=1.96.00"></script>
<script src="modules/ui/stylePresets.js?v=1.95.00"></script> <script src="modules/ui/stylePresets.js?v=1.96.00"></script>
<script src="modules/ui/general.js?v=1.94.01"></script> <script src="modules/ui/general.js?v=1.96.00"></script>
<script src="modules/ui/options.js?v=1.94.06"></script> <script src="modules/ui/options.js?v=1.96.00"></script>
<script src="main.js?v=1.94.05"></script> <script src="main.js?v=1.96.00"></script>
<script defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js?v=1.95.00"></script> <script defer src="modules/ui/style.js?v=1.96.00"></script>
<script defer src="modules/ui/editors.js?v=1.95.04"></script> <script defer src="modules/ui/editors.js?v=1.96.00"></script>
<script defer src="modules/ui/tools.js?v=1.95.01"></script> <script defer src="modules/ui/tools.js?v=1.95.01"></script>
<script defer src="modules/ui/world-configurator.js?v=1.91.05"></script> <script defer src="modules/ui/world-configurator.js?v=1.91.05"></script>
<script defer src="modules/ui/heightmap-editor.js?v=1.93.00"></script> <script defer src="modules/ui/heightmap-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/provinces-editor.js?v=1.92.00"></script> <script defer src="modules/ui/provinces-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/biomes-editor.js?v=1.91.05"></script> <script defer src="modules/ui/biomes-editor.js?v=1.91.05"></script>
<script defer src="modules/ui/namesbase-editor.js?v=1.95.02"></script> <script defer src="modules/ui/namesbase-editor.js?v=1.95.02"></script>
<script defer src="modules/ui/elevation-profile.js"></script> <script defer src="modules/ui/elevation-profile.js"></script>
@ -8021,12 +8080,12 @@
<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?v=1.89.13"></script> <script defer src="modules/ui/rivers-creator.js?v=1.89.13"></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?v=1.96.00"></script>
<script defer src="modules/ui/units-editor.js?v=1.94.02"></script> <script defer src="modules/ui/units-editor.js?v=1.96.00"></script>
<script defer src="modules/ui/notes-editor.js?v=1.93.09"></script> <script defer src="modules/ui/notes-editor.js?v=1.93.09"></script>
<script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script> <script defer src="modules/ui/diplomacy-editor.js?v=1.88.04"></script>
<script defer src="modules/ui/zones-editor.js"></script> <script defer src="modules/ui/zones-editor.js"></script>
<script defer src="modules/ui/burgs-overview.js?v=1.93.10"></script> <script defer src="modules/ui/burgs-overview.js?v=1.96.00"></script>
<script defer src="modules/ui/rivers-overview.js"></script> <script defer src="modules/ui/rivers-overview.js"></script>
<script defer src="modules/ui/military-overview.js"></script> <script defer src="modules/ui/military-overview.js"></script>
<script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script> <script defer src="modules/ui/regiments-overview.js?v=1.89.20"></script>
@ -8036,15 +8095,15 @@
<script defer src="modules/ui/emblems-editor.js?v=1.91.00"></script> <script defer src="modules/ui/emblems-editor.js?v=1.91.00"></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?v=1.94.03"></script> <script defer src="modules/ui/3d.js?v=1.94.03"></script>
<script defer src="modules/ui/submap.js?v=1.94.03"></script> <script defer src="modules/ui/submap.js?v=1.96.00"></script>
<script defer src="modules/ui/hotkeys.js?v=1.95.00"></script> <script defer src="modules/ui/hotkeys.js?v=1.95.00"></script>
<script defer src="modules/coa-renderer.js?v=1.94.00"></script> <script defer src="modules/coa-renderer.js?v=1.94.00"></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=1.93.02"></script> <script defer src="modules/io/save.js?v=1.96.00"></script>
<script defer src="modules/io/load.js?v=1.95.00"></script> <script defer src="modules/io/load.js?v=1.96.00"></script>
<script defer src="modules/io/cloud.js?v=1.94.04"></script> <script defer src="modules/io/cloud.js?v=1.96.00"></script>
<script defer src="modules/io/export.js?v=1.95.05"></script> <script defer src="modules/io/export.js?v=1.96.00"></script>
<script defer src="modules/io/formats.js"></script> <script defer src="modules/io/formats.js"></script>
<!-- Web Components --> <!-- Web Components -->

View file

@ -1 +1 @@
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).polylabel=t()}}(function(){return function t(n,e,r){function o(a,u){if(!e[a]){if(!n[a]){var f="function"==typeof require&&require;if(!u&&f)return f(a,!0);if(i)return i(a,!0);var h=new Error("Cannot find module '"+a+"'");throw h.code="MODULE_NOT_FOUND",h}var s=e[a]={exports:{}};n[a][0].call(s.exports,function(t){var e=n[a][1][t];return o(e||t)},s,s.exports,t,n,e,r)}return e[a].exports}for(var i="function"==typeof require&&require,a=0;a<r.length;a++)o(r[a]);return o}({1:[function(t,n,e){"use strict";var r=t("tinyqueue");function o(t,n){return n.max-t.max}function i(t,n,e,r){this.x=t,this.y=n,this.h=e,this.d=function(t,n,e){for(var r=!1,o=1/0,i=0;i<e.length;i++)for(var u=e[i],f=0,h=u.length,s=h-1;f<h;s=f++){var d=u[f],l=u[s];d[1]>n!=l[1]>n&&t<(l[0]-d[0])*(n-d[1])/(l[1]-d[1])+d[0]&&(r=!r),o=Math.min(o,a(t,n,d,l))}return(r?1:-1)*Math.sqrt(o)}(t,n,r),this.max=this.d+this.h*Math.SQRT2}function a(t,n,e,r){var o=e[0],i=e[1],a=r[0]-o,u=r[1]-i;if(0!==a||0!==u){var f=((t-o)*a+(n-i)*u)/(a*a+u*u);f>1?(o=r[0],i=r[1]):f>0&&(o+=a*f,i+=u*f)}return(a=t-o)*a+(u=n-i)*u}n.exports=function(t,n,e){var a,u,f,h;n=n||1;for(var s=0;s<t[0].length;s++){var d=t[0][s];(!s||d[0]<a)&&(a=d[0]),(!s||d[1]<u)&&(u=d[1]),(!s||d[0]>f)&&(f=d[0]),(!s||d[1]>h)&&(h=d[1])}for(var l=f-a,p=h-u,c=Math.min(l,p),v=c/2,g=new r(null,o),x=a;x<f;x+=c)for(var y=u;y<h;y+=c)g.push(new i(x+v,y+v,v,t));var w=function(t){for(var n=0,e=0,r=0,o=t[0],a=0,u=o.length,f=u-1;a<u;f=a++){var h=o[a],s=o[f],d=h[0]*s[1]-s[0]*h[1];e+=(h[0]+s[0])*d,r+=(h[1]+s[1])*d,n+=3*d}return new i(e/n,r/n,0,t)}(t),m=g.length;for(;g.length;){var b=g.pop();b.d>w.d&&(w=b,e&&console.log("found best %d after %d probes",Math.round(1e4*b.d)/1e4,m)),b.max-w.d<=n||(v=b.h/2,g.push(new i(b.x-v,b.y-v,v,t)),g.push(new i(b.x+v,b.y-v,v,t)),g.push(new i(b.x-v,b.y+v,v,t)),g.push(new i(b.x+v,b.y+v,v,t)),m+=4)}e&&(console.log("num probes: "+m),console.log("best distance: "+w.d));return[w.x,w.y]}},{tinyqueue:2}],2:[function(t,n,e){"use strict";function r(t,n){if(!(this instanceof r))return new r(t,n);if(this.data=t||[],this.length=this.data.length,this.compare=n||o,t)for(var e=Math.floor(this.length/2);e>=0;e--)this._down(e)}function o(t,n){return t<n?-1:t>n?1:0}function i(t,n,e){var r=t[n];t[n]=t[e],t[e]=r}n.exports=r,r.prototype={push:function(t){this.data.push(t),this.length++,this._up(this.length-1)},pop:function(){var t=this.data[0];return this.data[0]=this.data[this.length-1],this.length--,this.data.pop(),this._down(0),t},peek:function(){return this.data[0]},_up:function(t){for(var n=this.data,e=this.compare;t>0;){var r=Math.floor((t-1)/2);if(!(e(n[t],n[r])<0))break;i(n,r,t),t=r}},_down:function(t){for(var n=this.data,e=this.compare,r=this.length;;){var o=2*t+1,a=o+1,u=t;if(o<r&&e(n[o],n[u])<0&&(u=o),a<r&&e(n[a],n[u])<0&&(u=a),u===t)return;i(n,u,t),t=u}}}},{}]},{},[1])(1)}); !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).polylabel=t()}}(function(){return function t(n,e,r){function o(a,u){if(!e[a]){if(!n[a]){var f="function"==typeof require&&require;if(!u&&f)return f(a,!0);if(i)return i(a,!0);var h=new Error("Cannot find module '"+a+"'");throw h.code="MODULE_NOT_FOUND",h}var s=e[a]={exports:{}};n[a][0].call(s.exports,function(t){var e=n[a][1][t];return o(e||t)},s,s.exports,t,n,e,r)}return e[a].exports}for(var i="function"==typeof require&&require,a=0;a<r.length;a++)o(r[a]);return o}({1:[function(t,n,e){"use strict";var r=t("tinyqueue");function o(t,n){return n.max-t.max}function i(t,n,e,r){this.x=t,this.y=n,this.h=e,this.d=function(t,n,e){for(var r=!1,o=1/0,i=0;i<e.length;i++)for(var u=e[i],f=0,h=u.length,s=h-1;f<h;s=f++){var d=u[f],l=u[s];d[1]>n!=l[1]>n&&t<(l[0]-d[0])*(n-d[1])/(l[1]-d[1])+d[0]&&(r=!r),o=Math.min(o,a(t,n,d,l))}return(r?1:-1)*Math.sqrt(o)}(t,n,r),this.max=this.d+this.h*Math.SQRT2}function a(t,n,e,r){var o=e[0],i=e[1],a=r[0]-o,u=r[1]-i;if(0!==a||0!==u){var f=((t-o)*a+(n-i)*u)/(a*a+u*u);f>1?(o=r[0],i=r[1]):f>0&&(o+=a*f,i+=u*f)}return(a=t-o)*a+(u=n-i)*u}n.exports=function(t,n,e){var a,u,f,h;n=n||1;for(var s=0;s<t[0].length;s++){var d=t[0][s];(!s||d[0]<a)&&(a=d[0]),(!s||d[1]<u)&&(u=d[1]),(!s||d[0]>f)&&(f=d[0]),(!s||d[1]>h)&&(h=d[1])}for(var l=f-a,p=h-u,c=Math.min(l,p),v=c/2,g=new r(null,o),x=a;x<f;x+=c)for(var y=u;y<h;y+=c)g.push(new i(x+v,y+v,v,t));var w=function(t){for(var n=0,e=0,r=0,o=t[0],a=0,u=o.length,f=u-1;a<u;f=a++){var h=o[a],s=o[f],d=h[0]*s[1]-s[0]*h[1];e+=(h[0]+s[0])*d,r+=(h[1]+s[1])*d,n+=3*d}return new i(e/n,r/n,0,t)}(t),m=g.length;for(;g.length;){var b=g.pop();b.d>w.d&&(w=b,e&&console.info("found best %d after %d probes",Math.round(1e4*b.d)/1e4,m)),b.max-w.d<=n||(v=b.h/2,g.push(new i(b.x-v,b.y-v,v,t)),g.push(new i(b.x+v,b.y-v,v,t)),g.push(new i(b.x-v,b.y+v,v,t)),g.push(new i(b.x+v,b.y+v,v,t)),m+=4)}e&&(console.info("num probes: "+m),console.info("best distance: "+w.d));return[w.x,w.y]}},{tinyqueue:2}],2:[function(t,n,e){"use strict";function r(t,n){if(!(this instanceof r))return new r(t,n);if(this.data=t||[],this.length=this.data.length,this.compare=n||o,t)for(var e=Math.floor(this.length/2);e>=0;e--)this._down(e)}function o(t,n){return t<n?-1:t>n?1:0}function i(t,n,e){var r=t[n];t[n]=t[e],t[e]=r}n.exports=r,r.prototype={push:function(t){this.data.push(t),this.length++,this._up(this.length-1)},pop:function(){var t=this.data[0];return this.data[0]=this.data[this.length-1],this.length--,this.data.pop(),this._down(0),t},peek:function(){return this.data[0]},_up:function(t){for(var n=this.data,e=this.compare;t>0;){var r=Math.floor((t-1)/2);if(!(e(n[t],n[r])<0))break;i(n,r,t),t=r}},_down:function(t){for(var n=this.data,e=this.compare,r=this.length;;){var o=2*t+1,a=o+1,u=t;if(o<r&&e(n[o],n[u])<0&&(u=o),a<r&&e(n[a],n[u])<0&&(u=a),u===t)return;i(n,u,t),t=u}}}},{}]},{},[1])(1)});

2
libs/three.min.js vendored

File diff suppressed because one or more lines are too long

53
main.js
View file

@ -92,16 +92,19 @@ let fogging = viewbox
let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none"); let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none");
let debug = viewbox.append("g").attr("id", "debug"); let debug = viewbox.append("g").attr("id", "debug");
// lake and coast groups
lakes.append("g").attr("id", "freshwater"); lakes.append("g").attr("id", "freshwater");
lakes.append("g").attr("id", "salt"); lakes.append("g").attr("id", "salt");
lakes.append("g").attr("id", "sinkhole"); lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen"); lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava"); lakes.append("g").attr("id", "lava");
lakes.append("g").attr("id", "dry"); lakes.append("g").attr("id", "dry");
coastline.append("g").attr("id", "sea_island"); coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_island"); coastline.append("g").attr("id", "lake_island");
terrs.append("g").attr("id", "oceanHeights");
terrs.append("g").attr("id", "landHeights");
labels.append("g").attr("id", "states"); labels.append("g").attr("id", "states");
labels.append("g").attr("id", "addedLabels"); labels.append("g").attr("id", "addedLabels");
@ -841,8 +844,8 @@ function openNearSeaLakes() {
const LIMIT = 22; // max height that can be breached by water const LIMIT = 22; // max height that can be breached by water
for (const i of cells.i) { for (const i of cells.i) {
const lake = cells.f[i]; const lakeFeatureId = cells.f[i];
if (features[lake].type !== "lake") continue; // not a lake cell if (features[lakeFeatureId].type !== "lake") continue; // not a lake
check_neighbours: for (const c of cells.c[i]) { check_neighbours: for (const c of cells.c[i]) {
if (cells.t[c] !== 1 || cells.h[c] > LIMIT) continue; // water cannot break this if (cells.t[c] !== 1 || cells.h[c] > LIMIT) continue; // water cannot break this
@ -850,20 +853,24 @@ function openNearSeaLakes() {
for (const n of cells.c[c]) { for (const n of cells.c[c]) {
const ocean = cells.f[n]; const ocean = cells.f[n];
if (features[ocean].type !== "ocean") continue; // not an ocean if (features[ocean].type !== "ocean") continue; // not an ocean
removeLake(c, lake, ocean); removeLake(c, lakeFeatureId, ocean);
break check_neighbours; break check_neighbours;
} }
} }
} }
function removeLake(threshold, lake, ocean) { function removeLake(thresholdCellId, lakeFeatureId, oceanFeatureId) {
cells.h[threshold] = 19; cells.h[thresholdCellId] = 19;
cells.t[threshold] = -1; cells.t[thresholdCellId] = -1;
cells.f[threshold] = ocean; cells.f[thresholdCellId] = oceanFeatureId;
cells.c[threshold].forEach(function (c) { cells.c[thresholdCellId].forEach(function (c) {
if (cells.h[c] >= 20) cells.t[c] = 1; // mark as coastline if (cells.h[c] >= 20) cells.t[c] = 1; // mark as coastline
}); });
features[lake].type = "ocean"; // mark former lake as ocean
cells.i.forEach(i => {
if (cells.f[i] === lakeFeatureId) cells.f[i] = oceanFeatureId;
});
features[lakeFeatureId].type = "ocean"; // mark former lake as ocean
} }
TIME && console.timeEnd("openLakes"); TIME && console.timeEnd("openLakes");
@ -1250,6 +1257,7 @@ function drawCoastline() {
features[f].vertices = vchain; features[f].vertices = vchain;
const path = round(lineGen(points)); const path = round(lineGen(points));
if (features[f].type === "lake") { if (features[f].type === "lake") {
landMask landMask
.append("path") .append("path")
@ -1347,22 +1355,14 @@ function drawCoastline() {
// Re-mark features (ocean, lakes, islands) // Re-mark features (ocean, lakes, islands)
function reMarkFeatures() { function reMarkFeatures() {
TIME && console.time("reMarkFeatures"); TIME && console.time("reMarkFeatures");
const cells = pack.cells, const cells = pack.cells;
features = (pack.features = [0]); const features = (pack.features = [0]);
cells.f = new Uint16Array(cells.i.length); // cell feature number cells.f = new Uint16Array(cells.i.length); // cell feature number
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast; cells.t = new Int8Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast;
cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell); cells.haven = cells.i.length < 65535 ? new Uint16Array(cells.i.length) : new Uint32Array(cells.i.length); // cell haven (opposite water cell);
cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells); cells.harbor = new Uint8Array(cells.i.length); // cell harbor (number of adjacent water cells);
const defineHaven = i => {
const water = cells.c[i].filter(c => cells.h[c] < 20);
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
cells.haven[i] = closest;
cells.harbor[i] = water.length;
};
if (!cells.i.length) return; // no cells -> there is nothing to do if (!cells.i.length) return; // no cells -> there is nothing to do
for (let i = 1, queue = [0]; queue[0] !== -1; i++) { for (let i = 1, queue = [0]; queue[0] !== -1; i++) {
const start = queue[0]; // first cell const start = queue[0]; // first cell
@ -1403,6 +1403,15 @@ function reMarkFeatures() {
// markupPackLand // markupPackLand
markup(pack.cells, 3, 1, 0); markup(pack.cells, 3, 1, 0);
function defineHaven(i) {
const water = cells.c[i].filter(c => cells.h[c] < 20);
const dist2 = water.map(c => (cells.p[i][0] - cells.p[c][0]) ** 2 + (cells.p[i][1] - cells.p[c][1]) ** 2);
const closest = water[dist2.indexOf(Math.min.apply(Math, dist2))];
cells.haven[i] = closest;
cells.harbor[i] = water.length;
}
function defineOceanGroup(number) { function defineOceanGroup(number) {
if (number > grid.cells.i.length / 25) return "ocean"; if (number > grid.cells.i.length / 25) return "ocean";
if (number > grid.cells.i.length / 100) return "sea"; if (number > grid.cells.i.length / 100) return "sea";
@ -1925,7 +1934,7 @@ function showStatistics() {
mapId = Date.now(); // unique map id is it's creation date number mapId = Date.now(); // unique map id is it's creation date number
mapHistory.push({seed, width: graphWidth, height: graphHeight, template: heightmap, created: mapId}); mapHistory.push({seed, width: graphWidth, height: graphHeight, template: heightmap, created: mapId});
INFO && console.log(stats); INFO && console.info(stats);
} }
const regenerateMap = debounce(async function (options) { const regenerateMap = debounce(async function (options) {

View file

@ -71,28 +71,31 @@ window.Cultures = (function () {
return; return;
} }
const cell = (c.center = placeCenter(c.sort ? c.sort : i => cells.s[i])); const sortingFn = c.sort ? c.sort : i => cells.s[i];
centers.add(cells.p[cell]); const center = placeCenter(sortingFn);
centers.add(cells.p[center]);
c.center = center;
c.i = newId; c.i = newId;
delete c.odd; delete c.odd;
delete c.sort; delete c.sort;
c.color = colors[i]; c.color = colors[i];
c.type = defineCultureType(cell); c.type = defineCultureType(center);
c.expansionism = defineCultureExpansionism(c.type); c.expansionism = defineCultureExpansionism(c.type);
c.origins = [0]; c.origins = [0];
c.code = abbreviate(c.name, codes); c.code = abbreviate(c.name, codes);
codes.push(c.code); codes.push(c.code);
cultureIds[cell] = newId; cultureIds[center] = newId;
if (emblemShape === "random") c.shield = getRandomShield(); if (emblemShape === "random") c.shield = getRandomShield();
}); });
cells.culture = cultureIds; cells.culture = cultureIds;
function placeCenter(v) { function placeCenter(sortingFn) {
let spacing = (graphWidth + graphHeight) / 2 / count; let spacing = (graphWidth + graphHeight) / 2 / count;
const MAX_ATTEMPTS = 100; const MAX_ATTEMPTS = 100;
const sorted = [...populated].sort((a, b) => v(b) - v(a)); const sorted = [...populated].sort((a, b) => sortingFn(b) - sortingFn(a));
const max = Math.floor(sorted.length / 2); const max = Math.floor(sorted.length / 2);
let cellId = 0; let cellId = 0;

View file

@ -736,4 +736,95 @@ export function resolveVersionConflicts(version) {
.style("display", "none"); .style("display", "none");
vignette.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); vignette.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
} }
if (version < 1.96) {
// v1.96 added ocean rendering for heightmap
terrs.selectAll("*").remove();
const opacity = terrs.attr("opacity");
const filter = terrs.attr("filter");
const scheme = terrs.attr("scheme");
const terracing = terrs.attr("terracing");
const skip = terrs.attr("skip");
const relax = terrs.attr("relax");
const curveTypes = {0: "curveBasisClosed", 1: "curveLinear", 2: "curveStep"};
const curve = curveTypes[terrs.attr("curve")] || "curveBasisClosed";
terrs
.attr("mask", null)
.attr("scheme", null)
.attr("terracing", null)
.attr("skip", null)
.attr("relax", null)
.attr("curve", null);
terrs
.append("g")
.attr("id", "oceanHeights")
.attr("data-render", 0)
.attr("opacity", opacity)
.attr("filter", filter)
.attr("scheme", scheme)
.attr("terracing", 0)
.attr("skip", 0)
.attr("relax", 1)
.attr("curve", curve);
terrs
.append("g")
.attr("id", "landHeights")
.attr("opacity", opacity)
.attr("scheme", scheme)
.attr("filter", filter)
.attr("terracing", terracing)
.attr("skip", skip)
.attr("relax", relax)
.attr("curve", curve)
.attr("mask", "url(#land)");
if (layerIsOn("toggleHeight")) drawHeightmap();
// v1.96.00 moved scaleBar options from units editor to style
d3.select("#scaleBar").remove();
scaleBar = svg
.insert("g", "#viewbox + *")
.attr("id", "scaleBar")
.attr("opacity", 1)
.attr("fill", "#353540")
.attr("data-bar-size", 2)
.attr("font-size", 10)
.attr("data-x", 99)
.attr("data-y", 99)
.attr("data-label", "");
scaleBar
.append("rect")
.attr("id", "scaleBarBack")
.attr("opacity", 0.2)
.attr("fill", "#ffffff")
.attr("stroke", "#000000")
.attr("stroke-width", 1)
.attr("filter", "url(#blur5)")
.attr("data-top", 20)
.attr("data-right", 15)
.attr("data-bottom", 15)
.attr("data-left", 10);
drawScaleBar(scaleBar, scale);
fitScaleBar(scaleBar, svgWidth, svgHeight);
if (!layerIsOn("toggleScaleBar")) scaleBar.style("display", "none");
// v1.96.00 changed coloring approach for regiments
armies.selectAll(":scope > g").each(function () {
const fill = this.getAttribute("fill");
if (!fill) return;
const darkerColor = d3.color(fill).darker().hex();
this.setAttribute("color", darkerColor);
this.querySelectorAll("g > rect:nth-child(2)").forEach(rect => {
rect.setAttribute("fill", "currentColor");
});
});
}
} }

View file

@ -434,13 +434,13 @@ function editStateName(state) {
modules.editStateName = true; modules.editStateName = true;
// add listeners // add listeners
byId("stateNameEditorShortCulture").on("click", regenerateShortNameCuture); byId("stateNameEditorShortCulture").on("click", regenerateShortNameCulture);
byId("stateNameEditorShortRandom").on("click", regenerateShortNameRandom); byId("stateNameEditorShortRandom").on("click", regenerateShortNameRandom);
byId("stateNameEditorAddForm").on("click", addCustomForm); byId("stateNameEditorAddForm").on("click", addCustomForm);
byId("stateNameEditorCustomForm").on("change", addCustomForm); byId("stateNameEditorCustomForm").on("change", addCustomForm);
byId("stateNameEditorFullRegenerate").on("click", regenerateFullName); byId("stateNameEditorFullRegenerate").on("click", regenerateFullName);
function regenerateShortNameCuture() { function regenerateShortNameCulture() {
const state = +stateNameEditor.dataset.state; const state = +stateNameEditor.dataset.state;
const culture = pack.states[state].culture; const culture = pack.states[state].culture;
const name = Names.getState(Names.getCultureShort(culture), culture); const name = Names.getState(Names.getCultureShort(culture), culture);
@ -1394,6 +1394,7 @@ function openStateMergeDialog() {
function mergeStates(statesToMerge, rulingStateId) { function mergeStates(statesToMerge, rulingStateId) {
const rulingState = pack.states[rulingStateId]; const rulingState = pack.states[rulingStateId];
const rulingStateArmy = byId("army" + rulingStateId);
// remove states to be merged // remove states to be merged
statesToMerge.forEach(stateId => { statesToMerge.forEach(stateId => {
@ -1410,27 +1411,25 @@ function openStateMergeDialog() {
emblems.select(`#stateEmblems > use[data-i='${stateId}']`).remove(); emblems.select(`#stateEmblems > use[data-i='${stateId}']`).remove();
// add merged state regiments to the ruling state // add merged state regiments to the ruling state
state.military.forEach(m => { state.military.forEach(regiment => {
const oldId = `regiment${stateId}-${m.i}`; const oldId = `regiment${stateId}-${regiment.i}`;
const newIndex = rulingState.military.length;
const newRegiment = {...m, i: rulingState.military.length}; rulingState.military.push({...regiment, i: newIndex});
rulingState.military.push(newRegiment); const newId = `regiment${rulingStateId}-${newIndex}`;
const newId = `regiment${rulingStateId}-${newRegiment.i}`;
const note = notes.find(n => n.id === oldId); const note = notes.find(n => n.id === oldId);
if (note) note.id = newId; if (note) note.id = newId;
const rulingStateArmy = armies.select("g#army" + rulingStateId); const element = byId(oldId);
armies if (element) {
.select("g#army" + stateId) element.id = newId;
.selectAll("g") element.dataset.state = rulingStateId;
.each(function () { element.dataset.i = newIndex;
this.setAttribute("id", newId); rulingStateArmy.appendChild(element);
rulingStateArmy.node().appendChild(this); }
});
armies.select("g#army" + stateId).remove();
}); });
armies.select("g#army" + stateId).remove();
}); });
// reassing burgs // reassing burgs

View file

@ -88,12 +88,6 @@ function getSettings() {
heightUnit: heightUnit.value, heightUnit: heightUnit.value,
heightExponent: heightExponentInput.value, heightExponent: heightExponentInput.value,
temperatureScale: temperatureScale.value, temperatureScale: temperatureScale.value,
barSize: barSizeInput.value,
barLabel: barLabel.value,
barBackOpacity: barBackOpacity.value,
barBackColor: barBackColor.value,
barPosX: barPosX.value,
barPosY: barPosY.value,
populationRate: populationRate, populationRate: populationRate,
urbanization: urbanization, urbanization: urbanization,
mapSize: mapSizeOutput.value, mapSize: mapSizeOutput.value,

View file

@ -314,6 +314,6 @@ function confirmHeightmapEdit() {
function getHeightmapPreview(heights) { function getHeightmapPreview(heights) {
const scheme = getColorScheme(byId("heightmapSelectionColorScheme").value); const scheme = getColorScheme(byId("heightmapSelectionColorScheme").value);
const renderOcean = byId("heightmapSelectionRenderOcean").checked; const renderOcean = byId("heightmapSelectionRenderOcean").checked;
const dataUrl = drawHeights({heights, width: grid.cellsX, height: grid.cellsY, scheme, renderOcean}); const dataUrl = drawHeights({heights, width: graph.cellsX, height: graph.cellsY, scheme, renderOcean});
return dataUrl; return dataUrl;
} }

View file

@ -60,7 +60,7 @@ window.Cloud = (function () {
async save(fileName, contents) { async save(fileName, contents) {
const resp = await this.call("filesUpload", {path: "/" + fileName, contents}); const resp = await this.call("filesUpload", {path: "/" + fileName, contents});
DEBUG && console.log("Dropbox response:", resp); DEBUG && console.info("Dropbox response:", resp);
return true; return true;
}, },
@ -104,7 +104,7 @@ window.Cloud = (function () {
// Callback function for auth window // Callback function for auth window
async setDropBoxToken(token) { async setDropBoxToken(token) {
DEBUG && console.log("Access token:", token); DEBUG && console.info("Access token:", token);
setToken(this.name, token); setToken(this.name, token);
await this.connect(token); await this.connect(token);
this.authWindow.close(); this.authWindow.close();
@ -131,7 +131,7 @@ window.Cloud = (function () {
allow_download: true allow_download: true
}; };
const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings}); const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings});
DEBUG && console.log("Dropbox link object:", resp.result); DEBUG && console.info("Dropbox link object:", resp.result);
return resp.result.url; return resp.result.url;
} }
}; };

View file

@ -12,7 +12,7 @@ async function quickLoad() {
async function loadFromDropbox() { async function loadFromDropbox() {
const mapPath = byId("loadFromDropboxSelect")?.value; const mapPath = byId("loadFromDropboxSelect")?.value;
DEBUG && console.log("Loading map from Dropbox:", mapPath); DEBUG && console.info("Loading map from Dropbox:", mapPath);
const blob = await Cloud.providers.dropbox.load(mapPath); const blob = await Cloud.providers.dropbox.load(mapPath);
uploadMap(blob); uploadMap(blob);
} }
@ -183,22 +183,22 @@ function showUploadMessage(type, mapData, mapVersion) {
title = "Newer file"; title = "Newer file";
canBeLoaded = false; canBeLoaded = false;
} else if (type === "outdated") { } else if (type === "outdated") {
message = `The map version (${mapVersion}) does not match the Generator version (${version}).<br>That is fine, click OK to the get map <b style="color: #005000">auto-updated</b>.<br>In case of issues please keep using an ${archive} of the Generator`; INFO && console.info(`Loading map. Auto-update from ${mapVersion} to ${version}`);
title = "Outdated file"; parseLoadedData(mapData, mapVersion);
canBeLoaded = true; return;
} }
alertMessage.innerHTML = message; alertMessage.innerHTML = message;
const buttons = { const buttons = {
OK: function () { OK: function () {
$(this).dialog("close"); $(this).dialog("close");
if (canBeLoaded) parseLoadedData(mapData); if (canBeLoaded) parseLoadedData(mapData, mapVersion);
} }
}; };
$("#alert").dialog({title, buttons}); $("#alert").dialog({title, buttons});
} }
async function parseLoadedData(data) { async function parseLoadedData(data, mapVersion) {
try { try {
// exit customization // exit customization
if (window.closeDialogs) closeDialogs(); if (window.closeDialogs) closeDialogs();
@ -218,6 +218,7 @@ async function parseLoadedData(data) {
INFO && console.group("Loaded Map " + seed); INFO && console.group("Loaded Map " + seed);
// TODO: move all to options object
void (function parseSettings() { void (function parseSettings() {
const settings = data[1].split("|"); const settings = data[1].split("|");
if (settings[0]) applyOption(distanceUnitInput, settings[0]); if (settings[0]) applyOption(distanceUnitInput, settings[0]);
@ -226,23 +227,16 @@ async function parseLoadedData(data) {
if (settings[3]) applyOption(heightUnit, settings[3]); if (settings[3]) applyOption(heightUnit, settings[3]);
if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4]; if (settings[4]) heightExponentInput.value = heightExponentOutput.value = settings[4];
if (settings[5]) temperatureScale.value = settings[5]; if (settings[5]) temperatureScale.value = settings[5];
if (settings[6]) barSizeInput.value = barSizeOutput.value = settings[6]; // setting 6-11 (scaleBar) are part of style now, kept as "" in newer versions for compatibility
if (settings[7] !== undefined) barLabel.value = settings[7];
if (settings[8] !== undefined) barBackOpacity.value = settings[8];
if (settings[9]) barBackColor.value = settings[9];
if (settings[10]) barPosX.value = settings[10];
if (settings[11]) barPosY.value = settings[11];
if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12]; if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12];
if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13]; if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13];
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100); if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100);
if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100); if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100);
if (settings[18]) precInput.value = precOutput.value = settings[18]; if (settings[18]) precInput.value = precOutput.value = settings[18];
if (settings[19]) options = JSON.parse(settings[19]); if (settings[19]) options = JSON.parse(settings[19]);
// setting 16 and 17 (temperature) are part of options now, kept as "" in newer versions for compatibility // setting 16 and 17 (temperature) are part of options now, kept as "" in newer versions for compatibility
if (settings[16]) options.temperatureEquator = +settings[16]; if (settings[16]) options.temperatureEquator = +settings[16];
if (settings[17]) options.temperatureNorthPole = options.temperatureSouthPole = +settings[17]; if (settings[17]) options.temperatureNorthPole = options.temperatureSouthPole = +settings[17];
if (settings[20]) mapName.value = settings[20]; if (settings[20]) mapName.value = settings[20];
if (settings[21]) hideLabels.checked = +settings[21]; if (settings[21]) hideLabels.checked = +settings[21];
if (settings[22]) stylePreset.value = settings[22]; if (settings[22]) stylePreset.value = settings[22];
@ -462,16 +456,16 @@ async function parseLoadedData(data) {
{ {
// dynamically import and run auto-update script // dynamically import and run auto-update script
const versionNumber = parseFloat(params[0]); const versionNumber = parseFloat(params[0]);
const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.95.00"); const {resolveVersionConflicts} = await import("../dynamic/auto-update.js?v=1.96.00");
resolveVersionConflicts(versionNumber); resolveVersionConflicts(versionNumber);
} }
{ {
// add custom heightmap color scheme if any // add custom heightmap color scheme if any
const scheme = terrs.attr("scheme"); const oceanScheme = terrs.select("#oceanHeights").attr("scheme");
if (!(scheme in heightmapColorSchemes)) { const landScheme = terrs.select("#landHeights").attr("scheme");
addCustomColorScheme(scheme); if (!(oceanScheme in heightmapColorSchemes)) addCustomColorScheme(oceanScheme);
} if (!(landScheme in heightmapColorSchemes)) addCustomColorScheme(landScheme);
} }
{ {
@ -645,7 +639,7 @@ async function parseLoadedData(data) {
ERROR && console.error(error); ERROR && console.error(error);
clearMainTip(); clearMainTip();
alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br />generate a new random map or cancel the loading alertMessage.innerHTML = /* html */ `An error is occured on map loading. Select a different file to load, <br>generate a new random map or cancel the loading.<br>Map version: ${mapVersion}. Generator version: ${version}.
<p id="errorBox">${parseError(error)}</p>`; <p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({ $("#alert").dialog({

View file

@ -49,12 +49,12 @@ function prepareMapData() {
heightUnit.value, heightUnit.value,
heightExponentInput.value, heightExponentInput.value,
temperatureScale.value, temperatureScale.value,
barSizeInput.value, "", // previously used for barSize.value
barLabel.value, "", // previously used for barLabel.value
barBackOpacity.value, "", // previously used for barBackColor.value
barBackColor.value, "", // previously used for barBackColor.value
barPosX.value, "", // previously used for barPosX.value
barPosY.value, "", // previously used for barPosY.value
populationRate, populationRate,
urbanization, urbanization,
mapSizeOutput.value, mapSizeOutput.value,

View file

@ -10,7 +10,18 @@ window.Military = (function () {
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
const area = d3.sum(valid.map(s => s.area)); // total area const area = d3.sum(valid.map(s => s.area)); // total area
const rate = {x: 0, Ally: -0.2, Friendly: -0.1, Neutral: 0, Suspicion: 0.1, Enemy: 1, Unknown: 0, Rival: 0.5, Vassal: 0.5, Suzerain: -0.5}; const rate = {
x: 0,
Ally: -0.2,
Friendly: -0.1,
Neutral: 0,
Suspicion: 0.1,
Enemy: 1,
Unknown: 0,
Rival: 0.5,
Vassal: 0.5,
Suzerain: -0.5
};
const stateModifier = { const stateModifier = {
melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1}, melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1},
@ -24,14 +35,59 @@ window.Military = (function () {
}; };
const cellTypeModifier = { const cellTypeModifier = {
nomadic: {melee: 0.2, ranged: 0.5, mounted: 3, machinery: 0.4, naval: 0.3, armored: 1.6, aviation: 1, magical: 0.5}, nomadic: {
wetland: {melee: 0.8, ranged: 2, mounted: 0.3, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5}, melee: 0.2,
highland: {melee: 1.2, ranged: 1.6, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2} ranged: 0.5,
mounted: 3,
machinery: 0.4,
naval: 0.3,
armored: 1.6,
aviation: 1,
magical: 0.5
},
wetland: {
melee: 0.8,
ranged: 2,
mounted: 0.3,
machinery: 1.2,
naval: 1.0,
armored: 0.2,
aviation: 0.5,
magical: 0.5
},
highland: {
melee: 1.2,
ranged: 1.6,
mounted: 0.3,
machinery: 3,
naval: 1.0,
armored: 0.8,
aviation: 0.3,
magical: 2
}
}; };
const burgTypeModifier = { const burgTypeModifier = {
nomadic: {melee: 0.3, ranged: 0.8, mounted: 3, machinery: 0.4, naval: 1.0, armored: 1.6, aviation: 1, magical: 0.5}, nomadic: {
wetland: {melee: 1, ranged: 1.6, mounted: 0.2, machinery: 1.2, naval: 1.0, armored: 0.2, aviation: 0.5, magical: 0.5}, melee: 0.3,
ranged: 0.8,
mounted: 3,
machinery: 0.4,
naval: 1.0,
armored: 1.6,
aviation: 1,
magical: 0.5
},
wetland: {
melee: 1,
ranged: 1.6,
mounted: 0.2,
machinery: 1.2,
naval: 1.0,
armored: 0.2,
aviation: 0.5,
magical: 0.5
},
highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2} highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
}; };
@ -40,8 +96,16 @@ window.Military = (function () {
const d = s.diplomacy; const d = s.diplomacy;
const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness const diplomacyRate = d.some(d => d === "Enemy")
const neighborsRateRaw = s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5); ? 1
: d.some(d => d === "Rival")
? 0.8
: d.some(d => d === "Suspicion")
? 0.5
: 0.1; // peacefulness
const neighborsRateRaw = s.neighbors
.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion"))
.reduce((s, r) => (s += rate[r]), 0.5);
const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate
s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier) s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier)
s.temp.platoons = []; s.temp.platoons = [];
@ -86,8 +150,10 @@ window.Military = (function () {
let modifier = cells.pop[i] / 100; // basic rural army in percentages let modifier = cells.pop[i] / 100; // basic rural army in percentages
if (culture !== stateObj.culture) modifier = stateObj.form === "Union" ? modifier / 1.2 : modifier / 2; // non-dominant culture if (culture !== stateObj.culture) modifier = stateObj.form === "Union" ? modifier / 1.2 : modifier / 2; // non-dominant culture
if (religion !== cells.religion[stateObj.center]) modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion if (religion !== cells.religion[stateObj.center])
if (cells.f[i] !== cells.f[stateObj.center]) modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass modifier = stateObj.form === "Theocracy" ? modifier / 2.2 : modifier / 1.4; // non-dominant religion
if (cells.f[i] !== cells.f[stateObj.center])
modifier = stateObj.type === "Naval" ? modifier / 1.2 : modifier / 1.8; // different landmass
const type = getType(i); const type = getType(i);
for (const unit of options.military) { for (const unit of options.military) {
@ -111,7 +177,17 @@ window.Military = (function () {
n = 1; n = 1;
} }
stateObj.temp.platoons.push({cell: i, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type}); stateObj.temp.platoons.push({
cell: i,
a: total,
t: total,
x,
y,
u: unit.name,
n,
s: unit.separate,
type: unit.type
});
} }
} }
@ -153,7 +229,17 @@ window.Military = (function () {
n = 1; n = 1;
} }
stateObj.temp.platoons.push({cell: b.cell, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type}); stateObj.temp.platoons.push({
cell: b.cell,
a: total,
t: total,
x,
y,
u: unit.name,
n,
s: unit.separate,
type: unit.type
});
} }
} }
@ -261,7 +347,8 @@ window.Military = (function () {
const army = armies const army = armies
.append("g") .append("g")
.attr("id", "army" + s) .attr("id", "army" + s)
.attr("fill", baseColor); .attr("fill", baseColor)
.attr("color", darkerColor);
const g = army const g = army
.selectAll("g") .selectAll("g")
@ -282,7 +369,7 @@ window.Military = (function () {
.attr("y", d => d.y) .attr("y", d => d.y)
.text(d => getTotal(d)); .text(d => getTotal(d));
g.append("rect") g.append("rect")
.attr("fill", darkerColor) .attr("fill", "currentColor")
.attr("x", d => x(d) - h) .attr("x", d => x(d) - h)
.attr("y", d => y(d)) .attr("y", d => y(d))
.attr("width", h) .attr("width", h)
@ -304,12 +391,13 @@ window.Military = (function () {
let army = armies.select("g#army" + s); let army = armies.select("g#army" + s);
if (!army.size()) { if (!army.size()) {
const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999"; const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999";
const darkerColor = d3.color(army.attr("fill")).darker().hex();
army = armies army = armies
.append("g") .append("g")
.attr("id", "army" + s) .attr("id", "army" + s)
.attr("fill", baseColor); .attr("fill", baseColor)
.attr("color", darkerColor);
} }
const darkerColor = d3.color(army.attr("fill")).darker().hex();
const g = army const g = army
.append("g") .append("g")
@ -320,7 +408,7 @@ window.Military = (function () {
g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h); g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h);
g.append("text").attr("x", reg.x).attr("y", reg.y).text(getTotal(reg)); g.append("text").attr("x", reg.x).attr("y", reg.y).text(getTotal(reg));
g.append("rect") g.append("rect")
.attr("fill", darkerColor) .attr("fill", "currentColor")
.attr("x", x1 - h) .attr("x", x1 - h)
.attr("y", y1) .attr("y", y1)
.attr("width", h) .attr("width", h)
@ -379,7 +467,13 @@ window.Military = (function () {
// get default regiment emblem // get default regiment emblem
const getEmblem = function (r) { const getEmblem = function (r) {
if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops
if (!r.n && pack.states[r.state].form === "Monarchy" && pack.cells.burg[r.cell] && pack.burgs[pack.cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital if (
!r.n &&
pack.states[r.state].form === "Monarchy" &&
pack.cells.burg[r.cell] &&
pack.burgs[pack.cells.burg[r.cell]].capital
)
return "👑"; // "Royal" regiment based in capital
const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment
const unit = options.military.find(u => u.name === mainUnit); const unit = options.military.find(u => u.name === mainUnit);
return unit.icon; return unit.icon;
@ -400,7 +494,9 @@ window.Military = (function () {
.map(t => `${t}: ${r.u[t]}`) .map(t => `${t}: ${r.u[t]}`)
.join("\r\n") .join("\r\n")
: null; : null;
const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : ""; const troops = composition
? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.`
: "";
const campaign = s.campaigns ? ra(s.campaigns) : null; const campaign = s.campaigns ? ra(s.campaigns) : null;
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6); const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6);
@ -409,5 +505,16 @@ window.Military = (function () {
notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend}); notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend});
}; };
return {generate, redraw, getDefaultOptions, getName, generateNote, drawRegiments, drawRegiment, moveRegiment, getTotal, getEmblem}; return {
generate,
redraw,
getDefaultOptions,
getName,
generateNote,
drawRegiments,
drawRegiment,
moveRegiment,
getTotal,
getEmblem
};
})(); })();

View file

@ -28,7 +28,7 @@ window.Submap = (function () {
const projection = options.projection; const projection = options.projection;
const inverse = options.inverse; const inverse = options.inverse;
const stage = s => INFO && console.log("SUBMAP:", s); const stage = s => INFO && console.info("SUBMAP:", s);
const timeStart = performance.now(); const timeStart = performance.now();
invokeActiveZooming(); invokeActiveZooming();
@ -36,7 +36,7 @@ window.Submap = (function () {
seed = parentMap.seed; seed = parentMap.seed;
Math.random = aleaPRNG(seed); Math.random = aleaPRNG(seed);
INFO && console.group("SubMap with seed: " + seed); INFO && console.group("SubMap with seed: " + seed);
DEBUG && console.log("Using Options:", options); DEBUG && console.info("Using Options:", options);
// create new grid // create new grid
applyGraphSize(); applyGraphSize();
@ -396,7 +396,7 @@ window.Submap = (function () {
b.removed = true; b.removed = true;
return; return;
} }
DEBUG && console.log(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`); DEBUG && console.info(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`);
[b.x, b.y] = b.port ? getMiddlePoint(newCell, neighbor) : cells.p[newCell]; [b.x, b.y] = b.port ? getMiddlePoint(newCell, neighbor) : cells.p[newCell];
if (b.port) b.port = cells.f[neighbor]; // copy feature number if (b.port) b.port = cells.f[neighbor]; // copy feature number
b.cell = newCell; b.cell = newCell;

View file

@ -126,51 +126,6 @@ function editBurg(id) {
} }
} }
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
function getTemperatureLikeness(temperature) {
if (temperature < -5) return "Yakutsk";
const cities = [
"Snag (Yukon)",
"Yellowknife (Canada)",
"Okhotsk (Russia)",
"Fairbanks (Alaska)",
"Nuuk (Greenland)",
"Murmansk", // -5 - 0
"Arkhangelsk",
"Anchorage",
"Tromsø",
"Reykjavik",
"Riga",
"Stockholm",
"Halifax",
"Prague",
"Copenhagen",
"London", // 1 - 10
"Antwerp",
"Paris",
"Milan",
"Batumi",
"Rome",
"Dubrovnik",
"Lisbon",
"Barcelona",
"Marrakesh",
"Alexandria", // 11 - 20
"Tegucigalpa",
"Guangzhou",
"Rio de Janeiro",
"Dakar",
"Miami",
"Jakarta",
"Mogadishu",
"Bangkok",
"Aden",
"Khartoum"
]; // 21 - 30
if (temperature > 30) return "Mecca";
return cities[temperature + 5] || null;
}
function dragBurgLabel() { function dragBurgLabel() {
const tr = parseTransform(this.getAttribute("transform")); const tr = parseTransform(this.getAttribute("transform"));
const dx = +tr[0] - d3.event.x, const dx = +tr[0] - d3.event.x,
@ -284,7 +239,9 @@ function editBurg(id) {
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${ alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
basic || capital ? "all unlocked elements in the burg group" : "the entire burg group" basic || capital ? "all unlocked elements in the burg group" : "the entire burg group"
}? }?
<br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${burgsToRemove.length}`; <br />Please note that capital or locked burgs will not be deleted. <br /><br />Burgs to be removed: ${
burgsToRemove.length
}`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Remove burg group", title: "Remove burg group",
@ -433,7 +390,8 @@ function editBurg(id) {
function addCustomMfcgLink() { function addCustomMfcgLink() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
const burg = pack.burgs[id]; const burg = pack.burgs[id];
const message = "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed"; const message =
"Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed";
prompt(message, {default: burg.link || "", required: false}, link => { prompt(message, {default: burg.link || "", required: false}, link => {
if (link) burg.link = link; if (link) burg.link = link;
else delete burg.link; else delete burg.link;
@ -581,3 +539,48 @@ function editBurg(id) {
unselect(); unselect();
} }
} }
// in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
function getTemperatureLikeness(temperature) {
if (temperature < -5) return "Yakutsk";
const cities = [
"Snag (Yukon)",
"Yellowknife (Canada)",
"Okhotsk (Russia)",
"Fairbanks (Alaska)",
"Nuuk (Greenland)",
"Murmansk", // -5 - 0
"Arkhangelsk",
"Anchorage",
"Tromsø",
"Reykjavik",
"Riga",
"Stockholm",
"Halifax",
"Prague",
"Copenhagen",
"London", // 1 - 10
"Antwerp",
"Paris",
"Milan",
"Batumi",
"Rome",
"Dubrovnik",
"Lisbon",
"Barcelona",
"Marrakesh",
"Alexandria", // 11 - 20
"Tegucigalpa",
"Guangzhou",
"Rio de Janeiro",
"Dakar",
"Miami",
"Jakarta",
"Mogadishu",
"Bangkok",
"Aden",
"Khartoum"
]; // 21 - 30
if (temperature > 30) return "Mecca";
return cities[temperature + 5] || null;
}

View file

@ -1,5 +1,5 @@
"use strict"; "use strict";
function overviewBurgs(options = {stateId: null, cultureId: null}) { function overviewBurgs(settings = {stateId: null, cultureId: null}) {
if (customization) return; if (customization) return;
closeDialogs("#burgsOverview, .stable"); closeDialogs("#burgsOverview, .stable");
if (!layerIsOn("toggleIcons")) toggleIcons(); if (!layerIsOn("toggleIcons")) toggleIcons();
@ -45,7 +45,7 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
function updateFilter() { function updateFilter() {
const stateFilter = byId("burgsFilterState"); const stateFilter = byId("burgsFilterState");
const selectedState = options.stateId !== null ? options.stateId : stateFilter.value || -1; const selectedState = settings.stateId !== null ? settings.stateId : stateFilter.value || -1;
stateFilter.options.length = 0; // remove all options stateFilter.options.length = 0; // remove all options
stateFilter.options.add(new Option("all", -1, false, selectedState === -1)); stateFilter.options.add(new Option("all", -1, false, selectedState === -1));
stateFilter.options.add(new Option(pack.states[0].name, 0, false, selectedState === 0)); stateFilter.options.add(new Option(pack.states[0].name, 0, false, selectedState === 0));
@ -53,7 +53,7 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState))); statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
const cultureFilter = byId("burgsFilterCulture"); const cultureFilter = byId("burgsFilterCulture");
const selectedCulture = options.cultureId !== null ? options.cultureId : cultureFilter.value || -1; const selectedCulture = settings.cultureId !== null ? settings.cultureId : cultureFilter.value || -1;
cultureFilter.options.length = 0; // remove all options cultureFilter.options.length = 0; // remove all options
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture === -1)); cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture === -1));
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, selectedCulture === 0)); cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, selectedCulture === 0));
@ -480,10 +480,7 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
} }
function downloadBurgsData() { function downloadBurgsData() {
let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town`; // headers let data = `Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,X,Y,Latitude,Longitude,Elevation (${heightUnit.value}),Temperature,Temperature likeness,Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town,Emblem,City Generator Link\n`; // headers
if (options.showMFCGMap) data += `,City Generator Link`;
data += "\n";
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
valid.forEach(b => { valid.forEach(b => {
@ -504,6 +501,9 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
data += getLatitude(b.y, 2) + ","; data += getLatitude(b.y, 2) + ",";
data += getLongitude(b.x, 2) + ","; data += getLongitude(b.x, 2) + ",";
data += parseInt(getHeight(pack.cells.h[b.cell])) + ","; data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
data += convertTemperature(temperature) + ",";
data += getTemperatureLikeness(temperature) + ",";
// add status data // add status data
data += b.capital ? "capital," : ","; data += b.capital ? "capital," : ",";
@ -513,7 +513,9 @@ function overviewBurgs(options = {stateId: null, cultureId: null}) {
data += b.plaza ? "plaza," : ","; data += b.plaza ? "plaza," : ",";
data += b.temple ? "temple," : ","; data += b.temple ? "temple," : ",";
data += b.shanty ? "shanty town," : ","; data += b.shanty ? "shanty town," : ",";
if (options.showMFCGMap) data += getMFCGlink(b); data += b.coa ? JSON.stringify(b.coa).replace(/"/g, "").replace(/,/g, ";") + "," : ",";
data += getMFCGlink(b);
data += "\n"; data += "\n";
}); });

View file

@ -1176,18 +1176,18 @@ function refreshAllEditors() {
// dynamically loaded editors // dynamically loaded editors
async function editStates() { async function editStates() {
if (customization) return; if (customization) return;
const Editor = await import("../dynamic/editors/states-editor.js?v=1.93.10"); const Editor = await import("../dynamic/editors/states-editor.js?v=1.96.00");
Editor.open(); Editor.open();
} }
async function editCultures() { async function editCultures() {
if (customization) return; if (customization) return;
const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.95.04"); const Editor = await import("../dynamic/editors/cultures-editor.js?v=1.96.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=1.89.10"); const Editor = await import("../dynamic/editors/religions-editor.js?v=1.96.00");
Editor.open(); Editor.open();
} }

View file

@ -188,92 +188,135 @@ function restoreLayers() {
} }
function toggleHeight(event) { function toggleHeight(event) {
if (customization === 1) { if (customization === 1) return tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
tip("You cannot turn off the layer when heightmap is in edit mode", false, "error");
return;
}
if (!terrs.selectAll("*").size()) { const children = terrs.selectAll("#oceanHeights > *, #landHeights > *");
if (!children.size()) {
turnButtonOn("toggleHeight"); turnButtonOn("toggleHeight");
drawHeightmap(); drawHeightmap();
if (event && isCtrlClick(event)) editStyle("terrs"); if (event && isCtrlClick(event)) editStyle("terrs");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("terrs");
editStyle("terrs");
return;
}
turnButtonOff("toggleHeight"); turnButtonOff("toggleHeight");
terrs.selectAll("*").remove(); children.remove();
} }
} }
function drawHeightmap() { function drawHeightmap() {
TIME && console.time("drawHeightmap"); TIME && console.time("drawHeightmap");
terrs.selectAll("*").remove();
const {cells, vertices} = pack; const ocean = terrs.select("#oceanHeights");
const n = cells.i.length; const land = terrs.select("#landHeights");
const used = new Uint8Array(cells.i.length);
const paths = new Array(101).fill("");
const scheme = getColorScheme(terrs.attr("scheme")); ocean.selectAll("*").remove();
const terracing = terrs.attr("terracing") / 10; // add additional shifted darker layer for pseudo-3d effect land.selectAll("*").remove();
const skip = +terrs.attr("skip") + 1;
const simplification = +terrs.attr("relax");
switch (+terrs.attr("curve")) { const paths = new Array(101);
case 0:
lineGen.curve(d3.curveBasisClosed); // ocean cells
break; const renderOceanCells = Boolean(+ocean.attr("data-render"));
case 1: if (renderOceanCells) {
lineGen.curve(d3.curveLinear); const {cells, vertices} = grid;
break; const used = new Uint8Array(cells.i.length);
case 2:
lineGen.curve(d3.curveStep); const skip = +ocean.attr("skip") + 1 || 1;
break; const relax = +ocean.attr("relax") || 0;
default: lineGen.curve(d3[ocean.attr("curve") || "curveBasisClosed"]);
lineGen.curve(d3.curveBasisClosed);
let currentLayer = 0;
const heights = Array.from(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 (h < currentLayer) continue;
if (currentLayer >= 20) break;
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(cells, vertices, vertex, h, used);
if (chain.length < 3) continue;
const points = simplifyLine(chain, relax).map(v => vertices.p[v]);
if (!paths[h]) paths[h] = "";
paths[h] += round(lineGen(points));
}
} }
let currentLayer = 20; // land cells
const heights = cells.i.sort((a, b) => cells.h[a] - cells.h[b]); {
for (const i of heights) { const {cells, vertices} = pack;
const h = cells.h[i]; const used = new Uint8Array(cells.i.length);
if (h > currentLayer) currentLayer += skip;
if (currentLayer > 100) break; // no layers possible with height > 100 const skip = +land.attr("skip") + 1 || 1;
if (h < currentLayer) continue; const relax = +land.attr("relax") || 0;
if (used[i]) continue; // already marked lineGen.curve(d3[land.attr("curve") || "curveBasisClosed"]);
const onborder = cells.c[i].some(n => cells.h[n] < h);
if (!onborder) continue; let currentLayer = 20;
const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.h[i] < h)); const heights = Array.from(cells.i).sort((a, b) => cells.h[a] - cells.h[b]);
const chain = connectVertices(vertex, h); for (const i of heights) {
if (chain.length < 3) continue; const h = cells.h[i];
const points = simplifyLine(chain).map(v => vertices.p[v]); if (h > currentLayer) currentLayer += skip;
paths[h] += round(lineGen(points)); if (h < currentLayer) continue;
if (currentLayer > 100) break; // no layers possible with height > 100
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(cells, vertices, vertex, h, used);
if (chain.length < 3) continue;
const points = simplifyLine(chain, relax).map(v => vertices.p[v]);
if (!paths[h]) paths[h] = "";
paths[h] += round(lineGen(points));
}
} }
terrs // render paths
.append("rect") for (const height of d3.range(0, 101)) {
.attr("x", 0) const group = height < 20 ? ocean : land;
.attr("y", 0) const scheme = getColorScheme(group.attr("scheme"));
.attr("width", graphWidth)
.attr("height", graphHeight) if (height === 0 && renderOceanCells) {
.attr("fill", scheme(0.8)); // draw base layer // draw base ocean layer
for (const i of d3.range(20, 101)) { group
if (paths[i].length < 10) continue; .append("rect")
const color = getColor(i, scheme); .attr("x", 0)
if (terracing) .attr("y", 0)
terrs .attr("width", graphWidth)
.append("path") .attr("height", graphHeight)
.attr("d", paths[i]) .attr("fill", scheme(1));
.attr("transform", "translate(.7,1.4)") }
.attr("fill", d3.color(color).darker(terracing))
.attr("data-height", i); if (height === 20) {
terrs.append("path").attr("d", paths[i]).attr("fill", color).attr("data-height", i); // draw base land layer
group
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", graphWidth)
.attr("height", graphHeight)
.attr("fill", scheme(0.8));
}
if (paths[height] && paths[height].length >= 10) {
const terracing = group.attr("terracing") / 10 || 0;
const color = getColor(height, scheme);
if (terracing) {
group
.append("path")
.attr("d", paths[height])
.attr("transform", "translate(.7,1.4)")
.attr("fill", d3.color(color).darker(terracing))
.attr("data-height", height);
}
group.append("path").attr("d", paths[height]).attr("fill", color).attr("data-height", height);
}
} }
// connect vertices to chain // connect vertices to chain
function connectVertices(start, h) { function connectVertices(cells, vertices, start, h, used) {
const n = cells.i.length;
const chain = []; // vertices chain to form a path const chain = []; // vertices chain to form a path
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
const prev = chain[chain.length - 1]; // previous vertex in chain const prev = chain[chain.length - 1]; // previous vertex in chain
@ -295,7 +338,7 @@ function drawHeightmap() {
return chain; return chain;
} }
function simplifyLine(chain) { function simplifyLine(chain, simplification) {
if (!simplification) return chain; if (!simplification) return chain;
const n = simplification + 1; // filter each nth element const n = simplification + 1; // filter each nth element
return chain.filter((d, i) => i % n === 0); return chain.filter((d, i) => i % n === 0);
@ -1670,10 +1713,7 @@ function toggleLabels(event) {
invokeActiveZooming(); invokeActiveZooming();
if (event && isCtrlClick(event)) editStyle("labels"); if (event && isCtrlClick(event)) editStyle("labels");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("labels");
editStyle("labels");
return;
}
turnButtonOff("toggleLabels"); turnButtonOff("toggleLabels");
labels.style("display", "none"); labels.style("display", "none");
} }
@ -1685,10 +1725,7 @@ function toggleIcons(event) {
$("#icons").fadeIn(); $("#icons").fadeIn();
if (event && isCtrlClick(event)) editStyle("burgIcons"); if (event && isCtrlClick(event)) editStyle("burgIcons");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("burgIcons");
editStyle("burgIcons");
return;
}
turnButtonOff("toggleIcons"); turnButtonOff("toggleIcons");
$("#icons").fadeOut(); $("#icons").fadeOut();
} }
@ -1701,10 +1738,7 @@ function toggleRulers(event) {
rulers.draw(); rulers.draw();
ruler.style("display", null); ruler.style("display", null);
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("ruler");
editStyle("ruler");
return;
}
turnButtonOff("toggleRulers"); turnButtonOff("toggleRulers");
ruler.selectAll("*").remove(); ruler.selectAll("*").remove();
ruler.style("display", "none"); ruler.style("display", "none");
@ -1715,17 +1749,113 @@ function toggleScaleBar(event) {
if (!layerIsOn("toggleScaleBar")) { if (!layerIsOn("toggleScaleBar")) {
turnButtonOn("toggleScaleBar"); turnButtonOn("toggleScaleBar");
$("#scaleBar").fadeIn(); $("#scaleBar").fadeIn();
if (event && isCtrlClick(event)) editUnits(); if (event && isCtrlClick(event)) editStyle("scaleBar");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("scaleBar");
editUnits();
return;
}
$("#scaleBar").fadeOut(); $("#scaleBar").fadeOut();
turnButtonOff("toggleScaleBar"); turnButtonOff("toggleScaleBar");
} }
} }
function drawScaleBar(scaleBar, scaleLevel) {
if (!scaleBar.size() || scaleBar.style("display") === "none") return;
const distanceScale = +distanceScaleInput.value;
const unit = distanceUnitInput.value;
const size = +scaleBar.attr("data-bar-size");
const length = (function () {
const init = 100;
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
if (val > 900) val = rn(val, -3); // round to 1000
else if (val > 90) val = rn(val, -2); // round to 100
else if (val > 9) val = rn(val, -1); // round to 10
else val = rn(val); // round to 1
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
return length;
})();
scaleBar.select("#scaleBarContent").remove(); // redraw content every time
const content = scaleBar.append("g").attr("id", "scaleBarContent");
const lines = content.append("g");
lines
.append("line")
.attr("x1", 0.5)
.attr("y1", 0)
.attr("x2", length + size - 0.5)
.attr("y2", 0)
.attr("stroke-width", size)
.attr("stroke", "white");
lines
.append("line")
.attr("x1", 0)
.attr("y1", size)
.attr("x2", length + size)
.attr("y2", size)
.attr("stroke-width", size)
.attr("stroke", "#3d3d3d");
lines
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", length + size)
.attr("y2", 0)
.attr("stroke-width", rn(size * 3, 2))
.attr("stroke-dasharray", size + " " + rn(length / 5 - size, 2))
.attr("stroke", "#3d3d3d");
const texts = content.append("g").attr("text-anchor", "middle").attr("font-family", "var(--serif)");
texts
.selectAll("text")
.data(d3.range(0, 6))
.enter()
.append("text")
.attr("x", d => rn((d * length) / 5, 2))
.attr("y", 0)
.attr("dy", "-.6em")
.text(d => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? "" : " " + unit));
const label = scaleBar.attr("data-label");
if (label) {
texts
.append("text")
.attr("x", (length + 1) / 2)
.attr("dy", ".6em")
.attr("dominant-baseline", "text-before-edge")
.text(label);
}
const scaleBarBack = scaleBar.select("#scaleBarBack");
if (scaleBarBack.size()) {
const bbox = content.node().getBBox();
const paddingTop = +scaleBarBack.attr("data-top") || 0;
const paddingLeft = +scaleBarBack.attr("data-left") || 0;
const paddingRight = +scaleBarBack.attr("data-right") || 0;
const paddingBottom = +scaleBarBack.attr("data-bottom") || 0;
scaleBar
.select("#scaleBarBack")
.attr("x", -paddingLeft)
.attr("y", -paddingTop)
.attr("width", bbox.width + paddingRight)
.attr("height", bbox.height + paddingBottom);
}
}
// fit ScaleBar to screen size
function fitScaleBar(scaleBar, fullWidth, fullHeight) {
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
const posX = +scaleBar.attr("data-x") || 99;
const posY = +scaleBar.attr("data-y") || 99;
const bbox = scaleBar.select("rect").node().getBBox();
const x = rn((fullWidth * posX) / 100 - bbox.width + 10);
const y = rn((fullHeight * posY) / 100 - bbox.height + 20);
scaleBar.attr("transform", `translate(${x},${y})`);
}
function toggleZones(event) { function toggleZones(event) {
if (!layerIsOn("toggleZones")) { if (!layerIsOn("toggleZones")) {
turnButtonOn("toggleZones"); turnButtonOn("toggleZones");
@ -1748,10 +1878,7 @@ function toggleEmblems(event) {
$("#emblems").fadeIn(); $("#emblems").fadeIn();
if (event && isCtrlClick(event)) editStyle("emblems"); if (event && isCtrlClick(event)) editStyle("emblems");
} else { } else {
if (event && isCtrlClick(event)) { if (event && isCtrlClick(event)) return editStyle("emblems");
editStyle("emblems");
return;
}
$("#emblems").fadeOut(); $("#emblems").fadeOut();
turnButtonOff("toggleEmblems"); turnButtonOff("toggleEmblems");
} }

View file

@ -532,101 +532,3 @@ class Planimeter extends Measurer {
this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area); this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area);
} }
} }
// Scale bar
function drawScaleBar(scaleBar, scaleLevel) {
if (!scaleBar.size() || scaleBar.style("display") === "none") return;
scaleBar.selectAll("*").remove(); // fully redraw every time
const distanceScale = +distanceScaleInput.value;
const unit = distanceUnitInput.value;
const size = +barSizeInput.value;
// calculate size
const init = 100;
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
if (val > 900) val = rn(val, -3);
// round to 1000
else if (val > 90) val = rn(val, -2);
// round to 100
else if (val > 9) val = rn(val, -1);
// round to 10
else val = rn(val); // round to 1
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
scaleBar
.append("line")
.attr("x1", 0.5)
.attr("y1", 0)
.attr("x2", length + size - 0.5)
.attr("y2", 0)
.attr("stroke-width", size)
.attr("stroke", "white");
scaleBar
.append("line")
.attr("x1", 0)
.attr("y1", size)
.attr("x2", length + size)
.attr("y2", size)
.attr("stroke-width", size)
.attr("stroke", "#3d3d3d");
const dash = size + " " + rn(length / 5 - size, 2);
scaleBar
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", length + size)
.attr("y2", 0)
.attr("stroke-width", rn(size * 3, 2))
.attr("stroke-dasharray", dash)
.attr("stroke", "#3d3d3d");
const fontSize = rn(5 * size, 1);
scaleBar
.selectAll("text")
.data(d3.range(0, 6))
.enter()
.append("text")
.attr("x", d => rn((d * length) / 5, 2))
.attr("y", 0)
.attr("dy", "-.6em")
.attr("font-size", fontSize)
.text(d => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? "" : " " + unit));
if (barLabel.value !== "") {
scaleBar
.append("text")
.attr("x", (length + 1) / 2)
.attr("y", 2 * size)
.attr("dominant-baseline", "text-before-edge")
.attr("font-size", fontSize)
.text(barLabel.value);
}
const bbox = scaleBar.node().getBBox();
// append backbround rectangle
scaleBar
.insert("rect", ":first-child")
.attr("x", -10)
.attr("y", -20)
.attr("width", bbox.width + 10)
.attr("height", bbox.height + 15)
.attr("stroke-width", size)
.attr("stroke", "none")
.attr("filter", "url(#blur5)")
.attr("fill", barBackColor.value)
.attr("opacity", +barBackOpacity.value);
}
// fit ScaleBar to screen size
function fitScaleBar(scaleBar, fullWidth, fullHeight) {
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
const px = isNaN(+barPosX.value) ? 0.99 : barPosX.value / 100;
const py = isNaN(+barPosY.value) ? 0.99 : barPosY.value / 100;
const bbox = scaleBar.select("rect").node().getBBox();
const x = rn(fullWidth * px - bbox.width + 10);
const y = rn(fullHeight * py - bbox.height + 20);
scaleBar.attr("transform", `translate(${x},${y})`);
}

View file

@ -691,7 +691,7 @@ function changeEra() {
} }
async function openTemplateSelectionDialog() { async function openTemplateSelectionDialog() {
const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.93.12"); const HeightmapSelectionDialog = await import("../dynamic/heightmap-selection.js?v=1.96.00");
HeightmapSelectionDialog.open(); HeightmapSelectionDialog.open();
} }
@ -774,7 +774,7 @@ function showExportPane() {
} }
async function exportToJson(type) { async function exportToJson(type) {
const {exportToJson} = await import("../dynamic/export-json.js?v=1.93.03"); const {exportToJson} = await import("../dynamic/export-json.js?v=1.96.00");
exportToJson(type); exportToJson(type);
} }

View file

@ -44,12 +44,14 @@ function editProvinces() {
cl = el.classList, cl = el.classList,
line = el.parentNode, line = el.parentNode,
p = +line.dataset.id; p = +line.dataset.id;
const stateId = pack.provinces[p].state;
if (el.tagName === "FILL-BOX") changeFill(el); if (el.tagName === "FILL-BOX") changeFill(el);
else if (cl.contains("name")) editProvinceName(p); else if (cl.contains("name")) editProvinceName(p);
else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]); else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]);
else if (cl.contains("icon-star-empty")) capitalZoomIn(p); else if (cl.contains("icon-star-empty")) capitalZoomIn(p);
else if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p); else if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p);
else if (cl.contains("icon-dot-circled")) overviewBurgs({stateId});
else if (cl.contains("culturePopulation")) changePopulation(p); else if (cl.contains("culturePopulation")) changePopulation(p);
else if (cl.contains("icon-pin")) toggleFog(p, cl); else if (cl.contains("icon-pin")) toggleFog(p, cl);
else if (cl.contains("icon-trash-empty")) removeProvince(p); else if (cl.contains("icon-trash-empty")) removeProvince(p);
@ -71,9 +73,8 @@ function editProvinces() {
} }
function collectStatistics() { function collectStatistics() {
const cells = pack.cells, const {cells, provinces, burgs} = pack;
provinces = pack.provinces,
burgs = pack.burgs;
provinces.forEach(p => { provinces.forEach(p => {
if (!p.i || p.removed) return; if (!p.i || p.removed) return;
p.area = p.rural = p.urban = 0; p.area = p.rural = p.urban = 0;
@ -107,16 +108,18 @@ function editProvinces() {
statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState))); statesSorted.forEach(s => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
} }
// add line for each state // add line for each province
function provincesEditorAddLines() { function provincesEditorAddLines() {
const unit = " " + getAreaUnit(); const unit = " " + getAreaUnit();
const selectedState = +document.getElementById("provincesFilterState").value; const selectedState = +document.getElementById("provincesFilterState").value;
let filtered = pack.provinces.filter(p => p.i && !p.removed); // all valid burgs let filtered = pack.provinces.filter(p => p.i && !p.removed); // all valid burgs
if (selectedState != -1) filtered = filtered.filter(p => p.state === selectedState); // filtered by state if (selectedState != -1) filtered = filtered.filter(p => p.state === selectedState); // filtered by state
body.innerHTML = ""; body.innerHTML = "";
let lines = "",
totalArea = 0, let lines = "";
totalPopulation = 0; let totalArea = 0;
let totalPopulation = 0;
let totalBurgs = 0;
for (const p of filtered) { for (const p of filtered) {
const area = getArea(p.area); const area = getArea(p.area);
@ -128,6 +131,7 @@ function editProvinces() {
rural rural
)}; Urban population: ${si(urban)}`; )}; Urban population: ${si(urban)}`;
totalPopulation += population; totalPopulation += population;
totalBurgs += p.burgs.length;
const stateName = pack.states[p.state].name; const stateName = pack.states[p.state].name;
const capital = p.burg ? pack.burgs[p.burg].name : ""; const capital = p.burg ? pack.burgs[p.burg].name : "";
@ -144,6 +148,7 @@ function editProvinces() {
data-state="${stateName}" data-state="${stateName}"
data-area=${area} data-area=${area}
data-population=${population} data-population=${population}
data-burgs=${p.burgs.length}
> >
<fill-box fill="${p.color}"></fill-box> <fill-box fill="${p.color}"></fill-box>
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly /> <input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly />
@ -163,6 +168,8 @@ function editProvinces() {
${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""} ${p.burgs.length ? getCapitalOptions(p.burgs, p.burg) : ""}
</select> </select>
<input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled"> <input data-tip="Province owner" class="provinceOwner" value="${stateName}" disabled">
<span data-tip="Click to overview province burgs" style="padding-right: 1px" class="icon-dot-circled pointer hide"></span>
<div data-tip="Burgs count" class="provinceBurgs hide">${p.burgs.length}</div>
<span data-tip="Province area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Province area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Province area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
@ -179,11 +186,12 @@ function editProvinces() {
body.innerHTML = lines; body.innerHTML = lines;
// update footer // update footer
provincesFooterNumber.innerHTML = filtered.length; byId("provincesFooterNumber").innerHTML = filtered.length;
provincesFooterArea.innerHTML = filtered.length ? si(totalArea / filtered.length) + unit : 0 + unit; byId("provincesFooterBurgs").innerHTML = totalBurgs;
provincesFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0; byId("provincesFooterArea").innerHTML = filtered.length ? si(totalArea / filtered.length) + unit : 0 + unit;
provincesFooterArea.dataset.area = totalArea; byId("provincesFooterPopulation").innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
provincesFooterPopulation.dataset.population = totalPopulation; byId("provincesFooterArea").dataset.area = totalArea;
byId("provincesFooterPopulation").dataset.population = totalPopulation;
body.querySelectorAll("div.states").forEach(el => { body.querySelectorAll("div.states").forEach(el => {
el.addEventListener("click", selectProvinceOnLineClick); el.addEventListener("click", selectProvinceOnLineClick);
@ -294,7 +302,7 @@ function editProvinces() {
// move all burgs to a new state // move all burgs to a new state
province.burgs.forEach(b => (burgs[b].state = newStateId)); province.burgs.forEach(b => (burgs[b].state = newStateId));
// difine new state attributes // define new state attributes
const {cell: center, culture} = burgs[burgId]; const {cell: center, culture} = burgs[burgId];
const color = getRandomColor(); const color = getRandomColor();
const coa = province.coa; const coa = province.coa;
@ -501,6 +509,9 @@ function editProvinces() {
applyOption(provinceNameEditorSelectForm, p.formName); applyOption(provinceNameEditorSelectForm, p.formName);
document.getElementById("provinceNameEditorFull").value = p.fullName; document.getElementById("provinceNameEditorFull").value = p.fullName;
const cultureId = pack.cells.culture[p.center];
document.getElementById("provinceCultureDisplay").innerText = pack.cultures[cultureId].name;
$("#provinceNameEditor").dialog({ $("#provinceNameEditor").dialog({
resizable: false, resizable: false,
title: "Change province name", title: "Change province name",
@ -520,12 +531,12 @@ function editProvinces() {
modules.editProvinceName = true; modules.editProvinceName = true;
// add listeners // add listeners
document.getElementById("provinceNameEditorShortCulture").addEventListener("click", regenerateShortNameCuture); document.getElementById("provinceNameEditorShortCulture").addEventListener("click", regenerateShortNameCulture);
document.getElementById("provinceNameEditorShortRandom").addEventListener("click", regenerateShortNameRandom); document.getElementById("provinceNameEditorShortRandom").addEventListener("click", regenerateShortNameRandom);
document.getElementById("provinceNameEditorAddForm").addEventListener("click", addCustomForm); document.getElementById("provinceNameEditorAddForm").addEventListener("click", addCustomForm);
document.getElementById("provinceNameEditorFullRegenerate").addEventListener("click", regenerateFullName); document.getElementById("provinceNameEditorFullRegenerate").addEventListener("click", regenerateFullName);
function regenerateShortNameCuture() { function regenerateShortNameCulture() {
const province = +provinceNameEditor.dataset.province; const province = +provinceNameEditor.dataset.province;
const culture = pack.cells.culture[pack.provinces[province].center]; const culture = pack.cells.culture[pack.provinces[province].center];
const name = Names.getState(Names.getCultureShort(culture), culture); const name = Names.getState(Names.getCultureShort(culture), culture);
@ -576,12 +587,15 @@ function editProvinces() {
function togglePercentageMode() { function togglePercentageMode() {
if (body.dataset.type === "absolute") { if (body.dataset.type === "absolute") {
body.dataset.type = "percentage"; body.dataset.type = "percentage";
const totalBurgs = +byId("provincesFooterBurgs").innerText;
const totalArea = +provincesFooterArea.dataset.area; const totalArea = +provincesFooterArea.dataset.area;
const totalPopulation = +provincesFooterPopulation.dataset.population; const totalPopulation = +provincesFooterPopulation.dataset.population;
body.querySelectorAll(":scope > div").forEach(function (el) { body.querySelectorAll(":scope > div").forEach(function (el) {
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%"; const {cells, burgs, area, population} = el.dataset;
el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%"; el.querySelector(".provinceBurgs").innerText = rn((+burgs / totalBurgs) * 100) + "%";
el.querySelector(".biomeArea").innerHTML = rn((+area / totalArea) * 100) + "%";
el.querySelector(".culturePopulation").innerHTML = rn((+population / totalPopulation) * 100) + "%";
}); });
} else { } else {
body.dataset.type = "absolute"; body.dataset.type = "absolute";
@ -1064,10 +1078,7 @@ function editProvinces() {
function downloadProvincesData() { function downloadProvincesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
let data = let data = `Id,Province,Full Name,Form,State,Color,Capital,Area ${unit},Total Population,Rural Population,Urban Population,Burgs\n`; // headers
"Id,Province,Full Name,Form,State,Color,Capital,Area " +
unit +
",Total Population,Rural Population,Urban Population\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) { body.querySelectorAll(":scope > div").forEach(function (el) {
const key = parseInt(el.dataset.id); const key = parseInt(el.dataset.id);
@ -1081,8 +1092,9 @@ function editProvinces() {
data += el.dataset.capital + ","; data += el.dataset.capital + ",";
data += el.dataset.area + ","; data += el.dataset.area + ",";
data += el.dataset.population + ","; data += el.dataset.population + ",";
data += `${Math.round(provincePack.rural * populationRate)},`; data += Math.round(provincePack.rural * populationRate) + ",";
data += `${Math.round(provincePack.urban * populationRate * urbanization)}\n`; data += Math.round(provincePack.urban * populationRate * urbanization) + ",";
data += el.dataset.burgs + "\n";
}); });
const name = getFileName("Provinces") + ".csv"; const name = getFileName("Provinces") + ".csv";

View file

@ -14,6 +14,7 @@
byId("styleFilterInput").innerHTML = allOptions; byId("styleFilterInput").innerHTML = allOptions;
byId("styleStatesBodyFilter").innerHTML = allOptions; byId("styleStatesBodyFilter").innerHTML = allOptions;
byId("styleScaleBarBackgroundFilter").innerHTML = allOptions;
} }
// store some style inputs as options // store some style inputs as options
@ -31,6 +32,7 @@ function editStyle(element, group) {
styleElementSelect.classList.add("glow"); styleElementSelect.classList.add("glow");
if (group) styleGroupSelect.classList.add("glow"); if (group) styleGroupSelect.classList.add("glow");
setTimeout(() => { setTimeout(() => {
styleElementSelect.classList.remove("glow"); styleElementSelect.classList.remove("glow");
if (group) styleGroupSelect.classList.remove("glow"); if (group) styleGroupSelect.classList.remove("glow");
@ -81,10 +83,10 @@ function selectStyleElement() {
styleIsOff.style.display = isLayerOff ? "block" : "none"; styleIsOff.style.display = isLayerOff ? "block" : "none";
// active group element // active group element
const group = styleGroupSelect.value; if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(styleElement)) { const group = styleGroupSelect.value;
const gEl = group && el.select("#" + group); const defaultGroupSelector = styleElement === "terrs" ? "#landHeights" : "g";
el = group && gEl.size() ? gEl : el.select("g"); el = group && el.select("#" + group).size() ? el.select("#" + group) : el.select(defaultGroupSelector);
} }
// opacity // opacity
@ -94,13 +96,13 @@ function selectStyleElement() {
} }
// filter // filter
if (!["landmass", "legend", "regions"].includes(styleElement)) { if (!["landmass", "legend", "regions", "scaleBar"].includes(styleElement)) {
styleFilter.style.display = "block"; styleFilter.style.display = "block";
styleFilterInput.value = el.attr("filter") || ""; styleFilterInput.value = el.attr("filter") || "";
} }
// fill // fill
if (["rivers", "lakes", "landmass", "prec", "ice", "fogging", "vignette"].includes(styleElement)) { if (["rivers", "lakes", "landmass", "prec", "ice", "fogging", "scaleBar", "vignette"].includes(styleElement)) {
styleFill.style.display = "block"; styleFill.style.display = "block";
styleFillInput.value = styleFillOutput.value = el.attr("fill"); styleFillInput.value = styleFillOutput.value = el.attr("fill");
} }
@ -170,11 +172,14 @@ function selectStyleElement() {
if (styleElement === "terrs") { if (styleElement === "terrs") {
styleHeightmap.style.display = "block"; styleHeightmap.style.display = "block";
styleHeightmapScheme.value = terrs.attr("scheme"); styleHeightmapRenderOceanOption.style.display = el.attr("id") === "oceanHeights" ? "block" : "none";
styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = terrs.attr("terracing"); styleHeightmapRenderOcean.checked = +el.attr("data-render");
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = terrs.attr("skip");
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = terrs.attr("relax"); styleHeightmapScheme.value = el.attr("scheme");
styleHeightmapCurve.value = terrs.attr("curve"); styleHeightmapTerracingInput.value = styleHeightmapTerracingOutput.value = el.attr("terracing");
styleHeightmapSkipInput.value = styleHeightmapSkipOutput.value = el.attr("skip");
styleHeightmapSimplificationInput.value = styleHeightmapSimplificationOutput.value = el.attr("relax");
styleHeightmapCurve.value = el.attr("curve");
} }
if (styleElement === "markers") { if (styleElement === "markers") {
@ -336,7 +341,7 @@ function selectStyleElement() {
// update group options // update group options
styleGroupSelect.options.length = 0; // remove all options styleGroupSelect.options.length = 0; // remove all options
if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders"].includes(styleElement)) { if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
const groups = byId(styleElement).querySelectorAll("g"); const groups = byId(styleElement).querySelectorAll("g");
groups.forEach(el => { groups.forEach(el => {
if (el.id === "burgLabels") return; if (el.id === "burgLabels") return;
@ -356,6 +361,31 @@ function selectStyleElement() {
if (auto) styleFilter.style.display = "none"; if (auto) styleFilter.style.display = "none";
} }
if (styleElement === "scaleBar") {
styleScaleBar.style.display = "block";
styleScaleBarSize.value = el.attr("data-bar-size");
styleScaleBarFontSize.value = el.attr("font-size");
styleScaleBarPositionX.value = el.attr("data-x") || "99";
styleScaleBarPositionY.value = el.attr("data-y") || "99";
styleScaleBarLabel.value = el.attr("data-label") || "";
const scaleBarBack = el.select("#scaleBarBack");
if (scaleBarBack.size()) {
styleScaleBarBackgroundOpacityInput.value = styleScaleBarBackgroundOpacityOutput.value =
scaleBarBack.attr("opacity");
styleScaleBarBackgroundFillInput.value = styleScaleBarBackgroundFillOutput.value = scaleBarBack.attr("fill");
styleScaleBarBackgroundStrokeInput.value = styleScaleBarBackgroundStrokeOutput.value =
scaleBarBack.attr("stroke");
styleScaleBarBackgroundStrokeWidth.value = scaleBarBack.attr("stroke-width");
styleScaleBarBackgroundFilter.value = scaleBarBack.attr("filter");
styleScaleBarBackgroundPaddingTop.value = scaleBarBack.attr("data-top");
styleScaleBarBackgroundPaddingRight.value = scaleBarBack.attr("data-right");
styleScaleBarBackgroundPaddingBottom.value = scaleBarBack.attr("data-bottom");
styleScaleBarBackgroundPaddingLeft.value = scaleBarBack.attr("data-left");
}
}
if (styleElement === "vignette") { if (styleElement === "vignette") {
styleVignette.style.display = "block"; styleVignette.style.display = "block";
@ -519,18 +549,16 @@ outlineLayers.addEventListener("change", function () {
}); });
styleHeightmapScheme.addEventListener("change", function () { styleHeightmapScheme.addEventListener("change", function () {
terrs.attr("scheme", this.value); getEl().attr("scheme", this.value);
drawHeightmap(); drawHeightmap();
}); });
openCreateHeightmapSchemeButton.addEventListener("click", function () { openCreateHeightmapSchemeButton.addEventListener("click", function () {
// start with current scheme // start with current scheme
this.dataset.stops = terrs.attr("scheme").startsWith("#") const scheme = getEl().attr("scheme");
? terrs.attr("scheme") this.dataset.stops = scheme.startsWith("#")
: (function () { ? scheme
const scheme = heightmapColorSchemes[terrs.attr("scheme")]; : (() => [0, 0.25, 0.5, 0.75, 1].map(heightmapColorSchemes[scheme]).map(toHEX).join(","))();
return [0, 0.25, 0.5, 0.75, 1].map(scheme).map(toHEX).join(",");
})();
// render dialog base structure // render dialog base structure
alertMessage.innerHTML = /* html */ `<div> alertMessage.innerHTML = /* html */ `<div>
@ -622,7 +650,7 @@ openCreateHeightmapSchemeButton.addEventListener("click", function () {
if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error"); if (stops in heightmapColorSchemes) return tip("This scheme already exists", false, "error");
addCustomColorScheme(stops); addCustomColorScheme(stops);
terrs.attr("scheme", stops); getEl().attr("scheme", stops);
drawHeightmap(); drawHeightmap();
handleClose(); handleClose();
@ -644,23 +672,28 @@ openCreateHeightmapSchemeButton.addEventListener("click", function () {
}); });
}); });
styleHeightmapRenderOcean.addEventListener("change", function () {
getEl().attr("data-render", +this.checked);
drawHeightmap();
});
styleHeightmapTerracingInput.addEventListener("input", function () { styleHeightmapTerracingInput.addEventListener("input", function () {
terrs.attr("terracing", this.value); getEl().attr("terracing", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapSkipInput.addEventListener("input", function () { styleHeightmapSkipInput.addEventListener("input", function () {
terrs.attr("skip", this.value); getEl().attr("skip", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapSimplificationInput.addEventListener("input", function () { styleHeightmapSimplificationInput.addEventListener("input", function () {
terrs.attr("relax", this.value); getEl().attr("relax", this.value);
drawHeightmap(); drawHeightmap();
}); });
styleHeightmapCurve.addEventListener("change", function () { styleHeightmapCurve.addEventListener("change", function () {
terrs.attr("curve", this.value); getEl().attr("curve", this.value);
drawHeightmap(); drawHeightmap();
}); });
@ -957,7 +990,7 @@ function textureProvideURL() {
} }
function fetchTextureURL(url) { function fetchTextureURL(url) {
INFO && console.log("Provided URL is", url); INFO && console.info("Provided URL is", url);
const img = new Image(); const img = new Image();
img.onload = function () { img.onload = function () {
const canvas = byId("texturePreview"); const canvas = byId("texturePreview");
@ -1043,6 +1076,44 @@ styleVignetteBlur.addEventListener("input", function () {
byId("vignette-rect")?.setAttribute("filter", `blur(${this.value}px)`); byId("vignette-rect")?.setAttribute("filter", `blur(${this.value}px)`);
}); });
styleScaleBar.addEventListener("input", function (event) {
const scaleBarBack = scaleBar.select("#scaleBarBack");
if (!scaleBarBack.size()) return;
const {id, value} = event.target;
if (id === "styleScaleBarSize") scaleBar.attr("data-bar-size", value);
else if (id === "styleScaleBarFontSize") scaleBar.attr("font-size", value);
else if (id === "styleScaleBarPositionX") scaleBar.attr("data-x", value);
else if (id === "styleScaleBarPositionY") scaleBar.attr("data-y", value);
else if (id === "styleScaleBarLabel") scaleBar.attr("data-label", value);
else if (id === "styleScaleBarBackgroundOpacityInput") scaleBarBack.attr("opacity", value);
else if (id === "styleScaleBarBackgroundFillInput") scaleBarBack.attr("fill", value);
else if (id === "styleScaleBarBackgroundStrokeInput") scaleBarBack.attr("stroke", value);
else if (id === "styleScaleBarBackgroundStrokeWidth") scaleBarBack.attr("stroke-width", value);
else if (id === "styleScaleBarBackgroundFilter") scaleBarBack.attr("filter", value);
else if (id === "styleScaleBarBackgroundPaddingTop") scaleBarBack.attr("data-top", value);
else if (id === "styleScaleBarBackgroundPaddingRight") scaleBarBack.attr("data-right", value);
else if (id === "styleScaleBarBackgroundPaddingBottom") scaleBarBack.attr("data-bottom", value);
else if (id === "styleScaleBarBackgroundPaddingLeft") scaleBarBack.attr("data-left", value);
if (
[
"styleScaleBarSize",
"styleScaleBarPositionX",
"styleScaleBarPositionY",
"styleScaleBarLabel",
"styleScaleBarBackgroundPaddingLeft",
"styleScaleBarBackgroundPaddingTop",
"styleScaleBarBackgroundPaddingRight",
"styleScaleBarBackgroundPaddingBottom"
].includes(id)
) {
drawScaleBar(scaleBar, scale);
fitScaleBar(scaleBar, svgWidth, svgHeight);
}
});
function updateElements() { function updateElements() {
// burgIcons to desired size // burgIcons to desired size
burgIcons.selectAll("g").each(function () { burgIcons.selectAll("g").each(function () {

View file

@ -140,6 +140,9 @@ function applyStyleWithUiRefresh(style) {
invokeActiveZooming(); invokeActiveZooming();
setPresetRemoveButtonVisibiliy(); setPresetRemoveButtonVisibiliy();
drawScaleBar(scaleBar, scale);
fitScaleBar(scaleBar, svgWidth, svgHeight);
} }
function addStylePreset() { function addStylePreset() {
@ -239,7 +242,18 @@ function addStylePreset() {
"#oceanLayers": ["filter", "layers"], "#oceanLayers": ["filter", "layers"],
"#oceanBase": ["fill"], "#oceanBase": ["fill"],
"#oceanicPattern": ["href", "opacity"], "#oceanicPattern": ["href", "opacity"],
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"], "#terrs #oceanHeights": [
"data-render",
"opacity",
"scheme",
"terracing",
"skip",
"relax",
"curve",
"filter",
"mask"
],
"#terrs #landHeights": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
"#legend": [ "#legend": [
"data-size", "data-size",
"font-size", "font-size",
@ -301,7 +315,19 @@ function addStylePreset() {
], ],
"#fogging": ["opacity", "fill", "filter"], "#fogging": ["opacity", "fill", "filter"],
"#vignette": ["opacity", "fill", "filter"], "#vignette": ["opacity", "fill", "filter"],
"#vignette-rect": ["x", "y", "width", "height", "rx", "ry", "filter"] "#vignette-rect": ["x", "y", "width", "height", "rx", "ry", "filter"],
"#scaleBar": ["opacity", "fill", "font-size", "data-bar-size", "data-x", "data-y", "data-label"],
"#scaleBarBack": [
"opacity",
"fill",
"stroke",
"stroke-width",
"filter",
"data-top",
"data-right",
"data-bottom",
"data-left"
]
}; };
for (const selector in attributes) { for (const selector in attributes) {

View file

@ -24,13 +24,6 @@ function editUnits() {
byId("heightExponentInput").addEventListener("input", changeHeightExponent); byId("heightExponentInput").addEventListener("input", changeHeightExponent);
byId("heightExponentOutput").addEventListener("input", changeHeightExponent); byId("heightExponentOutput").addEventListener("input", changeHeightExponent);
byId("temperatureScale").addEventListener("change", changeTemperatureScale); byId("temperatureScale").addEventListener("change", changeTemperatureScale);
byId("barSizeOutput").addEventListener("input", renderScaleBar);
byId("barSizeInput").addEventListener("input", renderScaleBar);
byId("barLabel").addEventListener("input", renderScaleBar);
byId("barPosX").addEventListener("input", fitScaleBar);
byId("barPosY").addEventListener("input", fitScaleBar);
byId("barBackOpacity").addEventListener("input", changeScaleBarOpacity);
byId("barBackColor").addEventListener("input", changeScaleBarColor);
byId("populationRateOutput").addEventListener("input", changePopulationRate); byId("populationRateOutput").addEventListener("input", changePopulationRate);
byId("populationRateInput").addEventListener("change", changePopulationRate); byId("populationRateInput").addEventListener("change", changePopulationRate);
@ -84,14 +77,6 @@ function editUnits() {
if (layerIsOn("toggleTemp")) drawTemp(); if (layerIsOn("toggleTemp")) drawTemp();
} }
function changeScaleBarOpacity() {
scaleBar.select("rect").attr("opacity", this.value);
}
function changeScaleBarColor() {
scaleBar.select("rect").attr("fill", this.value);
}
function changePopulationRate() { function changePopulationRate() {
populationRate = +this.value; populationRate = +this.value;
} }
@ -129,19 +114,6 @@ function editUnits() {
localStorage.removeItem("heightExponent"); localStorage.removeItem("heightExponent");
calculateTemperatures(); calculateTemperatures();
// scale bar
barSizeOutput.value = barSizeInput.value = 2;
barLabel.value = "";
barBackOpacity.value = 0.2;
barBackColor.value = "#ffffff";
barPosX.value = barPosY.value = 99;
localStorage.removeItem("barSize");
localStorage.removeItem("barLabel");
localStorage.removeItem("barBackOpacity");
localStorage.removeItem("barBackColor");
localStorage.removeItem("barPosX");
localStorage.removeItem("barPosY");
renderScaleBar(); renderScaleBar();
// population // population

View file

@ -286,14 +286,25 @@
"href": "./images/kiwiroo.png", "href": "./images/kiwiroo.png",
"opacity": 0.4 "opacity": 0.4
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": null, "data-render": 0,
"scheme": "bright", "opacity": 1,
"scheme": "light",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": "url(#filter-sepia)",
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1,
"scheme": "light",
"terracing": 0, "terracing": 0,
"skip": 2, "skip": 2,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#blur3)", "filter": "url(#filter-sepia)",
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "0%", "rx": "0%",
"ry": "0%", "ry": "0%",
"filter": "blur(50px)" "filter": "blur(50px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,13 +286,24 @@
"href": "", "href": "",
"opacity": 1 "opacity": 1
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": null, "data-render": 1,
"opacity": 1,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 0, "skip": 0,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(30px)" "filter": "blur(30px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 9,
"data-bar-size": 1.5,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": null,
"data-top": 18,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -288,13 +288,24 @@
"href": "", "href": "",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.5,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.5, "opacity": 0.5,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -400,5 +411,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 8,
"data-bar-size": 1.5,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "",
"data-top": 17,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,14 +286,25 @@
"href": "", "href": "",
"opacity": 0.15 "opacity": 0.15
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 1, "opacity": 1,
"scheme": "monochrome", "scheme": "olive",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.8,
"scheme": "livid",
"terracing": 6, "terracing": 6,
"skip": 0, "skip": 0,
"relax": 2, "relax": 2,
"curve": 0, "curve": "curveBasisClosed",
"filter": "", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#d0d0dc",
"font-size": 11,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.3,
"fill": "#05001f",
"stroke": "#000000",
"stroke-width": 1,
"filter": "",
"data-top": 23,
"data-right": 18,
"data-bottom": 18,
"data-left": 12
} }
} }

View file

@ -286,13 +286,24 @@
"href": "./images/pattern1.png", "href": "./images/pattern1.png",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": null, "data-render": 0,
"opacity": 1,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -288,14 +288,25 @@
"href": "./images/pattern3.png", "href": "./images/pattern3.png",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"opacity": 1, "data-render": 1,
"opacity": 0.8,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.8,
"scheme": "bright", "scheme": "bright",
"terracing": 2, "terracing": 2,
"skip": 1, "skip": 1,
"relax": 2, "relax": 2,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#filter-grayscale)", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -400,5 +411,25 @@
"rx": "10%", "rx": "10%",
"ry": "10%", "ry": "10%",
"filter": "blur(30px)" "filter": "blur(30px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#121212",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#607671",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,13 +286,24 @@
"href": "./images/pattern1.png", "href": "./images/pattern1.png",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.4, "opacity": 0.4,
"scheme": "light", "scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": "url(#turbulence)",
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.4,
"scheme": "bright",
"terracing": 10, "terracing": 10,
"skip": 5, "skip": 5,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#turbulence)", "filter": "url(#turbulence)",
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -281,14 +281,25 @@
"href": "", "href": "",
"opacity": 0.2 "opacity": 0.2
}, },
"#terrs": { "#terrs #oceanHeights": {
"data-render": 1,
"opacity": 1, "opacity": 1,
"scheme": "monochrome", "scheme": "monochrome",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 0,
"relax": 0, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#blur3)", "filter": "url(#turbulence)",
"mask": null
},
"#terrs #landHeights": {
"opacity": 1,
"scheme": "monochrome",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": "url(#turbulence)",
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -394,5 +405,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#ffffff",
"font-size": 8,
"data-bar-size": 1.5,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "",
"data-top": 18,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,14 +286,25 @@
"href": "", "href": "",
"opacity": 0.3 "opacity": 0.3
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.5,
"scheme": "livid",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 1, "opacity": 1,
"scheme": "livid", "scheme": "livid",
"terracing": 0, "terracing": 0,
"skip": 10, "skip": 10,
"relax": 0, "relax": 0,
"curve": 0, "curve": "curveBasisClosed",
"filter": "url(#blurFilter)", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#b5c1d4",
"font-size": 11,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.5,
"fill": "#0a1e24",
"stroke": "#40547a",
"stroke-width": 1,
"filter": "",
"data-top": 22,
"data-right": 15,
"data-bottom": 16,
"data-left": 10
} }
} }

View file

@ -286,14 +286,25 @@
"href": "./images/kiwiroo.png", "href": "./images/kiwiroo.png",
"opacity": 0.3 "opacity": 0.3
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 0,
"opacity": 0.7,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.7, "opacity": 0.7,
"scheme": "bright", "scheme": "bright",
"terracing": 0, "terracing": 0,
"skip": 2, "skip": 2,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": "", "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
"#legend": { "#legend": {
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(30px)" "filter": "blur(30px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -286,13 +286,24 @@
"href": "./images/kiwiroo.png", "href": "./images/kiwiroo.png",
"opacity": 0.5 "opacity": 0.5
}, },
"#terrs": { "#terrs > #oceanHeights": {
"data-render": 1,
"opacity": 0.5,
"scheme": "light",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": "curveBasisClosed",
"filter": null,
"mask": null
},
"#terrs > #landHeights": {
"opacity": 0.5, "opacity": 0.5,
"scheme": "light", "scheme": "light",
"terracing": 0, "terracing": 0,
"skip": 5, "skip": 5,
"relax": 1, "relax": 1,
"curve": 0, "curve": "curveBasisClosed",
"filter": null, "filter": null,
"mask": "url(#land)" "mask": "url(#land)"
}, },
@ -398,5 +409,25 @@
"rx": "5%", "rx": "5%",
"ry": "5%", "ry": "5%",
"filter": "blur(20px)" "filter": "blur(20px)"
},
"#scaleBar": {
"opacity": 1,
"fill": "#353540",
"font-size": 10,
"data-bar-size": 2,
"data-x": 99,
"data-y": 99,
"data-label": ""
},
"#scaleBarBack": {
"opacity": 0.2,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 1,
"filter": "url(#blur5)",
"data-top": 20,
"data-right": 15,
"data-bottom": 15,
"data-left": 10
} }
} }

View file

@ -329,7 +329,7 @@ function drawCellsValue(data) {
function drawPolygons(data) { function drawPolygons(data) {
const max = d3.max(data), const max = d3.max(data),
min = d3.min(data), min = d3.min(data),
scheme = getColorScheme(terrs.attr("scheme")); scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
data = data.map(d => 1 - normalize(d, min, max)); data = data.map(d => 1 - normalize(d, min, max));
debug.selectAll("polygon").remove(); debug.selectAll("polygon").remove();
@ -338,7 +338,7 @@ function drawPolygons(data) {
.data(data) .data(data)
.enter() .enter()
.append("polygon") .append("polygon")
.attr("points", (d, i) => getPackPolygon(i)) .attr("points", (d, i) => getGridPolygon(i))
.attr("fill", d => scheme(d)) .attr("fill", d => scheme(d))
.attr("stroke", d => scheme(d)); .attr("stroke", d => scheme(d));
} }

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
// version and caching control // version and caching control
const version = "1.95.05"; // generator version, update each time const version = "1.96.00"; // generator version, update each time
{ {
document.title += " v" + version; document.title += " v" + version;
@ -28,6 +28,8 @@ const version = "1.95.05"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>Ability to render ocean heightmap</li>
<li>Scale bar styling features</li>
<li>Vignette visual layer and vignette styling options</li> <li>Vignette visual layer and vignette styling options</li>
<li>Ability to define custom heightmap color scheme</li> <li>Ability to define custom heightmap color scheme</li>
<li>New style preset Night and new heightmap color schemes</li> <li>New style preset Night and new heightmap color schemes</li>