Module

Hylograph.AST

Package
purescript-hylograph-selection
Repository
afcondon/purescript-hylograph-selection

PSD3.AST - Abstract Syntax Tree for Visualization Specifications

This module provides the declarative API for building visualizations. An AST node describes WHAT to render, and interpreters decide HOW to render it.

Usage

import Hylograph.AST as A
import Hylograph.Render (runD3, select, renderTree)

myChart :: A.AST DataPoint
myChart =
  A.named SVG "svg" [width 800.0, height 600.0]
    `A.withChildren`
      [ A.joinData "circles" "circle" myData $ \d ->
          A.elem Circle [cx d.x, cy d.y, radius 5.0, fill d.color]
      ]

main = void $ runD3 do
  container <- select "#chart"
  renderTree container myChart

Interpreters

The same AST can be interpreted multiple ways:

  • PSD3.Render - Renders to DOM via D3.js
  • PSD3.Interpreter.Mermaid - Generates Mermaid diagram of structure
  • PSD3.Interpreter.English - Produces English description (debugging)

Key Types

  • AST datum / Tree datum - A visualization specification parameterized by datum type
  • ASTNode datum / TreeNode datum - A node in the AST
  • PhaseBehavior, GUPBehaviors - GUP phase specifications

Smart Constructors

  • named - Create a named element (can be retrieved after rendering)
  • elem - Create an anonymous element
  • withChild, withChildren - Add children to a node
  • withBehaviors - Attach zoom, drag, click handlers

Data Joins

  • joinData - Simple data join (datum type stays same)
  • nestedJoin - Data join with type decomposition
  • updateJoin - Data join with enter/update/exit behaviors
  • updateNestedJoin - Type decomposition + GUP (recommended for most cases)

#AST Source

type AST datum = Tree datum

AST - Abstract Syntax Tree for visualization specifications

An AST describes the structure of a visualization declaratively. It can be interpreted by multiple backends (D3 DOM, Mermaid, English, etc.)

AST and Tree are synonyms - use whichever reads better in your code.

#ASTNode Source

type ASTNode datum = TreeNode datum

ASTNode - A node in the visualization AST

Contains:

  • name: Optional identifier for later retrieval
  • elemType: Element type (SVG, Circle, Rect, etc.)
  • attrs: Attributes to apply
  • behaviors: Attached behaviors (zoom, drag, etc.)
  • children: Child nodes

ASTNode and TreeNode are synonyms.

#Tree Source

data Tree datum

A tree is either a regular node or a data join point

Constructors

  • Node (TreeNode datum)
  • Join { joinData :: Array datum, key :: String, name :: String, template :: datum -> Tree datum }

    DataJoin creates N copies of a template tree, one per data item This is how we handle enter/update/exit with the declarative API

    The join itself is a named node in the tree - it represents the COLLECTION of elements created from the data.

  • NestedJoin { decompose :: datum -> Array datum, joinData :: Array datum, key :: String, name :: String, template :: datum -> Tree datum }

    NestedJoin allows datum type to change during decomposition Used for nested data structures like 2D arrays, arrays of records with arrays, etc.

    Uses unsafeCoerce internally to handle type changing. This enables patterns like: Array (Array a) → table rows → table cells

    SAFETY: The decompose and template functions are provided together by nestedJoin, ensuring type safety at the call site. Internally we erase the inner type.

  • UpdateJoin { behaviors :: GUPBehaviors datum, joinData :: Array datum, key :: String, keyFn :: Maybe (datum -> String), name :: String, template :: datum -> Tree datum }

    UpdateJoin handles General Update Pattern (GUP) with enter/update/exit

    This variant declaratively specifies behavior for all three phases:

    • Enter: New data items appearing (with optional transition)
    • Update: Existing data items that remain (with optional transition)
    • Exit: Old data items being removed (with optional transition)

    The interpreter handles all the complexity of:

    • Computing the join (determining enter/update/exit sets)
    • Applying the correct template and attributes to each set
    • Running transitions
    • Removing exited elements
  • UpdateNestedJoin { behaviors :: GUPBehaviors datum, decompose :: datum -> Array datum, joinData :: Array datum, key :: String, name :: String, template :: datum -> Tree datum }

    UpdateNestedJoin combines NestedJoin's type decomposition with UpdateJoin's GUP behaviors

    This is the IDEAL variant for most use cases - it allows:

    1. Type transitions (e.g., SceneData -> Array DataPoint)
    2. Full GUP with enter/update/exit behaviors

    Example: Container holds scene data, decompose extracts array of items, each item gets enter/update/exit transitions.

    Uses unsafeCoerce internally to handle type changing (same as NestedJoin).

  • ConditionalRender { cases :: Array { predicate :: datum -> Boolean, spec :: datum -> Tree datum } }

    ConditionalRender enables chimeric visualizations by rendering different specs based on data predicates.

    This is the foundation for "structural exception handling" - using different visual representations for different structural patterns in the data.

    Example: Sankey-Network chimera

    conditionalRender
      [ { predicate: isCyclicCluster, spec: \d -> forceNetworkSpec d }
      , { predicate: isNormalNode, spec: \d -> sankeyNodeSpec d }
      ]
    

    The first matching predicate determines which spec to render. If no predicates match, no element is rendered (could be extended with fallback).

  • LocalCoordSpace { child :: Tree datum, scaleX :: datum -> Number, scaleY :: datum -> Number }

    LocalCoordSpace creates a nested coordinate system for embedded visualizations.

    This enables chimeric visualizations where some nodes contain entire embedded visualizations (like a Sankey node that contains a force network).

    The child tree is rendered in its own coordinate space, isolated from the parent. Transform configuration specifies how to map between parent and child coordinates.

    Example: Embedded network in Sankey node

    localCoordSpace
      { scaleX: \d -> 100.0  -- child width
      , scaleY: \d -> 100.0  -- child height
      }
      forceNetworkTree
    

