Fossil Forum
Post: getting AI to generate sensible Pikchr
I've distilled this documentation to get AI to stop failing at Pikchr generation. The end of the document has specific prompts that may be more focused for particular issues. Maybe the official Fossil docs may be enhanced with some of this.
Pikchr distilled
1. Core model and script structure
Pikchr is a PIC-like DSL that emits SVG, and a script is a flat sequence of statements separated by newlines or ;. The language is single-pass, so an object can only refer to previously defined objects, and its geometry is fixed when the statement ends except for grouped container behavior.
Example:
right
line
box "Hello," "World!"
arrow
2. Shapes and object classes
Main built-ins include box, circle, ellipse, oval, cylinder, file, diamond, plus path-like objects such as line, arrow, spline, and arc, and point/text objects such as dot and text.
Representative example from the manual family of examples:
box "box"
circle "circle" at 1 right of previous
ellipse "ellipse" at 1 right of previous
oval "oval" at 1 right of previous
cylinder "cylinder" at .8 below first box
file "file" at 1 right of previous
diamond "diamond" at 1 right of previous
3. Automatic layout and move
Automatic layout with right, down, left, and up is useful for small diagrams. For larger technical diagrams, explicit placement is usually more reliable than long chains of implicit stacking.
Safe use of move:
right
box "A"
move
circle "B"
move
cylinder "C"
4. Anchors, coordinates, and placement
Every block object has the nine primary anchors .n, .ne, .e, .se, .s, .sw, .w, .nw, and .c. These anchors should be used explicitly for nontrivial connections because they encode the intended relation directly.
Anchor example:
A: box
dot color red at A.nw ".nw " rjust above
dot same at A.w ".w " rjust
dot same at A.sw ".sw " rjust below
dot same at A.s ".s" below
dot same at A.se " .se" ljust below
dot same at A.e " .e" ljust
dot same at A.ne " .ne" ljust above
dot same at A.n ".n" above
dot same at A.c " .c" ljust
Correct placement patterns:
For serious diagrams, a coordinate-based form is often clearer:
A: box "A" fit at (0,0)
B: box "B" fit at (3cm,0)
arrow from A.e to B.w
5. Relative movement for paths
Paths can use from, to, up, down, left, right, then, and until even with. The reliable pattern is to anchor the start explicitly and then write the route explicitly.
Example:
B1: box "SRC" fit at (0,0)
C1: box "DST" fit at (4cm,-2cm)
arrow from B1.s \
down 1cm \
then right until even with C1 \
then to C1.w
6. Sizing and fit
Objects support wid, ht, rad, and diameter, and fit resizes a block to contain its text. This is usually the right default for boxes and circles carrying labels.
boxwid = 0
boxht = 0
box "Hello"
move
box "A longer label" "with more text"
Variable names should begin with a lowercase letter, $, or @, so boxwid is valid but BoxWid is not.
7. Stroke, fill, and color
Useful style controls include thickness, thick, thin, invisible, solid, color, and fill.
box "normal"
move
box "thin" thin
move
box "thick" thick
move
box "filled" color White fill RoyalBlue fit
8. Text annotations
Objects can carry up to five strings, with modifiers such as above, below, ljust, rjust, small, big, bold, italic, and mono.
line wid 200% \
"ljust" ljust above \
"rjust" rjust below
dot color red at previous.c
9. Labels and robustness
Labels are strongly preferred over ordinal references such as last circle because they remain stable as the script evolves. A label starts with a capital letter and prefixes the object definition directly.
Do not try to reposition an already-created object later using a standalone Label: at ... form; attach at ... to the object definition itself.
10. Practical technical patterns
Simple pipeline / dataflow
A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
C: box "Host" fit at (6cm,0)
arrow from A.e to B.w
arrow from B.e to C.w
Bus with branch arrows
Bus: line from (-3cm,0) to (3cm,0) "I2C bus" above
Device1: box "Device 1" fit at (-1.5cm,1.6cm)
Device2: box "Device 2" fit at ( 1.5cm,1.6cm)
arrow from Device1.s to (-1.5cm,0)
arrow from Device2.s to ( 1.5cm,0)
Routed L-shaped connection
SRC: box "SRC" fit at (0,0)
DST: box "DST" fit at (4cm,-2cm)
arrow from SRC.e \
right 1cm \
then down 2cm \
then to DST.n
Dimensioned side-by-side objects
B1: box "A" fit at (0,0)
C1: circle "B" fit at (2cm,0)
X1: line thin color gray from (B1.w.x,-0.8cm) to (B1.w.x,-0.2cm)
X2: line thin color gray from (C1.e.x,-0.8cm) to (C1.e.x,-0.2cm)
arrow <-> thin color gray from (B1.w.x,-0.5cm) to (C1.e.x,-0.5cm) "2cm" above
Radial hub-and-spoke pattern
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm, 0.00cm)
Cog7: circle "COG7" fit at (-2.30cm, 2.30cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w to Cog6.e
arrow from Hub.nw to Cog7.se
11. Embedding note
In Markdown and Fossil workflows, Pikchr is typically embedded as a fenced code block and rendered by the host environment.
``` pikchr
A: box "Input" fit at (0,0)
B: box "Formatter" fit at (3cm,0)
arrow from A.e to B.w
# Pikchr templates
## 1. Core syntax tokens
| Token / form | Role | Minimal example |
| --- | --- | --- |
| `box` | Rectangular node | `box "MCU" fit` |
| `circle` | Circle node | `circle "IRQ" fit` |
| `ellipse` | Elliptical node | `ellipse "State" fit` |
| `oval` | Oval node | `oval "Loop" fit` |
| `cylinder` | DB/storage | `cylinder "DB" fit` |
| `file` | File/doc node | `file "config.json" fit` |
| `diamond` | Decision node | `diamond "?" fit` |
| `line` | Line segment/polyline | `line right 2cm` |
| `arrow` | Line with arrow head | `arrow from A.e to B.w` |
| `spline` | Curved path | `spline from A.e go right 2cm` |
| `arc` | Circular arc | `arc from 1cm right of A.e` |
| `dot` | Point marker | `dot at A.c` |
| `text` | Stand-alone text | `text "Note" at 5mm above A.n` |
| `right` | Set flow dir to right | `right; box "A"; box "B"` |
| `left` | Set flow dir to left | `left; box "A"; box "B"` |
| `down` | Set flow dir to down | `down; box "A"; box "B"` |
| `up` | Set flow dir to up | `up; box "A"; box "B"` |
| `move` | Invisible spacer | `box; move; box` |
| `from` | Start of path | `arrow from A.e to B.w` |
| `to` | End of path | `arrow from A.s to B.n` |
| `then` | Continue path | `arrow from A.e right 1cm then down 1cm then to B.n` |
| `go` | First segment in path | `line from A.e go right 2cm` |
| `heading` | Polar direction | `line go 3cm heading 150` |
| `until even with` | Axis alignment helper | `right until even with B` |
| `at` | Position current object | `box "A" at (0,0)` |
| `with .anchor` | Position by anchor | `circle with .w at 1cm right of B.e` |
| `.n/.s/.e/.w` | Cardinal anchors | `arrow from A.e to B.w` |
| `.ne/.nw/.se/.sw` | Diagonal anchors | `arrow from A.ne to B.sw` |
| `.c` | Center anchor | `dot at A.c` |
| `.start/.end` | Path endpoints | `text "len" at <L.start,L.end>` |
| `wid` / `width` | Object width | `box "A" wid 2cm` |
| `ht` / `height` | Object height | `box "A" ht 1cm` |
| `rad` / `radius` | Corner radius | `box "A" rad 4pt` |
| `diameter` | Circle size | `circle diameter 1cm` |
| `%` on size | Relative resize | `box wid 125% ht 80%` |
| `fit` | Auto-size to text | `box "Label" fit` |
| `"text"` | Annotation string | `box "CPU" "Core0" fit` |
| `above` | Text above object | `arrow "IRQ" above` |
| `below` | Text below object | `arrow "ack" below` |
| `ljust` | Left-justify text | `box "Left" ljust fit` |
| `rjust` | Right-justify text | `box "Right" rjust fit` |
| `small` / `big` | Font size hints | `text "note" small` |
| `bold` | Bold text | `box "CPU" bold fit` |
| `italic` | Italic text | `text "async" italic` |
| `mono` | Monospace | `box "0x10" mono fit` |
| `color` | Stroke color | `box "A" color CadetBlue` |
| `fill` | Fill color | `box "A" fill Bisque` |
| `thickness` | Stroke width | `box thickness 2pt` |
| `thick` | Thicker stroke | `box thick` |
| `thin` | Thinner stroke | `box thin` |
| `invis` / `invisible` | Hide stroke | `box invis` |
| `solid` | Restore stroke | `box invis; box solid` |
| `close` | Close path | `line go 2cm heading 0 then 2cm heading 120 close fill 0x006000` |
| Label `A:` | Name object | `A: box "CPU" fit` |
| `A.n` | Labeled anchor | `arrow from A.s to 1cm below A.s` |
| `previous` / `last` | Last object | `circle at 1cm right of previous` |
| `first box` | First of class | `arrow from first box.e to last box.w` |
| `<P,Q>` | Interpolate point | `dot at 0.5<Bus.start,Bus.end>` |
| `(x,y)` | Literal coords | `dot at (0,0)` |
| Units `cm/in/mm/pt/px` | Length units | `box wid 1.5cm ht 0.8cm` |
## 2. Template: simple state machine
This version keeps the geometry explicit enough that the back-edge is predictable.
```pikchr
# Simple state machine
S0: box "IDLE" fit at (0, 0)
S1: box "RUN" fit at (0,-2cm)
S2: box "FAULT" fit at (0,-4cm)
arrow from S0.s to S1.n "start" above
arrow from S1.s to S2.n "error" above
# Back edge RUN -> IDLE with explicit route
arrow from S1.w left 1.2cm then up 2cm then to S0.w "stop" above
3. Template: request/response timing
This corrected version avoids mixed implicit layout and uses fixed x positions for lifelines.
# Request/response over time
A: box "Client" fit at (0,0)
B: box "Server" fit at (5cm,0)
LA: line from A.s down 4cm
LB: line from B.s down 4cm
arrow from (A.e.x, -1.0cm) to (B.w.x, -1.0cm) "REQ" above
arrow from (B.w.x, -2.4cm) to (A.e.x, -2.4cm) "RESP" above
4. Template: simple multi-core block diagram
The earlier example mixed fragile placement with a bus line. This version is explicit and anchor-driven.
# Multi-core block diagram
MCU: box "MCU" fit at (0,0)
Core0: box "Core 0" fit at (-2.2cm,-2.0cm)
Core1: box "Core 1" fit at ( 0.0cm,-2.0cm)
Core2: box "Core 2" fit at ( 2.2cm,-2.0cm)
Bus: line from (-3.2cm,-3.8cm) to (3.2cm,-3.8cm) "APB" above
arrow from MCU.s to Core1.n
arrow from Core0.s to (-2.2cm,-3.8cm)
arrow from Core1.s to ( 0.0cm,-3.8cm)
arrow from Core2.s to ( 2.2cm,-3.8cm)
5. Template: bus with multiple devices
This corrected version uses explicit stub endpoints on the bus instead of relying on object-as-place shortcuts.
# Horizontal bus with devices
Master: box "Master" fit at (-5cm,0)
Bus: line from (-2.5cm,0) to (3.5cm,0) "I2C" above
Dev1: box "Dev 1" fit at (-1.5cm, 1.8cm)
Dev2: box "Dev 2" fit at ( 0.5cm, 1.8cm)
Dev3: box "Dev 3" fit at ( 2.5cm, 1.8cm)
arrow from Master.e to Bus.start
arrow from Dev1.s to (-1.5cm,0)
arrow from Dev2.s to ( 0.5cm,0)
arrow from Dev3.s to ( 2.5cm,0)
6. Template: minimal component with ports
This keeps the port geometry explicit and adds the actual connection stubs.
# Component with ports
C: box "Component" fit at (0,0)
P1: dot at (-1.6cm, 0.4cm)
P2: dot at (-1.6cm,-0.4cm)
Q1: dot at ( 1.6cm, 0.0cm)
line from P1 to C.w + (0, 0.4cm)
line from P2 to C.w + (0,-0.4cm)
line from C.e to Q1
text "IN1" at 4mm left of P1
text "IN2" at 4mm left of P2
text "OUT" at 4mm right of Q1
7. Template: radial technical layout
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm, 0.00cm)
Cog7: circle "COG7" fit at (-2.30cm, 2.30cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w to Cog6.e
arrow from Hub.nw to Cog7.se
Pikchr addendum for diagram generation
This addendum is a practical companion to the official Pikchr manual. It is meant to load fast into context and prevent common failures when generating technical diagrams. Pikchr is a PIC-like diagram language that emits SVG, and its documented model is single-pass, object-oriented, and heavily based on explicit placement, anchors, and relative geometry rather than any automatic graph layout.
Working model
Pikchr should be treated as a precise drawing language, not as an autorouter or graph-layout engine. A good generation strategy is to decide the geometry first, place objects second, and only then add connections.
The language fixes an object's geometry when its statement ends, except for grouped container behavior using [], so late correction by "nudging" earlier objects is the wrong mental model. When a diagram matters, layout should be explicit enough that every important object position and every important connection endpoint can be predicted before rendering.
Non-negotiable rules
- Place major objects explicitly with
at (...)or equivalent documented relative placement forms. - Use labels for every object that will be referenced later, because ordinal references like
last boxare fragile when the script changes. - Choose arrow endpoints from named anchors explicitly:
.n,.ne,.e,.se,.s,.sw,.w,.nw,.c. - Treat hub-and-spoke, bus, timing, and state diagrams as geometry problems, not prose-generation problems.
- Prefer deterministic scripts over compact clever scripts.
- Use
fitfor text-bearing objects unless fixed dimensions are semantically important. - Keep titles and captions outside the active geometry envelope so they do not collide with arrows or nodes.
- Use lowercase-leading variable names for variables; uppercase-leading names are suitable for object labels, not variables.
Anchor discipline
Every block object has nine primary anchors: .n, .ne, .e, .se, .s, .sw, .w, .nw, .c. These are the first choice for diagram connectivity because they encode intent directly in the source.
Examples:
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog1: circle "COG1" fit at ( 0.00cm, 3.20cm)
arrow from Hub.ne to Cog1.sw
This is better than vague placement because the relation is visible in the code itself. For radial topologies, use matching opposing anchors; for left-to-right flows, use .e -> .w; for top-to-bottom flows, use .s -> .n.
Preferred design workflow
1. Decide topology class first
Choose one of these before writing Pikchr:
- Linear pipeline
- Hub and spoke
- Bus with taps
- State machine
- Sequence / timing lanes
- Layered stack
- Matrix / grid
Once topology is chosen, assign a coordinate system or at least a strict directional layout plan before creating objects.
2. Place nodes explicitly
For nontrivial diagrams, define the important nodes first with fixed coordinates or with clearly documented relative placement. Example:
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Use hard-coded coordinates when clarity matters more than brevity. Use relative forms like with .w at 2cm right of A.e when the relation itself is the important fact.
3. Add connections last
After object placement is stable, add arrows and lines using explicit anchors. If a connection is visually important, write it as a dedicated statement and do not rely on default direction or implicit endpoints.
4. Only then add labels and styling
Text, fills, thickness, and captions should be the last pass. Styling should never be used to compensate for poor geometry.
Placement patterns that work
Radial pattern
Use explicit coordinates or explicit heading placement from a stable center when building rings.
Safe pattern, Use explicit anchor pairs for relations:
Hub: box "Hub" fit at (0,0)
Cog0: circle "0" fit at ( 0.0cm, 3.0cm)
Cog1: circle "1" fit at ( 2.1cm, 2.1cm)
Cog2: circle "2" fit at ( 3.0cm, 0.0cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
Left-to-right flow
Use .e -> .w consistently.
A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
C: box "Host" fit at (6cm,0)
arrow from A.e to B.w
arrow from B.e to C.w
Top-to-bottom flow
Use .s -> .n consistently.
S0: box "Idle" fit at (0,0)
S1: box "Run" fit at (0,-2cm)
arrow from S0.s to S1.n
Bus with taps
Draw the bus once, then attach devices by anchor.
Bus: line from (-3cm,0) to (3cm,0) "I2C" above
Dev1: box "Dev1" fit at (-2cm,1.5cm)
Dev2: box "Dev2" fit at ( 0cm,1.5cm)
Dev3: box "Dev3" fit at ( 2cm,1.5cm)
arrow from Dev1.s to (-2cm,0)
arrow from Dev2.s to ( 0cm,0)
arrow from Dev3.s to ( 2cm,0)
Connection routing policy
Pikchr should not be asked to "find a clean route". Instead, choose one of these explicit routing modes:
- Direct radial or axial segment
- Manhattan route with
then - Dedicated bus or backbone plus short stubs
- Multi-diagram decomposition when one drawing becomes semantically dense
Example Manhattan route:
A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
arrow from A.e right 1cm then down 1.5cm then to B.n
Use this when a direct segment would cross important geometry.
When to split a diagram
Split a diagram when any of these happen:
- More than one semantic layer competes in the same central area.
- Arrow crossings obscure labels.
- The code starts depending on trial-and-error shifts.
- The relation is easier to explain as two drawings than one.
For example, Hub <-> Cogs and Cogs <-> Smart Pins can be separate diagrams if the combined version becomes unreadable.
Common failure modes
1. Invalid variable names
Variables must begin with a lowercase letter, $, or @, so names like CogRadius are invalid variable names in Pikchr grammar. Use cogRadius, $cogRadius, or pinr instead.
2. Trying to reposition an already-created object
Label: at ... is not a standalone object-reposition statement in the way one might expect from a retained-mode graphics system. Put at ... on the object definition itself, such as Cog0: circle "COG0" fit at (...).
3. Overusing implicit layout
Default stacking directions are useful for small diagrams, but they become fragile for serious technical drawings. Once a diagram has more than a few nodes or has crossing semantics, switch to explicit coordinates or explicit relation-based placement.
4. Overdrawing the center
If many arrows terminate on a small central object, readability collapses even when the code is technically correct. Solutions are: enlarge the central object, route through a bus/backbone, or split the diagram.
5. Letting captions invade geometry
A title or caption belongs outside the connection field. Place it explicitly with coordinates beyond the node envelope.
Prompt aids for future AI use
The following prompt snippets are designed to force correct Pikchr behavior.
Prompt aid: strict placement
Generate Pikchr as explicit geometry. Do not rely on automatic layout, implicit stacking, or guessed routing. Place every major object with an explicit `at (...)` coordinate or a clearly documented relative placement. Label every referenced object.
Prompt aid: strict anchors
For every arrow, choose endpoints explicitly from named anchors such as `.n`, `.ne`, `.e`, `.se`, `.s`, `.sw`, `.w`, `.nw`, or `.c`. Do not use vague or implicit connection endpoints.
Prompt aid: topology-first
Before writing Pikchr, state the topology class: pipeline, hub-and-spoke, bus, state machine, timing lanes, layered stack, or grid. Then assign coordinates or a deterministic placement scheme.
Prompt aid: no autorouter fantasy
Treat Pikchr as a deterministic drawing language, not an autorouter. If a connection would be messy, route it explicitly with `from ... then ... then to ...`, or redesign the geometry.
Prompt aid: readability over compactness
Prefer longer but deterministic Pikchr over compact clever Pikchr. Hard-code coordinates when necessary. The source should make the geometry obvious before rendering.
Prompt aid: final verification checklist
Before presenting the Pikchr, verify:
1. Every referenced object has a label.
2. Every major object has explicit placement.
3. Every arrow names both endpoints explicitly.
4. Anchor choices match the intended relation.
5. Title and caption are outside the active geometry.
6. Variable names are valid in Pikchr.
7. No connection depends on hoping the renderer makes it look good.
Compact AI memory block
Use this as a short context injection block:
Pikchr memory block:
- Pikchr is single-pass; geometry should be deliberate.
- Use labels for all referenced objects.
- Use explicit `at (...)` placement for nontrivial diagrams.
- Use anchors explicitly: .n .ne .e .se .s .sw .w .nw .c.
- Write arrows as explicit anchor-to-anchor relations.
- Use Manhattan routing with `then` when direct lines would cross important geometry.
- Prefer deterministic coordinates over clever compact code.
- Variable names start with lowercase, `$`, or `@`.
- Do not try to reposition an already-created object with a later `at` statement.
- If the center gets crowded, enlarge it, use a bus, or split the diagram.
Example canonical pattern
This is a good default pattern for a radial technical diagram because it is explicit, stable, and readable:
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm, 0.00cm)
Cog7: circle "COG7" fit at (-2.30cm, 2.30cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w to Cog6.e
arrow from Hub.nw to Cog7.se
This pattern encodes the relations directly in the source and uses the documented anchor model exactly as intended.
I've distilled this documentation to get AI to stop failing at Pikchr generation. The end of the document has specific prompts that may be more focused for particular issues
Pikchr distilled
1. Core model and script structure
Pikchr is a PIC-like DSL that emits SVG, and a script is a flat sequence of statements separated by newlines or ;. The language is single-pass, so an object can only refer to previously defined objects, and its geometry is fixed when the statement ends except for grouped container behavior.
Example:
right
line
box "Hello," "World!"
arrow
2. Shapes and object classes
Main built-ins include box, circle, ellipse, oval, cylinder, file, diamond, plus path-like objects such as line, arrow, spline, and arc, and point/text objects such as dot and text.
Representative example from the manual family of examples:
box "box"
circle "circle" at 1 right of previous
ellipse "ellipse" at 1 right of previous
oval "oval" at 1 right of previous
cylinder "cylinder" at .8 below first box
file "file" at 1 right of previous
diamond "diamond" at 1 right of previous
3. Automatic layout and move
Automatic layout with right, down, left, and up is useful for small diagrams. For larger technical diagrams, explicit placement is usually more reliable than long chains of implicit stacking.
Safe use of move:
right
box "A"
move
circle "B"
move
cylinder "C"
4. Anchors, coordinates, and placement
Every block object has the nine primary anchors .n, .ne, .e, .se, .s, .sw, .w, .nw, and .c. These anchors should be used explicitly for nontrivial connections because they encode the intended relation directly.
Anchor example:
A: box
dot color red at A.nw ".nw " rjust above
dot same at A.w ".w " rjust
dot same at A.sw ".sw " rjust below
dot same at A.s ".s" below
dot same at A.se " .se" ljust below
dot same at A.e " .e" ljust
dot same at A.ne " .ne" ljust above
dot same at A.n ".n" above
dot same at A.c " .c" ljust
Correct placement patterns:
For serious diagrams, a coordinate-based form is often clearer:
A: box "A" fit at (0,0)
B: box "B" fit at (3cm,0)
arrow from A.e to B.w
5. Relative movement for paths
Paths can use from, to, up, down, left, right, then, and until even with. The reliable pattern is to anchor the start explicitly and then write the route explicitly.
Example:
B1: box "SRC" fit at (0,0)
C1: box "DST" fit at (4cm,-2cm)
arrow from B1.s \
down 1cm \
then right until even with C1 \
then to C1.w
6. Sizing and fit
Objects support wid, ht, rad, and diameter, and fit resizes a block to contain its text. This is usually the right default for boxes and circles carrying labels.
boxwid = 0
boxht = 0
box "Hello"
move
box "A longer label" "with more text"
Variable names should begin with a lowercase letter, $, or @, so boxwid is valid but BoxWid is not.
7. Stroke, fill, and color
Useful style controls include thickness, thick, thin, invisible, solid, color, and fill.
box "normal"
move
box "thin" thin
move
box "thick" thick
move
box "filled" color White fill RoyalBlue fit
8. Text annotations
Objects can carry up to five strings, with modifiers such as above, below, ljust, rjust, small, big, bold, italic, and mono.
line wid 200% \
"ljust" ljust above \
"rjust" rjust below
dot color red at previous.c
9. Labels and robustness
Labels are strongly preferred over ordinal references such as last circle because they remain stable as the script evolves. A label starts with a capital letter and prefixes the object definition directly.
Do not try to reposition an already-created object later using a standalone Label: at ... form; attach at ... to the object definition itself.
10. Practical technical patterns
Simple pipeline / dataflow
A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
C: box "Host" fit at (6cm,0)
arrow from A.e to B.w
arrow from B.e to C.w
Bus with branch arrows
Bus: line from (-3cm,0) to (3cm,0) "I2C bus" above
Device1: box "Device 1" fit at (-1.5cm,1.6cm)
Device2: box "Device 2" fit at ( 1.5cm,1.6cm)
arrow from Device1.s to (-1.5cm,0)
arrow from Device2.s to ( 1.5cm,0)
Routed L-shaped connection
SRC: box "SRC" fit at (0,0)
DST: box "DST" fit at (4cm,-2cm)
arrow from SRC.e \
right 1cm \
then down 2cm \
then to DST.n
Dimensioned side-by-side objects
B1: box "A" fit at (0,0)
C1: circle "B" fit at (2cm,0)
X1: line thin color gray from (B1.w.x,-0.8cm) to (B1.w.x,-0.2cm)
X2: line thin color gray from (C1.e.x,-0.8cm) to (C1.e.x,-0.2cm)
arrow <-> thin color gray from (B1.w.x,-0.5cm) to (C1.e.x,-0.5cm) "2cm" above
Radial hub-and-spoke pattern
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm, 0.00cm)
Cog7: circle "COG7" fit at (-2.30cm, 2.30cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w to Cog6.e
arrow from Hub.nw to Cog7.se
11. Embedding note
In Markdown and Fossil workflows, Pikchr is typically embedded as a fenced code block and rendered by the host environment.
``` pikchr
A: box "Input" fit at (0,0)
B: box "Formatter" fit at (3cm,0)
arrow from A.e to B.w
# Pikchr templates
## 1. Core syntax tokens
| Token / form | Role | Minimal example |
| --- | --- | --- |
| `box` | Rectangular node | `box "MCU" fit` |
| `circle` | Circle node | `circle "IRQ" fit` |
| `ellipse` | Elliptical node | `ellipse "State" fit` |
| `oval` | Oval node | `oval "Loop" fit` |
| `cylinder` | DB/storage | `cylinder "DB" fit` |
| `file` | File/doc node | `file "config.json" fit` |
| `diamond` | Decision node | `diamond "?" fit` |
| `line` | Line segment/polyline | `line right 2cm` |
| `arrow` | Line with arrow head | `arrow from A.e to B.w` |
| `spline` | Curved path | `spline from A.e go right 2cm` |
| `arc` | Circular arc | `arc from 1cm right of A.e` |
| `dot` | Point marker | `dot at A.c` |
| `text` | Stand-alone text | `text "Note" at 5mm above A.n` |
| `right` | Set flow dir to right | `right; box "A"; box "B"` |
| `left` | Set flow dir to left | `left; box "A"; box "B"` |
| `down` | Set flow dir to down | `down; box "A"; box "B"` |
| `up` | Set flow dir to up | `up; box "A"; box "B"` |
| `move` | Invisible spacer | `box; move; box` |
| `from` | Start of path | `arrow from A.e to B.w` |
| `to` | End of path | `arrow from A.s to B.n` |
| `then` | Continue path | `arrow from A.e right 1cm then down 1cm then to B.n` |
| `go` | First segment in path | `line from A.e go right 2cm` |
| `heading` | Polar direction | `line go 3cm heading 150` |
| `until even with` | Axis alignment helper | `right until even with B` |
| `at` | Position current object | `box "A" at (0,0)` |
| `with .anchor` | Position by anchor | `circle with .w at 1cm right of B.e` |
| `.n/.s/.e/.w` | Cardinal anchors | `arrow from A.e to B.w` |
| `.ne/.nw/.se/.sw` | Diagonal anchors | `arrow from A.ne to B.sw` |
| `.c` | Center anchor | `dot at A.c` |
| `.start/.end` | Path endpoints | `text "len" at <L.start,L.end>` |
| `wid` / `width` | Object width | `box "A" wid 2cm` |
| `ht` / `height` | Object height | `box "A" ht 1cm` |
| `rad` / `radius` | Corner radius | `box "A" rad 4pt` |
| `diameter` | Circle size | `circle diameter 1cm` |
| `%` on size | Relative resize | `box wid 125% ht 80%` |
| `fit` | Auto-size to text | `box "Label" fit` |
| `"text"` | Annotation string | `box "CPU" "Core0" fit` |
| `above` | Text above object | `arrow "IRQ" above` |
| `below` | Text below object | `arrow "ack" below` |
| `ljust` | Left-justify text | `box "Left" ljust fit` |
| `rjust` | Right-justify text | `box "Right" rjust fit` |
| `small` / `big` | Font size hints | `text "note" small` |
| `bold` | Bold text | `box "CPU" bold fit` |
| `italic` | Italic text | `text "async" italic` |
| `mono` | Monospace | `box "0x10" mono fit` |
| `color` | Stroke color | `box "A" color CadetBlue` |
| `fill` | Fill color | `box "A" fill Bisque` |
| `thickness` | Stroke width | `box thickness 2pt` |
| `thick` | Thicker stroke | `box thick` |
| `thin` | Thinner stroke | `box thin` |
| `invis` / `invisible` | Hide stroke | `box invis` |
| `solid` | Restore stroke | `box invis; box solid` |
| `close` | Close path | `line go 2cm heading 0 then 2cm heading 120 close fill 0x006000` |
| Label `A:` | Name object | `A: box "CPU" fit` |
| `A.n` | Labeled anchor | `arrow from A.s to 1cm below A.s` |
| `previous` / `last` | Last object | `circle at 1cm right of previous` |
| `first box` | First of class | `arrow from first box.e to last box.w` |
| `<P,Q>` | Interpolate point | `dot at 0.5<Bus.start,Bus.end>` |
| `(x,y)` | Literal coords | `dot at (0,0)` |
| Units `cm/in/mm/pt/px` | Length units | `box wid 1.5cm ht 0.8cm` |
## 2. Template: simple state machine
This version keeps the geometry explicit enough that the back-edge is predictable.
```pikchr
# Simple state machine
S0: box "IDLE" fit at (0, 0)
S1: box "RUN" fit at (0,-2cm)
S2: box "FAULT" fit at (0,-4cm)
arrow from S0.s to S1.n "start" above
arrow from S1.s to S2.n "error" above
# Back edge RUN -> IDLE with explicit route
arrow from S1.w left 1.2cm then up 2cm then to S0.w "stop" above
3. Template: request/response timing
This corrected version avoids mixed implicit layout and uses fixed x positions for lifelines.
# Request/response over time
A: box "Client" fit at (0,0)
B: box "Server" fit at (5cm,0)
LA: line from A.s down 4cm
LB: line from B.s down 4cm
arrow from (A.e.x, -1.0cm) to (B.w.x, -1.0cm) "REQ" above
arrow from (B.w.x, -2.4cm) to (A.e.x, -2.4cm) "RESP" above
4. Template: simple multi-core block diagram
The earlier example mixed fragile placement with a bus line. This version is explicit and anchor-driven.
# Multi-core block diagram
MCU: box "MCU" fit at (0,0)
Core0: box "Core 0" fit at (-2.2cm,-2.0cm)
Core1: box "Core 1" fit at ( 0.0cm,-2.0cm)
Core2: box "Core 2" fit at ( 2.2cm,-2.0cm)
Bus: line from (-3.2cm,-3.8cm) to (3.2cm,-3.8cm) "APB" above
arrow from MCU.s to Core1.n
arrow from Core0.s to (-2.2cm,-3.8cm)
arrow from Core1.s to ( 0.0cm,-3.8cm)
arrow from Core2.s to ( 2.2cm,-3.8cm)
5. Template: bus with multiple devices
This corrected version uses explicit stub endpoints on the bus instead of relying on object-as-place shortcuts.
# Horizontal bus with devices
Master: box "Master" fit at (-5cm,0)
Bus: line from (-2.5cm,0) to (3.5cm,0) "I2C" above
Dev1: box "Dev 1" fit at (-1.5cm, 1.8cm)
Dev2: box "Dev 2" fit at ( 0.5cm, 1.8cm)
Dev3: box "Dev 3" fit at ( 2.5cm, 1.8cm)
arrow from Master.e to Bus.start
arrow from Dev1.s to (-1.5cm,0)
arrow from Dev2.s to ( 0.5cm,0)
arrow from Dev3.s to ( 2.5cm,0)
6. Template: minimal component with ports
This keeps the port geometry explicit and adds the actual connection stubs.
# Component with ports
C: box "Component" fit at (0,0)
P1: dot at (-1.6cm, 0.4cm)
P2: dot at (-1.6cm,-0.4cm)
Q1: dot at ( 1.6cm, 0.0cm)
line from P1 to C.w + (0, 0.4cm)
line from P2 to C.w + (0,-0.4cm)
line from C.e to Q1
text "IN1" at 4mm left of P1
text "IN2" at 4mm left of P2
text "OUT" at 4mm right of Q1
7. Template: radial technical layout
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm, 0.00cm)
Cog7: circle "COG7" fit at (-2.30cm, 2.30cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w to Cog6.e
arrow from Hub.nw to Cog7.se
Pikchr addendum for diagram generation
This addendum is a practical companion to the official Pikchr manual. It is meant to load fast into context and prevent common failures when generating technical diagrams. Pikchr is a PIC-like diagram language that emits SVG, and its documented model is single-pass, object-oriented, and heavily based on explicit placement, anchors, and relative geometry rather than any automatic graph layout.
Working model
Pikchr should be treated as a precise drawing language, not as an autorouter or graph-layout engine. A good generation strategy is to decide the geometry first, place objects second, and only then add connections.
The language fixes an object's geometry when its statement ends, except for grouped container behavior using [], so late correction by "nudging" earlier objects is the wrong mental model. When a diagram matters, layout should be explicit enough that every important object position and every important connection endpoint can be predicted before rendering.
Non-negotiable rules
- Place major objects explicitly with
at (...)or equivalent documented relative placement forms. - Use labels for every object that will be referenced later, because ordinal references like
last boxare fragile when the script changes. - Choose arrow endpoints from named anchors explicitly:
.n,.ne,.e,.se,.s,.sw,.w,.nw,.c. - Treat hub-and-spoke, bus, timing, and state diagrams as geometry problems, not prose-generation problems.
- Prefer deterministic scripts over compact clever scripts.
- Use
fitfor text-bearing objects unless fixed dimensions are semantically important. - Keep titles and captions outside the active geometry envelope so they do not collide with arrows or nodes.
- Use lowercase-leading variable names for variables; uppercase-leading names are suitable for object labels, not variables.
Anchor discipline
Every block object has nine primary anchors: .n, .ne, .e, .se, .s, .sw, .w, .nw, .c. These are the first choice for diagram connectivity because they encode intent directly in the source.
Examples:
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog1: circle "COG1" fit at ( 0.00cm, 3.20cm)
arrow from Hub.ne to Cog1.sw
This is better than vague placement because the relation is visible in the code itself. For radial topologies, use matching opposing anchors; for left-to-right flows, use .e -> .w; for top-to-bottom flows, use .s -> .n.
Preferred design workflow
1. Decide topology class first
Choose one of these before writing Pikchr:
- Linear pipeline
- Hub and spoke
- Bus with taps
- State machine
- Sequence / timing lanes
- Layered stack
- Matrix / grid
Once topology is chosen, assign a coordinate system or at least a strict directional layout plan before creating objects.
2. Place nodes explicitly
For nontrivial diagrams, define the important nodes first with fixed coordinates or with clearly documented relative placement. Example:
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Use hard-coded coordinates when clarity matters more than brevity. Use relative forms like with .w at 2cm right of A.e when the relation itself is the important fact.
3. Add connections last
After object placement is stable, add arrows and lines using explicit anchors. If a connection is visually important, write it as a dedicated statement and do not rely on default direction or implicit endpoints.
4. Only then add labels and styling
Text, fills, thickness, and captions should be the last pass. Styling should never be used to compensate for poor geometry.
Placement patterns that work
Radial pattern
Use explicit coordinates or explicit heading placement from a stable center when building rings.
Safe pattern, Use explicit anchor pairs for relations:
Hub: box "Hub" fit at (0,0)
Cog0: circle "0" fit at ( 0.0cm, 3.0cm)
Cog1: circle "1" fit at ( 2.1cm, 2.1cm)
Cog2: circle "2" fit at ( 3.0cm, 0.0cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
Left-to-right flow
Use .e -> .w consistently.
A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
C: box "Host" fit at (6cm,0)
arrow from A.e to B.w
arrow from B.e to C.w
Top-to-bottom flow
Use .s -> .n consistently.
S0: box "Idle" fit at (0,0)
S1: box "Run" fit at (0,-2cm)
arrow from S0.s to S1.n
Bus with taps
Draw the bus once, then attach devices by anchor.
Bus: line from (-3cm,0) to (3cm,0) "I2C" above
Dev1: box "Dev1" fit at (-2cm,1.5cm)
Dev2: box "Dev2" fit at ( 0cm,1.5cm)
Dev3: box "Dev3" fit at ( 2cm,1.5cm)
arrow from Dev1.s to (-2cm,0)
arrow from Dev2.s to ( 0cm,0)
arrow from Dev3.s to ( 2cm,0)
Connection routing policy
Pikchr should not be asked to "find a clean route". Instead, choose one of these explicit routing modes:
- Direct radial or axial segment
- Manhattan route with
then - Dedicated bus or backbone plus short stubs
- Multi-diagram decomposition when one drawing becomes semantically dense
Example Manhattan route:
A: box "Sensor" fit at (0,0)
B: box "MCU" fit at (3cm,0)
arrow from A.e right 1cm then down 1.5cm then to B.n
Use this when a direct segment would cross important geometry.
When to split a diagram
Split a diagram when any of these happen:
- More than one semantic layer competes in the same central area.
- Arrow crossings obscure labels.
- The code starts depending on trial-and-error shifts.
- The relation is easier to explain as two drawings than one.
For example, Hub <-> Cogs and Cogs <-> Smart Pins can be separate diagrams if the combined version becomes unreadable.
Common failure modes
1. Invalid variable names
Variables must begin with a lowercase letter, $, or @, so names like CogRadius are invalid variable names in Pikchr grammar. Use cogRadius, $cogRadius, or pinr instead.
2. Trying to reposition an already-created object
Label: at ... is not a standalone object-reposition statement in the way one might expect from a retained-mode graphics system. Put at ... on the object definition itself, such as Cog0: circle "COG0" fit at (...).
3. Overusing implicit layout
Default stacking directions are useful for small diagrams, but they become fragile for serious technical drawings. Once a diagram has more than a few nodes or has crossing semantics, switch to explicit coordinates or explicit relation-based placement.
4. Overdrawing the center
If many arrows terminate on a small central object, readability collapses even when the code is technically correct. Solutions are: enlarge the central object, route through a bus/backbone, or split the diagram.
5. Letting captions invade geometry
A title or caption belongs outside the connection field. Place it explicitly with coordinates beyond the node envelope.
Prompt aids for future AI use
The following prompt snippets are designed to force correct Pikchr behavior.
Prompt aid: strict placement
Generate Pikchr as explicit geometry. Do not rely on automatic layout, implicit stacking, or guessed routing. Place every major object with an explicit `at (...)` coordinate or a clearly documented relative placement. Label every referenced object.
Prompt aid: strict anchors
For every arrow, choose endpoints explicitly from named anchors such as `.n`, `.ne`, `.e`, `.se`, `.s`, `.sw`, `.w`, `.nw`, or `.c`. Do not use vague or implicit connection endpoints.
Prompt aid: topology-first
Before writing Pikchr, state the topology class: pipeline, hub-and-spoke, bus, state machine, timing lanes, layered stack, or grid. Then assign coordinates or a deterministic placement scheme.
Prompt aid: no autorouter fantasy
Treat Pikchr as a deterministic drawing language, not an autorouter. If a connection would be messy, route it explicitly with `from ... then ... then to ...`, or redesign the geometry.
Prompt aid: readability over compactness
Prefer longer but deterministic Pikchr over compact clever Pikchr. Hard-code coordinates when necessary. The source should make the geometry obvious before rendering.
Prompt aid: final verification checklist
Before presenting the Pikchr, verify:
1. Every referenced object has a label.
2. Every major object has explicit placement.
3. Every arrow names both endpoints explicitly.
4. Anchor choices match the intended relation.
5. Title and caption are outside the active geometry.
6. Variable names are valid in Pikchr.
7. No connection depends on hoping the renderer makes it look good.
Compact AI memory block
Use this as a short context injection block:
Pikchr memory block:
- Pikchr is single-pass; geometry should be deliberate.
- Use labels for all referenced objects.
- Use explicit `at (...)` placement for nontrivial diagrams.
- Use anchors explicitly: .n .ne .e .se .s .sw .w .nw .c.
- Write arrows as explicit anchor-to-anchor relations.
- Use Manhattan routing with `then` when direct lines would cross important geometry.
- Prefer deterministic coordinates over clever compact code.
- Variable names start with lowercase, `$`, or `@`.
- Do not try to reposition an already-created object with a later `at` statement.
- If the center gets crowded, enlarge it, use a bus, or split the diagram.
Example canonical pattern
This is a good default pattern for a radial technical diagram because it is explicit, stable, and readable:
Hub: box "Hub RAM" "Shared" fit at (0,0)
Cog0: circle "COG0" fit at ( 0.00cm, 3.20cm)
Cog1: circle "COG1" fit at ( 2.30cm, 2.30cm)
Cog2: circle "COG2" fit at ( 3.20cm, 0.00cm)
Cog3: circle "COG3" fit at ( 2.30cm, -2.30cm)
Cog4: circle "COG4" fit at ( 0.00cm, -3.20cm)
Cog5: circle "COG5" fit at (-2.30cm, -2.30cm)
Cog6: circle "COG6" fit at (-3.20cm, 0.00cm)
Cog7: circle "COG7" fit at (-2.30cm, 2.30cm)
arrow from Hub.n to Cog0.s
arrow from Hub.ne to Cog1.sw
arrow from Hub.e to Cog2.w
arrow from Hub.se to Cog3.nw
arrow from Hub.s to Cog4.n
arrow from Hub.sw to Cog5.ne
arrow from Hub.w to Cog6.e
arrow from Hub.nw to Cog7.se
This pattern encodes the relations directly in the source and uses the documented anchor model exactly as intended.
Great work! Thanks!
this bit I find most useful. even as a human, I've had the problem of not getting good results from the sequential process that mixes these steps together. this outline has improved my process, too.
Preferred design workflow
Decide topology class first
Choose one of these before writing Pikchr:
Linear pipeline, Hub and spoke, Bus with taps, State machine, Sequence / timing lanes, Layered stack, Matrix / grid, Once topology is chosen, assign a coordinate system or at least a strict directional layout plan before creating objects.
Place nodes explicitly
For nontrivial diagrams, define the important nodes first with fixed coordinates or with clearly documented relative placement. Example:
Use hard-coded coordinates when clarity matters more than brevity. Use relative forms like with .w at 2cm right of A.e when the relation itself is the important fact.
Add connections last
After object placement is stable, add arrows and lines using explicit anchors. If a connection is visually important, write it as a dedicated statement and do not rely on default direction or implicit endpoints.
Only then add labels and styling
Text, fills, thickness, and captions should be the last pass. Styling should never be used to compensate for poor geometry.