Published
Edited
May 10, 2021
Insert cell
md`# Force Push

Dynamically add nodes to a link-less force graph.
`
Insert cell
import {Button} from '@observablehq/inputs'
Insert cell
viewof nodes = Button([
['Add', (nodes) => nodes.concat([generateNode(100, 100)])],
['Remove', (nodes) => nodes.slice(1)],
], {value: Array(10).fill().map(generateNode)})
Insert cell
chart = {
const height = 500
// const svgel = document.createElement("svg");
// svgel.setAttribute('viewBox', `${-width / 2}, ${-height / 2}, ${width}, ${height}`)
const svg = d3.create("svg").attr("viewBox", [-width / 2, -height / 2, width, height]);
// const svg = d3.select(svgel)
const container = svg.append('g')
.attr("transform", "translate(" + 0 + "," + 0 + ")")
const node = container
.selectAll('g')
.data(nodes).enter().append('g')
const circ = node.append('circle')
.attr('fill', 'white')
.attr("stroke", "blue")
.attr("stroke-width", 1.5)
.attr('r', d => d.r)
const text = node.append('text')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('fill', 'black')
.text(d => d.id)
const simulation = d3.forceSimulation(nodes)
.alphaTarget(0.7)
.velocityDecay(0.95)
.force("x", d3.forceX().strength(0.51))
.force("y", d3.forceY().strength(0.51))
.force("collide", d3.forceCollide().radius((d) => d.r + 1).iterations(3))
.force('charge', d3.forceManyBody()
.strength((d, i) => (i ? 0 : (-width * 2) / 3)))
.on('tick', function() {
node.attr('transform', d => `translate(${d.x},${d.y})`)
})
.restart()
return svg.node()
// return svgel
}
Insert cell
nodesb = [generateNode(), generateNode(), generateNode()]
Insert cell
d3 = require("d3@6")
Insert cell
generateNode = function generateNode(x, y) {
return { id: Math.floor(Math.random() * 900) % 900, r: 20, x, y }
}
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