Files
typlisp/visualization.md
2026-01-30 09:11:34 -10:00

11 KiB

Visualization & Diagrams

Three levels of visualization in Typst, all mappable to hiccup.


1. Built-in Shapes

Typst has basic shapes built-in. Simple and direct.

Rectangle

Clojure:

[:rect {:width "2cm" :height "1cm" :fill "blue"} "Label"]

Typst:

#rect(width: 2cm, height: 1cm, fill: blue)[Label]

Square

Clojure:

[:square {:size "1cm" :stroke "2pt + red"}]

Typst:

#square(size: 1cm, stroke: 2pt + red)

Circle

Clojure:

[:circle {:radius "0.5cm" :fill "green"}]

Typst:

#circle(radius: 0.5cm, fill: green)

Ellipse

Clojure:

[:ellipse {:width "2cm" :height "1cm"}]

Typst:

#ellipse(width: 2cm, height: 1cm)

Polygon

Clojure:

[:polygon {:fill "yellow"} [0 0] [1 0] [0.5 1]]

Typst:

#polygon(fill: yellow, (0pt, 0pt), (1pt, 0pt), (0.5pt, 1pt))

Line

Clojure:

[:line {:start [0 0] :end [100 50] :stroke "2pt"}]

Typst:

#line(start: (0pt, 0pt), end: (100pt, 50pt), stroke: 2pt)

Path

Clojure:

[:path {:closed true :fill "blue"} [0 0] [10 0] [10 10] [0 10]]

Typst:

#path(closed: true, fill: blue, (0pt, 0pt), (10pt, 0pt), (10pt, 10pt), (0pt, 10pt))

Curve (Bézier)

Clojure:

[:curve
  [:move [0 0]]
  [:cubic [10 20] [30 20] [40 0]]]

Typst:

#curve(
  curve.move((0pt, 0pt)),
  curve.cubic((10pt, 20pt), (30pt, 20pt), (40pt, 0pt)),
)

2. CeTZ - Canvas Drawing

For complex custom drawings. Like TikZ for LaTeX.

Basic Canvas

Clojure:

[:cetz/canvas
  [:cetz/line [0 0] [2 2]]
  [:cetz/circle [1 1] {:radius 0.5}]
  [:cetz/rect [0 0] [2 1] {:fill "blue"}]]

Typst:

#import "@preview/cetz:0.4.2"

#cetz.canvas({
  import cetz.draw: *

  line((0, 0), (2, 2))
  circle((1, 1), radius: 0.5)
  rect((0, 0), (2, 1), fill: blue)
})

Line with Multiple Points

Clojure:

[:cetz/line [0 0] [1 0] [1 1] [0 1] {:close true}]

Typst:

line((0, 0), (1, 0), (1, 1), (0, 1), close: true)

Arc

Clojure:

[:cetz/arc [0 0] {:start 0 :stop 90 :radius 1}]

Typst:

arc((0, 0), start: 0deg, stop: 90deg, radius: 1)

Bezier Curve

Clojure:

[:cetz/bezier [0 0] [1 1] [0.5 2] [0.5 -1]]

Typst:

bezier((0, 0), (1, 1), (0.5, 2), (0.5, -1))

Content Label in Canvas

Clojure:

[:cetz/content [1 1] "Label here"]
[:cetz/content [0 0] [:strong "Bold label"]]

Typst:

content((1, 1), [Label here])
content((0, 0), [#strong[Bold label]])

Architecture Boxes Example

Clojure:

(defn box-with-label [pos size label]
  (let [[x y] pos
        [w h] size
        cx (+ x (/ w 2))
        cy (+ y (/ h 2))]
    (list
      [:cetz/rect pos [(+ x w) (+ y h)] {:fill "lightblue" :stroke "black"}]
      [:cetz/content [cx cy] label])))

[:cetz/canvas
  (box-with-label [0 0] [3 2] "Frontend")
  (box-with-label [5 0] [3 2] "Backend")
  (box-with-label [10 0] [3 2] "Database")
  [:cetz/line [3 1] [5 1] {:mark {:end ">"}}]
  [:cetz/line [8 1] [10 1] {:mark {:end ">"}}]]

Typst:

#cetz.canvas({
  import cetz.draw: *

  // Frontend
  rect((0, 0), (3, 2), fill: lightblue, stroke: black)
  content((1.5, 1), [Frontend])

  // Backend
  rect((5, 0), (8, 2), fill: lightblue, stroke: black)
  content((6.5, 1), [Backend])

  // Database
  rect((10, 0), (13, 2), fill: lightblue, stroke: black)
  content((11.5, 1), [Database])

  // Arrows
  line((3, 1), (5, 1), mark: (end: ">"))
  line((8, 1), (10, 1), mark: (end: ">"))
})

