55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
/**
|
|
* 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<LayoutRequest>) => {
|
|
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<Seed>((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]))
|
|
}
|