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 + + + #ffffff + + + + + Gray line color + + + #808080 + + + + + + + + + + + + + + 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() {