Published
Edited
Jun 4, 2019
1 star
Insert cell
md`# Node Force Layout`
Insert cell
dragAlphaTarget = 0.3
Insert cell
initialAlphaTarget = 1.0
Insert cell
minV = 8e-4
Insert cell
yStrength = 0.001 * (yMax - yMin)
Insert cell
xStrength = 0.001 * (xMax - xMin)
Insert cell
linkStrength = 0.1
Insert cell
chargeDistanceMax = 10
Insert cell
chargeDistanceMin = 1
Insert cell
chargeStrength = -0.5
Insert cell
gravityYAnchor = yMax
Insert cell
gravityYStrength = 0.1
Insert cell
center = true
Insert cell
yExtent = [0, 100]
Insert cell
xExtent = [0, 100]
Insert cell
xMin = xExtent[0]
Insert cell
xMax = xExtent[1]
Insert cell
yMin = yExtent[0]
Insert cell
yMax = yExtent[1]
Insert cell
forces = {
simulation
.force("y", yStrength === 0 ? null : d3.forceY().y(yAnchor).strength(yStrength))
.force("x", xStrength === 0 ? null : d3.forceX().x(xAnchor).strength(xStrength))
.force("charge", chargeStrength === 0 ? null : d3.forceManyBody().strength(chargeStrength).distanceMin(chargeDistanceMin).distanceMax(chargeDistanceMax))
.force("link", linkStrength === 0 ? null : d3.forceLink().strength(linkStrength))
.force("center", center ? d3.forceCenter(d3.mean([xMin, xMax]), d3.mean([yMin, yMax])) : null)
.force("gravity", gravityYStrength === 0 ? null : d3.forceY().y(gravityYAnchor).strength(gravityYStrength))
;
}
Insert cell
drag = (dragX, dragY) => {
function dragged(d) {
if (dragX) {
const fx = dragX(d3.event.x)
if (fx != null) {
d.fx = fx
}
}
if (dragY) {
const fy = dragY(d3.event.y)
if (fy != null) {
d.fy = fy
}
}
}

function dragstarted(d) {
if (!d3.event.active) {
simulation.alphaTarget(dragAlphaTarget).restart();
d.dragging = true
}
}
function dragended(d) {
if (d.fx != null) {
d.x = d.fx;
}
delete d.fx;

if (d.fy != null) {
d.y = d.fy;
}
delete d.fy;
if (!d3.event.active) {
d.dragging = false
simulation.alphaTarget(0);
refreshForces();
}
}

return d3.drag()
.on("start", d => dragstarted(d))
.on("drag", d => dragged(d))
.on("end", d => dragended(d));
}
Insert cell
function refreshForces() {
simulation.force("x", simulation.force("x"))
simulation.force("y", simulation.force("y"))
}
Insert cell
viewof positions = new View([])
Insert cell
updatePositions = {
console.log("updatePositions")
forces;

// Initial positions
let noop = true
for (const d of nodes) {
const x = xAnchor(d)
const y = yAnchor(d)
// Avoid restarting the simulation if all nodes are in the same position as last time
if (noop &&
(d.xAnchor !== x || (d.fx !== undefined && d.fx !== x) ||
(d.yAnchor !== y || (d.fy !== undefined && d.fy !== y)))) {
noop = false
}

d.xAnchor = x
if (d.x == null || isNaN(d.x)) {
d.x = x
}
d.yAnchor = y
if (d.y == null || isNaN(d.y)) {
d.y = y
}
}

if (noop && simulation.alpha() == simulation.alphaTarget()) {
console.log("returning early!!!")
console.log({
noop,
alpha: simulation.alpha(),
alphaTarget: simulation.alphaTarget(),
atTarget: simulation.alpha() == simulation.alphaTarget()
})

return
}

refreshForces()
refreshPolygons(nodes)

simulation
.nodes(nodes)
;

const linkForce = simulation.force('link')
linkForce && linkForce.links(links)

simulation.on("tick", () => {
refreshPolygons(nodes)
viewof positions.value = nodes
})
invalidation.then(() => {
simulation.on("tick", null)
})
simulation
.alpha(initialAlphaTarget)
.alphaTarget(0)
.restart()

viewof positions.value = nodes
}
Insert cell
md `---

## Test values`
Insert cell
nodes = {
const raw = [
{
name: "water",
attrs: {
depth: 20,
phase: [null, 0.5],
},
temps: {
},
sources: {},
users: ["tea"],
},
{
name: "tea",
attrs: {
depth: 1,
phase: [null, 0.5],
},
temps: {
},
sources: {},
users: [],
},
]
const index = Object.fromEntries(raw.map(component => [component.name, component]))
raw.forEach(component => component.users = component.users.map(name => index[name]))
console.log(raw)
return raw.map(
component => Object.assign(
component.temps,
{
name: component.name,
attrs: component.attrs,
sources: component.sources,
users: component.users.map(user => user.temps),
}))
}
Insert cell
Insert cell
xStableOrder = new WeakMap()
Insert cell
yStableOrder = new WeakMap()
Insert cell
function lookup(wm, obj, fn) {
if (!wm.has(obj)) {
wm.set(obj, fn(obj))
}
return wm.get(obj)
}
Insert cell
function yAnchor(d, i, arr) {
return lookup(yStableOrder, d, () => (Math.random() * (yMax - yMin)) + yMin)
}
Insert cell
function xAnchor(d) {
return lookup(xStableOrder, d, () => (Math.random() * (xMax - xMin)) + xMin)
}
Insert cell
positions
Insert cell
Insert cell
simulation = {
let simulation = d3.forceSimulation()
.force("collide", d3.forceCollide().radius(2))
// .force("gravity", d3.forceY().y(height).strength(.01))

invalidation.then(() => simulation.stop());
return simulation
}
Insert cell
function refreshPolygons(nodes) {
if (nodes.length < 3) {
nodes.forEach(n => n.polygon = [])
return
}

try {
let voronoisPolygons = d3.Delaunay.from(nodes, d => d.x, d => d.y)
.voronoi([xMin, yMin, xMax, yMax]);
nodes.forEach((d, i) => {
d.polygon = voronoisPolygons.cellPolygon(i)
})
} catch (e) {
console.log("nodes", nodes)
nodes.forEach(n => n.polygon = [])
console.error(e)
return
}
}

Insert cell
class View {
constructor(value) {
Object.defineProperties(this, {
_list: {value: [], writable: true},
_value: {value, writable: true}
});
}
get value() {
return this._value;
}
set value(value) {
this._value = value;
this.dispatchEvent({type: "input", value});
}
addEventListener(type, listener) {
if (type != "input" || this._list.includes(listener)) return;
this._list = [listener].concat(this._list);
}
removeEventListener(type, listener) {
if (type != "input") return;
this._list = this._list.filter(l => l !== listener);
}
dispatchEvent(event) {
const p = Promise.resolve(event);
this._list.forEach(l => p.then(l));
}
}
Insert cell
d3 = require("d3@5", "d3-array@2", "https://files-5xxzmmll5.now.sh/d3-delaunay.js")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more