v1.5.79 - river overview update

This commit is contained in:
Azgaar 2021-03-01 01:51:31 +03:00
parent 41d03e8039
commit fbec08f982
7 changed files with 104 additions and 100 deletions

View file

@ -1383,7 +1383,11 @@ div.states span.inactive:hover {
color: #abaaaa; color: #abaaaa;
} }
div.states>input.riverType { div.states > .riverName {
width: 7em;
}
div.states > .riverType {
width: 5em; width: 5em;
} }

View file

@ -1611,7 +1611,7 @@
<div style="padding: .1em" data-tip="River width multiplier"> <div style="padding: .1em" data-tip="River width multiplier">
<div class="label">Width modifier:</div> <div class="label">Width modifier:</div>
<input id="riverWidthFactor" type="number" min=.2 max=4 step=.1 /> <input id="riverWidthFactor" type="number" min=.1 max=4 step=.1 />
</div> </div>
</div> </div>
@ -3088,19 +3088,21 @@
<div id="riversOverview" class="dialog stable" style="display: none"> <div id="riversOverview" class="dialog stable" style="display: none">
<div id="riversHeader" class="header"> <div id="riversHeader" class="header">
<div style="left:1.7em" data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">River&nbsp;</div> <div style="left:1.3em" data-tip="Click to sort by river name" class="sortable alphabetically" data-sortby="name">River&nbsp;</div>
<div style="left:7.9em" data-tip="Click to sort by river type name" class="sortable alphabetically" data-sortby="type">Type&nbsp;</div> <div style="left:8.6em" data-tip="Click to sort by river type name" class="sortable alphabetically" data-sortby="type">Type&nbsp;</div>
<div style="left:11.9em" data-tip="Click to sort by discharge (flux in m3/s)" class="sortable icon-sort-number-down" data-sortby="discharge">Discharge&nbsp;</div> <div style="left:13em" data-tip="Click to sort by discharge (flux in m3/s)" class="sortable icon-sort-number-down" data-sortby="discharge">Discharge&nbsp;</div>
<div style="left:18.2em" data-tip="Click to sort by river length" class="sortable" data-sortby="length">Length&nbsp;</div> <div style="left:19.3em" data-tip="Click to sort by river length" class="sortable" data-sortby="length">Length&nbsp;</div>
<div style="left:23.4em" data-tip="Click to sort by river mouth width" class="sortable" data-sortby="width">Width&nbsp;</div> <div style="left:24.4em" data-tip="Click to sort by river mouth width" class="sortable" data-sortby="width">Width&nbsp;</div>
<div style="left:29em" data-tip="Click to sort by river basin" class="sortable alphabetically" data-sortby="basin">Basin&nbsp;</div> <div style="left:30em" data-tip="Click to sort by river basin" class="sortable alphabetically" data-sortby="basin">Basin&nbsp;</div>
</div> </div>
<div id="riversBody" class="burgs-table"></div> <div id="riversBody" class="burgs-table"></div>
<div id="riversFooter" class="totalLine"> <div id="riversFooter" class="totalLine">
<div data-tip="Rivers number" style="margin-left: 4px">Rivers:&nbsp;<span id="riversFooterNumber">0</span></div> <div data-tip="Rivers number" style="margin-left: 4px">Rivers:&nbsp;<span id="riversFooterNumber">0</span></div>
<div data-tip="Average length" style="margin-left: 14px">Average length:&nbsp;<span id="riversFooterLength">0</span></div> <div data-tip="Average discharge" style="margin-left: 12px">Average discharge:&nbsp;<span id="riversFooterDischarge">0</span></div>
<div data-tip="Average length" style="margin-left: 12px">Length:&nbsp;<span id="riversFooterLength">0</span></div>
<div data-tip="Average mouth width" style="margin-left: 12px">Width:&nbsp;<span id="riversFooterWidth">0</span></div>
</div> </div>
<div id="riversBottom"> <div id="riversBottom">

View file

