ggaction 0.0.3 → 0.0.5

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.
Files changed (223) hide show
  1. package/CHANGELOG.md +58 -3
  2. package/README.md +117 -83
  3. package/package.json +27 -11
  4. package/src/actions/boxPlots/create.js +13 -7
  5. package/src/actions/boxPlots/edit.js +31 -35
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +8 -1
  8. package/src/actions/boxPlots/resolve.js +10 -2
  9. package/src/actions/charts/bar.js +50 -0
  10. package/src/actions/charts/heatmap.js +205 -0
  11. package/src/actions/charts/histogram.js +58 -0
  12. package/src/actions/charts/index.js +15 -0
  13. package/src/actions/charts/line.js +69 -0
  14. package/src/actions/charts/parallel.js +76 -0
  15. package/src/actions/charts/scatter.js +56 -0
  16. package/src/actions/charts/shared.js +121 -0
  17. package/src/actions/coordinates/parallel.js +50 -0
  18. package/src/actions/data/bin2d.js +137 -0
  19. package/src/actions/data/density.js +54 -13
  20. package/src/actions/data/derived.js +22 -0
  21. package/src/actions/data/filter.js +5 -0
  22. package/src/actions/data/gradientProfile.js +65 -0
  23. package/src/actions/data/horizon.js +71 -0
  24. package/src/actions/data/index.js +29 -2
  25. package/src/actions/data/regression.js +1 -1
  26. package/src/actions/data/window.js +50 -0
  27. package/src/actions/encodings/appearance.js +1 -1
  28. package/src/actions/encodings/color/continuous.js +137 -0
  29. package/src/actions/encodings/color/index.js +167 -0
  30. package/src/actions/encodings/color/layout.js +99 -0
  31. package/src/actions/encodings/color/policy.js +88 -0
  32. package/src/actions/encodings/density/resolve.js +109 -0
  33. package/src/actions/encodings/density.js +193 -88
  34. package/src/actions/encodings/horizon/resolve.js +283 -0
  35. package/src/actions/encodings/horizon.js +364 -0
  36. package/src/actions/encodings/index.js +8 -2
  37. package/src/actions/encodings/offset.js +1 -1
  38. package/src/actions/encodings/parallel.js +115 -0
  39. package/src/actions/encodings/pathOrder.js +131 -0
  40. package/src/actions/encodings/position/apply.js +73 -4
  41. package/src/actions/encodings/{position.js → position/index.js} +21 -8
  42. package/src/actions/encodings/position/policies/arc.js +27 -4
  43. package/src/actions/encodings/position/policies/area.js +33 -2
  44. package/src/actions/encodings/position/policies/index.js +7 -2
  45. package/src/actions/encodings/position/policies/line.js +18 -2
  46. package/src/actions/encodings/position/resolve.js +25 -3
  47. package/src/actions/encodings/ranged.js +5 -3
  48. package/src/actions/encodings/ruleAppearance.js +88 -10
  49. package/src/actions/encodings/strokeDash.js +1 -1
  50. package/src/actions/encodings/text.js +1 -1
  51. package/src/actions/errorBands/options.js +1 -1
  52. package/src/actions/errorBars/options.js +1 -1
  53. package/src/actions/facets/actions.js +2 -3
  54. package/src/actions/facets/derive.js +35 -5
  55. package/src/actions/facets/guides.js +1 -1
  56. package/src/actions/facets/replay.js +7 -36
  57. package/src/actions/gradientPlots/components.js +177 -0
  58. package/src/actions/gradientPlots/create.js +96 -0
  59. package/src/actions/gradientPlots/edit.js +153 -0
  60. package/src/actions/gradientPlots/index.js +24 -0
  61. package/src/actions/gradientPlots/materialize.js +287 -0
  62. package/src/actions/gradientPlots/options.js +120 -0
  63. package/src/actions/gradientPlots/paint.js +107 -0
  64. package/src/actions/gradientPlots/rebind.js +51 -0
  65. package/src/actions/gradientPlots/resolve.js +81 -0
  66. package/src/actions/guides/applicability.js +44 -17
  67. package/src/actions/guides/axes/axes.js +37 -3
  68. package/src/actions/guides/axes/index.js +2 -0
  69. package/src/actions/guides/axes/labels.js +12 -7
  70. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  71. package/src/actions/guides/axes/parallel.js +228 -0
  72. package/src/actions/guides/axes/ticks.js +1 -1
  73. package/src/actions/guides/axes/titles.js +12 -9
  74. package/src/actions/guides/grids/resolve.js +1 -1
  75. package/src/actions/guides/guides.js +60 -5
  76. package/src/actions/guides/legends/categorical/actions.js +48 -11
  77. package/src/actions/guides/legends/categorical/layout.js +4 -2
  78. package/src/actions/guides/legends/continuous/interval.js +1 -1
  79. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  80. package/src/actions/guides/legends/edit.js +7 -20
  81. package/src/actions/guides/legends/index.js +2 -0
  82. package/src/actions/guides/legends/remove.js +6 -31
  83. package/src/actions/guides/legends/size.js +1 -1
  84. package/src/actions/guides/legends/strokeWidth.js +170 -0
  85. package/src/actions/guides/legends/target.js +25 -0
  86. package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
  87. package/src/actions/guides/polar/index.js +1 -1
  88. package/src/actions/guides/polar/resolve.js +1 -1
  89. package/src/actions/guides/tickValues.js +1 -1
  90. package/src/actions/index.js +6 -0
  91. package/src/actions/intervals/resolve.js +1 -1
  92. package/src/actions/marks/arc/actions.js +2 -2
  93. package/src/actions/marks/area/actions.js +61 -137
  94. package/src/actions/marks/area/materialize.js +163 -0
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/index.js +5 -1
  97. package/src/actions/marks/line/actions.js +100 -81
  98. package/src/actions/marks/line/materialize.js +146 -0
  99. package/src/actions/marks/point/create.js +76 -0
  100. package/src/actions/marks/point/edit.js +85 -0
  101. package/src/actions/marks/point/index.js +11 -1
  102. package/src/actions/marks/point/jitter.js +72 -0
  103. package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
  104. package/src/actions/marks/rect/actions.js +4 -1
  105. package/src/actions/marks/remove.js +13 -2
  106. package/src/actions/marks/rule/actions.js +26 -4
  107. package/src/actions/marks/shared.js +1 -1
  108. package/src/actions/marks/text/actions.js +20 -5
  109. package/src/actions/marks/text/index.js +7 -1
  110. package/src/actions/marks/text/layout.js +321 -0
  111. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  112. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  113. package/src/actions/primitives/semanticValidation/index.js +23 -0
  114. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  115. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  116. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  117. package/src/actions/primitives/semanticValue.js +1 -204
  118. package/src/actions/regression/components.js +1 -1
  119. package/src/actions/regression/create.js +1 -1
  120. package/src/actions/regression/edit.js +20 -22
  121. package/src/actions/scales/consumers/common.js +109 -0
  122. package/src/actions/scales/consumers/families.js +58 -0
  123. package/src/actions/scales/consumers/index.js +41 -0
  124. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
  125. package/src/actions/scales/create.js +1 -1
  126. package/src/actions/scales/definitions.js +32 -2
  127. package/src/actions/scales/edit.js +8 -206
  128. package/src/actions/scales/editPolicy.js +214 -0
  129. package/src/actions/scales/materialize.js +3 -3
  130. package/src/actions/scales/patch.js +26 -0
  131. package/src/actions/selection/actions.js +28 -3
  132. package/src/actions/violinPlots/create.js +198 -0
  133. package/src/actions/violinPlots/index.js +5 -0
  134. package/src/core/ChartProgram.js +22 -236
  135. package/src/core/compositionState.js +169 -0
  136. package/src/core/materializationState.js +65 -0
  137. package/src/core/programState.js +28 -0
  138. package/src/core/textMetrics.js +47 -0
  139. package/src/core/vocabulary.js +7 -2
  140. package/src/grammar/arcs.js +50 -13
  141. package/src/grammar/areaSeries.js +42 -6
  142. package/src/grammar/bars/aggregate.js +1 -1
  143. package/src/grammar/bin2d.js +333 -0
  144. package/src/grammar/categoricalDensity.js +192 -0
  145. package/src/grammar/coordinates.js +1 -1
  146. package/src/grammar/curveCommands.js +3 -1
  147. package/src/grammar/density.js +253 -27
  148. package/src/grammar/facets/dependencies.js +3 -11
  149. package/src/grammar/{facets.js → facets/index.js} +8 -6
  150. package/src/grammar/gradientProfile.js +213 -0
  151. package/src/grammar/horizon.js +455 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/jitter.js +197 -0
  154. package/src/grammar/lineSeries.js +70 -1
  155. package/src/grammar/paint.js +88 -0
  156. package/src/grammar/palettes.js +5 -7
  157. package/src/grammar/parallelCoordinates.js +213 -0
  158. package/src/grammar/pathOrder.js +35 -0
  159. package/src/grammar/pointShapes.js +24 -0
  160. package/src/grammar/polarLineCommands.js +1 -1
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +2 -1
  166. package/src/grammar/scales/appearance.js +21 -0
  167. package/src/grammar/scales/color.js +4 -2
  168. package/src/grammar/schemas/concreteGraphic.js +11 -1
  169. package/src/grammar/schemas/graphicBounds.js +19 -37
  170. package/src/grammar/schemas/semanticPath.js +17 -2
  171. package/src/grammar/statistics/studentT.js +98 -0
  172. package/src/grammar/transforms.js +101 -12
  173. package/src/grammar/window.js +339 -0
  174. package/src/layout/labels.js +270 -0
  175. package/src/layout/text.js +4 -1
  176. package/src/layout/title.js +10 -20
  177. package/src/materialization/bars/aggregate.js +1 -1
  178. package/src/materialization/bars/grouped.js +1 -1
  179. package/src/materialization/bars/histogram.js +1 -1
  180. package/src/materialization/bars/ranged.js +1 -1
  181. package/src/materialization/dataProvenance.js +56 -2
  182. package/src/materialization/density.js +1 -1
  183. package/src/materialization/dependencies.js +15 -101
  184. package/src/materialization/encodings.js +2 -2
  185. package/src/materialization/facetGuides/index.js +2 -0
  186. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  187. package/src/materialization/facetGuides/placement.js +126 -0
  188. package/src/materialization/facetGuides/preparation.js +188 -0
  189. package/src/materialization/facets.js +1 -1
  190. package/src/materialization/guides/resources.js +74 -24
  191. package/src/materialization/horizon.js +29 -0
  192. package/src/materialization/layout.js +15 -0
  193. package/src/materialization/marks/capabilities.js +161 -0
  194. package/src/materialization/marks/index.js +108 -0
  195. package/src/materialization/marks/pathOrder.js +71 -0
  196. package/src/materialization/marks/policies.js +91 -0
  197. package/src/materialization/rect.js +1 -1
  198. package/src/materialization/rowEncoding.js +1 -1
  199. package/src/materialization/scaleGuideDependencies.js +102 -0
  200. package/src/materialization/scales/map.js +1 -1
  201. package/src/materialization/scales/policies/series.js +1 -1
  202. package/src/materialization/scales/resolve.js +3 -1
  203. package/src/materialization/selection/items/bar.js +1 -1
  204. package/src/materialization/selection/items/path.js +27 -1
  205. package/src/materialization/selection/items/point.js +1 -5
  206. package/src/materialization/selection/items/rect.js +21 -0
  207. package/src/materialization/selection/styles.js +21 -10
  208. package/src/materialization/text.js +12 -4
  209. package/src/renderers/canvas/fill.js +33 -0
  210. package/src/renderers/canvas/index.js +7 -0
  211. package/src/renderers/canvas/path.js +7 -4
  212. package/src/renderers/canvas/rect.js +9 -3
  213. package/src/renderers/canvas/text.js +14 -1
  214. package/src/selectors/datasets.js +4 -0
  215. package/src/theme/defaults.js +4 -0
  216. package/types/extension.d.ts +10 -1
  217. package/types/index.d.ts +76 -0
  218. package/types/program.d.ts +632 -10
  219. package/src/actions/encodings/color.js +0 -424
  220. package/src/grammar/regression.js +0 -578
  221. package/src/grammar/scales.js +0 -1
  222. package/src/materialization/facetGuides.js +0 -426
  223. package/src/materialization/marks.js +0 -316
package/CHANGELOG.md CHANGED
@@ -4,6 +4,59 @@ All notable changes to `ggaction` are recorded in this file.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.0.5] - 2026-07-21
8
+
9
+ ### Added
10
+
11
+ - Added complete `createScatterPlot`, `createLinePlot`, `createBarPlot`, `createHistogram`, `createHeatmap`, and
12
+ `createParallelCoordinates` facades that reuse ordinary mark, encoding, scale, coordinate, and guide actions.
13
+ - Added deterministic point jitter, ordered line paths, collision-aware text labels, field-driven rule widths, and
14
+ weighted Polar sectors with immutable rematerialization across data, scale, Canvas, selection, and facet changes.
15
+ - Added window and rectangular 2D-bin data actions, binned heatmaps, categorical density and violin plots, Horizon
16
+ charts, Parallel Coordinates, and density-filled gradient plots with Browser Canvas and Node PNG parity.
17
+ - Added backend-neutral item-local gradient paint, expanded public declarations and action contracts, and runnable
18
+ chart examples for every new capability.
19
+
20
+ ### Changed
21
+
22
+ - Aligned Box Plot with the shared facade inference, ambiguity, public option-type, and opt-in guide contracts while
23
+ preserving its existing omitted-guide behavior.
24
+ - Reorganized source, test, documentation, and internal architecture ownership around explicit policies and capability
25
+ registries without changing the renderer's concrete `graphicSpec` boundary.
26
+ - Expanded public documentation with task-oriented API routing, generated split action references, improved mobile and
27
+ no-JavaScript navigation, complete facade discovery, and release-scoped deployment checks.
28
+
29
+ ### Fixed
30
+
31
+ - Materialized the documented default point radius, made direct quantitative line x/y authoring order-independent, and
32
+ made layered datum rules resolve to the expected full-span geometry instead of an empty result.
33
+ - Kept sticky documentation deep links below the top bar by sharing one computed fragment offset between CSS and the
34
+ page table of contents.
35
+
36
+ ## [0.0.4] - 2026-07-19
37
+
38
+ ### Changed
39
+
40
+ - Transferred the canonical repository to the `ggaction` organization and moved public documentation to
41
+ `https://ggaction.github.io/ggaction/`.
42
+ - Made public documentation deployment release-scoped so ordinary `main` pushes continue to verify docs without
43
+ changing the published site.
44
+ - Refactored source ownership and materialization boundaries while preserving the public API, stored specifications,
45
+ trace hierarchy, and rendered output.
46
+
47
+ ### Fixed
48
+
49
+ - Normalized numeric Canvas font weights before rendering so valid intermediate values such as `650` retain normal
50
+ text geometry in both Browser Canvas and Node PNG output.
51
+ - Applied right categorical legend offsets from the plot boundary consistently during creation and focused layout
52
+ edits, including labels, titles, and optional backgrounds.
53
+ - Accepted `count` on sequential palette descriptors as a concrete gradient-stop count, consistently across
54
+ top-level palette shorthands, nested ranges, encodings, direct scales, and scale edits.
55
+ - Preserved concrete `ChartProgram` subclasses in the TypeScript signature of wrapped extension actions and added
56
+ a strict NodeNext declaration-merging authoring pattern.
57
+ - Routed every selective `llms.txt` target to a deployed HTML page, stabilized action fragments, and made built-site
58
+ checks validate both HTTP targets and DOM IDs.
59
+
7
60
  ## [0.0.3] - 2026-07-19
8
61
 
9
62
  ### Added
@@ -58,6 +111,8 @@ All notable changes to `ggaction` are recorded in this file.
58
111
  - Cartesian charts are the complete current path. Polar semantic tokens exist only where explicitly documented and do
59
112
  not imply complete polar rendering.
60
113
 
61
- [0.0.3]: https://github.com/hj-n/ggaction/releases/tag/v0.0.3
62
- [0.0.2]: https://github.com/hj-n/ggaction/releases/tag/v0.0.2
63
- [0.0.1]: https://github.com/hj-n/ggaction/releases/tag/v0.0.1
114
+ [0.0.5]: https://github.com/ggaction/ggaction/releases/tag/v0.0.5
115
+ [0.0.4]: https://github.com/ggaction/ggaction/releases/tag/v0.0.4
116
+ [0.0.3]: https://github.com/ggaction/ggaction/releases/tag/v0.0.3
117
+ [0.0.2]: https://github.com/ggaction/ggaction/releases/tag/v0.0.2
118
+ [0.0.1]: https://github.com/ggaction/ggaction/releases/tag/v0.0.1
package/README.md CHANGED
@@ -1,119 +1,153 @@
1
1
  # ggaction
2
2
 
3
- `ggaction` is a JavaScript library for building charts through immutable,
4
- traceable actions.
3
+ [![npm version](https://img.shields.io/npm/v/ggaction.svg)](https://www.npmjs.com/package/ggaction) [![license](https://img.shields.io/npm/l/ggaction.svg)](./LICENSE) [![documentation](https://img.shields.io/badge/docs-ggaction.github.io-2563eb)](https://ggaction.github.io/ggaction/)
4
+
5
+ ### A grammar for how charts are made.
6
+
7
+ Most visualization grammars describe a finished chart. **ggaction** represents chart authoring itself as an immutable, traceable sequence of graphical actions.
8
+
9
+ Build, inspect, select, and revise charts one meaningful action at a time.
10
+
11
+ <p align="left">
12
+ <img src="./docs/assets/images/readme-authoring-sequence.gif" width="960" height="540" loading="eager" alt="A ggaction program progressively creates a scatterplot, adds grouped regression fits and confidence bands, highlights the Japan group across chart layers, and finishes with an R-squared annotation.">
13
+ </p>
14
+
15
+ Every frame is rendered from an immutable `ChartProgram`. The final R² label is a custom traceable action composed from ggaction's public extension primitives.
16
+
17
+ ```bash
18
+ npm install ggaction
19
+ ```
20
+
21
+ ## A grammar of graphical action
22
+
23
+ Actions are verbs:
24
+
25
+ `create · transform · encode · edit · select · compose`
26
+
27
+ Chart resources are nouns:
28
+
29
+ `data · marks · scales · coordinates · guides`
30
+
31
+ A `ChartProgram` is the immutable sentence they produce.
32
+
33
+ The following fragment assumes `cars` is an array of row objects:
34
+
35
+ ```javascript
36
+ import { chart } from "ggaction";
37
+
38
+ const program = chart()
39
+ .createCanvas()
40
+ .createData({ values: cars })
41
+ .createScatterPlot({
42
+ id: "points",
43
+ x: "Displacement",
44
+ y: "Acceleration",
45
+ color: "Origin",
46
+ guides: false
47
+ })
48
+ .createRegression()
49
+ .createGuides();
50
+ ```
51
+
52
+ ## Why actions?
53
+
54
+ - **Progressive** — build and revise a chart one meaningful operation at a time.
55
+ - **Traceable** — retain high-level actions and the wrapped actions they invoke.
56
+ - **Materialized** — actions create concrete backend-neutral graphics; rendering does not perform hidden semantic compilation.
57
+
58
+ ## Quick start
59
+
60
+ ```bash
61
+ npm install ggaction
62
+ ```
5
63
 
6
64
  ```javascript
7
65
  import { chart, render } from "ggaction";
8
66
 
9
- const cars = [
10
- { horsepower: 88, mpg: 27, origin: "USA" },
11
- { horsepower: 70, mpg: 36, origin: "Japan" },
12
- { horsepower: 110, mpg: 24, origin: "Europe" }
67
+ const observations = [
68
+ { displacement: 97, acceleration: 14.5, origin: "Japan" },
69
+ { displacement: 140, acceleration: 15.5, origin: "USA" },
70
+ { displacement: 86, acceleration: 16.4, origin: "Japan" }
13
71
  ];
14
72
 
15
73
  const program = chart()
16
74
  .createCanvas({
17
75
  width: 640,
18
76
  height: 400,
19
- margin: { top: 30, right: 30, bottom: 60, left: 70 }
77
+ margin: { top: 30, right: 130, bottom: 60, left: 70 }
20
78
  })
21
- .createData({ values: cars })
22
- .createPointMark()
23
- .encodeX({ field: "horsepower" })
24
- .encodeY({ field: "mpg" })
25
- .encodeColor({ field: "origin" })
26
- .encodeRadius({ value: 3 })
27
- .createGuides({
28
- axes: {
29
- x: { title: { text: "Horsepower" } },
30
- y: { title: { text: "Miles per Gallon" } }
31
- }
79
+ .createData({ values: observations })
80
+ .createScatterPlot({
81
+ x: "displacement",
82
+ y: "acceleration",
83
+ color: "origin"
32
84
  });
33
85
 
34
- render(program, document.querySelector("#chart").getContext("2d"));
86
+ const context = document.querySelector("#chart").getContext("2d");
87
+ render(program, context);
35
88
  ```
36
89
 
37
- The renderer reads only fully materialized, backend-neutral graphics. Semantic
38
- state is never automatically compiled during rendering.
90
+ Use `createLinePlot`, `createBarPlot`, `createHistogram`, and `createHeatmap` for
91
+ the other basic Cartesian charts. Each facade records the regular mark,
92
+ encoding, and guide actions as trace children, and the same resource-specific
93
+ actions remain available for later edits. See the
94
+ [Basic Charts API](https://ggaction.github.io/ggaction/api/basic-charts/).
39
95
 
40
- > **Status:** `0.0.3` is the current experimental public release. APIs may change
41
- > before `1.0.0`; changes are recorded in the [changelog](./CHANGELOG.md).
96
+ Use `createParallelCoordinates({ dimensions })` to connect each source row
97
+ across ordered, dimension-local scales and axes. See the
98
+ [Parallel Coordinates API](https://ggaction.github.io/ggaction/api/parallel-coordinates/)
99
+ or the [runnable Cars example](./examples/cars-parallel-coordinates/).
42
100
 
43
- ## Install
101
+ For an advanced layered example, follow the [regression recipe](https://ggaction.github.io/ggaction/recipes/regression-scatterplot/)
102
+ or open the [runnable regression example](./examples/cars-regression-scatterplot/).
103
+ To compare category distributions with density-filled strips, read the
104
+ [gradient-plot guide](https://ggaction.github.io/ggaction/api/gradient-plots/)
105
+ or open the [runnable example](./examples/cars-gradient-plot/).
106
+ For symmetric or split density shapes centered on categories, use the
107
+ [violin-plot API](https://ggaction.github.io/ggaction/api/violin-plots/) or the
108
+ [runnable Cars example](./examples/cars-acceleration-violins/).
109
+ For compact signed time-series bands, use `encodeHorizon` on an area mark and
110
+ open the [runnable Gapminder example](./examples/gapminder-horizon/).
44
111
 
45
- ```bash
46
- npm install ggaction
47
- ```
112
+ ## What it supports
113
+
114
+ - Cartesian, Polar, and Parallel-coordinate charts
115
+ - Statistical layers and intervals
116
+ - Faceting and program composition
117
+ - Mark selection and coordinated highlighting
118
+ - Browser Canvas and Node PNG output
119
+ - TypeScript declarations and traceable extension actions
48
120
 
49
- The package is ESM-only and supports Node.js 20 or later. The default `ggaction`
50
- entry works with modern browser build tools and Canvas. `ggaction/png` is
51
- Node-only and uses a native Canvas dependency.
121
+ See the current [supported features](https://ggaction.github.io/ggaction/supported-features/), [tutorials and examples](https://ggaction.github.io/ggaction/tutorials/), and [action reference](https://ggaction.github.io/ggaction/reference/actions/) for exact coverage. Runnable programs are collected in [`examples/`](./examples/README.md).
122
+
123
+ ## Package entries
124
+
125
+ The package is ESM-only and requires Node.js 20 or later.
52
126
 
53
127
  | Entry | Purpose |
54
128
  | --- | --- |
55
129
  | `ggaction` | Create chart programs and render them to Browser Canvas |
56
- | `ggaction/extension` | Author wrapped actions and use public low-level primitives |
130
+ | `ggaction/extension` | Author wrapped actions with public low-level primitives |
57
131
  | `ggaction/png` | Render a completed program to a PNG file in Node.js |
58
132
 
59
- All three entries include TypeScript declarations. See [Getting
60
- Started](https://hyeonword.com/ggaction/getting-started/) for a complete browser
61
- setup and [Rendering](https://hyeonword.com/ggaction/api/rendering/) for Node PNG
62
- output.
133
+ All entries include TypeScript declarations. The default entry is browser-safe; the PNG adapter is Node-only.
63
134
 
64
135
  ## Documentation
65
136
 
66
- - [npm package](https://www.npmjs.com/package/ggaction)
67
- - [Getting started](https://hyeonword.com/ggaction/getting-started/)
68
- - [Cars scatterplot tutorial](https://hyeonword.com/ggaction/tutorials/scatterplot/)
69
- - [Cars line chart tutorial](https://hyeonword.com/ggaction/tutorials/line-chart/)
70
- - [Cars histogram tutorial](https://hyeonword.com/ggaction/tutorials/histogram/)
71
- - [Bar chart tutorial](https://hyeonword.com/ggaction/tutorials/grouped-bar/)
72
- - [Regression scatterplot tutorial](https://hyeonword.com/ggaction/tutorials/regression-scatterplot/)
73
- - [Density area chart tutorial](https://hyeonword.com/ggaction/tutorials/density-area/)
74
- - [Error-bar chart tutorial](https://hyeonword.com/ggaction/tutorials/error-bar/)
75
- - [Error-band chart tutorial](https://hyeonword.com/ggaction/tutorials/error-band/)
76
- - [Mark selection and highlighting tutorial](https://hyeonword.com/ggaction/tutorials/mark-selection/)
77
- - [Box-plot API](https://hyeonword.com/ggaction/api/box-plots/)
78
- - [Chart concepts](https://hyeonword.com/ggaction/concepts/chart-program/)
79
- - [Complete action reference](https://hyeonword.com/ggaction/reference/actions/)
80
- - [Supported features](https://hyeonword.com/ggaction/supported-features/)
81
- - [Documentation index](https://hyeonword.com/ggaction/)
82
-
83
- The runnable [cars scatterplot](https://github.com/hj-n/ggaction/tree/main/examples/cars-scatterplot/),
84
- [aggregate line chart](https://github.com/hj-n/ggaction/tree/main/examples/cars-line-chart/), and
85
- [stacked histogram](https://github.com/hj-n/ggaction/tree/main/examples/cars-histogram/) use `data/cars.json`. The
86
- [regression scatterplot](https://github.com/hj-n/ggaction/tree/main/examples/cars-regression-scatterplot/) also uses the
87
- cars data and layers grouped fits with confidence bands. The [grouped bar
88
- chart](https://github.com/hj-n/ggaction/tree/main/examples/jobs-grouped-bar/) uses `data/jobs.json`. The [density area
89
- chart](https://github.com/hj-n/ggaction/tree/main/examples/cars-density-area/) derives grouped Acceleration
90
- distributions from the cars data. The [error-bar chart](https://github.com/hj-n/ggaction/tree/main/examples/cars-error-bar/)
91
- summarizes mean Acceleration with 95% confidence intervals. The
92
- [error-band chart](https://github.com/hj-n/ggaction/tree/main/examples/gapminder-error-band/) summarizes grouped
93
- confidence intervals as closed area paths over time.
94
- The [box plot](https://github.com/hj-n/ggaction/tree/main/examples/cars-box-plot/) supports vertical or horizontal
95
- Tukey/min–max ranges, configurable factor and component appearance, and
96
- optional outlier rendering from categorical and quantitative field pairs.
97
- The [mark-selection example](https://github.com/hj-n/ggaction/tree/main/examples/mark-selection/) compares grouped point,
98
- complete stacked-bar, and line-series highlighting through one selector grammar.
99
- The [program-composition example](https://github.com/hj-n/ggaction/tree/main/examples/program-composition/) combines
100
- complete child programs, edits their inferred layout, and replaces one stable slot.
101
- The [facet example](https://github.com/hj-n/ggaction/tree/main/examples/cars-origin-scatterplot-facet/)
102
- repeats one chart with `.facet({ field: "Origin" })`, channel-level shared or
103
- independent scales, per-cell or occupied-outer axes, and an optional compatible
104
- parent-owned legend. Regression, density, interval, and box-derived data are
105
- recomputed independently inside each cell.
106
- Complete Cartesian and Polar programs can be nested with `hconcat` and
107
- `vconcat`; the [cross-feature dashboard](https://github.com/hj-n/ggaction/tree/main/examples/cross-feature-dashboard/)
108
- shows a Polar child replacement beside a Cartesian facet. Polar sources cannot
109
- currently be faceted and fail before partial child state is created.
110
- Completed programs can also be exported through
111
- [`ggaction/png`](https://hyeonword.com/ggaction/api/rendering/#png-output).
112
-
113
- ## Development
137
+ - [Getting Started](https://ggaction.github.io/ggaction/getting-started/)
138
+ - [Tutorials and Examples](https://ggaction.github.io/ggaction/tutorials/)
139
+ - [Action Reference](https://ggaction.github.io/ggaction/reference/actions/)
140
+ - [Concepts](https://ggaction.github.io/ggaction/concepts/chart-program/)
141
+ - [Supported Features](https://ggaction.github.io/ggaction/supported-features/)
142
+
143
+ ## Status and development
144
+
145
+ > **Status:** `0.0.5` is the current experimental public release. APIs may change before `1.0.0`; changes are recorded in the [changelog](./CHANGELOG.md).
114
146
 
115
147
  ```bash
116
148
  npm install
149
+ npm run assets:readme
117
150
  npm test
118
151
  npm run test:render
152
+ npm run test:docs
119
153
  ```
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "ggaction",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Build charts through immutable, traceable graphical actions.",
5
5
  "license": "MIT",
6
6
  "author": "Hyeon Jeon",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/hj-n/ggaction.git"
9
+ "url": "git+https://github.com/ggaction/ggaction.git"
10
10
  },
11
- "homepage": "https://hyeonword.com/ggaction/",
11
+ "homepage": "https://ggaction.github.io/ggaction/",
12
12
  "bugs": {
13
- "url": "https://github.com/hj-n/ggaction/issues"
13
+ "url": "https://github.com/ggaction/ggaction/issues"
14
14
  },
15
15
  "keywords": [
16
16
  "visualization",
@@ -22,7 +22,7 @@
22
22
  "type": "module",
23
23
  "files": [
24
24
  "src/",
25
- "!src/AGENTS.md",
25
+ "!src/**/AGENTS.md",
26
26
  "types/",
27
27
  "README.md",
28
28
  "CHANGELOG.md",
@@ -54,16 +54,29 @@
54
54
  "test:docs:built": "node scripts/check-built-docs.js _site",
55
55
  "test:docs:browser": "node scripts/test-built-docs.js _site",
56
56
  "test:coverage": "node scripts/run-tests.js coverage",
57
- "test:render": "node test/support/artifacts.js && node scripts/run-tests.js render && node scripts/generate-roadmap-gallery.js && node scripts/test-roadmap-gallery.js",
58
- "artifacts:gallery": "node scripts/generate-roadmap-gallery.js",
59
- "artifacts:gallery:test": "node scripts/test-roadmap-gallery.js",
57
+ "test:render": "node scripts/run-render-tests.js",
58
+ "assets:readme": "node scripts/generate-readme-demo.js",
59
+ "artifacts:clean": "node test/support/artifacts.js",
60
+ "artifacts:gallery": "node scripts/generate-artifact-gallery.js",
61
+ "artifacts:gallery:test": "node scripts/test-artifact-gallery.js",
60
62
  "docs:images": "node scripts/generate-doc-images.js",
61
63
  "docs:llms": "node scripts/generate-llm-docs.js",
64
+ "docs:capabilities": "node scripts/generate-doc-capabilities.js",
65
+ "docs:capabilities:check": "node scripts/generate-doc-capabilities.js --check",
66
+ "docs:actions": "node scripts/generate-doc-action-metadata.js",
67
+ "docs:actions:check": "node scripts/generate-doc-action-metadata.js --check",
68
+ "docs:search": "node scripts/generate-doc-search-index.js",
69
+ "docs:search:check": "node scripts/generate-doc-search-index.js --check",
70
+ "docs:reference": "node scripts/generate-doc-action-reference.js",
71
+ "docs:reference:check": "node scripts/generate-doc-action-reference.js --check",
72
+ "docs:metadata": "node scripts/generate-doc-page-metadata.js",
73
+ "docs:metadata:check": "node scripts/generate-doc-page-metadata.js --check",
74
+ "docs:preflight": "node scripts/check-docs-environment.js",
62
75
  "docs:signatures": "node scripts/generate-doc-signatures.js",
63
76
  "docs:signatures:check": "node scripts/generate-doc-signatures.js --check",
64
- "docs:generate": "npm run docs:signatures && npm run docs:images && npm run docs:llms",
77
+ "docs:generate": "npm run docs:signatures && npm run docs:capabilities && npm run docs:actions && npm run docs:reference && npm run docs:metadata && npm run docs:search && npm run docs:images && npm run docs:llms",
65
78
  "docs:build": "bundle exec jekyll build --source docs --destination _site --baseurl /ggaction",
66
- "docs:verify": "npm run docs:generate && npm run test:docs && npm run docs:build && npm run test:docs:built && npm run test:docs:browser",
79
+ "docs:verify": "npm run docs:preflight && npm run docs:generate && npm run test:docs && npm run docs:build && npm run test:docs:built && npm run test:docs:browser",
67
80
  "contracts:catalog": "node scripts/generate-action-catalog.js",
68
81
  "contracts:catalog:check": "node scripts/generate-action-catalog.js --check",
69
82
  "package:check": "node scripts/package-artifact.js --check",
@@ -83,8 +96,11 @@
83
96
  "@napi-rs/canvas": "^1.0.2"
84
97
  },
85
98
  "devDependencies": {
99
+ "@axe-core/playwright": "^4.12.1",
86
100
  "es-module-lexer": "^1.7.0",
101
+ "gifenc": "^1.0.3",
87
102
  "playwright": "^1.61.1",
88
- "typescript": "^7.0.2"
103
+ "typescript": "^7.0.2",
104
+ "vite": "^6.4.3"
89
105
  }
90
106
  }
@@ -1,7 +1,7 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateKeys } from "../../core/validation.js";
3
- import { findDataset } from "../../selectors/datasets.js";
4
3
  import { findSemanticScale } from "../../selectors/scales.js";
4
+ import { resolveFacadeData } from "../charts/shared.js";
5
5
  import {
6
6
  BOX_PLOT_OPTIONS,
7
7
  boxEncodingArgs,
@@ -9,6 +9,7 @@ import {
9
9
  resolveBoxMedianAppearance,
10
10
  resolveBoxOutlierAppearance,
11
11
  resolveBoxPosition,
12
+ resolveBoxGuides,
12
13
  resolveBoxWhisker,
13
14
  resolveBoxWidth
14
15
  } from "./options.js";
@@ -26,11 +27,14 @@ export const createBoxPlot = action(
26
27
  function (args = {}) {
27
28
  validateKeys(args, BOX_PLOT_OPTIONS, "createBoxPlot");
28
29
  const id = resolveBoxPlotId(this, args.id);
29
- const source = resolveBoxSourceLayer(this, args.target);
30
- const data = args.data ?? source?.data ?? this.context.currentData;
31
- if (findDataset(this, data) === undefined) {
32
- throw new Error("createBoxPlot requires data or one inferable dataset.");
33
- }
30
+ const source = resolveBoxSourceLayer(this, args.target, {
31
+ requiresInference: args.x === undefined || args.y === undefined
32
+ });
33
+ const data = resolveFacadeData(
34
+ this,
35
+ args.data ?? source?.data,
36
+ "createBoxPlot"
37
+ );
34
38
  const x = resolveBoxPosition(args.x, "x") ?? source?.encoding?.x;
35
39
  const y = resolveBoxPosition(args.y, "y") ?? source?.encoding?.y;
36
40
  const whisker = resolveBoxWhisker(args.whisker);
@@ -41,6 +45,7 @@ export const createBoxPlot = action(
41
45
  const box = resolveBoxAppearance(args.box);
42
46
  const median = resolveBoxMedianAppearance(args.median);
43
47
  const outlier = resolveBoxOutlierAppearance(args.outlier);
48
+ const guides = resolveBoxGuides(args.guides);
44
49
  if (x !== undefined && y !== undefined && resolveBoxOrientation(x, y) === undefined) {
45
50
  throw new Error(
46
51
  "createBoxPlot requires one categorical axis and one quantitative axis."
@@ -62,7 +67,8 @@ export const createBoxPlot = action(
62
67
  outliers: args.outliers ?? true,
63
68
  box,
64
69
  median,
65
- outlier
70
+ outlier,
71
+ guides
66
72
  }
67
73
  });
68
74
  if (x !== undefined) {
@@ -4,7 +4,8 @@ import { validateUserId } from "../../core/identifiers.js";
4
4
  import { validateKeys } from "../../core/validation.js";
5
5
  import { BOX_FIELDS, deriveBoxData, normalizeBoxTransform } from
6
6
  "../../grammar/boxPlot.js";
7
- import { hasDataset } from "../../selectors/index.js";
7
+ import { planDerivedDataRevision } from
8
+ "../../materialization/dataProvenance.js";
8
9
  import { findDataset } from "../../selectors/datasets.js";
9
10
  import { findLayer } from "../../selectors/layers.js";
10
11
  import {
@@ -61,14 +62,6 @@ function resolveEditedWhisker(current, value) {
61
62
  return resolveBoxWhisker(candidate, "editBoxPlot");
62
63
  }
63
64
 
64
- function nextRevisionId(program, ownerId, kind) {
65
- let revision = 1;
66
- while (hasDataset(program, `${ownerId}${kind}Revision${revision}`)) {
67
- revision += 1;
68
- }
69
- return `${ownerId}${kind}Revision${revision}`;
70
- }
71
-
72
65
  function removeOwnedMark(program, id) {
73
66
  return program
74
67
  .editSemantic({ property: `layer[${id}]`, remove: true })
@@ -76,10 +69,6 @@ function removeOwnedMark(program, id) {
76
69
  ._withoutMaterializationConfig(["marks", id]);
77
70
  }
78
71
 
79
- function rebindRuleData(program, id, data) {
80
- return program.editSemantic({ property: `layer[${id}].data`, value: data });
81
- }
82
-
83
72
  export const editBoxPlot = action(
84
73
  {
85
74
  op: "editBoxPlot",
@@ -156,7 +145,18 @@ export const editBoxPlot = action(
156
145
  derived.outliers.length > 0;
157
146
 
158
147
  if (revisesData) {
159
- summaryId = nextRevisionId(this, owner.id, "SummaryData");
148
+ const whiskerConfig = next.markConfigs[current.whiskerId];
149
+ const capIds = [
150
+ whiskerConfig.errorBar.lowerCapId,
151
+ whiskerConfig.errorBar.upperCapId
152
+ ].filter(id => id !== undefined);
153
+ const summaryRevision = planDerivedDataRevision(this, {
154
+ owner: owner.id,
155
+ role: "SummaryData",
156
+ previous: current.summaryId,
157
+ consumers: [owner.id, current.whiskerId, ...capIds, current.medianId]
158
+ });
159
+ summaryId = summaryRevision.id;
160
160
  next = next.createBoxSummaryData({
161
161
  id: summaryId,
162
162
  source: current.source,
@@ -166,7 +166,16 @@ export const editBoxPlot = action(
166
166
  ...(whisker.factor === undefined ? {} : { factor: whisker.factor })
167
167
  });
168
168
  if (hasOutliers) {
169
- outlierDataId = nextRevisionId(this, owner.id, "OutlierData");
169
+ const hadOutlierLayer = findLayer(next, current.outlierId) !== undefined;
170
+ const outlierRevision = planDerivedDataRevision(this, {
171
+ owner: owner.id,
172
+ role: "OutlierData",
173
+ ...(current.outlierDataId === undefined
174
+ ? {}
175
+ : { previous: current.outlierDataId }),
176
+ consumers: hadOutlierLayer ? [current.outlierId] : []
177
+ });
178
+ outlierDataId = outlierRevision.id;
170
179
  next = next.createBoxOutlierData({
171
180
  id: outlierDataId,
172
181
  source: current.source,
@@ -175,37 +184,24 @@ export const editBoxPlot = action(
175
184
  whisker: whisker.type,
176
185
  factor: whisker.factor
177
186
  });
187
+ for (const rebind of outlierRevision.rebinds) {
188
+ next = next.rebindLayerData(rebind);
189
+ }
178
190
  } else {
179
191
  outlierDataId = undefined;
180
192
  }
181
193
 
182
- next = next.editSemantic({
183
- property: `layer[${owner.id}].data`,
184
- value: summaryId
185
- });
186
- const whiskerConfig = next.markConfigs[current.whiskerId];
187
- const capIds = [
188
- whiskerConfig.errorBar.lowerCapId,
189
- whiskerConfig.errorBar.upperCapId
190
- ].filter(id => id !== undefined);
191
- next = rebindRuleData(next, current.whiskerId, summaryId)
192
- ._withMarkConfig(current.whiskerId, {
194
+ for (const rebind of summaryRevision.rebinds) {
195
+ next = next.rebindLayerData(rebind);
196
+ }
197
+ next = next._withMarkConfig(current.whiskerId, {
193
198
  ...whiskerConfig,
194
199
  errorBar: { ...whiskerConfig.errorBar, data: summaryId }
195
200
  });
196
- for (const capId of capIds) {
197
- next = rebindRuleData(next, capId, summaryId);
198
- }
199
- next = rebindRuleData(next, current.medianId, summaryId);
200
201
 
201
202
  const hadOutlierLayer = findLayer(next, current.outlierId) !== undefined;
202
203
  if (hadOutlierLayer && !hasOutliers) {
203
204
  next = removeOwnedMark(next, current.outlierId);
204
- } else if (hadOutlierLayer && hasOutliers) {
205
- next = next.editSemantic({
206
- property: `layer[${current.outlierId}].data`,
207
- value: outlierDataId
208
- });
209
205
  } else if (!hadOutlierLayer && hasOutliers) {
210
206
  next = next.createBoxOutliers({
211
207
  id: current.outlierId,
@@ -189,6 +189,9 @@ export const materializeBoxPlot = action(
189
189
  outlierDataId: hasOutliers ? outlierDataId : undefined
190
190
  }
191
191
  });
192
+ if (config.guides !== false) {
193
+ next = next.createGuides(config.guides);
194
+ }
192
195
  return next._withContext({ currentMark: ownerId, currentData: source });
193
196
  }
194
197
  );
@@ -8,10 +8,11 @@ import {
8
8
  } from "../../core/validation.js";
9
9
  import { validatePointShape } from "../../grammar/pointShapes.js";
10
10
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
11
+ import { normalizeGuides } from "../charts/shared.js";
11
12
 
12
13
  export const BOX_PLOT_OPTIONS = Object.freeze([
13
14
  "id", "target", "data", "x", "y", "coordinate", "whisker",
14
- "width", "outliers", "box", "median", "outlier"
15
+ "width", "outliers", "box", "median", "outlier", "guides"
15
16
  ]);
16
17
 
17
18
  const DEFAULT_BOX = Object.freeze({
@@ -52,6 +53,12 @@ export function boxEncodingArgs(value) {
52
53
  };
53
54
  }
54
55
 
56
+ export function resolveBoxGuides(value) {
57
+ return value === undefined
58
+ ? false
59
+ : normalizeGuides(value, "createBoxPlot");
60
+ }
61
+
55
62
  export function resolveBoxWhisker(value, operation = "createBoxPlot") {
56
63
  if (value === undefined) return Object.freeze({ type: "tukey", factor: 1.5 });
57
64
  if (!isPlainObject(value)) {
@@ -11,7 +11,9 @@ export function resolveBoxOrientation(x, y) {
11
11
  return undefined;
12
12
  }
13
13
 
14
- export function resolveBoxSourceLayer(program, target) {
14
+ export function resolveBoxSourceLayer(program, target, {
15
+ requiresInference = true
16
+ } = {}) {
15
17
  if (target !== undefined) {
16
18
  const layer = findLayer(program, validateUserId(target, "Box source layer id"));
17
19
  if (layer === undefined) throw new Error(`Unknown box source layer "${target}".`);
@@ -24,7 +26,13 @@ export function resolveBoxSourceLayer(program, target) {
24
26
  const eligible = program.semanticSpec.layers.filter(
25
27
  layer => layer.encoding?.x !== undefined && layer.encoding?.y !== undefined
26
28
  );
27
- return eligible.length === 1 ? eligible[0] : undefined;
29
+ if (eligible.length === 1) return eligible[0];
30
+ if (eligible.length > 1 && requiresInference) {
31
+ throw new Error(
32
+ "createBoxPlot target is ambiguous; provide target or explicit x and y."
33
+ );
34
+ }
35
+ return undefined;
28
36
  }
29
37
 
30
38
  export function resolveBoxPlotId(program, requested) {