diff --git a/index.css b/index.css
index eb1e82fc1..64a2fa2cf 100644
--- a/index.css
+++ b/index.css
@@ -1907,6 +1907,12 @@ div.editorLine {
stroke: #737373;
}
+#rulerShowText {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 5px;
+}
+
#militaryOptionsTable select {
border: 1px solid #d4d4d4;
}
diff --git a/index.html b/index.html
index 0b9503971..26f19dbe6 100644
--- a/index.html
+++ b/index.html
@@ -933,6 +933,46 @@
+
+
+ |
+ |
+
+
+
+ |
+
+
+ |
+
+
+
+ | White line color |
+
+
+
+ |
+
+
+
+ | Gray line color |
+
+
+
+ |
+
+
+
+ |
+ |
+
+
+
+ |
+ |
+
+
+
| Image |
diff --git a/modules/io/load.js b/modules/io/load.js
index 81786374a..f8700c1b8 100644
--- a/modules/io/load.js
+++ b/modules/io/load.js
@@ -255,7 +255,16 @@ async function parseLoadedData(data, mapVersion) {
void (function parseConfiguration() {
if (data[2]) mapCoordinates = JSON.parse(data[2]);
if (data[4]) notes = JSON.parse(data[4]);
- if (data[33]) rulers.fromString(data[33]);
+ if (data[33]) {
+ rulers.fromString(data[33]);
+ const rulerPreferences = JSON.parse(data[33]);
+ localStorage.setItem("rulerInitialLength", rulerPreferences.initialLength);
+ localStorage.setItem("rulerWhiteLineColor", rulerPreferences.whiteColor);
+ localStorage.setItem("rulerGrayLineColor", rulerPreferences.grayColor);
+ localStorage.setItem("rulerWhiteLineWidth", rulerPreferences.whiteWidth);
+ localStorage.setItem("rulerGrayLineWidth", rulerPreferences.grayWidth);
+ localStorage.setItem("rulerShowText", rulerPreferences.showText);
+ }
if (data[34]) {
const usedFonts = JSON.parse(data[34]);
usedFonts.forEach(usedFont => {
diff --git a/modules/io/save.js b/modules/io/save.js
index 73e6845f8..37f0e833c 100644
--- a/modules/io/save.js
+++ b/modules/io/save.js
@@ -73,6 +73,14 @@ function prepareMapData() {
const coords = JSON.stringify(mapCoordinates);
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
const notesData = JSON.stringify(notes);
+ const rulerPreferences = JSON.stringify({
+ initialLength: localStorage.getItem("rulerInitialLength") || "100",
+ whiteColor: localStorage.getItem("rulerWhiteLineColor") || "#ffffff",
+ grayColor: localStorage.getItem("rulerGrayLineColor") || "#3d3d3d",
+ whiteWidth: parseFloat(localStorage.getItem("rulerWhiteLineWidth")) || 1,
+ grayWidth: parseFloat(localStorage.getItem("rulerGrayLineWidth")) || 1.2,
+ showText: localStorage.getItem("rulerShowText") || true
+ });
const rulersString = rulers.toString();
const fonts = JSON.stringify(getUsedFonts(svg.node()));
@@ -148,6 +156,7 @@ function prepareMapData() {
provinces,
namesData,
rivers,
+ rulerPreferences,
rulersString,
fonts,
markers,
diff --git a/modules/ui/measurers.js b/modules/ui/measurers.js
index 1f51cd7db..4c22ae861 100644
--- a/modules/ui/measurers.js
+++ b/modules/ui/measurers.js
@@ -141,6 +141,13 @@ class Ruler extends Measurer {
const size = this.getSize();
const dash = this.getDash();
+ const whiteColor = localStorage.getItem("rulerWhiteLineColor") || "#ffffff";
+ const grayColor = localStorage.getItem("rulerGrayLineColor") || "#808080";
+ const whiteWidth = parseFloat(localStorage.getItem("rulerWhiteLineWidth")) || 1;
+ const grayWidth = parseFloat(localStorage.getItem("rulerGrayLineWidth")) || 1.2;
+ const showText = localStorage.getItem("rulerShowText") !== "false"; // by default, show the text
+
+
const el = (this.el = ruler
.append("g")
.attr("class", "ruler")
@@ -149,12 +156,14 @@ class Ruler extends Measurer {
el.append("polyline")
.attr("points", points)
.attr("class", "white")
- .attr("stroke-width", size)
+ .style("stroke", whiteColor)
+ .style("stroke-width", size * whiteWidth)
.call(d3.drag().on("start", () => this.addControl(this)));
el.append("polyline")
.attr("points", points)
.attr("class", "gray")
- .attr("stroke-width", rn(size * 1.2, 2))
+ .style("stroke", grayColor)
+ .style("stroke-width", size * grayWidth)
.attr("stroke-dasharray", dash);
el.append("g")
.attr("class", "rulerPoints")
@@ -163,6 +172,7 @@ class Ruler extends Measurer {
el.append("text")
.attr("dx", ".35em")
.attr("dy", "-.45em")
+ .style("display", showText ? "inline" : "none")
.on("click", () => rulers.remove(this.id));
this.drawPoints(el);
this.updateLabel();
diff --git a/modules/ui/style.js b/modules/ui/style.js
index fb62a8e5d..9826321bf 100644
--- a/modules/ui/style.js
+++ b/modules/ui/style.js
@@ -75,8 +75,15 @@ styleElementSelect.addEventListener("change", selectStyleElement);
function selectStyleElement() {
const styleElement = styleElementSelect.value;
+ console.log("Selecting style for element:", styleElement);
let el = d3.select("#" + styleElement);
+ // if element not found, return
+ if (!el.node()) {
+ console.warn(`Element ${styleElement} not found`);
+ return;
+ }
+
styleElements.querySelectorAll("tbody").forEach(e => (e.style.display = "none")); // hide all sections
// show alert line if layer is not visible
@@ -84,14 +91,14 @@ function selectStyleElement() {
styleIsOff.style.display = isLayerOff ? "block" : "none";
// active group element
- if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
+ if (["anchors", "borders", "burgIcons", "coastline", "labels", "lakes", "routes", "ruler", "terrs"].includes(styleElement)) {
const group = styleGroupSelect.value;
const defaultGroupSelector = styleElement === "terrs" ? "#landHeights" : "g";
el = group && el.select("#" + group).size() ? el.select("#" + group) : el.select(defaultGroupSelector);
}
// opacity
- if (!["landmass", "ocean", "regions", "legend"].includes(styleElement)) {
+ if (!["landmass", "legend", "ocean", "regions"].includes(styleElement)) {
styleOpacity.style.display = "block";
styleOpacityInput.value = el.attr("opacity") || 1;
}
@@ -103,7 +110,7 @@ function selectStyleElement() {
}
// fill
- if (["rivers", "lakes", "landmass", "prec", "ice", "fogging", "scaleBar", "vignette"].includes(styleElement)) {
+ if (["fogging", "ice", "lakes", "landmass", "prec", "rivers", "scaleBar", "vignette"].includes(styleElement)) {
styleFill.style.display = "block";
styleFillInput.value = styleFillOutput.value = el.attr("fill");
}
@@ -112,19 +119,19 @@ function selectStyleElement() {
if (
[
"armies",
- "routes",
- "lakes",
"borders",
- "cults",
- "relig",
"cells",
"coastline",
- "prec",
+ "coordinates",
+ "cults",
+ "gridOverlay",
"ice",
"icons",
- "coordinates",
+ "lakes",
+ "prec",
+ "relig",
+ "routes",
"zones",
- "gridOverlay"
].includes(styleElement)
) {
styleStroke.style.display = "block";
@@ -135,7 +142,7 @@ function selectStyleElement() {
// stroke dash
if (
- ["routes", "borders", "temperature", "legend", "population", "coordinates", "zones", "gridOverlay"].includes(
+ ["borders", "coordinates", "gridOverlay", "legend", "population", "ruler", "routes", "temperature", "zones"].includes(
styleElement
)
) {
@@ -344,9 +351,48 @@ function selectStyleElement() {
emblemsBurgSizeInput.value = emblems.select("#burgEmblems").attr("data-size") || 1;
}
+ if (styleElement === "ruler") {
+ styleRuler.style.display = "block";
+ styleStrokeDash.style.display = "block";
+
+ const rulerEl = el.select("polyline");
+ styleStrokeDasharrayInput.value = rulerEl.attr("stroke-dasharray") || "";
+ styleStrokeLinecapInput.value = rulerEl.attr("stroke-linecap") || "inherit";
+
+ // Ruler preferences
+ const rulerInitialLength = byId("rulerInitialLength");
+ if (rulerInitialLength) {
+ rulerInitialLength.value = localStorage.getItem("rulerInitialLength") || "10";
+ }
+
+ // Color and width controls
+ const whiteLineColor = localStorage.getItem("rulerWhiteLineColor") || "#ffffff";
+ const grayLineColor = localStorage.getItem("rulerGrayLineColor") || "#808080";
+ const whiteLineWidth = localStorage.getItem("rulerWhiteLineWidth") || "1";
+ const grayLineWidth = localStorage.getItem("rulerGrayLineWidth") || "1.2";
+
+ byId("rulerWhiteLineColor").value = whiteLineColor;
+ byId("rulerGrayLineColor").value = grayLineColor;
+ byId("rulerWhiteLineWidth").value = whiteLineWidth;
+ byId("rulerGrayLineWidth").value = grayLineWidth;
+
+ // Update color outputs if they exist
+ const whiteColorOutput = byId("rulerWhiteLineColorOutput");
+ const grayColorOutput = byId("rulerGrayLineColorOutput");
+ if (whiteColorOutput) whiteColorOutput.value = whiteLineColor;
+ if (grayColorOutput) grayColorOutput.value = grayLineColor;
+
+ // Text visibility control
+ const rulerShowText = byId("rulerShowText");
+ if (rulerShowText) {
+ rulerShowText.checked = localStorage.getItem("rulerShowText") !== "false";
+ rulerShowText.addEventListener("change", toggleRulerText);
+ }
+ }
+
// update group options
styleGroupSelect.options.length = 0; // remove all options
- if (["routes", "labels", "coastline", "lakes", "anchors", "burgIcons", "borders", "terrs"].includes(styleElement)) {
+ if (["anchors", "borders", "burgIcons", "coastline", "labels", "lakes", "routes", "ruler", "terrs"].includes(styleElement)) {
const groups = byId(styleElement).querySelectorAll("g");
groups.forEach(el => {
if (el.id === "burgLabels") return;
@@ -427,10 +473,15 @@ styleStrokeInput.addEventListener("input", function () {
if (styleElementSelect.value === "gridOverlay" && layerIsOn("toggleGrid")) drawGrid();
});
-styleStrokeWidthInput.addEventListener("input", e => {
- getEl().attr("stroke-width", e.target.value);
- if (styleElementSelect.value === "gridOverlay" && layerIsOn("toggleGrid")) drawGrid();
-});
+const styleStrokeWidthInput = document.getElementById('styleStrokeWidthInput');
+if (styleStrokeWidthInput && styleStrokeWidthInput.querySelector('input')) {
+ styleStrokeWidthInput.querySelector('input').addEventListener("input", e => {
+ getEl().attr("stroke-width", e.target.value);
+ if (styleElementSelect.value === "gridOverlay" && layerIsOn("toggleGrid")) drawGrid();
+ });
+} else {
+ console.warn('styleStrokeWidthInput not found or does not contain an input element');
+}
styleStrokeDasharrayInput.addEventListener("input", function () {
getEl().attr("stroke-dasharray", this.value);
@@ -1075,6 +1126,28 @@ styleVignetteBlur.addEventListener("input", e => {
byId("vignette-rect")?.setAttribute("filter", `blur(${e.target.value}px)`);
});
+document.addEventListener('DOMContentLoaded', function() {
+ const rulerInitialLength = document.getElementById("rulerInitialLength");
+
+ if (rulerInitialLength) {
+ rulerInitialLength.addEventListener("change", e => {
+ localStorage.setItem("rulerInitialLength", e.target.value);
+ });
+ }
+});
+
+function toggleRulerText() {
+ const checkbox = byId("rulerShowText");
+ const showText = checkbox.checked;
+
+ // Update visibility for existing rulers
+ d3.selectAll("g.ruler text").style("display", showText ? "inline" : "none");
+ console.log(`toggleRulerText called with showText: ${showText}`);
+
+ // Save the preference for future rulers
+ localStorage.setItem("rulerShowText", showText);
+}
+
styleScaleBar.addEventListener("input", function (event) {
const scaleBarBack = scaleBar.select("#scaleBarBack");
if (!scaleBarBack.size()) return;
diff --git a/modules/ui/units-editor.js b/modules/ui/units-editor.js
index 67562adac..eaa83c411 100644
--- a/modules/ui/units-editor.js
+++ b/modules/ui/units-editor.js
@@ -34,6 +34,13 @@ function editUnits() {
byId("removeRulers").on("click", removeAllRulers);
byId("unitsRestore").on("click", restoreDefaultUnits);
+ // Add listeners for the new ruler controls
+ byId("rulerWhiteLineColor").addEventListener("change", changeRulerLineColor);
+ byId("rulerGrayLineColor").addEventListener("change", changeRulerLineColor);
+ byId("rulerWhiteLineWidth").addEventListener("change", changeRulerLineWidth);
+ byId("rulerGrayLineWidth").addEventListener("change", changeRulerLineWidth);
+ byId("rulerShowText").addEventListener("change", toggleRulerText);
+
function changeDistanceUnit() {
if (this.value === "custom_name") {
prompt("Provide a custom name for a distance unit", {default: ""}, custom => {
@@ -117,18 +124,134 @@ function editUnits() {
localStorage.removeItem("populationRate");
localStorage.removeItem("urbanization");
localStorage.removeItem("urbanDensity");
+
+ // Ruler defaults
+ localStorage.setItem("rulerShowText", "true");
+ localStorage.setItem("rulerInitialLength", "10");
+ localStorage.setItem("rulerWhiteLineColor", "#ffffff");
+ localStorage.setItem("rulerGrayLineColor", "#3d3d3d");
+ localStorage.setItem("rulerWhiteLineWidth", "1");
+ localStorage.setItem("rulerGrayLineWidth", "1.2");
+
+ // Update UI elements
+ byId("rulerShowText").checked = true;
+ byId("rulerInitialLength").value = "10";
+ byId("rulerWhiteLineColor").value = "#ffffff";
+ byId("rulerGrayLineColor").value = "#3d3d3d";
+ byId("rulerWhiteLineWidth").value = "1";
+ byId("rulerGrayLineWidth").value = "1.2";
+
+ // Update color outputs if they exist
+ const whiteColorOutput = byId("rulerWhiteLineColorOutput");
+ const grayColorOutput = byId("rulerGrayLineColorOutput");
+ if (whiteColorOutput) whiteColorOutput.value = "#ffffff";
+ if (grayColorOutput) grayColorOutput.value = "#3d3d3d";
+
+ // Apply changes to existing rulers
+ d3.selectAll("g.ruler").each(function() {
+ const ruler = d3.select(this);
+ ruler.select("text").style("display", "inline");
+ ruler.select("polyline.white")
+ .style("stroke", "#ffffff")
+ .style("stroke-width", ruler.attr("font-size") / 10);
+ ruler.select("polyline.gray")
+ .style("stroke", "#3d3d3d")
+ .style("stroke-width", ruler.attr("font-size") / 10 * 1.2);
+ });
+ }
+
+ function changeRulerLineColor() {
+ const whiteColor = byId("rulerWhiteLineColor").value;
+ const grayColor = byId("rulerGrayLineColor").value;
+
+ // Update the colors of existing lines
+ d3.selectAll("g.ruler > polyline.white").style("stroke", whiteColor);
+ d3.selectAll("g.ruler > polyline.gray").style("stroke", grayColor);
+
+ // Save the colors for future rulers
+ localStorage.setItem("rulerWhiteLineColor", whiteColor);
+ localStorage.setItem("rulerGrayLineColor", grayColor);
+ }
+
+ function changeRulerLineWidth() {
+ const whiteWidth = parseFloat(byId("rulerWhiteLineWidth").value);
+ const grayWidth = parseFloat(byId("rulerGrayLineWidth").value);
+
+ // Update the widths of existing lines with absolute values
+ d3.selectAll("g.ruler > polyline.white").each(function() {
+ const baseSize = parseFloat(d3.select(this.parentNode).attr("font-size")) / 10;
+ d3.select(this).style("stroke-width", baseSize * whiteWidth);
+ });
+ d3.selectAll("g.ruler > polyline.gray").each(function() {
+ const baseSize = parseFloat(d3.select(this.parentNode).attr("font-size")) / 10;
+ d3.select(this).style("stroke-width", baseSize * grayWidth);
+ });
+
+ // Save the width values for future rulers
+ localStorage.setItem("rulerWhiteLineWidth", whiteWidth);
+ localStorage.setItem("rulerGrayLineWidth", grayWidth);
}
function addRuler() {
if (!layerIsOn("toggleRulers")) toggleRulers();
const pt = byId("map").createSVGPoint();
- (pt.x = graphWidth / 2), (pt.y = graphHeight / 4);
+
+ // Initial position in the center.
+ let x = 50;
+ let y = 50;
+
+ const existingRulers = rulers.data;
+ const yStep = 10; // Percentage step for the Y coordinate
+
+ // Search for the last Y position used (in percentage)
+ let lastY = existingRulers.length > 0 ? (existingRulers[existingRulers.length - 1].points[0][1] / graphHeight) * 100 : null;
+
+ if (lastY !== null) {
+ // If there are existing rulers, calculate the new Y position
+ y = (lastY + yStep) % 100;
+
+ // If we return to the center and it was the last operation, move one step more
+ if (y === 50 && lastY !== 50) {
+ y = (y + yStep) % 100;
+ }
+ }
+
+ // Calculate coordinates based on percentages
+ pt.x = graphWidth * (x / 100);
+ pt.y = graphHeight * (y / 100);
+
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
- const dx = graphWidth / 4 / scale;
- const dy = (rulers.data.length * 40) % (graphHeight / 2);
- const from = [(p.x - dx) | 0, (p.y + dy) | 0];
- const to = [(p.x + dx) | 0, (p.y + dy) | 0];
- rulers.create(Ruler, [from, to]).draw();
+
+ // Use the rulerInitialLength value from localStorage or the default value
+ const initialLength = parseFloat(localStorage.getItem("rulerInitialLength")) || 10;
+ const dx = (initialLength / 200) * graphWidth / scale;
+
+ const from = [(p.x - dx) | 0, p.y | 0];
+ const to = [(p.x + dx) | 0, p.y | 0];
+
+ const whiteColor = localStorage.getItem("rulerWhiteLineColor") || "#ffffff";
+ const grayColor = localStorage.getItem("rulerGrayLineColor") || "#3d3d3d";
+ const whiteWidth = parseFloat(localStorage.getItem("rulerWhiteLineWidth")) || 1;
+ const grayWidth = parseFloat(localStorage.getItem("rulerGrayLineWidth")) || 1.2;
+ const showText = localStorage.getItem("rulerShowText") !== "false";
+
+ const ruler = rulers.create(Ruler, [from, to]);
+ ruler.draw();
+
+ // Apply the custom colors and widths to the new ruler
+ const baseSize = ruler.getSize();
+ ruler.el.select("polyline.white")
+ .style("stroke", whiteColor)
+ .style("stroke-width", baseSize * whiteWidth);
+ ruler.el.select("polyline.gray")
+ .style("stroke", grayColor)
+ .style("stroke-width", baseSize * grayWidth);
+
+ // Apply the text visibility preference to the new ruler
+ ruler.el.select("text").style("display", showText ? "inline" : "none");
+
+
+ console.log(`New ruler created at x: ${x.toFixed(2)}%, y: ${y.toFixed(2)}%, length: ${initialLength}%`);
}
function toggleOpisometerMode() {