mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
feat(hierarchy tree): animate on update
This commit is contained in:
parent
e3ea5cd479
commit
434907fa9e
7 changed files with 106 additions and 29 deletions
|
|
@ -7801,7 +7801,7 @@
|
||||||
<script src="modules/cultures-generator.js?v=06062022"></script>
|
<script src="modules/cultures-generator.js?v=06062022"></script>
|
||||||
<script src="modules/burgs-and-states.js?v=29052022"></script>
|
<script src="modules/burgs-and-states.js?v=29052022"></script>
|
||||||
<script src="modules/routes-generator.js"></script>
|
<script src="modules/routes-generator.js"></script>
|
||||||
<script src="modules/religions-generator.js?v=07062022"></script>
|
<script src="modules/religions-generator.js?v=12062022"></script>
|
||||||
<script src="modules/military-generator.js"></script>
|
<script src="modules/military-generator.js"></script>
|
||||||
<script src="modules/markers-generator.js"></script>
|
<script src="modules/markers-generator.js"></script>
|
||||||
<script src="modules/coa-generator.js"></script>
|
<script src="modules/coa-generator.js"></script>
|
||||||
|
|
@ -7820,7 +7820,7 @@
|
||||||
|
|
||||||
<script defer src="modules/relief-icons.js"></script>
|
<script defer src="modules/relief-icons.js"></script>
|
||||||
<script defer src="modules/ui/style.js"></script>
|
<script defer src="modules/ui/style.js"></script>
|
||||||
<script defer src="modules/ui/editors.js?v=080620222"></script>
|
<script defer src="modules/ui/editors.js?v=12062022"></script>
|
||||||
<script defer src="modules/ui/tools.js?v=12062022"></script>
|
<script defer src="modules/ui/tools.js?v=12062022"></script>
|
||||||
<script defer src="modules/ui/world-configurator.js?v=29052022"></script>
|
<script defer src="modules/ui/world-configurator.js?v=29052022"></script>
|
||||||
<script defer src="modules/ui/heightmap-editor.js?v=29052020"></script>
|
<script defer src="modules/ui/heightmap-editor.js?v=29052020"></script>
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ function getShapeOptions(selectShape, selected) {
|
||||||
return `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureEmblems hide">${options}</select>`;
|
return `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureEmblems hide">${options}</select>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cultureHighlightOn(event) {
|
const cultureHighlightOn = debounce(event => {
|
||||||
const cultureId = Number(event.id || event.target.dataset.id);
|
const cultureId = Number(event.id || event.target.dataset.id);
|
||||||
|
|
||||||
if (!layerIsOn("toggleCultures")) return;
|
if (!layerIsOn("toggleCultures")) return;
|
||||||
|
|
@ -312,7 +312,7 @@ function cultureHighlightOn(event) {
|
||||||
.transition(animate)
|
.transition(animate)
|
||||||
.attr("r", 8)
|
.attr("r", 8)
|
||||||
.attr("stroke", "#d0240f");
|
.attr("stroke", "#d0240f");
|
||||||
}
|
}, 200);
|
||||||
|
|
||||||
function cultureHighlightOff(event) {
|
function cultureHighlightOff(event) {
|
||||||
const cultureId = Number(event.id || event.target.dataset.id);
|
const cultureId = Number(event.id || event.target.dataset.id);
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ function getTypeOptions(type) {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
function religionHighlightOn(event) {
|
const religionHighlightOn = debounce(event => {
|
||||||
const religionId = Number(event.id || event.target.dataset.id);
|
const religionId = Number(event.id || event.target.dataset.id);
|
||||||
const $el = $body.querySelector(`div[data-id='${religionId}']`);
|
const $el = $body.querySelector(`div[data-id='${religionId}']`);
|
||||||
if ($el) $el.classList.add("active");
|
if ($el) $el.classList.add("active");
|
||||||
|
|
@ -280,7 +280,7 @@ function religionHighlightOn(event) {
|
||||||
.attr("r", 8)
|
.attr("r", 8)
|
||||||
.attr("stroke-width", 2)
|
.attr("stroke-width", 2)
|
||||||
.attr("stroke", "#c13119");
|
.attr("stroke", "#c13119");
|
||||||
}
|
}, 200);
|
||||||
|
|
||||||
function religionHighlightOff(event) {
|
function religionHighlightOff(event) {
|
||||||
const religionId = Number(event.id || event.target.dataset.id);
|
const religionId = Number(event.id || event.target.dataset.id);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ const MARGINS = {top: 10, right: 10, bottom: -5, left: 10};
|
||||||
const handleZoom = () => viewbox.attr("transform", d3.event.transform);
|
const handleZoom = () => viewbox.attr("transform", d3.event.transform);
|
||||||
const zoom = d3.zoom().scaleExtent([0.2, 1.5]).on("zoom", handleZoom);
|
const zoom = d3.zoom().scaleExtent([0.2, 1.5]).on("zoom", handleZoom);
|
||||||
|
|
||||||
|
// store old root for transitions
|
||||||
|
let oldRoot;
|
||||||
|
|
||||||
// define svg elements
|
// define svg elements
|
||||||
const svg = d3.select("#hierarchyTree > svg").call(zoom);
|
const svg = d3.select("#hierarchyTree > svg").call(zoom);
|
||||||
const viewbox = svg.select("g#hierarchyTree_viewbox");
|
const viewbox = svg.select("g#hierarchyTree_viewbox");
|
||||||
|
|
@ -159,10 +162,21 @@ function insertHtml() {
|
||||||
function addListeners() {}
|
function addListeners() {}
|
||||||
|
|
||||||
function getRoot() {
|
function getRoot() {
|
||||||
return d3
|
const root = d3
|
||||||
.stratify()
|
.stratify()
|
||||||
.id(d => d.i)
|
.id(d => d.i)
|
||||||
.parentId(d => d.origins[0])(validElements);
|
.parentId(d => d.origins[0])(validElements);
|
||||||
|
|
||||||
|
oldRoot = root;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLinkKey(d) {
|
||||||
|
return `${d.source.id}-${d.target.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeKey(d) {
|
||||||
|
return d.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLinkPath(d) {
|
function getLinkPath(d) {
|
||||||
|
|
@ -211,14 +225,13 @@ const getSortIndex = node => {
|
||||||
function renderTree(root, treeLayout) {
|
function renderTree(root, treeLayout) {
|
||||||
treeLayout(root.sort((a, b) => getSortIndex(a) - getSortIndex(b)));
|
treeLayout(root.sort((a, b) => getSortIndex(a) - getSortIndex(b)));
|
||||||
|
|
||||||
primaryLinks.selectAll("path").data(root.links()).enter().append("path").attr("d", getLinkPath);
|
primaryLinks.selectAll("path").data(root.links(), getLinkKey).join("path").attr("d", getLinkPath);
|
||||||
secondaryLinks.selectAll("path").data(getSecondaryLinks(root)).enter().append("path").attr("d", getLinkPath);
|
secondaryLinks.selectAll("path").data(getSecondaryLinks(root), getLinkKey).join("path").attr("d", getLinkPath);
|
||||||
|
|
||||||
const node = nodes
|
const node = nodes
|
||||||
.selectAll("g")
|
.selectAll("g")
|
||||||
.data(root.descendants())
|
.data(root.descendants(), getNodeKey)
|
||||||
.enter()
|
.join("g")
|
||||||
.append("g")
|
|
||||||
.attr("data-id", d => d.data.i)
|
.attr("data-id", d => d.data.i)
|
||||||
.attr("stroke", "#333")
|
.attr("stroke", "#333")
|
||||||
.attr("transform", d => `translate(${d.x}, ${d.y})`)
|
.attr("transform", d => `translate(${d.x}, ${d.y})`)
|
||||||
|
|
@ -236,27 +249,86 @@ function renderTree(root, treeLayout) {
|
||||||
node.append("text").text(d => d.data.code || "");
|
node.append("text").text(d => d.data.code || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function rerenderTree() {
|
function mapCoords(newRoot, prevRoot) {
|
||||||
nodes.selectAll("*").remove();
|
newRoot.x = prevRoot.x;
|
||||||
primaryLinks.selectAll("*").remove();
|
newRoot.y = prevRoot.y;
|
||||||
secondaryLinks.selectAll("*").remove();
|
|
||||||
|
|
||||||
|
for (const node of newRoot.descendants()) {
|
||||||
|
const prevNode = prevRoot.descendants().find(n => n.data.i === node.data.i);
|
||||||
|
if (prevNode) {
|
||||||
|
node.x = prevNode.x;
|
||||||
|
node.y = prevNode.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTree() {
|
||||||
|
const prevRoot = oldRoot;
|
||||||
const root = getRoot();
|
const root = getRoot();
|
||||||
|
mapCoords(root, prevRoot);
|
||||||
|
|
||||||
|
const linksUpdateDuration = 50;
|
||||||
|
const moveDuration = 1000;
|
||||||
|
|
||||||
|
// old layout: update links at old nodes positions
|
||||||
|
const linkEnter = enter =>
|
||||||
|
enter
|
||||||
|
.append("path")
|
||||||
|
.attr("d", getLinkPath)
|
||||||
|
.attr("opacity", 0)
|
||||||
|
.call(enter => enter.transition().duration(linksUpdateDuration).attr("opacity", 1));
|
||||||
|
|
||||||
|
const linkUpdate = update =>
|
||||||
|
update.call(update => update.transition().duration(linksUpdateDuration).attr("d", getLinkPath));
|
||||||
|
|
||||||
|
const linkExit = exit =>
|
||||||
|
exit.call(exit => exit.transition().duration(linksUpdateDuration).attr("opacity", 0).remove());
|
||||||
|
|
||||||
|
primaryLinks.selectAll("path").data(root.links(), getLinkKey).join(linkEnter, linkUpdate, linkExit);
|
||||||
|
secondaryLinks.selectAll("path").data(getSecondaryLinks(root), getLinkKey).join(linkEnter, linkUpdate, linkExit);
|
||||||
|
|
||||||
|
// new layout: move nodes with links to new positions
|
||||||
const treeWidth = root.leaves().length * 50;
|
const treeWidth = root.leaves().length * 50;
|
||||||
const treeHeight = root.height * 50;
|
const treeHeight = root.height * 50;
|
||||||
|
|
||||||
const w = treeWidth - MARGINS.left - MARGINS.right;
|
const w = treeWidth - MARGINS.left - MARGINS.right;
|
||||||
const h = treeHeight + 30 - MARGINS.top - MARGINS.bottom;
|
const h = treeHeight + 30 - MARGINS.top - MARGINS.bottom;
|
||||||
const treeLayout = d3.tree().size([w, h]);
|
|
||||||
|
|
||||||
renderTree(root, treeLayout);
|
const treeLayout = d3.tree().size([w, h]);
|
||||||
|
treeLayout(root.sort((a, b) => getSortIndex(a) - getSortIndex(b)));
|
||||||
|
|
||||||
|
primaryLinks
|
||||||
|
.selectAll("path")
|
||||||
|
.data(root.links(), getLinkKey)
|
||||||
|
.transition()
|
||||||
|
.duration(moveDuration)
|
||||||
|
.delay(linksUpdateDuration)
|
||||||
|
.attr("d", getLinkPath);
|
||||||
|
|
||||||
|
secondaryLinks
|
||||||
|
.selectAll("path")
|
||||||
|
.data(getSecondaryLinks(root), getLinkKey)
|
||||||
|
.transition()
|
||||||
|
.duration(moveDuration)
|
||||||
|
.delay(linksUpdateDuration)
|
||||||
|
.attr("d", getLinkPath);
|
||||||
|
|
||||||
|
nodes
|
||||||
|
.selectAll("g")
|
||||||
|
.data(root.descendants(), getNodeKey)
|
||||||
|
.transition()
|
||||||
|
.delay(linksUpdateDuration)
|
||||||
|
.duration(moveDuration)
|
||||||
|
.attr("transform", d => `translate(${d.x},${d.y})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectElement(d) {
|
function selectElement(d) {
|
||||||
const dataElement = d.data;
|
const dataElement = d.data;
|
||||||
|
|
||||||
|
const node = nodes.select(`g[data-id="${d.id}"]`);
|
||||||
nodes.selectAll("g").style("outline", "none");
|
nodes.selectAll("g").style("outline", "none");
|
||||||
this.style.outline = "1px solid #c13119";
|
node.style("outline", "1px solid #c13119");
|
||||||
|
|
||||||
byId("hierarchyTree_selected").style.display = "block";
|
byId("hierarchyTree_selected").style.display = "block";
|
||||||
byId("hierarchyTree_infoLine").style.display = "none";
|
byId("hierarchyTree_infoLine").style.display = "none";
|
||||||
|
|
||||||
|
|
@ -266,7 +338,8 @@ function selectElement(d) {
|
||||||
byId("hierarchyTree_selectedCode").onchange = function () {
|
byId("hierarchyTree_selectedCode").onchange = function () {
|
||||||
if (this.value.length > 3) return tip("Abbreviation must be 3 characters or less", false, "error", 3000);
|
if (this.value.length > 3) return tip("Abbreviation must be 3 characters or less", false, "error", 3000);
|
||||||
if (!this.value.length) return tip("Abbreviation cannot be empty", false, "error", 3000);
|
if (!this.value.length) return tip("Abbreviation cannot be empty", false, "error", 3000);
|
||||||
nodes.select(`g[data-id="${d.id}"] > text`).text(this.value);
|
|
||||||
|
node.select("text").text(this.value);
|
||||||
dataElement.code = this.value;
|
dataElement.code = this.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -288,7 +361,7 @@ function selectElement(d) {
|
||||||
const filtered = dataElement.origins.filter(elementOrigin => elementOrigin !== origin);
|
const filtered = dataElement.origins.filter(elementOrigin => elementOrigin !== origin);
|
||||||
dataElement.origins = filtered.length ? filtered : [0];
|
dataElement.origins = filtered.length ? filtered : [0];
|
||||||
target.remove();
|
target.remove();
|
||||||
rerenderTree();
|
updateTree();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -324,6 +397,7 @@ function selectElement(d) {
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
byId("hierarchyTree_originSelector").innerHTML = /*html*/ `
|
byId("hierarchyTree_originSelector").innerHTML = /*html*/ `
|
||||||
<form style="max-height: 35vh">
|
<form style="max-height: 35vh">
|
||||||
${selectableElementsHtml.join("")}
|
${selectableElementsHtml.join("")}
|
||||||
|
|
@ -347,7 +421,7 @@ function selectElement(d) {
|
||||||
|
|
||||||
dataElement.origins = [primary, ...secondary];
|
dataElement.origins = [primary, ...secondary];
|
||||||
|
|
||||||
rerenderTree();
|
updateTree();
|
||||||
createOriginButtons();
|
createOriginButtons();
|
||||||
},
|
},
|
||||||
Cancel: () => {
|
Cancel: () => {
|
||||||
|
|
@ -365,6 +439,8 @@ function selectElement(d) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNoteEnter(d) {
|
function handleNoteEnter(d) {
|
||||||
|
if (d.depth === 0) return;
|
||||||
|
|
||||||
this.classList.add("selected");
|
this.classList.add("selected");
|
||||||
onNodeEnter(d);
|
onNodeEnter(d);
|
||||||
|
|
||||||
|
|
@ -404,6 +480,7 @@ function dragToReorigin(from) {
|
||||||
if (element.origins[0] === 0) element.origins = [];
|
if (element.origins[0] === 0) element.origins = [];
|
||||||
element.origins.push(newOrigin);
|
element.origins.push(newOrigin);
|
||||||
|
|
||||||
rerenderTree();
|
selectElement(from);
|
||||||
|
updateTree();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,7 @@ window.Religions = (function () {
|
||||||
const folk = isFolkBased && religions.find(r => r.culture === culture && r.type === "Folk");
|
const folk = isFolkBased && religions.find(r => r.culture === culture && r.type === "Folk");
|
||||||
if (folk && expansion === "culture" && folk.name.slice(0, 3) !== "Old") folk.name = "Old " + folk.name;
|
if (folk && expansion === "culture" && folk.name.slice(0, 3) !== "Old") folk.name = "Old " + folk.name;
|
||||||
|
|
||||||
const origins = folk ? [folk.i] : getReligionsInRadius({x, y, r: 30, max: 2});
|
const origins = folk ? [folk.i] : getReligionsInRadius({x, y, r: 150 / count, max: 2});
|
||||||
const expansionism = rand(3, 8);
|
const expansionism = rand(3, 8);
|
||||||
const baseColor = religions[culture]?.color || states[state]?.color || getRandomColor();
|
const baseColor = religions[culture]?.color || states[state]?.color || getRandomColor();
|
||||||
const color = getMixedColor(baseColor, 0.3, 0);
|
const color = getMixedColor(baseColor, 0.3, 0);
|
||||||
|
|
@ -451,7 +451,7 @@ window.Religions = (function () {
|
||||||
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
||||||
|
|
||||||
const culture = cells.culture[center];
|
const culture = cells.culture[center];
|
||||||
const origins = getReligionsInRadius({x, y, r: 75, max: rand(0, 4)});
|
const origins = getReligionsInRadius({x, y, r: 300 / count, max: rand(0, 4)});
|
||||||
|
|
||||||
const deity = getDeityName(culture);
|
const deity = getDeityName(culture);
|
||||||
const name = getCultName(form, center);
|
const name = getCultName(form, center);
|
||||||
|
|
|
||||||
|
|
@ -1174,18 +1174,18 @@ function refreshAllEditors() {
|
||||||
// dynamically loaded editors
|
// dynamically loaded editors
|
||||||
async function editStates() {
|
async function editStates() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
const Editor = await import("../dynamic/editors/states-editor.js?v=08062022");
|
const Editor = await import("../dynamic/editors/states-editor.js?v=12062022");
|
||||||
Editor.open();
|
Editor.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function editCultures() {
|
async function editCultures() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
const Editor = await import("../dynamic/editors/cultures-editor.js?v=08062022");
|
const Editor = await import("../dynamic/editors/cultures-editor.js?v=12062022");
|
||||||
Editor.open();
|
Editor.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function editReligions() {
|
async function editReligions() {
|
||||||
if (customization) return;
|
if (customization) return;
|
||||||
const Editor = await import("../dynamic/editors/religions-editor.js?v=080620222");
|
const Editor = await import("../dynamic/editors/religions-editor.js?v=12062022");
|
||||||
Editor.open();
|
Editor.open();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
// version and caching control
|
// version and caching control
|
||||||
|
|
||||||
const version = "1.86.05"; // generator version, update each time
|
const version = "1.86.06"; // generator version, update each time
|
||||||
|
|
||||||
{
|
{
|
||||||
document.title += " v" + version;
|
document.title += " v" + version;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue