diff --git a/index.html b/index.html
index b26e2076..204f61e6 100644
--- a/index.html
+++ b/index.html
@@ -4105,11 +4105,11 @@
+
-
diff --git a/main.js b/main.js
index 5f6689d5..37e1349b 100644
--- a/main.js
+++ b/main.js
@@ -80,7 +80,7 @@ let anchors = icons.append("g").attr("id", "anchors");
let armies = viewbox.append("g").attr("id", "armies").style("display", "none");
let markers = viewbox.append("g").attr("id", "markers").style("display", "none");
let fogging = viewbox.append("g").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none");
-let ruler = viewbox.append("g").attr("id", "ruler").style("display", "none");
+let ruler = viewbox.append("g").attr("id", "ruler");
let debug = viewbox.append("g").attr("id", "debug");
// lake and coast groups
@@ -126,6 +126,7 @@ legend.on("mousemove", () => tip("Drag to change the position. Click to hide the
let grid = {}; // initial grapg based on jittered square grid and data
let pack = {}; // packed graph and data
let seed, mapId, mapHistory = [], elSelected, modules = {}, notes = [];
+let rulers = new Rulers();
let customization = 0; // 0 - no; 1 = heightmap draw; 2 - states draw; 3 - add state/burg; 4 - cultures draw
let biomesData = applyDefaultBiomesSystem();
@@ -591,9 +592,6 @@ function generate() {
WARN && console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
showStatistics();
INFO && console.groupEnd("Generated Map " + seed);
-
- var r = new Ruler([[70,150],[100,300],[400,360.1]])
- r.render()
}
catch(error) {
ERROR && console.error(error);
@@ -1030,7 +1028,7 @@ function drawCoastline() {
if (f === largestLand) {
const from = points[d3.scan(points, (a, b) => a[0] - b[0])];
const to = points[d3.scan(points, (a, b) => b[0] - a[0])];
- addRuler(from[0], from[1], to[0], to[1]);
+ rulers.linear([from, to]);
}
}
@@ -1748,5 +1746,6 @@ function undraw() {
document.getElementById("deftemp").querySelectorAll("path, clipPath, svg").forEach(el => el.remove());
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
notes = [];
+ rulers = new Rulers();
unfog();
}
diff --git a/modules/save-and-load.js b/modules/save-and-load.js
index 3e145e98..2d1c8b48 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -1131,6 +1131,12 @@ function parseLoadedData(data) {
delete f.river;
}
}
+
+ if (version < 1.61) {
+ // v 1.61 changed rulers data
+ ruler.style("display", null);
+ rulers = new Rulers();
+ }
}()
void function checkDataIntegrity() {
diff --git a/modules/ui/layers.js b/modules/ui/layers.js
index 85d057ef..ac432df9 100644
--- a/modules/ui/layers.js
+++ b/modules/ui/layers.js
@@ -1202,12 +1202,12 @@ function toggleIcons(event) {
function toggleRulers(event) {
if (!layerIsOn("toggleRulers")) {
turnButtonOn("toggleRulers");
- $('#ruler').fadeIn();
if (event && isCtrlClick(event)) editStyle("ruler");
+ rulers.draw();
} else {
if (event && isCtrlClick(event)) {editStyle("ruler"); return;}
- $('#ruler').fadeOut();
turnButtonOff("toggleRulers");
+ rulers.undraw();
}
}
diff --git a/modules/ui/measurers.js b/modules/ui/measurers.js
index 093be6b3..68a5f4a4 100644
--- a/modules/ui/measurers.js
+++ b/modules/ui/measurers.js
@@ -1,14 +1,57 @@
// UI measurers: rulers (linear, curve, area) and Scale Bar
-class Ruler {
- constructor(points) {
- this.points = points;
+class Rulers {
+ constructor() {
+ this.data = [];
+ }
+
+ linear(points) {
+ const ruler = new LinearRuler(points);
+ this.data.push(ruler);
+ return ruler;
}
toString() {
- return "ruler" + ": " + this.points.join(" ");
+ const string = this.data.map(ruler => ruler.toString()).join("; ");
+ return string;
}
- getPoints() {
+ fromString(string) {
+ this.data = [];
+ const rulers = string.split("; ");
+ for (ruler of rulers) {
+ const [type, pointsString] = ruler.split(": ");
+ const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
+ if (type === "linear") this.linear(points);
+ }
+ }
+
+ draw() {
+ this.data.forEach(ruler => ruler.draw());
+ }
+
+ undraw() {
+ this.data.forEach(ruler => ruler.undraw());
+ }
+
+ remove(id) {
+ const ruler = this.data.find(ruler => ruler.id === id);
+ ruler.undraw();
+ const rulerIndex = this.data.indexOf(ruler);
+ rulers.data.splice(rulerIndex, 1);
+ }
+}
+
+class LinearRuler {
+ constructor(points) {
+ this.points = points;
+ this.id = rulers.data.length;
+ }
+
+ toString() {
+ return "linear" + ": " + this.points.join(" ");
+ }
+
+ getPointsString() {
return this.points.join(" ");
}
@@ -25,9 +68,9 @@ class Ruler {
i ? this.points.push([x, y]) : this.points.unshift([x, y]);
}
- render() {
+ draw() {
if (this.el) this.el.selectAll("*").remove();
- const points = this.getPoints();
+ const points = this.getPointsString();
const size = rn(1 / scale ** .3 * 2, 2);
const dash = rn(30 / distanceScaleInput.value, 2);
@@ -36,22 +79,22 @@ class Ruler {
.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)).attr("stroke-dasharray", dash);
el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size);
- el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", this.remove);
- this.renderPoints(el);
+ el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id));
+ this.drawPoints(el);
this.updateLabel();
}
- renderPoints(el) {
+ drawPoints(el) {
const g = el.select(".rulerPoints");
g.selectAll("circle").remove();
for (let i=0; i < this.points.length; i++) {
const [x, y] = this.points[i];
- this.renderPoint(g, x, y, i);
+ this.drawPoint(g, x, y, i);
}
}
- renderPoint(el, x, y, i) {
+ drawPoint(el, x, y, i) {
const context = this;
const circle = el.append("circle")
.attr("r", "1em").attr("cx", x).attr("cy", y)
@@ -101,7 +144,7 @@ class Ruler {
if (edge) {
if (d3.event.dx < .1 && d3.event.dy < .1) return;
context.addPoint(pointId);
- context.renderPoints(context.el);
+ context.drawPoints(context.el);
if (pointId) pointId++;
circle = context.el.select(`circle:nth-child(${pointId+1})`);
edge = false;
@@ -110,7 +153,7 @@ class Ruler {
const x = rn(d3.event.x, 1);
const y = rn(d3.event.y, 1);
context.updatePoint(pointId, x, y);
- line.attr("points", context.getPoints());
+ line.attr("points", context.getPointsString());
circle.attr("cx", x).attr("cy", y);
context.updateLabel();
});
@@ -122,127 +165,19 @@ class Ruler {
const pointId = getSegmentId(context.points, [x, y]);
context.points.splice(pointId, 0, [x, y]);
- context.renderPoints(context.el);
+ context.drawPoints(context.el);
context.dragControl(context, pointId);
}
removePoint(context, pointId) {
this.points.splice(pointId, 1);
if (this.points.length < 2) context.el.remove();
- else context.render();
+ else context.draw();
}
- remove() {
- this.parentNode.parentNode.removeChild(this.parentNode);
+ undraw() {
+ this.el.remove();
}
-
-}
-
-
-// Linear measurer (one is added by default)
-function addRuler(x1, y1, x2, y2) {
- const cx = rn((x1 + x2) / 2, 2), cy = rn((y1 + y2) / 2, 2);
- const size = rn(1 / scale ** .3 * 2, 1);
- const dash = rn(30 / distanceScaleInput.value, 2);
-
- // body
- const rulerNew = ruler.append("g").attr("class", "ruler").call(d3.drag().on("start", dragRuler));
- rulerNew.append("line").attr("x1", x1).attr("y1", y1).attr("x2", x2).attr("y2", y2).attr("class", "white").attr("stroke-width", size);
- rulerNew.append("line").attr("x1", x1).attr("y1", y1).attr("x2", x2).attr("y2", y2).attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
- rulerNew.append("circle").attr("r", 2 * size).attr("stroke-width", .5 * size).attr("cx", x1).attr("cy", y1).attr("data-edge", "left").call(d3.drag().on("drag", dragRulerEdge));
- rulerNew.append("circle").attr("r", 2 * size).attr("stroke-width", .5 * size).attr("cx", x2).attr("cy", y2).attr("data-edge", "right").call(d3.drag().on("drag", dragRulerEdge));
-
- // label and center
- const angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
- const rotate = `rotate(${angle} ${cx} ${cy})`;
- const dist = rn(Math.hypot(x1 - x2, y1 - y2));
- const label = rn(dist * distanceScaleInput.value) + " " + distanceUnitInput.value;
- rulerNew.append("rect").attr("x", cx - size * 1.5).attr("y", cy - size * 1.5).attr("width", size * 3).attr("height", size * 3).attr("transform", rotate).attr("stroke-width", .5 * size).call(d3.drag().on("start", rulerCenterDrag));
- rulerNew.append("text").attr("x", cx).attr("y", cy).attr("dx", ".3em").attr("dy", "-.3em").attr("transform", rotate).attr("font-size", 10 * size).text(label).on("click", removeParent);
-}
-
-function dragRuler() {
- const tr = parseTransform(this.getAttribute("transform"));
- const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
-
- d3.event.on("drag", function() {
- const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`;
- this.setAttribute("transform", transform);
- });
-}
-
-function dragRulerEdge() {
- const ruler = d3.select(this.parentNode);
- const x = d3.event.x, y = d3.event.y;
-
- d3.select(this).attr("cx", x).attr("cy", y);
- const line = ruler.selectAll("line");
- const left = this.dataset.edge === "left";
- const x0 = left ? +line.attr("x2") : +line.attr("x1");
- const y0 = left ? +line.attr("y2") : +line.attr("y1");
- if (left) line.attr("x1", x).attr("y1", y); else line.attr("x2", x).attr("y2", y);
-
- const cx = rn((x + x0) / 2, 2), cy = rn((y + y0) / 2, 2);
- const dist = Math.hypot(x0 - x, y0 - y);
- const label = rn(dist * distanceScaleInput.value) + " " + distanceUnitInput.value;
- const atan = x0 > x ? Math.atan2(y0 - y, x0 - x) : Math.atan2(y - y0, x - x0);
- const angle = rn(atan * 180 / Math.PI, 3);
- const rotate = `rotate(${angle} ${cx} ${cy})`;
-
- const size = rn(1 / scale ** .3 * 2, 1);
- ruler.select("rect").attr("x", cx - size * 1.5).attr("y", cy - size * 1.5).attr("transform", rotate);
- ruler.select("text").attr("x", cx).attr("y", cy).attr("transform", rotate).text(label);
-}
-
-function rulerCenterDrag() {
- let xc1, yc1, xc2, yc2, r1, r2;
- const rulerOld = d3.select(this.parentNode); // current ruler
- let x = d3.event.x, y = d3.event.y; // current coords
- const line = rulerOld.selectAll("line"); // current lines
- const x1 = +line.attr("x1"), y1 = +line.attr("y1"), x2 = +line.attr("x2"), y2 = +line.attr("y2"); // initial line edge points
- const size = rn(1 / scale ** .3 * 2, 1);
- const dash = +rulerOld.select(".gray").attr("stroke-dasharray");
-
- const rulerNew = ruler.insert("g", ":first-child");
- rulerNew.attr("transform", rulerOld.attr("transform")).call(d3.drag().on("start", dragRuler));
- rulerNew.append("line").attr("class", "white").attr("stroke-width", size);
- rulerNew.append("line").attr("class", "gray").attr("stroke-dasharray", dash).attr("stroke-width", size);
- rulerNew.append("text").attr("dx", ".3em").attr("dy", "-.3em").on("click", removeParent).attr("font-size", 10 * size).attr("stroke-width", size);
-
- d3.event.on("drag", function() {
- x = d3.event.x, y = d3.event.y;
-
- // change first part
- let dist = rn(Math.hypot(x1 - x, y1 - y));
- let label = rn(dist * distanceScaleInput.value) + " " + distanceUnitInput.value;
- let atan = x1 > x ? Math.atan2(y1 - y, x1 - x) : Math.atan2(y - y1, x - x1);
- xc1 = rn((x + x1) / 2, 2), yc1 = rn((y + y1) / 2, 2);
- r1 = `rotate(${rn(atan * 180 / Math.PI, 3)} ${xc1} ${yc1})`;
- line.attr("x1", x1).attr("y1", y1).attr("x2", x).attr("y2", y);
- rulerOld.select("rect").attr("x", x - size * 1.5).attr("y", y - size * 1.5).attr("transform", null);
- rulerOld.select("text").attr("x", xc1).attr("y", yc1).attr("transform", r1).text(label);
-
- // change second (new) part
- dist = rn(Math.hypot(x2 - x, y2 - y));
- label = rn(dist * distanceScaleInput.value) + " " + distanceUnitInput.value;
- atan = x2 > x ? Math.atan2(y2 - y, x2 - x) : Math.atan2(y - y2, x - x2);
- xc2 = rn((x + x2) / 2, 2), yc2 = rn((y + y2) / 2, 2);
- r2 = `rotate(${rn(atan * 180 / Math.PI, 3)} ${xc2} ${yc2})`;
- rulerNew.selectAll("line").attr("x1", x).attr("y1", y).attr("x2", x2).attr("y2", y2);
- rulerNew.select("text").attr("x", xc2).attr("y", yc2).attr("transform", r2).text(label);
- });
-
- d3.event.on("end", function() {
- // contols for 1st part
- rulerOld.select("circle[data-edge='left']").attr("cx", x1).attr("cy", y1);
- rulerOld.select("circle[data-edge='right']").attr("cx", x).attr("cy", y);
- rulerOld.select("rect").attr("x", xc1 - size * 1.5).attr("y", yc1 - size * 1.5).attr("transform", r1);
-
- // contols for 2nd part
- rulerNew.append("circle").attr("cx", x).attr("cy", y).attr("r", 2 * size).attr("stroke-width", 0.5 * size).attr("data-edge", "left").call(d3.drag().on("drag", dragRulerEdge));
- rulerNew.append("circle").attr("cx", x2).attr("cy", y2).attr("r", 2 * size).attr("stroke-width", 0.5 * size).attr("data-edge", "right").call(d3.drag().on("drag", dragRulerEdge));
- rulerNew.append("rect").attr("x", xc2 - size * 1.5).attr("y", yc2 - size * 1.5).attr("width", size * 3).attr("height", size * 3).attr("transform", r2).attr("stroke-width", .5 * size).call(d3.drag().on("start", rulerCenterDrag));
- });
}
function drawOpisometer() {
diff --git a/modules/ui/units-editor.js b/modules/ui/units-editor.js
index d545c128..e0605560 100644
--- a/modules/ui/units-editor.js
+++ b/modules/ui/units-editor.js
@@ -33,7 +33,7 @@ function editUnits() {
document.getElementById("urbanizationOutput").addEventListener("input", changeUrbanizationRate);
document.getElementById("urbanization").addEventListener("change", changeUrbanizationRate);
- document.getElementById("addLinearRuler").addEventListener("click", addAdditionalRuler);
+ document.getElementById("addLinearRuler").addEventListener("click", addRuler);
document.getElementById("addOpisometer").addEventListener("click", toggleOpisometerMode);
document.getElementById("addPlanimeter").addEventListener("click", togglePlanimeterMode);
document.getElementById("removeRulers").addEventListener("click", removeAllRulers);
@@ -200,14 +200,16 @@ function editUnits() {
localStorage.removeItem("urbanization");
}
- function addAdditionalRuler() {
+ function addRuler() {
if (!layerIsOn("toggleRulers")) toggleRulers();
- const x = graphWidth/2, y = graphHeight/2;
const pt = document.getElementById('map').createSVGPoint();
- pt.x = x, pt.y = y;
+ pt.x = graphWidth / 2, pt.y = graphHeight / 4;
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
- const dx = rn(graphWidth / 4 / scale), dy = rand(dx / 2, dx * 2) - rand(dx / 2, dx * 2);
- addRuler(p.x - dx, p.y + dy, p.x + dx, p.y + dy);
+ const dx = graphWidth / 4 / scale;
+ const dy = (rulers.data.length * 10) % (graphHeight / 2);
+ const from = [p.x-dx | 0, p.y+dy | 0];
+ const to = [p.x+dx | 0, p.y+dy | 0];
+ rulers.linear([from, to]).draw();
}
function toggleOpisometerMode() {
@@ -239,13 +241,16 @@ function editUnits() {
}
function removeAllRulers() {
- if (!ruler.selectAll("g").size()) return;
- alertMessage.innerHTML = `Are you sure you want to remove all placed rulers?`;
+ if (!rulers.data.length) return;
+ alertMessage.innerHTML = `
+ Are you sure you want to remove all placed rulers?
+
If you just want to hide rulers, toggle the Rulers layer off in Menu`;
$("#alert").dialog({resizable: false, title: "Remove all rulers",
buttons: {
Remove: function() {
$(this).dialog("close");
- ruler.selectAll("g").remove();
+ rulers.undraw();
+ rulers = new Rulers();
},
Cancel: function() {$(this).dialog("close");}
}