diff --git a/index.css b/index.css
index 457cc5f2..2f077fa0 100644
--- a/index.css
+++ b/index.css
@@ -205,6 +205,7 @@ i.icon-lock {
}
#religionHierarchy text,
+#statesTree text,
#provincesTree text {
pointer-events: none;
user-select: none;
@@ -212,11 +213,13 @@ i.icon-lock {
font-size: 11px;
}
-#provincesTree .selected {
- stroke: #c13119;
- stroke-width: 2;
+#statesTree circle {
+ filter: url(#dropShadow05);
+ stroke: #666666;
+ stroke-width: 1;
}
+#statesTree circle.selected,
#provincesTree .selected {
stroke: #c13119;
stroke-width: 2;
diff --git a/index.html b/index.html
index f5031177..d9b6a5e7 100644
--- a/index.html
+++ b/index.html
@@ -2503,6 +2503,7 @@
+
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index 80d26d6e..52041e0c 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -157,7 +157,7 @@
if (b.capital) b.population = rn(b.population * 1.3, 3); // increase capital population
if (port) {
- b.population *= b.population * 1.3; // increase port population
+ b.population = b.population * 1.3; // increase port population
const e = cells.v[i].filter(v => vertices.c[v].some(c => c === cells.haven[i])); // vertices of common edge
b.x = rn((vertices.p[e[0]][0] + vertices.p[e[1]][0]) / 2, 2);
b.y = rn((vertices.p[e[0]][1] + vertices.p[e[1]][1]) / 2, 2);
diff --git a/modules/save-and-load.js b/modules/save-and-load.js
index 29d65c67..6c4741f3 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -162,11 +162,11 @@ function getMapData() {
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
const params = [version, license, dateString, seed, graphWidth, graphHeight].join("|");
- const options = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value,
+ const options = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value,
heightUnit.value, heightExponentInput.value, temperatureScale.value,
- barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value,
+ barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value,
barPosX.value, barPosY.value, populationRate.value, urbanization.value,
- mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value,
+ mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value,
temperaturePoleOutput.value, precOutput.value, JSON.stringify(winds),
mapName.value].join("|");
const coords = JSON.stringify(mapCoordinates);
@@ -240,6 +240,7 @@ function saveGeoJSON() {
Cells: saveGeoJSON_Cells,
Routes: saveGeoJSON_Roads,
Rivers: saveGeoJSON_Rivers,
+ Markers: saveGeoJSON_Markers,
Close: function() {$(this).dialog("close");}
}
});
@@ -377,7 +378,7 @@ const saveReminder = function() {
"Safety is number one priority. Please save the map",
"Don't forget to save your map on a regular basis!",
"Just a gentle reminder for you to save the map",
- "Please forget to save your progress (saving as .map is the best option)",
+ "Please don't forget to save your progress (saving as .map is the best option)",
"Don't want to be reminded about need to save? Press CTRL+Q"];
saveReminder.reminder = setInterval(() => {
@@ -430,12 +431,13 @@ function saveGeoJSON_Cells() {
data += "["+x+","+y+"]";
data += "]] },\n \"properties\": {\n";
- let height = parseInt(getFriendlyHeight(cells.h[i]));
+ let height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]]));
data += " \"id\": \""+i+"\",\n";
data += " \"height\": \""+height+"\",\n";
data += " \"biome\": \""+cells.biome[i]+"\",\n";
- data += " \"population\": \""+cells.pop[i]+"\",\n";
+ data += " \"type\": \""+pack.features[cells.f[i]].type+"\",\n";
+ data += " \"population\": \""+getFriendlyPopulation(i)+"\",\n";
data += " \"state\": \""+cells.state[i]+"\",\n";
data += " \"province\": \""+cells.province[i]+"\",\n";
data += " \"culture\": \""+cells.culture[i]+"\",\n";
@@ -456,6 +458,34 @@ function saveGeoJSON_Cells() {
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
+function saveGeoJSON_Markers() {
+
+ let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
+
+ markers._groups[0][0].childNodes.forEach(n => {
+ let x = mapCoordinates.lonW + (n.dataset.x / graphWidth) * mapCoordinates.lonT;
+ let y = mapCoordinates.latN - (n.dataset.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
+
+ data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Point\", \"coordinates\": ["+x+", "+y+"]";
+ data += " },\n \"properties\": {\n";
+ data += " \"id\": \""+n.id+"\",\n";
+ data += " \"type\": \""+n.dataset.id.substring(8)+"\"\n";
+ data +=" }\n},\n";
+
+ });
+ data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
+ data += "]}";
+
+ const dataBlob = new Blob([data], {type: "application/json"});
+ const url = window.URL.createObjectURL(dataBlob);
+ const link = document.createElement("a");
+ document.body.appendChild(link);
+ link.download = getFileName("Markers") + ".geojson";
+ link.href = url;
+ link.click();
+ window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
+}
+
function uploadFile(file, callback) {
uploadFile.timeStart = performance.now();
@@ -822,4 +852,4 @@ function parseLoadedData(data) {
});
}
-}
\ No newline at end of file
+}
diff --git a/modules/ui/burgs-editor.js b/modules/ui/burgs-editor.js
index 22c3bf37..e23a69a5 100644
--- a/modules/ui/burgs-editor.js
+++ b/modules/ui/burgs-editor.js
@@ -259,7 +259,7 @@ function editBurgs() {
data += b.name + ",";
const province = pack.cells.province[b.cell];
data += province ? pack.provinces[province].fullName + "," : ",";
- data += b.state ? pack.states[b.state].fullName : pack.states[b.state].name + ",";
+ data += b.state ? pack.states[b.state].fullName +"," : pack.states[b.state].name + ",";
data += pack.cultures[b.culture].name + ",";
data += pack.religions[pack.cells.religion[b.cell]].name + ",";
data += rn(b.population * populationRate.value * urbanization.value) + ",";
@@ -267,11 +267,11 @@ function editBurgs() {
// add geography data
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ",";
data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ","; // this is inverted in QGIS otherwise
- data += parseInt(getHeight(pack.cells.h[b.cell])) + ",";
+ data += parseInt(getFriendlyHeight([b.x,b.y])) + ",";
// add status data
- data += b.capital ? "capital," : ",";
- data += b.port ? "port\n" : "\n";
+ data += b.capital ? "true," : "false,";
+ data += b.port ? "true\n" : "false\n";
});
const dataBlob = new Blob([data], {type: "text/plain"});
diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js
index 69808fc4..1b927509 100644
--- a/modules/ui/provinces-editor.js
+++ b/modules/ui/provinces-editor.js
@@ -30,7 +30,7 @@ function editProvinces() {
document.getElementById("provincesManuallyApply").addEventListener("click", applyProvincesManualAssignent);
document.getElementById("provincesManuallyCancel").addEventListener("click", () => exitProvincesManualAssignment());
document.getElementById("provincesAdd").addEventListener("click", enterAddProvinceMode);
-
+
body.addEventListener("click", function(ev) {
if (customization) return;
const el = ev.target, cl = el.classList, line = el.parentNode, p = +line.dataset.id;
@@ -373,7 +373,7 @@ function editProvinces() {
: d => d.rural + d.urban;
const newRoot = d3.stratify().parentId(d => d.state)(data).sum(value);
- node.data(treeLayout(newRoot).leaves())
+ node.data(treeLayout(newRoot).leaves());
node.select("rect").transition().duration(1500)
.attr("x", d => d.x0).attr("y", d => d.y0)
diff --git a/modules/ui/religions-editor.js b/modules/ui/religions-editor.js
index 589381b8..795e5799 100644
--- a/modules/ui/religions-editor.js
+++ b/modules/ui/religions-editor.js
@@ -151,7 +151,7 @@ function editReligions() {
const urban = r.urban * populationRate.value * urbanization.value;
const population = rural + urban > 0 ? ". " + si(rn(rural + urban)) + " believers" : ". Extinct";
info.innerHTML = `${r.name}${type}${form}${population}`;
- tip("Drag to change parent. Hold CTRL and click to change abbrebiation");
+ tip("Drag to change parent. Hold CTRL and click to change abbreviation");
}
const el = body.querySelector(`div[data-id='${religion}']`);
diff --git a/modules/ui/states-editor.js b/modules/ui/states-editor.js
index c041e18f..75bdfb15 100644
--- a/modules/ui/states-editor.js
+++ b/modules/ui/states-editor.js
@@ -24,6 +24,7 @@ function editStates() {
document.getElementById("statesEditorRefresh").addEventListener("click", refreshStatesEditor);
document.getElementById("statesLegend").addEventListener("click", toggleLegend);
document.getElementById("statesPercentage").addEventListener("click", togglePercentageMode);
+ document.getElementById("statesChart").addEventListener("click", showStatesChart);
document.getElementById("statesRegenerate").addEventListener("click", openRegenerationMenu);
document.getElementById("statesRegenerateBack").addEventListener("click", exitRegenerationMenu);
document.getElementById("statesRecalculate").addEventListener("click", () => recalculateStates(true));
@@ -387,6 +388,102 @@ function editStates() {
}
}
+ function showStatesChart() {
+ // build hierarchy tree
+ const data = pack.states.filter(s => !s.removed);
+ const root = d3.stratify().id(d => d.i).parentId(d => d.i ? 0 : null)(data)
+ .sum(d => d.area).sort((a, b) => b.value - a.value);
+
+ const width = 150 + 200 * uiSizeOutput.value, height = 150 + 200 * uiSizeOutput.value;
+ const margin = {top: 0, right: -50, bottom: 0, left: -50};
+ const w = width - margin.left - margin.right;
+ const h = height - margin.top - margin.bottom;
+ const treeLayout = d3.pack().size([w, h]).padding(3);
+
+ // prepare svg
+ alertMessage.innerHTML = `
`;
+ alertMessage.innerHTML += `
`;
+ const svg = d3.select("#alertMessage").insert("svg", "#statesInfo").attr("id", "statesTree")
+ .attr("width", width).attr("height", height).style("font-family", "Almendra SC")
+ .attr("text-anchor", "middle").attr("dominant-baseline", "central");
+ const graph = svg.append("g").attr("transform", `translate(-50, 0)`);
+ document.getElementById("statesTreeType").addEventListener("change", updateChart);
+
+ treeLayout(root);
+
+ const node = graph.selectAll("g").data(root.leaves()).enter()
+ .append("g").attr("transform", d => `translate(${d.x},${d.y})`)
+ .attr("data-id", d => d.data.id)
+ .on("mouseenter", d => showInfo(event, d))
+ .on("mouseleave", d => hideInfo(event, d));
+
+ node.append("circle").attr("fill", d => d.data.color).attr("r", d => d.r);
+
+ const exp = /(?=[A-Z][^A-Z])/g;
+ const lp = n => d3.max(n.split(exp).map(p => p.length)) + 1; // longest name part + 1
+
+ node.append("text")
+ .style("font-size", d => rn(d.r ** .97 * 4 / lp(d.data.name), 2) + "px")
+ .selectAll("tspan").data(d => d.data.name.split(exp))
+ .join("tspan").attr("x", 0).text(d => d)
+ .attr("dy", (d, i, n) => `${i ? 1 : (n.length-1) / -2}em`);
+
+ function showInfo(ev, d) {
+ d3.select(ev.target).select("circle").classed("selected", 1);
+ const state = d.data.fullName;
+
+ const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
+ const area = d.data.area * (distanceScaleInput.value ** 2) + unit;
+ const rural = rn(d.data.rural * populationRate.value);
+ const urban = rn(d.data.urban * populationRate.value * urbanization.value);
+
+ const option = statesTreeType.value;
+ const value = option === "area" ? "Area: " + area
+ : option === "rural" ? "Rural population: " + si(rural)
+ : option === "urban" ? "Urban population: " + si(urban)
+ : option === "burgs" ? "Burgs number: " + d.data.burgs
+ : "Population: " + si(rural + urban);
+
+ statesInfo.innerHTML = `${state}. ${value}`;
+ stateHighlightOn(ev);
+ }
+
+ function hideInfo(ev) {
+ stateHighlightOff(ev);
+ if (!document.getElementById("statesInfo")) return;
+ statesInfo.innerHTML = "";
+ d3.select(ev.target).select("circle").classed("selected", 0);
+ }
+
+ function updateChart() {
+ const value = this.value === "area" ? d => d.area
+ : this.value === "rural" ? d => d.rural
+ : this.value === "urban" ? d => d.urban
+ : this.value === "burgs" ? d => d.burgs
+ : d => d.rural + d.urban;
+
+ root.sum(value);
+ node.data(treeLayout(root).leaves());
+
+ node.transition().duration(1500).attr("transform", d => `translate(${d.x},${d.y})`)
+ node.select("circle").transition().duration(1500).attr("r", d => d.r);
+ node.select("text").transition().duration(1500)
+ .style("font-size", d => rn(d.r ** .97 * 4 / lp(d.data.name), 2) + "px");
+ }
+
+ $("#alert").dialog({
+ title: "States bubble chart", width: fitContent(),
+ position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {},
+ close: () => {alertMessage.innerHTML = "";}
+ });
+ }
+
function openRegenerationMenu() {
statesBottom.querySelectorAll(":scope > button").forEach(el => el.style.display = "none");
statesRegenerateButtons.style.display = "block";