3. Fletcher - Diagrams & Flowcharts

High-level diagram DSL. Best for architecture, flowcharts, state machines.

Basic Diagram

Clojure:

[:fletcher/diagram {:node-stroke "1pt" :spacing "2em"}
  [:node [0 0] "Start"]
  [:edge "->"]
  [:node [1 0] "Process"]
  [:edge "->"]
  [:node [2 0] "End"]]

Typst:

#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge

#diagram(
  node-stroke: 1pt,
  spacing: 2em,
  node((0, 0), [Start]),
  edge("-|>"),
  node((1, 0), [Process]),
  edge("-|>"),
  node((2, 0), [End]),
)

Node Shapes

Clojure:

[:node [0 0] "Default"]
[:node [1 0] "Circle" {:shape :circle}]
[:node [2 0] "Diamond" {:shape :diamond}]
[:node [3 0] "Pill" {:shape :pill}]
[:node [4 0] "Hexagon" {:shape :hexagon}]
[:node [5 0] "Cylinder" {:shape :cylinder}]

Typst:

node((0, 0), [Default]),
node((1, 0), [Circle], shape: circle),
node((2, 0), [Diamond], shape: fletcher.shapes.diamond),
node((3, 0), [Pill], shape: fletcher.shapes.pill),
node((4, 0), [Hexagon], shape: fletcher.shapes.hexagon),
node((5, 0), [Cylinder], shape: fletcher.shapes.cylinder),

Edge Styles

Clojure:

[:edge "->"]       ; simple arrow
[:edge "->>"]      ; double arrow
[:edge "--"]       ; no arrow (line)
[:edge "<->"]      ; bidirectional
[:edge "hook->"]   ; hook arrow
[:edge "|->"]      ; maps-to arrow

Typst:

edge("-|>"),
edge("-|>|>"),
edge("--"),
edge("<|-|>"),
edge("hook-|>"),
edge("|-|>"),

Directional Edges

Clojure:

[:edge "r" "->"]    ; go right
[:edge "d" "->"]    ; go down
[:edge "u" "->"]    ; go up
[:edge "l" "->"]    ; go left
[:edge "r,d" "->"]  ; right then down

Typst:

edge("r", "-|>"),
edge("d", "-|>"),
edge("u", "-|>"),
edge("l", "-|>"),
edge("r,d", "-|>"),

Edge with Label

Clojure:

[:edge [0 0] [1 0] "->" {:label "HTTP" :label-pos 0.5}]

Typst:

edge((0, 0), (1, 0), "-|>", label: [HTTP], label-pos: 0.5),

Curved Edge

Clojure:

[:edge [0 0] [1 0] "->" {:bend 20}]

Typst:

edge((0, 0), (1, 0), "-|>", bend: 20deg),

Flowchart Example

Clojure:

[:fletcher/diagram {:node-stroke "1pt" :node-fill "white" :spacing "3em"}
  ;; Nodes
  [:node [0 0] "Start" {:shape :pill}]
  [:node [0 1] "Input Data"]
  [:node [0 2] "Valid?" {:shape :diamond}]
  [:node [1 2] "Error" {:fill "red.lighten(80%)"}]
  [:node [0 3] "Process"]
  [:node [0 4] "End" {:shape :pill}]

  ;; Edges
  [:edge [0 0] [0 1] "->"]
  [:edge [0 1] [0 2] "->"]
  [:edge [0 2] [1 2] "->" {:label "no"}]
  [:edge [0 2] [0 3] "->" {:label "yes"}]
  [:edge [1 2] [0 1] "->" {:bend -40}]
  [:edge [0 3] [0 4] "->"]]

Typst:

#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge

#diagram(
  node-stroke: 1pt,
  node-fill: white,
  spacing: 3em,

  // Nodes
  node((0, 0), [Start], shape: fletcher.shapes.pill),
  node((0, 1), [Input Data]),
  node((0, 2), [Valid?], shape: fletcher.shapes.diamond),
  node((1, 2), [Error], fill: red.lighten(80%)),
  node((0, 3), [Process]),
  node((0, 4), [End], shape: fletcher.shapes.pill),

  // Edges
  edge((0, 0), (0, 1), "-|>"),
  edge((0, 1), (0, 2), "-|>"),
  edge((0, 2), (1, 2), "-|>", label: [no]),
  edge((0, 2), (0, 3), "-|>", label: [yes]),
  edge((1, 2), (0, 1), "-|>", bend: -40deg),
  edge((0, 3), (0, 4), "-|>"),
)

Architecture Diagram Example

Clojure:

[:fletcher/diagram {:spacing "4em" :node-stroke "1pt"}
  ;; Frontend layer
  [:node [0 0] "Web App" {:fill "blue.lighten(80%)"}]
  [:node [1 0] "Mobile App" {:fill "blue.lighten(80%)"}]

  ;; API Gateway
  [:node [0.5 1] "API Gateway" {:shape :hexagon :fill "green.lighten(80%)"}]

  ;; Services
  [:node [0 2] "Auth Service" {:shape :pill}]
  [:node [1 2] "User Service" {:shape :pill}]
  [:node [2 2] "Order Service" {:shape :pill}]

  ;; Data layer
  [:node [0 3] "PostgreSQL" {:shape :cylinder}]
  [:node [1 3] "Redis" {:shape :cylinder :fill "red.lighten(80%)"}]
  [:node [2 3] "MongoDB" {:shape :cylinder :fill "green.lighten(80%)"}]

  ;; Connections
  [:edge [0 0] [0.5 1] "->"]
  [:edge [1 0] [0.5 1] "->"]
  [:edge [0.5 1] [0 2] "->"]
  [:edge [0.5 1] [1 2] "->"]
  [:edge [0.5 1] [2 2] "->"]
  [:edge [0 2] [0 3] "->"]
  [:edge [1 2] [1 3] "->"]
  [:edge [2 2] [2 3] "->"]]

Typst:

#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge

#diagram(
  spacing: 4em,
  node-stroke: 1pt,

  // Frontend layer
  node((0, 0), [Web App], fill: blue.lighten(80%)),
  node((1, 0), [Mobile App], fill: blue.lighten(80%)),

  // API Gateway
  node((0.5, 1), [API Gateway], shape: fletcher.shapes.hexagon, fill: green.lighten(80%)),

  // Services
  node((0, 2), [Auth Service], shape: fletcher.shapes.pill),
  node((1, 2), [User Service], shape: fletcher.shapes.pill),
  node((2, 2), [Order Service], shape: fletcher.shapes.pill),

  // Data layer
  node((0, 3), [PostgreSQL], shape: fletcher.shapes.cylinder),
  node((1, 3), [Redis], shape: fletcher.shapes.cylinder, fill: red.lighten(80%)),
  node((2, 3), [MongoDB], shape: fletcher.shapes.cylinder, fill: green.lighten(80%)),

  // Connections
  edge((0, 0), (0.5, 1), "-|>"),
  edge((1, 0), (0.5, 1), "-|>"),
  edge((0.5, 1), (0, 2), "-|>"),
  edge((0.5, 1), (1, 2), "-|>"),
  edge((0.5, 1), (2, 2), "-|>"),
  edge((0, 2), (0, 3), "-|>"),
  edge((1, 2), (1, 3), "-|>"),
  edge((2, 2), (2, 3), "-|>"),
)

4. DSL Helpers

Build higher-level abstractions on top.

Clojure:

(defn service [id name & {:keys [x y color] :or {color "white"}}]
  [:node [x y] name {:id id :shape :pill :fill color}])

(defn database [id name & {:keys [x y color] :or {color "gray.lighten(80%)"}}]
  [:node [x y] name {:id id :shape :cylinder :fill color}])

(defn connects [from to & {:keys [label style] :or {style "->"}}]
  [:edge from to style (when label {:label label})])

[:fletcher/diagram {:spacing "3em"}
  (service :auth "Auth" :x 0 :y 0)
  (service :users "Users" :x 1 :y 0)
  (database :pg "PostgreSQL" :x 0 :y 1)
  (database :redis "Redis" :x 1 :y 1 :color "red.lighten(80%)")

  (connects :auth :pg)
  (connects :users :redis :label "cache")]

Typst:

#diagram(
  spacing: 3em,
  node((0, 0), [Auth], shape: fletcher.shapes.pill, fill: white),
  node((1, 0), [Users], shape: fletcher.shapes.pill, fill: white),
  node((0, 1), [PostgreSQL], shape: fletcher.shapes.cylinder, fill: gray.lighten(80%)),
  node((1, 1), [Redis], shape: fletcher.shapes.cylinder, fill: red.lighten(80%)),

  edge((0, 0), (0, 1), "-|>"),
  edge((1, 0), (1, 1), "-|>", label: [cache]),
)

Summary

Use Case Tool Hiccup Prefix
Simple shapes in content Built-in :rect, :circle, :line
Custom drawings, illustrations CeTZ :cetz/canvas, :cetz/line
Flowcharts, architecture Fletcher :fletcher/diagram, :node, :edge

Sources