Instances

  • TreeDSL Tree

    TreeDSL instance for Tree.

    This makes the Tree ADT usable with the finally-tagless TreeDSL interface. User code can be written against TreeDSL tree => and work with any interpreter, including this one that builds a concrete Tree.

#TreeNode Source

type TreeNode datum = { attrs :: Array (Attribute datum), behaviors :: Array (Behavior datum), children :: Array (Tree datum), elemType :: ElementType, name :: Maybe String }

A node in the visualization tree

  • name: Optional name to retrieve this selection later (Nothing = anonymous)
  • elemType: What kind of element (SVG, Group, Circle, etc.)
  • attrs: Attributes to set on this element
  • behaviors: Behaviors to attach (zoom, drag, click handlers, etc.)
  • children: Child nodes

#PhaseBehavior Source

type PhaseBehavior datum = { attrs :: Array (Attribute datum), transition :: Maybe TransitionConfig }

Unified behavior specification for any GUP phase (enter, update, or exit)

Each phase can specify:

  • attrs: Attributes to apply for this phase
  • transition: Optional animation configuration

For enter: attrs are initial state before animating to template attrs For update: attrs override template attrs, then animate For exit: attrs applied before animating out and removing

#GUPBehaviors Source

type GUPBehaviors datum = { enter :: Maybe (PhaseBehavior datum), exit :: Maybe (PhaseBehavior datum), update :: Maybe (PhaseBehavior datum) }

Complete GUP (General Update Pattern) behavior specification

Bundles enter/update/exit behaviors together. Each phase is optional - if Nothing, the phase uses default behavior (no special attrs, no animation).

#named Source

named :: forall datum. ElementType -> String -> Array (Attribute datum) -> Tree datum

Smart constructors Create a named element

Usage: named SVG "svg" [width 800, height 600]

#elem Source

elem :: forall datum. ElementType -> Array (Attribute datum) -> Tree datum

