ggaction 0.0.3 → 0.0.4
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/CHANGELOG.md +28 -3
- package/README.md +95 -83
- package/package.json +11 -6
- package/src/actions/data/regression.js +1 -1
- package/src/actions/encodings/appearance.js +1 -1
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +151 -0
- package/src/actions/encodings/color/layout.js +91 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/index.js +2 -2
- package/src/actions/encodings/offset.js +1 -1
- package/src/actions/encodings/{position.js → position/index.js} +6 -6
- package/src/actions/encodings/position/resolve.js +1 -1
- package/src/actions/encodings/ranged.js +2 -2
- package/src/actions/encodings/strokeDash.js +1 -1
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/options.js +1 -1
- package/src/actions/errorBars/options.js +1 -1
- package/src/actions/facets/actions.js +1 -1
- package/src/actions/facets/derive.js +2 -2
- package/src/actions/facets/guides.js +1 -1
- package/src/actions/guides/axes/labels.js +1 -1
- package/src/actions/guides/axes/ticks.js +1 -1
- package/src/actions/guides/axes/titles.js +1 -1
- package/src/actions/guides/grids/resolve.js +1 -1
- package/src/actions/guides/legends/categorical/layout.js +4 -2
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +2 -20
- package/src/actions/guides/legends/remove.js +2 -20
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
- package/src/actions/guides/polar/index.js +1 -1
- package/src/actions/guides/polar/resolve.js +1 -1
- package/src/actions/guides/tickValues.js +1 -1
- package/src/actions/intervals/resolve.js +1 -1
- package/src/actions/marks/arc/actions.js +2 -2
- package/src/actions/marks/area/actions.js +2 -2
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/line/actions.js +3 -3
- package/src/actions/marks/rect/actions.js +1 -1
- package/src/actions/marks/rule/actions.js +1 -1
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +1 -1
- package/src/actions/primitives/semanticValue.js +1 -1
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +1 -1
- package/src/actions/scales/consumers/common.js +91 -0
- package/src/actions/scales/consumers/families.js +47 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +2 -2
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/materialize.js +2 -2
- package/src/core/ChartProgram.js +22 -236
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/bars/aggregate.js +1 -1
- package/src/grammar/density.js +1 -1
- package/src/grammar/{facets.js → facets/index.js} +5 -5
- package/src/grammar/interval.js +2 -96
- package/src/grammar/lineSeries.js +1 -1
- package/src/grammar/palettes.js +5 -7
- package/src/grammar/polarLineCommands.js +1 -1
- package/src/grammar/regression/derive.js +97 -0
- package/src/grammar/regression/index.js +22 -0
- package/src/grammar/regression/models.js +282 -0
- package/src/grammar/regression/parameters.js +106 -0
- package/src/grammar/rules.js +1 -1
- package/src/grammar/scales/color.js +4 -2
- package/src/grammar/statistics/studentT.js +98 -0
- package/src/grammar/transforms.js +1 -1
- package/src/materialization/bars/aggregate.js +1 -1
- package/src/materialization/bars/grouped.js +1 -1
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +6 -87
- package/src/materialization/encodings.js +2 -2
- package/src/materialization/facetGuides/index.js +2 -0
- package/src/materialization/facetGuides/legacyCategorical.js +125 -0
- package/src/materialization/facetGuides/placement.js +126 -0
- package/src/materialization/facetGuides/preparation.js +188 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/marks/capabilities.js +141 -0
- package/src/materialization/marks/index.js +105 -0
- package/src/materialization/marks/policies.js +87 -0
- package/src/materialization/rect.js +1 -1
- package/src/materialization/rowEncoding.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +94 -0
- package/src/materialization/scales/map.js +1 -1
- package/src/materialization/scales/policies/series.js +1 -1
- package/src/materialization/scales/resolve.js +1 -1
- package/src/materialization/selection/items/bar.js +1 -1
- package/src/materialization/selection/styles.js +1 -1
- package/src/renderers/canvas/text.js +14 -1
- package/types/extension.d.ts +4 -1
- package/src/actions/encodings/color.js +0 -424
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/facetGuides.js +0 -426
- package/src/materialization/marks.js +0 -316
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to `ggaction` are recorded in this file.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [0.0.4] - 2026-07-19
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Transferred the canonical repository to the `ggaction` organization and moved public documentation to
|
|
12
|
+
`https://ggaction.github.io/ggaction/`.
|
|
13
|
+
- Made public documentation deployment release-scoped so ordinary `main` pushes continue to verify docs without
|
|
14
|
+
changing the published site.
|
|
15
|
+
- Refactored source ownership and materialization boundaries while preserving the public API, stored specifications,
|
|
16
|
+
trace hierarchy, and rendered output.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Normalized numeric Canvas font weights before rendering so valid intermediate values such as `650` retain normal
|
|
21
|
+
text geometry in both Browser Canvas and Node PNG output.
|
|
22
|
+
- Applied right categorical legend offsets from the plot boundary consistently during creation and focused layout
|
|
23
|
+
edits, including labels, titles, and optional backgrounds.
|
|
24
|
+
- Accepted `count` on sequential palette descriptors as a concrete gradient-stop count, consistently across
|
|
25
|
+
top-level palette shorthands, nested ranges, encodings, direct scales, and scale edits.
|
|
26
|
+
- Preserved concrete `ChartProgram` subclasses in the TypeScript signature of wrapped extension actions and added
|
|
27
|
+
a strict NodeNext declaration-merging authoring pattern.
|
|
28
|
+
- Routed every selective `llms.txt` target to a deployed HTML page, stabilized action fragments, and made built-site
|
|
29
|
+
checks validate both HTTP targets and DOM IDs.
|
|
30
|
+
|
|
7
31
|
## [0.0.3] - 2026-07-19
|
|
8
32
|
|
|
9
33
|
### Added
|
|
@@ -58,6 +82,7 @@ All notable changes to `ggaction` are recorded in this file.
|
|
|
58
82
|
- Cartesian charts are the complete current path. Polar semantic tokens exist only where explicitly documented and do
|
|
59
83
|
not imply complete polar rendering.
|
|
60
84
|
|
|
61
|
-
[0.0.
|
|
62
|
-
[0.0.
|
|
63
|
-
[0.0.
|
|
85
|
+
[0.0.4]: https://github.com/ggaction/ggaction/releases/tag/v0.0.4
|
|
86
|
+
[0.0.3]: https://github.com/ggaction/ggaction/releases/tag/v0.0.3
|
|
87
|
+
[0.0.2]: https://github.com/ggaction/ggaction/releases/tag/v0.0.2
|
|
88
|
+
[0.0.1]: https://github.com/ggaction/ggaction/releases/tag/v0.0.1
|
package/README.md
CHANGED
|
@@ -1,119 +1,131 @@
|
|
|
1
1
|
# ggaction
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ggaction) [](./LICENSE) [](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
|
+
.createPointMark({ id: "points" })
|
|
42
|
+
.encodeX({ field: "Displacement" })
|
|
43
|
+
.encodeY({ field: "Acceleration" })
|
|
44
|
+
.encodeColor({ field: "Origin" })
|
|
45
|
+
.createRegression()
|
|
46
|
+
.createGuides();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Why actions?
|
|
50
|
+
|
|
51
|
+
- **Progressive** — build and revise a chart one meaningful operation at a time.
|
|
52
|
+
- **Traceable** — retain high-level actions and the wrapped actions they invoke.
|
|
53
|
+
- **Materialized** — actions create concrete backend-neutral graphics; rendering does not perform hidden semantic compilation.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install ggaction
|
|
59
|
+
```
|
|
5
60
|
|
|
6
61
|
```javascript
|
|
7
62
|
import { chart, render } from "ggaction";
|
|
8
63
|
|
|
9
|
-
const
|
|
10
|
-
{
|
|
11
|
-
{
|
|
12
|
-
{
|
|
64
|
+
const observations = [
|
|
65
|
+
{ displacement: 97, acceleration: 14.5, origin: "Japan" },
|
|
66
|
+
{ displacement: 140, acceleration: 15.5, origin: "USA" },
|
|
67
|
+
{ displacement: 86, acceleration: 16.4, origin: "Japan" }
|
|
13
68
|
];
|
|
14
69
|
|
|
15
70
|
const program = chart()
|
|
16
71
|
.createCanvas({
|
|
17
72
|
width: 640,
|
|
18
73
|
height: 400,
|
|
19
|
-
margin: { top: 30, right:
|
|
74
|
+
margin: { top: 30, right: 130, bottom: 60, left: 70 }
|
|
20
75
|
})
|
|
21
|
-
.createData({ values:
|
|
76
|
+
.createData({ values: observations })
|
|
22
77
|
.createPointMark()
|
|
23
|
-
.encodeX({ field: "
|
|
24
|
-
.encodeY({ field: "
|
|
78
|
+
.encodeX({ field: "displacement" })
|
|
79
|
+
.encodeY({ field: "acceleration" })
|
|
25
80
|
.encodeColor({ field: "origin" })
|
|
26
|
-
.encodeRadius({ value:
|
|
27
|
-
.createGuides(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
render(program, document.querySelector("#chart").getContext("2d"));
|
|
81
|
+
.encodeRadius({ value: 4 })
|
|
82
|
+
.createGuides();
|
|
83
|
+
|
|
84
|
+
const context = document.querySelector("#chart").getContext("2d");
|
|
85
|
+
render(program, context);
|
|
35
86
|
```
|
|
36
87
|
|
|
37
|
-
|
|
38
|
-
state is never automatically compiled during rendering.
|
|
88
|
+
For the advanced version, follow the [regression recipe](https://ggaction.github.io/ggaction/recipes/regression-scatterplot/) or open the [runnable example](./examples/cars-regression-scatterplot/).
|
|
39
89
|
|
|
40
|
-
|
|
41
|
-
> before `1.0.0`; changes are recorded in the [changelog](./CHANGELOG.md).
|
|
90
|
+
## What it supports
|
|
42
91
|
|
|
43
|
-
|
|
92
|
+
- Cartesian and polar charts
|
|
93
|
+
- Statistical layers and intervals
|
|
94
|
+
- Faceting and program composition
|
|
95
|
+
- Mark selection and coordinated highlighting
|
|
96
|
+
- Browser Canvas and Node PNG output
|
|
97
|
+
- TypeScript declarations and traceable extension actions
|
|
44
98
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
99
|
+
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).
|
|
100
|
+
|
|
101
|
+
## Package entries
|
|
48
102
|
|
|
49
|
-
The package is ESM-only and
|
|
50
|
-
entry works with modern browser build tools and Canvas. `ggaction/png` is
|
|
51
|
-
Node-only and uses a native Canvas dependency.
|
|
103
|
+
The package is ESM-only and requires Node.js 20 or later.
|
|
52
104
|
|
|
53
105
|
| Entry | Purpose |
|
|
54
106
|
| --- | --- |
|
|
55
107
|
| `ggaction` | Create chart programs and render them to Browser Canvas |
|
|
56
|
-
| `ggaction/extension` | Author wrapped actions
|
|
108
|
+
| `ggaction/extension` | Author wrapped actions with public low-level primitives |
|
|
57
109
|
| `ggaction/png` | Render a completed program to a PNG file in Node.js |
|
|
58
110
|
|
|
59
|
-
All
|
|
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.
|
|
111
|
+
All entries include TypeScript declarations. The default entry is browser-safe; the PNG adapter is Node-only.
|
|
63
112
|
|
|
64
113
|
## Documentation
|
|
65
114
|
|
|
66
|
-
- [
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
69
|
-
- [
|
|
70
|
-
- [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
115
|
+
- [Getting Started](https://ggaction.github.io/ggaction/getting-started/)
|
|
116
|
+
- [Tutorials and Examples](https://ggaction.github.io/ggaction/tutorials/)
|
|
117
|
+
- [Action Reference](https://ggaction.github.io/ggaction/reference/actions/)
|
|
118
|
+
- [Concepts](https://ggaction.github.io/ggaction/concepts/chart-program/)
|
|
119
|
+
- [Supported Features](https://ggaction.github.io/ggaction/supported-features/)
|
|
120
|
+
|
|
121
|
+
## Status and development
|
|
122
|
+
|
|
123
|
+
> **Status:** `0.0.4` is the current experimental public release. APIs may change before `1.0.0`; changes are recorded in the [changelog](./CHANGELOG.md).
|
|
114
124
|
|
|
115
125
|
```bash
|
|
116
126
|
npm install
|
|
127
|
+
npm run assets:readme
|
|
117
128
|
npm test
|
|
118
129
|
npm run test:render
|
|
130
|
+
npm run test:docs
|
|
119
131
|
```
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ggaction",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
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/
|
|
9
|
+
"url": "git+https://github.com/ggaction/ggaction.git"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://ggaction.github.io/ggaction/",
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/
|
|
13
|
+
"url": "https://github.com/ggaction/ggaction/issues"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"visualization",
|
|
@@ -55,13 +55,16 @@
|
|
|
55
55
|
"test:docs:browser": "node scripts/test-built-docs.js _site",
|
|
56
56
|
"test:coverage": "node scripts/run-tests.js coverage",
|
|
57
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
|
+
"assets:readme": "node scripts/generate-readme-demo.js",
|
|
58
59
|
"artifacts:gallery": "node scripts/generate-roadmap-gallery.js",
|
|
59
60
|
"artifacts:gallery:test": "node scripts/test-roadmap-gallery.js",
|
|
60
61
|
"docs:images": "node scripts/generate-doc-images.js",
|
|
61
62
|
"docs:llms": "node scripts/generate-llm-docs.js",
|
|
63
|
+
"docs:capabilities": "node scripts/generate-doc-capabilities.js",
|
|
64
|
+
"docs:capabilities:check": "node scripts/generate-doc-capabilities.js --check",
|
|
62
65
|
"docs:signatures": "node scripts/generate-doc-signatures.js",
|
|
63
66
|
"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",
|
|
67
|
+
"docs:generate": "npm run docs:signatures && npm run docs:capabilities && npm run docs:images && npm run docs:llms",
|
|
65
68
|
"docs:build": "bundle exec jekyll build --source docs --destination _site --baseurl /ggaction",
|
|
66
69
|
"docs:verify": "npm run docs:generate && npm run test:docs && npm run docs:build && npm run test:docs:built && npm run test:docs:browser",
|
|
67
70
|
"contracts:catalog": "node scripts/generate-action-catalog.js",
|
|
@@ -84,7 +87,9 @@
|
|
|
84
87
|
},
|
|
85
88
|
"devDependencies": {
|
|
86
89
|
"es-module-lexer": "^1.7.0",
|
|
90
|
+
"gifenc": "^1.0.3",
|
|
87
91
|
"playwright": "^1.61.1",
|
|
88
|
-
"typescript": "^7.0.2"
|
|
92
|
+
"typescript": "^7.0.2",
|
|
93
|
+
"vite": "^6.4.3"
|
|
89
94
|
}
|
|
90
95
|
}
|
|
@@ -4,7 +4,7 @@ import { validateKeys } from "../../core/validation.js";
|
|
|
4
4
|
import {
|
|
5
5
|
deriveRegression,
|
|
6
6
|
normalizeRegressionParameters
|
|
7
|
-
} from "../../grammar/regression.js";
|
|
7
|
+
} from "../../grammar/regression/index.js";
|
|
8
8
|
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
9
9
|
|
|
10
10
|
const OPTIONS = Object.freeze([
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readQuantitativeField,
|
|
3
|
+
readScaleField,
|
|
4
|
+
readTemporalField
|
|
5
|
+
} from "../../../grammar/scales/index.js";
|
|
6
|
+
import {
|
|
7
|
+
BAR_GRAINS,
|
|
8
|
+
resolveBarChannels,
|
|
9
|
+
resolveBarGrain
|
|
10
|
+
} from "../../../grammar/bars/policy.js";
|
|
11
|
+
import {
|
|
12
|
+
validateAggregate,
|
|
13
|
+
validateAggregateFieldType,
|
|
14
|
+
validateAggregateFieldValues
|
|
15
|
+
} from "../../../grammar/aggregate.js";
|
|
16
|
+
import {
|
|
17
|
+
resolveQuantitativeColorScaleDefinition
|
|
18
|
+
} from "../../scales/definitions.js";
|
|
19
|
+
import {
|
|
20
|
+
resolveReassignmentScaleOptions,
|
|
21
|
+
resolveTarget
|
|
22
|
+
} from "../shared.js";
|
|
23
|
+
import { applyMaterializationPlan } from "../../../materialization/dependencies.js";
|
|
24
|
+
import {
|
|
25
|
+
planEncodingRematerialization
|
|
26
|
+
} from "../../../materialization/encodings.js";
|
|
27
|
+
import {
|
|
28
|
+
assertNoConstantColor,
|
|
29
|
+
resolveColorScaleOptions
|
|
30
|
+
} from "./policy.js";
|
|
31
|
+
|
|
32
|
+
export function encodeContinuousColor(program, args) {
|
|
33
|
+
if (!["quantitative", "temporal"].includes(args.fieldType)) {
|
|
34
|
+
throw new Error(`Unsupported color field type "${args.fieldType}".`);
|
|
35
|
+
}
|
|
36
|
+
if (args.layout !== undefined) {
|
|
37
|
+
throw new Error("Continuous color does not support layout.");
|
|
38
|
+
}
|
|
39
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
40
|
+
program,
|
|
41
|
+
args.target,
|
|
42
|
+
["point", "bar", "rect"],
|
|
43
|
+
"continuous color mark"
|
|
44
|
+
);
|
|
45
|
+
assertNoConstantColor(program, layer);
|
|
46
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
47
|
+
layer.encoding?.color,
|
|
48
|
+
resolveColorScaleOptions(args)
|
|
49
|
+
);
|
|
50
|
+
const scale = resolveQuantitativeColorScaleDefinition(
|
|
51
|
+
program,
|
|
52
|
+
args.fieldType,
|
|
53
|
+
requestedScale
|
|
54
|
+
);
|
|
55
|
+
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"Continuous color scale unknown currently requires a row-owned point mark."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
layer.mark.type === "bar" &&
|
|
62
|
+
["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)
|
|
63
|
+
) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"Continuous bar color cannot replace an existing nominal color layout."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
let aggregate;
|
|
69
|
+
if (["point", "rect"].includes(layer.mark.type)) {
|
|
70
|
+
if (args.aggregate !== undefined) {
|
|
71
|
+
throw new Error(`${layer.mark.type} continuous color does not support aggregate.`);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
if (args.fieldType !== "quantitative") {
|
|
75
|
+
throw new Error("Aggregate bar color currently requires a quantitative field.");
|
|
76
|
+
}
|
|
77
|
+
if (resolveBarGrain(layer) !== BAR_GRAINS.aggregate) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Continuous bar color requires a complete categorical aggregate bar."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const channels = resolveBarChannels(layer);
|
|
83
|
+
const measure = layer.encoding?.[channels.measure];
|
|
84
|
+
aggregate = args.aggregate ?? (
|
|
85
|
+
measure?.field === args.field ? measure.aggregate : undefined
|
|
86
|
+
);
|
|
87
|
+
if (aggregate === undefined) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
"Continuous bar color requires aggregate when its field differs from the measure field."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
aggregate = validateAggregate(aggregate);
|
|
93
|
+
validateAggregateFieldType(aggregate, args.fieldType);
|
|
94
|
+
validateAggregateFieldValues(dataset.values, args.field, args.fieldType);
|
|
95
|
+
}
|
|
96
|
+
if (layer.mark.type === "rect") {
|
|
97
|
+
readScaleField(dataset.values, args.field, args.fieldType, {
|
|
98
|
+
allowUnknown: true
|
|
99
|
+
});
|
|
100
|
+
} else if (Object.hasOwn(scale, "unknown")) {
|
|
101
|
+
readScaleField(dataset.values, args.field, args.fieldType, {
|
|
102
|
+
allowUnknown: true
|
|
103
|
+
});
|
|
104
|
+
} else if (args.fieldType === "temporal") {
|
|
105
|
+
readTemporalField(dataset.values, args.field);
|
|
106
|
+
} else {
|
|
107
|
+
readQuantitativeField(dataset.values, args.field);
|
|
108
|
+
}
|
|
109
|
+
const next = program
|
|
110
|
+
.editSemantic({
|
|
111
|
+
property: `layer[${target}].encoding.color.field`,
|
|
112
|
+
value: args.field
|
|
113
|
+
})
|
|
114
|
+
.editSemantic({
|
|
115
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
116
|
+
value: args.fieldType
|
|
117
|
+
})
|
|
118
|
+
.editSemantic({
|
|
119
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
120
|
+
value: scale.id
|
|
121
|
+
});
|
|
122
|
+
const encoded = aggregate === undefined
|
|
123
|
+
? next
|
|
124
|
+
: next.editSemantic({
|
|
125
|
+
property: `layer[${target}].encoding.color.aggregate`,
|
|
126
|
+
value: aggregate
|
|
127
|
+
});
|
|
128
|
+
const scaled = encoded.setQuantitativeColorScale(scale);
|
|
129
|
+
return applyMaterializationPlan(
|
|
130
|
+
scaled,
|
|
131
|
+
planEncodingRematerialization(scaled, {
|
|
132
|
+
target,
|
|
133
|
+
channel: "color",
|
|
134
|
+
scale: scale.id
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import {
|
|
3
|
+
readNominalField,
|
|
4
|
+
readScaleField,
|
|
5
|
+
validateCategoricalFieldType
|
|
6
|
+
} from "../../../grammar/scales/index.js";
|
|
7
|
+
import {
|
|
8
|
+
resolveBarGrain
|
|
9
|
+
} from "../../../grammar/bars/policy.js";
|
|
10
|
+
import { resolveColorScaleDefinition } from "../../scales/definitions.js";
|
|
11
|
+
import {
|
|
12
|
+
applyEncodingScale,
|
|
13
|
+
resolveReassignmentScaleOptions,
|
|
14
|
+
resolveTarget,
|
|
15
|
+
validateLineSeriesCompatibility,
|
|
16
|
+
validateOptions
|
|
17
|
+
} from "../shared.js";
|
|
18
|
+
import { applyMaterializationPlan } from "../../../materialization/dependencies.js";
|
|
19
|
+
import {
|
|
20
|
+
planEncodingRematerialization
|
|
21
|
+
} from "../../../materialization/encodings.js";
|
|
22
|
+
import { encodeContinuousColor } from "./continuous.js";
|
|
23
|
+
import {
|
|
24
|
+
applyColorLayoutCompanion,
|
|
25
|
+
preSynchronizeGroupedOffset
|
|
26
|
+
} from "./layout.js";
|
|
27
|
+
import {
|
|
28
|
+
assertNoConstantColor,
|
|
29
|
+
COLOR_ENCODING_OPTIONS,
|
|
30
|
+
resolveColorLayout,
|
|
31
|
+
resolveColorScaleOptions
|
|
32
|
+
} from "./policy.js";
|
|
33
|
+
|
|
34
|
+
const encodeColor = action(
|
|
35
|
+
{
|
|
36
|
+
op: "encodeColor",
|
|
37
|
+
description: "Encode a field as graphical color."
|
|
38
|
+
},
|
|
39
|
+
function (args = {}) {
|
|
40
|
+
validateOptions(args, COLOR_ENCODING_OPTIONS, "encodeColor");
|
|
41
|
+
const requestedFieldType = args.fieldType ?? "nominal";
|
|
42
|
+
if (!["nominal", "ordinal"].includes(requestedFieldType)) {
|
|
43
|
+
return encodeContinuousColor(this, {
|
|
44
|
+
...args,
|
|
45
|
+
fieldType: requestedFieldType
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (args.aggregate !== undefined) {
|
|
49
|
+
throw new Error("Categorical color does not support aggregate.");
|
|
50
|
+
}
|
|
51
|
+
const fieldType = validateCategoricalFieldType(requestedFieldType);
|
|
52
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
53
|
+
this,
|
|
54
|
+
args.target,
|
|
55
|
+
["point", "line", "bar", "area", "arc", "rect"],
|
|
56
|
+
"color mark"
|
|
57
|
+
);
|
|
58
|
+
assertNoConstantColor(this, layer);
|
|
59
|
+
if (
|
|
60
|
+
layer.mark.type === "area" &&
|
|
61
|
+
(layer.encoding?.group?.field === undefined ||
|
|
62
|
+
layer.encoding.group.field !== args.field)
|
|
63
|
+
) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"Area color encoding must match an existing group encoding."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
validateLineSeriesCompatibility(layer, "color", args.field);
|
|
69
|
+
|
|
70
|
+
const barGrain = resolveBarGrain(layer);
|
|
71
|
+
if (layer.mark.type === "bar" && barGrain === undefined) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
"Bar color encoding requires a complete histogram encoding or a complete ordinal aggregate encoding."
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const layout = resolveColorLayout(layer, args.layout, barGrain);
|
|
77
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
78
|
+
layer.encoding?.color,
|
|
79
|
+
resolveColorScaleOptions(args)
|
|
80
|
+
);
|
|
81
|
+
const scale = resolveColorScaleDefinition(this, requestedScale);
|
|
82
|
+
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
83
|
+
throw new Error(
|
|
84
|
+
"Categorical color scale unknown currently requires a row-owned point mark."
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (layer.mark.type === "rect") {
|
|
88
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
89
|
+
allowUnknown: true
|
|
90
|
+
});
|
|
91
|
+
} else if (Object.hasOwn(scale, "unknown")) {
|
|
92
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
93
|
+
allowUnknown: true
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
readNominalField(dataset.values, args.field);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let next = this
|
|
100
|
+
.editSemantic({
|
|
101
|
+
property: `layer[${target}].encoding.color.field`,
|
|
102
|
+
value: args.field
|
|
103
|
+
})
|
|
104
|
+
.editSemantic({
|
|
105
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
106
|
+
value: fieldType
|
|
107
|
+
})
|
|
108
|
+
.editSemantic({
|
|
109
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
110
|
+
value: scale.id
|
|
111
|
+
});
|
|
112
|
+
if (layout !== undefined) {
|
|
113
|
+
next = next.editSemantic({
|
|
114
|
+
property: `layer[${target}].encoding.color.layout`,
|
|
115
|
+
value: layout
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
next = preSynchronizeGroupedOffset(next, {
|
|
119
|
+
target,
|
|
120
|
+
layer,
|
|
121
|
+
layout,
|
|
122
|
+
field: args.field,
|
|
123
|
+
fieldType,
|
|
124
|
+
scale,
|
|
125
|
+
requestedScale
|
|
126
|
+
});
|
|
127
|
+
next = applyEncodingScale(next, scale, requestedScale, {
|
|
128
|
+
reassignment: layer.encoding?.color?.scale === scale.id
|
|
129
|
+
});
|
|
130
|
+
next = applyColorLayoutCompanion(next, {
|
|
131
|
+
target,
|
|
132
|
+
layer,
|
|
133
|
+
layout,
|
|
134
|
+
scale,
|
|
135
|
+
field: args.field
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return applyMaterializationPlan(
|
|
139
|
+
next,
|
|
140
|
+
planEncodingRematerialization(next, {
|
|
141
|
+
target,
|
|
142
|
+
channel: "color",
|
|
143
|
+
scale: scale.id
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
export function registerColorEncodingAction(ProgramClass) {
|
|
150
|
+
ProgramClass.prototype.encodeColor = encodeColor;
|
|
151
|
+
}
|