Generated from cat-harness/skills/kg/graph-management/graph-rendering.md — do not edit here.

✎ Edit this page’s source

Graph rendering: one set of rules for every drawn graph

Skill id: graph-rendering · Package: graph-management

This repository draws graphs in at least six places, and until this skill each one followed its own conventions. Owner, 2026-09-23 (issue #1137): “make skill that applies to graphs in general…. then apply to papers, schemas, math proofs/lean etc”.

This skill is the general part. It says what any drawing of a graph owes its reader. The per-graph skills say how one generator meets it: uml-overview for schemas, bpmn-authoring for processes, and content-graph for a paper’s blocks.

A drawing is how a person READS a graph. graph-detanglement is how the graph is RESTRUCTURED, and it needs the drawing: a partition you cannot see is a partition you argue about from numbers alone.

The ten rules

Each rule was learned on the UML overview (issue #990). The failure each one prevents is stated beside it.

  1. Draw from a declared graph, never by hand. Every node and edge in the picture is read from a source: a schema, a declaration, a BPMN file, a uses[] list, a Lean dependency cache. A wrong picture is fixed at its source. A hand-edited diagram is overwritten on the next run, and a check fails while it is stale.
  2. Groups are declared sub-graphs. A box around nodes is a named sub-graph (a directories[] entry, a chapter, a package), never a grouping the renderer invented. Owner, 2026-09-23: “sections are sub-graphs in a harness”.
  3. No empty box without a reason. A node drawn with nothing in it reads as a rendering fault; the owner asked “why empty?” of exactly that. A node with no fields to show carries one line saying why. A node whose shape could not be determined is drawn dashed, as a finding, never as a quiet empty shape. The UML overview went from 242 empty boxes to 0.
  4. Edge kinds are drawn distinctly and never merged. A graph has more than one kind of edge (edge-kinds-and-blast-radius). Editorial uses[] and formal Lean edges, enforced and prose-only references, and a field-carried link versus one declared elsewhere (the UML’s dashed bootstrap.processes:skill edge) each get their own line style and a legend. Merging two kinds into one line destroys the signal every metric on that graph is computed from.
  5. Pick the layout engine by what the graph is, and write down why. See §”Layout engines” below. The choice is a claim with a measurement behind it, not a taste.
  6. Offer two orientations from one source. A tall graph on a wide screen and a wide graph on a phone are both unreadable. Derive the second view from the first (landscapeOf in scripts/plantuml-render.ts) rather than keeping two sources that can drift apart. Owner: “can we have portrait and landscape views?”
  7. Declare colour once. Colour a node by its family (for the UML: schema, scenario, process, state, test) in one stylesheet, and have every renderer read that stylesheet (uml-palette.ts). A second palette is a second answer free to disagree with the first.
  8. Stamp the output with its source’s hash. A rendering whose staleness check needs the renderer is a check CI cannot run: Java, a browser, or a font set that measures text a pixel differently. Write the sha256 of the exact source text into the output and compare stamps. The UML and block-graph SVGs do this through scripts/plantuml-render.ts, which every PlantUML generator shares, with its pinned jar and one JVM for all diagrams.
  9. Show it in a zoomable figure. On the site, use the bpmn-figure markup so docs-ui.js adds zoom, reset and full-width controls, and the export ones: download the SVG, download a 2× PNG, or copy it (as an image where the browser allows, else as SVG text, and it says which; #1270). A pages template missing layout: default renders with no site script and so no controls, and nothing reports it.

    There is ONE viewer, and every diagram on a folio or IG page gets it. It is mountFigure in docs-ui.js; mountFigures decides what is a figure. Do not write a second one for a new kind of page. Extend what mountFigures and inlineDiagrams recognise instead. Since bean n7f8 (owner, 2026-10-06, on an IG’s sequence-diagrams page: “…are now resiable/scrollable like the bpmn diagrams are. tehy should be”), it takes each way a page embeds a drawing:

    embedded astaken bywhere it appears
    .bpmn-figure markupalwaysthe process pages
    an inline <svg>always (an icon-sized one, or one inside a link or nav, is not a figure)Mermaid, an IG’s {% include x.svg %} (PlantUML)
    <img src="x.svg">always; inlined firstUML overviews, workflow figures
    <object data="x.svg">always; inlined first, and its fallback text becomes the namethe IG Publisher’s convention for a pre-rendered diagram
    a raster <img>only on a page that opts in with data-fa-figure-images, and only when the column has shrunk itan IG’s .drawio.png architecture drawings

    The raster row is an opt-in because a raster image on the platform’s own pages is as often a card face or a photo. A site build that knows its images are drawings stamps the attribute on the page. The IG site build does this on every IG page, and its own skill (ig-site-links) says where. This skill names no instance, because the viewer belongs to the platform.

    At 100% a plain figure is shown at its own width, capped at the column. It is not stretched to fill the column. A figure that is wider than its column scrolls inside its card and never runs past the page’s edge.

    The viewer is keyboard-operable, because the profile is low-dexterity. Drag-to-pan is an accelerator, never the only way to pan (board-windows §”The floor”). The figure takes focus (Tab from its toolbar). While it has focus, the arrow keys pan it, + and - zoom, and 0 resets. An arrow key is taken only when the figure can scroll in that direction. Otherwise the page scrolls as usual. the tools layer’s test/ig-diagram-viewer.e2e.ts checks all of this on a fixture page.

  10. Put the measurement on the drawing. When a graph is being partitioned, show each group’s detangle numbers (size, cohesion, links in and out) beside the group, so the picture and the metric are read together. The UML overview pages do this through detangleResultsDir (schemas/detangle-sidecar.ts), the same function the detangler writes through. A number shown without its picture invites arguing about the number.

Layout engines

Measured on the UML overview, 2026-09-23. Re-measure before relying on it for a different graph.

engineuse it forwhat it gets wrong
stored layout (BPMN DI)a graph whose author placed the nodesnothing to choose: never re-lay a graph that carries its own layout
ELK (!pragma layout elk in PlantUML)the compact, portrait default; it routes edges around boxesit lays everything top to bottom and ignores left to right direction and arrow hints (output byte-identical). Unconnected groups end up in one wide row.
ELK + hidden linksa grid of unconnected groups (an overview)nothing, if the column count suits the orientation: ceil(√(n/2)) for portrait, ceil(√(2n)) for landscape
Graphviz dot, left to right, polyline edgesthe landscape view of a connected graphit can route a long edge across a box, which is why ELK stays the portrait default. Use nodesep 70, ranksep 160.
Graphviz with orthogonal edgesavoidit places edge labels away from their edges and runs lines through boxes. The owner: “make wider, its messy”.
Mermaid (dagre)a secondary view where CSS classes on nodes matterthe same model came out three times taller than ELK’s
Graphviz dot in the browser (WASM, unflatten then dot)the interactive view a reader rearranges by dragging nodes (kg-subgraph-layout)like ELK it sets unconnected clusters, and unconnected nodes in one cluster, on one rank: fold both into grids with invisible, marked anchors

Applying it

graphsourcegroupsedge kindsrenderer todayowes
schemasgraph-typology registry, Zod schemas<instance>/<sub-graph>composition; field-carried vs declared elsewheregen-uml-overview.ts (PlantUML, Mermaid and the DOT of the Interactive view), gen-object-model-uml.tsmeets all ten
processes.bpmn filespools and lanessequence, message, callrender-bpmn.ts (stored layout)rule 6 (orientation) does not apply: the author placed it
paper blocks and Lean proofsbuildContentGraph in pipeline/content-graph.ts; status from proof-objects.jsonchapterseditorial (uses[], interprets), solid, vs formal (Lean type / value), dashed purplegen-content-graph-uml.ts (Tool content-graph-uml), run from a foliomeets rules 1 to 8. Rule 9 waits on a folio page to show it; rule 10 on detangle scanning a paper
detangle partitionthe sidecars under detangleResultsDirdetangle groupsenforced / recorded / prosenumbers only; the UML pages show them (rule 10)a drawing of the cross-group edges themselves

A paper or a Lean project is content, and content lives in a folio repository, not here. Apply the rules there through the platform’s generator, and ask the owner before a PR in a mathematics repository.

Papers and Lean are one drawing, not two. buildContentGraph already holds both relations over the same blocks, so gen-content-graph-uml.ts draws them together and keeps them apart by line style. The two drawings it supersedes each re-parsed the sources and drew only one relation: content-graph-analysis.py (blocks) and .github/scripts/generate_dependency_graph.py (Lean, still qou-specific: it imports qou_lib). Neither has been removed: qou still runs the second in CI, and retiring it is the owner’s call.

Status fills come from the same stylesheet as the family colours (--fa-uml-status-* in uml.css). A human review outranks an agentic one, which outranks the Lean status: the order the CI graph used.

Never populate uses[] from the formal graph to make a drawing look connected. The editorial relation is authored; see AGENTS.md and uses-editorial-review. Rule 4 is how the drawing keeps them apart.

  • graph-detanglement: restructuring the graph this skill draws.
  • edge-kinds-and-blast-radius: why edge kinds must stay distinct (rule 4).
  • uml-overview: the schema diagrams, where these rules were first learned.
  • kg-viewer: the shared rule for an HTML viewer that fetches its projection by a path relative to itself.
  • kg-subgraph-layout: one declared sub-graph as one Graphviz diagram in the i2ce Form Documentor’s convention, laid out in the browser with draggable nodes. Its viewer is the one exception to rule 9’s “one viewer”, because it re-lays and moves a graph rather than zooming a picture, and it keeps the same keyboard floor.

folio-assistant — code under the Apache License 2.0, documentation under CC BY 3.0. Generated schema and API references are rebuilt from source on every push to main.

deployed dev