diff --git a/index.css b/index.css
index 4bd1a328..505b0716 100644
--- a/index.css
+++ b/index.css
@@ -263,7 +263,7 @@ i.icon-lock {
}
#labels {
- text-anchor: start;
+ text-anchor: middle;
dominant-baseline: central;
cursor: pointer;
}
diff --git a/index.html b/index.html
index b13b0df4..752da95b 100644
--- a/index.html
+++ b/index.html
@@ -138,7 +138,7 @@
}
-
+
@@ -7947,7 +7947,7 @@
-
+
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index d41ea9bc..ff433f46 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -502,223 +502,6 @@ window.BurgsAndStates = (function () {
TIME && console.timeEnd("updateCulturesForBurgsAndStates");
};
- // calculate and draw curved state labels for a list of states
- const drawStateLabelsOld = function (list) {
- TIME && console.time("drawStateLabels");
- const {cells, features, states} = pack;
- const paths = []; // text paths
- lineGen.curve(d3.curveBundle.beta(1));
- const mode = options.stateLabelsMode || "auto";
-
- for (const s of states) {
- if (!s.i || s.removed || s.lock || !s.cells || (list && !list.includes(s.i))) continue;
-
- const used = [];
- const visualCenter = findCell(s.pole[0], s.pole[1]);
- const start = cells.state[visualCenter] === s.i ? visualCenter : s.center;
- const hull = getHull(start, s.i, s.cells / 10);
- const points = [...hull].map(v => pack.vertices.p[v]);
- const delaunay = Delaunator.from(points);
- const chain = connectCenters(voronoi.vertices, s.pole[1]);
- const voronoi = new Voronoi(delaunay, points, points.length);
- const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i % 15 === 0 || i + 1 === chain.length);
- paths.push([s.i, relaxed]);
-
- function getHull(start, state, maxLake) {
- const queue = [start];
- const hull = new Set();
-
- while (queue.length) {
- const q = queue.pop();
- const sameStateNeibs = cells.c[q].filter(c => cells.state[c] === state);
-
- cells.c[q].forEach(function (c, d) {
- const passableLake = features[cells.f[c]].type === "lake" && features[cells.f[c]].cells < maxLake;
- if (cells.b[c] || (cells.state[c] !== state && !passableLake)) return hull.add(cells.v[q][d]);
-
- const hasCoadjacentSameStateCells = sameStateNeibs.some(neib => cells.c[c].includes(neib));
- if (hull.size > 20 && !hasCoadjacentSameStateCells && !passableLake) return hull.add(cells.v[q][d]);
-
- if (used[c]) return;
- used[c] = 1;
- queue.push(c);
- });
- }
-
- return hull;
- }
-
- function connectCenters(c, y) {
- // check if vertex is inside the area
- const inside = c.p.map(function (p) {
- if (p[0] <= 0 || p[1] <= 0 || p[0] >= graphWidth || p[1] >= graphHeight) return false; // out of the screen
- return used[findCell(p[0], p[1])];
- });
-
- const pointsInside = d3.range(c.p.length).filter(i => inside[i]);
- if (!pointsInside.length) return [0];
- const h = c.p.length < 200 ? 0 : c.p.length < 600 ? 0.5 : 1; // power of horyzontality shift
- const end =
- pointsInside[
- d3.scan(
- pointsInside,
- (a, b) => c.p[a][0] - c.p[b][0] + (Math.abs(c.p[a][1] - y) - Math.abs(c.p[b][1] - y)) * h
- )
- ]; // left point
- const start =
- pointsInside[
- d3.scan(
- pointsInside,
- (a, b) => c.p[b][0] - c.p[a][0] - (Math.abs(c.p[b][1] - y) - Math.abs(c.p[a][1] - y)) * h
- )
- ]; // right point
-
- // connect leftmost and rightmost points with shortest path
- const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
- const cost = [],
- from = [];
- queue.queue({e: start, p: 0});
-
- while (queue.length) {
- const next = queue.dequeue(),
- n = next.e,
- p = next.p;
- if (n === end) break;
-
- for (const v of c.v[n]) {
- if (v === -1) continue;
- const totalCost = p + (inside[v] ? 1 : 100);
- if (from[v] || totalCost >= cost[v]) continue;
- cost[v] = totalCost;
- from[v] = n;
- queue.queue({e: v, p: totalCost});
- }
- }
-
- // restore path
- const chain = [end];
- let cur = end;
- while (cur !== start) {
- cur = from[cur];
- if (inside[cur]) chain.push(cur);
- }
- return chain;
- }
- }
-
- void (function drawLabels() {
- const g = labels.select("#states");
- const t = defs.select("#textPaths");
- const displayed = layerIsOn("toggleLabels");
- if (!displayed) toggleLabels();
-
- // remove state labels to be redrawn
- for (const state of pack.states) {
- if (!state.i || state.removed || state.lock) continue;
- if (list && !list.includes(state.i)) continue;
-
- byId(`stateLabel${state.i}`)?.remove();
- byId(`textPath_stateLabel${state.i}`)?.remove();
- }
-
- const example = g.append("text").attr("x", 0).attr("x", 0).text("Average");
- const letterLength = example.node().getComputedTextLength() / 7; // average length of 1 letter
-
- paths.forEach(p => {
- const id = p[0];
- const state = states[p[0]];
- const {name, fullName} = state;
-
- const path = p[1].length > 1 ? round(lineGen(p[1])) : `M${p[1][0][0] - 50},${p[1][0][1]}h${100}`;
- const textPath = t
- .append("path")
- .attr("d", path)
- .attr("id", "textPath_stateLabel" + id);
- const pathLength = p[1].length > 1 ? textPath.node().getTotalLength() / letterLength : 0; // path length in letters
-
- const [lines, ratio] = getLines(mode, name, fullName, pathLength);
-
- // prolongate path if it's too short
- if (pathLength && pathLength < lines[0].length) {
- const points = p[1];
- const f = points[0];
- const l = points[points.length - 1];
- const [dx, dy] = [l[0] - f[0], l[1] - f[1]];
- const mod = Math.abs((letterLength * lines[0].length) / dx) / 2;
- points[0] = [rn(f[0] - dx * mod), rn(f[1] - dy * mod)];
- points[points.length - 1] = [rn(l[0] + dx * mod), rn(l[1] + dy * mod)];
- textPath.attr("d", round(lineGen(points)));
- }
-
- example.attr("font-size", ratio + "%");
- const top = (lines.length - 1) / -2; // y offset
- const spans = lines.map((l, d) => {
- example.text(l);
- const left = example.node().getBBox().width / -2; // x offset
- return `${l}`;
- });
-
- const el = g
- .append("text")
- .attr("id", "stateLabel" + id)
- .append("textPath")
- .attr("xlink:href", "#textPath_stateLabel" + id)
- .attr("startOffset", "50%")
- .attr("font-size", ratio + "%")
- .node();
-
- el.insertAdjacentHTML("afterbegin", spans.join(""));
- if (mode === "full" || lines.length === 1) return;
-
- // check whether multilined label is generally inside the state. If no, replace with short name label
- const cs = pack.cells.state;
- const b = el.parentNode.getBBox();
- const c1 = () => +cs[findCell(b.x, b.y)] === id;
- const c2 = () => +cs[findCell(b.x + b.width / 2, b.y)] === id;
- const c3 = () => +cs[findCell(b.x + b.width, b.y)] === id;
- const c4 = () => +cs[findCell(b.x + b.width, b.y + b.height)] === id;
- const c5 = () => +cs[findCell(b.x + b.width / 2, b.y + b.height)] === id;
- const c6 = () => +cs[findCell(b.x, b.y + b.height)] === id;
- if (c1() + c2() + c3() + c4() + c5() + c6() > 3) return; // generally inside => exit
-
- // move to one-line name
- const text = pathLength > fullName.length * 1.8 ? fullName : name;
- example.text(text);
- const left = example.node().getBBox().width / -2; // x offset
- el.innerHTML = `${text}`;
-
- const correctedRatio = minmax(rn((pathLength / text.length) * 60), 40, 130);
- el.setAttribute("font-size", correctedRatio + "%");
- });
-
- example.remove();
- if (!displayed) toggleLabels();
- })();
-
- function getLines(mode, name, fullName, pathLength) {
- // short name
- if (mode === "short" || (mode === "auto" && pathLength < name.length)) {
- const lines = splitInTwo(name);
- const ratio = pathLength / lines[0].length;
- return [lines, minmax(rn(ratio * 60), 50, 150)];
- }
-
- // full name: one line
- if (pathLength > fullName.length * 2.5) {
- const lines = [fullName];
- const ratio = pathLength / lines[0].length;
- return [lines, minmax(rn(ratio * 70), 70, 170)];
- }
-
- // full name: two lines
- const lines = splitInTwo(fullName);
- const ratio = pathLength / lines[0].length;
- return [lines, minmax(rn(ratio * 60), 70, 150)];
- }
-
- TIME && console.timeEnd("drawStateLabels");
- };
-
// calculate states data like area, population etc.
const collectStatistics = function () {
TIME && console.time("collectStatistics");
diff --git a/modules/dynamic/auto-update.js b/modules/dynamic/auto-update.js
index 5bcb2c2b..fcd12273 100644
--- a/modules/dynamic/auto-update.js
+++ b/modules/dynamic/auto-update.js
@@ -698,4 +698,11 @@ export function resolveVersionConflicts(version) {
}
});
}
+
+ if (version < 1.92) {
+ // v1.92 change labels text-anchor from 'start' to 'middle'
+ labels.selectAll("tspan").each(function () {
+ this.setAttribute("x", 0);
+ });
+ }
}
diff --git a/modules/renderers/drawStatelabels.js b/modules/renderers/drawStatelabels.js
index d51e3e66..0c82a4cd 100644
--- a/modules/renderers/drawStatelabels.js
+++ b/modules/renderers/drawStatelabels.js
@@ -1,6 +1,7 @@
"use strict";
-function drawStateLabels() {
+// list - an optional array of stateIds to regenerate
+function drawStateLabels(list) {
console.time("drawStateLabels");
const {cells, states, features} = pack;
@@ -22,7 +23,8 @@ function drawStateLabels() {
const labelPaths = [];
for (const state of states) {
- if (!state.i || state.removed || state.locked) continue;
+ if (!state.i || state.removed || state.lock) continue;
+ if (list && !list.includes(state.i)) continue;
const offset = getOffsetWidth(state.cells);
const maxLakeSize = state.cells / 50;
@@ -115,17 +117,17 @@ function drawStateLabels() {
const textGroup = d3.select("g#labels > g#states");
const pathGroup = d3.select("defs > g#deftemp > g#textPaths");
- const testLabel = textGroup.append("text").attr("x", 0).attr("x", 0).text("Example");
+ const testLabel = textGroup.append("text").attr("x", 0).attr("y", 0).text("Example");
const letterLength = testLabel.node().getComputedTextLength() / 7; // approximate length of 1 letter
testLabel.remove();
for (const [stateId, pathPoints] of labelPaths) {
const state = states[stateId];
- if (!state.i || state.removed) throw new Error("State must not be neutral");
+ if (!state.i || state.removed) throw new Error("State must not be neutral or removed");
if (pathPoints.length < 2) throw new Error("Label path must have at least 2 points");
- textGroup.select("#textPath_stateLabel" + stateId).remove();
- pathGroup.select("#stateLabel" + stateId).remove();
+ textGroup.select("#stateLabel" + stateId).remove();
+ pathGroup.select("#textPath_stateLabel" + stateId).remove();
const textPath = pathGroup
.append("path")
diff --git a/modules/ui/labels-editor.js b/modules/ui/labels-editor.js
index 8bd04cdd..d19de7ae 100644
--- a/modules/ui/labels-editor.js
+++ b/modules/ui/labels-editor.js
@@ -78,7 +78,9 @@ function editLabel() {
}
function updateValues(textPath) {
- document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")].map(tspan => tspan.textContent).join("|");
+ document.getElementById("labelText").value = [...textPath.querySelectorAll("tspan")]
+ .map(tspan => tspan.textContent)
+ .join("|");
document.getElementById("labelStartOffset").value = parseFloat(textPath.getAttribute("startOffset"));
document.getElementById("labelRelativeSize").value = parseFloat(textPath.getAttribute("font-size"));
}
@@ -298,22 +300,15 @@ function editLabel() {
function changeText() {
const input = document.getElementById("labelText").value;
const el = elSelected.select("textPath").node();
- const example = d3.select(elSelected.node().parentNode).append("text").attr("x", 0).attr("x", 0).attr("font-size", el.getAttribute("font-size")).node();
const lines = input.split("|");
- const top = (lines.length - 1) / -2; // y offset
- const inner = lines
- .map((l, d) => {
- example.innerHTML = l;
- const left = example.getBBox().width / -2; // x offset
- return `${l}`;
- })
- .join("");
+ if (lines.length > 1) {
+ const top = (lines.length - 1) / -2; // y offset
+ el.innerHTML = lines.map((line, index) => `${line}`).join("");
+ } else el.innerHTML = `${lines}`;
- el.innerHTML = inner;
- example.remove();
-
- if (elSelected.attr("id").slice(0, 10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning");
+ if (elSelected.attr("id").slice(0, 10) === "stateLabel")
+ tip("Use States Editor to change an actual state name, not just a label", false, "warning");
}
function generateRandomName() {
diff --git a/modules/ui/tools.js b/modules/ui/tools.js
index 4f00d983..e1d554da 100644
--- a/modules/ui/tools.js
+++ b/modules/ui/tools.js
@@ -570,9 +570,8 @@ function addLabelOnClick() {
.attr("data-size", 18)
.attr("filter", null);
- const example = group.append("text").attr("x", 0).attr("x", 0).text(name);
+ const example = group.append("text").attr("x", 0).attr("y", 0).text(name);
const width = example.node().getBBox().width;
- const x = width / -2; // x offset;
example.remove();
group.classed("hidden", false);
@@ -584,7 +583,7 @@ function addLabelOnClick() {
.attr("startOffset", "50%")
.attr("font-size", "100%")
.append("tspan")
- .attr("x", x)
+ .attr("x", 0)
.text(name);
defs
diff --git a/versioning.js b/versioning.js
index ab4fb4e1..fccb2bac 100644
--- a/versioning.js
+++ b/versioning.js
@@ -1,7 +1,7 @@
"use strict";
// version and caching control
-const version = "1.91.05"; // generator version, update each time
+const version = "1.92.00"; // generator version, update each time
{
document.title += " v" + version;