๐ TikZ figure style: layout, typography, and the pre-flight checklist¶
Status: standard, not mechanically checked. Written 2026-08-21. Updated 2026-10-05 (#1012: the house figure-style block, the type floor, the zoned spine). A future review-layer aid (FEATURE-ROADMAP.md's D2) may check some of the binary items below by compiling the figure; nothing here is gated today.
This is the TikZ-specific half of WRITING-STANDARDS.md ยง10's figure contract: that section owns the two-form (TikZ + ASCII) pair, the marker syntax, and originality. This document is what makes the TikZ half of the pair good, before it is kept.
Written for the figure-drawer skill, which survey-writer,
tutorial-writer, thesis-chapter-writer and textbook-chapter-writer
hand a figure to, and for anyone drawing one by hand.
FIGURE-DRAWER.md says how the skill applies this
document. deep-research does not draw ยง10 figures at all, so it has no
reason to link here.
Not covered here: the two-form pair contract, the marker syntax and originality, all of which stay in WRITING-STANDARDS.md ยง10; the ASCII form's own 7-bit alphabet and column limit, also ยง10.
๐ Commit to a layout metaphor before you draw¶
TikZ figures out of this pipeline sprawl when nothing asks for a plan
before the first \node: boxes placed near other boxes, arrows added
as needed. Committing to one metaphor before placing a node turns
"place these nine things" into a constrained problem, which is the
single change most likely to fix sprawl at its source. Each metaphor
maps onto a TikZ idiom to use:
| Metaphor | TikZ idiom |
|---|---|
| Pipeline | a chain, via the positioning library |
| Map | matrix |
| Layered stack | stacked fit layers |
| Control loop | cyclic edges with bend |
| Branching tree | tree |
| Hub-and-spoke network | a star |
| Zoned spine | fit plus backgrounds |
(The first six are PaperBanana's planner supplement, read and not copied; INSPIRATION.md carries the credit.)
The zoned spine is one left-to-right path that carries the argument,
with each zone card a tinted region behind a group of members and its
title outside the card's top-left corner. Reach for it when the figure
has one storyline and several parallel commentaries on points along it.
The card is a fit node drawn on background layer, so it never paints
over a member and re-wording a member widens the card instead of
escaping it. If nothing comments on the path, it is a pipeline.
There is a starting file for every row of that table, in
assets/tikz/. One per metaphor, named for it, each compiling on its
own and each reporting no binary finding from
python -m chitragupta.review figure. Copy the one whose metaphor fits
and re-label it; the choice above exists to hand you a file rather than
a blank tikzpicture. assets/ is copied into a
project by chitragupta init, so they are already beside a scaffolded
draft and there is nothing to download. assets/tikz/README.md says
what each one is for.
Every scaffold places its nodes relative to one another, and none of
them places a node at a coordinate in millimetres. That is the property
to keep when you edit one. A figure laid out in hand-computed absolute
millimetres cannot express "do not collide": re-wording one label, or
setting the figure at a different type size, re-opens every adjacency in
the picture at once and each of them has to be re-checked by eye.
Relative placement and fit layers make most of those collisions
impossible, not merely detectable.
The millimetre rule governs composition, not objects. Every node in
a figure is placed relative to another node, and that does not change.
Inside a pic definition, explicit millimetre coordinates are correct
and expected: a pic is a fixed object drawn once, whose internal
geometry does not reflow when a sibling's label changes. The boundary
is the pic, not the authoring method: a shape generated by a
derivation gets the same carve-out as a hand-drawn one. Hand-placing a
whole figure is still the defect the rule exists to prevent.
Name every node you draw, in whichever idiom. review figure measures a
node's geometry only where the source gives it an explicit (name), so a
picture that names nothing reports no overlap and no protrusion because nothing
was measurable. That output reads exactly like a clean figure, but the figure
was never checked. Both spellings count: \node (a) and child { node (a) ... }.
That includes the well a pic is drawn into: draw a pic only at a
named cgwell (see House shapes).
The tree idiom has one cost that is not visible in the output:
child draws its edges internally rather than as \draw statements, so
the edge list review figure reports for such a figure is empty rather
than short. Confirm a tree's wiring from the source's own nesting.
Every idiom in that table needs a \usetikzlibrary line, and the
figure file carries it at the top, above the tikzpicture:
1 2 | |
The renderer collects those lines and loads the union in the preamble
(chitragupta/render_output/_tikz_libraries.py, #781), so by the time
your figure is \input the library is already there and your own line is
a no-op. Keep the line anyway: it is what lets the same file compile in
the two documents this pipeline does not write, which are
thesis-chapter-writer's fragment inside a user's own thesis and the
minimal probe document WRITING-STANDARDS.md ยง10
tells you to build around one figure. Without any load at all, a picture
that reaches for below=4mm of store does not fall back; it fails the
whole render with a message that names neither the library nor the
figure:
1 | |
Write nothing else about loading. No clearing of
\tikz@library@<name>@loaded, no saving or restoring of
\tikz@node@reset@hook, no \ifdefined guard around the load. Those
workarounds exist in older figure files and they are the bug, not the
fix. \usetikzlibrary is legal in the document body, but a figure file
is \input inside a figure float, and a float is a group: the load
defines the library's macros locally while setting the loaded flag
globally, so the next float skips the load and finds no macros.
Clearing the flag to force a reload is worse: positioning appends its
placement transform to a global hook on every load, so the Nth figure
shifts every node N times. Measured on a three-float document: 71.26pt,
then 128.17pt, then 185.07pt, with pdflatex exiting 0 and nothing but
Overfull \hbox in the log. That is how figures that fit in their
single-chapter PDF spilled off the page of this project's own assembled
book. python -m chitragupta.review figure reports one as
loads-library-by-hand.
Before you reach for a library:
- A missing name takes the whole call down.
\usetikzlibrary{}fails fatally on the comma list rather than skipping the name it cannot resolve, so one typo reads exactly like several missing packages. Probe a library by compiling a two-line document that loads it, not withkpsewhich tikzlibrary<name>.code.tex, which only sees tikz-layer files and so misses every pgf-layer library (arrows.metaispgflibraryarrows.meta.code.tex, and resolves fine). shapes.geometric, notshapes.geometry. The second is not a PGF library at all.
texlive-pictures, which scripts/install_full_pipeline.sh already
installs, ships all of these; there is no extra package to add for any
of them.
๐ Panels in one figure, and the letters they need¶
A figure that shows the same thing under two or three conditions is one
figure with panels, not two or three figures. However many panels it
has, it stays one figure, one figure: marker and one .tex/.txt
pair. One marker per panel would give you separate floats with
separate numbers, which is a row of figures rather than a panelled one.
Nothing here is specific to two: letter as far as the figure goes.
Every panel carries a label node reading (<letter>) <short title>,
and the letter is the panel's position in reading order: (a) for
the first panel, (b) for the second, (c) for the third, and on
through the alphabet. Reading order is left to right, then top to
bottom, so it is the panel's place in the picture that decides its
letter, not the order you happened to draw the nodes in: move a panel
and its letter moves with the position, not with the panel. Draw the
label as an ordinary node under the panel it names; a panel with a bold
title and no letter cannot be pointed at from the prose, which is what
the letter is for. Prose then refers to Figure~\ref{fig:x}(b) for the
second panel, with the letter typed, since these letters are drawn
rather than counted by LaTeX.
Put the same letters in the ASCII twin. Every format except
tex/pdf (md, html and docx alike) renders the .txt and
never sees the picture, so for three of the five formats the ASCII form
is the only place the sub-captions exist. Letter it by taking the four
columns from the gap to the left of each title rather than by
inserting them, or every title after the first slides off the panel it
labels; the letters then overhang their panels by four columns, which
is the intended look and keeps ยง10's ~70-column cap intact.
When a row stops fitting, wrap into another row; never scale the
picture. Three panels of about 48mm each overflow an ordinary text
block by 71.8pt, and pdflatex only warns: Overfull \hbox, in a log
nobody reads, with the figure in the margin. Four of the same
panels as a 2x2 grid fit with no warning at all. Reaching for
\resizebox or scale= instead would shrink the node text below the
document's own size, which "Type and line weight" below forbids for
exactly this reason.
Where the figure is captioned, write the caption in the draft, not in
the figure file. Issue 411 moved that half out of TikZ style's reach:
a Markdown draft writes its caption directly below the figure: marker
(WRITING-STANDARDS.md ยง10), and the renderer, not the figure file,
wraps the marker in a figure float with \caption/\label, so LaTeX's
own counter numbers it. The figure file itself stays a bare
tikzpicture fragment, same shape as assets/tikz/'s own scaffolds,
and copying one no longer includes wrapping it in a float by hand.
thesis-chapter-writer's .tex fragment keeps the older shape, because
it carries no marker for the renderer to wrap: it hand-authors its own
\begin{figure}...\caption{}...\label{fig:<id>}...\end{figure} around
the inline \input. The one change there too: never write
\renewcommand{\thefigure}{N.M}. The figure counter it would override
is exactly what makes \ref agree with the consuming thesis's own
numbering; a hand-typed number is wrong the moment that thesis renumbers
a chapter, which is the defect issue 411 exists to remove.
Do not reach for subcaption, subfig or subfigure. The package
is installed on a typical TeX stack, so this is a choice rather than a
limit. \usetikzlibrary is legal in the document body and
\usepackage is not (! LaTeX Error: Can be used only in preamble.),
so a figure file can load its own TikZ library but never its own
package, and the preamble that would have to load it is not always
ours. thesis-chapter-writer's fragment is \input into the user's own
thesis, whose preamble we never see, so a figure needing subcaption
compiles here and fails there. Drawn letters work in every genre and
change no preamble. The whole cost is \subref and automatic "Figure 1a"
numbering, which is one typed letter per reference.
Record the convention, don't re-decide it. Which letters a venue
wants ((a), (i), A, or titles alone) is a house-style decision
in WRITING-STANDARDS.md ยง8's sense, and belongs
in the dossier's scope.md beside the dialect. draft-reviser reads
that file before every edit; a convention agreed in chat is gone by the
next session.
A three-panel figure, relative placement throughout, verified on this
host: it compiles, every node it names comes back measured, and
python -m chitragupta.review figure reports nothing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
tests/test_tikz_subcaptions.py reads that example out of this file and
compiles it, so it cannot drift into something that no longer works.
The empty fit nodes are what give each panel an extent to hang its
label under; they are grouping boxes, which review figure excludes
from its overlap check by design, and any other way of finding the same
point is equally fine. What the rule asks for is the label and its
letter.
โ Check the drawn figure against this list¶
Concrete defects, not taste:
- Occlusion and overlap: no two nodes' boxes intersect.
- A
fillerases everything under it, not just the line you aimed it at. Filling a label white and drawing it last is the standard way to make a line break at the text instead of striking through it, and it works, but the fill is a rectangle the height of the whole node and the width of the whole label, and it paints out every arrowhead, border and rule that rectangle happens to cover. A one-line label 139mm wide, filled to interrupt one vertical arrow at x=24, also deleted an arrowhead and the top edge of a box at x=70: the edge rendered as a stub going nowhere and the box as three sides. Ask what else is in the band before reaching forfill=white, and note that shrinking the band rarely rescues it:inner ysep=0ptbought 0.06mm in that case, and breaking the label over two lines makes the band taller, not narrower. - One arrow is one
\draw. Two colinear segments that meet, each carrying->, render as an arrowhead where they join as well as at the end, a second head pointing at nothing in the middle of the line. If a line needs to be built in pieces, put the->on the last piece only. This one is mechanically checked, where the pieces meet at a bare coordinate: chaining head-to-tail through a named node is how a pipeline is normally drawn and is not a defect, because TikZ clips each path at the node's boundary so the head points at the node rather than at empty space. - Chaotic routing: arrows should not cross unnecessarily or form spaghetti loops. Left off the mechanical checks that may eventually exist; a bad approximation of "does this route look chaotic" would be worse than not checking it at all, so this one stays a human judgement.
- Illegible or inconsistent type, and low contrast: one font size per role, held consistent across every node in the figure. See "Type and line weight" below for what size that should be.
- Inefficient non-rectangular composition: LaTeX treats a figure as a rectangular box, so anything protruding above the main block forces the surrounding text to wrap around the highest point, wasting vertical space. Keep the picture's bounding box close to the union of its node boxes.
- Conciseness: a node whose text runs past 15 words is too long for a box; cut it or split the node. Shrinking the font to fit more words in is not a fix (see "Type and line weight" below).
- The figure's main point resting on colour alone. Colour is
house-standard here and carries meaning freely (see "The house
palette" below). What it may not do is carry the whole point on its
own, because two readers get none of it: the ASCII twin is 7-bit, and
md,docxandhtmlrender only that form; and a black-and-white print greyscales the TikZ form, wherecgFlowandcgAltland at similar lightness. Position, arrow direction and node labels should still deliver the argument; colour should make it fast. The test is quick: read the.txttwin and ask whether the point still arrives. A secondary distinction living only in colour is fine and expected. - Literal copying: a box-ified copy-paste of prose, with no
visual abstraction, is not a figure. This is
WRITING-STANDARDS.md ยง10's originality rule
applied to figures, and it matters more than it looks: a figure
can launder borrowed wording past every detector in
PLAGIARISM.md, because a citekey is already kept out
of figure files and the gate does not follow
\input.
๐ค Type and line weight¶
Two conventions, both about a figure looking like a native part of the document rather than something pasted in:
- The smallest type in a figure equals the document's body size. That
is a floor, not a target: nothing in a picture may be set smaller than
the paragraph beside it, and a figure is free to go up from there.
The house ramp is two steps:
\normalsizefor every gloss, edge tag, note, legend entry and badge, and\large\bfseriesfor node names and zone titles. Set the floor explicitly as a picture option (\begin{tikzpicture}[cg]does it), rather than inheriting it, so a picture that lands inside a float, a table cell or a\smallgroup does not quietly drop below it.
Shrinking type is never the fix for a box with too many words in it:
that is the conciseness defect, and the fixes are fewer words or a
wider box. Going the other way costs nothing: a larger name gives the
figure a hierarchy the flat version has to spell out in the caption.
Two exceptions, both automatic and both correct: a mathematical
subscript or superscript set by TeX's own script style, and the
\caption, which is the document's, not the figure's.
tests/test_tikz_scaffolds.py holds every file in assets/tikz/ to
this by reading each glyph's printed size back out of the compiled
PDF, not by reading the source, because the size that breaks it
usually arrives from somewhere the figure file cannot see.
- Lines are distinctly thicker than TikZ's own default.
\pgflinewidth defaults to 0.4pt, TikZ's thin key (verified with
\typeout{\the\pgflinewidth} on this host). Draw node borders and
arrows at thick (0.8pt, double the default, same probe) instead;
a figure left at the bare default reads as faint next to a document's
normal text weight. Set it as a picture option, not a restated style:
\begin{tikzpicture}[thick], which the house block's [cg] already
includes. Do not reach for
\tikzset{every node/.style={thick}} to do this: /.style=
replaces whatever every node already held, so a figure with its
own node style (draw, circle, a fill colour) silently loses all of
it the moment this line runs after that one. Verified on this host: a
circular node's bounding box, forced through exactly that sequence,
came out identical to a plain undecorated node's; the circle and the
border were both gone. \begin{tikzpicture}[thick] cannot clobber
anything, because it never touches every node at all.
๐จ The house palette¶
Figures here are in colour, and they use one palette. Five named colours with a role each, and four more that a zoned figure needs. A figure that invents its own hues reads as imported from somewhere else, which a house palette exists to prevent.
1 2 3 4 5 6 7 8 9 | |
(Inside assets/tikz/cg-figstyle.tex each of these lines ends its
} with a %, so the block sets no stray space; see "The house figure
style" below.)
cgWarm and cgRose are one hue per zone. They are Okabe-Ito's
remaining qualitative members, so the extension is the same palette
rather than a second one: a zoned figure needs one hue per zone, and five
roles do not stretch to six zones without reusing one, which reads as a
claim the figure is not making. cgMute is a named grey because
the house styles take a colour as an argument and paste it into a tint
(fill=#1!11): cgInk!55 passed in becomes cgInk!55!11, which xcolor
rejects with "Undefined color". Every role that needs muted ink
therefore needs a name. cgPaper is the card ground, one step off white
for the same reason cgInk is not black.
The values are Okabe-Ito's colourblind-safe qualitative set (Okabe &
Ito, 2008), the usual choice for this purpose, and it costs nothing to
adopt. cgInk is a near-black rather than black, because a pure
black border next to body text set in the same ink reads heavier than
the text does.
Each colour has a role. Use cgAccent for one thing per figure: if
two things are accented, neither is. cgFlow carries the spine; cgAux
is for what happens off to the side, usually dashed as well as coloured.
cgAlt exists so a figure with two node classes does not have to reach
outside the palette.
Tints for fills, the named colour for strokes. fill=cgFlow!8 is a
zone wash that text still reads through; fill=cgFlow is a block of
solid blue with your label lost inside it. Between 8 and 15 is the
usable band, and draw= always takes the colour undiluted.
One contrast caveat: cgAux is light. It is fine as a fill and as a thick
stroke, and not usable for label text on white. cgInk, cgFlow and
cgAccent are all safe for text.
๐ The palette travels inside the figure¶
The palette travels as part of the house block (next section), which
every figure carries whole. Not in a
shared file the figure \inputs, and not in a preamble: the
renderer injects \usepackage{tikz} and nothing else, and
thesis-chapter-writer's fragment is \input directly into the user's
own thesis, which has never heard of this project. A figure that depends
on a colour defined elsewhere compiles here and fails there, which is
the worst of the two orders to fail in.
This is the same discipline the \usetikzlibrary line already follows,
for the same reason, and it means the definitions are duplicated across
figures on purpose. The seven scaffolds in assets/tikz/ all carry the
block, so copying one gets it for free.
๐งฑ The house figure style¶
assets/tikz/cg-figstyle.tex is the palette grown into the rest of the
vocabulary a figure needs to look drawn rather than assembled, so a
figure stops re-deciding corner radius, rule weight, tint depth,
arrowhead and label structure from nothing each time:
| Group | Keys |
|---|---|
| Palette | the nine colours above |
| Picture default | cg: thick, round caps and joins, font=\normalsize, ink colour |
| Node roles | cgbox cgalt cgaux cgwarm cgrose cgplain cgkey cgnone cgopt |
| Shadow | cgsoft (on in every filled role) and cgflat (cancels it; cgnone and cgopt use it) |
| Zone card | cgzone=<colour> and \cgzonelabel{<fit node>}{<colour>}{<text>} |
| Edges | cgedge cgweak cgback cglead=<colour> |
| Annotation | cgtag (a word on an arrow, on its own ground), cgnote |
| Badges | cgbadge=<colour> and \cgstep{<n>}{<node>}{<colour>} |
| Legend | cgswatch=<colour>, \cglegenditem{...} |
| Label ramp | \cglab{<name>}{<gloss>} |
| Icon well | cgwell: a named, invisible, fixed-size node a pic is drawn into |
It also defines five house pics, each drawn into a cgwell; see
House shapes.
The block needs one library line, at the top of the figure with the picture's own:
1 | |
All six ship with texlive-pictures. shadows.blur comes from the
pgf-blur package but is reached by \usetikzlibrary, not
\usepackage, which is what lets the soft shadow travel into a thesis
preamble this project never sees: a figure file cannot load a package.
The block travels between its two marker comments, and
python -m chitragupta figure sync keeps it current. The command
stamps the block into a figure file that lacks one, above its first
\usetikzlibrary or tikzpicture line, and replaces a block from an
older release with the installed one. Everything outside the markers
stays byte for byte as it was. With no paths it walks every
content/drafts/**/figures/*.tex; --check writes nothing and exits 1
if any file is not current. chitragupta draft render reports a stale
or missing block as a [figure] warning naming the command, and renders
anyway: it never rewrites a figure itself.
Never edit inside the markers. A style one figure needs goes in that
picture's own options, or in a \tikzset below the end marker. figure
sync recognises every block this project has released by its digest, so
a region that matches none of them was edited by hand. It leaves that
region alone and prints the diff against the house block, for a person
to move the edit out. A style that three or more figures need is a pull
request against cg-figstyle.tex, not a local override.
Changing the block means a new version. Bump the v<N> in its first
line and add that version's digest to
assets/tikz/cg-figstyle.versions.toml, never editing an existing line
there. tests/test_figure_block.py fails when the block and the register
disagree, so an edit without a version bump cannot reach main. Then
run python -m chitragupta figure sync assets/tikz/*.tex
assets/tikz/exemplars/*.tex to refresh every scaffold and exemplar,
which tests/test_tikz_scaffolds.py still checks byte for byte.
Repeating it once per figure is safe, by the same argument #781
makes for \usetikzlibrary. \tikzset re-defines the same keys to the
same values, and every macro is \providecommand, so N figures in one
document carrying N copies cost N definitions and change nothing. No
global hook is appended to and no loaded flag is set. The one construct
without this property is \tikzset{every node/.style=...} (see "Type and
line weight" above), and the block touches every node only through
.append style inside the cg picture option, scoped to one picture.
๐งฐ House shapes, and the well they sit in¶
The block defines five pics: small conventional shapes drawn in the
house line weight.
pic |
Stands for | Twin says |
|---|---|---|
cgstore |
a drum: any persistent store, such as the ledger, the vector store or a forward index | [ledger] |
cgdocs |
a stack of pages: a document, or a set of them | [PDFs] |
cgfunnel |
any place the set gets smaller: a cap, a top-k, a filter | [cap 3 per source] |
cgchip |
anything that computes: a model, an encoder, a reranker | [bi-encoder] |
cgshield |
the one blocking check, at most once per figure, if at all | [gate] |
Each passes the ASCII twin's test by construction. It sits directly above a node whose label already says the same thing, so the shape is a rendering choice and never a proposition, and the twin loses nothing by dropping it.
Draw every pic into a well. A pic is a path, not a node, so it
has no name, and two things break quietly without one. review figure
measures only named nodes, so an icon colliding with a label reports
clean. And a zone card can fit only named nodes, so an icon placed
above a node pokes through the top of its own zone. cgwell is an
invisible, fixed-size (10mm), named node that the pic is drawn into:
1 2 | |
Every house pic fits inside its well, and
tests/test_tikz_scaffolds.py measures that, so an edit that makes one
overflow fails there. pipeline.tex shows the idiom.
Colour. cg pic colour takes a palette name and applies to that
one \pic. Give it the hue of the zone the shape sits in: the same
drum is green in the lexical zone and blue in the dense route.
This is not an icon set, and it should not become one by accretion.
A shape is added only when a metaphor in the
layout table wants it,
and only when the node's own label already carries the proposition.
Five is the ceiling for now. A sixth needs the same argument made again,
in writing, in its pull request, and a row in the table above;
tests/test_tikz_scaffolds.py fails if the block and the table name
different shapes.
๐จ What the other two forms do with it¶
Two readers receive no colour at all, and this is accepted rather than designed around:
- The ASCII twin is 7-bit, so
md,docxandhtmlget none of it. The twin still has to work as a figure (that is WRITING-STANDARDS.md ยง10's two-form contract, and it is unchanged), but it is not required to reproduce the colour form's every distinction. - A black-and-white print greyscales the TikZ form, where
cgFlowandcgAltland at similar lightness.
So the practical rule is weaker than "never encode meaning in colour" and stronger than nothing: colour may carry meaning, and should not be the only thing carrying the figure's main point. Keep the spine legible through position and arrow direction, which every layout metaphor above already gives you, and let colour do the work of making the figure quick to read rather than possible to read.
๐ Draw for the width it will be printed at¶
The six layout metaphors have no width dimension, and a figure drawn
without one in mind is a figure whose type size is decided by accident.
A single-column figure in a two-column paper sets at roughly half a page
width; the same picture dropped into a thesis sets at nearly double
that. Nothing in the picture changes, but the type does, because
scale= and the consuming document's own width between them decide the
final physical size of every label.
So settle the target width before choosing a metaphor: a hub-and-spoke needs horizontal room a single column does not have, and is the wrong metaphor there however well it fits the content. A layered stack degrades gracefully to a narrow column; a wide map does not.
Never wrap a figure in \resizebox or \scalebox, and never set
scale= on the picture. A picture drawn at its natural size and then
scaled to fit prints its type at whatever the scale factor happens to
be, so the floor in "Type and line weight" becomes a number nobody
chose. Settle the target width first, lay the figure out to it, and
\input it bare. If it does not fit, change the layout (wrap a row, cut
a gloss, drop a stage), never the scale. The row count following from
the type size is the right direction for that decision to run.
This is the rule that actually gets broken, because the scale is
invisible in the figure file: it lives in the draft that \inputs
it. Measured on six hand-written figures in one acmart sigconf paper
(9pt body), each wrapped in \resizebox{\textwidth}{!}{...}:
| Figure | Natural width | Scale applied | Smallest type printed at |
|---|---|---|---|
architecture |
462.8pt | 1.094 | 6.6pt |
rag-stages |
548.9pt | 0.922 | 5.5pt |
retrieval |
519.5pt | 0.975 | 5.9pt |
coauthoring |
520.8pt | 0.972 | 5.8pt |
prompt |
425.6pt | 1.118 | 6.7pt |
revision |
420.6pt | 1.180 | 7.1pt |
Six figures, six effective type sizes, none of them chosen. Laid out
again to fit 506.3pt unscaled, every label printed at 8.97pt or above.
Expect the floor to change layouts, not just sizes: rag-stages went
from two rows to three, and retrieval's dense route wrapped. Three of
those redrawn figures ship as assets/tikz/exemplars/.
The renderer is already clean here: render_output/_figure_captions.py
emits a bare \begin{figure} around the marker with no scale knob, so a
pipeline-rendered figure is drawn at natural size. The defect is what a
hand-written document does, which is exactly thesis-chapter-writer's
case: its fragment is \input into a thesis this project never sees.
The seven scaffolds are each laid out to the article class's 345pt
text width, and a test compiles each one there and fails on an
Overfull \hbox.
No venue table is bundled here, deliberately. Column widths are per-publisher, differ between initial and revised submission, and go stale; a table checked into this repository would be read as current long after it stopped being. If you keep one, keep it the way a maintained one is kept: a date it was accessed, a source URL per entry, and the standing caveat that a passing check is not compliance. Re-read the publisher's live page before you submit.
๐งฟ Rules that are not universal¶
Figure guidance is mostly written for raster images, and several rules that sound authoritative do not transfer to a TikZ picture at all. Named here so nobody spends an afternoon satisfying one:
- "Line art must be 600 or 1000 dpi." A TikZ figure is vector and has no DPI. There is nothing to set and nothing to check. The requirement that does transfer is physical width, above.
- "Convert everything to CMYK / everything to RGB." A colour-space
question about exported raster assets. It reaches a
\input-ed TikZ picture only through whatever the consuming document does at export, which this pipeline does not control and should not pretend to. - "Make the figure bigger so the text is readable." Backwards.
Enlarging the picture enlarges the type and everything else, so it
overflows the column and gets scaled back down. Type size at final
scale is the quantity that matters, and
\footnotesizeinside a picture that is then scaled to 0.8 is the real defect. - "Pick a colourblind-safe palette and accessibility is handled." Necessary, and not the end of it. The house palette is already Okabe-Ito, so the hue question is settled and you should not be re-deciding it per figure. What a safe palette does not address is the two readers who receive no colour at all (the ASCII twin's audience and anyone printing in black and white), which is a redundancy question rather than a palette one.
- "A vector figure is resolution-independent, so size does not matter." True of fidelity, false of legibility, and the second is what a reader notices.
๐ซ Nothing here is a gate¶
This is a checklist an author checks a figure against, not something enforced mechanically today.
python -m chitragupta.review figure reaches the part of it that
geometry can decide, and it is a user-driven aid: a person runs it
over a finished figure, it exits 0 whatever it finds, and nothing
downstream blocks on it. Three consequences follow, and they are the
ones authors get wrong:
- It measures; it never places. TikZ computes the layout, and the
aid compiles the figure and reads back where things landed. So the
fix for a finding is to change what you asked TikZ for (a
sibling distance, arow sep, a different library), and never to nudge a coordinate until the number moves.assets/tikz/is the starting point for that, and none of its seven scaffolds places a node at a coordinate. - Its thresholds are the checker's, not this document's. "An empty band worth more than a third of the height" is a number chosen so ordinary layouts pass; it is not a rule from the list above. It does shape what passes (a two-row diagram spread out generously trips it with nothing wrong), so when a finding and your own eyes disagree, the eyes win and the figure stays.
- A clean report is not a checked figure, and the report now says
which it is. The geometry checks measure only nodes the source
names, so a picture that names none has nothing to measure. That used
to print as
No layout findings, identical to a figure where every check ran and found nothing; it now prints as its own finding, with the declared names that did not come back listed beside it. Name the nodes you draw, in whichever idiom;\node (a)andchild { node (a) ... }both count.
REVIEW.md has the aid among the other nine; CLI.md has its flags and the full statement of the boundary.