@pithyjs/pidl-catalog 0.1.0-beta.1 → 0.1.0-beta.3

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.
@@ -0,0 +1,343 @@
1
+ {
2
+ "kind": "component",
3
+ "id": "ui.line",
4
+ "name": "Line",
5
+ "displayName": "Line Chart",
6
+ "version": "0.1.0",
7
+ "level": "atom",
8
+ "domain": "data",
9
+ "bidi": true,
10
+ "description": "Up to six named series over one shared category axis, drawn as stroked lines with a marker on every reading. The chart for COMPARING trends: several quantities measured at the same points, on one domain, so their shapes can be read against each other. The plot is SVG because a polyline is a path; the axis labels beneath it are DOM, so a long category name can ellipsis, a numeric axis can use tabular figures, and the type rescales with the reader's own font setting. A colour key is on by default whenever there is more than one series, because a line is named by its colour and by nothing else on screen. The drawing, the axis and the key are all aria-hidden; a visually-hidden data table with one column per series carries every exact reading. Readings may be negative. A non-finite reading is a hole the line breaks across. Loading, empty and error states are first-class.",
11
+ "props": [
12
+ {
13
+ "name": "label",
14
+ "label": "Accessible name",
15
+ "type": "string",
16
+ "required": true,
17
+ "placeholder": "Latency by region, p95",
18
+ "description": "Captions the visually-hidden data table screen readers read instead of the graphic. Required — it is the chart's accessible name."
19
+ },
20
+ {
21
+ "name": "series",
22
+ "label": "Series",
23
+ "type": "LineSeries[] | Signal<LineSeries[]>",
24
+ "required": true,
25
+ "reactive": true,
26
+ "placeholder": [
27
+ { "name": "eu-west", "values": [120, 118, 131, 126, 119] },
28
+ { "name": "us-east", "values": [96, 101, 99, 143, 104] }
29
+ ],
30
+ "description": "The lines, in draw order. One to six: past the sixth the categorical palette has no slot, and a seventh series is painted neutral AND dashed rather than being given the first series' colour. Every series is measured against ONE domain, which is what makes their shapes comparable.",
31
+ "itemSchema": {
32
+ "name": {
33
+ "type": "string",
34
+ "required": true,
35
+ "description": "Series name. Appears in the key and as a column header in the accessible table — required, because colour alone cannot name a line. An empty or whitespace-only name becomes 'Series N'."
36
+ },
37
+ "values": {
38
+ "type": "number[]",
39
+ "required": true,
40
+ "description": "The readings, in axis order. Each must be a finite number; either sign is allowed. A non-finite entry is a hole the line breaks across, listed in the table as an em dash. A series with fewer readings than the axis has categories stops where its data does."
41
+ },
42
+ "color": {
43
+ "type": "string",
44
+ "editor": { "type": "token-color" },
45
+ "enum": [
46
+ "primary",
47
+ "secondary",
48
+ "tertiary",
49
+ "success",
50
+ "info",
51
+ "warning",
52
+ "danger",
53
+ "categorical-1",
54
+ "categorical-2",
55
+ "categorical-3",
56
+ "categorical-4",
57
+ "categorical-5",
58
+ "categorical-6"
59
+ ],
60
+ "description": "Design-token name for this line. Omit to take the categorical palette slot matching the series' index, which is the right default for identity encoding — the key a reader learned once stays true across renders and themes."
61
+ }
62
+ }
63
+ },
64
+ {
65
+ "name": "labels",
66
+ "label": "Category names",
67
+ "type": "string[] | Signal<string[]>",
68
+ "required": true,
69
+ "reactive": true,
70
+ "placeholder": ["Mon", "Tue", "Wed", "Thu", "Fri"],
71
+ "description": "Category names, in axis order — shared by every series. The labels define the axis. Past eight categories the axis shows every other label, always keeping the first and the last."
72
+ },
73
+ {
74
+ "name": "min",
75
+ "label": "Scale minimum",
76
+ "type": "number | Signal<number>",
77
+ "reactive": true,
78
+ "placeholder": 0,
79
+ "description": "The value drawn at the bottom of the plot. Defaults to zero, or to the lowest reading across every series when the data goes below it. May be negative. A bound that is not a finite number, or a min/max pair that does not increase, is DEV-warned and ignored."
80
+ },
81
+ {
82
+ "name": "max",
83
+ "label": "Scale maximum",
84
+ "type": "number | Signal<number>",
85
+ "reactive": true,
86
+ "placeholder": 200,
87
+ "description": "The value drawn at the top of the plot. Defaults to the highest reading across every series plus 12% headroom, so no peak's marker is clipped. A reading past this is drawn ON the top edge and DEV-warned; the table still reports its true value."
88
+ },
89
+ {
90
+ "name": "legend",
91
+ "label": "Colour key",
92
+ "editor": { "tab": "layout" },
93
+ "type": "enum",
94
+ "enum": ["end", "bottom", "none"],
95
+ "description": "Where the colour key sits. Defaults to 'end' when the first read of `series` holds more than one line and to 'none' when it holds exactly one — colour distinguishes nothing in a single-line chart, and the chart's own label names it. Pass this explicitly when the series count varies at runtime, because the default is decided once, at construction. 'none' is expressible and is never right for a multi-series chart."
96
+ },
97
+ {
98
+ "name": "size",
99
+ "label": "Size",
100
+ "editor": { "tab": "layout" },
101
+ "type": "enum",
102
+ "enum": ["sm", "md", "lg"],
103
+ "default": "md",
104
+ "description": "Sets the type scale of the axis. It does NOT cap the chart's width, unlike the circular charts: a line chart's inline extent is its TIME AXIS. 'sm' and 'md' share --font-size-xs, which is the design system's smallest step and already the floor for a tabular axis; 'lg' steps up."
105
+ },
106
+ {
107
+ "name": "loading",
108
+ "label": "Loading",
109
+ "editor": { "tab": "settings" },
110
+ "type": "boolean | Signal<boolean>",
111
+ "default": false,
112
+ "reactive": true,
113
+ "description": "Hides the drawing and the table, shows a pulsing skeleton in the drawing's slot, and sets aria-busy on the drawing's wrapper. Outranks every other state. The key keeps its box and loses its text, so the chart does not resize when data arrives."
114
+ },
115
+ {
116
+ "name": "error",
117
+ "label": "Error",
118
+ "editor": { "tab": "settings" },
119
+ "type": "boolean | Signal<boolean>",
120
+ "default": false,
121
+ "reactive": true,
122
+ "description": "Hides the drawing and the table and shows the error message. Outranks the empty state — a failed fetch usually leaves an empty array behind it."
123
+ },
124
+ {
125
+ "name": "errorMessage",
126
+ "label": "Error message",
127
+ "editor": { "tab": "settings" },
128
+ "type": "string | Signal<string>",
129
+ "default": "Chart data unavailable",
130
+ "reactive": true,
131
+ "description": "Copy shown in the error state."
132
+ },
133
+ {
134
+ "name": "emptyMessage",
135
+ "label": "Empty message",
136
+ "editor": { "tab": "settings" },
137
+ "type": "string",
138
+ "default": "No data to display",
139
+ "description": "Copy shown when no series holds a single reading. NOT shown for all-zero series, which stay ready and draw flat lines along the floor."
140
+ },
141
+ {
142
+ "name": "loadingLabel",
143
+ "label": "Loading message",
144
+ "editor": { "tab": "settings" },
145
+ "type": "string",
146
+ "default": "Loading chart…",
147
+ "description": "Copy shown in the loading state."
148
+ },
149
+ {
150
+ "name": "id",
151
+ "label": "ID",
152
+ "editor": { "tab": "settings" },
153
+ "type": "string",
154
+ "description": "Explicit element ID on the <figure> root."
155
+ }
156
+ ],
157
+ "events": [],
158
+ "slots": [],
159
+ "tokens": {
160
+ "line.stroke": "--color-categorical-1 … --color-categorical-6, or any semantic colour",
161
+ "line.stroke.fallback": "--color-text-muted (a series past the last palette slot, or an unrecognised token)",
162
+ "dot.fill": "the same token as its line",
163
+ "dot.separator": "--color-surface",
164
+ "dot.separator.width": "--border-width-thin",
165
+ "grid.stroke": "--color-border",
166
+ "axis.color": "--color-text-muted",
167
+ "axis.font": "--font-family-mono",
168
+ "axis.size": "--font-size-xs (--font-size-sm at lg)",
169
+ "axis.tracking": "typography $letter-spacings 'wide'",
170
+ "legend.swatch": "the same tokens as the lines",
171
+ "line.motion.duration": "--motion-duration-slow",
172
+ "line.motion.easing": "--motion-easing-emphasized",
173
+ "status.color": "--color-text-muted",
174
+ "status.error.color": "--color-danger",
175
+ "placeholder.background": "--color-surface-variant",
176
+ "placeholder.radius": "--radius-md"
177
+ },
178
+ "spacing": {
179
+ "internal": {
180
+ "plot-to-axis": {
181
+ "relationship": "lovers",
182
+ "token": "xs",
183
+ "note": "The axis is part of the drawing, not a caption of it."
184
+ },
185
+ "drawing-to-key": {
186
+ "relationship": "acquaintances",
187
+ "token": "lg",
188
+ "note": "Two different kinds of thing, read in turn. Inherited from the shared chart body."
189
+ },
190
+ "graphic-to-status": { "relationship": "close_friends", "token": "sm" }
191
+ }
192
+ },
193
+ "constraints": [
194
+ "Use logical properties only — no left/right, no width/height where inline-size/block-size applies",
195
+ "Colour is a design-token NAME, never a CSS value — there is no prop through which a hex code can reach a stylesheet",
196
+ "No function-valued props. Every prop has to be editable and serialisable through PIDL, so there is no formatValue callback",
197
+ "The PLOT is SVG and the AXIS LABELS are DOM, and the split follows the argument rather than the component. A polyline through N readings IS a path; axis labels are text, and SVG <text> has no ellipsis, no tabular figures, and no WCAG 1.4.4 text scaling",
198
+ "The axis row is a grid of 2(N-1) equal columns, with each inner label spanning two so its centre falls exactly on its point. N equal columns — one per label — is off by (i+0.5)/N against i/(N-1)",
199
+ "NOTHING outside an axis label may clip it: text-overflow paints its ellipsis at the label's own end edge, so an ancestor overflow: hidden removes the cue along with the characters",
200
+ "EVERY series is measured against ONE domain. A per-series scale would draw a series peaking at 10 and one peaking at 1000 as the same shape at the same height, which is the one thing a reader will assume it is not",
201
+ "Every reading gets a marker, unlike LineArea which marks only its terminus. With lines crossing, a marker is what says which line owns a reading AT the crossing, and it is the only thing separating a real observation from a point the eye invents along a straight segment",
202
+ "Every line is drawn UNDER every marker, so a marker is never buried by a later series' line",
203
+ "One to six series. Past the sixth the palette has no slot and the series is painted neutral AND dashed and flagged data-overflow — never given a slot already in use, which would be a wrong answer rendered as a key entry. Two overflow series remain ambiguous with each other, which is what the DEV warning says to fix: fold the tail into an explicit 'Other' series",
204
+ "The key is on by default for more than one series and off for exactly one. A line is identified by its colour and by nothing else on screen, so a multi-series chart without a key fails WCAG 1.4.1; a single-line chart's colour distinguishes nothing and the chart's label names it",
205
+ "The key's default is decided from the FIRST read of `series`, because a key is structure — the element exists or it does not — and it cannot be added and removed under the reader per signal write. Pass `legend` explicitly when the count varies",
206
+ "The graphic, the axis and the key are all aria-hidden — the visually-hidden data table is the accessible representation, so `label` is required",
207
+ "The chart is width-agnostic: it fills its column and `size` sets the type scale instead",
208
+ "A SINGLE reading is centred and carried by its marker, with no line. `i / (n - 1)` is 0/0 for one observation",
209
+ "A non-finite reading BREAKS its line rather than being bridged, and gets no marker at all — a marker parked at the origin would read as a reading of zero",
210
+ "A reading outside an explicit min/max is drawn at the edge of the plot and DEV-warned; the accessible table reports the true value",
211
+ "An inverted or collapsed domain (min >= max) is DEV-warned and the derived one is used, rather than drawing the chart upside down",
212
+ "Series whose readings are ALL zero stay `ready` and draw flat lines along the floor. The axis and the key still name everything, so going empty would hide a chart a sighted reader can see. Only 'no reading anywhere' is empty",
213
+ "The <tfoot> states each series' PLOTTED RANGE, never a total: adding Monday's latency to Tuesday's produces a number that is not a quantity of anything. The key shows the same string, from the same computation, so the two cannot disagree",
214
+ "The key is painted from the SAME colour resolution as the lines, never a second call — a key painted twice is a key that can disagree with the drawing, quietly",
215
+ "The <thead> is rebuilt only when the series NAMES change. A head row swapped on every signal write moves a screen reader's cursor out of the table it is reading",
216
+ "The drawing and the data table are hidden together in every non-ready state so the two cannot disagree about whether there is data",
217
+ "Each line's shape transition is inside a motion-safe block"
218
+ ],
219
+ "a11y": [
220
+ "The SVG carries aria-hidden='true' — it is a picture of the table beside it",
221
+ "The axis row and the key are aria-hidden too: both restate what the table's headers already carry, and exposing them would read the dataset twice in a flatter form",
222
+ "A <table class='u-sr-only'> carries one column per series and every exact reading, captioned with the required `label` prop",
223
+ "Table headers are scoped: <th scope='col'> in the head, <th scope='row'> per row. A reader moves down a column to follow one series and across a row to compare them at one point — the two readings a sighted reader takes off the drawing",
224
+ "A missing reading appears as an em dash, never as the zero the geometry substitutes to keep its arithmetic finite",
225
+ "A reading clamped to the edge of an explicit domain appears at its true value — the table is the reading of record wherever the drawing is an approximation",
226
+ "The <tfoot> states each series' plotted range, which stays true under an explicit min/max",
227
+ "Colour is never the sole encoding: the key is on by default for every multi-series chart, and the table names every series in its column headers",
228
+ "A series past the last palette slot is dashed as well as neutral, so two off-palette lines are still visibly off-palette where two neutral strokes alone would be indistinguishable",
229
+ "Axis text rescales with the reader's font-size setting rather than with the drawing, which SVG text cannot do (WCAG 1.4.4)",
230
+ "Status messages live in a role='status' region present from first render",
231
+ "aria-busy='true' while loading goes on the drawing's wrapper (.ui-chart-body), NEVER on the root. A live region inside an aria-busy subtree has its changes deferred until busy clears, which is the same call that blanks the region",
232
+ "In forced-colors the lines and markers keep their colours — the colour IS the data, and flattening six slots to one system colour would delete the key rather than raise contrast. The marker's separator becomes Canvas and the overflow dash opts out of substitution",
233
+ "Each line's shape transition is inside a prefers-reduced-motion: no-preference block"
234
+ ],
235
+ "ai": {
236
+ "synonyms": [
237
+ "line chart",
238
+ "line graph",
239
+ "multi-series line",
240
+ "trend comparison",
241
+ "time series comparison",
242
+ "metrics over time",
243
+ "series comparison",
244
+ "multi-line chart"
245
+ ],
246
+ "avoid": [
247
+ "Passing a CSS colour — the color field takes a design-token name",
248
+ "More than six series: the palette has six slots, and the seventh is drawn neutral and dashed with a DEV warning. Fold the tail into an explicit 'Other' series instead",
249
+ "Using it for ONE series with a fill — that is LineArea",
250
+ "Using it for unordered categories — a line implies a sequence. Use Bars",
251
+ "Using it for parts of one whole — that is StackedBar or Donut",
252
+ "legend='none' on a multi-series chart: the lines then have no name anywhere a sighted reader can see",
253
+ "Series measured in different units on one chart — they share a domain, so 'requests' and 'error rate' on one plot compare two things that cannot be compared",
254
+ "Omitting min/max when the scale should stay still as data changes: without them the domain follows the data and a growing value can look flat",
255
+ "Relying on the graphic alone for accessibility — the table is the accessible representation and `label` captions it"
256
+ ],
257
+ "patterns": [
258
+ "Basic: <Line label=\"'Latency by region'\" series=\"regions\" labels=\"days\" />",
259
+ "Key under the plot: <Line label=\"'Latency'\" series=\"regions\" labels=\"days\" legend=\"'bottom'\" />",
260
+ "Stable scale: <Line label=\"'Latency'\" series=\"regions\" labels=\"days\" min=\"0\" max=\"200\" />",
261
+ "Semantic colours: series=[{name:'Failed',values:[…],color:'danger'},{name:'Passed',values:[…],color:'success'}]",
262
+ "Live: <Line label=\"'Latency'\" series=\"() => regions()\" labels=\"days\" loading=\"isFetching\" />"
263
+ ]
264
+ },
265
+ "examples": [
266
+ {
267
+ "id": "basic",
268
+ "title": "Two regions over one week",
269
+ "pithy": "<Line label=\"'Latency by region'\" series=\"regions\" labels=\"days\" />",
270
+ "assertions": [
271
+ "figure.ui-chart.ui-line with data-status='ready' and data-legend='end'",
272
+ "one .ui-line-path per series, one .ui-line-dot per reading",
273
+ "every line is drawn before every marker",
274
+ "the key's swatches match the paths' data-color exactly",
275
+ "no axe violations"
276
+ ]
277
+ },
278
+ {
279
+ "id": "single-series",
280
+ "title": "One line needs no key",
281
+ "pithy": "<Line label=\"'Latency'\" series=\"[{name:'eu-west',values:[…]}]\" labels=\"days\" />",
282
+ "assertions": [
283
+ "no data-legend attribute and no .ui-chart-legend element",
284
+ "the chart's own label names the line"
285
+ ]
286
+ },
287
+ {
288
+ "id": "shared-domain",
289
+ "title": "One domain, so the shapes compare",
290
+ "pithy": "<Line label=\"'Two scales'\" series=\"[{name:'small',values:[0,10]},{name:'large',values:[0,100]}]\" labels=\"['a','b']\" />",
291
+ "assertions": [
292
+ "the small series' peak rises a tenth as far as the large one's",
293
+ "both are measured against one derived ceiling"
294
+ ]
295
+ },
296
+ {
297
+ "id": "palette-overflow",
298
+ "title": "A seventh series",
299
+ "pithy": "<Line label=\"'Seven'\" series=\"sevenSeries\" labels=\"days\" />",
300
+ "assertions": [
301
+ "the seventh path carries no data-color and data-overflow='true'",
302
+ "it is drawn neutral and dashed, never in categorical-1",
303
+ "one DEV warning says the palette has no slot for it"
304
+ ]
305
+ },
306
+ {
307
+ "id": "hole",
308
+ "title": "A probe that missed a day",
309
+ "pithy": "<Line label=\"'Latency'\" series=\"[{name:'eu-west',values:[1,2,NaN,3,4]}]\" labels=\"days\" />",
310
+ "assertions": [
311
+ "the line's `d` carries TWO subpaths",
312
+ "the third marker has no cx and no r at all",
313
+ "the table's third row reads an em dash"
314
+ ]
315
+ },
316
+ {
317
+ "id": "empty",
318
+ "title": "No readings at all",
319
+ "pithy": "<Line label=\"'Latency'\" series=\"[]\" labels=\"[]\" />",
320
+ "assertions": [
321
+ "data-status='empty'",
322
+ "graphic and table both hidden, role='status' region reads 'No data to display'"
323
+ ]
324
+ },
325
+ {
326
+ "id": "loading",
327
+ "title": "Loading",
328
+ "pithy": "<Line label=\"'Latency'\" series=\"regions\" labels=\"days\" loading=\"true\" />",
329
+ "assertions": [
330
+ "aria-busy='true' on .ui-chart-body, and absent from the root",
331
+ "graphic and table both hidden",
332
+ "the key keeps its box and loses its text, so the chart does not resize on resolve"
333
+ ]
334
+ }
335
+ ],
336
+ "designGuidance": {
337
+ "surface": "No background of its own. Three grid rules in --color-border give the eye a height reference across a shallower plot than the area chart's — fewer rules, because several lines crossing already give the plot plenty to carry. Each line takes a categorical slot in index order, so a key a reader learned once stays true across renders and themes.",
338
+ "depth": "Flat. No shadow under a line and no glow on a marker: both add apparent weight that is not data, and with several lines they make the topmost look more important than the rest. The only layering is order — grid, then every line, then every marker.",
339
+ "borders": "None around the figure. Each marker carries a hairline of --color-surface so it stays separate from any line crossing behind it; in forced-colors that becomes Canvas. A series past the last palette slot gets a dash rather than a border, because a dash survives colour substitution and is the only thing left distinguishing it.",
340
+ "interactivity": "None. Nothing is focusable and there is no hover treatment: the key names every series, the axis names every point, and the table carries every value. A tooltip would put readings somewhere keyboard and touch users cannot reach, and a chart that needs one is a chart being asked to be a table.",
341
+ "bestPractices": "Use it to COMPARE trends measured in the same unit at the same points. One trend is LineArea; unordered categories are Bars; parts of a whole are StackedBar or Donut. Two to four series is the readable range — six is the hard limit and already crowded, and past that the reader is tracing lines rather than reading a chart. Sort the key's order to match the lines' order at the right-hand edge where you can: a reader looks up the last reading first. Pass min and max whenever the scale should stay still as data changes; start min at zero unless the data goes below it, since a scale starting at the smallest reading exaggerates every wobble. Give the chart room — it is width-agnostic on purpose, and a compressed time axis makes crossings unreadable. Reach for the semantic palette only when a series genuinely means pass or fail; for arbitrary series the categorical slots are the right default. Always give a specific label."
342
+ }
343
+ }