canvas.diagram
A diagram environment on the scene's canvas: an uploaded image annotated with pins, a flowchart, a simple tree. Its image: names an uploaded asset by file name — canvas.diagram:cell.jpg is an annotated image with no child node needed — and a flowchart environment simply omits it. Boxes are arranged by the environment, layered along flow:, so a flowchart is written as what connects to what; pins carry their own coordinates. Positions here are fractions of the content frame with the origin at the TOP LEFT and y running DOWN — 0.62, 0.31 is 62% across, 31% from the top, the way a position is read off an image — unlike the graph's y-up mathematics. The frame is a camera: focus: (a point, or #id of a box or pin) and zoom: (a multiplier over the fitted frame) are both animatable with !cue.to, so one keyframe on each glides into a feature as the narrative reaches it.
#The diagram environment
holds the pictures that are neither plots nor maps: an uploaded image annotated with pins, a flowchart, a simple tree. Its implicit attribute is image, an uploaded asset named by its file name — canvas.diagram:cell.jpg is an annotated image with no child node needed — and a flowchart environment simply omits it.
The environment compiles whether or not the asset is there: an image: naming nothing renders a message in place of the picture, saying which file name it looked for. A diagram is therefore written and compiled against the coordinates first, and the picture uploaded after.
Positions are fractions of the content frame with the origin at the top left and y running down: 0.62, 0.31 is 62% across and 31% from the top, the way a position is read off an image. This is deliberately not the graph's y-up mathematics — a free-form coordinate drawing belongs in with !show background.
#Images and pins
A is a labelled point on the image: a dot on the feature, a short hairline leader, the label beside it. at: is its implicit attribute; side: places the label, defaulting to whichever side has room. Cues gate pins exactly as they gate graph marks, so annotations arrive with the narrative.
canvas.diagram:cell-micrograph.jpg{
pin#nucleus(at: 0.62, 0.31; label: Nucleus)
cue{
pin(at: 0.28, 0.55; label: Mitochondrion)
}(
in: 1
)
}#Boxes and arrows
A flowchart is written as what connects to what, never as coordinates. declares the things and the relations between them — endpoints are #ids of boxes or pins in the same environment — and the environment arranges the result in layers along flow:, down by default or right. A tree is the same arrangement with the arrows all pointing one way.
A run longer than the environment has room for wraps rather than shrinking: it continues on a further line, reading back the other way, and the arrow between the two turns at the edge. So a box is always the size its text needs — a chain of fourteen steps sets the same type as a chain of three, on more lines. The arrows are routed through the space between the layers and down the sides, never across a box, and each label sits on its own run.
canvas.diagram{
box#sample:terminal{Sample collected}
box#pcr{
PCR amplify
30 cycles
}
box#check:decision{Enough DNA?}
arrow(from: #sample; to: #pcr)
arrow(from: #pcr; to: #check)
arrow(from: #check; to: #pcr; label: no; dashed)
}(
flow: down
)hidden: box keeps its place for the same reason. cue.draw strokes an arrow in along its own length; a dashed: arrow fades instead, since a dash pattern and the draw reveal cannot share one stroke.#Shapes
shape: carries the flowchart conventions a reader already knows: box (the default rounded rectangle), decision (the diamond), terminal (the pill) and ellipse. It is the implicit attribute, so box#check:decision{Enough DNA?} reads as the diamond it draws. A box's text is its body, rich inline — maths, bold and {{…}} all work — and the body keeps its source lines: the first is the label, every further line a smaller muted line beneath it. Text wider than the widest box wraps rather than spilling.
#Placing a box by hand
A box with at: sits out of the arrangement at that fraction of the frame — a legend beside a flowchart, a caption box on an image. Everything else still arranges around it.
#focus, zoom and the glide
The frame is a camera. focus: takes a fractional point or #id naming a box or pin the environment already draws, and zoom: multiplies over the fitted frame — omitted, the content fits the environment. Both are animatable, so one !cue.to on each glides into a feature as the narrative reaches it; interactive additionally hands the reader wheel-zoom and drag, with a double-click back to the authored frame.
canvas.diagram:cell-micrograph.jpg{
pin#nucleus(at: 0.62, 0.31; label: Nucleus)
}(
focus: 0.5, 0.5 !cue.to{#nucleus}(at: 1; over: 800ms)
zoom: 1 !cue.to{3}(at: 1; over: 800ms)
)#Attributes
image names one. Normally written in the head, canvas.diagram:cell.jpg. Omitted, the environment's frame is the arranged extent of its boxes.Can be written as shorthand: canvas.diagram:valuedown stacks layers top to bottom, right runs them left to right. Trees and flowcharts are the same arrangement read in different directions. Either way the arrangement wraps rather than shrinking: a run longer than the environment continues on a further line, reading back the other way, so boxes keep the size their text needs.down#id naming a box or pin this environment already draws — so a frame aimed at a feature it also labels states that feature once. Animatable with !cue.to; absent, the frame centres.!cue.to. NO default: an omitted zoom is auto — the content fits the environment — and zoom: 1 written out means exactly the fit.false#Allowed content
#Allowed in
#Examples
canvas.diagram:cell-micrograph.jpg{
pin#nucleus(at: 0.62, 0.31; label: Nucleus)
pin(at: 0.28, 0.55; label: Mitochondrion)
}(
focus: 0.5, 0.5 !cue.to{#nucleus}(at: 1; over: 800ms)
zoom: 1 !cue.to{3}(at: 1; over: 800ms)
)canvas.diagram{
box#sample(label: Sample collected; shape: terminal)
box#pcr(label: PCR amplify; sub: 30 cycles)
box#check(label: Enough DNA?; shape: decision)
arrow(from: #sample; to: #pcr)
arrow(from: #pcr; to: #check)
arrow(from: #check; to: #pcr; label: no)
}(
flow: down
)