How-To Guides
Self-contained examples. Each one builds from a fresh clone. Demo on the left, source on the right.
src/Main.purs
-- | Axes and Ticks — Scatter plot with X and Y axes using pure scales
module Main where
import Prelude
import Effect (Effect)
import Hylograph.Scale.Pure as Scale
import Hylograph.HATS (Tree, elem, forEach, staticStr)
import Hylograph.HATS.Friendly as F
import Hylograph.HATS.InterpreterTick (rerender)
import Hylograph.Internal.Element.Types (ElementType(..))
type Point = { x :: Number, y :: Number, label :: String }
points :: Array Point
points =
[ { x: 10.0, y: 28.0, label: "A" }, { x: 25.0, y: 62.0, label: "B" }
, { x: 38.0, y: 45.0, label: "C" }, { x: 52.0, y: 80.0, label: "D" }
, { x: 60.0, y: 35.0, label: "E" }, { x: 72.0, y: 90.0, label: "F" }
, { x: 85.0, y: 55.0, label: "G" }, { x: 95.0, y: 72.0, label: "H" }
, { x: 42.0, y: 18.0, label: "I" }, { x: 68.0, y: 68.0, label: "J" }
]
chart :: Tree
chart =
let
ml = 50.0
mt = 10.0
pw = 440.0
ph = 300.0
w = ml + pw + 10.0
h = mt + ph + 40.0
xS = Scale.linear # Scale.domain [0.0, 100.0] # Scale.range [ml, ml + pw]
yS = Scale.linear # Scale.domain [0.0, 100.0] # Scale.range [mt + ph, mt]
xT = Scale.ticks 5 xS
yT = Scale.ticks 5 yS
bY = mt + ph
in
elem SVG
[ F.viewBox 0.0 0.0 w h, F.width w
, F.style "max-width: 520px; font-family: system-ui, sans-serif;"
]
[ forEach "pts" Circle points _.label \pt ->
elem Circle
[ F.cx (Scale.applyScale xS pt.x), F.cy (Scale.applyScale yS pt.y)
, F.r 4.0, F.fill "#2563eb", F.fillOpacity "0.7"
] []
, elem Line
[ F.x1 ml, F.y1 bY, F.x2 (ml + pw), F.y2 bY
, F.stroke "#333", F.strokeWidth 1.0
] []
, forEach "xTicks" Group xT show \tv ->
let px = Scale.applyScale xS tv in
elem Group []
[ elem Line
[ F.x1 px, F.y1 bY, F.x2 px, F.y2 (bY + 6.0)
, F.stroke "#333", F.strokeWidth 1.0
] []
, elem Text
[ F.x px, F.y (bY + 20.0), F.textAnchor "middle"
, F.fontSize "11", F.fill "#555"
, staticStr "textContent" (show tv)
] []
]
, elem Line
[ F.x1 ml, F.y1 mt, F.x2 ml, F.y2 (mt + ph)
, F.stroke "#333", F.strokeWidth 1.0
] []
, forEach "yTicks" Group yT show \tv ->
let py = Scale.applyScale yS tv in
elem Group []
[ elem Line
[ F.x1 (ml - 6.0), F.y1 py, F.x2 ml, F.y2 py
, F.stroke "#333", F.strokeWidth 1.0
] []
, elem Text
[ F.x (ml - 10.0), F.y (py + 4.0), F.textAnchor "end"
, F.fontSize "11", F.fill "#555"
, staticStr "textContent" (show tv)
] []
]
]
main :: Effect Unit
main = do
_ <- rerender "#app" chart
pure unit
src/Main.purs
-- | Color Scales — Linear scales, color interpolators, and tick marks
-- | Demonstrates Hylograph's pure scale system without D3.
module Main where
import Prelude
import Data.Array (range)
import Data.Int (toNumber)
import Effect (Effect)
import Hylograph.Scale.Pure as Scale
import Hylograph.Scale.Sequential (interpolateViridis, interpolateRdYlGn)
import Hylograph.HATS (Tree, elem, forEach, staticStr)
import Hylograph.HATS.Friendly as F
import Hylograph.HATS.InterpreterTick (rerender)
import Hylograph.Internal.Element.Types (ElementType(..))
-- | Build an SVG with two color gradient bars and tick marks
colorScales :: Number -> Tree
colorScales w =
let
n = 100 -- color samples per bar
indices = range 0 (n - 1) <#> \i ->
{ idx: i, t: toNumber i / toNumber (n - 1) }
xScale = Scale.linear # Scale.domain [0.0, toNumber (n - 1)] # Scale.range [0.0, w]
nScale = Scale.linear # Scale.domain [0.0, 1.0] # Scale.range [0.0, w]
ticks = Scale.ticks 10 nScale
barH = 32.0
stripW = w / toNumber n + 1.0
y1 = 10.0 -- first bar top
yT = y1 + barH + 8.0 -- tick area
y2 = yT + 40.0 -- second bar top
-- Helper: one horizontal gradient bar via thin rects
gradientBar name yPos interp =
forEach name Rect indices (show <<< _.idx) \s ->
elem Rect
[ F.x (Scale.applyScale xScale (toNumber s.idx)), F.y yPos
, F.width stripW, F.height barH
, F.fill (interp s.t)
] []
in
elem SVG
[ F.viewBox 0.0 0.0 (w + 20.0) (y2 + barH + 10.0)
, F.width (w + 20.0)
, F.style "max-width: 640px; font-family: system-ui, sans-serif;"
]
[ gradientBar "viridis" y1 interpolateViridis
, forEach "ticks" Group ticks show \tv ->
let px = Scale.applyScale nScale tv in
elem Group []
[ elem Line
[ F.x1 px, F.y1 yT, F.x2 px, F.y2 (yT + 8.0)
, F.stroke "#333", F.strokeWidth 1.0
] []
, elem Text
[ F.x px, F.y (yT + 20.0), F.textAnchor "middle"
, F.fontSize "11", F.fill "#555"
, staticStr "textContent" (show tv)
] []
]
, gradientBar "rdylgn" y2 interpolateRdYlGn
]
main :: Effect Unit
main = do
_ <- rerender "#app" (colorScales 500.0)
pure unit
Coordinated Highlighting
Coordinated Highlighting — hover a category in the key and all
View in repo →
src/Main.purs
-- | Coordinated Highlighting — hover a category in the key and all
-- | matching circles in the scatter plot highlight.
module Main where
import Prelude
import Data.Array (mapWithIndex)
import Data.Int as Int
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Hylograph.HATS (Tree, elem, forEach, staticStr, withBehaviors, onCoordinatedHighlight)
import Hylograph.HATS.Friendly as F
import Hylograph.HATS.InterpreterTick (rerender)
import Hylograph.Internal.Behavior.Types (HighlightClass(..))
import Hylograph.Internal.Element.Types (ElementType(..))
-- Categories for the key
type Category = { name :: String, color :: String, idx :: Int }
categories :: Array Category
categories =
[ { name: "Igneous", color: "#e15759", idx: 0 }
, { name: "Sedimentary", color: "#4e79a7", idx: 1 }
, { name: "Metamorphic", color: "#59a14f", idx: 2 }
, { name: "Volcanic", color: "#f28e2b", idx: 3 }
]
-- Scattered circles, each belonging to a category
type Dot = { x :: Number, y :: Number, r :: Number, category :: String, color :: String, id :: String }
dots :: Array Dot
dots =
[ { x: 45.0, y: 35.0, r: 10.0, category: "Igneous", color: "#e15759", id: "d1" }
, { x: 160.0, y: 55.0, r: 14.0, category: "Sedimentary", color: "#4e79a7", id: "d2" }
, { x: 90.0, y: 120.0, r: 8.0, category: "Metamorphic", color: "#59a14f", id: "d3" }
, { x: 210.0, y: 40.0, r: 11.0, category: "Igneous", color: "#e15759", id: "d4" }
, { x: 130.0, y: 160.0, r: 13.0, category: "Volcanic", color: "#f28e2b", id: "d5" }
, { x: 55.0, y: 190.0, r: 9.0, category: "Sedimentary", color: "#4e79a7", id: "d6" }
, { x: 240.0, y: 130.0, r: 12.0, category: "Metamorphic", color: "#59a14f", id: "d7" }
, { x: 180.0, y: 200.0, r: 10.0, category: "Igneous", color: "#e15759", id: "d8" }
, { x: 30.0, y: 100.0, r: 15.0, category: "Volcanic", color: "#f28e2b", id: "d9" }
, { x: 270.0, y: 80.0, r: 7.0, category: "Sedimentary", color: "#4e79a7", id: "d10" }
, { x: 200.0, y: 170.0, r: 11.0, category: "Volcanic", color: "#f28e2b", id: "d11" }
, { x: 100.0, y: 60.0, r: 9.0, category: "Metamorphic", color: "#59a14f", id: "d12" }
, { x: 150.0, y: 100.0, r: 16.0, category: "Igneous", color: "#e15759", id: "d13" }
, { x: 260.0, y: 190.0, r: 8.0, category: "Sedimentary", color: "#4e79a7", id: "d14" }
, { x: 70.0, y: 155.0, r: 12.0, category: "Volcanic", color: "#f28e2b", id: "d15" }
]
-- | The key: labeled color swatches
keyView :: Tree
keyView =
let swatchH = 28.0
gap = 8.0
in
elem SVG [ F.viewBox 0.0 0.0 140.0 160.0, F.width 140.0, F.height 160.0 ]
[ forEach "key" Group categories _.name \cat ->
let y = 10.0 + Int.toNumber cat.idx * (swatchH + gap)
in withBehaviors
[ onCoordinatedHighlight
{ identify: cat.name
, classify: \hovered -> if hovered == cat.name then Primary else Dimmed
, group: Nothing
}
] $
elem Group [ F.transform ("translate(0," <> show y <> ")") ]
[ elem Rect
[ F.x 0.0, F.y 0.0, F.width 20.0, F.height swatchH
, staticStr "rx" "3"
, F.fill cat.color
] []
, elem Text
[ F.x 28.0, F.y (swatchH / 2.0 + 5.0)
, F.fontSize "13"
, F.fontFamily "system-ui, sans-serif"
, F.fill "#333"
, staticStr "textContent" cat.name
] []
]
]
-- | The scatter: circles colored by category
scatterView :: Tree
scatterView =
elem SVG [ F.viewBox 0.0 0.0 300.0 230.0, F.width 300.0, F.height 230.0 ]
[ forEach "scatter" Group dots _.id \d ->
withBehaviors
[ onCoordinatedHighlight
{ identify: d.category -- identifies as category, not individual dot
, classify: \hovered -> if hovered == d.category then Primary else Dimmed
, group: Nothing
}
] $
elem Group []
[ elem Circle
[ F.cx d.x, F.cy d.y, F.r d.r
, F.fill d.color
, F.opacity "0.85"
] []
]
]
main :: Effect Unit
main = do
_ <- rerender "#key" keyView
_ <- rerender "#scatter" scatterView
pure unit
src/Main.purs
-- | CSS Hover — pure CSS :hover on HATS elements, no JavaScript needed
module Main where
import Prelude
import Effect (Effect)
import Hylograph.HATS (Tree, elem, forEach, staticStr)
import Hylograph.HATS.Friendly as F
import Hylograph.HATS.InterpreterTick (rerender)
import Hylograph.Internal.Element.Types (ElementType(..))
type Dot = { color :: String, cx :: Number }
dots :: Array Dot
dots =
[ { color: "#e15759", cx: 40.0 }
, { color: "#f28e2b", cx: 104.0 }
, { color: "#edc948", cx: 168.0 }
, { color: "#59a14f", cx: 232.0 }
, { color: "#4e79a7", cx: 296.0 }
, { color: "#b07aa1", cx: 360.0 }
]
circles :: Tree
circles =
elem SVG
[ F.viewBox 0.0 0.0 400.0 100.0
, F.style "max-width: 500px;"
]
[ forEach "dots" Circle dots _.color \d ->
elem Circle
[ F.cx d.cx
, F.cy 50.0
, F.r 24.0
, F.fill d.color
, staticStr "class" "hoverable"
] []
]
main :: Effect Unit
main = do
_ <- rerender "#app" circles
pure unit
src/Main.purs
module Main where
import Prelude
import Effect (Effect)
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
import App as App
main :: Effect Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
runUI App.component unit body
src/Main.purs
module Main where
import Prelude
import Effect (Effect)
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
import App as App
main :: Effect Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
runUI App.component unit body
src/Main.purs
-- | Simple Webpage — minimum viable HATS visualization
module Main where
import Prelude
import Effect (Effect)
import Hylograph.HATS (Tree, elem, forEach, staticStr)
import Hylograph.HATS.Friendly as F
import Hylograph.HATS.InterpreterTick (rerender)
import Hylograph.Internal.Element.Types (ElementType(..))
type Bar = { label :: String, h :: Number, x :: Number, color :: String }
bars :: Array Bar
bars =
[ { label: "A", h: 90.0, x: 20.0, color: "#4e79a7" }
, { label: "B", h: 60.0, x: 75.0, color: "#f28e2b" }
, { label: "C", h: 75.0, x: 130.0, color: "#e15759" }
, { label: "D", h: 40.0, x: 185.0, color: "#76b7b2" }
, { label: "E", h: 55.0, x: 240.0, color: "#59a14f" }
]
chart :: Tree
chart =
elem SVG [ F.viewBox 0.0 0.0 300.0 130.0, F.style "max-width: 400px;" ]
[ forEach "bars" Group bars _.label \d ->
elem Group []
[ elem Rect
[ F.x d.x, F.y (100.0 - d.h), F.width 40.0, F.height d.h
, F.fill d.color
] []
, elem Text
[ F.x (d.x + 20.0), F.y 120.0
, F.textAnchor "middle", F.fontSize "12px", F.fill "#333"
, staticStr "textContent" d.label
] []
]
]
main :: Effect Unit
main = do
_ <- rerender "#app" chart
pure unit
src/Main.purs
-- | Tooltips — hover any circle to see a tooltip with its details.
-- |
-- | NOTE: tooltip content is plain text here because the HATS coordinated
-- | tooltip path in hylograph-selection@0.5.0 uses textContent instead of
-- | innerHTML, so HTML tags render as literal text. Fix tracked upstream.
module Main where
import Prelude
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Hylograph.HATS (Tree, elem, forEach, staticStr, withBehaviors, onCoordinatedHighlightWithTooltip)
import Hylograph.HATS.Friendly as F
import Hylograph.HATS.InterpreterTick (rerender)
import Hylograph.Internal.Behavior.Types (HighlightClass(..), TooltipTrigger(..))
import Hylograph.Internal.Element.Types (ElementType(..))
import Hylograph.Tooltip (configureTooltip, lightTheme)
type Planet =
{ name :: String
, radius :: Number
, color :: String
, x :: Number
, y :: Number
, detail :: String
}
planets :: Array Planet
planets =
[ { name: "Mercury", radius: 8.0, color: "#b0b0b0", x: 50.0, y: 120.0, detail: "Closest to the Sun" }
, { name: "Venus", radius: 12.0, color: "#e8c96a", x: 130.0, y: 90.0, detail: "Hottest surface temperature" }
, { name: "Earth", radius: 13.0, color: "#4e9fd5", x: 220.0, y: 100.0, detail: "The only known living world" }
, { name: "Mars", radius: 10.0, color: "#d4644a", x: 310.0, y: 130.0, detail: "The Red Planet" }
, { name: "Jupiter", radius: 28.0, color: "#d4a65a", x: 430.0, y: 100.0, detail: "Largest planet in the system" }
, { name: "Saturn", radius: 24.0, color: "#e8d88c", x: 550.0, y: 110.0, detail: "Famous for its rings" }
]
view :: Tree
view =
elem SVG [ F.viewBox 0.0 0.0 640.0 240.0, F.width 640.0, F.height 240.0 ]
[ forEach "planets" Group planets _.name \d ->
withBehaviors
[ onCoordinatedHighlightWithTooltip
{ identify: d.name
, classify: \hovered ->
if hovered == d.name then Primary else Dimmed
, group: Nothing
, tooltip: Just
{ content: d.name <> " — " <> d.detail
, showWhen: OnHover
, borderColor: Just d.color
}
}
] $
elem Group []
[ elem Circle
[ F.cx d.x, F.cy d.y, F.r d.radius
, F.fill d.color
, F.opacity "0.9"
] []
, elem Text
[ F.x d.x, F.y (d.y + d.radius + 16.0)
, F.fontSize "11"
, F.fontFamily "system-ui, sans-serif"
, F.fill "#555"
, F.textAnchor "middle"
, staticStr "textContent" d.name
] []
]
]
main :: Effect Unit
main = do
configureTooltip lightTheme
_ <- rerender "#chart" view
pure unit
src/Main.purs
-- | Zoom and Pan — Minimal HATS demo
-- |
-- | Renders a 10x10 grid of circles, then attaches native zoom/pan behavior.
-- | The zoom applies to a <g class="zoom-group"> wrapping all content.
module Main where
import Prelude
import Data.Array ((..))
import Data.Int (toNumber)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Hylograph.HATS (Tree, elem, staticStr, staticNum, forEach)
import Hylograph.HATS.InterpreterTick (rerender)
import Hylograph.Interaction.Zoom (attachZoomNative)
import Hylograph.Internal.Element.Types (ElementType(..))
import Web.DOM.ParentNode (querySelector, QuerySelector(..))
import Web.HTML (window)
import Web.HTML.Window (document)
import Web.HTML.HTMLDocument (toParentNode)
-- | Grid datum: row and column index
type Dot = { row :: Int, col :: Int }
-- | Generate a 10x10 grid of dots
dots :: Array Dot
dots = do
row <- 0 .. 9
col <- 0 .. 9
pure { row, col }
-- | Build the HATS tree: an SVG containing a zoom-group with circles
circleGrid :: Tree
circleGrid =
elem SVG
[ staticStr "viewBox" "0 0 500 500"
, staticStr "width" "500"
, staticStr "height" "500"
, staticStr "style" "background: #fff;"
]
-- Wrap all content in a <g class="zoom-group"> so zoom can target it
[ elem Group [ staticStr "class" "zoom-group" ]
[ forEach "dots" Circle dots
(\d -> show d.row <> "," <> show d.col)
\d ->
elem Circle
[ staticNum "cx" (toNumber d.col * 45.0 + 50.0)
, staticNum "cy" (toNumber d.row * 45.0 + 50.0)
, staticNum "r" 12.0
, staticStr "fill" "#4e79a7"
, staticStr "opacity" "0.7"
] []
]
]
main :: Effect Unit
main = do
-- 1. Render the circle grid into #app
_ <- rerender "#app" circleGrid
-- 2. Find the SVG element we just created
doc <- window >>= document
mSvg <- querySelector (QuerySelector "#app svg") (toParentNode doc)
-- 3. Attach zoom: scale 0.1x to 10x, targeting the .zoom-group <g>
case mSvg of
Just svg -> do
_ <- attachZoomNative svg 0.1 10.0 ".zoom-group"
pure unit
Nothing ->
pure unit
