Hylograph.Simulation
- Package
- purescript-hylograph-simulation
- Repository
- afcondon/purescript-hylograph-simulation
Hylograph Force Simulation
High-level API for running force-directed simulations. Supports both D3.js and WASM physics engines with the same interface.
Quick Start
import Hylograph.Simulation (runSimulation, Engine(..), SimulationEvent(..))
import Hylograph.Simulation.Emitter (subscribe)
main = do
-- Run simulation - returns handle AND event emitter
{ handle, events } <- runSimulation
{ engine: D3 -- or WASM (same config!)
, setup: setup "physics"
[ manyBody "charge" # withStrength (static (-30.0))
, center "center"
]
, nodes: myNodes
, links: []
, container: "#my-svg"
, alphaMin: 0.001
}
-- Subscribe to events (framework-agnostic)
unsubscribe <- subscribe events \event -> case event of
Tick { alpha } -> log $ "Alpha: " <> show alpha
Completed -> log "Simulation converged!"
_ -> pure unit
-- Get nodes for rendering (use HATS or your own rendering)
nodes <- handle.getNodes
-- Update data (GUP semantics, auto-reheat)
result <- handle.updateData newNodes newLinks
Framework Integration
Halogen: Use toHalogenEmitter from Hylograph.Simulation.Halogen
React: Use subscribe in a useEffect hook with cleanup
Vanilla: Just call subscribe directly
#runSimulation Source
runSimulation :: forall r. SimulationConfig r -> Effect (SimulationResult r)Run a force simulation with Hylograph visualization.
Returns both a handle for controlling the simulation AND an event emitter for subscribing to simulation events. This design is framework-agnostic:
- The emitter works with Halogen, React, or vanilla JS
- Events include Tick, Started, Stopped, and Completed
- The same code works for both D3 and WASM engines Note: No Ord constraint required! We use renderTreeKeyed with a key function based on node.id, which doesn't require Ord on the datum type. This allows SimulationNode r (an extensible record) to work without needing Ord derivation.
#SimulationResult Source
type SimulationResult :: Row Type -> Typetype SimulationResult r = { events :: SimulationEmitter, handle :: SimulationHandle r }
Result returned by runSimulation
handle: Control the simulation (update data, stop, start, etc.)events: Subscribe to simulation events (Tick, Completed, etc.)
#SimulationConfig Source
type SimulationConfig :: Row Type -> Typetype SimulationConfig r = { alphaMin :: Number, container :: String, engine :: Engine, links :: Array { source :: Int, target :: Int }, nodes :: Array (SimulationNode r), setup :: Setup (SimulationNode r) }
Configuration for running a simulation
Note: There's no onComplete callback - use the events emitter instead.
This keeps the API framework-agnostic.
Rendering is handled externally via the Tick event and handle.getNodes.
Use HATS or any other rendering approach to render node positions on each tick.
#SimulationHandle Source
type SimulationHandle :: Row Type -> Typetype SimulationHandle r = { getAlpha :: Effect Number, getNodes :: Effect (Array (SimulationNode r)), interpolatePositions :: PositionMap -> PositionMap -> Number -> Effect Unit, pinNodes :: Effect Unit, reheat :: Effect Unit, start :: Effect Unit, stop :: Effect Unit, unpinNodes :: Effect Unit, updateData :: Array (SimulationNode r) -> Array { source :: Int, target :: Int } -> Effect { links :: GUPLinkResult (), nodes :: GUPResult (SimulationNode r) }, updateSetup :: Setup (SimulationNode r) -> Effect Unit }
Handle for controlling the simulation
#PositionMap Source
type PositionMap = PositionMapPosition map for interpolation - keyed by node ID (as string)
Re-exports from Hylograph.ForceEngine.Setup
#Setup Source
type Setup node = { forces :: Array (ForceConfig node), name :: String, params :: SetupParams }Complete setup: forces + simulation params
#GUPResult Source
type GUPResult node = { entered :: Array node, exited :: Array node, updated :: Array node }Result of a node data update with enter/update/exit categorization
- entered: Nodes newly added to simulation (use for enter animations)
- updated: Existing nodes that were modified (simulation state preserved)
- exited: Nodes removed from simulation (use for exit animations)
#GUPLinkResult Source
type GUPLinkResult :: Row Type -> Typetype GUPLinkResult linkRow = { entered :: Array { source :: NodeID, target :: NodeID | linkRow }, exited :: Array { source :: NodeID, target :: NodeID | linkRow }, updated :: Array { source :: NodeID, target :: NodeID | linkRow } }
Result of a link data update with enter/update/exit categorization Links are keyed by (source, target) pair
#withY Source
withY :: forall node. Value node Number -> ForceConfig node -> ForceConfig nodeSet Y position/target (for Center, PositionY, Radial)
#withX Source
withX :: forall node. Value node Number -> ForceConfig node -> ForceConfig nodeSet X position/target (for Center, PositionX, Radial)
#withTheta Source
withTheta :: forall node. Number -> ForceConfig node -> ForceConfig nodeSet theta (Barnes-Hut approximation, for ManyBody)
#withStrength Source
withStrength :: forall node. Value node Number -> ForceConfig node -> ForceConfig nodeSet strength (works for all force types)
#withRadius Source
withRadius :: forall node. Value node Number -> ForceConfig node -> ForceConfig nodeSet radius (for Collide, Radial)
#withIterations Source
withIterations :: forall node. Int -> ForceConfig node -> ForceConfig nodeSet iterations (for Collide, Link)
#withFilter Source
withFilter :: forall node. (node -> Boolean) -> ForceConfig node -> ForceConfig nodeAdd a filter predicate (force only applies to matching nodes)
#withDistanceMin Source
withDistanceMin :: forall node. Number -> ForceConfig node -> ForceConfig nodeSet distance min (for ManyBody)
#withDistanceMax Source
withDistanceMax :: forall node. Number -> ForceConfig node -> ForceConfig nodeSet distance max (for ManyBody)
#withDistance Source
withDistance :: forall node. Value node Number -> ForceConfig node -> ForceConfig nodeSet distance (for Link)
#setup Source
setup :: forall node. String -> Array (ForceConfig node) -> Setup nodeCreate a setup with default params
#radial Source
radial :: forall node. String -> ForceConfig nodeCreate a radial force Default: radius 100, center (0,0), strength 0.1
#positionY Source
positionY :: forall node. String -> ForceConfig nodeCreate a Y-positioning force Default: y = 0, strength 0.1
#positionX Source
positionX :: forall node. String -> ForceConfig nodeCreate an X-positioning force Default: x = 0, strength 0.1
#manyBody Source
manyBody :: forall node. String -> ForceConfig nodeCreate a many-body (charge) force Default: repulsive with strength -30
#link Source
link :: forall node. String -> ForceConfig nodeCreate a link (spring) force Default: distance 30, strength 1
#collide Source
collide :: forall node. String -> ForceConfig nodeCreate a collision force Default: radius 1, strength 1
#center Source
center :: forall node. String -> ForceConfig nodeCreate a centering force Default: center at (0, 0), strength 1
Re-exports from Hylograph.ForceEngine.Simulation
#SimulationNode Source
type SimulationNode :: Row Type -> Typetype SimulationNode r = Record (D3_ID + D3_XY + D3_VxyFxy + r)
A simulation node with all required fields for force simulation and transitions. Extends user data row with id, position (x/y), velocity (vx/vy), and fixed position (fx/fy).
Example:
type MyNode = SimulationNode (name :: String, group :: Int)
-- Expands to: { id :: Int, x, y, vx, vy, fx, fy, name :: String, group :: Int }
Re-exports from Hylograph.Simulation.Emitter
#SimulationEvent Source
data SimulationEventEvent types emitted by simulations
These are the same regardless of whether D3 or WASM is running the physics.
Constructors
Instances
#SimulationEmitter Source
newtype SimulationEmitterFramework-agnostic event emitter
This is intentionally opaque - use subscribe to listen for events.
The internal representation is a list of listeners with unique IDs.
#subscribe Source
subscribe :: SimulationEmitter -> (SimulationEvent -> Effect Unit) -> Effect UnsubscribeSubscribe to events
Returns an unsubscribe function that removes the listener.
unsubscribe <- subscribe emitter \event -> case event of
Tick { alpha } -> log $ "Alpha: " <> show alpha
Completed -> log "Simulation converged!"
_ -> pure unit
-- Later, to stop listening:
unsubscribe
- Modules
- Hylograph.
Config. Apply - Hylograph.
Config. Force - Hylograph.
Config. Scene - Hylograph.
ForceEngine - Hylograph.
ForceEngine. Core - Hylograph.
ForceEngine. Demo - Hylograph.
ForceEngine. Events - Hylograph.
ForceEngine. Links - Hylograph.
ForceEngine. Registry - Hylograph.
ForceEngine. Render - Hylograph.
ForceEngine. Setup - Hylograph.
ForceEngine. Setup. WASM - Hylograph.
ForceEngine. Simulation - Hylograph.
ForceEngine. Types - Hylograph.
ForceEngine. WASM - Hylograph.
ForceEngine. WASMEngine - Hylograph.
Scene. Engine - Hylograph.
Scene. Handle - Hylograph.
Scene. Rules - Hylograph.
Scene. Types - Hylograph.
Simulation - Hylograph.
Simulation. Emitter - Hylograph.
Simulation. HATS - Hylograph.
Simulation. Scene - Hylograph.
Transition. Consumers - Hylograph.
Transition. Example