Documentation / Diagon

The editor

Last updated: 1 September 2026

Diagon's window is a text editor on the left and a live diagram on the right. This page covers what happens between the two: how the preview keeps up, how the layout engine decides where things go, what dragging does to your code, and how exports come out.

If you haven't written a diagram yet, start with Getting started. The language is the syntax reference.

The window

The toolbar holds, left to right: the input-mode toggle, ✨ Generate, file actions, view actions, export, theme, auto-colour, and the direction and routing dropdowns. The status line at the right end reads N nodes · M edges · Xms after each render, or N issue(s) when something didn't parse.

The code pane has a line-number gutter that scrolls with the text and a red error bar underneath that appears only when there's something to say. Pressing Tab in the code pane inserts two spaces rather than moving focus.

The preview shows the diagram. The name of the open file sits above the code pane; it reads untitled.diag until you save.

The divider between the panes is draggable.

Live preview

Typing re-parses and re-renders after a short pause — about a seventh of a second — so the diagram follows you without redrawing on every keystroke.

When a line doesn't parse, the error bar names the line and the reason, and the preview keeps showing the last diagram that worked. You're never left staring at a blank canvas because of a typo halfway down.

The layout engine

Diagon places every node itself. There are no third-party layout libraries in it.

Flat diagrams go through a layered pipeline: cycles are broken, nodes are assigned to ranks, the order within each rank is shuffled to reduce crossings, and then coordinates are assigned and overlaps resolved. Larger diagrams get fewer refinement passes so they still render quickly.

Diagrams with containers are laid out container by container, innermost first, and then cross-container edges are straightened where that doesn't cost anything. Children with no edges between them are arranged on a grid.

You don't configure any of this. What you control is direction, routing, and pinning individual nodes.

Edge routing

routing: curved (the default) draws smooth splines, orthogonal draws right angles with rounded corners, and straight draws direct lines.

In diagrams with boxes, curved and orthogonal edges are routed around nodes rather than through them, using a visibility graph and a shortest-path search. Where several edges converge on the same node they're given separate landing points on its face, so arrowheads don't pile up on one spot. Edge labels are nudged apart from each other and pushed off any node they'd otherwise sit on top of.

Two limits are worth knowing on very large diagrams:

  • Above 600 edges, edge labels are hidden. The lines stay.
  • Routing gets a 1.5 second budget per layout. Edges that don't get routed in time fall back to direct lines, which may cross a node.

Zoom and pan

  • Scroll zooms, centred on the cursor, between 0.04× and 4×.
  • Drag the background pans.
  • Ctrl+0, or the Fit button, frames the whole diagram with a margin, never magnifying past 1.6×.

Zoom and pan are a view transform. They never re-run the layout, so nothing shifts underneath you.

Dragging a node

Drag any node and it moves. What happens next depends on the kind of diagram, and the difference matters.

In a flat diagram, letting go writes the position into your code. Diagon rewrites that node's line with x: and y: inside its attribute block, keeping any attributes already there:

diagon

api: API
db: Database
api -> db

Drag db and the line becomes:

diagon

api: API
db: Database { x: 320, y: 40 }
api -> db

If the node has no line of its own — it only ever appeared in an edge — Diagon appends one:

diagon

api -> db
db { x: 320, y: 40 }

That's the round trip: the diagram and the code stay the same thing.

In a diagram with containers, or in D2 mode, dragging still moves the node, but the position is held in memory only. It isn't written to your file and it's gone when you close the app. Positions in boxed diagrams come from the container layout, so there's nowhere in the text for a coordinate to live.

A pinned node shows a small purple dot at its top-right corner and stays where you put it while everything else lays out around it. Its edges take the shortest clear path to it: a straight line when nothing is in the way, and a detour around whatever is.

Dragging a container's title bar moves the whole group, children included. That pin is in memory too.

Resizing

In diagrams with boxes, every root-level node and box has a small triangular grip at its bottom-right. Dragging it makes that item bigger.

Manual sizes are grow-only — you can't shrink something below the size its contents need — and they live in memory for the session, like boxed-diagram pins: nothing is written to your file, and they're gone when you close the app.

