diff --git a/index.css b/index.css
index 457cc5f2..ad5ec3eb 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,6 +213,12 @@ i.icon-lock {
font-size: 11px;
}
+#statesTree circle {
+ filter: url(#dropShadow05);
+ stroke: #666666;
+ stroke-width: 1;
+}
+
#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..a7d0f4f6 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -377,7 +377,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(() => {
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";