mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 03:51:23 +01:00
feat: edit routes - main
This commit is contained in:
parent
d6c01c8995
commit
68b4cfd370
9 changed files with 401 additions and 357 deletions
|
|
@ -258,7 +258,6 @@ i.icon-lock {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#routeEditor > *,
|
|
||||||
#labelEditor div {
|
#labelEditor div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
@ -1610,6 +1609,7 @@ div.states > .riverType {
|
||||||
|
|
||||||
#burgBody > div > div,
|
#burgBody > div > div,
|
||||||
#riverBody > div,
|
#riverBody > div,
|
||||||
|
#routeBody > div,
|
||||||
#lakeBody > div {
|
#lakeBody > div {
|
||||||
padding: 0.1em;
|
padding: 0.1em;
|
||||||
}
|
}
|
||||||
|
|
@ -1617,6 +1617,7 @@ div.states > .riverType {
|
||||||
#riverBody div.label,
|
#riverBody div.label,
|
||||||
#riverBody input,
|
#riverBody input,
|
||||||
#riverBody select,
|
#riverBody select,
|
||||||
|
#routeBody div.label,
|
||||||
#lakeBody div.label,
|
#lakeBody div.label,
|
||||||
#lakeBody input,
|
#lakeBody input,
|
||||||
#lakeBody select {
|
#lakeBody select {
|
||||||
|
|
@ -1624,6 +1625,12 @@ div.states > .riverType {
|
||||||
width: 7em;
|
width: 7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#routeBody input,
|
||||||
|
#routeBody select {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
#stateNameEditor div.label,
|
#stateNameEditor div.label,
|
||||||
#provinceNameEditor div.label,
|
#provinceNameEditor div.label,
|
||||||
#regimentBody div.label,
|
#regimentBody div.label,
|
||||||
|
|
|
||||||
56
index.html
56
index.html
|
|
@ -2897,35 +2897,51 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="routeEditor" class="dialog" style="display: none">
|
<div id="routeEditor" class="dialog" style="display: none">
|
||||||
<button id="routeGroupsShow" data-tip="Show the group selection" class="icon-tags"></button>
|
<div id="routeBody" style="padding-bottom: 0.3em">
|
||||||
<div id="routeGroupsSelection" style="display: none">
|
<div>
|
||||||
<button id="routeGroupsHide" data-tip="Hide the group section" class="icon-tags"></button>
|
<div class="label" style="width: 4.8em">Name:</div>
|
||||||
<select id="routeGroup" data-tip="Select a group for this route" style="width: 12em"></select>
|
|
||||||
<input
|
|
||||||
id="routeGroupName"
|
|
||||||
placeholder="new group name"
|
|
||||||
data-tip="Provide a name for the new group"
|
|
||||||
style="display: none; width: 12em"
|
|
||||||
/>
|
|
||||||
<span id="routeGroupAdd" data-tip="Create new group for this route" class="icon-plus pointer"></span>
|
|
||||||
<span
|
<span
|
||||||
id="routeGroupRemove"
|
id="routeNameCulture"
|
||||||
data-tip="Remove all routes of this group"
|
data-tip="Generate culture-specific name for the route"
|
||||||
class="icon-trash-empty pointer"
|
class="icon-book pointer"
|
||||||
></span>
|
></span>
|
||||||
|
<span id="routeNameRandom" data-tip="Generate random name for the route" class="icon-globe pointer"></span>
|
||||||
|
<input id="routeName" data-tip="Type to rename the route" autocorrect="off" spellcheck="false" />
|
||||||
|
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="routeEditStyle" data-tip="Edit route group style in Style Editor" class="icon-brush"></button>
|
<div data-tip="Select route group">
|
||||||
<button id="routeLength" data-tip="Route length in selected units">0</button>
|
<div class="label">Group:</div>
|
||||||
|
<select id="routeGroup"></select>
|
||||||
|
<span id="routeGroupEdit" data-tip="Edit route groups" class="icon-pencil pointer"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-tip="Route length in selected units">
|
||||||
|
<div class="label">Length:</div>
|
||||||
|
<input id="routeLength" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="routeBottom">
|
||||||
|
<button
|
||||||
|
id="routeCreateSelectingCells"
|
||||||
|
data-tip="Create new route selecting route cells"
|
||||||
|
class="icon-map-pin"
|
||||||
|
></button>
|
||||||
|
<button id="routeEditStyle" data-tip="Edit style for all routes in Style Editor" class="icon-brush"></button>
|
||||||
<button
|
<button
|
||||||
id="routeElevationProfile"
|
id="routeElevationProfile"
|
||||||
data-tip="Show the elevation profile for the route"
|
data-tip="Show the elevation profile for the route"
|
||||||
class="icon-chart-area"
|
class="icon-chart-area"
|
||||||
></button>
|
></button>
|
||||||
<button id="routeSplit" data-tip="Click on a control point to split the route" class="icon-unlink"></button>
|
|
||||||
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
|
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
|
||||||
<button id="routeNew" data-tip="Create new route clicking on map" class="icon-map-pin"></button>
|
<button
|
||||||
<button id="routeRemove" data-tip="Remove route" data-shortcut="Delete" class="icon-trash fastDelete"></button>
|
id="routeRemove"
|
||||||
|
data-tip="Remove route"
|
||||||
|
data-shortcut="Delete"
|
||||||
|
class="icon-trash fastDelete"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="iceEditor" class="dialog" style="display: none">
|
<div id="iceEditor" class="dialog" style="display: none">
|
||||||
|
|
@ -8008,9 +8024,9 @@
|
||||||
<script src="versioning.js"></script>
|
<script src="versioning.js"></script>
|
||||||
<script src="libs/d3.min.js"></script>
|
<script src="libs/d3.min.js"></script>
|
||||||
<script src="libs/priority-queue.min.js"></script>
|
<script src="libs/priority-queue.min.js"></script>
|
||||||
|
<script src="libs/flatqueue.js"></script>
|
||||||
<script src="libs/delaunator.min.js"></script>
|
<script src="libs/delaunator.min.js"></script>
|
||||||
<script src="libs/indexedDB.js?v=1.91.01"></script>
|
<script src="libs/indexedDB.js?v=1.91.01"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flatqueue"></script>
|
|
||||||
|
|
||||||
<script src="utils/shorthands.js"></script>
|
<script src="utils/shorthands.js"></script>
|
||||||
<script src="utils/commonUtils.js?v=1.89.29"></script>
|
<script src="utils/commonUtils.js?v=1.89.29"></script>
|
||||||
|
|
|
||||||
57
libs/flatqueue.js
Normal file
57
libs/flatqueue.js
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
!(function (t, s) {
|
||||||
|
"object" == typeof exports && "undefined" != typeof module
|
||||||
|
? (module.exports = s())
|
||||||
|
: "function" == typeof define && define.amd
|
||||||
|
? define(s)
|
||||||
|
: ((t = "undefined" != typeof globalThis ? globalThis : t || self).FlatQueue = s());
|
||||||
|
})(this, function () {
|
||||||
|
"use strict";
|
||||||
|
return class {
|
||||||
|
constructor() {
|
||||||
|
(this.ids = []), (this.values = []), (this.length = 0);
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
this.length = 0;
|
||||||
|
}
|
||||||
|
push(t, s) {
|
||||||
|
let i = this.length++;
|
||||||
|
for (; i > 0; ) {
|
||||||
|
const t = (i - 1) >> 1,
|
||||||
|
e = this.values[t];
|
||||||
|
if (s >= e) break;
|
||||||
|
(this.ids[i] = this.ids[t]), (this.values[i] = e), (i = t);
|
||||||
|
}
|
||||||
|
(this.ids[i] = t), (this.values[i] = s);
|
||||||
|
}
|
||||||
|
pop() {
|
||||||
|
if (0 === this.length) return;
|
||||||
|
const t = this.ids[0];
|
||||||
|
if ((this.length--, this.length > 0)) {
|
||||||
|
const t = (this.ids[0] = this.ids[this.length]),
|
||||||
|
s = (this.values[0] = this.values[this.length]),
|
||||||
|
i = this.length >> 1;
|
||||||
|
let e = 0;
|
||||||
|
for (; e < i; ) {
|
||||||
|
let t = 1 + (e << 1);
|
||||||
|
const i = t + 1;
|
||||||
|
let h = this.ids[t],
|
||||||
|
l = this.values[t];
|
||||||
|
const n = this.values[i];
|
||||||
|
if ((i < this.length && n < l && ((t = i), (h = this.ids[i]), (l = n)), l >= s)) break;
|
||||||
|
(this.ids[e] = h), (this.values[e] = l), (e = t);
|
||||||
|
}
|
||||||
|
(this.ids[e] = t), (this.values[e] = s);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
peek() {
|
||||||
|
if (0 !== this.length) return this.ids[0];
|
||||||
|
}
|
||||||
|
peekValue() {
|
||||||
|
if (0 !== this.length) return this.values[0];
|
||||||
|
}
|
||||||
|
shrink() {
|
||||||
|
this.ids.length = this.values.length = this.length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -465,16 +465,11 @@ function saveGeoJSON_Cells() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeoJSON_Routes() {
|
function saveGeoJSON_Routes() {
|
||||||
const json = {type: "FeatureCollection", features: []};
|
const features = pack.routes.map(({id, points, cells, group}) => {
|
||||||
|
const coordinates = points || getRoutePoints(cells, group);
|
||||||
routes.selectAll("g > path").each(function () {
|
return {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, group}};
|
||||||
const coordinates = getRoutePoints(this);
|
|
||||||
const id = this.id;
|
|
||||||
const type = this.parentElement.id;
|
|
||||||
|
|
||||||
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
|
|
||||||
json.features.push(feature);
|
|
||||||
});
|
});
|
||||||
|
const json = {type: "FeatureCollection", features};
|
||||||
|
|
||||||
const fileName = getFileName("Routes") + ".geojson";
|
const fileName = getFileName("Routes") + ".geojson";
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
|
|
@ -519,17 +514,6 @@ function getCellCoordinates(vertices) {
|
||||||
return [coordinates.concat([coordinates[0]])];
|
return [coordinates.concat([coordinates[0]])];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRoutePoints(node) {
|
|
||||||
let points = [];
|
|
||||||
const l = node.getTotalLength();
|
|
||||||
const increment = l / Math.ceil(l / 2);
|
|
||||||
for (let i = 0; i <= l; i += increment) {
|
|
||||||
const p = node.getPointAtLength(i);
|
|
||||||
points.push(getCoordinates(p.x, p.y, 4));
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRiverPoints(node) {
|
function getRiverPoints(node) {
|
||||||
let points = [];
|
let points = [];
|
||||||
const l = node.getTotalLength() / 2; // half-length
|
const l = node.getTotalLength() / 2; // half-length
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ function clicked() {
|
||||||
|
|
||||||
if (grand.id === "emblems") editEmblem();
|
if (grand.id === "emblems") editEmblem();
|
||||||
else if (parent.id === "rivers") editRiver(el.id);
|
else if (parent.id === "rivers") editRiver(el.id);
|
||||||
else if (grand.id === "routes") editRoute({node: el});
|
else if (grand.id === "routes") editRoute(el.id);
|
||||||
else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
|
else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
|
||||||
else if (grand.id === "burgLabels") editBurg();
|
else if (grand.id === "burgLabels") editBurg();
|
||||||
else if (grand.id === "burgIcons") editBurg();
|
else if (grand.id === "burgIcons") editBurg();
|
||||||
|
|
|
||||||
|
|
@ -1633,29 +1633,23 @@ function toggleRoutes(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawRoutes() {
|
const ROUTE_CURVES = {
|
||||||
TIME && console.time("drawRoutes");
|
|
||||||
const {cells, burgs} = pack;
|
|
||||||
|
|
||||||
const points = adjustBurgPoints(); // mutable array of points
|
|
||||||
const routePaths = {};
|
|
||||||
|
|
||||||
const lineGen = d3.line();
|
|
||||||
const curves = {
|
|
||||||
roads: d3.curveCatmullRom.alpha(0.1),
|
roads: d3.curveCatmullRom.alpha(0.1),
|
||||||
trails: d3.curveCatmullRom.alpha(0.1),
|
trails: d3.curveCatmullRom.alpha(0.1),
|
||||||
searoutes: d3.curveCatmullRom.alpha(0.5),
|
searoutes: d3.curveCatmullRom.alpha(0.5),
|
||||||
default: d3.curveCatmullRom.alpha(0.1)
|
default: d3.curveCatmullRom.alpha(0.1)
|
||||||
};
|
};
|
||||||
const SHARP_ANGLE = 135;
|
|
||||||
const VERY_SHARP_ANGLE = 115;
|
|
||||||
|
|
||||||
for (const {i, group, cells} of pack.routes) {
|
function drawRoutes() {
|
||||||
if (group !== "searoutes") straightenPathAngles(cells); // mutates points
|
TIME && console.time("drawRoutes");
|
||||||
const pathPoints = cells.map(cellId => points[cellId]);
|
const routePaths = {};
|
||||||
|
const lineGen = d3.line();
|
||||||
|
|
||||||
lineGen.curve(curves[group] || curves.default);
|
for (const route of pack.routes) {
|
||||||
const path = round(lineGen(pathPoints), 1);
|
const {i, group} = route;
|
||||||
|
lineGen.curve(ROUTE_CURVES[group] || ROUTE_CURVES.default);
|
||||||
|
const routePoints = getRoutePoints(route);
|
||||||
|
const path = round(lineGen(routePoints), 1);
|
||||||
|
|
||||||
if (!routePaths[group]) routePaths[group] = [];
|
if (!routePaths[group]) routePaths[group] = [];
|
||||||
routePaths[group].push(`<path id="route${i}" d="${path}"/>`);
|
routePaths[group].push(`<path id="route${i}" d="${path}"/>`);
|
||||||
|
|
@ -1667,27 +1661,33 @@ function drawRoutes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
TIME && console.timeEnd("drawRoutes");
|
TIME && console.timeEnd("drawRoutes");
|
||||||
|
|
||||||
function adjustBurgPoints() {
|
|
||||||
const points = Array.from(cells.p);
|
|
||||||
|
|
||||||
for (const burg of burgs) {
|
|
||||||
if (burg.i === 0) continue;
|
|
||||||
const {cell, x, y} = burg;
|
|
||||||
points[cell] = [x, y];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return points;
|
const ROUTES_SHARP_ANGLE = 135;
|
||||||
|
const ROUTES_VERY_SHARP_ANGLE = 115;
|
||||||
|
|
||||||
|
function getRoutePoints({points, cells: cellIds, group}) {
|
||||||
|
if (points) return points;
|
||||||
|
const {cells, burgs} = pack;
|
||||||
|
|
||||||
|
const routePoints = cellIds.map(cellId => {
|
||||||
|
const burgId = cells.burg[cellId];
|
||||||
|
if (burgId) {
|
||||||
|
const {x, y} = burgs[burgId];
|
||||||
|
return [x, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
function straightenPathAngles(cellIds) {
|
return cells.p[cellId];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (group !== "searoutes") {
|
||||||
for (let i = 1; i < cellIds.length - 1; i++) {
|
for (let i = 1; i < cellIds.length - 1; i++) {
|
||||||
const cellId = cellIds[i];
|
const cellId = cellIds[i];
|
||||||
if (cells.burg[cellId]) continue;
|
if (cells.burg[cellId]) continue;
|
||||||
|
|
||||||
const prev = points[cellIds[i - 1]];
|
const prev = routePoints[i - 1];
|
||||||
const that = points[cellId];
|
const that = routePoints[i];
|
||||||
const next = points[cellIds[i + 1]];
|
const next = routePoints[i + 1];
|
||||||
|
|
||||||
const dAx = prev[0] - that[0];
|
const dAx = prev[0] - that[0];
|
||||||
const dAy = prev[1] - that[1];
|
const dAy = prev[1] - that[1];
|
||||||
|
|
@ -1695,25 +1695,29 @@ function drawRoutes() {
|
||||||
const dBy = next[1] - that[1];
|
const dBy = next[1] - that[1];
|
||||||
const angle = (Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy) * 180) / Math.PI;
|
const angle = (Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy) * 180) / Math.PI;
|
||||||
|
|
||||||
if (Math.abs(angle) < SHARP_ANGLE) {
|
if (Math.abs(angle) < ROUTES_SHARP_ANGLE) {
|
||||||
const middleX = (prev[0] + next[0]) / 2;
|
const middleX = (prev[0] + next[0]) / 2;
|
||||||
const middleY = (prev[1] + next[1]) / 2;
|
const middleY = (prev[1] + next[1]) / 2;
|
||||||
|
|
||||||
if (Math.abs(angle) < VERY_SHARP_ANGLE) {
|
if (Math.abs(angle) < ROUTES_VERY_SHARP_ANGLE) {
|
||||||
const newX = (that[0] + middleX * 2) / 3;
|
const newX = (that[0] + middleX * 2) / 3;
|
||||||
const newY = (that[1] + middleY * 2) / 3;
|
const newY = (that[1] + middleY * 2) / 3;
|
||||||
points[cellId] = [newX, newY];
|
routePoints[i] = [newX, newY];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newX = (that[0] + middleX) / 2;
|
const newX = (that[0] + middleX) / 2;
|
||||||
const newY = (that[1] + middleY) / 2;
|
const newY = (that[1] + middleY) / 2;
|
||||||
points[cellId] = [newX, newY];
|
routePoints[i] = [newX, newY];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return routePoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawRoute() {}
|
||||||
|
|
||||||
function toggleMilitary() {
|
function toggleMilitary() {
|
||||||
if (!layerIsOn("toggleMilitary")) {
|
if (!layerIsOn("toggleMilitary")) {
|
||||||
turnButtonOn("toggleMilitary");
|
turnButtonOn("toggleMilitary");
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,10 @@ function editRiver(id) {
|
||||||
|
|
||||||
elSelected = d3.select("#" + id).on("click", addControlPoint);
|
elSelected = d3.select("#" + id).on("click", addControlPoint);
|
||||||
|
|
||||||
tip("Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead", true);
|
tip(
|
||||||
|
"Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead",
|
||||||
|
true
|
||||||
|
);
|
||||||
debug.append("g").attr("id", "controlCells");
|
debug.append("g").attr("id", "controlCells");
|
||||||
debug.append("g").attr("id", "controlPoints");
|
debug.append("g").attr("id", "controlPoints");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,306 +1,280 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function editRoute({node, mode}) {
|
function editRoute(id) {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
|
if (elSelected && id === elSelected.attr("id")) return;
|
||||||
closeDialogs(".stable");
|
closeDialogs(".stable");
|
||||||
|
|
||||||
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
if (!layerIsOn("toggleRoutes")) toggleRoutes();
|
||||||
|
byId("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
||||||
|
if (!layerIsOn("toggleCells")) toggleCells();
|
||||||
|
|
||||||
|
elSelected = d3.select("#" + id).on("click", addControlPoint);
|
||||||
|
|
||||||
|
tip(
|
||||||
|
"Drag control points to change the route. Click on point to remove it. Click on the route to add additional control point. For major changes please create a new route instead",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
debug.append("g").attr("id", "controlCells");
|
||||||
|
debug.append("g").attr("id", "controlPoints");
|
||||||
|
|
||||||
|
updateRouteData();
|
||||||
|
|
||||||
|
drawControlPoints(getRoutePoints(getRoute()));
|
||||||
|
drawCells();
|
||||||
|
|
||||||
$("#routeEditor").dialog({
|
$("#routeEditor").dialog({
|
||||||
title: "Edit Route",
|
title: "Edit Route",
|
||||||
resizable: false,
|
resizable: false,
|
||||||
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"},
|
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||||
close: closeRoutesEditor
|
close: closeRouteEditor
|
||||||
});
|
});
|
||||||
|
|
||||||
debug.append("g").attr("id", "controlPoints");
|
|
||||||
d3.select(node).on("click", addInterimControlPoint);
|
|
||||||
drawControlPoints(node);
|
|
||||||
selectRouteGroup(node);
|
|
||||||
|
|
||||||
viewbox.on("touchmove mousemove", showEditorTips);
|
|
||||||
if (mode === "onclick") toggleRouteCreationMode();
|
|
||||||
|
|
||||||
if (modules.editRoute) return;
|
if (modules.editRoute) return;
|
||||||
modules.editRoute = true;
|
modules.editRoute = true;
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
document.getElementById("routeGroupsShow").addEventListener("click", showGroupSection);
|
byId("routeCreateSelectingCells").on("click", createRoute);
|
||||||
document.getElementById("routeGroup").addEventListener("change", changeRouteGroup);
|
byId("routeEditStyle").on("click", () => editStyle("routes"));
|
||||||
document.getElementById("routeGroupAdd").addEventListener("click", toggleNewGroupInput);
|
byId("routeElevationProfile").on("click", showElevationProfile);
|
||||||
document.getElementById("routeGroupName").addEventListener("change", createNewGroup);
|
byId("routeLegend").on("click", editRouteLegend);
|
||||||
document.getElementById("routeGroupRemove").addEventListener("click", removeRouteGroup);
|
byId("routeRemove").on("click", removeRoute);
|
||||||
document.getElementById("routeGroupsHide").addEventListener("click", hideGroupSection);
|
byId("routeName").on("input", changeName);
|
||||||
document.getElementById("routeElevationProfile").addEventListener("click", showElevationProfile);
|
byId("routeGroup").on("input", changeGroup);
|
||||||
|
byId("routeNameCulture").on("click", generateNameCulture);
|
||||||
|
byId("routeNameRandom").on("click", generateNameRandom);
|
||||||
|
|
||||||
document.getElementById("routeEditStyle").addEventListener("click", editGroupStyle);
|
function getRoute() {
|
||||||
document.getElementById("routeSplit").addEventListener("click", toggleRouteSplitMode);
|
const routeId = +elSelected.attr("id").slice(5);
|
||||||
document.getElementById("routeLegend").addEventListener("click", editRouteLegend);
|
const route = pack.routes.find(r => r.i === routeId);
|
||||||
document.getElementById("routeNew").addEventListener("click", toggleRouteCreationMode);
|
return route;
|
||||||
document.getElementById("routeRemove").addEventListener("click", removeRoute);
|
|
||||||
|
|
||||||
function showEditorTips() {
|
|
||||||
showMainTip();
|
|
||||||
if (routeNew.classList.contains("pressed")) return;
|
|
||||||
if (d3.event.target.id === node.getAttribute("id")) tip("Click to add a control point");
|
|
||||||
else if (d3.event.target.parentNode.id === "controlPoints") tip("Drag to move, click to delete the control point");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawControlPoints(node) {
|
function updateRouteData() {
|
||||||
const totalLength = node.getTotalLength();
|
const route = getRoute();
|
||||||
const CONTROL_POINST_DISTANCE = 10;
|
|
||||||
const increment = totalLength / Math.ceil(totalLength / CONTROL_POINST_DISTANCE);
|
route.name = route.name || generateRouteName(route);
|
||||||
for (let i = 0; i <= totalLength; i += increment) {
|
byId("routeName").value = route.name;
|
||||||
const point = node.getPointAtLength(i);
|
|
||||||
addControlPoint([point.x, point.y]);
|
const routeGroup = byId("routeGroup");
|
||||||
}
|
routeGroup.options.length = 0;
|
||||||
routeLength.innerHTML = rn(totalLength * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
routes.selectAll("g").each(function () {
|
||||||
|
routeGroup.options.add(new Option(this.id, this.id, false, this.id === route.group));
|
||||||
|
});
|
||||||
|
|
||||||
|
updateRouteLength(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addControlPoint(point, before = null) {
|
function generateRouteName(route) {
|
||||||
debug
|
const {cells, burgs} = pack;
|
||||||
.select("#controlPoints")
|
|
||||||
.insert("circle", before)
|
const burgName = (() => {
|
||||||
.attr("cx", point[0])
|
const priority = [route.cells.at(-1), route.cells.at(0), route.cells.slice(1, -1).reverse()];
|
||||||
.attr("cy", point[1])
|
for (const cellId of priority) {
|
||||||
.attr("r", 0.6)
|
const burgId = cells.burg[cellId];
|
||||||
.call(d3.drag().on("drag", dragControlPoint))
|
if (burgId) return burgs[burgId].name;
|
||||||
.on("click", clickControlPoint);
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const type = route.group.replace(/s$/, "");
|
||||||
|
if (burgName) return `${getAdjective(burgName)} ${type}`;
|
||||||
|
|
||||||
|
return "Unnamed route";
|
||||||
}
|
}
|
||||||
|
|
||||||
function addInterimControlPoint() {
|
function updateRouteLength(route) {
|
||||||
const point = d3.mouse(this);
|
route.length = rn(elSelected.node().getTotalLength() / 2, 2);
|
||||||
const controls = document.getElementById("controlPoints").querySelectorAll("circle");
|
const lengthUI = `${rn(route.length * distanceScaleInput.value)} ${distanceUnitInput.value}`;
|
||||||
const points = Array.from(controls).map(circle => [+circle.getAttribute("cx"), +circle.getAttribute("cy")]);
|
byId("routeLength").value = lengthUI;
|
||||||
const index = getSegmentId(points, point, 2);
|
|
||||||
addControlPoint(point, ":nth-child(" + (index + 1) + ")");
|
|
||||||
|
|
||||||
redrawRoute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function dragControlPoint() {
|
function drawControlPoints(points) {
|
||||||
this.setAttribute("cx", d3.event.x);
|
|
||||||
this.setAttribute("cy", d3.event.y);
|
|
||||||
redrawRoute();
|
|
||||||
}
|
|
||||||
|
|
||||||
function redrawRoute() {
|
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
|
||||||
const points = [];
|
|
||||||
debug
|
debug
|
||||||
.select("#controlPoints")
|
.select("#controlPoints")
|
||||||
.selectAll("circle")
|
.selectAll("circle")
|
||||||
.each(function () {
|
.data(points)
|
||||||
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
|
.join("circle")
|
||||||
|
.attr("cx", d => d[0])
|
||||||
|
.attr("cy", d => d[1])
|
||||||
|
.attr("r", 0.6)
|
||||||
|
.call(d3.drag().on("start", dragControlPoint))
|
||||||
|
.on("click", removeControlPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawCells() {
|
||||||
|
const {cells} = getRoute();
|
||||||
|
debug.select("#controlCells").selectAll("polygon").data(cells).join("polygon").attr("points", getPackPolygon);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragControlPoint() {
|
||||||
|
const initCell = findCell(d3.event.x, d3.event.y);
|
||||||
|
const route = getRoute();
|
||||||
|
const cellIndex = route.cells.indexOf(initCell);
|
||||||
|
|
||||||
|
d3.event.on("drag", function () {
|
||||||
|
this.setAttribute("cx", d3.event.x);
|
||||||
|
this.setAttribute("cy", d3.event.y);
|
||||||
|
this.__data__ = [rn(d3.event.x, 2), rn(d3.event.y, 2)];
|
||||||
|
|
||||||
|
redrawRoute();
|
||||||
|
drawCells();
|
||||||
});
|
});
|
||||||
|
|
||||||
node.setAttribute("d", round(lineGen(points)));
|
d3.event.on("end", () => {
|
||||||
routeLength.innerHTML = rn(node.getTotalLength() * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
const movedToCell = findCell(d3.event.x, d3.event.y);
|
||||||
|
|
||||||
if (modules.elevation) showEPForRoute(node);
|
if (movedToCell !== initCell) {
|
||||||
|
route.cells[cellIndex] = movedToCell;
|
||||||
|
|
||||||
|
const prevCell = route.cells[cellIndex - 1];
|
||||||
|
if (prevCell) {
|
||||||
|
removeConnection(initCell, prevCell);
|
||||||
|
addConnection(movedToCell, prevCell, route.i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextCell = route.cells[cellIndex + 1];
|
||||||
|
if (nextCell) {
|
||||||
|
removeConnection(initCell, nextCell);
|
||||||
|
addConnection(movedToCell, nextCell, route.i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function redrawRoute() {
|
||||||
|
const route = getRoute();
|
||||||
|
route.points = debug.selectAll("#controlPoints > *").data();
|
||||||
|
route.cells = unique(route.points.map(([x, y]) => findCell(x, y)));
|
||||||
|
|
||||||
|
const lineGen = d3.line();
|
||||||
|
lineGen.curve(ROUTE_CURVES[route.group] || ROUTE_CURVES.default);
|
||||||
|
|
||||||
|
const path = round(lineGen(route.points), 1);
|
||||||
|
elSelected.attr("d", path);
|
||||||
|
|
||||||
|
updateRouteLength(route);
|
||||||
|
if (modules.elevation) showEPForRoute(elSelected.node());
|
||||||
|
}
|
||||||
|
|
||||||
|
function addControlPoint() {
|
||||||
|
const [x, y] = d3.mouse(this);
|
||||||
|
|
||||||
|
const route = getRoute();
|
||||||
|
if (!route.points) route.points = debug.selectAll("#controlPoints > *").data();
|
||||||
|
|
||||||
|
const point = [rn(x, 2), rn(y, 2)];
|
||||||
|
const index = getSegmentId(route.points, point, 2);
|
||||||
|
route.points.splice(index, 0, point);
|
||||||
|
|
||||||
|
const cellId = findCell(x, y);
|
||||||
|
if (!route.cells.includes(cellId)) {
|
||||||
|
route.cells = unique(route.points.map(([x, y]) => findCell(x, y)));
|
||||||
|
const cellIndex = route.cells.indexOf(cellId);
|
||||||
|
|
||||||
|
const prev = route.cells[cellIndex - 1];
|
||||||
|
const next = route.cells[cellIndex + 1];
|
||||||
|
|
||||||
|
removeConnection(prev, next);
|
||||||
|
addConnection(prev, cellId, route.i);
|
||||||
|
addConnection(cellId, next, route.i);
|
||||||
|
|
||||||
|
drawCells();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawControlPoints(route.points);
|
||||||
|
redrawRoute();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawConnections() {
|
||||||
|
debug.selectAll("line").remove();
|
||||||
|
for (const [fromCellId, connections] of Object.entries(pack.cells.routes)) {
|
||||||
|
const from = pack.cells.p[fromCellId];
|
||||||
|
for (const toCellId of Object.keys(connections)) {
|
||||||
|
const to = pack.cells.p[toCellId];
|
||||||
|
debug
|
||||||
|
.append("line")
|
||||||
|
.attr("x1", from[0])
|
||||||
|
.attr("y1", from[1])
|
||||||
|
.attr("x2", to[0])
|
||||||
|
.attr("y2", to[1])
|
||||||
|
.attr("stroke", "red")
|
||||||
|
.attr("stroke-width", 0.4)
|
||||||
|
.attr("opacity", 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeConnection(from, to) {
|
||||||
|
const routes = pack.cells.routes;
|
||||||
|
if (routes[from]) delete routes[from][to];
|
||||||
|
if (routes[to]) delete routes[to][from];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addConnection(from, to, routeId) {
|
||||||
|
const routes = pack.cells.routes;
|
||||||
|
|
||||||
|
if (!routes[from]) routes[from] = {};
|
||||||
|
routes[from][to] = routeId;
|
||||||
|
|
||||||
|
if (!routes[to]) routes[to] = {};
|
||||||
|
routes[to][from] = routeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeControlPoint() {
|
||||||
|
this.remove();
|
||||||
|
redrawRoute();
|
||||||
|
drawCells();
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeName() {
|
||||||
|
getRoute().name = this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGroup() {
|
||||||
|
const group = this.value;
|
||||||
|
byId(group).appendChild(elSelected.node());
|
||||||
|
getRoute().group = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNameCulture() {
|
||||||
|
const route = getRoute();
|
||||||
|
const cell = ra(route.cells);
|
||||||
|
const cultureId = pack.cells.culture[cell];
|
||||||
|
route.name = routeName.value = Names.getCulture(cultureId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNameRandom() {
|
||||||
|
const route = getRoute();
|
||||||
|
route.name = routeName.value = Names.getBase(rand(nameBases.length - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
function showElevationProfile() {
|
function showElevationProfile() {
|
||||||
modules.elevation = true;
|
modules.elevation = true;
|
||||||
showEPForRoute(node);
|
showEPForRoute(elSelected.node());
|
||||||
}
|
|
||||||
|
|
||||||
function showGroupSection() {
|
|
||||||
document.querySelectorAll("#routeEditor > button").forEach(el => (el.style.display = "none"));
|
|
||||||
document.getElementById("routeGroupsSelection").style.display = "inline-block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideGroupSection() {
|
|
||||||
document.querySelectorAll("#routeEditor > button").forEach(el => (el.style.display = "inline-block"));
|
|
||||||
document.getElementById("routeGroupsSelection").style.display = "none";
|
|
||||||
document.getElementById("routeGroupName").style.display = "none";
|
|
||||||
document.getElementById("routeGroupName").value = "";
|
|
||||||
document.getElementById("routeGroup").style.display = "inline-block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectRouteGroup(node) {
|
|
||||||
const group = node.parentNode.id;
|
|
||||||
const select = document.getElementById("routeGroup");
|
|
||||||
select.options.length = 0; // remove all options
|
|
||||||
|
|
||||||
routes.selectAll("g").each(function () {
|
|
||||||
select.options.add(new Option(this.id, this.id, false, this.id === group));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeRouteGroup() {
|
|
||||||
document.getElementById(this.value).appendChild(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleNewGroupInput() {
|
|
||||||
if (routeGroupName.style.display === "none") {
|
|
||||||
routeGroupName.style.display = "inline-block";
|
|
||||||
routeGroupName.focus();
|
|
||||||
routeGroup.style.display = "none";
|
|
||||||
} else {
|
|
||||||
routeGroupName.style.display = "none";
|
|
||||||
routeGroup.style.display = "inline-block";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewGroup() {
|
|
||||||
if (!this.value) {
|
|
||||||
tip("Please provide a valid group name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const group = this.value
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/ /g, "_")
|
|
||||||
.replace(/[^\w\s]/gi, "");
|
|
||||||
|
|
||||||
if (document.getElementById(group)) {
|
|
||||||
tip("Element with this id already exists. Please provide a unique name", false, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Number.isFinite(+group.charAt(0))) {
|
|
||||||
tip("Group name should start with a letter", false, "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// just rename if only 1 element left
|
|
||||||
const oldGroup = node.parentNode;
|
|
||||||
const basic = ["roads", "trails", "searoutes"].includes(oldGroup.id);
|
|
||||||
if (!basic && oldGroup.childElementCount === 1) {
|
|
||||||
document.getElementById("routeGroup").selectedOptions[0].remove();
|
|
||||||
document.getElementById("routeGroup").options.add(new Option(group, group, false, true));
|
|
||||||
oldGroup.id = group;
|
|
||||||
toggleNewGroupInput();
|
|
||||||
document.getElementById("routeGroupName").value = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newGroup = node.parentNode.cloneNode(false);
|
|
||||||
document.getElementById("routes").appendChild(newGroup);
|
|
||||||
newGroup.id = group;
|
|
||||||
document.getElementById("routeGroup").options.add(new Option(group, group, false, true));
|
|
||||||
document.getElementById(group).appendChild(node);
|
|
||||||
|
|
||||||
toggleNewGroupInput();
|
|
||||||
document.getElementById("routeGroupName").value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeRouteGroup() {
|
|
||||||
const group = node.parentNode.id;
|
|
||||||
const basic = ["roads", "trails", "searoutes"].includes(group);
|
|
||||||
const count = node.parentNode.childElementCount;
|
|
||||||
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
|
|
||||||
basic ? "all elements in the group" : "the entire route group"
|
|
||||||
}? <br /><br />Routes to be
|
|
||||||
removed: ${count}`;
|
|
||||||
$("#alert").dialog({
|
|
||||||
resizable: false,
|
|
||||||
title: "Remove route group",
|
|
||||||
buttons: {
|
|
||||||
Remove: function () {
|
|
||||||
$(this).dialog("close");
|
|
||||||
$("#routeEditor").dialog("close");
|
|
||||||
hideGroupSection();
|
|
||||||
if (basic)
|
|
||||||
routes
|
|
||||||
.select("#" + group)
|
|
||||||
.selectAll("path")
|
|
||||||
.remove();
|
|
||||||
else routes.select("#" + group).remove();
|
|
||||||
},
|
|
||||||
Cancel: function () {
|
|
||||||
$(this).dialog("close");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function editGroupStyle() {
|
|
||||||
const g = node.parentNode.id;
|
|
||||||
editStyle("routes", g);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleRouteSplitMode() {
|
|
||||||
document.getElementById("routeNew").classList.remove("pressed");
|
|
||||||
this.classList.toggle("pressed");
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickControlPoint() {
|
|
||||||
if (routeSplit.classList.contains("pressed")) splitRoute(this);
|
|
||||||
else {
|
|
||||||
this.remove();
|
|
||||||
redrawRoute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function splitRoute(clicked) {
|
|
||||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
|
||||||
const group = d3.select(node.parentNode);
|
|
||||||
routeSplit.classList.remove("pressed");
|
|
||||||
|
|
||||||
const points1 = [];
|
|
||||||
const points2 = [];
|
|
||||||
let points = points1;
|
|
||||||
|
|
||||||
debug
|
|
||||||
.select("#controlPoints")
|
|
||||||
.selectAll("circle")
|
|
||||||
.each(function () {
|
|
||||||
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
|
|
||||||
if (this === clicked) {
|
|
||||||
points = points2;
|
|
||||||
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
|
|
||||||
}
|
|
||||||
this.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
node.setAttribute("d", round(lineGen(points1)));
|
|
||||||
const id = getNextId("route");
|
|
||||||
group.append("path").attr("id", id).attr("d", lineGen(points2));
|
|
||||||
debug.select("#controlPoints").selectAll("circle").remove();
|
|
||||||
drawControlPoints(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleRouteCreationMode() {
|
|
||||||
document.getElementById("routeSplit").classList.remove("pressed");
|
|
||||||
document.getElementById("routeNew").classList.toggle("pressed");
|
|
||||||
if (document.getElementById("routeNew").classList.contains("pressed")) {
|
|
||||||
tip("Click on map to add control points", true);
|
|
||||||
viewbox.on("click", addPointOnClick).style("cursor", "crosshair");
|
|
||||||
d3.select(node).on("click", null);
|
|
||||||
} else {
|
|
||||||
clearMainTip();
|
|
||||||
viewbox.on("click", clicked).style("cursor", "default");
|
|
||||||
d3.select(node).on("click", addInterimControlPoint).attr("data-new", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPointOnClick() {
|
|
||||||
// create new route
|
|
||||||
if (!node.dataset.new) {
|
|
||||||
debug.select("#controlPoints").selectAll("circle").remove();
|
|
||||||
const parent = node.parentNode;
|
|
||||||
const id = getNextId("route");
|
|
||||||
const newRoute = d3.select(parent).append("path").attr("id", id).attr("data-new", 1);
|
|
||||||
node = newRoute.node();
|
|
||||||
}
|
|
||||||
|
|
||||||
addControlPoint(d3.mouse(this));
|
|
||||||
redrawRoute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function editRouteLegend() {
|
function editRouteLegend() {
|
||||||
const id = node.getAttribute("id");
|
const id = elSelected.attr("id");
|
||||||
editNotes(id, id);
|
const route = getRoute();
|
||||||
|
editNotes(id, route.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRoute() {
|
||||||
|
// TODO: white the code :)
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeRoute() {
|
function removeRoute() {
|
||||||
alertMessage.innerHTML = "Are you sure you want to remove the route?";
|
alertMessage.innerHTML = "Are you sure you want to remove the route and all its tributaries";
|
||||||
$("#alert").dialog({
|
$("#alert").dialog({
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: "Remove route",
|
width: "22em",
|
||||||
|
title: "Remove route and tributaries",
|
||||||
buttons: {
|
buttons: {
|
||||||
Remove: function () {
|
Remove: function () {
|
||||||
$(this).dialog("close");
|
$(this).dialog("close");
|
||||||
node.remove();
|
const route = +elSelected.attr("id").slice(5);
|
||||||
|
Routes.remove(route);
|
||||||
|
elSelected.remove();
|
||||||
$("#routeEditor").dialog("close");
|
$("#routeEditor").dialog("close");
|
||||||
},
|
},
|
||||||
Cancel: function () {
|
Cancel: function () {
|
||||||
|
|
@ -310,13 +284,16 @@ function editRoute({node, mode}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeRoutesEditor() {
|
function closeRouteEditor() {
|
||||||
node.data.new = null;
|
|
||||||
d3.select(node).on("click", null);
|
|
||||||
clearMainTip();
|
|
||||||
routeSplit.classList.remove("pressed");
|
|
||||||
routeNew.classList.remove("pressed");
|
|
||||||
debug.select("#controlPoints").remove();
|
debug.select("#controlPoints").remove();
|
||||||
|
debug.select("#controlCells").remove();
|
||||||
|
|
||||||
|
elSelected.on("click", null);
|
||||||
unselect();
|
unselect();
|
||||||
|
clearMainTip();
|
||||||
|
|
||||||
|
const forced = +byId("toggleCells").dataset.forced;
|
||||||
|
byId("toggleCells").dataset.forced = 0;
|
||||||
|
if (forced && layerIsOn("toggleCells")) toggleCells();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -793,13 +793,9 @@ function addRouteOnClick() {
|
||||||
unpressClickToAddButton();
|
unpressClickToAddButton();
|
||||||
const [x, y] = d3.mouse(this);
|
const [x, y] = d3.mouse(this);
|
||||||
|
|
||||||
const newRoute = routes
|
const id = getNextId("route");
|
||||||
.select("g")
|
routes.select("g").append("path").attr("id", id).attr("data-new", 1).attr("d", `M${x},${y}`);
|
||||||
.append("path")
|
editRoute(id);
|
||||||
.attr("id", getNextId("route"))
|
|
||||||
.attr("data-new", 1)
|
|
||||||
.attr("d", `M${x},${y}`);
|
|
||||||
editRoute({node: newRoute.node(), mode: "onclick"});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleAddMarker() {
|
function toggleAddMarker() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue