Published
Edited
Dec 1, 2020
1 star
Insert cell
md`# Fruits`
Insert cell
chart = {
const svg = d3
.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);

let link = svg.append("g").selectAll("line");
let node = svg.append("g").selectAll("circle");
let title = svg.append("g").selectAll("text");

let simulation = d3.forceSimulation();
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();
}
}

// Distanz der Links zwischen den Nodes
// Je mehr Kindelemente vorhanden sind, desto grösser wird die Distanz
function getDistance(linkDatum) {
let source = linkDatum.source;
let target = linkDatum.target;

const childrenCount = target.item.children.length;

const childrenVisible =
childrenCount > 0 && target.item.children.some(item => item.visible);

const ratio = childrenCount * .2;

const factor =
configuration.childItemDistanceFactor +
(childrenVisible ? Math.max(1, ratio) : 0);

return factor * (source.radius + target.radius);
}

// Link Distanz Gewichtung.
function getStrength(linkDatum) {
return 1;
}

// Kollisionsradius
function getCollisionRadius(node) {
if (node.item.parent == null) {
return node.radius + 30;
}

return node.radius + 10;
}

// Funktion, die bei jedem Tick des Force-Renderings aufgerufen wird
function updateLinksAndNodes() {
if (linkForce) {
linkForce.distance(getDistance);
linkForce.strength(getStrength);
}

if (collisionForce) {
collisionForce.radius(getCollisionRadius);
}

simulation.force(
"manybody",
d3.forceManyBody().strength(node => {
return node.radius * configuration.manyBodyStrength;
})
);

node.attr("cx", d => d.x).attr("cy", d => d.y);
title.attr("x", d => d.x).attr("y", d => d.y);

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);

simulation.nodes(nodes);
simulation.force(
"charge",
d3.forceManyBody().strength(configuration.manyBodyStrength)
);

linkForce.links(links);
simulation.force('link', linkForce);

simulation.force(
'x',
d3.forceX().strength((configuration.centerStrength * 100) / width)
);

simulation.force(
'y',
d3.forceY().strength((configuration.centerStrength * 100) / height)
);

collisionForce = d3.forceCollide();
simulation.force('collision', collisionForce);

simulation.velocityDecay(.6);

node = node
.data(nodes, d => d.id)
.join(
enter =>
enter
.append("circle")
.attr('opacity', 0)
.attr("stroke-width", "0")
.call(g =>
g
.transition()
.ease(d3.easeCubicOut)
.delay(0)
.duration(configuration.transitionDuration)
.attr('opacity', 1)
.attr("stroke-width", "10px")
),
update => update,
exit =>
exit
.transition()
.ease(d3.easeCubicOut)
.duration(50)
.attr("r", Math.E / 2)
.remove()
)
.attr('r', d => d.radius)
.attr('cx', d => d.x)
.attr("cy", d => d.y)
.attr('fill', d => d.fill)
.attr("stroke-width", "10px")
.attr("stroke", d => d.fill + 60)
.attr('cursor', d => (d.item.children.length > 0 ? 'pointer' : ''))
.on("click", (e, d) => {
if (d.item.children.length > 0) {
clickItem(d.id);
updateNodeList();
restartSimulation();
}
});

title = title
.data(nodes, d => d.id)
.join(
enter =>
enter
.append("text")
.style('opacity', 0)
.call(g =>
g
.transition()
.ease(d3.easeCubicOut)
.delay(0)
.duration(configuration.transitionDuration)
.style('opacity', 1)
),
update => update,
exit => exit.remove()
)
.attr("x", d => d.x)
.attr("y", d => d.y)
.text(d => d.item.name)
.attr("text-anchor", "middle")
.attr("fill", "#fff")
.attr(
"font-family",
"-apple-system, system-ui, avenir, helvetica, roboto, noto, arial"
)
.attr("font-weight", "700")
.attr("dy", "2px")
.attr("font-size", "12px")
.style("pointer-events", "none");

link = link
.data(links, d => d.target.id)
.join(enter =>
enter
.append("line")
.attr("stroke-dasharray", "0 5")
.attr("stroke-opacity", "0")
.attr("stroke-linecap", "round")
.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-width", 4)
.attr("stroke", d => d.target.fill)
.call(enter =>
enter
.transition()
.ease(d3.easeCubicOut)
.delay(0)
.duration(configuration.transitionDuration)
.attr("stroke-opacity", 1)
)
);
}

simulation.on('tick', () => {
updateLinksAndNodes();
});

// helps to avoid initial scatter
for (let i = 0; i < 10; i++) {
simulation.tick();
}

simulation.alphaDecay(1 - Math.pow(0.001, 1 / 600));

updateNodeList();

return svg.node();
}
Insert cell
Insert cell
Insert cell
data = initDataItems(dataItems)
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