plotleaf 0.1.0 → 0.3.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/README.md +42 -6
- package/dist/assets/base.js +1 -1
- package/dist/assets/charts.js +17 -17
- package/dist/assets/styles.css +244 -0
- package/dist/cli.js +798 -124
- package/examples/catalog/assets/desk-lamp.jpg +0 -0
- package/examples/catalog/assets/field-notebook.png +0 -0
- package/examples/catalog/assets/linen-tote.png +0 -0
- package/examples/catalog/products.json +78 -0
- package/examples/catalog/report.md +39 -0
- package/examples/catalog/showcase-products.json +78 -0
- package/examples/onboarding/funnel.json +30 -0
- package/examples/onboarding/plans.json +14 -0
- package/examples/onboarding/report.md +42 -0
- package/examples/onboarding/signups.json +50 -0
- package/examples/report.md +96 -0
- package/package.json +19 -2
- package/schema/components.json +169 -5
- package/skills/plotleaf/SKILL.md +34 -5
package/schema/components.json
CHANGED
|
@@ -9,11 +9,18 @@
|
|
|
9
9
|
"datasets": 64,
|
|
10
10
|
"rows": 100000,
|
|
11
11
|
"chartRows": 10000,
|
|
12
|
+
"donutSlices": 8,
|
|
13
|
+
"funnelStages": 20,
|
|
12
14
|
"tableRows": 1000,
|
|
13
|
-
"tableColumns": 40
|
|
15
|
+
"tableColumns": 40,
|
|
16
|
+
"sparklinePoints": 64,
|
|
17
|
+
"imageFiles": 64,
|
|
18
|
+
"imageBytes": 1048576,
|
|
19
|
+
"imageDimension": 4096,
|
|
20
|
+
"imagePixels": 4000000
|
|
14
21
|
},
|
|
15
22
|
"data": {
|
|
16
|
-
"shape": "array of flat JSON records",
|
|
23
|
+
"shape": "array of flat JSON records; explicit table sparkline columns permit bounded numeric/null arrays only in their declared fields",
|
|
17
24
|
"resolution": "relative to the report directory; local .json files only",
|
|
18
25
|
"percent": "ratios: 0.12 displays as 12%",
|
|
19
26
|
"percentagePoints": "point values: 0.8 displays as 0.8 pt",
|
|
@@ -32,6 +39,8 @@
|
|
|
32
39
|
"line",
|
|
33
40
|
"area",
|
|
34
41
|
"bar",
|
|
42
|
+
"donut",
|
|
43
|
+
"funnel",
|
|
35
44
|
"table",
|
|
36
45
|
"callout"
|
|
37
46
|
],
|
|
@@ -286,6 +295,135 @@
|
|
|
286
295
|
},
|
|
287
296
|
"example": "::bar{data=\"./errors.json\" x=\"category\" y=\"count\" title=\"Errors by category\"}"
|
|
288
297
|
},
|
|
298
|
+
"donut": {
|
|
299
|
+
"description": "A part-to-whole donut with a readable legend and native data table.",
|
|
300
|
+
"forms": [
|
|
301
|
+
"leaf"
|
|
302
|
+
],
|
|
303
|
+
"children": [],
|
|
304
|
+
"props": {
|
|
305
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
306
|
+
"type": "object",
|
|
307
|
+
"properties": {
|
|
308
|
+
"data": {
|
|
309
|
+
"type": "string",
|
|
310
|
+
"minLength": 1,
|
|
311
|
+
"maxLength": 1000
|
|
312
|
+
},
|
|
313
|
+
"category": {
|
|
314
|
+
"type": "string",
|
|
315
|
+
"minLength": 1,
|
|
316
|
+
"maxLength": 1000
|
|
317
|
+
},
|
|
318
|
+
"value": {
|
|
319
|
+
"type": "string",
|
|
320
|
+
"minLength": 1,
|
|
321
|
+
"maxLength": 1000
|
|
322
|
+
},
|
|
323
|
+
"title": {
|
|
324
|
+
"type": "string",
|
|
325
|
+
"minLength": 1,
|
|
326
|
+
"maxLength": 1000
|
|
327
|
+
},
|
|
328
|
+
"format": {
|
|
329
|
+
"default": "number",
|
|
330
|
+
"type": "string",
|
|
331
|
+
"pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
"required": [
|
|
335
|
+
"data",
|
|
336
|
+
"category",
|
|
337
|
+
"value",
|
|
338
|
+
"title",
|
|
339
|
+
"format"
|
|
340
|
+
],
|
|
341
|
+
"additionalProperties": false
|
|
342
|
+
},
|
|
343
|
+
"example": "::donut{data=\"./plans.json\" category=\"plan\" value=\"teams\" title=\"Teams by plan\"}",
|
|
344
|
+
"dataRules": {
|
|
345
|
+
"category": "Unique nonblank strings of at most 1000 characters; source order is preserved.",
|
|
346
|
+
"value": "Finite nonnegative numbers with a finite sum. Nulls and numeric strings are invalid. All-zero values show an empty state.",
|
|
347
|
+
"maximumRows": 8
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
"funnel": {
|
|
351
|
+
"description": "Ordered stage/progress rows with prepared values, denominators, shares and optional explicit status. Works without JavaScript.",
|
|
352
|
+
"forms": [
|
|
353
|
+
"leaf"
|
|
354
|
+
],
|
|
355
|
+
"children": [],
|
|
356
|
+
"props": {
|
|
357
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
358
|
+
"type": "object",
|
|
359
|
+
"properties": {
|
|
360
|
+
"data": {
|
|
361
|
+
"type": "string",
|
|
362
|
+
"minLength": 1,
|
|
363
|
+
"maxLength": 1000
|
|
364
|
+
},
|
|
365
|
+
"stage": {
|
|
366
|
+
"type": "string",
|
|
367
|
+
"minLength": 1,
|
|
368
|
+
"maxLength": 1000
|
|
369
|
+
},
|
|
370
|
+
"value": {
|
|
371
|
+
"type": "string",
|
|
372
|
+
"minLength": 1,
|
|
373
|
+
"maxLength": 1000
|
|
374
|
+
},
|
|
375
|
+
"total": {
|
|
376
|
+
"type": "string",
|
|
377
|
+
"minLength": 1,
|
|
378
|
+
"maxLength": 1000
|
|
379
|
+
},
|
|
380
|
+
"share": {
|
|
381
|
+
"type": "string",
|
|
382
|
+
"minLength": 1,
|
|
383
|
+
"maxLength": 1000
|
|
384
|
+
},
|
|
385
|
+
"status": {
|
|
386
|
+
"type": "string",
|
|
387
|
+
"minLength": 1,
|
|
388
|
+
"maxLength": 1000
|
|
389
|
+
},
|
|
390
|
+
"title": {
|
|
391
|
+
"type": "string",
|
|
392
|
+
"minLength": 1,
|
|
393
|
+
"maxLength": 1000
|
|
394
|
+
},
|
|
395
|
+
"format": {
|
|
396
|
+
"default": "number",
|
|
397
|
+
"type": "string",
|
|
398
|
+
"pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
"required": [
|
|
402
|
+
"data",
|
|
403
|
+
"stage",
|
|
404
|
+
"value",
|
|
405
|
+
"total",
|
|
406
|
+
"share",
|
|
407
|
+
"title",
|
|
408
|
+
"format"
|
|
409
|
+
],
|
|
410
|
+
"additionalProperties": false
|
|
411
|
+
},
|
|
412
|
+
"example": "::funnel{data=\"./funnel.json\" stage=\"stage\" value=\"teams\" total=\"baseline\" share=\"share\" status=\"status\" title=\"Funnel by stage\"}",
|
|
413
|
+
"dataRules": {
|
|
414
|
+
"stage": "Unique nonblank strings of at most 1000 characters; source order is preserved.",
|
|
415
|
+
"value": "Finite nonnegative number no greater than its row's total.",
|
|
416
|
+
"total": "Finite positive denominator supplied per row. No denominator is inferred from adjacent stages.",
|
|
417
|
+
"share": "Prepared ratio from 0 to 1, matching value / total to within 0.0005 (rounding to 0.1 percentage point).",
|
|
418
|
+
"status": [
|
|
419
|
+
"on-target",
|
|
420
|
+
"below-target",
|
|
421
|
+
"at-risk",
|
|
422
|
+
"neutral"
|
|
423
|
+
],
|
|
424
|
+
"maximumRows": 20
|
|
425
|
+
}
|
|
426
|
+
},
|
|
289
427
|
"table": {
|
|
290
428
|
"description": "A semantic sortable table, with inferred or explicitly formatted columns.",
|
|
291
429
|
"forms": [
|
|
@@ -318,7 +456,7 @@
|
|
|
318
456
|
"example": ":::table{data=\"./plans.json\" title=\"By plan\"}\n::column{field=\"plan\" label=\"Plan\"}\n::column{field=\"change\" label=\"Δ QoQ\" format=\"percent\" sign=\"always\"}\n:::"
|
|
319
457
|
},
|
|
320
458
|
"column": {
|
|
321
|
-
"description": "
|
|
459
|
+
"description": "A scalar, local image or bounded sparkline column. Omitted kind means scalar.",
|
|
322
460
|
"forms": [
|
|
323
461
|
"leaf"
|
|
324
462
|
],
|
|
@@ -337,6 +475,24 @@
|
|
|
337
475
|
"minLength": 1,
|
|
338
476
|
"maxLength": 1000
|
|
339
477
|
},
|
|
478
|
+
"kind": {
|
|
479
|
+
"type": "string",
|
|
480
|
+
"enum": [
|
|
481
|
+
"scalar",
|
|
482
|
+
"image",
|
|
483
|
+
"sparkline"
|
|
484
|
+
]
|
|
485
|
+
},
|
|
486
|
+
"alt": {
|
|
487
|
+
"type": "string",
|
|
488
|
+
"minLength": 1,
|
|
489
|
+
"maxLength": 1000
|
|
490
|
+
},
|
|
491
|
+
"sortBy": {
|
|
492
|
+
"type": "string",
|
|
493
|
+
"minLength": 1,
|
|
494
|
+
"maxLength": 1000
|
|
495
|
+
},
|
|
340
496
|
"format": {
|
|
341
497
|
"type": "string",
|
|
342
498
|
"pattern": "^(number|percent|percentage-points|currency:[A-Z]{3})$"
|
|
@@ -366,7 +522,12 @@
|
|
|
366
522
|
],
|
|
367
523
|
"additionalProperties": false
|
|
368
524
|
},
|
|
369
|
-
"example": "::column{field=\"change\" label=\"Δ QoQ\" format=\"percent\" sign=\"always\" semantics=\"higher-is-better\"}"
|
|
525
|
+
"example": "::column{field=\"change\" label=\"Δ QoQ\" format=\"percent\" sign=\"always\" semantics=\"higher-is-better\"}",
|
|
526
|
+
"dataRules": {
|
|
527
|
+
"image": "Local static PNG/JPEG path or null. alt names a nonblank string field for each image. Fixed 64 × 48 display box; deduplicated and embedded offline. Paths resolve from the report directory.",
|
|
528
|
+
"sparkline": "Only explicitly declared sparkline fields may contain arrays: at most 64 finite numbers/nulls, in sample order. Nulls are gaps. Shared observed y scale per column; no aggregation or resampling.",
|
|
529
|
+
"sortBy": "Optional scalar field for image/sparkline sorting. Without it a rich column has a plain header. Scalar columns sort their own raw values. Sort keys are readable in the export."
|
|
530
|
+
}
|
|
370
531
|
},
|
|
371
532
|
"callout": {
|
|
372
533
|
"description": "A contextual note or warning containing ordinary Markdown.",
|
|
@@ -411,6 +572,9 @@
|
|
|
411
572
|
"Dataset fields are literal own-property keys, not expressions.",
|
|
412
573
|
"Charts preserve source order; date/number x values must increase.",
|
|
413
574
|
"Line/area null y values create gaps; bar null y values are invalid.",
|
|
414
|
-
"
|
|
575
|
+
"Donut category/value fields must differ; category count is bounded, and all-zero values do not create equal slices.",
|
|
576
|
+
"Funnel fields must differ; shares and denominators are supplied, validated and displayed per row, with no sorting or status inference.",
|
|
577
|
+
"Only explicit image columns can load local static PNG/JPEG; every resource stays within the report root and shared byte limits.",
|
|
578
|
+
"Filters, maps, arbitrary nested data and executable components are not supported in this release."
|
|
415
579
|
]
|
|
416
580
|
}
|
package/skills/plotleaf/SKILL.md
CHANGED
|
@@ -11,9 +11,9 @@ Use an installed `plotleaf` CLI or `npx plotleaf`. In an unpublished source chec
|
|
|
11
11
|
|
|
12
12
|
## Discover the contract
|
|
13
13
|
|
|
14
|
-
Run `plotleaf schema` (or `npx plotleaf schema`) for the current machine-readable component/prop specifications, rules and input limits. Prefer that output over remembered syntax. Supported
|
|
14
|
+
Run `plotleaf schema` (or `npx plotleaf schema`) for the current machine-readable component/prop specifications, rules and input limits. Prefer that output over remembered syntax. Supported components include `grid`, `metric`, `line`, `area`, `bar`, `donut`, `funnel`, `table`, `column` and `callout`. Donut and funnel require plotleaf 0.2.0 or newer; check the installed CLI's schema when using an older version.
|
|
15
15
|
|
|
16
|
-
Use ordinary Markdown for headings, prose, findings, emphasis, lists, links and dividers. Headings provide sections. Design is built in; do not add custom CSS, raw HTML or framework components. Filters, maps,
|
|
16
|
+
Use ordinary Markdown for headings, prose, findings, emphasis, lists, links and dividers. Headings provide sections. Design is built in; do not add custom CSS, raw HTML or framework components. Image/sparkline column kinds require 0.3.0 or newer; check the installed CLI version and schema. Filters, maps, subtotals and executable/custom components remain future capabilities.
|
|
17
17
|
|
|
18
18
|
## Author prepared content
|
|
19
19
|
|
|
@@ -43,14 +43,43 @@ An overview of the prepared dataset, its period and relevant findings.
|
|
|
43
43
|
|
|
44
44
|
Literal attributes are quoted strings, decoded by the component schema. `::` is a leaf; `:::` encloses a container. Close every container explicitly. Use a longer outer fence (`::::grid`) when placing another container, such as a table, inside it. Put components at the document root or in allowed component containers, not inline or inside lists/blockquotes. `column` belongs only inside a table. A plain `::table{data="./file.json"}` infers all fields; explicit columns select/order/format fields and exclude unused columns from export.
|
|
45
45
|
|
|
46
|
-
- Datasets are JSON arrays of flat scalar records. Paths resolve from the Markdown file's directory. Keep them there or in subdirectories; remote, hidden, absolute and outside-root/symlink paths are rejected.
|
|
46
|
+
- Datasets are JSON arrays of flat scalar records, except explicitly declared table sparkline fields. Paths resolve from the Markdown file's directory. Keep them there or in subdirectories; remote, hidden, absolute and outside-root/symlink paths are rejected.
|
|
47
47
|
- Chart fields are literal keys. Charts do not aggregate, join or sort. Prepare a distinct category per bar; date/number x values must be unique and increasing. `xType="date"` accepts real `YYYY-MM-DD` dates or timestamps with an explicit zone. Dates are displayed in UTC.
|
|
48
48
|
- y values are finite JSON numbers. Line/area nulls create gaps; bar nulls are invalid. Missing fields are errors. Empty arrays produce an explicit no-data state and a warning.
|
|
49
|
-
- Table records have consistent keys and non-null types per column. Scalar values stay text;
|
|
49
|
+
- Table records have consistent keys and non-null types per column. Scalar values stay text; rich cells use explicit column kinds, never HTML templates. Sorting uses raw values. Use a smaller prepared summary if validation reports table/point limits; the engine never truncates silently.
|
|
50
50
|
- Numeric formats are `number`, `percent`, `percentage-points` and `currency:GBP` (validated ISO code). Percent uses ratios (`0.12` → 12%); percentage-points uses point values (`0.8` → 0.8 pt); currency uses major units. Whole currency amounts omit `.00`; nonzero cents retain two decimal places. Formatting uses `en-GB`.
|
|
51
51
|
- Metrics, comparisons and business sentiment are supplied by the agent. An increase is not automatically positive. Comparison direction and sentiment are independent; both require comparison text when explicitly supplied. Numeric table changes can declare `higher-is-better` or `lower-is-better` semantics.
|
|
52
52
|
- Include data period, provenance and limitations in the prose where they help readers assess the report. Do not invent findings or imply filters/refresh that the file does not provide.
|
|
53
53
|
|
|
54
|
+
## Part-to-whole and stage views
|
|
55
|
+
|
|
56
|
+
```markdown
|
|
57
|
+
::donut{data="./plans.json" category="plan" value="teams" title="Teams by plan"}
|
|
58
|
+
::funnel{data="./funnel.json" stage="stage" value="teams" total="baseline" share="share" status="status" title="Funnel by stage"}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- Donuts require at most eight unique, nonblank string categories and finite nonnegative values with a finite sum. Prepare mutually exclusive parts of one whole; group a tail into an explicit Other row before rendering if appropriate. Zero values remain in the legend/data; all-zero data is an empty state, never equal slices.
|
|
62
|
+
- Funnel rows supply value, positive denominator and share explicitly, such as `{"stage":"Activated","teams":25,"baseline":100,"share":0.25,"status":"below-target"}`. Shares use ratios and must match value / total within 0.0005 (rounding to 0.1 percentage point). No baseline means an empty array, not a zero denominator.
|
|
63
|
+
- Funnel stage names are unique nonblank strings, with at most 20 stages in source order. Each stage can have its own denominator; explain that choice in prose. Status values are `on-target`, `below-target`, `at-risk` or `neutral`. Omit the status mapping for neutral styling; the engine never infers business status. All mapped fields must differ.
|
|
64
|
+
- Use the full-value data disclosures to check counts behind compact funnel labels. Both components retain readable HTML data without JavaScript. Keep the denominator of any nearby conversion KPI explicit when it differs from the funnel baseline.
|
|
65
|
+
|
|
66
|
+
## Rich tables (0.3.0 or newer)
|
|
67
|
+
|
|
68
|
+
```markdown
|
|
69
|
+
:::table{data="./products.json" title="Products"}
|
|
70
|
+
::column{field="image" kind="image" alt="description" label="Preview"}
|
|
71
|
+
::column{field="product" label="Product"}
|
|
72
|
+
::column{field="weekly_orders" kind="sparkline" sortBy="last_week" label="Weekly orders"}
|
|
73
|
+
:::
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Omitted `kind` means scalar. Images require an `alt` field containing a nonblank string for each non-null image; null image cells may have null alternatives. Image fields contain a local static PNG/JPEG path or null, resolved from the Markdown directory. Do not supply SVG, GIF/WebP, remote/data URLs or absolute/hidden/outside-root paths. The display box is 64 × 48; prepare images within the schema's byte/dimension/reference limits.
|
|
77
|
+
- Each unique image is embedded once. Its complete original bytes and metadata are readable by recipients; prepare assets for that disclosure. Alternatives are text, not markup. No image styling or numeric-format properties are supported.
|
|
78
|
+
- Sparkline fields contain at most 64 finite numbers/nulls or null. Only explicitly declared sparkline fields may contain sequences. Use a separate flat dataset for a chart or an inferred/scalar table. Nested objects/arrays and numeric strings are invalid; rows retain consistent keys.
|
|
79
|
+
- Sparkline x positions follow sample order. All rows in the same column share its observed min/max y scale. Nulls are gaps; last-sample labels remain missing when the final sample is null. Empty/all-null series show no invented values. Use `format` for numeric labels; sign/sentiment settings apply only to scalar columns.
|
|
80
|
+
- Rich columns are unsortable unless `sortBy` names a separate scalar field with consistent non-null types. Prepare any sort key explicitly, such as `last_week`; it is readable in the export. The engine does not compare arrays or derive a total/trend. Scalar columns keep their existing raw-value sort.
|
|
81
|
+
- Check image alternatives, line gaps/shared scale, keyboard value disclosures, row sorting, both themes and narrow table scrolling. Images/sparklines remain visible without JavaScript. The fictional catalogue example demonstrates this syntax.
|
|
82
|
+
|
|
54
83
|
## Validate, preview and export
|
|
55
84
|
|
|
56
85
|
```bash
|
|
@@ -59,7 +88,7 @@ npx plotleaf dev report.md --no-open
|
|
|
59
88
|
npx plotleaf build report.md --out ./report.html
|
|
60
89
|
```
|
|
61
90
|
|
|
62
|
-
Repair diagnostics using their code, one-based source location, message and hint, then validate again. Preview watches Markdown and
|
|
91
|
+
Repair diagnostics using their code, one-based source location, message and hint, then validate again. Preview watches Markdown, referenced JSON and local table images; an invalid edit displays diagnostics and recovers after repair. Omit `--no-open` to open a browser. No backend/account is required.
|
|
63
92
|
|
|
64
93
|
Export is one self-contained interactive HTML file. Check it after copying it away from sources and disabling networking. Charts require JavaScript; prose, metrics, tables and native chart-data disclosures remain readable without it. All displayed/embedded values are readable by recipients: collapsing a table or hiding a field visually is not redaction. Prepare sensitive data before rendering; explicit-column tables and chart field projection help avoid exporting unused fields.
|
|
65
94
|
|