@ -607,7 +607,7 @@ function highlightElement(element) {
if (tr[0]) x += tr[0]; if (tr[0]) x += tr[0];
let y = box.y + box.height / 2; let y = box.y + box.height / 2;
if (tr[1]) y += tr[1]; if (tr[1]) y += tr[1];
if (scale >= 2) zoomTo(x, y, scale, 1600); zoomTo(x, y, scale > 2 ? scale : 3, 1600);
} }
function selectIcon(initial, callback) { function selectIcon(initial, callback) {

View file

@ -55,7 +55,10 @@ function showElevationProfile(data, routeLen, isRiver) {
for (let i = 0, prevB = 0, prevH = -1; i < data.length; i++) { for (let i = 0, prevB = 0, prevH = -1; i < data.length; i++) {
let cell = data[i]; let cell = data[i];
let h = pack.cells.h[cell]; let h = pack.cells.h[cell];
if (h < 20) h = 20; if (h < 20) {
const f = pack.features[pack.cells.f[cell]];
if (f.type === "lake") h = f.height; else h = 20;
}
// check for river up-hill // check for river up-hill
if (prevH != -1) { if (prevH != -1) {

View file

@ -44,7 +44,7 @@ function editRiver(id) {
} }
function getRiver() { function getRiver() {
const riverId = +node.id.slice(5); const riverId = +elSelected.attr("id").slice(5);
const river = pack.rivers.find(r => r.i === riverId); const river = pack.rivers.find(r => r.i === riverId);
return river; return river;
} }
@ -78,7 +78,7 @@ function editRiver(id) {
function drawControlPoints(node) { function drawControlPoints(node) {
const length = getRiver().length; const length = getRiver().length;
const segments = Math.ceil(length / 5); const segments = Math.ceil(length / 4);
const increment = rn(length / segments * 1e5); const increment = rn(length / segments * 1e5);
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) { for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
const p1 = node.getPointAtLength(i / 1e5); const p1 = node.getPointAtLength(i / 1e5);
@ -89,7 +89,7 @@ function editRiver(id) {
function addControlPoint(point) { function addControlPoint(point) {
debug.select("#controlPoints").append("circle") debug.select("#controlPoints").append("circle")
.attr("cx", point[0]).attr("cy", point[1]).attr("r", .8) .attr("cx", point[0]).attr("cy", point[1]).attr("r", .6)
.call(d3.drag().on("drag", dragControlPoint)) .call(d3.drag().on("drag", dragControlPoint))
.on("click", clickControlPoint); .on("click", clickControlPoint);
} }
@ -106,7 +106,7 @@ function editRiver(id) {
points.push([+this.getAttribute("cx"), +this.getAttribute("cy")]); points.push([+this.getAttribute("cx"), +this.getAttribute("cy")]);
}); });
if (points.length === 1) return; if (points.length < 2) return;
if (points.length === 2) { if (points.length === 2) {
const p0 = points[0], p1 = points[1]; const p0 = points[0], p1 = points[1];
const angle = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]); const angle = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]);
@ -114,13 +114,20 @@ function editRiver(id) {
elSelected.attr("d", `M${p0[0]},${p0[1]} L${p1[0]},${p1[1]} l${-sin/2},${cos/2} Z`); elSelected.attr("d", `M${p0[0]},${p0[1]} L${p1[0]},${p1[1]} l${-sin/2},${cos/2} Z`);
return; return;
} }
const [d, length] = Rivers.getPath(points, +riverWidthInput.value, +riverIncrement.value);
elSelected.attr("d", d);
//updateRiverLength(length);
if (modules.elevation) { const widthFactor = +document.getElementById("riverWidthFactor").value;
showEPForRiver(elSelected.node()); const sourceWidth = +document.getElementById("riverSourceWidth").value;
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
elSelected.attr("d", path);
const r = getRiver();
if (r) {
r.width = rn(offset ** 2, 2);
r.length = length;
updateRiverData();
} }
if (modules.elevation) showEPForRiver(elSelected.node());
} }
function clickControlPoint() { function clickControlPoint() {
@ -181,21 +188,25 @@ function editRiver(id) {
} }
function changeSourceWidth() { function changeSourceWidth() {
getRiver().sourceWidth = this.value; getRiver().sourceWidth = +this.value;
redrawRiver(); redrawRiver();
} }
function changeWidthFactor() { function changeWidthFactor() {
getRiver().widthFactor = this.value; getRiver().widthFactor = +this.value;
redrawRiver(); redrawRiver();
} }
function showElevationProfile() { function showElevationProfile() {
modules.elevation = true; modules.elevation = true;
showEPForRiver(elSelected.node()); showEPForRiver(elSelected.node());
} }
function editRiverLegend() {
const id = elSelected.attr("id");
editNotes(id, id);
}
function toggleRiverCreationMode() { function toggleRiverCreationMode() {
if (document.getElementById("riverNew").classList.contains("pressed")) exitRiverCreationMode(); if (document.getElementById("riverNew").classList.contains("pressed")) exitRiverCreationMode();
else { else {
@ -210,8 +221,7 @@ function editRiver(id) {
if (!elSelected.attr("data-new")) { if (!elSelected.attr("data-new")) {
debug.select("#controlPoints").selectAll("circle").remove(); debug.select("#controlPoints").selectAll("circle").remove();
const id = getNextId("river"); const id = getNextId("river");
elSelected = d3.select(elSelected.node().parentNode).append("path").attr("id", id) elSelected = d3.select(elSelected.node().parentNode).append("path").attr("id", id).attr("data-new", 1);
.attr("data-new", 1).attr("data-width", 1).attr("data-increment", .5);
} }
// add control point // add control point
@ -220,9 +230,38 @@ function editRiver(id) {
redrawRiver(); redrawRiver();
} }
function editRiverLegend() { function exitRiverCreationMode() {
const id = elSelected.attr("id"); riverNew.classList.remove("pressed");
editNotes(id, id); clearMainTip();
viewbox.on("click", clicked).style("cursor", "default");
elSelected.on("click", addInterimControlPoint);
if (!elSelected.attr("data-new")) return; // no need to create a new river
elSelected.attr("data-new", null);
// add a river
const r = +elSelected.attr("id").slice(5);
const node = elSelected.node(), length = node.getTotalLength() / 2;
const cells = [];
const segments = Math.ceil(length / 4), increment = rn(length / segments * 1e5);
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
const p = node.getPointAtLength(i / 1e5);
const cell = findCell(p.x, p.y);
if (!pack.cells.r[cell]) pack.cells.r[cell] = r;
cells.push(cell);
}
const source = cells[0], mouth = last(cells);
const name = Rivers.getName(mouth);
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
const discharge = rn(cells.length * 20 * Math.random());
const widthFactor = +document.getElementById("riverWidthFactor").value;
const sourceWidth = +document.getElementById("riverSourceWidth").value;
pack.rivers.push({i:r, source, mouth, discharge, length, width: sourceWidth, widthFactor, sourceWidth, parent:0, name, type, basin:r});
} }
function removeRiver() { function removeRiver() {
@ -240,40 +279,10 @@ function editRiver(id) {
}); });
} }
function exitRiverCreationMode() {
riverNew.classList.remove("pressed");
clearMainTip();
viewbox.on("click", clicked).style("cursor", "default");
elSelected.on("click", addInterimControlPoint);
if (!elSelected.attr("data-new")) return; // no need to create a new river
elSelected.attr("data-new", null);
// add a river
const r = +elSelected.attr("id").slice(5);
const node = elSelected.node(), length = node.getTotalLength() / 2;
const cells = [];
const segments = Math.ceil(length / 8), increment = rn(length / segments * 1e5);
for (let i=increment*segments, c=i; i >= 0; i -= increment, c += increment) {
const p = node.getPointAtLength(i / 1e5);
const cell = findCell(p.x, p.y);
if (!pack.cells.r[cell]) pack.cells.r[cell] = r;
cells.push(cell);
}
const source = cells[0], mouth = last(cells);
const name = Rivers.getName(mouth);
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
pack.rivers.push({i:r, parent:0, length, source, mouth, basin:r, name, type});
}
function closeRiverEditor() { function closeRiverEditor() {
exitRiverCreationMode(); exitRiverCreationMode();
elSelected.on("click", null); elSelected.on("click", null);
debug.select("#controlPoints").remove(); debug.select("#controlPoints").remove();
unselect(); unselect();
} }
} }

View file

@ -27,17 +27,18 @@ function overviewRivers() {
function riversOverviewAddLines() { function riversOverviewAddLines() {
body.innerHTML = ""; body.innerHTML = "";
let lines = ""; let lines = "";
const unit = distanceUnitInput.value;
for (const r of pack.rivers) { for (const r of pack.rivers) {
const discharge = r.discharge + " m³/s"; const discharge = r.discharge + " m³/s";
const length = rn(r.length * distanceScaleInput.value) + " " + distanceUnitInput.value; const length = rn(r.length * distanceScaleInput.value) + " " + unit;
const width = rn(r.width * distanceScaleInput.value, 3) + " " + distanceUnitInput.value; const width = rn(r.width * distanceScaleInput.value, 3) + " " + unit;
const basin = pack.rivers.find(river => river.i === r.basin).name; const basin = pack.rivers.find(river => river.i === r.basin).name;
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-type="${r.type}" data-discharge="${r.discharge}" data-length="${r.length}" data-width="${r.width}" data-basin="${basin}"> lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-type="${r.type}" data-discharge="${r.discharge}" data-length="${r.length}" data-width="${r.width}" data-basin="${basin}">
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span> <span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
<input data-tip="River proper name. Click to change. Ctrl + click to regenerate" class="riverName" value="${r.name}" autocorrect="off" spellcheck="false"> <div data-tip="River name" class="riverName">${r.name}</div>
<input data-tip="River type name. Click to change" class="riverType" value="${r.type}"> <div data-tip="River type name" class="riverType">${r.type}</div>
<div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div> <div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div>
<div data-tip="River length from source to mouth" class="biomeArea">${length}</div> <div data-tip="River length from source to mouth" class="biomeArea">${length}</div>
<div data-tip="River mouth width" class="biomeArea">${width}</div> <div data-tip="River mouth width" class="biomeArea">${width}</div>
@ -50,15 +51,16 @@ function overviewRivers() {
// update footer // update footer
riversFooterNumber.innerHTML = pack.rivers.length; riversFooterNumber.innerHTML = pack.rivers.length;
const averageLength = rn(d3.sum(pack.rivers.map(r => r.length)) / pack.rivers.length); const averageDischarge = rn(d3.mean(pack.rivers.map(r => r.discharge)));
riversFooterLength.innerHTML = (averageLength * distanceScaleInput.value) + " " + distanceUnitInput.value; riversFooterDischarge.innerHTML = averageDischarge + " m³/s";
const averageLength = rn(d3.mean(pack.rivers.map(r => r.length)) );
riversFooterLength.innerHTML = (averageLength * distanceScaleInput.value) + " " + unit;
const averageWidth = rn(d3.mean(pack.rivers.map(r => r.width)), 3);
riversFooterWidth.innerHTML = rn(averageWidth * distanceScaleInput.value, 3) + " " + unit;
// add listeners // add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => riverHighlightOn(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => riverHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => riverHighlightOff(ev)));
body.querySelectorAll("div > input.riverName").forEach(el => el.addEventListener("input", changeRiverName));
body.querySelectorAll("div > input.riverName").forEach(el => el.addEventListener("click", regenerateRiverName));
body.querySelectorAll("div > input.riverType").forEach(el => el.addEventListener("input", changeRiverType));
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver)); body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomToRiver));
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor)); body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openRiverEditor));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerRiverRemove)); body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerRiverRemove));
@ -72,32 +74,11 @@ function overviewRivers() {
rivers.select("#river"+r).attr("stroke", "red").attr("stroke-width", 1); rivers.select("#river"+r).attr("stroke", "red").attr("stroke-width", 1);
} }
function riverHighlightOff() { function riverHighlightOff(e) {
const r = +event.target.dataset.id; const r = +e.target.dataset.id;
rivers.select("#river"+r).attr("stroke", null).attr("stroke-width", null); rivers.select("#river"+r).attr("stroke", null).attr("stroke-width", null);
} }
function changeRiverName() {
if (this.value == "") tip("Please provide a proper name", false, "error");
const river = +this.parentNode.dataset.id;
pack.rivers.find(r => r.i === river).name = this.value;
this.parentNode.dataset.name = this.value;
}
function regenerateRiverName(event) {
if (!isCtrlClick(event)) return;
const river = +this.parentNode.dataset.id;
const r = pack.rivers.find(r => r.i === river);
r.name = this.value = this.parentNode.dataset.name = Rivers.getName(r.mouth);
}
function changeRiverType() {
if (this.value == "") tip("Please provide a type name", false, "error");
const river = +this.parentNode.dataset.id;
pack.rivers.find(r => r.i === river).type = this.value;
this.parentNode.dataset.type = this.value;
}
function zoomToRiver() { function zoomToRiver() {
const r = +this.parentNode.dataset.id; const r = +this.parentNode.dataset.id;
const river = rivers.select("#river"+r).node(); const river = rivers.select("#river"+r).node();

View file

@ -522,25 +522,30 @@ function addRiverOnClick() {
} }
const points = Rivers.addMeandering(dataRiver, 1, .5); const points = Rivers.addMeandering(dataRiver, 1, .5);
const width = Math.random() * .5 + .9; const widthFactor = rn(.8 + Math.random() * .4, 1); // river width modifier [.8, 1.2]
const increment = Math.random() * .4 + .8; const sourceWidth = .1;
const [path, length] = Rivers.getPath(points, width, increment); const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
rivers.append("path").attr("d", path).attr("id", "river"+river).attr("data-width", width).attr("data-increment", increment); rivers.append("path").attr("d", path).attr("id", "river"+river);
// add new river to data or change extended river attributes // add new river to data or change extended river attributes
const r = pack.rivers.find(r => r.i === river); const r = pack.rivers.find(r => r.i === river);
const mouth = last(dataRiver).cell;
const discharge = cells.fl[mouth]; // in m3/s
if (r) { if (r) {
r.source = dataRiver[0].cell; r.source = dataRiver[0].cell;
r.length = length; r.length = length;
r.discharge = discharge;
} else { } else {
const parent = dataRiver[0].parent || 0; const parent = dataRiver[0].parent || 0;
const basin = Rivers.getBasin(river); const basin = Rivers.getBasin(river);
const source = dataRiver[0].cell; const source = dataRiver[0].cell;
const mouth = last(dataRiver).cell; const width = rn(offset ** 2, 2); // mounth width in km
const name = Rivers.getName(mouth); const name = Rivers.getName(mouth);
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)]; const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River"; const type = length < smallLength ? rw({"Creek":9, "River":3, "Brook":3, "Stream":1}) : "River";
pack.rivers.push({i:river, parent, length, source, mouth, basin, name, type});
pack.rivers.push({i:river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type});
} }
if (d3.event.shiftKey === false) { if (d3.event.shiftKey === false) {