function SpaceTree(
data,
{
path,
id = Array.isArray(data) ? (d) => d.id : null,
parentId = Array.isArray(data) ? (d) => d.parentId : null,
children,
tree = d3.tree,
sort,
label,
title,
link,
linkTarget = "_blank",
width = 640,
r = 3,
padding = 1,
fill = "#999",
fillOpacity,
stroke = "#555",
strokeWidth = 1.5,
strokeOpacity = 0.4,
strokeLinejoin,
strokeLinecap,
halo = "#fff",
haloWidth = 3,
curve = d3.curveBumpX,
thumbSize = 25,
transitionDuration = 1000,
highlight = "#ca2c92",
value = null,
onClick = null
} = {}
) {
const root =
path != null
? d3.stratify().path(path)(data)
: id != null || parentId != null
? d3.stratify().id(id).parentId(parentId)(data)
: d3.hierarchy(data, children);
value = value || root;
if (sort != null) root.sort(sort);
const descendants = root.descendants();
const diagonal = d3
.link(curve)
.x((d) => d.y)
.y((d) => d.x);
const margin = { top: 10, right: 120, bottom: 10, left: 40 };
const dx = thumbSize + 2;
const dy = width / (root.height + padding);
const layout = tree().nodeSize([dx, dy]);
layout(root);
root.x0 = dy / 2;
root.y0 = 0;
descendants.forEach((d, i) => {
d.id = i;
d._children = d.children;
if (d.depth) d.children = null;
});
if (typeof curve !== "function") throw new Error(`Unsupported curve`);
const svg = d3
.create("svg")
.attr("viewBox", [-margin.left, -margin.top, width, dx])
.style("font", "10px sans-serif")
.style("user-select", "none");
svg.append("defs").html(`
<style>
.highlight circle { fill:${highlight} }
.highlight circle { fill:${highlight} }
.highlight text { fill:${highlight} }
.leaf circle { fill:${highlight} }
.leaf circle { fill:${highlight} }
.leaf text { fill:${highlight} }
path.highlight { stroke:${highlight} }
<style>`);
const gLink = svg
.append("g")
.attr("fill", "none")
.attr("stroke", stroke)
.attr("stroke-opacity", strokeOpacity)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth);
const gNode = svg
.append("g")
.attr("cursor", "pointer")
.attr("pointer-events", "all");
function removeThumb(source) {
return svg
.selectAll("image.thumb")
.filter((d) => d === source)
.transition()
.duration(transitionDuration / 5)
.attr("opacity", 0);
}
function addThumb(nodeIds) {
console.log("add Thumbs", nodeIds);
return svg
.selectAll("image.thumb")
.filter((d) => nodeIds.includes(d.id) )
.transition()
.duration(transitionDuration / 5)
.attr("opacity", 1);
}
async function _onClick(event, d, triggerEvent = true) {
if (triggerEvent)
svg.node().dispatchEvent(new Event("input", { bubbles: true }));
if (triggerEvent && onClick && typeof onClick === "function") onClick(d);
try {
const trimmingNodes = trim(d);
await update(d, trimmingNodes).end();
await addThumb(trimmingNodes).end();
await removeThumb(d).end();
expand(d);
await update(d, trimmingNodes).end();
addThumb([d.id])
} catch (e) {
console.log("Error running animation", e);
}
}
function update(source, trimmingNodes = []) {
value = source;
const duration =
d3.event && d3.event.altKey
? transitionDuration * 10
: (transitionDuration * 2) / 5;
const nodes = root.descendants().reverse();
const links = root.links();
layout(root);
let left = root;
let right = root;
root.eachBefore((node) => {
if (node.x < left.x) left = node;
if (node.x > right.x) right = node;
});
let height = right.x - left.x + dx * 2;
let transition = svg
.transition()
.duration(duration)
.attr("viewBox", [(-dy * padding) / 2, left.x - dx, width, height])
.attr("width", width)
.attr("height", height)
.tween(
"resize",
window.ResizeObserver ? null : () => () => svg.dispatch("toggle")
);
const context = {
gNode,
transition,
nodes,
source,
update,
r,
stroke,
fill,
label,
halo,
haloWidth,
links,
diagonal,
gLink,
thumbSize,
curve,
root,
_onClick,
trimmingNodes
};
const node = updateNodes(context);
const link = updateLinks(context);
updatePath({ node, link, root, source });
root.eachBefore((d) => {
d.x0 = d.x;
d.y0 = d.y;
});
return transition;
}
update(root);
svg.node().value = root;
Object.defineProperty(svg.node(), "value", {
get() {
return value;
},
set(v) {
value = v;
_onClick(null, value, false);
}
});
return svg.node();
}