Module

Hylograph.Unified.Join

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

PSD3.Unified.Join - Composable Join Combinators

This module decomposes PSD3's four Join constructors into orthogonal, composable pieces. Instead of a Cartesian product of features:

Join              = basic
NestedJoin        = basic + decompose
UpdateJoin        = basic + GUP
UpdateNestedJoin  = basic + decompose + GUP

We have independent combinators that compose:

join data template                           -- basic
withDecompose decomp (join data template)    -- + decomposition
withGUP behaviors (join data template)       -- + GUP
withGUP behaviors (withDecompose decomp ...) -- + both

Benefits

  1. Extensibility: Add new features without exponential constructors
  2. Composability: Mix and match features freely
  3. Clarity: Each combinator does one thing
  4. Unification: Same patterns work in viz and spreadsheet contexts

Migration

Old code using Join constructors continues to work unchanged. New code can use the composable combinators.

See PSD3.Unified.Join.Compat for automatic migration shims.

#JoinSpec Source

data JoinSpec outer inner

A join specification: data + template + config

The outer/inner type parameters allow type-changing joins. For simple joins where outer = inner, they'll be the same.

Constructors

#JoinConfig Source

type JoinConfig outer inner = { decompose :: Maybe (Decomposer outer inner), gup :: Maybe (GUPSpec inner), key :: String, keyFn :: Maybe (inner -> String), name :: String }

Full join configuration

This captures ALL the information needed to perform any kind of join, with optional decomposition and GUP behaviors.

#join Source

join :: forall datum. String -> String -> Array datum -> (datum -> String) -> (datum -> Tree datum) -> JoinSpec datum datum

Create a basic join specification with key function

This is the foundation - data, a name, keyFn for identity, and a template. Decomposition and GUP can be added with combinators.

The key function extracts identity for matching in updates. This is mandatory because dynamic updates need stable identity matching.

join "nodes" "g" nodeData _.id $ \node ->
  elem Group [...]

#withDecompose Source

withDecompose :: forall outer inner. (outer -> Array inner) -> JoinSpec outer outer -> JoinSpec outer inner

Add decomposition to a join

Decomposition extracts inner data from outer data, enabling type changes. This replaces NestedJoin.

join "rows" "tr" tableData identity
  # withDecompose (_.cells)  -- Extract cells from each row

Or with type change:

join "circles" "circle" [sceneData] identity
  # withDecompose (_.points)  -- SceneData -> Array DataPoint

#Decomposer Source

type Decomposer outer inner = outer -> Array inner

A decomposer extracts inner data from outer data

Example: _.points :: SceneData -> Array DataPoint

#withGUP Source

withGUP :: forall outer inner. GUPSpec inner -> JoinSpec outer inner -> JoinSpec outer inner

Add GUP (General Update Pattern) behaviors to a join

GUP specifies enter/update/exit behaviors with optional transitions. This replaces UpdateJoin.

join "circles" "circle" circleData $ \d -> ...
  # withGUP
      { enter: Just { attrs: [opacity 0.0], transition: Just fadeIn }
      , update: Just { attrs: [], transition: Just move }
      , exit: Just { attrs: [opacity 0.0], transition: Just fadeOut }
      }

#GUPSpec Source

type GUPSpec datum = { enter :: Maybe (PhaseSpec datum), exit :: Maybe (PhaseSpec datum), update :: Maybe (PhaseSpec datum) }

Complete GUP specification with all three phases

Each phase is optional - if Nothing, default behavior is used.

#PhaseSpec Source

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

Configuration for a single GUP phase (enter, update, or exit)

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

#enterSpec Source

enterSpec :: forall datum. Array (Attribute datum) -> Maybe TransitionConfig -> PhaseSpec datum

Create an enter phase specification

#updateSpec Source

updateSpec :: forall datum. Array (Attribute datum) -> Maybe TransitionConfig -> PhaseSpec datum

Create an update phase specification

#exitSpec Source

exitSpec :: forall datum. Array (Attribute datum) -> Maybe TransitionConfig -> PhaseSpec datum

Create an exit phase specification

#noTransition Source

noTransition :: forall datum. Array (Attribute datum) -> PhaseSpec datum

Phase with no transition (immediate attribute application)

#JoinBuilder Source

type JoinBuilder outer inner = { data_ :: Array outer, decompose :: Maybe (outer -> Array inner), gup :: Maybe (GUPSpec inner), key :: String, keyFn :: Maybe (inner -> String), name :: String }

Builder type for constructing joins step by step

#buildJoin Source

buildJoin :: forall outer inner. JoinBuilder outer inner -> (inner -> Tree inner) -> JoinSpec outer inner

Build a join from a builder spec

This is the low-level API for full control.

#basicJoin Source

basicJoin :: forall datum. String -> String -> Array datum -> (datum -> String) -> (datum -> Tree datum) -> JoinSpec datum datum

Basic join (equivalent to old Join constructor)

#nestedJoin Source

nestedJoin :: forall outer inner. String -> String -> Array outer -> (outer -> Array inner) -> (inner -> String) -> (inner -> Tree inner) -> JoinSpec outer inner

Nested join with decomposition (equivalent to old NestedJoin)

#gupJoin Source

gupJoin :: forall datum. String -> String -> Array datum -> (datum -> String) -> (datum -> Tree datum) -> GUPSpec datum -> JoinSpec datum datum

GUP join (equivalent to old UpdateJoin)

#fullJoin Source

fullJoin :: forall outer inner. String -> String -> Array outer -> (outer -> Array inner) -> (inner -> String) -> (inner -> Tree inner) -> GUPSpec inner -> JoinSpec outer inner

Full join with decomposition and GUP (equivalent to old UpdateNestedJoin)

#toTree Source

toTree :: forall outer inner. JoinSpec outer inner -> Tree outer

Convert a JoinSpec to a Tree (execute the join specification)

This is the final step - it produces the AST node that interpreters (D3, Mermaid, etc.) can process.

myTree :: Tree DataPoint
myTree = join "circles" "circle" dataPoints circleTemplate
  # withGUP myGUP
  # toTree

SAFETY: The unsafeCoerce calls in toTree are safe because:

  1. Type erasure for existentials: PureScript lacks existential types, so we erase the inner datum type when storing decompose/template/gup functions. The functions were provided together at the call site (join/withDecompose/withGUP), guaranteeing they agree on the inner type.

  2. outer = inner when no decompose: Without a decompose function, the join operates on outer directly (JoinSpec outer outer). The coercions are identity in this case.

  3. AST stores erased types: The AST nodes (NestedJoin, UpdateNestedJoin) also use type erasure internally - they recover types via the stored functions at runtime.

Re-exports from Hylograph.Internal.Transition.Types

#TransitionConfig Source

type TransitionConfig = { delay :: Maybe Milliseconds, duration :: Milliseconds, easing :: Maybe Easing, staggerDelay :: Maybe Number }

Configuration for a transition

Specifies timing and easing for animated attribute changes. All times are in milliseconds.

For staggered transitions, use staggerDelay to add incremental delay per element: element i gets delay = baseDelay + (i * staggerDelay)

Example:

config = transition (Milliseconds 1500.0)
configWithDelay = transitionWith
  { duration: Milliseconds 1500.0
  , delay: Just (Milliseconds 500.0)
  , staggerDelay: Nothing
  , easing: Just Linear
  }
staggeredConfig = transitionWith
  { duration: Milliseconds 600.0
  , delay: Nothing
  , staggerDelay: Just 100.0  -- 100ms between each element
  , easing: Nothing
  }
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