chart = {
const svg = d3
.create("svg")
.attr("viewBox", [-width, -height, 2 * width, 2 * height]);
let position = {};
const zoom_g = svg.append("g");
function zoomed({ transform }) {
zoom_g.attr("transform", transform);
}
svg.call(
d3
.zoom()
.scaleExtent([0.2, 6])
.on("zoom", zoomed)
);
let link = zoom_g.append("g").selectAll("line");
let node = zoom_g.append("g").selectAll("circle");
let title = zoom_g.append("g").selectAll("text");
let simulation = d3.forceSimulation().on('tick', updateLinksAndNodes);
let collisionForce = d3.forceCollide();
let linkForce = d3.forceLink().id(d => d.id);
function restartSimulation() {
if (simulation.alpha() <= 0.3) {
simulation.alpha(0.3);
simulation.restart();
}
}
// Kollisionsradius
function getCollisionRadius(node) {
if (node.item.parentId == null) return node.radius + 30;
return node.radius + 10;
}
////////////////////////////////
// TICK-FUNKTION (wird bei jedem Tick des Force-Renderings aufgerufen)
function updateLinksAndNodes() {
if (linkForce) {
linkForce.distance(getDistance);
linkForce.strength(config.linkStrength);
}
if (collisionForce) {
collisionForce.radius(getCollisionRadius);
}
simulation.force(
"charge",
d3.forceManyBody().strength(node => node.radius * config.manyBodyStrength)
);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.call(drag(simulation));
title
.attr("x", d => d.x)
.attr("y", d => d.y)
.text(d => d.item.title)
.call(wrap);
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
}
////////////////////////////////
// Funktion, um Nodes und force zu initialisieren
function updateNodeList() {
validateData();
const nodes = getNodes(data);
const links = getLinks(data);
console.log(nodes);
simulation
.nodes(nodes)
.force("charge", d3.forceManyBody().strength(config.manyBodyStrength))
.force('link', linkForce.links(links))
.force('x', d3.forceX().strength((config.centerStrength * 100) / width))
.force('y', d3.forceY().strength((config.centerStrength * 100) / height))
.force('collision', d3.forceCollide())
.velocityDecay(.25);
node = node
.data(nodes, d => d.id)
.join(
enter =>
enter.append("circle").call(g =>
g
.transition()
.ease(d3.easeCubicOut)
.duration(config.transitionDuration)
.attr("r", d => d.radius)
),
update => update,
exit =>
exit
.transition()
.ease(d3.easeCubicOut)
.duration(config.transitionDuration)
.attr("opacity", 0.5)
.attr("r", Math.E / 4)
.attr("cx", d =>
d.item.parent.visible
? d.item.parent.node.x
: getRootNode(d).node.x
)
.attr("cy", d =>
d.item.parent.visible
? d.item.parent.node.y
: getRootNode(d).node.y
)
.remove()
)
.attr('cx', d => d.x)
.attr("cy", d => d.y)
.attr('fill', d => d.fill)
.attr("stroke-width", d =>
d.item.isOpen ? 0.2 * d.radius + "px" : "0px"
)
.attr("stroke", d => (d.item.isOpen ? d.item.color + '60' : d.item.color))
.attr('cursor', d => (d.item.children.length > 0 ? 'pointer' : ''))
.on("click", (e, d) => {
if (d.item.children.length > 0) {
clickItem(d.id);
updateNodeList();
restartSimulation();
}
})
.on("contextmenu", (e, d) => {
e.preventDefault();
if (position.id) {
console.log(nodes);
// alle nodes unter position durchgehen und dasjenige, das mit position.id matcht, x und y zurücksetzen auf die Werte von position
}
position = { id: d.id, x: d.x, y: d.y };
d.fx = 0;
d.fy = 0;
});
title = title
.data(nodes, d => d.id)
.join(
enter =>
enter
.append("text")
.style('opacity', 0)
.text(d => d.item.title)
.call(g =>
g
.transition()
.ease(d3.easeCubicOut)
.delay(0)
.duration(config.transitionDuration)
.style('opacity', 1)
),
update => update,
exit => exit.remove()
)
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr("font-size", d => d.radius / 4 + "px")
.attr(
"font-family",
"-apple-system, system-ui, avenir, helvetica, roboto, noto, arial"
);
link = link
.data(links, d => d.target.id)
.join(enter =>
enter
.append("line")
.attr("stroke-opacity", "0")
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
.attr("stroke", d => d.target.fill)
.call(enter =>
enter
.transition()
.ease(d3.easeCubicOut)
.delay(0)
.duration(config.transitionDuration)
.attr("stroke-opacity", 1)
)
);
}
updateNodeList();
return svg.node();
}