Exports keep them. Only the little grip itself is removed on the way out; the geometry an export is cut from is the resized geometry, so an SVG or PNG comes out at the sizes you dragged to. If you want the engine's own sizes back, Auto-arrange first and then export.

Auto-arrange

Ctrl+L, or the Auto-arrange button, is how you undo positioning.

In a flat diagram it strips every x: and y: from your code and lets the engine place everything again. In a boxed diagram it clears the in-memory pins and manual sizes.

This matters because Diagon has no undo. There's no Edit menu and no Ctrl+Z for the app's own actions. The code pane has the ordinary text-editing undo your browser engine provides, but any change Diagon makes for you — a drag write-back, Auto-arrange, a dropdown that edits your code, an AI result — replaces the pane's contents and clears that history.

In practice: save before a dragging session, and reach for Auto-arrange rather than Ctrl+Z when you want the pins gone.

Two related gaps: there's no prompt about unsaved changes when you press New, Open, or quit. Save first.

Direction and routing controls

The toolbar dropdowns do slightly different things depending on the diagram.

In a flat diagram they write the directive into your code, exactly as if you'd typed direction: right at the top. In a boxed or D2 diagram they change the view without touching your file.

Theme

Theme: Dark / Theme: Light switches the canvas and what exports look like — background and palette both. Your choice is remembered between sessions.

Auto-colour

Auto-color gives each group in a diagram with containers its own colour from a fixed palette, and tints each edge to match the deeper of the two groups it connects. It only applies to grouped diagrams.

The Groups dropdown next to it decides how coarsely to group: innermost (the default) colours each leaf-most container separately, while depth 0 gives every top-level group one colour, so a whole cluster shares a hue. Both the toggle and the depth are remembered between sessions.

Files

Open (Ctrl+O) reads .diag, .d2 and .txt. Opening a .d2 switches the input mode to D2; opening a .diag switches it back.

Save (Ctrl+S) and Save As (Ctrl+Shift+S) write plain UTF-8 text, defaulting to diagram.diag. There's no project format and no database — a diagram is one text file.

Export

Export SVG produces a standalone file: the styling is embedded, remote icons are converted to inline data, the drawing is cropped tight with a 40-pixel margin, and a background rectangle is included so it looks right wherever you drop it.

Export PNG rasterises the same thing at 2× onto the current theme's background.

Both are on the toolbar and in the File menu. Neither has a keyboard shortcut.

Icons in exports

Icons are fetched fresh at export time, through the app's own network stack — which means it honours your system proxy and certificate store, so it works behind a corporate proxy the way a browser does. Each icon gets 10 seconds and a 6 MB ceiling; an icon that fails is left out rather than blocking the export.

One case produces a visible message: if a remote icon host serves no CORS headers, the browser engine refuses to rasterise the canvas and PNG export is blocked. The error bar tells you so and suggests Export SVG, which is unaffected, or a CORS-friendly icon host.

Keyboard shortcuts

ShortcutAction
Ctrl/Cmd+NNew
Ctrl/Cmd+OOpen…
Ctrl/Cmd+SSave
Ctrl/Cmd+Shift+SSave As…
Ctrl/Cmd+0Fit to screen
Ctrl/Cmd+LAuto-arrange, clearing pins
Tab, in the code paneInsert two spaces

That's the full list. Export, ✨ Generate, theme, auto-colour and the licence panel are toolbar or menu only. Help → Syntax cheatsheet shows a short reminder of the grammar without leaving the app.

Performance notes

The layout engine trades refinement for speed as diagrams grow: it does its most thorough ordering work up to about 400 nodes, fewer passes up to 1,500, and skips the most expensive step above that. Diagon's own test suite lays out an 80-node, 65-edge architecture diagram on every run.

Past about 1,200 nodes the canvas switches to a lighter drawing mode, and past 600 edges the edge labels are hidden — both to keep the preview responsive while you type. If you're near those numbers, routing: straight skips the pathfinding entirely and is noticeably faster.