Module

Hylograph.ForceEngine.Render

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

Force Simulation Rendering

FFI-optimized helpers for rendering force-directed graphs.

IMPORTANT PATTERN: For simulations, bind data ONCE then update positions via FFI. Do NOT use Tree API or data joins in tick callbacks - they are O(n) per tick.

Correct usage:

-- Define group IDs (type-safe, no typos)
nodesGroup = GroupId "#my-nodes"
linksGroup = GroupId "#my-links"

-- At initialization (once):
nodeSel <- appendData Circle nodes [cx _.x, cy _.y, ...]
linkSel <- appendData Line swizzledLinks [x1 (\l -> l.source.x), ...]

-- In tick callback (every frame):
updateCirclePositions nodesGroup
updateLinkPositions linksGroup

The FFI updates use D3's .attr() method directly on selections, which is O(n) DOM updates with no data join overhead.

#GroupId Source

newtype GroupId

A type-safe wrapper for DOM selector strings.

Use this to identify groups of elements for position updates. The newtype provides compile-time checking that you're using group IDs consistently, without any runtime overhead.

Example:

-- Define once, use everywhere
packageNodes :: GroupId
packageNodes = GroupId "#package-nodes"

moduleNodes :: GroupId
moduleNodes = GroupId "#module-nodes"

Constructors

Instances

#updateCirclePositions Source

updateCirclePositions :: GroupId -> Effect Unit

Update circle positions (cx, cy) from bound data's x, y fields.

Assumes data bound to circles has x :: Number and y :: Number fields. Uses D3's selection.attr() which reads from __data__ on each element.

Example:

nodesGroup :: GroupId
nodesGroup = GroupId "#nodes"

updateCirclePositions nodesGroup

#updateLinkPositions Source

updateLinkPositions :: GroupId -> Effect Unit

Update line positions (x1, y1, x2, y2) from bound data's source/target nodes.

Assumes data bound to lines has:

  • source :: { x :: Number, y :: Number, ... }
  • target :: { x :: Number, y :: Number, ... }

This is the "swizzled link" format where source/target are node objects, not integer indices. Use Sim.getSwizzledLinks after adding a Link force.

Example:

linksGroup :: GroupId
linksGroup = GroupId "#links"

updateLinkPositions linksGroup

#updateGroupPositions Source

updateGroupPositions :: GroupId -> Effect Unit

Update group positions (transform) from bound data's x, y fields.

Selects g.module-pack elements and updates their transform attribute. Assumes data bound to groups has x :: Number and y :: Number fields.

Example:

nodesGroup :: GroupId
nodesGroup = GroupId "#nodes"

updateGroupPositions nodesGroup

#updateTreeLinkPaths Source

updateTreeLinkPaths :: GroupId -> Effect Unit

Update tree link paths (d attribute) from bound data's source/target nodes.

Expects data bound to path elements has:

  • source :: Int (node ID)
  • target :: Int (node ID)

This function looks up node positions from the DOM (circles) and computes vertical bezier paths. Used for animating tree links during transitions.

Example:

linksGroup :: GroupId
linksGroup = GroupId "#explorer-links"

updateTreeLinkPaths linksGroup

#updatePositions Source

updatePositions :: forall a. GroupId -> (a -> Effect Unit) -> Effect Unit

Generic position update with custom attribute setter.

The setter receives the D3 selection and should call .attr() methods.

Example (custom node representation):

rectsGroup :: GroupId
rectsGroup = GroupId "#node-rects"

updatePositions rectsGroup \sel -> do
  sel # setAttr "x" (_.x - 10.0)  -- center rect
  sel # setAttr "y" (_.y - 10.0)