Published
Edited
Dec 14, 2020
1 fork
Insert cell
Insert cell
chart = {
const svg = d3
.create("svg")
.attr("viewBox", [-width, -height, 2 * width, 2 * height]);

let position = {};

////////////////////////////////
// ZOOM //
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();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more