Create an anonymous element (won't be in the returned selections)

Usage: elem Group [class_ "container"]

#withChild Source

withChild :: forall datum. Tree datum -> Tree datum -> Tree datum

Add a single child to a tree node

Usage: parent withChild child

#withChildren Source

withChildren :: forall datum. Tree datum -> Array (Tree datum) -> Tree datum

Add multiple children to a tree node

Usage: parent withChildren [child1, child2, child3]

#withBehaviors Source

withBehaviors :: forall datum. Tree datum -> Array (Behavior datum) -> Tree datum

Add behaviors to a tree node

Behaviors are attached to the element after creation. This enables declarative specification of zoom, drag, click handlers, etc.

Usage:

named SVG "svg" [width 800, height 600]
  `withBehaviors` [Zoom $ defaultZoom (ScaleExtent 0.1 10.0) ".zoom-group"]
  `withChildren` [...]

Multiple behaviors can be attached:

elem Circle [radius 5.0]
  `withBehaviors` [Drag SimpleDrag, onClickWithDatum \d -> log d.name]

#joinData Source

joinData :: forall datum. String -> String -> Array datum -> (datum -> Tree datum) -> Tree datum

Create a named data join

The join itself becomes a named selection representing the COLLECTION of elements created from the data.

Usage:

joinData "nodes" "g" nodeData $ \node ->
  elem Group [transform (translate node)] `withChildren`
    [ elem Circle [radius node.r]
    , elem Text [textContent node.name]
    ]

Later you can access the collection:

case Map.lookup "nodes" selections of
  Just nodeGroups -> addTickFunction "nodes" $ Step nodeGroups [...]

#nestedJoin Source

nestedJoin :: forall outerDatum innerDatum. String -> String -> Array outerDatum -> (outerDatum -> Array innerDatum) -> (innerDatum -> Tree innerDatum) -> Tree outerDatum

Create a nested data join with decomposition

This allows the datum type to change at each level of nesting. The decomposer extracts inner collections from outer data items.

Usage:

-- 2D array → table
nestedJoin "rows" "tr" matrixData identity $ \rowData ->
  nestedJoin "cells" "td" [rowData] identity $ \cellValue ->
    elem Td [textContent (show cellValue)]

Or more commonly:

-- Array of records with nested arrays
nestedJoin "groups" "g" groups (_.items) $ \item ->
  elem Circle [cx item.x, cy item.y]

#updateJoin Source

updateJoin :: forall datum. String -> String -> Array datum -> (datum -> Tree datum) -> { enter :: Maybe (PhaseBehavior datum), exit :: Maybe (PhaseBehavior datum), keyFn :: Maybe (datum -> String), update :: Maybe (PhaseBehavior datum) } -> Tree datum

Create an update join with General Update Pattern behavior

This is the declarative way to specify enter/update/exit behavior. The interpreter handles all the complexity of computing joins and applying transitions.

Usage:

updateJoin "nodes" "circle" nodeData
  (\node -> elem Circle [ cx node.x, cy node.y, radius 5.0 ])
  { enter: Just { attrs: [ y 0.0, opacity 0.0 ], transition: Just slideDown }
  , update: Just { attrs: [], transition: Just moveToPosition }
  , exit: Just { attrs: [ class_ "exit" ], transition: Just fadeOut }
  , keyFn: Just _.id  -- Optional key function for identity matching
  }

#updateNestedJoin Source

updateNestedJoin :: forall outerDatum innerDatum. String -> String -> Array outerDatum -> (outerDatum -> Array innerDatum) -> (innerDatum -> Tree innerDatum) -> GUPBehaviors innerDatum -> Tree outerDatum

Create an update nested join with type decomposition and GUP behaviors

This is the RECOMMENDED way to use UpdateJoin - it combines type decomposition with enter/update/exit behaviors, solving the type mixing problem.

Usage:

-- Container has SceneData, decompose to DataPoints, each gets GUP
updateNestedJoin "circles" "circle"
  [sceneData]              -- Outer data (SceneData)
  (_.points)               -- Decompose: SceneData -> Array DataPoint
  (\point -> elem Circle   -- Template for each DataPoint
    [ cx point.x
    , cy point.y
    ])
  { enter: Just { attrs: [radius 0.0], transition: Just fadeIn }
  , update: Just { attrs: [], transition: Just move }
  , exit: Just { attrs: [], transition: Just fadeOut }
  }

#conditionalRender Source

conditionalRender :: forall datum. Array { predicate :: datum -> Boolean, spec :: datum -> Tree datum } -> Tree datum

Create a conditional render that chooses specs based on predicates

This is the foundation for chimeric visualizations - rendering different visual representations based on data properties.

Usage:

conditionalRender
  [ { predicate: \d -> d.nodeType == "cluster"
    , spec: \d -> forceNetworkSpec d
    }
  , { predicate: \d -> d.nodeType == "normal"
    , spec: \d -> normalNodeSpec d
    }
  ]

The first matching predicate determines which spec to render. If no predicates match, nothing is rendered.

#conditionalRenderOr Source

conditionalRenderOr :: forall datum. Array { predicate :: datum -> Boolean, spec :: datum -> Tree datum } -> (datum -> Tree datum) -> Tree datum

Combinator version that takes a fallback spec for unmatched cases

Usage:

conditionalRenderOr
  [ { predicate: isCluster, spec: clusterSpec }
  , { predicate: isSpecial, spec: specialSpec }
  ]
  defaultSpec  -- Used when no predicate matches

#localCoordSpace Source

localCoordSpace :: forall datum. { scaleX :: datum -> Number, scaleY :: datum -> Number } -> Tree datum -> Tree datum

Create a local coordinate space for embedded visualizations

This enables nesting one visualization inside another with its own coordinate system.

Usage:

localCoordSpace
  { scaleX: \d -> d.width   -- Local coordinate width
  , scaleY: \d -> d.height  -- Local coordinate height
  }
  embeddedVizTree

#localCoordSpaceFixed Source

localCoordSpaceFixed :: forall datum. Number -> Number -> Tree datum -> Tree datum

Simplified version with fixed dimensions

Usage:

localCoordSpaceFixed 100.0 100.0 embeddedVizTree

#beside Source

beside :: forall datum. Tree datum -> Tree datum -> Array (Tree datum)

Combine two trees as siblings Returns an array, meant to be used with withChildren

Usage: group withChildren (circle +: text)

#siblings Source

siblings :: forall datum. Array (Tree datum) -> Array (Tree datum)

Helper to combine multiple siblings

Usage: siblings [child1, child2, child3]

#(>:) Source

Operator alias for Hylograph.AST.withChild (left-associative / precedence 6)

Operators for Emmet-style syntax

#(+:) Source

Operator alias for Hylograph.AST.beside (left-associative / precedence 5)

Re-exports from Hylograph.Internal.Selection.Types

#ElementType Source

data ElementType

Element types organized by rendering context

This ADT makes the distinction between SVG and HTML elements explicit, allowing the type system to guide proper namespace handling.

Constructors

Instances

Re-exports from Hylograph.TreeDSL

#TreeDSL Source

class TreeDSL :: (Type -> Type) -> Constraintclass TreeDSL (tree :: Type -> Type) 

The core tree-building DSL.

tree is a type constructor: tree :: Type -> Type where the parameter is the datum type.

Implementations include:

  • Tree from PSD3.AST (builds a data structure)
  • Direct interpreters (could render immediately)
  • Analysis interpreters (could extract metadata)
Modules
Data.DependencyGraph
Hylograph.AST
Hylograph.Axis.Axis
Hylograph.Brush
Hylograph.Brush.FFI
Hylograph.Brush.Types
Hylograph.Classify
Hylograph.Data.Graph
Hylograph.Data.Graph.Algorithms
Hylograph.Data.Node
Hylograph.Data.Tree
Hylograph.Expr.Animation
Hylograph.Expr.Attr
Hylograph.Expr.Datum
Hylograph.Expr.Expr
Hylograph.Expr.Friendly
Hylograph.Expr.Integration
Hylograph.Expr.Interpreter.CodeGen
Hylograph.Expr.Interpreter.Eval
Hylograph.Expr.Interpreter.Meta
Hylograph.Expr.Interpreter.PureSVG
Hylograph.Expr.Interpreter.SVG
Hylograph.Expr.Path
Hylograph.Expr.Path.Generators
Hylograph.Expr.Sugar
Hylograph.Expr.Units
Hylograph.HATS
Hylograph.HATS.Friendly
Hylograph.HATS.InterpreterTick
Hylograph.HATS.Transitions
Hylograph.Interaction.Brush
Hylograph.Interaction.Coordinated
Hylograph.Interaction.Pointer
Hylograph.Interaction.Zoom
Hylograph.Internal.Attribute
Hylograph.Internal.Behavior.FFI
Hylograph.Internal.Behavior.Types
Hylograph.Internal.Capabilities.Selection
Hylograph.Internal.Capabilities.Transition
Hylograph.Internal.FFI
Hylograph.Internal.Selection.Join
Hylograph.Internal.Selection.Operations
Hylograph.Internal.Selection.Operations.Conversions
Hylograph.Internal.Selection.Operations.Helpers
Hylograph.Internal.Selection.Operations.Selection
Hylograph.Internal.Selection.Query
Hylograph.Internal.Selection.Types
Hylograph.Internal.Transition.FFI
Hylograph.Internal.Transition.Manager
Hylograph.Internal.Transition.Scene
Hylograph.Internal.Transition.Types
Hylograph.Internal.Types
Hylograph.Interpreter.D3
Hylograph.Interpreter.English
Hylograph.Interpreter.Mermaid
Hylograph.Interpreter.MetaAST
Hylograph.Interpreter.SemiQuine
Hylograph.Interpreter.SemiQuine.TreeToCode
Hylograph.Interpreter.SemiQuine.Types
Hylograph.Render
Hylograph.Scale
Hylograph.Scale.FP
Hylograph.Shape.Arc
Hylograph.Shape.Pie
Hylograph.Shape.Polygon
Hylograph.Tooltip
Hylograph.Transform
Hylograph.TreeDSL
Hylograph.TreeDSL.ShapeTree
Hylograph.Unified
Hylograph.Unified.Attribute
Hylograph.Unified.DataDSL
Hylograph.Unified.Display
Hylograph.Unified.Examples
Hylograph.Unified.Join
Hylograph.Unified.Sugar