@liggi/figkit 0.3.0 → 0.5.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 +194 -8
- package/README.md +4 -4
- package/bin/fig.mjs +3 -3
- package/dist/viz.iife.js +81 -18
- package/examples/charts/bars-range.json +6 -0
- package/examples/charts/bars-ref.json +6 -0
- package/examples/charts/dots.json +6 -0
- package/examples/charts/estimates.json +7 -0
- package/examples/charts/heatmap-signed.json +5 -0
- package/examples/charts/scatter.json +6 -0
- package/examples/charts/sequence.json +67 -0
- package/examples/charts/series-range.json +6 -0
- package/examples/charts/states.json +71 -0
- package/examples/charts/units.json +4 -0
- package/package.json +1 -1
- package/runtime/fig.css +95 -0
- package/runtime/fig.js +80 -3
- package/viz/build.mjs +1 -1
- package/viz/draw.js +437 -25
- package/viz/sequence-spec.js +60 -0
- package/viz/sequence.js +171 -0
- package/viz/spec.js +201 -10
- package/viz/states-spec.js +161 -0
- package/viz/states.js +203 -0
package/GUIDE.md
CHANGED
|
@@ -5,7 +5,7 @@ work out coordinates or text widths: boxes size to their text, arrows route them
|
|
|
5
5
|
measure the real result.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
npx figkit figure.html [--out figure.png]
|
|
8
|
+
npx @liggi/figkit figure.html [--out figure.png]
|
|
9
9
|
```
|
|
10
10
|
|
|
11
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.
|
|
@@ -28,6 +28,10 @@ the images and look, then embed the PNG: ``.
|
|
|
28
28
|
A question about structure ("what are the entities?", "where does X live?", "how do these relate?", "what loads
|
|
29
29
|
what?", "walk me through it") is answered with a picture first, not with numbered steps.
|
|
30
30
|
|
|
31
|
+
- "What happens when…?", "walk me through a run" and "who calls whom?" are a `sequence` spec (below): the steps in
|
|
32
|
+
order between the parts, not a numbered list or a table of steps.
|
|
33
|
+
- "What's the lifecycle of X?" and "what states can it be in?" are a `states` spec (below): the states one thing
|
|
34
|
+
moves through and what moves it on, not boxes and arrows in HTML.
|
|
31
35
|
- "What are the entities and how do they relate?" is an `entities` spec (below), not boxes and arrows in HTML. It
|
|
32
36
|
draws references: which record holds a pointer to which. The order work happens in (approve, then label, then
|
|
33
37
|
train) is not a reference; it goes in prose, a list or a separate flow. A record that creates others still points
|
|
@@ -46,12 +50,12 @@ what?", "walk me through it") is answered with a picture first, not with numbere
|
|
|
46
50
|
|
|
47
51
|
## Charts and entities from a spec
|
|
48
52
|
|
|
49
|
-
When the whole figure is one chart
|
|
53
|
+
When the whole figure is one chart, one entities picture, one sequence or one lifecycle of a type below, write a JSON spec instead of HTML. You give the data; figkit
|
|
50
54
|
does the layout, colours and labels. Use HTML (the rest of this guide) for anything else, including a chart that sits
|
|
51
55
|
beside boxes or arrows.
|
|
52
56
|
|
|
53
57
|
```
|
|
54
|
-
npx figkit chart.json [--out chart.png]
|
|
58
|
+
npx @liggi/figkit chart.json [--out chart.png]
|
|
55
59
|
```
|
|
56
60
|
|
|
57
61
|
A spec that breaks a rule is refused with one line per problem; fix them and run it again. Then look at the images
|
|
@@ -74,6 +78,21 @@ and embed the PNG, as for any figure.
|
|
|
74
78
|
"bars": [ { "label": "Current checkout", "value": 86 }, { "label": "New checkout", "value": 41, "tone": "good" } ] }
|
|
75
79
|
```
|
|
76
80
|
|
|
81
|
+
A bar chart that compares every bar with one level, such as production or the current version, takes `ref`: the
|
|
82
|
+
level's `value` and a `label` naming it in words ("Production (ash-prod-27b)", never a bare number). figkit draws it as
|
|
83
|
+
its own first row in the second colour, with a dashed line from its end down through every bar; bars above it are
|
|
84
|
+
accented and the rest are grey, and the key says what the colour means. The colours are figkit's, so a `tone` on a bar
|
|
85
|
+
is refused, and `note` is required so the reader knows what the level's number counts. With ranges, a bar counts as
|
|
86
|
+
above only when its whole range is above. `show` on the ref replaces its printed value, e.g. `"50%, a tie"`.
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{ "type": "bars", "title": "Two of the four new layouts beat the current page",
|
|
90
|
+
"note": "Share of visitors who finished checkout", "unit": "%",
|
|
91
|
+
"ref": { "label": "Current page", "value": 61 },
|
|
92
|
+
"bars": [ { "label": "One-page form", "value": 68 }, { "label": "Saved cards first", "value": 64 },
|
|
93
|
+
{ "label": "Address lookup", "value": 60 }, { "label": "Guest by default", "value": 57 } ] }
|
|
94
|
+
```
|
|
95
|
+
|
|
77
96
|
`before-after`: one measure that changed, the two values side by side on one scale; `note` (required) says what
|
|
78
97
|
the number counts. For several things changing,
|
|
79
98
|
use `bars` or `dumbbell`.
|
|
@@ -146,7 +165,9 @@ different things on the same scale.
|
|
|
146
165
|
```
|
|
147
166
|
|
|
148
167
|
`heatmap`: a grid of values over two dimensions (day by hour, team by week), coloured from pale to dark blue: darker
|
|
149
|
-
is always more.
|
|
168
|
+
is always more. When any value is below zero (a change, a gap, a correlation), the grid is signed and the colour runs
|
|
169
|
+
from orange below zero through pale at zero to blue above, equally strong at equal distances from zero, with the
|
|
170
|
+
numbers printed with their sign; the scale shows 0 between the two halves. Numbers keep the decimal places you give. Up to 12 `rows` and 31 `columns`; `values` is one array per row. `note` (required) says what the
|
|
150
171
|
colour counts, and the scale is drawn above the grid. Here the colour is the measure, so the one-accent rule moves:
|
|
151
172
|
`mark` outlines the one cell to notice in the accent colour and names it beside the scale. Cells show their numbers
|
|
152
173
|
when there is room. Columns label themselves at even steps, so give hours as "12am", "1am", … and they read 12am,
|
|
@@ -159,6 +180,96 @@ when there is room. Columns label themselves at even steps, so give hours as "12
|
|
|
159
180
|
"mark": { "row": "Wed", "column": "12pm", "label": "Busiest: Wed noon" } }
|
|
160
181
|
```
|
|
161
182
|
|
|
183
|
+
### How sure a number is
|
|
184
|
+
|
|
185
|
+
Most numbers are counted from a sample: 71% of 75 days is not 71% of 7,500. When the reader will compare such
|
|
186
|
+
numbers, draw how sure each one is.
|
|
187
|
+
|
|
188
|
+
Ranges on `bars`, `series` and `lines`: give each bar or point a `low` and a `high` around its value, and say once
|
|
189
|
+
in `range` what they are, in the reader's words ("likely range (95%)", "lowest to highest week"); it is printed under
|
|
190
|
+
the reading line beside the mark it describes. A bar steps back to a pale fill, its range is drawn across its end in
|
|
191
|
+
the bar's colour and the range's ends are printed after the value; a series or line gets a band. Accent the bar whose
|
|
192
|
+
range the title is about. Give every item a
|
|
193
|
+
range or none, since one without reads as certain. figkit refuses a range that does not contain its value, a range
|
|
194
|
+
with no `range` text, and a bar range below zero. `n` on a bar prints its sample size under its label, as "14 runs" when `each` names
|
|
195
|
+
the things counted (`"each": "runs"`), else "n = 14"; give every bar one or none. `ref` on a `series`,
|
|
196
|
+
`{ "y": 67, "label": "Jul to mid-Oct: 67%" }`, draws a dashed level to compare the line with, named in the key line. A percentage of fewer than 10 things is refused: show it as a count (`"show": "3 of 6"`) or draw `units`.
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{ "type": "bars", "title": "The new reply check catches more, but the ranges still overlap",
|
|
200
|
+
"note": "Share of unsafe replies caught, on the same test set", "unit": "%", "max": 100, "range": "likely range (95%)", "each": "replies",
|
|
201
|
+
"bars": [ { "label": "Current check", "value": 64, "low": 53, "high": 74, "n": 72 },
|
|
202
|
+
{ "label": "New check", "value": 78, "low": 67, "high": 86, "n": 72, "tone": "accent" } ] }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The title claims only what the ranges allow: when they overlap, "catches more" needs a "but".
|
|
206
|
+
|
|
207
|
+
`estimates`: signed results with their ranges, one row each around a zero line (a forest plot): a gap between two
|
|
208
|
+
groups, a change against control, a correlation. Use it when the sign is the point and each number has a range: "+7.4
|
|
209
|
+
(+2.7 to +12.4)". For numbers counted up from zero, use `bars`; for a move from one value to another, `dumbbell`. Every
|
|
210
|
+
row needs `low` and `high`, and `range` says once what they are. `note` (required) says what the number is and which
|
|
211
|
+
way round ("matched minus not matched"). `sides` names what each side of zero means, printed under the axis and meeting at zero; keep each to two or three words, or figkit reports it. figkit
|
|
212
|
+
chooses the colours: a row whose range clears zero is filled in the accent colour, one whose range includes zero is a
|
|
213
|
+
hollow grey dot, and the key says so; a `tone` is refused. With `"better": "higher"` or `"lower"`, a row clear of zero
|
|
214
|
+
is `good` or `bad` instead. Up to 12 rows; `n` and `each` as on `bars`. Numbers keep the decimal places you give, with
|
|
215
|
+
their sign. A range that does not contain its value, or a range of no width, is refused.
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{ "type": "estimates", "title": "Free delivery lifts orders in two regions; the north's range includes zero",
|
|
219
|
+
"note": "Change in orders per 100 visitors, free delivery minus paid", "range": "95% interval",
|
|
220
|
+
"sides": { "below": "Paid better", "above": "Free better" },
|
|
221
|
+
"rows": [ { "label": "South", "value": 2.4, "low": 1.1, "high": 3.7 },
|
|
222
|
+
{ "label": "Midlands", "value": 1.8, "low": 0.6, "high": 3.0 },
|
|
223
|
+
{ "label": "North", "value": 0.7, "low": -0.9, "high": 2.3 } ] }
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The title says a result only for rows whose range clears zero; a row whose range includes zero is "no clear change",
|
|
227
|
+
not a small gain.
|
|
228
|
+
|
|
229
|
+
`dots`: every value as a dot, for a sample small enough to look at one by one (up to 300 a group, 600 in all), in 1
|
|
230
|
+
to 6 groups on one axis. Use it instead of a bar of the average when the spread or a few odd values are the point.
|
|
231
|
+
Dots that would overlap stack upward and downward, so none hides another, and each group's count is printed. `note`
|
|
232
|
+
(required) says what one dot is. `each` names what a dot is, so counts read "50 runs". `summary` draws the `"median"` or `"mean"` as a line across each group, labelled;
|
|
233
|
+
`mark` rings one value and labels it; `zero` starts the axis at zero; `tone` on a group colours its dots.
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{ "type": "dots", "title": "Most builds finish in four minutes; a few take over ten",
|
|
237
|
+
"note": "One dot per build: minutes from push to result", "unit": " min", "summary": "median", "zero": true, "each": "builds",
|
|
238
|
+
"groups": [ { "label": "Main branch", "values": [3.8, 4.1, 3.6, 11.8, 4.0, 12.6, 4.2] },
|
|
239
|
+
{ "label": "Pull requests", "values": [4.9, 5.2, 4.6, 5.8, 5.1], "tone": "accent" } ],
|
|
240
|
+
"mark": { "group": "Main branch", "value": 12.6, "label": "12.6 min, cache miss" } }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`scatter`: two measures against each other, one dot per thing measured, up to 1000. It is its own type, not a mode
|
|
244
|
+
of `dots`, because it takes pairs and answers a different question: whether one measure moves with the other. `x`
|
|
245
|
+
and `y` each need a `label` saying what they measure, and take `unit`, `"log": true` for measures that span several
|
|
246
|
+
powers of ten (every value above zero; the axis runs to whole powers of ten), `zero`, and `time` (`"seconds"`,
|
|
247
|
+
`"minutes"`, `"hours"` or `"days"`, the unit the values are in) so ticks read 1 min, 1 h, 1 day, 1 wk.
|
|
248
|
+
`"medians": true` draws the median y of each band of x (each power of ten on a log axis, else fifths) in the accent
|
|
249
|
+
colour, with the middle half of the band's dots shaded behind it, for bands with 5 or more dots: the relation, or its
|
|
250
|
+
absence, drawn without fitting a model. `label` names a point beside it, at most 5; `tone` colours it.
|
|
251
|
+
figkit draws no fitted trend line: it claims more than dots do. Say the relation in the title only when the dots or
|
|
252
|
+
the medians show it, and "barely" when they mostly do not.
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{ "type": "scatter", "title": "Bigger pull requests wait longer for review",
|
|
256
|
+
"note": "One dot per merged pull request",
|
|
257
|
+
"x": { "label": "Lines changed", "log": true }, "y": { "label": "Time to first review", "time": "hours", "zero": true },
|
|
258
|
+
"points": [ { "x": 12, "y": 1.5 }, { "x": 120, "y": 5.5 }, { "x": 540, "y": 22, "label": "Search rewrite", "tone": "accent" } ] }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`units`: a small count made concrete, "11 of 29" as 29 squares with 11 filled. `total` is at most 100: beyond that
|
|
262
|
+
the squares cannot be counted by eye, and a `split` bar of the shares reads better. Squares sit ten to a row and fill
|
|
263
|
+
column by column, so each part is one block from the left. The blocks show counts, not order: if when things happened
|
|
264
|
+
matters, draw it over time instead. Up to 3 `parts` fill squares in order (the first in the accent colour
|
|
265
|
+
unless it has a `tone`); `rest` (required) names the empty squares, so at least one is left. Counts are whole things.
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{ "type": "units", "title": "11 of the 29 flagged days were real problems",
|
|
269
|
+
"note": "One square per day the monitor flagged in October", "total": 29,
|
|
270
|
+
"parts": [ { "label": "real problems", "count": 11 } ], "rest": "false alarms" }
|
|
271
|
+
```
|
|
272
|
+
|
|
162
273
|
`entities`: what the things are and which points at which. 2 to 5 `records`, each with an `id` you use in `refs`,
|
|
163
274
|
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
|
|
164
275
|
needs it). Each of up to 8 `refs` is a pointer the `from` record holds to the `to` record: `count` is how many `from`
|
|
@@ -183,11 +294,84 @@ in plain words, not in-house terms or button labels.
|
|
|
183
294
|
|
|
184
295
|
More than 5 records is two figures: the core records, then one corner of it with the core record it hangs off.
|
|
185
296
|
|
|
186
|
-
|
|
297
|
+
`sequence`: who calls whom, in order. Use it when the question is "what happens when…", "walk me through a run",
|
|
298
|
+
"who starts what": the order is the point and more than one part takes part. A table of steps (step, who starts it,
|
|
299
|
+
who is involved) is this picture without the shape; draw it instead. A flow of boxes and arrows shows what connects
|
|
300
|
+
to what, not when; use it for structure. 2 to 5 `participants`, left to right in the order the reader meets them, each
|
|
301
|
+
an `id`, a `name` and an optional `kind` (where it runs: "GitHub Actions", "Cloud Run job"). 2 to 12 `messages` in
|
|
302
|
+
the order they happen, each from one participant to another with a `label` saying what is asked for or sent. figkit
|
|
303
|
+
numbers the steps, so your prose can say "step 4". `"reply": true` is the answer to an earlier call, drawn dashed;
|
|
304
|
+
leave replies out when nothing comes back that the reader needs. A message from a participant to itself is work it
|
|
305
|
+
does on its own. `note` adds one short line under a step: a condition ("only if the tests pass"), a wait ("up to an
|
|
306
|
+
hour"). `repeats` frames steps `from` to `to` (numbered from 1) that run more than once, with a `label` such as "For
|
|
307
|
+
each model". Accent one participant, or the steps the title is about, not both. The title claims only what the
|
|
308
|
+
steps show.
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{ "type": "sequence", "title": "A failed card charge is retried before the shopper hears anything",
|
|
312
|
+
"participants": [ { "id": "app", "name": "Shop app", "kind": "browser" }, { "id": "api", "name": "Checkout API" },
|
|
313
|
+
{ "id": "pay", "name": "Card processor" } ],
|
|
314
|
+
"messages": [ { "from": "app", "to": "api", "label": "Place order" },
|
|
315
|
+
{ "from": "api", "to": "pay", "label": "Charge the card", "tone": "accent" },
|
|
316
|
+
{ "from": "pay", "to": "api", "label": "Approved or declined", "reply": true },
|
|
317
|
+
{ "from": "api", "to": "app", "label": "Order number", "reply": true } ],
|
|
318
|
+
"repeats": [ { "from": 2, "to": 3, "label": "Up to 3 tries" } ] }
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
More than 5 participants or 12 steps is two figures: the whole run with minor parts merged into one participant, then
|
|
322
|
+
the stage that needs detail. Leave out steps that only log or record what happened unless the question is about them.
|
|
323
|
+
|
|
324
|
+
`states`: the states one thing moves through and what moves it on: a pull request, an order, a ticket, a person
|
|
325
|
+
in a study. Use it for "what's the lifecycle of X?", "what states can it be in?", "what happens after it's
|
|
326
|
+
approved?": the question is about one thing and where it can go next. A `sequence` is about several parts taking turns;
|
|
327
|
+
a flow is about what connects to what. 2 to 7 `states`, each an `id`, a `name` and an optional one-line `note` (a rule
|
|
328
|
+
that holds there: "A restart needs a new review"). Mark the one state things begin in `"start": true`, and the states
|
|
329
|
+
they stop in `"end": true`: nothing ever leaves an end, so a mark that comes and goes on a state (the current
|
|
330
|
+
default, a pin) is that state's `note`, not an end. `entry` labels the way in ("Card created"). 1 to 10 `moves`, each `from` one state `to`
|
|
331
|
+
another with a `label` saying what makes the move happen. figkit draws the longest run from the start down one
|
|
332
|
+
column, the other states beside it, and a move back to an earlier state as a loop up the left side. `from` can be a
|
|
333
|
+
list of states for one move any of them can make (retire, cancel), drawn as one bracket beside them and one arrow; `back` names the same
|
|
334
|
+
move the other way (`"back": "Restore"`), drawn with a head at both ends.
|
|
335
|
+
|
|
336
|
+
Counts: `count` on every move is how many of the things made that move at least once, `each` names them
|
|
337
|
+
(`"people"`), and the start's `count` is how many entered. `stayed` on a state that is not an end is how many went no further
|
|
338
|
+
than it. `"shares": true` adds each move's share of those who
|
|
339
|
+
reached the state it leaves; a loop back brings nothing new into that base. figkit says what the counts mean under
|
|
340
|
+
the reading line, and refuses counts on only some moves, a move counted higher than reached the state it leaves,
|
|
341
|
+
moves out of a state passed through once adding up to more than reached it, and a share of fewer than 10.
|
|
342
|
+
|
|
343
|
+
It also refuses a lifecycle with no start or two, an end with a way out, a state nothing leaves that is not marked
|
|
344
|
+
end, a state the start cannot reach, a move to its own state (put that in the state's `note`), and two moves between
|
|
345
|
+
the same states the same way. Accent one state or the moves the title is about. The title claims only what the moves
|
|
346
|
+
show.
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{ "type": "states", "title": "An order waits after a declined card until a new card is added",
|
|
350
|
+
"entry": "Shopper checks out",
|
|
351
|
+
"states": [ {"id": "waiting", "name": "Awaiting payment", "start": true},
|
|
352
|
+
{"id": "declined", "name": "Card declined", "note": "Shopper emailed", "tone": "accent"},
|
|
353
|
+
{"id": "paid", "name": "Paid"},
|
|
354
|
+
{"id": "packed", "name": "Packed"},
|
|
355
|
+
{"id": "shipped", "name": "Shipped", "end": true},
|
|
356
|
+
{"id": "cancelled", "name": "Cancelled", "end": true} ],
|
|
357
|
+
"moves": [ {"from": "waiting", "to": "paid", "label": "Charge succeeds"},
|
|
358
|
+
{"from": "waiting", "to": "declined", "label": "Charge fails"},
|
|
359
|
+
{"from": "declined", "to": "waiting", "label": "New card added"},
|
|
360
|
+
{"from": "paid", "to": "packed", "label": "Warehouse picks"},
|
|
361
|
+
{"from": "packed", "to": "shipped", "label": "Carrier collects"},
|
|
362
|
+
{"from": ["paid", "packed"], "to": "cancelled", "label": "Shopper cancels"} ] }
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
A states picture holds 7 states, not the 5 boxes of other figures: each state is a short name read in order down one
|
|
366
|
+
path, and an ordinary lifecycle is four or five steps plus one or two ways off it. More than 7 is two figures: the
|
|
367
|
+
main path with the side branches merged into one state, then the branch that needs detail.
|
|
368
|
+
|
|
369
|
+
Flows (boxes and arrows) have no spec: write them in HTML, below. A flow says what connects to what; when the order
|
|
370
|
+
things happen in is the point, use `sequence`; when one thing's states are the point, use `states`.
|
|
187
371
|
|
|
188
372
|
- `title` (required) is the point, 14 words at most. `note` is the reading line under it; `source` a footer.
|
|
189
373
|
- `"style": "report"` draws a light chart in one sans font, for a chart that sits in a page: a research report, a doc.
|
|
190
|
-
It also draws a phone layout, `chart.phone.png
|
|
374
|
+
It also draws a phone layout, `chart.phone.png`, as do `estimates`, `heatmap` and `bars` with a `ref` in either style. Leave it out for the default dark figure for a dark chat.
|
|
191
375
|
- Keep `note` to what is measured, and "lower is better" where it applies; dates and method go in `source`.
|
|
192
376
|
- `unit` is printed straight after each number. `show` on an item replaces its printed value, e.g. `"11 of 29"`.
|
|
193
377
|
- `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.
|
|
@@ -320,7 +504,8 @@ A few numbers compared:
|
|
|
320
504
|
```
|
|
321
505
|
|
|
322
506
|
Grouped bars, several lines, small multiples and heat maps have specs (`groups`, `lines`, `panels`, `heatmap`); use
|
|
323
|
-
them when the chart is the whole figure.
|
|
507
|
+
them when the chart is the whole figure. Dots, ranges, signed estimates, scatters and small counts have specs too
|
|
508
|
+
(`dots`, `estimates`, `scatter`, ranges on `bars` and `series`, `units`). Anything else (a distribution of thousands, a chart beside boxes) is
|
|
324
509
|
Observable Plot, already loaded with d3:
|
|
325
510
|
|
|
326
511
|
```html
|
|
@@ -353,7 +538,8 @@ notice in `accent`, and label it directly on the chart; no legends. Start bar ax
|
|
|
353
538
|
|
|
354
539
|
## What the checks catch, and what they cannot
|
|
355
540
|
|
|
356
|
-
Measured in the rendered page: text past its box or the frame, text too small,
|
|
541
|
+
Measured in the rendered page: text past its box or the frame, text too small, text too faint against what is drawn
|
|
542
|
+
behind it (a contrast under 3 to 1), boxes and labels that overlap, arrows
|
|
357
543
|
through boxes, arrows crossing, edges that almost line up, too many words, boxes, arrows or colours, facts strung
|
|
358
544
|
together with separators, a missing or long title.
|
|
359
545
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Node 20 or later, and a Chromium-based browser: figkit uses Chrome or Edge if on
|
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
npm install @liggi/figkit
|
|
19
|
-
npx figkit guide # prints GUIDE.md, the rules for writing figures
|
|
19
|
+
npx @liggi/figkit guide # prints GUIDE.md, the rules for writing figures
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## A figure from HTML
|
|
@@ -39,7 +39,7 @@ Save this as `order.html`:
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
npx figkit order.html --out order.png
|
|
42
|
+
npx @liggi/figkit order.html --out order.png
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|

|
|
@@ -47,7 +47,7 @@ npx figkit order.html --out order.png
|
|
|
47
47
|
## A chart from a spec
|
|
48
48
|
|
|
49
49
|
For one chart of a known type (`split`, `bars`, `before-after`, `dumbbell`, `series`, `groups`, `lines`, `panels`,
|
|
50
|
-
`heatmap`), or an `entities` picture of records and the references between them, give the data and figkit does the
|
|
50
|
+
`heatmap`, `dots`, `scatter`, `units`), or an `entities` picture of records and the references between them, give the data and figkit does the
|
|
51
51
|
rest, including the layout. Add `"style": "report"` for a light chart that sits in a page. Save this as `checkout.json`:
|
|
52
52
|
|
|
53
53
|
```json
|
|
@@ -59,7 +59,7 @@ rest, including the layout. Add `"style": "report"` for a light chart that sits
|
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
```
|
|
62
|
-
npx figkit checkout.json --out checkout.png
|
|
62
|
+
npx @liggi/figkit checkout.json --out checkout.png
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|

|
package/bin/fig.mjs
CHANGED
|
@@ -46,7 +46,7 @@ if (/\.json$/i.test(src)) {
|
|
|
46
46
|
console.log(`This chart spec has ${spec.problems.length} problem${spec.problems.length === 1 ? '' : 's'}:\n${spec.problems.map((p) => ` - ${p}`).join('\n')}`);
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
|
-
const cls = [spec.spec.type === 'entities' ? 'entities-fig' : 'chart', spec.spec.type === 'panels' ? 'panels-fig' : '', spec.spec.style ?? ''].filter(Boolean).join(' ');
|
|
49
|
+
const cls = [spec.spec.type === 'entities' ? 'entities-fig' : spec.spec.type === 'sequence' ? 'sequence-fig' : spec.spec.type === 'states' ? 'states-fig' : 'chart', spec.spec.type === 'panels' ? 'panels-fig' : '', spec.spec.style ?? ''].filter(Boolean).join(' ');
|
|
50
50
|
body = `<main class="fig ${cls}"><script type="application/json" id="figkit-spec">${JSON.stringify(spec.spec).replace(/</g, '\\u003c')}</script></main>`;
|
|
51
51
|
} else if (!/class="[^"]*\bfig\b/.test(body)) body = `<main class="fig">\n${body}\n</main>`;
|
|
52
52
|
await ensureBuilt();
|
|
@@ -101,7 +101,7 @@ try {
|
|
|
101
101
|
if (phone) await page.addInitScript(() => { window.__figPhone = true; });
|
|
102
102
|
await page.goto(pathToFileURL(wrapped).href);
|
|
103
103
|
await page.waitForFunction('window.__figDone === true', null, { timeout: 30000 });
|
|
104
|
-
const r = await page.evaluate(() => ({ problems: window.fig.problems, info: window.fig.info, sides: !!document.querySelector('.fig .sides, .fig .entities, .fig .panels, .fig.report'), entities: !!document.querySelector('.fig .entities'), panels: !!document.querySelector('.fig .panels'), stack: !!document.querySelector('.fig .sides') }));
|
|
104
|
+
const r = await page.evaluate(() => ({ problems: window.fig.problems, info: window.fig.info, sides: !!document.querySelector('.fig .sides, .fig .entities, .fig .sequence, .fig .states, .fig .panels, .fig .estimates, .fig .heatmap, .fig fig-bars.has-ref, .fig.report'), states: !!document.querySelector('.fig .states'), entities: !!document.querySelector('.fig .entities'), sequence: !!document.querySelector('.fig .sequence'), panels: !!document.querySelector('.fig .panels'), stack: !!document.querySelector('.fig .sides') }));
|
|
105
105
|
await page.locator('.fig').first().screenshot({ path: shot });
|
|
106
106
|
await page.close();
|
|
107
107
|
return { ...r, problems: [...errors.map((e) => `Script error: ${e}`), ...r.problems] };
|
|
@@ -116,7 +116,7 @@ try {
|
|
|
116
116
|
|
|
117
117
|
const { info } = desk;
|
|
118
118
|
const lines = [`Figure: ${png} (${info.width * 2}×${info.height * 2})`];
|
|
119
|
-
if (mob) lines.push(`Phone layout: ${phonePng} (${mob.info.width * 2}×${mob.info.height * 2}), ${desk.entities ? 'the records in two columns' : desk.panels ? 'the panels stacked' : desk.stack ? 'the sides stacked' : 'at phone width'}, for a viewer to show on a phone`);
|
|
119
|
+
if (mob) lines.push(`Phone layout: ${phonePng} (${mob.info.width * 2}×${mob.info.height * 2}), ${desk.entities ? 'the records in two columns' : desk.sequence ? 'the participants narrower' : desk.states ? 'the states narrower' : desk.panels ? 'the panels stacked' : desk.stack ? 'the sides stacked' : 'at phone width'}, for a viewer to show on a phone`);
|
|
120
120
|
lines.push(`Phone inline: ${inline} (preview only)`);
|
|
121
121
|
const ph = (mob ?? desk).info.phone;
|
|
122
122
|
const sizes = [['the title', ph?.title], ['headings', ph?.heading], ['body', ph?.body]].filter(([, v]) => v != null).map(([k, v]) => `${k} ${v}px`);
|