Module

Hylograph.ForceEngine.Core

Package
purescript-hylograph-simulation
Repository
afcondon/purescript-hylograph-simulation

Pure Force Engine Core

This module provides the core force simulation loop. We use D3's force calculation algorithms but manage the simulation ourselves.

Key principle: Forces are just functions that mutate vx/vy on nodes. We control when they run and how alpha decays.

#ForceHandle Source

#createManyBody Source

createManyBody :: ManyBodyConfig -> ForceHandle

Create a many-body (charge) force

#createCollide Source

createCollide :: CollideConfig -> ForceHandle

Create a collision force

#createCenter Source

createCenter :: CenterConfig -> ForceHandle

Create a centering force

#createForceX Source

createForceX :: ForceXConfig -> ForceHandle

Create an X positioning force

#createForceY Source

createForceY :: ForceYConfig -> ForceHandle

Create a Y positioning force

#createRadial Source

createRadial :: RadialConfig -> ForceHandle

Create a radial force

#createManyBodyFiltered Source

createManyBodyFiltered :: forall node. ManyBodyFilteredConfig node -> ForceHandle

Create a many-body force that only applies to nodes matching a predicate Useful for applying charge only to certain node types (e.g., tree parents)

#createRadialFiltered Source

createRadialFiltered :: forall node. RadialFilteredConfig node -> ForceHandle

Create a radial force that only applies to nodes matching a predicate

#createCollideDynamic Source

createCollideDynamic :: forall node. CollideDynamicConfig node -> ForceHandle

Create a collision force with dynamic radius per-node The radiusAccessor function is called for each node to determine collision radius Example: { radiusAccessor: \n -> n.r + 5.0, strength: 1.0, iterations: 1 }

#createForceXDynamic Source

createForceXDynamic :: forall node. ForceXDynamicConfig node -> ForceHandle

Create an X positioning force with dynamic target per-node The xAccessor function is called for each node to determine target X Useful for clustering (e.g., modules toward their parent package's X)

#createForceYDynamic Source

createForceYDynamic :: forall node. ForceYDynamicConfig node -> ForceHandle

Create a Y positioning force with dynamic target per-node

#createLinkDynamic Source

createLinkDynamic :: forall link. LinkDynamicConfig link -> ForceHandle

Create a link force with dynamic strength per-link The strengthAccessor function is called for each link to determine strength Useful for encoding link weight as force strength Example: { distance: 30.0, strengthAccessor: \link -> link.weight, iterations: 1 }

#createForceXGrid Source

createForceXGrid :: Number -> ForceHandle

Create an X positioning force that reads node.gridX directly Much faster than createForceXDynamic because it avoids FFI callbacks. Nodes must have a gridX :: Number field.

#createForceYGrid Source

createForceYGrid :: Number -> ForceHandle

Create a Y positioning force that reads node.gridY directly Much faster than createForceYDynamic because it avoids FFI callbacks. Nodes must have a gridY :: Number field.

#createCollideGrid Source

createCollideGrid :: Number -> Number -> Int -> ForceHandle

Create a collision force that reads node.r directly Much faster than createCollideDynamic because it avoids FFI callbacks. Nodes must have an r :: Number field (radius). Parameters: padding, strength, iterations

#initializeNodes Source

initializeNodes :: forall r. Array (Record r) -> Effect Unit

Initialize nodes with indices and default velocities This mutates the nodes array to add index, vx, vy if missing

#initializeForce Source

initializeForce :: forall r. ForceHandle -> Array (Record r) -> Effect ForceHandle

Initialize a force with nodes Must be called before applying the force

#initializeLinkForce Source

initializeLinkForce :: forall nodeRow linkRow. ForceHandle -> Array (Record nodeRow) -> Array (Record linkRow) -> Effect ForceHandle

Initialize a link force with nodes and links Link forces need both nodes and links

#applyForce Source

applyForce :: ForceHandle -> Number -> Effect Unit

Apply a single force This mutates vx/vy on the nodes the force was initialized with

#applyForces Source

applyForces :: Array ForceHandle -> Number -> Effect Unit

Apply multiple forces in sequence

#integratePositions Source

integratePositions :: forall r. Array (Record r) -> Number -> Effect Unit

Integrate positions: apply velocity decay and update positions Call this once per tick after all forces have been applied

#decayAlpha Source

decayAlpha :: Number -> Number -> Number -> Number -> Number

Calculate new alpha value (the "cooling" step) Returns 0 if alpha falls below alphaMin

#simulationTick Source

simulationTick :: forall r. { alpha :: Number, alphaDecay :: Number, alphaMin :: Number, alphaTarget :: Number, forces :: Array ForceHandle, nodes :: Array (Record r), velocityDecay :: Number } -> Effect Number

Complete simulation tick:

  1. Apply all forces
  2. Integrate positions
  3. Return new alpha

#AnimationHandle Source

type AnimationHandle = Effect Unit

Handle to a running animation (can be used to stop it)

#startAnimation Source

startAnimation :: (Number -> Effect Boolean) -> Effect AnimationHandle

Start an animation loop The callback receives the current timestamp and should return whether to continue

#stopAnimation Source

stopAnimation :: AnimationHandle -> Effect Unit

Stop a running animation

#attachDragWithReheat Source

attachDragWithReheat :: Array Element -> Effect Unit -> Effect Unit

Attach drag with a reheat callback

#attachGroupDragWithReheat Source

attachGroupDragWithReheat :: Array Element -> String -> Effect Unit -> Effect Unit

Attach drag to transformed group elements (like bubble packs)

Unlike attachDragWithReheat, this version takes a container selector to get pointer coordinates in the correct coordinate space. Use this when dragging <g> elements that have transform attributes.

Example:

-- For bubble packs inside a zoom group
attachGroupDragWithReheat packElements "#zoom-group" (Sim.reheat sim)

#attachPinningDrag Source

attachPinningDrag :: Array Element -> Effect Unit -> Effect Unit

Attach drag with toggle-pinning behavior

Unlike attachDragWithReheat, this version keeps nodes pinned after drag ends.

  • First drag: pins the node where you drop it
  • Subsequent drags: if you barely move (< 3px), unpins the node

This is useful for allowing users to "fix" certain nodes in place while letting others float freely in the simulation.

Example:

nodeElements <- getElementsByClassName "node"
attachPinningDrag nodeElements (Sim.reheat sim)

#querySelectorElements Source

querySelectorElements :: String -> Effect (Array Element)

Query DOM elements by CSS selector

Returns all elements matching the given CSS selector. Useful for getting element references for drag behavior attachment.

Example:

nodeCircles <- querySelectorElements "#my-graph circle.node"
attachPinningDrag nodeCircles (Sim.reheat sim)

#logNodes Source

logNodes :: forall r. String -> Array (Record r) -> Effect Unit

Log node positions for debugging