mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
feature(hierarchy tree): dynamically load, control dialog
This commit is contained in:
parent
b9a578e0be
commit
6e5e570c90
8 changed files with 639 additions and 573 deletions
|
|
@ -254,13 +254,6 @@ i.icon-lock {
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
#hierarchy .selected {
|
||||
stroke: #c13119;
|
||||
stroke-width: 1;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#hierarchy text,
|
||||
#statesTree text,
|
||||
#provincesTree text {
|
||||
pointer-events: none;
|
||||
|
|
@ -281,7 +274,7 @@ i.icon-lock {
|
|||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.dragLine {
|
||||
.regimentDragLine {
|
||||
marker-end: url(#end-arrow);
|
||||
stroke: #333333;
|
||||
stroke-dasharray: 5;
|
||||
|
|
|
|||
|
|
@ -295,15 +295,6 @@ function getShapeOptions(selectShape, selected) {
|
|||
|
||||
function cultureHighlightOn(event) {
|
||||
const cultureId = Number(event.id || event.target.dataset.id);
|
||||
const $info = byId("cultureInfo");
|
||||
if ($info) {
|
||||
d3.select("#hierarchy").select(`g[data-id='${cultureId}']`).classed("selected", 1);
|
||||
const {name, type, rural, urban} = pack.cultures[cultureId];
|
||||
const population = rural * populationRate + urban * populationRate * urbanization;
|
||||
const populationText = population > 0 ? si(rn(population)) + " people" : "Extinct";
|
||||
$info.innerHTML = `${name} culture. ${type}. ${populationText}`;
|
||||
tip("Drag to other node to add parent, click to edit");
|
||||
}
|
||||
|
||||
if (!layerIsOn("toggleCultures")) return;
|
||||
if (customization) return;
|
||||
|
|
@ -326,13 +317,6 @@ function cultureHighlightOn(event) {
|
|||
function cultureHighlightOff(event) {
|
||||
const cultureId = Number(event.id || event.target.dataset.id);
|
||||
|
||||
const $info = byId("cultureInfo");
|
||||
if ($info) {
|
||||
d3.select("#hierarchy").select(`g[data-id='${cultureId}']`).classed("selected", 0);
|
||||
$info.innerHTML = "‍";
|
||||
tip("");
|
||||
}
|
||||
|
||||
if (!layerIsOn("toggleCultures")) return;
|
||||
cults
|
||||
.select("#culture" + cultureId)
|
||||
|
|
@ -644,189 +628,36 @@ function togglePercentageMode() {
|
|||
}
|
||||
}
|
||||
|
||||
function showHierarchy() {
|
||||
// build hierarchy tree
|
||||
pack.cultures[0].origins = [null];
|
||||
const validCultures = pack.cultures.filter(c => !c.removed);
|
||||
if (validCultures.length < 3) return tip("Not enough cultures to show hierarchy", false, "error");
|
||||
async function showHierarchy() {
|
||||
if (customization) return;
|
||||
const HeirarchyTree = await import("../hierarchy-tree.js");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.id(d => d.i)
|
||||
.parentId(d => d.origins[0])(validCultures);
|
||||
const treeWidth = root.leaves().length;
|
||||
const treeHeight = root.height;
|
||||
const width = Math.max(treeWidth * 40, 300);
|
||||
const height = treeHeight * 60;
|
||||
const getDescription = culture => {
|
||||
const {name, type, rural, urban} = culture;
|
||||
|
||||
const margin = {top: 10, right: 10, bottom: -5, left: 10};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height + 30 - margin.top - margin.bottom;
|
||||
const treeLayout = d3.tree().size([w, h]);
|
||||
|
||||
alertMessage.innerHTML = /* html */ `<div id="cultureChartDetails" class='chartInfo'>
|
||||
<div id='cultureInfo' style="display: block">‍</div>
|
||||
<div id='cultureSelected' style="display: none">
|
||||
<span><span id='cultureSelectedName'></span> culture. </span>
|
||||
<span data-name="Type culture short name (abbreviation)">Abbreviation: <input id='cultureSelectedCode' type='text' maxlength='3' size='3' /></span>
|
||||
<button data-tip='Clear origin, culture will be linked to top level' id='cultureSelectedClear'>Clear</button>
|
||||
<button data-tip='Close edit mode' id='cultureSelectedClose'>Close</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// prepare svg
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#cultureChartDetails")
|
||||
.attr("id", "hierarchy")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.style("text-anchor", "middle")
|
||||
.style("min-width", "300px");
|
||||
const graph = svg.append("g").attr("transform", `translate(10, -45)`);
|
||||
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
|
||||
const primaryLinks = links.append("g");
|
||||
const secondaryLinks = links.append("g").attr("stroke-dasharray", 1);
|
||||
const nodes = graph.append("g");
|
||||
|
||||
// render helper functions
|
||||
const getLinkPath = d => {
|
||||
const {
|
||||
source: {x: sx, y: sy},
|
||||
target: {x: tx, y: ty}
|
||||
} = d;
|
||||
return `M${sx},${sy} C${sx},${(sy * 3 + ty) / 4} ${tx},${(sy * 2 + ty) / 3} ${tx},${ty}`;
|
||||
const population = rural * populationRate + urban * populationRate * urbanization;
|
||||
const populationText = population > 0 ? si(rn(population)) + " people" : "Extinct";
|
||||
return `${name} culture. ${type}. ${populationText}`;
|
||||
};
|
||||
|
||||
const getSecondaryLinks = root => {
|
||||
const nodes = root.descendants();
|
||||
const links = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
const origins = node.data.origins;
|
||||
if (node.depth < 2) continue;
|
||||
|
||||
for (let i = 1; i < origins.length; i++) {
|
||||
const source = nodes.find(n => n.data.i === origins[i]);
|
||||
if (source) links.push({source, target: node});
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
const getShape = ({type}) => {
|
||||
if (type === "Generic") return "circle";
|
||||
if (type === "River") return "diamond";
|
||||
if (type === "Lake") return "hexagon";
|
||||
if (type === "Naval") return "square";
|
||||
if (type === "Highland") return "concave";
|
||||
if (type === "Nomadic") return "octagon";
|
||||
if (type === "Hunting") return "pentagon";
|
||||
};
|
||||
|
||||
const nodePathMap = {
|
||||
undefined: "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0", // small circle
|
||||
Generic: "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0", // circle
|
||||
River: "M0,-14L14,0L0,14L-14,0Z", // diamond
|
||||
Lake: "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z", // hexagon
|
||||
Naval: "M-11,-11h22v22h-22Z", // square
|
||||
Highland: "M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z", // concave square
|
||||
Nomadic: "M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z", // octagon
|
||||
Hunting: "M0,-14l14,11l-6,14h-16l-6,-14Z" // pentagon
|
||||
};
|
||||
|
||||
const getNodePath = d => nodePathMap[d.data.type];
|
||||
|
||||
renderTree();
|
||||
function renderTree() {
|
||||
treeLayout(root);
|
||||
|
||||
primaryLinks.selectAll("path").data(root.links()).enter().append("path").attr("d", getLinkPath);
|
||||
secondaryLinks.selectAll("path").data(getSecondaryLinks(root)).enter().append("path").attr("d", getLinkPath);
|
||||
|
||||
const node = nodes
|
||||
.selectAll("g")
|
||||
.data(root.descendants())
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("data-id", d => d.data.i)
|
||||
.attr("stroke", "#333333")
|
||||
.attr("transform", d => `translate(${d.x}, ${d.y})`)
|
||||
.on("mouseenter", cultureHighlightOn)
|
||||
.on("mouseleave", cultureHighlightOff)
|
||||
.on("click", cultureSelect)
|
||||
.call(d3.drag().on("start", dragToReorigin));
|
||||
|
||||
node
|
||||
.append("path")
|
||||
.attr("d", getNodePath)
|
||||
.attr("fill", d => d.data.color || "#ffffff")
|
||||
.attr("stroke-dasharray", d => (d.data.cells ? "null" : "1"));
|
||||
|
||||
node
|
||||
.append("text")
|
||||
.attr("dy", ".35em")
|
||||
.text(d => d.data.code || "");
|
||||
}
|
||||
|
||||
$("#alert").dialog({
|
||||
title: "Cultures tree",
|
||||
width: fitContent(),
|
||||
resizable: false,
|
||||
position: {my: "left center", at: "left+10 center", of: "svg"},
|
||||
buttons: null,
|
||||
close: () => {
|
||||
alertMessage.innerHTML = "";
|
||||
}
|
||||
HeirarchyTree.open({
|
||||
type: "cultures",
|
||||
data: pack.cultures,
|
||||
onNodeEnter: cultureHighlightOn,
|
||||
onNodeLeave: cultureHighlightOff,
|
||||
getDescription,
|
||||
getShape
|
||||
});
|
||||
|
||||
function cultureSelect(d) {
|
||||
d3.event.stopPropagation();
|
||||
|
||||
nodes.selectAll("g").style("outline", "none");
|
||||
this.style.outline = "1px solid #c13119";
|
||||
byId("cultureSelected").style.display = "block";
|
||||
byId("cultureInfo").style.display = "none";
|
||||
|
||||
const culture = d.data;
|
||||
byId("cultureSelectedName").innerText = culture.name;
|
||||
byId("cultureSelectedCode").value = culture.code;
|
||||
|
||||
byId("cultureSelectedCode").onchange = function () {
|
||||
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);
|
||||
nodes.select(`g[data-id="${d.id}"] > text`).text(this.value);
|
||||
culture.code = this.value;
|
||||
};
|
||||
|
||||
byId("cultureSelectedClear").onclick = () => {
|
||||
culture.origins = [0];
|
||||
showHierarchy();
|
||||
};
|
||||
|
||||
byId("cultureSelectedClose").onclick = () => {
|
||||
this.style.outline = "none";
|
||||
byId("cultureSelected").style.display = "none";
|
||||
byId("cultureInfo").style.display = "block";
|
||||
};
|
||||
}
|
||||
|
||||
function dragToReorigin(d) {
|
||||
const originLine = graph.append("path").attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`);
|
||||
|
||||
d3.event.on("drag", () => {
|
||||
originLine.attr("d", `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`);
|
||||
});
|
||||
|
||||
d3.event.on("end", () => {
|
||||
originLine.remove();
|
||||
const selected = graph.select("g.selected");
|
||||
if (!selected.size()) return;
|
||||
|
||||
const cultureId = d.data.i;
|
||||
const newOrigin = selected.datum().data.i;
|
||||
if (cultureId === newOrigin) return; // dragged to itself
|
||||
if (d.data.origins.includes(newOrigin)) return; // already a child of the selected node
|
||||
if (d.descendants().some(node => node.data.i === newOrigin)) return; // cannot be a child of its own child
|
||||
|
||||
const culture = pack.cultures[cultureId];
|
||||
if (culture.origins[0] === 0) culture.origins = [];
|
||||
culture.origins.push(newOrigin);
|
||||
|
||||
showHierarchy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function recalculateCultures(must) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export function open() {
|
|||
if (layerIsOn("toggleCultures")) toggleReligions();
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
|
||||
appendStyleSheet();
|
||||
refreshReligionsEditor();
|
||||
drawReligionCenters();
|
||||
|
||||
|
|
@ -22,38 +21,6 @@ export function open() {
|
|||
$body.focus();
|
||||
}
|
||||
|
||||
function appendStyleSheet() {
|
||||
const styles = /* css */ `
|
||||
#religionSelectedOrigins > button {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.religionSelectedButton {
|
||||
border: 1px solid #aaa;
|
||||
background: none;
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
.religionSelectedButton:hover {
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.religionSelectedOrigin::after {
|
||||
content: "✕";
|
||||
margin-left: 8px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.religionSelectedOrigin:hover:after {
|
||||
color: #333;
|
||||
}
|
||||
`;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.appendChild(document.createTextNode(styles));
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function insertEditorHtml() {
|
||||
const editorHtml = /* html */ `<div id="religionsEditor" class="dialog stable">
|
||||
<div id="religionsHeader" class="header" style="grid-template-columns: 13em 6em 7em 18em 5em 6em">
|
||||
|
|
@ -293,25 +260,6 @@ function getTypeOptions(type) {
|
|||
|
||||
function religionHighlightOn(event) {
|
||||
const religionId = Number(event.id || event.target.dataset.id);
|
||||
const $info = byId("religionInfo");
|
||||
if ($info && religionId) {
|
||||
d3.select("#hierarchy").select(`g[data-id='${religionId}']`).classed("selected", 1);
|
||||
const {name, type, form, rural, urban} = pack.religions[religionId];
|
||||
|
||||
const getTypeText = () => {
|
||||
if (name.includes(type)) return "";
|
||||
if (form.includes(type)) return "";
|
||||
if (type === "Folk" || type === "Organized") return `. ${type} religion`;
|
||||
return `. ${type}`;
|
||||
};
|
||||
const formText = form === type ? "" : ". " + form;
|
||||
const population = rural * populationRate + urban * populationRate * urbanization;
|
||||
const populationText = population > 0 ? si(rn(population)) + " people" : "Extinct";
|
||||
|
||||
$info.innerHTML = `${name}${getTypeText()}${formText}. ${populationText}`;
|
||||
tip("Drag to other node to add parent, click to edit");
|
||||
}
|
||||
|
||||
const $el = $body.querySelector(`div[data-id='${religionId}']`);
|
||||
if ($el) $el.classList.add("active");
|
||||
|
||||
|
|
@ -336,13 +284,6 @@ function religionHighlightOn(event) {
|
|||
|
||||
function religionHighlightOff(event) {
|
||||
const religionId = Number(event.id || event.target.dataset.id);
|
||||
const $info = byId("religionInfo");
|
||||
if ($info) {
|
||||
d3.select("#hierarchy").select(`g[data-id='${religionId}']`).classed("selected", 0);
|
||||
$info.innerHTML = "‍";
|
||||
tip("");
|
||||
}
|
||||
|
||||
const $el = $body.querySelector(`div[data-id='${religionId}']`);
|
||||
if ($el) $el.classList.remove("active");
|
||||
|
||||
|
|
@ -590,240 +531,42 @@ function togglePercentageMode() {
|
|||
}
|
||||
}
|
||||
|
||||
function showHierarchy() {
|
||||
// build hierarchy tree
|
||||
pack.religions[0].origins = [null];
|
||||
const validReligions = pack.religions.filter(r => !r.removed);
|
||||
if (validReligions.length < 3) return tip("Not enough religions to show hierarchy", false, "error");
|
||||
async function showHierarchy() {
|
||||
if (customization) return;
|
||||
const HeirarchyTree = await import("../hierarchy-tree.js");
|
||||
|
||||
const getRoot = () =>
|
||||
d3
|
||||
.stratify()
|
||||
.id(d => d.i)
|
||||
.parentId(d => d.origins[0])(validReligions);
|
||||
const getDescription = religion => {
|
||||
const {name, type, form, rural, urban} = religion;
|
||||
|
||||
const root = getRoot();
|
||||
const treeWidth = root.leaves().length * 50;
|
||||
const treeHeight = root.height * 50;
|
||||
const getTypeText = () => {
|
||||
if (name.includes(type)) return "";
|
||||
if (form.includes(type)) return "";
|
||||
if (type === "Folk" || type === "Organized") return `. ${type} religion`;
|
||||
return `. ${type}`;
|
||||
};
|
||||
|
||||
const margin = {top: 10, right: 10, bottom: -5, left: 10};
|
||||
const w = treeWidth - margin.left - margin.right;
|
||||
const h = treeHeight + 30 - margin.top - margin.bottom;
|
||||
const treeLayout = d3.tree().size([w, h]);
|
||||
const formText = form === type ? "" : ". " + form;
|
||||
const population = rural * populationRate + urban * populationRate * urbanization;
|
||||
const populationText = population > 0 ? si(rn(population)) + " people" : "Extinct";
|
||||
|
||||
const width = minmax(treeWidth, 300, innerWidth * 0.75);
|
||||
const height = minmax(treeHeight, 200, innerHeight * 0.75);
|
||||
|
||||
alertMessage.innerHTML = /* html */ `<div id="religionChart" style="overflow: hidden; width: ${width}px">
|
||||
<div id="religionChartDetails" class='chartInfo'>
|
||||
<div id='religionInfo' style="display: block">‍</div>
|
||||
<div id='religionSelected' style="display: none">
|
||||
<span><span id='religionSelectedName'></span>. </span>
|
||||
<span data-name="Type religion short name (abbreviation)">Abbreviation: <input id='religionSelectedCode' type='text' maxlength='3' size='3' /></span>
|
||||
<span>Origins: <span id='religionSelectedOrigins'></span></span>
|
||||
<button data-tip='Add origin' class="religionSelectedButton" id='religionSelectedAdd'>Add</button>
|
||||
<button data-tip='Exit edit mode' class="religionSelectedButton" id='religionSelectedClose'>Exit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// prepare svg
|
||||
const zoom = d3
|
||||
.zoom()
|
||||
.extent([Array(2).fill(0), [width, height]])
|
||||
.scaleExtent([0.2, 1.5])
|
||||
.on("zoom", () => {
|
||||
viewbox.attr("transform", d3.event.transform);
|
||||
});
|
||||
|
||||
const svg = d3
|
||||
.select("#religionChart")
|
||||
.insert("svg", "#religionChartDetails")
|
||||
.attr("id", "hierarchy")
|
||||
.attr("viewBox", [0, 0, width, height])
|
||||
.style("text-anchor", "middle")
|
||||
.call(zoom);
|
||||
|
||||
const viewbox = svg.append("g");
|
||||
const graph = viewbox.append("g").attr("transform", `translate(10, -45)`);
|
||||
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
|
||||
const primaryLinks = links.append("g");
|
||||
const secondaryLinks = links.append("g").attr("stroke-dasharray", 1);
|
||||
const nodes = graph.append("g");
|
||||
|
||||
// render helper functions
|
||||
const getLinkPath = d => {
|
||||
const {
|
||||
source: {x: sx, y: sy},
|
||||
target: {x: tx, y: ty}
|
||||
} = d;
|
||||
return `M${sx},${sy} C${sx},${(sy * 3 + ty) / 4} ${tx},${(sy * 2 + ty) / 3} ${tx},${ty}`;
|
||||
return `${name}${getTypeText()}${formText}. ${populationText}`;
|
||||
};
|
||||
|
||||
const getSecondaryLinks = root => {
|
||||
const nodes = root.descendants();
|
||||
const links = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
const origins = node.data.origins;
|
||||
if (node.depth < 2) continue;
|
||||
|
||||
for (let i = 1; i < origins.length; i++) {
|
||||
const source = nodes.find(n => n.data.i === origins[i]);
|
||||
if (source) links.push({source, target: node});
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
const getShape = ({type}) => {
|
||||
if (type === "Folk") return "circle";
|
||||
if (type === "Organized") return "square";
|
||||
if (type === "Cult") return "hexagon";
|
||||
if (type === "Heresy") return "diamond";
|
||||
};
|
||||
|
||||
const nodePathMap = {
|
||||
undefined: "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0", // small circle
|
||||
Folk: "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0", // circle
|
||||
Organized: "M-11,-11h22v22h-22Z", // square
|
||||
Cult: "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z", // hexagon
|
||||
Heresy: "M0,-14L14,0L0,14L-14,0Z" // diamond
|
||||
};
|
||||
|
||||
const getNodePath = d => nodePathMap[d.data.type];
|
||||
|
||||
renderTree(root, treeLayout);
|
||||
|
||||
function renderTree(root, treeLayout) {
|
||||
treeLayout(root);
|
||||
|
||||
primaryLinks.selectAll("path").data(root.links()).enter().append("path").attr("d", getLinkPath);
|
||||
secondaryLinks.selectAll("path").data(getSecondaryLinks(root)).enter().append("path").attr("d", getLinkPath);
|
||||
|
||||
const node = nodes
|
||||
.selectAll("g")
|
||||
.data(root.descendants())
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("data-id", d => d.data.i)
|
||||
.attr("stroke", "#333333")
|
||||
.attr("transform", d => `translate(${d.x}, ${d.y})`)
|
||||
.on("mouseenter", religionHighlightOn)
|
||||
.on("mouseleave", religionHighlightOff)
|
||||
.on("click", religionSelect)
|
||||
.call(d3.drag().on("start", dragToReorigin));
|
||||
|
||||
node
|
||||
.append("path")
|
||||
.attr("d", getNodePath)
|
||||
.attr("fill", d => d.data.color || "#ffffff")
|
||||
.attr("stroke-dasharray", d => (d.data.cells ? "null" : "1"));
|
||||
|
||||
node
|
||||
.append("text")
|
||||
.attr("dy", ".35em")
|
||||
.text(d => d.data.code || "");
|
||||
}
|
||||
|
||||
function rerenderTree() {
|
||||
nodes.selectAll("*").remove();
|
||||
primaryLinks.selectAll("*").remove();
|
||||
secondaryLinks.selectAll("*").remove();
|
||||
|
||||
const root = getRoot();
|
||||
const treeWidth = root.leaves().length * 50;
|
||||
const treeHeight = root.height * 50;
|
||||
|
||||
const w = treeWidth - margin.left - margin.right;
|
||||
const h = treeHeight + 30 - margin.top - margin.bottom;
|
||||
const treeLayout = d3.tree().size([w, h]);
|
||||
|
||||
renderTree(root, treeLayout);
|
||||
}
|
||||
|
||||
$("#alert").dialog({
|
||||
title: "Religions tree",
|
||||
position: {my: "left center", at: "left+10 center", of: "svg"},
|
||||
buttons: {},
|
||||
close: () => {
|
||||
alertMessage.innerHTML = "";
|
||||
}
|
||||
HeirarchyTree.open({
|
||||
type: "religions",
|
||||
data: pack.religions,
|
||||
onNodeEnter: religionHighlightOn,
|
||||
onNodeLeave: religionHighlightOff,
|
||||
getDescription,
|
||||
getShape
|
||||
});
|
||||
|
||||
function religionSelect(d) {
|
||||
d3.event.stopPropagation();
|
||||
const religion = d.data;
|
||||
|
||||
nodes.selectAll("g").style("outline", "none");
|
||||
this.style.outline = "1px solid #c13119";
|
||||
byId("religionSelected").style.display = "block";
|
||||
byId("religionInfo").style.display = "none";
|
||||
|
||||
byId("religionSelectedName").innerText = religion.name;
|
||||
byId("religionSelectedCode").value = religion.code;
|
||||
|
||||
byId("religionSelectedCode").onchange = function () {
|
||||
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);
|
||||
nodes.select(`g[data-id="${d.id}"] > text`).text(this.value);
|
||||
religion.code = this.value;
|
||||
};
|
||||
|
||||
byId("religionSelectedOrigins").innerHTML = religion.origins
|
||||
.filter(origin => origin)
|
||||
.map((origin, index) => {
|
||||
const {name, code} = validReligions.find(r => r.i === origin) || {};
|
||||
const type = index ? "Secondary" : "Primary";
|
||||
const tip = `${type} origin: ${name}. Click to remove link to that origin`;
|
||||
return `<button data-id="${origin}" class="religionSelectedButton religionSelectedOrigin" data-tip="${tip}">${code}</button>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
byId("religionSelectedOrigins").onclick = function (event) {
|
||||
const target = event.target;
|
||||
if (target.tagName !== "BUTTON") return;
|
||||
const origin = Number(target.dataset.id);
|
||||
const filtered = religion.origins.filter(religionOrigin => religionOrigin !== origin);
|
||||
religion.origins = filtered.length ? filtered : [0];
|
||||
target.remove();
|
||||
rerenderTree();
|
||||
};
|
||||
|
||||
byId("religionSelectedAdd").onclick = () => {
|
||||
const ancestors = d.ancestors().map(({id}) => Number(id));
|
||||
const origins = religion.origins;
|
||||
|
||||
const selectableReligions = validReligions.filter(({i}) => i && !origins.includes(i) && !ancestors.includes(i));
|
||||
console.log(selectableReligions);
|
||||
};
|
||||
|
||||
byId("religionSelectedClose").onclick = () => {
|
||||
this.style.outline = "none";
|
||||
byId("religionSelected").style.display = "none";
|
||||
byId("religionInfo").style.display = "block";
|
||||
};
|
||||
}
|
||||
|
||||
function dragToReorigin(d) {
|
||||
const originLine = graph.append("path").attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`);
|
||||
|
||||
d3.event.on("drag", () => {
|
||||
originLine.attr("d", `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`);
|
||||
});
|
||||
|
||||
d3.event.on("end", () => {
|
||||
originLine.remove();
|
||||
const selected = graph.select("g.selected");
|
||||
if (!selected.size()) return;
|
||||
|
||||
const religionId = d.data.i;
|
||||
const newOrigin = selected.datum().data.i;
|
||||
if (religionId === newOrigin) return; // dragged to itself
|
||||
if (d.data.origins.includes(newOrigin)) return; // already a child of the selected node
|
||||
if (d.descendants().some(node => node.data.i === newOrigin)) return; // cannot be a child of its own child
|
||||
|
||||
const religion = pack.religions[religionId];
|
||||
if (religion.origins[0] === 0) religion.origins = [];
|
||||
religion.origins.push(newOrigin);
|
||||
|
||||
rerenderTree();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleExtinct() {
|
||||
|
|
|
|||
|
|
@ -707,7 +707,6 @@ function togglePercentageMode() {
|
|||
}
|
||||
|
||||
function showStatesChart() {
|
||||
// build hierarchy tree
|
||||
const statesData = pack.states.filter(s => !s.removed);
|
||||
if (statesData.length < 2) return tip("There are no states to show", false, "error");
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ const initialSeed = generateSeed();
|
|||
let graph = getGraph(grid);
|
||||
|
||||
appendStyleSheet();
|
||||
insertEditorHtml();
|
||||
insertHtml();
|
||||
addListeners();
|
||||
|
||||
export function open() {
|
||||
|
|
@ -150,7 +150,7 @@ function appendStyleSheet() {
|
|||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function insertEditorHtml() {
|
||||
function insertHtml() {
|
||||
const heightmapSelectionHtml = /* html */ `<div id="heightmapSelection" class="dialog stable">
|
||||
<div class="heightmap-selection">
|
||||
<section data-tip="Select heightmap template – template provides unique, but similar-looking maps on generation">
|
||||
|
|
|
|||
401
modules/dynamic/hierarchy-tree.js
Normal file
401
modules/dynamic/hierarchy-tree.js
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
appendStyleSheet();
|
||||
insertHtml();
|
||||
addListeners();
|
||||
|
||||
const MARGINS = {top: 10, right: 10, bottom: -5, left: 10};
|
||||
|
||||
const handleZoom = () => viewbox.attr("transform", d3.event.transform);
|
||||
const zoom = d3.zoom().scaleExtent([0.2, 1.5]).on("zoom", handleZoom);
|
||||
|
||||
// define svg elements
|
||||
const svg = d3.select("#hierarchyTree > svg").call(zoom);
|
||||
const viewbox = svg.select("g#hierarchyTree_viewbox");
|
||||
const primaryLinks = viewbox.select("g#hierarchyTree_linksPrimary");
|
||||
const secondaryLinks = viewbox.select("g#hierarchyTree_linksSecondary");
|
||||
const nodes = viewbox.select("g#hierarchyTree_nodes");
|
||||
const dragLine = viewbox.select("path#hierarchyTree_dragLine");
|
||||
|
||||
// properties
|
||||
let dataElements; // {i, name, type, origins}[], e.g. path.religions
|
||||
let validElements; // not-removed dataElements
|
||||
let onNodeEnter; // d3Data => void
|
||||
let onNodeLeave; // d3Data => void
|
||||
let getDescription; // dataElement => string
|
||||
let getShape; // dataElement => string;
|
||||
|
||||
export function open(props) {
|
||||
closeDialogs(".stable");
|
||||
|
||||
dataElements = props.data;
|
||||
dataElements[0].origins = [null];
|
||||
validElements = dataElements.filter(r => !r.removed);
|
||||
if (validElements.length < 3) return tip(`Not enough ${props.type} to show hierarchy`, false, "error");
|
||||
|
||||
onNodeEnter = props.onNodeEnter;
|
||||
onNodeLeave = props.onNodeLeave;
|
||||
getDescription = props.getDescription;
|
||||
getShape = props.getShape;
|
||||
|
||||
const root = getRoot();
|
||||
const treeWidth = root.leaves().length * 50;
|
||||
const treeHeight = root.height * 50;
|
||||
|
||||
const w = treeWidth - MARGINS.left - MARGINS.right;
|
||||
const h = treeHeight + 30 - MARGINS.top - MARGINS.bottom;
|
||||
const treeLayout = d3.tree().size([w, h]);
|
||||
|
||||
const width = minmax(treeWidth, 300, innerWidth * 0.75);
|
||||
const height = minmax(treeHeight, 200, innerHeight * 0.75);
|
||||
|
||||
zoom.extent([Array(2).fill(0), [width, height]]);
|
||||
svg.attr("viewBox", `0, 0, ${width}, ${height}`);
|
||||
|
||||
$("#hierarchyTree").dialog({
|
||||
title: `${capitalize(props.type)} tree`,
|
||||
position: {my: "left center", at: "left+10 center", of: "svg"},
|
||||
width
|
||||
});
|
||||
|
||||
renderTree(root, treeLayout);
|
||||
}
|
||||
|
||||
function appendStyleSheet() {
|
||||
const styles = /* css */ `
|
||||
#hierarchyTree_selectedOrigins > button {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.hierarchyTree_selectedButton {
|
||||
border: 1px solid #aaa;
|
||||
background: none;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.hierarchyTree_selectedButton:hover {
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.hierarchyTree_selectedOrigin::after {
|
||||
content: "✕";
|
||||
margin-left: 8px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.hierarchyTree_selectedOrigin:hover:after {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#hierarchyTree_originSelector > form > div {
|
||||
padding: 0.3em;
|
||||
margin: 1px 0;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
#hierarchyTree_originSelector > form > div:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#hierarchyTree_originSelector > form > div[checked] {
|
||||
background-color: #c6d6d6;
|
||||
}
|
||||
|
||||
#hierarchyTree_nodes > g > text {
|
||||
pointer-events: none;
|
||||
stroke: none;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#hierarchyTree_nodes > g.selected {
|
||||
stroke: #c13119;
|
||||
stroke-width: 1;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#hierarchyTree_dragLine {
|
||||
marker-end: url(#end-arrow);
|
||||
stroke: #333333;
|
||||
stroke-dasharray: 5;
|
||||
stroke-dashoffset: 1000;
|
||||
animation: dash 80s linear backwards;
|
||||
}
|
||||
`;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.appendChild(document.createTextNode(styles));
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function insertHtml() {
|
||||
const html = /* html */ `<div id="hierarchyTree" style="overflow: hidden;">
|
||||
<svg>
|
||||
<g id="hierarchyTree_viewbox" style="text-anchor: middle; dominant-baseline: central">
|
||||
<g transform="translate(10, -45)">
|
||||
<g id="hierarchyTree_links" fill="none" stroke="#aaa">
|
||||
<g id="hierarchyTree_linksPrimary"></g>
|
||||
<g id="hierarchyTree_linksSecondary" stroke-dasharray="1"></g>
|
||||
</g>
|
||||
<g id="hierarchyTree_nodes"></g>
|
||||
<path id="hierarchyTree_dragLine" path='' />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div id="hierarchyTree_details" class='chartInfo'>
|
||||
<div id='hierarchyTree_infoLine' style="display: block">‍</div>
|
||||
<div id='hierarchyTree_selected' style="display: none">
|
||||
<span><span id='hierarchyTree_selectedName'></span>. </span>
|
||||
<span data-name="Type short name (abbreviation)">Abbreviation: <input id='hierarchyTree_selectedCode' type='text' maxlength='3' size='3' /></span>
|
||||
<span>Origins: <span id='hierarchyTree_selectedOrigins'></span></span>
|
||||
<button data-tip='Add origin' class="hierarchyTree_selectedButton" id='hierarchyTree_selectedSelectButton'>Select</button>
|
||||
<button data-tip='Exit edit mode' class="hierarchyTree_selectedButton" id='hierarchyTree_selectedCloseButton'>Exit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hierarchyTree_originSelector"></div>
|
||||
</div>`;
|
||||
|
||||
byId("dialogs").insertAdjacentHTML("beforeend", html);
|
||||
}
|
||||
|
||||
function addListeners() {}
|
||||
|
||||
function getRoot() {
|
||||
return d3
|
||||
.stratify()
|
||||
.id(d => d.i)
|
||||
.parentId(d => d.origins[0])(validElements);
|
||||
}
|
||||
|
||||
function getLinkPath(d) {
|
||||
const {
|
||||
source: {x: sx, y: sy},
|
||||
target: {x: tx, y: ty}
|
||||
} = d;
|
||||
return `M${sx},${sy} C${sx},${(sy * 3 + ty) / 4} ${tx},${(sy * 2 + ty) / 3} ${tx},${ty}`;
|
||||
}
|
||||
|
||||
function getSecondaryLinks(root) {
|
||||
const nodes = root.descendants();
|
||||
const links = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
const origins = node.data.origins;
|
||||
|
||||
for (let i = 1; i < origins.length; i++) {
|
||||
const source = nodes.find(n => n.data.i === origins[i]);
|
||||
if (source) links.push({source, target: node});
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
const shapesMap = {
|
||||
undefined: "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0", // small circle
|
||||
circle: "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0",
|
||||
square: "M-11,-11h22v22h-22Z",
|
||||
hexagon: "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z",
|
||||
diamond: "M0,-14L14,0L0,14L-14,0Z",
|
||||
concave: "M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z",
|
||||
octagon: "M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z",
|
||||
pentagon: "M0,-14l14,11l-6,14h-16l-6,-14Z"
|
||||
};
|
||||
|
||||
function renderTree(root, treeLayout) {
|
||||
treeLayout(root);
|
||||
|
||||
primaryLinks.selectAll("path").data(root.links()).enter().append("path").attr("d", getLinkPath);
|
||||
secondaryLinks.selectAll("path").data(getSecondaryLinks(root)).enter().append("path").attr("d", getLinkPath);
|
||||
|
||||
const node = nodes
|
||||
.selectAll("g")
|
||||
.data(root.descendants())
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("data-id", d => d.data.i)
|
||||
.attr("stroke", "#333")
|
||||
.attr("transform", d => `translate(${d.x}, ${d.y})`)
|
||||
.on("mouseenter", handleNoteEnter)
|
||||
.on("mouseleave", handleNodeExit)
|
||||
.on("click", selectElement)
|
||||
.call(d3.drag().on("start", dragToReorigin));
|
||||
|
||||
node
|
||||
.append("path")
|
||||
.attr("d", ({data}) => shapesMap[getShape(data)])
|
||||
.attr("fill", d => d.data.color || "#ffffff")
|
||||
.attr("stroke-dasharray", d => (d.data.cells ? "none" : "1"));
|
||||
|
||||
node.append("text").text(d => d.data.code || "");
|
||||
}
|
||||
|
||||
function rerenderTree() {
|
||||
nodes.selectAll("*").remove();
|
||||
primaryLinks.selectAll("*").remove();
|
||||
secondaryLinks.selectAll("*").remove();
|
||||
|
||||
const root = getRoot();
|
||||
const treeWidth = root.leaves().length * 50;
|
||||
const treeHeight = root.height * 50;
|
||||
|
||||
const w = treeWidth - MARGINS.left - MARGINS.right;
|
||||
const h = treeHeight + 30 - MARGINS.top - MARGINS.bottom;
|
||||
const treeLayout = d3.tree().size([w, h]);
|
||||
|
||||
renderTree(root, treeLayout);
|
||||
}
|
||||
|
||||
function selectElement(d) {
|
||||
const dataElement = d.data;
|
||||
|
||||
nodes.selectAll("g").style("outline", "none");
|
||||
this.style.outline = "1px solid #c13119";
|
||||
byId("hierarchyTree_selected").style.display = "block";
|
||||
byId("hierarchyTree_infoLine").style.display = "none";
|
||||
|
||||
byId("hierarchyTree_selectedName").innerText = dataElement.name;
|
||||
byId("hierarchyTree_selectedCode").value = dataElement.code;
|
||||
|
||||
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) return tip("Abbreviation cannot be empty", false, "error", 3000);
|
||||
nodes.select(`g[data-id="${d.id}"] > text`).text(this.value);
|
||||
dataElement.code = this.value;
|
||||
};
|
||||
|
||||
const createOriginButtons = () => {
|
||||
byId("hierarchyTree_selectedOrigins").innerHTML = dataElement.origins
|
||||
.filter(origin => origin)
|
||||
.map((origin, index) => {
|
||||
const {name, code} = validElements.find(r => r.i === origin) || {};
|
||||
const type = index ? "Secondary" : "Primary";
|
||||
const tip = `${type} origin: ${name}. Click to remove link to that origin`;
|
||||
return `<button data-id="${origin}" class="hierarchyTree_selectedButton hierarchyTree_selectedOrigin" data-tip="${tip}">${code}</button>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
byId("hierarchyTree_selectedOrigins").onclick = event => {
|
||||
const target = event.target;
|
||||
if (target.tagName !== "BUTTON") return;
|
||||
const origin = Number(target.dataset.id);
|
||||
const filtered = dataElement.origins.filter(elementOrigin => elementOrigin !== origin);
|
||||
dataElement.origins = filtered.length ? filtered : [0];
|
||||
target.remove();
|
||||
rerenderTree();
|
||||
};
|
||||
};
|
||||
|
||||
createOriginButtons();
|
||||
|
||||
byId("hierarchyTree_selectedSelectButton").onclick = () => {
|
||||
const origins = dataElement.origins;
|
||||
|
||||
const descendants = d.descendants().map(d => d.data.i);
|
||||
const selectableElements = validElements.filter(({i}) => !descendants.includes(i));
|
||||
|
||||
const selectableElementsHtml = selectableElements.map(({i, name, code, color}) => {
|
||||
const isPrimary = origins[0] === i ? "checked" : "";
|
||||
const isChecked = origins.includes(i) ? "checked" : "";
|
||||
|
||||
if (i === 0) {
|
||||
return /*html*/ `
|
||||
<div ${isChecked}>
|
||||
<input data-tip="Set as primary origin" type="radio" name="primary" value="${i}" ${isPrimary} />
|
||||
Top level
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return /*html*/ `
|
||||
<div ${isChecked}>
|
||||
<input data-tip="Set as primary origin" type="radio" name="primary" value="${i}" ${isPrimary} />
|
||||
<input data-id="${i}" id="selectElementOrigin${i}" class="checkbox" type="checkbox" ${isChecked} />
|
||||
<label data-tip="Check to set as a secondary origin" for="selectElementOrigin${i}" class="checkbox-label">
|
||||
<fill-box fill="${color}" size=".8em" disabled></fill-box>
|
||||
${code}: ${name}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
byId("hierarchyTree_originSelector").innerHTML = /*html*/ `
|
||||
<form style="max-height: 35vh">
|
||||
${selectableElementsHtml.join("")}
|
||||
</form>
|
||||
`;
|
||||
|
||||
$("#hierarchyTree_originSelector").dialog({
|
||||
title: "Select origins",
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Select: () => {
|
||||
$("#hierarchyTree_originSelector").dialog("close");
|
||||
const $selector = byId("hierarchyTree_originSelector");
|
||||
const selectedRadio = $selector.querySelector("input[type='radio']:checked");
|
||||
const selectedCheckboxes = $selector.querySelectorAll("input[type='checkbox']:checked");
|
||||
|
||||
const primary = selectedRadio ? Number(selectedRadio.value) : 0;
|
||||
const secondary = Array.from(selectedCheckboxes)
|
||||
.map(input => Number(input.dataset.id))
|
||||
.filter(origin => origin !== primary);
|
||||
|
||||
dataElement.origins = [primary, ...secondary];
|
||||
|
||||
rerenderTree();
|
||||
createOriginButtons();
|
||||
},
|
||||
Cancel: () => {
|
||||
$("#hierarchyTree_originSelector").dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
byId("hierarchyTree_selectedCloseButton").onclick = () => {
|
||||
this.style.outline = "none";
|
||||
byId("hierarchyTree_selected").style.display = "none";
|
||||
byId("hierarchyTree_infoLine").style.display = "block";
|
||||
};
|
||||
}
|
||||
|
||||
function handleNoteEnter(d) {
|
||||
this.classList.add("selected");
|
||||
onNodeEnter(d);
|
||||
|
||||
byId("hierarchyTree_infoLine").innerText = getDescription(d.data);
|
||||
tip("Drag to other node to add parent, click to edit");
|
||||
}
|
||||
|
||||
function handleNodeExit(d) {
|
||||
this.classList.remove("selected");
|
||||
onNodeLeave(d);
|
||||
|
||||
byId("hierarchyTree_infoLine").innerHTML = "‍";
|
||||
tip("");
|
||||
}
|
||||
|
||||
function dragToReorigin(from) {
|
||||
dragLine.attr("d", `M${from.x},${from.y}L${from.x},${from.y}`);
|
||||
|
||||
d3.event.on("drag", () => {
|
||||
dragLine.attr("d", `M${from.x},${from.y}L${d3.event.x},${d3.event.y}`);
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
dragLine.attr("d", "");
|
||||
const selected = nodes.select("g.selected");
|
||||
if (!selected.size()) return;
|
||||
|
||||
const elementId = from.data.i;
|
||||
const newOrigin = selected.datum().data.i;
|
||||
if (elementId === newOrigin) return; // dragged to itself
|
||||
if (from.data.origins.includes(newOrigin)) return; // already a child of the selected node
|
||||
if (from.descendants().some(node => node.data.i === newOrigin)) return; // cannot be a child of its own child
|
||||
|
||||
const element = dataElements.find(({i}) => i === elementId);
|
||||
if (!element) return;
|
||||
|
||||
if (element.origins[0] === 0) element.origins = [];
|
||||
element.origins.push(newOrigin);
|
||||
|
||||
rerenderTree();
|
||||
});
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ function clicked() {
|
|||
else if (grand.id === "coastline") editCoastline();
|
||||
else if (great.id === "armies") editRegiment();
|
||||
else if (pack.cells.t[i] === 1) {
|
||||
const node = document.getElementById("island_" + pack.cells.f[i]);
|
||||
const node = byId("island_" + pack.cells.f[i]);
|
||||
editCoastline(node);
|
||||
} else if (grand.id === "lakes") editLake();
|
||||
}
|
||||
|
|
@ -58,10 +58,10 @@ function closeDialogs(except = "#except") {
|
|||
|
||||
// move brush radius circle
|
||||
function moveCircle(x, y, r = 20) {
|
||||
let circle = document.getElementById("brushCircle");
|
||||
let circle = byId("brushCircle");
|
||||
if (!circle) {
|
||||
const html = /* html */ `<circle id="brushCircle" cx=${x} cy=${y} r=${r}></circle>`;
|
||||
document.getElementById("debug").insertAdjacentHTML("afterBegin", html);
|
||||
byId("debug").insertAdjacentHTML("afterBegin", html);
|
||||
} else {
|
||||
circle.setAttribute("cx", x);
|
||||
circle.setAttribute("cy", y);
|
||||
|
|
@ -70,7 +70,7 @@ function moveCircle(x, y, r = 20) {
|
|||
}
|
||||
|
||||
function removeCircle() {
|
||||
if (document.getElementById("brushCircle")) document.getElementById("brushCircle").remove();
|
||||
if (byId("brushCircle")) byId("brushCircle").remove();
|
||||
}
|
||||
|
||||
// get browser-defined fit-content
|
||||
|
|
@ -79,8 +79,8 @@ function fitContent() {
|
|||
}
|
||||
|
||||
// apply sorting behaviour for lines on Editor header click
|
||||
document.querySelectorAll(".sortable").forEach(function (e) {
|
||||
e.addEventListener("click", function () {
|
||||
document.querySelectorAll(".sortable").forEach(function (event) {
|
||||
event.on("click", function () {
|
||||
sortLines(this);
|
||||
});
|
||||
});
|
||||
|
|
@ -90,7 +90,7 @@ function applySortingByHeader(headerContainer) {
|
|||
.getElementById(headerContainer)
|
||||
.querySelectorAll(".sortable")
|
||||
.forEach(function (element) {
|
||||
element.addEventListener("click", function () {
|
||||
element.on("click", function () {
|
||||
sortLines(this);
|
||||
});
|
||||
});
|
||||
|
|
@ -235,7 +235,7 @@ function removeBurg(id) {
|
|||
|
||||
if (burg.coa) {
|
||||
const coaId = "burgCOA" + id;
|
||||
if (document.getElementById(coaId)) document.getElementById(coaId).remove();
|
||||
if (byId(coaId)) byId(coaId).remove();
|
||||
emblems.select(`#burgEmblems > use[data-i='${id}']`).remove();
|
||||
delete burg.coa; // remove to save data
|
||||
}
|
||||
|
|
@ -629,7 +629,7 @@ function createPicker() {
|
|||
}
|
||||
|
||||
function updateSelectedRect(fill) {
|
||||
document.getElementById("picker").querySelector("rect.selected").classList.remove("selected");
|
||||
byId("picker").querySelector("rect.selected").classList.remove("selected");
|
||||
document
|
||||
.getElementById("picker")
|
||||
.querySelector("rect[fill='" + fill.toLowerCase() + "']")
|
||||
|
|
@ -687,7 +687,7 @@ function openPicker(fill, callback) {
|
|||
updateSelectedRect(fill);
|
||||
|
||||
openPicker.updateFill = function () {
|
||||
const selected = document.getElementById("picker").querySelector("rect.selected");
|
||||
const selected = byId("picker").querySelector("rect.selected");
|
||||
if (!selected) return;
|
||||
callback(selected.getAttribute("fill"));
|
||||
};
|
||||
|
|
@ -888,8 +888,8 @@ function selectIcon(initial, callback) {
|
|||
if (!callback) return;
|
||||
$("#iconSelector").dialog();
|
||||
|
||||
const table = document.getElementById("iconTable");
|
||||
const input = document.getElementById("iconInput");
|
||||
const table = byId("iconTable");
|
||||
const input = byId("iconInput");
|
||||
input.value = initial;
|
||||
|
||||
if (!table.innerHTML) {
|
||||
|
|
@ -1119,13 +1119,11 @@ function selectIcon(initial, callback) {
|
|||
}
|
||||
|
||||
function getAreaUnit(squareMark = "²") {
|
||||
return document.getElementById("areaUnit").value === "square"
|
||||
? document.getElementById("distanceUnitInput").value + squareMark
|
||||
: document.getElementById("areaUnit").value;
|
||||
return byId("areaUnit").value === "square" ? byId("distanceUnitInput").value + squareMark : byId("areaUnit").value;
|
||||
}
|
||||
|
||||
function getArea(rawArea) {
|
||||
const distanceScale = document.getElementById("distanceScaleInput")?.value;
|
||||
const distanceScale = byId("distanceScaleInput")?.value;
|
||||
return rawArea * distanceScale ** 2;
|
||||
}
|
||||
|
||||
|
|
@ -1150,26 +1148,26 @@ function confirmationDialog(options) {
|
|||
}
|
||||
};
|
||||
|
||||
document.getElementById("alertMessage").innerHTML = message;
|
||||
byId("alertMessage").innerHTML = message;
|
||||
$("#alert").dialog({resizable: false, title, buttons});
|
||||
}
|
||||
|
||||
// add and register event listeners to clean up on editor closure
|
||||
function listen(element, event, handler) {
|
||||
element.addEventListener(event, handler);
|
||||
element.on(event, handler);
|
||||
return () => element.removeEventListener(event, handler);
|
||||
}
|
||||
|
||||
// Calls the refresh functionality on all editors currently open.
|
||||
function refreshAllEditors() {
|
||||
TIME && console.time("refreshAllEditors");
|
||||
if (document.getElementById("culturesEditorRefresh")?.offsetParent) culturesEditorRefresh.click();
|
||||
if (document.getElementById("biomesEditorRefresh")?.offsetParent) biomesEditorRefresh.click();
|
||||
if (document.getElementById("diplomacyEditorRefresh")?.offsetParent) diplomacyEditorRefresh.click();
|
||||
if (document.getElementById("provincesEditorRefresh")?.offsetParent) provincesEditorRefresh.click();
|
||||
if (document.getElementById("religionsEditorRefresh")?.offsetParent) religionsEditorRefresh.click();
|
||||
if (document.getElementById("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById("zonesEditorRefresh")?.offsetParent) zonesEditorRefresh.click();
|
||||
if (byId("culturesEditorRefresh")?.offsetParent) culturesEditorRefresh.click();
|
||||
if (byId("biomesEditorRefresh")?.offsetParent) biomesEditorRefresh.click();
|
||||
if (byId("diplomacyEditorRefresh")?.offsetParent) diplomacyEditorRefresh.click();
|
||||
if (byId("provincesEditorRefresh")?.offsetParent) provincesEditorRefresh.click();
|
||||
if (byId("religionsEditorRefresh")?.offsetParent) religionsEditorRefresh.click();
|
||||
if (byId("statesEditorRefresh")?.offsetParent) statesEditorRefresh.click();
|
||||
if (byId("zonesEditorRefresh")?.offsetParent) zonesEditorRefresh.click();
|
||||
TIME && console.timeEnd("refreshAllEditors");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ function editRegiment(selector) {
|
|||
drawBase();
|
||||
|
||||
$("#regimentEditor").dialog({
|
||||
title: "Edit Regiment", resizable: false, close: closeEditor,
|
||||
title: "Edit Regiment",
|
||||
resizable: false,
|
||||
close: closeEditor,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"}
|
||||
});
|
||||
|
||||
|
|
@ -40,17 +42,19 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function updateRegimentData(regiment) {
|
||||
document.getElementById("regimentType").className = regiment.n ? "icon-anchor" :"icon-users";
|
||||
document.getElementById("regimentType").className = regiment.n ? "icon-anchor" : "icon-users";
|
||||
document.getElementById("regimentName").value = regiment.name;
|
||||
document.getElementById("regimentEmblem").value = regiment.icon;
|
||||
const composition = document.getElementById("regimentComposition");
|
||||
|
||||
composition.innerHTML = options.military.map(u => {
|
||||
return `<div data-tip="${capitalize(u.name)} number. Input to change">
|
||||
composition.innerHTML = options.military
|
||||
.map(u => {
|
||||
return `<div data-tip="${capitalize(u.name)} number. Input to change">
|
||||
<div class="label">${capitalize(u.name)}:</div>
|
||||
<input data-u="${u.name}" type="number" min=0 step=1 value="${(regiment.u[u.name]||0)}">
|
||||
<i>${u.type}</i></div>`
|
||||
}).join("");
|
||||
<input data-u="${u.name}" type="number" min=0 step=1 value="${regiment.u[u.name] || 0}">
|
||||
<i>${u.type}</i></div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
composition.querySelectorAll("input").forEach(el => el.addEventListener("change", changeUnit));
|
||||
}
|
||||
|
|
@ -58,26 +62,49 @@ function editRegiment(selector) {
|
|||
function drawBase() {
|
||||
const reg = regiment();
|
||||
const clr = pack.states[elSelected.dataset.state].color;
|
||||
const base = viewbox.insert("g", "g#armies").attr("id", "regimentBase").attr("stroke-width", .3).attr("stroke", "#000").attr("cursor", "move");
|
||||
base.on("mouseenter", () => {tip("Regiment base. Drag to re-base the regiment", true);}).on("mouseleave", () => {tip('', true);});
|
||||
const base = viewbox
|
||||
.insert("g", "g#armies")
|
||||
.attr("id", "regimentBase")
|
||||
.attr("stroke-width", 0.3)
|
||||
.attr("stroke", "#000")
|
||||
.attr("cursor", "move");
|
||||
base
|
||||
.on("mouseenter", () => {
|
||||
tip("Regiment base. Drag to re-base the regiment", true);
|
||||
})
|
||||
.on("mouseleave", () => {
|
||||
tip("", true);
|
||||
});
|
||||
|
||||
base.append("line").attr("x1", reg.bx).attr("y1", reg.by).attr("x2", reg.x).attr("y2", reg.y).attr("class", "dragLine");
|
||||
base.append("circle").attr("cx", reg.bx).attr("cy", reg.by).attr("r", 2).attr("fill", clr).call(d3.drag().on("drag", dragBase));
|
||||
base
|
||||
.append("line")
|
||||
.attr("x1", reg.bx)
|
||||
.attr("y1", reg.by)
|
||||
.attr("x2", reg.x)
|
||||
.attr("y2", reg.y)
|
||||
.attr("class", "regimentDragLine");
|
||||
base
|
||||
.append("circle")
|
||||
.attr("cx", reg.bx)
|
||||
.attr("cy", reg.by)
|
||||
.attr("r", 2)
|
||||
.attr("fill", clr)
|
||||
.call(d3.drag().on("drag", dragBase));
|
||||
}
|
||||
|
||||
function changeType() {
|
||||
const reg = regiment();
|
||||
reg.n = +!reg.n;
|
||||
document.getElementById("regimentType").className = reg.n ? "icon-anchor" :"icon-users";
|
||||
document.getElementById("regimentType").className = reg.n ? "icon-anchor" : "icon-users";
|
||||
|
||||
const size = +armies.attr("box-size");
|
||||
const baseRect = elSelected.querySelectorAll("rect")[0];
|
||||
const iconRect = elSelected.querySelectorAll("rect")[1];
|
||||
const icon = elSelected.querySelector(".regimentIcon");
|
||||
const x = reg.n ? reg.x-size*2 : reg.x-size*3;
|
||||
const x = reg.n ? reg.x - size * 2 : reg.x - size * 3;
|
||||
baseRect.setAttribute("x", x);
|
||||
baseRect.setAttribute("width", reg.n ? size*4 : size*6);
|
||||
iconRect.setAttribute("x", x - size*2);
|
||||
baseRect.setAttribute("width", reg.n ? size * 4 : size * 6);
|
||||
iconRect.setAttribute("x", x - size * 2);
|
||||
icon.setAttribute("x", x - size);
|
||||
elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
|
||||
}
|
||||
|
|
@ -87,13 +114,17 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function restoreName() {
|
||||
const reg = regiment(), regs = pack.states[elSelected.dataset.state].military;
|
||||
const reg = regiment(),
|
||||
regs = pack.states[elSelected.dataset.state].military;
|
||||
const name = Military.getName(reg, regs);
|
||||
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
|
||||
}
|
||||
|
||||
function selectEmblem() {
|
||||
selectIcon(regimentEmblem.value, v => {regimentEmblem.value = v; changeEmblem()});
|
||||
selectIcon(regimentEmblem.value, v => {
|
||||
regimentEmblem.value = v;
|
||||
changeEmblem();
|
||||
});
|
||||
}
|
||||
|
||||
function changeEmblem() {
|
||||
|
|
@ -104,7 +135,7 @@ function editRegiment(selector) {
|
|||
function changeUnit() {
|
||||
const u = this.dataset.u;
|
||||
const reg = regiment();
|
||||
reg.u[u] = (+this.value)||0;
|
||||
reg.u[u] = +this.value || 0;
|
||||
reg.a = d3.sum(Object.values(reg.u));
|
||||
elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
|
||||
if (militaryOverviewRefresh.offsetParent) militaryOverviewRefresh.click();
|
||||
|
|
@ -112,24 +143,47 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function splitRegiment() {
|
||||
const reg = regiment(), u1 = reg.u;
|
||||
const state = +elSelected.dataset.state, military = pack.states[state].military;
|
||||
const i = last(military).i + 1, u2 = Object.assign({}, u1); // u clone
|
||||
const reg = regiment(),
|
||||
u1 = reg.u;
|
||||
const state = +elSelected.dataset.state,
|
||||
military = pack.states[state].military;
|
||||
const i = last(military).i + 1,
|
||||
u2 = Object.assign({}, u1); // u clone
|
||||
|
||||
Object.keys(u2).forEach(u => u2[u] = Math.floor(u2[u]/2)); // halved new reg
|
||||
Object.keys(u2).forEach(u => (u2[u] = Math.floor(u2[u] / 2))); // halved new reg
|
||||
const a = d3.sum(Object.values(u2)); // new reg total
|
||||
if (!a) {tip("Not enough forces to split", false, "error"); return}; // nothing to add
|
||||
if (!a) {
|
||||
tip("Not enough forces to split", false, "error");
|
||||
return;
|
||||
} // nothing to add
|
||||
|
||||
// update old regiment
|
||||
Object.keys(u1).forEach(u => u1[u] = Math.ceil(u1[u]/2)); // halved old reg
|
||||
Object.keys(u1).forEach(u => (u1[u] = Math.ceil(u1[u] / 2))); // halved old reg
|
||||
reg.a = d3.sum(Object.values(u1)); // old reg total
|
||||
regimentComposition.querySelectorAll("input").forEach(el => el.value = reg.u[el.dataset.u]||0);
|
||||
regimentComposition.querySelectorAll("input").forEach(el => (el.value = reg.u[el.dataset.u] || 0));
|
||||
elSelected.querySelector("text").innerHTML = Military.getTotal(reg);
|
||||
|
||||
// create new regiment
|
||||
const shift = +armies.attr("box-size") * 2;
|
||||
const y = function(x, y) {do {y+=shift} while (military.find(r => r.x === x && r.y === y)); return y;}
|
||||
const newReg = {a, cell:reg.cell, i, n:reg.n, u:u2, x:reg.x, y:y(reg.x, reg.y), bx:reg.bx, by:reg.by, state, icon: reg.icon};
|
||||
const y = function (x, y) {
|
||||
do {
|
||||
y += shift;
|
||||
} while (military.find(r => r.x === x && r.y === y));
|
||||
return y;
|
||||
};
|
||||
const newReg = {
|
||||
a,
|
||||
cell: reg.cell,
|
||||
i,
|
||||
n: reg.n,
|
||||
u: u2,
|
||||
x: reg.x,
|
||||
y: y(reg.x, reg.y),
|
||||
bx: reg.bx,
|
||||
by: reg.by,
|
||||
state,
|
||||
icon: reg.icon
|
||||
};
|
||||
newReg.name = Military.getName(newReg, military);
|
||||
military.push(newReg);
|
||||
Military.generateNote(newReg, pack.states[state]); // add legend
|
||||
|
|
@ -152,11 +206,13 @@ function editRegiment(selector) {
|
|||
function addRegimentOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1];
|
||||
const state = +elSelected.dataset.state, military = pack.states[state].military;
|
||||
const x = pack.cells.p[cell][0],
|
||||
y = pack.cells.p[cell][1];
|
||||
const state = +elSelected.dataset.state,
|
||||
military = pack.states[state].military;
|
||||
const i = military.length ? last(military).i + 1 : 0;
|
||||
const n = +(pack.cells.h[cell] < 20); // naval or land
|
||||
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"};
|
||||
const reg = {a: 0, cell, i, n, u: {}, x, y, bx: x, by: y, state, icon: "🛡️"};
|
||||
reg.name = Military.getName(reg, military);
|
||||
military.push(reg);
|
||||
Military.generateNote(reg, pack.states[state]); // add legend
|
||||
|
|
@ -179,30 +235,59 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function attackRegimentOnClick() {
|
||||
const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement;
|
||||
const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state;
|
||||
const target = d3.event.target,
|
||||
regSelected = target.parentElement,
|
||||
army = regSelected.parentElement;
|
||||
const oldState = +elSelected.dataset.state,
|
||||
newState = +regSelected.dataset.state;
|
||||
|
||||
if (army.parentElement.id !== "armies") {tip("Please click on a regiment to attack", false, "error"); return;}
|
||||
if (regSelected === elSelected) {tip("Regiment cannot attack itself", false, "error"); return;}
|
||||
if (oldState === newState) {tip("Cannot attack fraternal regiment", false, "error"); return;}
|
||||
if (army.parentElement.id !== "armies") {
|
||||
tip("Please click on a regiment to attack", false, "error");
|
||||
return;
|
||||
}
|
||||
if (regSelected === elSelected) {
|
||||
tip("Regiment cannot attack itself", false, "error");
|
||||
return;
|
||||
}
|
||||
if (oldState === newState) {
|
||||
tip("Cannot attack fraternal regiment", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const attacker = regiment();
|
||||
const defender = pack.states[regSelected.dataset.state].military.find(r => r.i == regSelected.dataset.id);
|
||||
if (!attacker.a || !defender.a) {tip("Regiment has no troops to battle", false, "error"); return;}
|
||||
if (!attacker.a || !defender.a) {
|
||||
tip("Regiment has no troops to battle", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// save initial position to temp attribute
|
||||
attacker.px = attacker.x, attacker.py = attacker.y;
|
||||
defender.px = defender.x, defender.py = defender.y;
|
||||
(attacker.px = attacker.x), (attacker.py = attacker.y);
|
||||
(defender.px = defender.x), (defender.py = defender.y);
|
||||
|
||||
// move attacker to defender
|
||||
Military.moveRegiment(attacker, defender.x, defender.y-8);
|
||||
Military.moveRegiment(attacker, defender.x, defender.y - 8);
|
||||
|
||||
// draw battle icon
|
||||
const attack = d3.transition().delay(300).duration(700).ease(d3.easeSinInOut).on("end", () => new Battle(attacker, defender));
|
||||
svg.append("text").attr("x", window.innerWidth/2).attr("y", window.innerHeight/2)
|
||||
.text("⚔️").attr("font-size", 0).attr("opacity", 1)
|
||||
.style("dominant-baseline", "central").style("text-anchor", "middle")
|
||||
.transition(attack).attr("font-size", 1000).attr("opacity", .2).remove();
|
||||
const attack = d3
|
||||
.transition()
|
||||
.delay(300)
|
||||
.duration(700)
|
||||
.ease(d3.easeSinInOut)
|
||||
.on("end", () => new Battle(attacker, defender));
|
||||
svg
|
||||
.append("text")
|
||||
.attr("x", window.innerWidth / 2)
|
||||
.attr("y", window.innerHeight / 2)
|
||||
.text("⚔️")
|
||||
.attr("font-size", 0)
|
||||
.attr("opacity", 1)
|
||||
.style("dominant-baseline", "central")
|
||||
.style("text-anchor", "middle")
|
||||
.transition(attack)
|
||||
.attr("font-size", 1000)
|
||||
.attr("opacity", 0.2)
|
||||
.remove();
|
||||
|
||||
clearMainTip();
|
||||
$("#regimentEditor").dialog("close");
|
||||
|
|
@ -222,18 +307,27 @@ function editRegiment(selector) {
|
|||
}
|
||||
|
||||
function attachRegimentOnClick() {
|
||||
const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement;
|
||||
const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state;
|
||||
const target = d3.event.target,
|
||||
regSelected = target.parentElement,
|
||||
army = regSelected.parentElement;
|
||||
const oldState = +elSelected.dataset.state,
|
||||
newState = +regSelected.dataset.state;
|
||||
|
||||
if (army.parentElement.id !== "armies") {tip("Please click on a regiment", false, "error"); return;}
|
||||
if (regSelected === elSelected) {tip("Cannot attach regiment to itself. Please click on another regiment", false, "error"); return;}
|
||||
if (army.parentElement.id !== "armies") {
|
||||
tip("Please click on a regiment", false, "error");
|
||||
return;
|
||||
}
|
||||
if (regSelected === elSelected) {
|
||||
tip("Cannot attach regiment to itself. Please click on another regiment", false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const reg = regiment(); // reg to be attached
|
||||
const sel = pack.states[newState].military.find(r => r.i == regSelected.dataset.id); // reg to attach to
|
||||
|
||||
for (const unit of options.military) {
|
||||
const u = unit.name;
|
||||
if (reg.u[u]) sel.u[u] ? sel.u[u] += reg.u[u] : sel.u[u] = reg.u[u];
|
||||
if (reg.u[u]) sel.u[u] ? (sel.u[u] += reg.u[u]) : (sel.u[u] = reg.u[u]);
|
||||
}
|
||||
sel.a = d3.sum(Object.values(sel.u)); // reg total
|
||||
regSelected.querySelector("text").innerHTML = Military.getTotal(sel); // update selected reg total text
|
||||
|
|
@ -247,7 +341,7 @@ function editRegiment(selector) {
|
|||
|
||||
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
|
||||
$("#regimentEditor").dialog("close");
|
||||
editRegiment("#"+regSelected.id);
|
||||
editRegiment("#" + regSelected.id);
|
||||
}
|
||||
|
||||
function regenerateLegend() {
|
||||
|
|
@ -264,9 +358,11 @@ function editRegiment(selector) {
|
|||
|
||||
function removeRegiment() {
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the regiment?";
|
||||
$("#alert").dialog({resizable: false, title: "Remove regiment",
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove regiment",
|
||||
buttons: {
|
||||
Remove: function() {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
const military = pack.states[elSelected.dataset.state].military;
|
||||
const regIndex = military.indexOf(regiment());
|
||||
|
|
@ -281,7 +377,9 @@ function editRegiment(selector) {
|
|||
if (regimentsOverviewRefresh.offsetParent) regimentsOverviewRefresh.click();
|
||||
$("#regimentEditor").dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -305,16 +403,17 @@ function editRegiment(selector) {
|
|||
const self = elSelected === this;
|
||||
const baseLine = viewbox.select("g#regimentBase > line");
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const x = reg.x = d3.event.x, y = reg.y = d3.event.y;
|
||||
d3.event.on("drag", function () {
|
||||
const x = (reg.x = d3.event.x),
|
||||
y = (reg.y = d3.event.y);
|
||||
|
||||
baseRect.setAttribute("x", x1(x));
|
||||
baseRect.setAttribute("y", y1(y));
|
||||
text.setAttribute("x", x);
|
||||
text.setAttribute("y", y);
|
||||
iconRect.setAttribute("x", x1(x)-h);
|
||||
iconRect.setAttribute("x", x1(x) - h);
|
||||
iconRect.setAttribute("y", y1(y));
|
||||
icon.setAttribute("x", x1(x)-size);
|
||||
icon.setAttribute("x", x1(x) - size);
|
||||
icon.setAttribute("y", y);
|
||||
if (self) baseLine.attr("x2", x).attr("y2", y);
|
||||
});
|
||||
|
|
@ -324,13 +423,16 @@ function editRegiment(selector) {
|
|||
const baseLine = viewbox.select("g#regimentBase > line");
|
||||
const reg = regiment();
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
d3.event.on("drag", function () {
|
||||
this.setAttribute("cx", d3.event.x);
|
||||
this.setAttribute("cy", d3.event.y);
|
||||
baseLine.attr("x1", d3.event.x).attr("y1", d3.event.y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function() {reg.bx = d3.event.x; reg.by = d3.event.y;});
|
||||
d3.event.on("end", function () {
|
||||
reg.bx = d3.event.x;
|
||||
reg.by = d3.event.y;
|
||||
});
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
|
|
@ -343,5 +445,4 @@ function editRegiment(selector) {
|
|||
restoreDefaultEvents();
|
||||
elSelected = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue