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.
@@ -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": "An explicit field, label and format inside a table.",
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
- "Filters, maps, images, nested data and executable components are not supported in this release."
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
  }
@@ -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 initial components are `grid`, `metric`, `line`, `area`, `bar`, `table`, `column` and `callout`.
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, donut/funnel, image cells and executable/custom components are future capabilities, not supported syntax.
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; no HTML cell templates. Sorting uses raw values. Use a smaller prepared summary if validation reports table/point limits; the engine never truncates silently.
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 referenced JSON; an invalid edit displays diagnostics and recovers after repair. Omit `--no-open` to open a browser. No backend/account is required.
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