Module

DataViz.Layout.Hierarchy.Tree

Package
purescript-hylograph-layout
Repository
afcondon/purescript-hylograph-layout

DataViz.Layout.Hierarchy.Tree

Reingold-Tilford tree layout adapted from the elegant Haskell implementation Translates binary tree algorithm to n-ary trees (Data.Tree)

#TreeConfig Source

type TreeConfig a = { layerScale :: Maybe (Int -> Number), layerSeparation :: Maybe Number, minSeparation :: Number, separation :: Maybe (a -> a -> Number), size :: { height :: Number, width :: Number } }

Configuration for tree layout

The separation function controls horizontal spacing between adjacent nodes. It receives the node data for two adjacent nodes and returns the desired separation. Default behavior uses minSeparation for all nodes.

The layerScale function controls vertical spacing between depth levels. It receives the depth (0 = root) and returns a scale factor for that level. Default is identity (linear scaling).

Example for "flatter at top" effect:

config { layerScale = \depth -> toNumber depth ** 1.5 }

#defaultTreeConfig Source

defaultTreeConfig :: forall a. TreeConfig a

Default configuration

#withLayerScale Source

withLayerScale :: forall a. (Int -> Number) -> TreeConfig a -> TreeConfig a

Configuration with custom layer scaling

Common patterns:

  • \d -> toNumber d ** 1.5 - compressed at top, expanded at bottom
  • \d -> log (toNumber d + 1.0) - very flat at top
  • \d -> sqrt (toNumber d) - moderate compression at top

#withLayerSeparation Source

withLayerSeparation :: forall a. Number -> TreeConfig a -> TreeConfig a

Configuration with fixed layer separation in pixels

When set, layers are spaced by a fixed pixel amount rather than being normalized to fit within the configured height. This gives consistent vertical spacing regardless of tree depth.

Example for 60px between layers:

withLayerSeparation 60.0 config

#withSeparation Source

withSeparation :: forall a. (a -> a -> Number) -> TreeConfig a -> TreeConfig a

Configuration with custom separation function

Example for radius-based separation:

withSeparation (\a b -> (a.radius + b.radius) / 2.0 + 1.0) config

#Contour Source

type Contour = List Number

A contour is a list of offsets at each depth level Represents the left or right edge of a subtree

#Contours Source

data Contours

Contours for a subtree: left edge and right edge at each depth

Constructors

#tree Source

tree :: forall r. TreeConfig { depth :: Int, x :: Number, y :: Number | r } -> Tree { depth :: Int, x :: Number, y :: Number | r } -> Tree { depth :: Int, x :: Number, y :: Number | r }

Tree layout in 3 steps:

  1. render: Bottom-up pass computing relative positions (distances from parent)
  2. petrify: Top-down pass converting distances to absolute (x,y) coordinates
  3. scale: Scale abstract coordinates to pixel coordinates

Uses Tuple annotation to temporarily hold offset values during layout computation Input must have x, y, depth fields (initial values don't matter, they'll be overwritten)

#treeWithSorting Source

treeWithSorting :: forall r. TreeConfig { depth :: Int, height :: Int, x :: Number, y :: Number | r } -> Tree { depth :: Int, height :: Int, x :: Number, y :: Number | r } -> Tree { depth :: Int, height :: Int, x :: Number, y :: Number | r }

Tree layout with height-based sorting for consistent ordering with Cluster Use this variant when your tree has height computed and you want sorted children

#NodeSize Source

type NodeSize = { height :: Number, width :: Number }

Node size for layout calculations Width affects horizontal spacing (contours track edges, not centers) Height affects vertical layer spacing (each layer uses max height at that depth)

#treeSized Source

treeSized :: forall r. TreeConfig { depth :: Int, x :: Number, y :: Number | r } -> ({ depth :: Int, x :: Number, y :: Number | r } -> NodeSize) -> Tree { depth :: Int, x :: Number, y :: Number | r } -> Tree { depth :: Int, x :: Number, y :: Number | r }

Size-aware tree layout for heterogeneous nodes

Unlike the standard tree function which assumes uniform node sizes, this variant uses a sizing function to query each node's dimensions.

Key differences from standard layout:

  • Contours track node edges (center ± width/2), not centers
  • Layer y-positions based on accumulated max heights, not uniform spacing
  • minSeparation becomes pure gap between node edges

Example for a tree with circles and tables:

treeSized config nodeSize myTree
  where
  nodeSize node = case node.shape of
    Circle { radius } -> { width: radius * 2.0, height: radius * 2.0 }
    Table { rows } -> { width: 50.0 * numCols, height: 20.0 * numRows }