The inline-SVG primitives the term page's concept graph is drawn with: a node, an edge, an arrowhead, and the arithmetic that lays out a row, a column or a ring.
No graph library. The browser ships two JavaScript files and this adds none — a layout that is a fold over a row of boxes is a dozen lines, and a dependency that drew it would be the largest thing the client loads. Nor a shell-out: a page that renders by starting a process is a page that cannot be served.
Everything here is pure — no KB, no access facade, no belief — so it is tested on hand-built maps. What a node means is the caller's: it supplies the term, the colour class, the link and the tooltip, and this decides only where the box goes and what shape it is.
Coordinates live in one flat user space and may be negative; scene crops the
viewBox to the union of what was actually drawn, so a sparse graph is centred rather
than adrift in a fixed canvas and a long snake_case label is never clipped. Every
number reaching an attribute is a long: Clojure's / yields a ratio, and 1/2 in
an SVG attribute is not a coordinate.
The inline-SVG primitives the term page's concept graph is drawn with: a node, an edge, an arrowhead, and the arithmetic that lays out a row, a column or a ring. **No graph library.** The browser ships two JavaScript files and this adds none — a layout that is a fold over a row of boxes is a dozen lines, and a dependency that drew it would be the largest thing the client loads. Nor a shell-out: a page that renders by starting a process is a page that cannot be served. Everything here is **pure** — no KB, no access facade, no belief — so it is tested on hand-built maps. What a node *means* is the caller's: it supplies the term, the colour class, the link and the tooltip, and this decides only where the box goes and what shape it is. Coordinates live in one flat user space and may be negative; `scene` crops the `viewBox` to the union of what was actually drawn, so a sparse graph is centred rather than adrift in a fixed canvas and a long snake_case label is never clipped. Every number reaching an attribute is a **long**: Clojure's `/` yields a ratio, and `1/2` in an SVG attribute is not a coordinate.
(arc nodes cx cy r a)Place measured nodes on a short arc of radius r centred on angle a around
(cx, cy) — a cluster hanging off one direction rather than a full ring, which is
what puts a neighbour's own neighbours out past it instead of somewhere else entirely.
The angular step is derived from the widest box exactly as ring's radius is: the arc
between two of them is 1.1× the widest, so a cluster of long names spreads and a pair of
short ones stays tight.
Place measured `nodes` on a short arc of radius `r` **centred on angle** `a` around (`cx`, `cy`) — a cluster hanging off one direction rather than a full ring, which is what puts a neighbour's own neighbours out past it instead of somewhere else entirely. The angular step is derived from the widest box exactly as `ring`'s radius is: the arc between two of them is 1.1× the widest, so a cluster of long names spreads and a pair of short ones stays tight.
(column nodes x-edge y gap side)Place measured nodes in a vertical column beside the centre, stacked gap apart and
centred on y. side is -1 for a column to the left of x-edge (its right edge
there) and 1 for one to the right, so the two flanks mirror without the caller doing
the arithmetic twice.
Place measured `nodes` in a vertical column beside the centre, stacked `gap` apart and centred on `y`. `side` is -1 for a column to the *left* of `x-edge` (its right edge there) and 1 for one to the right, so the two flanks mirror without the caller doing the arithmetic twice.
(label term)The text a node carries for term, cut to label-cap with an ellipsis.
The text a node carries for `term`, cut to `label-cap` with an ellipsis.
How many characters of a term's name a node label carries. Past this it is cut and
the whole term goes in the node's title, so nothing is lost — only moved one hover
away. Twenty-two holds physical_object and every predicate the shipped schema has,
and is short enough that a row of eight still reads.
How many characters of a term's name a node label carries. Past this it is cut and the whole term goes in the node's `title`, so nothing is lost — only moved one hover away. Twenty-two holds `physical_object` and every predicate the shipped schema has, and is short enough that a row of eight still reads.
(measure node)Give a node its label and its box — :label, :w, :h — leaving where it goes to
the layout fns below. Idempotent, so a caller may measure once and place twice.
The text is :display when the caller supplied one, else the term itself: what a term
is called on the page is the caller's business (a reified term reads as the
expression it denotes), and :term stays the identity the layout dedups on.
The width is rounded up to an even number, and node-h is even for the same reason:
a box's edge is then an integer wherever its centre is, so trim lands an arrowhead
exactly on the boundary instead of half a pixel inside it.
Give a node its label and its box — `:label`, `:w`, `:h` — leaving *where* it goes to the layout fns below. Idempotent, so a caller may measure once and place twice. The text is `:display` when the caller supplied one, else the term itself: what a term is *called* on the page is the caller's business (a reified term reads as the expression it denotes), and `:term` stays the identity the layout dedups on. The width is rounded **up to an even number**, and `node-h` is even for the same reason: a box's edge is then an integer wherever its centre is, so `trim` lands an arrowhead exactly on the boundary instead of half a pixel inside it.
Every node is the same height. A row is then a straight line of pills and the eye reads the row rather than the boxes.
Every node is the same height. A row is then a straight line of pills and the eye reads the row rather than the boxes.
(ring nodes cx cy min-r start)Place measured nodes evenly around a circle centred at (cx, cy), starting at
start radians and going clockwise.
The radius is computed, not given: min-r, or whatever radius spaces the arc
between neighbours at 1.15× the widest box — so eight long names spread onto a wider
ring instead of overlapping, and two short ones do not fly apart. That is the whole
reason a ring is affordable without a force simulation.
Place measured `nodes` evenly around a circle centred at (`cx`, `cy`), starting at `start` radians and going clockwise. The radius is **computed, not given**: `min-r`, or whatever radius spaces the arc between neighbours at 1.15× the widest box — so eight long names spread onto a wider ring instead of overlapping, and two short ones do not fly apart. That is the whole reason a ring is affordable without a force simulation.
(row nodes cx y gap)Place measured nodes left to right in a row centred on cx at height y, gap
apart.
Place measured `nodes` left to right in a row centred on `cx` at height `y`, `gap` apart.
(scene {:keys [nodes edges aria-label]})The whole picture as one <svg>, or nil when there is nothing to draw — a caller
renders the nil and gets no empty frame.
aria-label is what a screen reader is told the image shows; it is role="img"
because the prose above it, not the picture, is the accessible answer. The viewBox
is the crop, and width=100% with preserveAspectRatio is what makes the drawing
scale to the column instead of a fixed pixel canvas.
The whole picture as one `<svg>`, or **nil** when there is nothing to draw — a caller renders the nil and gets no empty frame. `aria-label` is what a screen reader is told the image shows; it is `role="img"` because the prose above it, not the picture, is the accessible answer. The `viewBox` is the crop, and `width=100%` with `preserveAspectRatio` is what makes the drawing scale to the column instead of a fixed pixel canvas.
(trim a b)The visible segment between two placed nodes: the line between their centres, clipped at each one's box, so an arrowhead lands on the boundary of the node it points at rather than under its label. Nil when the two centres coincide — there is no direction to draw.
The visible segment between two **placed** nodes: the line between their centres, clipped at each one's box, so an arrowhead lands on the boundary of the node it points at rather than under its label. Nil when the two centres coincide — there is no direction to draw.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |