@liggi/figkit 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GUIDE.md +303 -0
- package/LICENSE +21 -0
- package/README.md +98 -2
- package/THIRD_PARTY_NOTICES.md +632 -0
- package/bin/fig.mjs +124 -0
- package/dist/viz.iife.js +59 -0
- package/examples/assets/settings-page.html +14 -0
- package/examples/assets/settings-page.png +0 -0
- package/examples/boundary.html +25 -0
- package/examples/charts/bars.json +8 -0
- package/examples/charts/before-after.json +16 -0
- package/examples/charts/dumbbell.json +9 -0
- package/examples/charts/entities.json +17 -0
- package/examples/charts/series.json +46 -0
- package/examples/charts/split.json +10 -0
- package/examples/consolidate.html +33 -0
- package/examples/custom-bar.html +25 -0
- package/examples/flow.html +50 -0
- package/examples/grouping.html +33 -0
- package/examples/line-chart.html +25 -0
- package/examples/screenshot-notes.html +16 -0
- package/examples/sides.html +35 -0
- package/examples/split.html +13 -0
- package/examples/timeline.html +33 -0
- package/package.json +55 -4
- package/runtime/fig.css +188 -0
- package/runtime/fig.js +704 -0
- package/viz/build.mjs +27 -0
- package/viz/draw.js +315 -0
- package/viz/index.js +2 -0
- package/viz/spec.js +177 -0
package/GUIDE.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# figkit: explanatory images
|
|
2
|
+
|
|
3
|
+
Write a small HTML file, run the command below, and get a PNG to embed. The browser does the layout, so you never
|
|
4
|
+
work out coordinates or text widths: boxes size to their text, arrows route themselves around boxes, and the checks
|
|
5
|
+
measure the real result.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npx figkit figure.html [--out figure.png]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The file is just the `<main>` below; no doctype or `<head>` needed. Without `--out` the PNG goes to a temporary folder; the command prints where.
|
|
12
|
+
It prints the PNG, a preview of how small it sits inline on a phone (`figure-inline.png`, for looking at, never to
|
|
13
|
+
embed), and a list of problems. Fix every problem, open
|
|
14
|
+
the images and look, then embed the PNG: ``.
|
|
15
|
+
|
|
16
|
+
## Before you draw
|
|
17
|
+
|
|
18
|
+
- One figure, one point. Write the sentence it must get across; that sentence is the `<h1>`, 14 words at most. Two points are two figures.
|
|
19
|
+
- Draw only when position carries meaning: a flow, parts of a system, what owns or contains what, a chart, a before
|
|
20
|
+
and after. Steps and comparisons with no shape are markdown lists and tables.
|
|
21
|
+
- Use real names and real numbers. Numbers that relate go in a shape (a split bar, bars, a chart), not a sentence.
|
|
22
|
+
- One colour for what the reader must notice (`accent`), at most one more with a second meaning (`second`), everything
|
|
23
|
+
else grey. Say what each colour means in the reading line, in that colour. `good` and `bad` are for results only.
|
|
24
|
+
- Cut every word that explains how you got there. Labels, not sentences.
|
|
25
|
+
|
|
26
|
+
## Explaining a system
|
|
27
|
+
|
|
28
|
+
A question about structure ("what are the entities?", "where does X live?", "how do these relate?", "what loads
|
|
29
|
+
what?", "walk me through it") is answered with a picture first, not with numbered steps.
|
|
30
|
+
|
|
31
|
+
- "What are the entities and how do they relate?" is an `entities` spec (below), not boxes and arrows in HTML. It
|
|
32
|
+
draws references: which record holds a pointer to which. The order work happens in (approve, then label, then
|
|
33
|
+
train) is not a reference; it goes in prose, a list or a separate flow. A record that creates others still points
|
|
34
|
+
at them: draw the pointers it holds, not the steps it runs. Draw every pointer a record holds, even when another
|
|
35
|
+
line reaches the same record by a longer way; leaving one out says the column is not there.
|
|
36
|
+
- Each figure is a plain `fig` with at most 5 boxes and no `group`. More than that is two or three figures in a row,
|
|
37
|
+
not one map: what exists, then one case through it, then what changes. A process or machine the code runs in is a
|
|
38
|
+
`tag` on the box, not a box around it. Use `fig detail` only when the reader asked for the whole map.
|
|
39
|
+
- Under each figure, write only what it cannot show: why, the exceptions, what to do. Never retell it as steps.
|
|
40
|
+
- Put a real instance in each box, with its kind as the smaller line: "Refunds over £500" over "Approval rule",
|
|
41
|
+
not a box that says only "Rule".
|
|
42
|
+
- Label where every flow starts and where its input comes from. An arrow that appears from nowhere gets asked about.
|
|
43
|
+
- When one thing feeds several, draw the copies: three workers each loading the catalogue is three copies and
|
|
44
|
+
three arrows; one shared copy is one box fanning out to the three. "How many" must be visible without reading.
|
|
45
|
+
- Name each thing once, and use that name in the title, the boxes and your prose.
|
|
46
|
+
|
|
47
|
+
## Charts and entities from a spec
|
|
48
|
+
|
|
49
|
+
When the whole figure is one chart or one entities picture of a type below, write a JSON spec instead of HTML. You give the data; figkit
|
|
50
|
+
does the layout, colours and labels. Use HTML (the rest of this guide) for anything else, including a chart that sits
|
|
51
|
+
beside boxes or arrows.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
npx figkit chart.json [--out chart.png]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A spec that breaks a rule is refused with one line per problem; fix them and run it again. Then look at the images
|
|
58
|
+
and embed the PNG, as for any figure.
|
|
59
|
+
|
|
60
|
+
`split`: parts of a whole, as one bar. 2 to 8 parts that add up to the whole; group the small ones as "Other".
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{ "type": "split", "title": "Search brings in most October trial sign-ups",
|
|
64
|
+
"note": "By where the visitor came from", "unit": "%",
|
|
65
|
+
"parts": [ { "label": "Search", "value": 58, "tone": "accent" }, { "label": "Referral", "value": 27 },
|
|
66
|
+
{ "label": "Ads", "value": 11 }, { "label": "Other", "value": 4 } ] }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`bars`: a few numbers compared, one bar each from zero. Up to 12 bars.
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{ "type": "bars", "title": "The new checkout halves the time to pay",
|
|
73
|
+
"note": "Median time from cart to payment", "unit": " s",
|
|
74
|
+
"bars": [ { "label": "Current checkout", "value": 86 }, { "label": "New checkout", "value": 41, "tone": "good" } ] }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`before-after`: one measure that changed, the two values side by side on one scale; `note` (required) says what
|
|
78
|
+
the number counts. For several things changing,
|
|
79
|
+
use `bars` or `dumbbell`.
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{ "type": "before-after", "title": "Late deliveries fell by three quarters after the route change",
|
|
83
|
+
"note": "Share of orders delivered late", "unit": "%",
|
|
84
|
+
"before": { "label": "Before 3 March", "value": 18 }, "after": { "label": "From 3 March", "value": 4.5, "tone": "good" } }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`dumbbell`: up to 8 rows, each a move from one value to another (dates or numbers), drawn as an arrow with the gap
|
|
88
|
+
labelled. `fromLabel` and `toLabel` name the two ends once. `gap` on a row replaces the computed gap text.
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{ "type": "dumbbell", "title": "Every spring release shipped later than planned",
|
|
92
|
+
"fromLabel": "Planned", "toLabel": "Shipped",
|
|
93
|
+
"rows": [ { "label": "Offline mode", "from": "2026-03-02", "to": "2026-03-09" },
|
|
94
|
+
{ "label": "Shared folders", "from": "2026-03-16", "to": "2026-04-06" } ] }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`series`: a line over time (or any ordered x) with exactly one point marked and labelled. Points in order of x; the
|
|
98
|
+
mark's `x` must be one of them. The y axis starts at zero unless `"zero": false`.
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{ "type": "series", "title": "Launch day brought twice the usual sign-ups",
|
|
102
|
+
"note": "Trial sign-ups per day",
|
|
103
|
+
"points": [ { "x": "2026-10-12", "y": 96 }, { "x": "2026-10-13", "y": 91 }, { "x": "2026-10-14", "y": 212 },
|
|
104
|
+
{ "x": "2026-10-15", "y": 131 }, { "x": "2026-10-16", "y": 98 } ],
|
|
105
|
+
"mark": { "x": "2026-10-14", "label": "212, launch day" } }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`entities`: what the things are and which points at which. 2 to 5 `records`, each with an `id` you use in `refs`,
|
|
109
|
+
a `name` in the reader's words, and up to 4 `fields` that say what it holds (put a table name here only if the reader
|
|
110
|
+
needs it). Each of up to 8 `refs` is a pointer the `from` record holds to the `to` record: `count` is how many `from`
|
|
111
|
+
records can point at one `to` (`"many"`, the default, or `"one"`), and `"optional": true` when the pointer may be
|
|
112
|
+
empty. figkit places the records (what is pointed at sits above what points at it) and draws each reference as a line
|
|
113
|
+
joined at the records' names: a dot on the record named, and on the holder a crow's foot when many can point at one or
|
|
114
|
+
a short bar when only one can; dashed when it may be empty; never an arrow, never a key. It draws a two-column layout
|
|
115
|
+
for a phone. Every record must be in a reference. Accent at most one record: the one the title is about. The title
|
|
116
|
+
claims only what the lines show; if it says "everything names X", every record has a line to X. Names and fields are
|
|
117
|
+
in plain words, not in-house terms or button labels.
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{ "type": "entities", "title": "Every order line names one product and one order",
|
|
121
|
+
"records": [ { "id": "order", "name": "Order", "fields": ["placed at"] },
|
|
122
|
+
{ "id": "line", "name": "Order line", "fields": ["quantity"], "tone": "accent" },
|
|
123
|
+
{ "id": "product", "name": "Product", "fields": ["sku"] },
|
|
124
|
+
{ "id": "payment", "name": "Payment" } ],
|
|
125
|
+
"refs": [ { "from": "line", "to": "order" },
|
|
126
|
+
{ "from": "line", "to": "product" },
|
|
127
|
+
{ "from": "payment", "to": "order", "count": "one", "optional": true } ] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
More than 5 records is two figures: the core records, then one corner of it with the core record it hangs off.
|
|
131
|
+
|
|
132
|
+
Flows (boxes and arrows) have no spec: write them in HTML, below.
|
|
133
|
+
|
|
134
|
+
- `title` (required) is the point, 14 words at most. `note` is the reading line under it; `source` a footer.
|
|
135
|
+
- `unit` is printed straight after each number. `show` on an item replaces its printed value, e.g. `"11 of 29"`.
|
|
136
|
+
- `tone` is `accent` for what to notice, `good` or `bad` when it is a result; at most two tones in a chart. Leave the rest grey.
|
|
137
|
+
- `bars` takes `max` to fix the end of the axis (e.g. 100 for percentages) and a short `note` per bar.
|
|
138
|
+
- Labels are plain text, 40 characters at most. Put bars in reading order (before, then after).
|
|
139
|
+
- Examples: `examples/charts/` in the figkit package.
|
|
140
|
+
|
|
141
|
+
## The page
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<main class="fig"> <!-- add "detail" for a map to open and read; "paper" for a light figure for docs -->
|
|
145
|
+
<header>
|
|
146
|
+
<h1>The point, stated. <em>The part to notice</em></h1>
|
|
147
|
+
<p class="read">How to read it. <b class="t-accent">Orange</b> means code.</p>
|
|
148
|
+
</header>
|
|
149
|
+
... the picture ...
|
|
150
|
+
<footer><p>Source and date, if the reader needs them.</p></footer>
|
|
151
|
+
</main>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- In the `<h1>`, `<em>` turns the words accent-coloured italic and `<br>` sets a line break.
|
|
155
|
+
- `fig` is a glance figure, 960 wide: one point, big type, reads inline on a phone. At most 5 boxes, 8 arrows and 70 words.
|
|
156
|
+
- `fig detail` is 1360 wide, for a flow or map the reader asked for and opens full screen; to explain a system, use
|
|
157
|
+
several `fig` figures instead (see Explaining a system). At most 18 boxes, 18 arrows and 190 words.
|
|
158
|
+
Widen it with `style="--w: 1560px"` only when a row will not fit.
|
|
159
|
+
- Height follows the content. There is no fixed canvas to fill.
|
|
160
|
+
- Text must be at least 15px; the theme's sizes all pass. The phone line the command prints is information, and only the problems list needs fixing.
|
|
161
|
+
|
|
162
|
+
## Sides that stack on a phone
|
|
163
|
+
|
|
164
|
+
Two or three columns compared side by side (before and after, now and proposed) shrink to unreadable text inline on
|
|
165
|
+
a phone. Add `sides` to the row or grid that holds them, `<div class="grid sides" style="--cols: 2">`, and the command
|
|
166
|
+
also draws a phone layout, `figure.phone.png`, with the columns one above the other at a narrower width. Embed only
|
|
167
|
+
`figure.png`; a viewer that knows the convention shows the phone layout on narrow screens, and any other shows the
|
|
168
|
+
figure. The checks run on both layouts.
|
|
169
|
+
|
|
170
|
+
- Use it for columns that each make sense alone. Arrows from one column to another get routed round the stack on a
|
|
171
|
+
phone, and the checks report it; leave those figures side by side.
|
|
172
|
+
- If the command says your columns will shrink on a phone, it found columns with no arrows between them that are not
|
|
173
|
+
marked as sides.
|
|
174
|
+
|
|
175
|
+
## Layout: ordinary CSS
|
|
176
|
+
|
|
177
|
+
Use flex and grid; write inline `style` freely.
|
|
178
|
+
|
|
179
|
+
| Helper | Effect |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `row`, `col` | flex row or column. Children of a row stretch to equal height, with text at the top |
|
|
182
|
+
| `grid` | equal columns; set `--cols` |
|
|
183
|
+
| `grow` | child takes an equal share of the width. Long labels then wrap, and no check flags that |
|
|
184
|
+
| `fit` | child keeps its natural width |
|
|
185
|
+
| `middle`, `top`, `bottom` | cross-axis alignment; `middle` stops boxes of different text length stretching |
|
|
186
|
+
| `center`, `between` | main-axis centring or space-between |
|
|
187
|
+
| `tight` | an 8px gap |
|
|
188
|
+
| `arrows`, `wide` | the gap for arrows; `wide` is 1.6 times `arrows`, for arrows with labels |
|
|
189
|
+
|
|
190
|
+
A row or column whose children are joined by arrows needs room for them: add `arrows` or `wide`, or set `column-gap`
|
|
191
|
+
to 44px or more. `between` combines with either. Put things that should line up in the same row, column or grid;
|
|
192
|
+
never nudge with margins.
|
|
193
|
+
|
|
194
|
+
## Boxes
|
|
195
|
+
|
|
196
|
+
```html
|
|
197
|
+
<div class="node accent" id="route">
|
|
198
|
+
<h3>Route to a carrier</h3>
|
|
199
|
+
<p>A table instead of code</p>
|
|
200
|
+
<span class="tag">routing.yaml</span>
|
|
201
|
+
</div>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
| Class | What it is |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `node` | a box; add `accent`, `second`, `good`, `bad` for its colour |
|
|
207
|
+
| `node fill` | solid colour: the one thing to notice, or a start and end point |
|
|
208
|
+
| `node tint` | a wash of its colour: marks a set of boxes that share something, more quietly than `fill` |
|
|
209
|
+
| `node dashed` / `quiet` | optional or background things |
|
|
210
|
+
| `node bare` | text with no box that arrows can still attach to |
|
|
211
|
+
| `node pill` | rounded start or end; its text is centred, other nodes are left-aligned |
|
|
212
|
+
| `decision` | a diamond; put the question in a `<span>`, keep it short |
|
|
213
|
+
| `group` | a dashed container; its first child `<span class="label">` names it. `group flush` lines its contents up with the boxes outside it |
|
|
214
|
+
| `num` | a big number inside a node |
|
|
215
|
+
| `tag` | a small mono line: where something lives |
|
|
216
|
+
| `step` | a numbered badge on a node's corner |
|
|
217
|
+
| `note` | loose grey text; `lane-title` a column heading |
|
|
218
|
+
|
|
219
|
+
## Arrows
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
<fig-arrow from="route" to="load" label="draft"></fig-arrow>
|
|
223
|
+
<fig-arrow from="check" to="route" accent label="Yes: start again" from-side="top" to-side="top"></fig-arrow>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Put them anywhere inside the figure; `from` and `to` are element ids. Any element can be an end: a node, a group, a
|
|
227
|
+
`mark` on a screenshot, a `<span>` of text. Arrows keep clear of boxes and loose text, and arrows sharing an end and
|
|
228
|
+
a colour share a trunk. When a label has no free place, figkit opens the gap between the two boxes (or widens the
|
|
229
|
+
figure a little) and draws again; keep labels to a few words so it rarely has to.
|
|
230
|
+
|
|
231
|
+
- colour: `accent`, `second`, `good`, `bad`; `dashed`; `weight="3"`; `both` for two heads; `head="none"`
|
|
232
|
+
- `blocked`: a route that does not exist. The line stops short of the target at a cross, with no arrowhead
|
|
233
|
+
- `from-side` / `to-side`: `top`, `right`, `bottom`, `left` when the automatic side reads wrong
|
|
234
|
+
- `from-at` / `to-at`: 0 to 1 along that side, to separate two arrows on one side, such as a pair going there and back
|
|
235
|
+
- `route="curve"` for a loose pointer such as a note to a screenshot; `route="straight"` for a direct line
|
|
236
|
+
- `label-at="start"` or `"end"` to move a label
|
|
237
|
+
|
|
238
|
+
If the checks report a crossing or a route through a box, move the boxes; do not fight it with more attributes.
|
|
239
|
+
|
|
240
|
+
## Numbers
|
|
241
|
+
|
|
242
|
+
Parts of a whole:
|
|
243
|
+
|
|
244
|
+
```html
|
|
245
|
+
<fig-split unit="%">
|
|
246
|
+
<part value="58.6" accent>National post</part>
|
|
247
|
+
<part value="32.3">Parcel lockers</part>
|
|
248
|
+
<part value="9.1">Other</part>
|
|
249
|
+
</fig-split>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Segments too thin for their label move it to a line under the bar. `show="11 of 29"` overrides the printed value.
|
|
253
|
+
`unit` is printed straight after the number, so write `unit=" s"` for a space. Parts must add up to a whole; for a real
|
|
254
|
+
time axis with marked points, use Plot below.
|
|
255
|
+
|
|
256
|
+
A few numbers compared:
|
|
257
|
+
|
|
258
|
+
```html
|
|
259
|
+
<fig-bars unit="%" max="100">
|
|
260
|
+
<bar value="58.6" accent>National post</bar>
|
|
261
|
+
<bar value="32.3" note="cheapest">Parcel lockers</bar>
|
|
262
|
+
</fig-bars>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Anything else (lines, dots, intervals, distributions, small multiples) is Observable Plot, already loaded with d3:
|
|
266
|
+
|
|
267
|
+
```html
|
|
268
|
+
<div id="chart"></div>
|
|
269
|
+
<script>
|
|
270
|
+
fig.plot("#chart", ({ Plot, d3, width, theme }) => ({
|
|
271
|
+
height: 360, marginLeft: 44,
|
|
272
|
+
x: { label: null }, y: { label: null, grid: true, ticks: 4 },
|
|
273
|
+
marks: [
|
|
274
|
+
Plot.lineY(rows, { x: "day", y: "n", stroke: theme.soft, strokeWidth: 2.5 }),
|
|
275
|
+
Plot.dot([peak], { x: "day", y: "n", fill: theme.accent, r: 6 }),
|
|
276
|
+
Plot.text([peak], { x: "day", y: "n", text: () => "131 on 3 Feb", dx: 12, textAnchor: "start", fill: theme.accent, fontWeight: 600, fontSize: 17 }),
|
|
277
|
+
],
|
|
278
|
+
}));
|
|
279
|
+
</script>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`theme` has `ink`, `muted`, `soft`, `line`, `accent`, `second`, `good`, `bad`. Draw the series in `soft`, the thing to
|
|
283
|
+
notice in `accent`, and label it directly on the chart; no legends. Start bar axes at zero.
|
|
284
|
+
|
|
285
|
+
## Screenshots
|
|
286
|
+
|
|
287
|
+
```html
|
|
288
|
+
<div class="shot"><img src="/abs/path/page.png">
|
|
289
|
+
<div class="mark" id="m1" style="left: 3%; top: 30%; width: 94%; height: 36%"></div>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="node bare" id="n1"><h3>One row per measure</h3></div>
|
|
292
|
+
<fig-arrow from="n1" to="m1" accent route="curve"></fig-arrow>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## What the checks catch, and what they cannot
|
|
296
|
+
|
|
297
|
+
Measured in the rendered page: text past its box or the frame, text too small, boxes and labels that overlap, arrows
|
|
298
|
+
through boxes, arrows crossing, edges that almost line up, too many words, boxes, arrows or colours, facts strung
|
|
299
|
+
together with separators, a missing or long title.
|
|
300
|
+
|
|
301
|
+
They cannot tell whether the picture is clear, true to the mechanism, or worth drawing. Look at the images yourself,
|
|
302
|
+
and before an important figure goes out, give the PNGs, without your reasoning, to someone who has not seen them
|
|
303
|
+
and ask what they take from it.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason Liggi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,99 @@
|
|
|
1
|
-
#
|
|
1
|
+
# figkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Explanatory figures from a little HTML or a chart spec. You write boxes, arrows and labels as ordinary HTML (or give a
|
|
4
|
+
chart's data as JSON); a headless browser does the layout, figkit routes the arrows, measures the result and tells you
|
|
5
|
+
what is wrong with it: text that is too small or runs out of its box, boxes that overlap, arrows that cross, too many
|
|
6
|
+
words for one picture. You get a PNG to embed in a chat, a doc or an issue.
|
|
7
|
+
|
|
8
|
+
It was written for AI agents that explain things to people, so the rules live in one file an agent can read
|
|
9
|
+
([GUIDE.md](GUIDE.md)), and every problem comes back as a sentence that says what to change. People can use it the
|
|
10
|
+
same way.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Node 20 or later, and a Chromium-based browser: figkit uses Chrome or Edge if one is installed, otherwise run
|
|
15
|
+
`npx playwright install chromium` once.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npm install @liggi/figkit
|
|
19
|
+
npx figkit guide # prints GUIDE.md, the rules for writing figures
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## A figure from HTML
|
|
23
|
+
|
|
24
|
+
Save this as `order.html`:
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<main class="fig">
|
|
28
|
+
<header>
|
|
29
|
+
<h1>An order is <em>checked before it is charged</em></h1>
|
|
30
|
+
</header>
|
|
31
|
+
<div class="row wide">
|
|
32
|
+
<div class="node pill" id="cart"><h3>Basket</h3><p>3 items</p></div>
|
|
33
|
+
<div class="node accent" id="check"><h3>Fraud check</h3><p>Holds 1 order in 200</p></div>
|
|
34
|
+
<div class="node" id="pay"><h3>Charge the card</h3><p>£42.50</p></div>
|
|
35
|
+
</div>
|
|
36
|
+
<fig-arrow from="cart" to="check"></fig-arrow>
|
|
37
|
+
<fig-arrow from="check" to="pay" label="passed"></fig-arrow>
|
|
38
|
+
</main>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
npx figkit order.html --out order.png
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
## A chart from a spec
|
|
48
|
+
|
|
49
|
+
For one chart of a known type (`split`, `bars`, `before-after`, `dumbbell`, `series`), or an `entities` picture of
|
|
50
|
+
records and the references between them, give the data and figkit does the rest, including the layout. Save this as `checkout.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{ "type": "bars", "title": "The new checkout halves the time to pay",
|
|
54
|
+
"note": "Median time from cart to payment", "unit": " s",
|
|
55
|
+
"bars": [ { "label": "Current checkout", "value": 86 },
|
|
56
|
+
{ "label": "New checkout", "value": 41, "tone": "good" } ],
|
|
57
|
+
"source": "Invented example data" }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
npx figkit checkout.json --out checkout.png
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
A spec that breaks a rule is refused before anything is drawn, with one line per problem, for example a title over
|
|
67
|
+
14 words or a split with more than 8 parts.
|
|
68
|
+
|
|
69
|
+
## How problems are reported
|
|
70
|
+
|
|
71
|
+
Each run prints the PNG's path, a preview of how it sits inline on a phone, and the problems the checks found. Here
|
|
72
|
+
is the figure above with `class="row arrows middle"` instead of `class="row wide"`:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
3 problems:
|
|
76
|
+
- #check overlaps the label of arrow check → pay.
|
|
77
|
+
- #pay overlaps the label of arrow check → pay.
|
|
78
|
+
- Almost aligned but not quite: #cart and #check (top edges, 2px apart); #cart and #check (bottom edges, 2px apart);
|
|
79
|
+
#cart and #pay (top edges, 2px apart); #cart and #pay (bottom edges, 2px apart). Put them in one row, column or
|
|
80
|
+
grid so they share the edge.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Fix them and run it again until it says `No problems found by the checks.` The checks measure the rendered page; they
|
|
84
|
+
cannot tell whether the picture is clear or true, so look at it.
|
|
85
|
+
|
|
86
|
+
A figure whose columns are two sides to compare can add `class="sides"` to the row that holds them; figkit then also
|
|
87
|
+
writes `name.phone.png`, the same figure with the columns stacked, for viewers that show it on narrow screens.
|
|
88
|
+
|
|
89
|
+
## Examples
|
|
90
|
+
|
|
91
|
+
`examples/` has a figure of each kind, all with invented data: a flow with a loop and decisions, a boundary between
|
|
92
|
+
two systems, a screenshot with a note, before and after, side by side, a split bar, line and timeline charts made
|
|
93
|
+
with [Observable Plot](https://observablehq.com/plot/), and `examples/charts/` has one spec per chart type. From a
|
|
94
|
+
clone, `npm run examples` renders them all into `out/` and fails if any reports a problem.
|
|
95
|
+
|
|
96
|
+
## Licence
|
|
97
|
+
|
|
98
|
+
MIT. figkit's bundle includes Observable Plot and d3 (ISC); see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). The
|
|
99
|
+
fonts (Newsreader, Geist) are installed as dependencies under the SIL Open Font License.
|