Nodes
Nodes (nodes) are the static objects of the scene: servers,
clients, databases... The engine places them automatically (see
Layout); you only describe their identity and
appearance. Each node has a unique id reused everywhere else
(from/to of actions, connections, zones, object of comments...).
Node types
The type field chooses the appearance. Ten types display a pictogram:
{ id: 'web', type: 'server', text: 'Web Server' }
| Category | Types |
|---|---|
| Workstations | desktop, laptop, mobile |
| Network | client, server, cloud |
| Data | database |
| Actors | user, admin, users |
| Characters | alice, bob, eve |
Two additional types display text rather than a pictogram:
simple_node and complex_node (see below).
Eight types draw a geometric shape that can contain a short text:
square, diamond, circle, triangle, parallelogram, width_rectangle,
height_rectangle and star (see Geometric shapes).
Text nodes: simple_node and complex_node
When a node needs to show text (a code snippet, an HTTP header, a config key...) rather than a pictogram, use:
simple_node— a text box (bodyfield). No large pictogram, but thesubicon(icon) is still possible.complex_node— likesimple_node, plus aheaderdisplayed above the body and separated by a line: the node looks like an HTTP packet.
The language field applies syntax highlighting to all text areas of the
node (the header and the body). The recognized values are the same as
for content (javascript, json, sql, http...).
nodes: [
// simple_node: text body + subicon, no pictogram.
{
id: 'snippet',
type: 'simple_node',
icon: 'node',
body: 'const total = a + b;',
language: 'javascript',
},
// complex_node: header + body, like an HTTP packet.
{
id: 'request',
type: 'complex_node',
header: 'GET /api/users HTTP/1.1',
body: 'Host: api.example.com\nAccept: application/json',
language: 'http', // highlights both header AND body
},
],
body/header vs texttext remains the label under the node (common to all types). For text nodes,
the content of the box goes in body (and header for complex_node).
An active set_content replaces the text panel,
exactly as it hides a pictogram.
Geometric shapes
Eight types draw a shape rather than a pictogram: square, diamond,
circle, triangle, parallelogram, width_rectangle, height_rectangle and
star. Each shape can contain a short centered text via body.
nodes: [
{ id: 'cache', type: 'circle', text: 'Cache', body: 'Redis' },
{ id: 'choice', type: 'diamond', text: 'Routing', body: 'GET ?' },
{ id: 'queue', type: 'height_rectangle', body: 'Queue' },
],
The body of a shape is designed for a brief label (a word, a number,
an acronym). The shape expands to accommodate the text, but it is bounded
(max-width) and cropped if necessary to never overflow the outline: an
entire paragraph would be truncated. For long text, prefer a
simple_node.
Electrical components
A family of schematic symbols — resistor, capacitor, inductor,
battery, dc_source / ac_source, diode, led, transistor_npn, opamp,
switch, push_button, lamp, motor, ground, junction, ammeter,
voltmeter, fuse, potentiometer, transformer… — for drawing electrical
circuits. Unlike the other types, they expose named terminals you wire to by
name ("R1:a", "battery:+", "Q1:collector"), and they read best in
direction: 'circuit', where
connections become orthogonal wires. Two extra fields go with them:
value+unitbuild the label (value: 220, unit: 'Ω'→"220 Ω"; combined withtextif both are set, e.g."R1 · 220 Ω");closed(on aswitch/push_button) sets the initial contact state, animated at runtime by thetoggleaction.
The family also includes digital logic gates — and_gate, or_gate,
not_gate, nand_gate, nor_gate, xor_gate, xnor_gate, buffer_gate —
with two inputs a / b on the left and an output y on the right (a single
input for not_gate / buffer_gate), their three-input counterparts
and3_gate, or3_gate, nand3_gate, nor3_gate and xor3_gate (inputs
a / b / c, the middle one at mid-height so a straight wire needs no bend),
and a signal node — a labelled I/O
pad that shows a bit in its centre (set via set_icon,
lit with set_color) — for the inputs and
outputs of a logic diagram.
Functional blocks
Some circuits are clearer when a sub-circuit is drawn as one labelled box instead of the gates inside it. Eight types cover the blocks whose terminal count is fixed:
| Type | Terminals |
|---|---|
d_flip_flop, t_flip_flop | d / t, clk → q, qn |
jk_flip_flop | j, clk, k → q, qn |
sr_latch | s, r → q, qn (level-sensitive: no clock) |
mux_2to1 | i0, i1, sel (from below) → y |
demux_1to2 | i, sel (from below) → y0, y1 |
half_adder | a, b → s (sum), cout (carry) |
full_adder | a, b, cin → s, cout |
qn also answers to q_bar, sel to s, and s to sum — spell a terminal
whichever way reads best in your spec.
nodes: [
{ id: 'i0', type: 'signal', x: 0.12, y: 0.28, text: 'A', icon: '1' },
{ id: 'i1', type: 'signal', x: 0.12, y: 0.72, text: 'B', icon: '0' },
{ id: 'm', type: 'mux_2to1', x: 0.55, y: 0.45, text: 'MUX 2:1' },
{ id: 'sel', type: 'signal', x: 0.45, y: 0.86, text: 'S', icon: '0' },
{ id: 'y', type: 'signal', x: 0.88, y: 0.45, text: 'Y', icon: '1' },
],
connections: [
{ from: 'i0', to: 'm:i0' },
{ from: 'i1', to: 'm:i1' },
{ from: 'sel', to: 'm:sel' },
{ from: 'm:y', to: 'y' },
],
A block whose terminal COUNT varies with its size — an N-bit register, a 4:1 multiplexer, an n→2ⁿ decoder — is deliberately not a type. One type per size would multiply symbols without end; those blocks need their pins declared in the spec, which the format does not do yet.
MOS transistors
mosfet_n and mosfet_p expose gate (g), drain (d) and source (s),
and transmission_gate is the CMOS pass gate (in → out, controls en above
and enb below). N and P are told apart by the bubble on the gate, the
convention CMOS logic diagrams use.
The two channel terminals are mirrored between the two: source is the upper
terminal on a pMOS, drain is the upper one on an nMOS. That is not a quirk —
it is what every CMOS schematic does, and it is what lets a pull-up and a
pull-down stack be wired straight down the page:
connections: [
{ from: 'vdd', to: 'P:s' }, // pMOS source faces the supply
{ from: 'P:d', to: 'out' },
{ from: 'out', to: 'N:d' }, // nMOS drain faces the output
{ from: 'N:s', to: 'gnd:a' },
{ from: 'out', to: 'y' },
{ from: 'in', to: 'P:g' },
{ from: 'in', to: 'N:g' },
],
In a logic diagram, each wire driven by a signal input or a gate output is
automatically tinted by its net (its driver), so wires that cross or run in
parallel read as distinct and are visibly not joined. The blocks above drive a
net too, so a flip-flop's q is tinted like a gate's y. A transmission_gate
does not — it passes a net rather than driving one — and neither do the MOS
transistors, whose pull-up and pull-down networks share one output node. A wire's explicit color
(or a set_color on the connection) always
overrides the automatic tint; wires driven by a non-logic source (a battery, a
junction) stay neutral.
Several gallery demos build on all this: Electrical circuit (a battery,
switch, resistor and LED loop with animated current), Parallel circuit (two
resistor + LED branches), Ohm's law (voltage vs current vs resistance vs
power), RC circuit (a capacitor charging — the transient and its time
constant τ = R·C), Logic gates (all eight gates stepping through the full
truth table), Half-adder, and a family built entirely from the
universal NAND gate —
Half-adder, Half-subtractor, Full adder, Full subtractor and an
SR latch (two cross-coupled NANDs — one bit of memory), and one level
further down, the CMOS NAND gate — the same NAND drawn as its four MOS
transistors, lighting up the ones that conduct. In the logic demos
each wire is coloured by the bit it carries (green = 1) via set_color, so
students can trace the signal propagating through the gates.
Colors: background_color and border_color
Two fields adjust a node's colors:
background_color— the background: fill of a shape, background of a panel (simple_node/complex_node), or badge behind a pictogram.border_color— the border: stroke of a shape, border of a panel, or color of the lines of a pictogram.text_color— the color of the text inside the node (body of a shape, header/body of a panel), only if syntax highlighting is disabled (nolanguage). Withlanguage, syntax colors take precedence.
Each field accepts a predefined color (any CSS name: tomato,
steelblue, gold...) or an exact hexadecimal value (#3b82f6).
nodes: [
// background_color only → border AND text derived automatically.
{ id: 'api', type: 'server', background_color: '#bfdbfe' },
// predefined name + explicit border, on a shape.
{
id: 'cache',
type: 'circle',
body: 'Cache',
background_color: 'gold',
border_color: 'darkgoldenrod',
},
// dark background, auto-contrasted text (white); or explicit text_color.
{ id: 'note', type: 'simple_node', body: 'TODO', background_color: '#1e3a8a' },
{
id: 'tag',
type: 'square',
body: 'SALE',
background_color: '#fee2e2',
text_color: '#b91c1c',
},
],
If you provide a background_color without border_color, the border is
derived from the background (a darker variant that looks good). Similarly, without
text_color, the color of the internal text is chosen to offer a very high
contrast with the background (black or white depending on its luminance) — a dark background
therefore remains legible without setting anything. Specify border_color / text_color to force
a hue. With language (syntax highlighting), token colors take precedence
over text_color.
text — the label
The text is displayed under the node. It is optional but recommended to
remove any ambiguity between two nodes of the same type.
{ id: 'authdb', type: 'database', text: 'Auth DB' }
icon — the tech badge
The icon field overlays a small badge in the corner of the node. Three sources are
accepted, in this resolution order:
- a known technology (built-in react-icons
icon) — e.g.,
react,node,postgres,mongodb,redis,nginx,docker,kubernetes,dotnet,python,typescript,go,rust,git,azure,aws,graphql, protocols (http,dns,oidc,wifi,bluetooth,5g) and payment brands (visa,mastercard,googlepay,applepay)...; - an icon registered by you via
registerSubIcon; - otherwise, free text displayed in a pill (truncated to 4 characters).
{ id: 'spa', type: 'laptop', text: 'react', icon: 'react' }, // known tech
{ id: 'edge', type: 'cloud', text: 'v2', icon: 'v2' }, // free text → pill
registerSubIcon(name, icon) adds a technology to the global registry, where
icon is SVG markup or a () => SVGElement factory:
registerSubIcon('k8s', '<svg viewBox="0 0 24 24">…</svg>');
Call it only once at application startup (never inside a component body) — see the warning in the API Reference.
url — making a node clickable
Providing a url makes the node clickable: it opens the link in a new
tab. Handy for pointing to a service's documentation.
{ id: 'api', type: 'server', text: 'API', url: 'https://example.com/api' }
content — initial content
A node can display content upon initialization (before any action),
via the content field. It uses the same shape as the
set_content action: a code terminal,
a browser window, an image or a table.
{
id: 'editor',
type: 'laptop',
text: 'Editor',
content: {
type: 'code',
language: 'javascript',
value: 'const add = (a, b) => a + b;',
},
}
visible — initial visibility
By default, every node is visible (visible: true). Pass visible: false
to hide it initially and reveal it later with the
set_visible action — useful for showing
the addition of a component (cache, replica...) as the narrative progresses.
nodes: [
{ id: 'app', type: 'server', text: 'App' },
{ id: 'cache', type: 'database', text: 'Cache', visible: false }, // hidden initially
],
timeline: [
{ type: 'set_visible', object: 'cache', visible: true }, // … then revealed
],
rotation — node orientation
rotation orients the node's visual (pictogram, shape or panel) by an angle
in degrees (clockwise, default 0). The label below the node stays upright,
and arrow anchoring is computed on the unrotated box — so a rotated node connects
exactly like a straight one.
The orientation can be animated at runtime with the
rotate action, which moves the node toward an
absolute target angle. Successive rotations chain from the current angle.
nodes: [
{ id: 'arm', type: 'triangle', text: '45°', rotation: 45 }, // static orientation
{ id: 'spin', type: 'width_rectangle', text: 'rotate' },
],
timeline: [
{ type: 'rotate', object: 'spin', to: 180 }, // animate toward 180°
{ type: 'rotate', object: 'spin', to: 360 }, // … then a full turn
],