/** * The brain's force layout, off the render thread. * * Run to rest in one go, it took several frames on the main thread: about * 75 ms on a page's first layout and 30 ms after for the Demo's 29 neurons, * and 250 ms at five times that. Answers with where each neuron settled, in * the order they came. * * `forceX`/`forceY` rather than `forceCenter`: centering only translates the * whole thing, so unconnected flows — which is most of them — would push each * other apart forever with nothing pulling back. */ import { forceCollide, forceLink, forceManyBody, forceSimulation, forceX, forceY, type SimulationNodeDatum, } from "d3-force" export type Seed = SimulationNodeDatum & { id: string; room: number } export type LayoutRequest = { placed: Seed[] links: { source: string; target: string }[] } self.onmessage = ({ data }: MessageEvent) => { const { placed, links } = data forceSimulation(placed) .force( "link", forceLink(links) .id((node) => (node as Seed).id) .distance(130), ) .force("charge", forceManyBody().strength(-320).distanceMax(500)) .force( "collide", forceCollide((node) => node.room), ) // Centring pulls nearly three times as hard vertically as horizontally, so // the graph compresses into a band roughly twice as wide as it is tall // instead of the square an even pull settles into. Coming out of the // simulation rather than out of a transform keeps the circles round and the // edges the length the link force asked for. .force("x", forceX(0).strength(0.05)) .force("y", forceY(0).strength(0.14)) .stop() .tick(300) self.postMessage(placed.map((node) => [node.x ?? 0, node.y ?? 0])) }