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.jsPSD3.Interpreter.Mermaid- Generates Mermaid diagram of structurePSD3.Interpreter.English- Produces English description (debugging)
Key Types
AST datum/Tree datum- A visualization specification parameterized by datum typeASTNode datum/TreeNode datum- A node in the ASTPhaseBehavior,GUPBehaviors- GUP phase specifications
Smart Constructors
named- Create a named element (can be retrieved after rendering)elem- Create an anonymous elementwithChild,withChildren- Add children to a nodewithBehaviors- Attach zoom, drag, click handlers
Data Joins
joinData- Simple data join (datum type stays same)nestedJoin- Data join with type decompositionupdateJoin- Data join with enter/update/exit behaviorsupdateNestedJoin- Type decomposition + GUP (recommended for most cases)
#ASTNode Source
type ASTNode datum = TreeNode datumASTNode - A node in the visualization AST
Contains:
name: Optional identifier for later retrievalelemType: Element type (SVG, Circle, Rect, etc.)attrs: Attributes to applybehaviors: Attached behaviors (zoom, drag, etc.)children: Child nodes
ASTNode and TreeNode are synonyms.
#Tree Source
data Tree datumA 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 }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:
- Type transitions (e.g., SceneData -> Array DataPoint)
- 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
#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 phasetransition: 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).
#elem Source
elem :: forall datum. ElementType -> Array (Attribute datum) -> Tree datumCreate an anonymous element (won't be in the returned selections)
Usage: elem Group [class_ "container"]
#withChildren Source
withChildren :: forall datum. Tree datum -> Array (Tree datum) -> Tree datumAdd multiple children to a tree node
Usage: parent withChildren [child1, child2, child3]
#withBehaviors Source
withBehaviors :: forall datum. Tree datum -> Array (Behavior datum) -> Tree datumAdd 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 datumCreate 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 outerDatumCreate 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 datumCreate 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 outerDatumCreate 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 datumCreate 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 datumCombinator 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 datumCreate 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 datumSimplified version with fixed dimensions
Usage:
localCoordSpaceFixed 100.0 100.0 embeddedVizTree
#(>:) Source
Operator alias for Hylograph.AST.withChild (left-associative / precedence 6)
Operators for Emmet-style syntax
Re-exports from Hylograph.Internal.Selection.Types
#ElementType Source
data ElementTypeElement 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:
Treefrom 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
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.