ggaction 0.0.6 → 0.0.8
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 +43 -0
- package/README.md +77 -14
- package/package.json +19 -2
- package/src/BasicChartProgram.js +10 -0
- package/src/actions/basic.js +21 -0
- package/src/actions/canvas/actions.js +42 -0
- package/src/actions/canvas/index.js +5 -1
- package/src/actions/categoryOrder/index.js +111 -0
- package/src/actions/charts/basic.js +13 -0
- package/src/actions/data/basic.js +11 -0
- package/src/actions/data/basicBin2d.js +43 -0
- package/src/actions/data/bin2d.js +1 -26
- package/src/actions/data/bin2dMaterialize.js +29 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/timeUnit.js +48 -0
- package/src/actions/encodings/angle.js +77 -0
- package/src/actions/encodings/appearance.js +6 -2
- package/src/actions/encodings/basic.js +21 -0
- package/src/actions/encodings/color/index.js +17 -13
- package/src/actions/encodings/color/layout.js +2 -0
- package/src/actions/encodings/color/policy.js +3 -0
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/position/index.js +5 -1
- package/src/actions/encodings/position/policies/area.js +40 -2
- package/src/actions/encodings/position/policies/bar.js +6 -0
- package/src/actions/encodings/position/policies/index.js +2 -0
- package/src/actions/encodings/position/policies/tick.js +19 -0
- package/src/actions/encodings/ranged.js +6 -2
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/guides/axes/index.js +10 -2
- package/src/actions/guides/basic.js +19 -0
- package/src/actions/guides/grids/grid.js +7 -3
- package/src/actions/guides/grids/index.js +1 -1
- package/src/actions/guides/legends/categorical/actions.js +23 -6
- package/src/actions/guides/legends/categorical/index.js +14 -2
- package/src/actions/guides/legends/continuous/common.js +17 -5
- package/src/actions/guides/legends/continuous/gradient.js +12 -8
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/opacity.js +70 -17
- package/src/actions/guides/legends/edit.js +13 -8
- package/src/actions/guides/legends/lane.js +468 -0
- package/src/actions/guides/legends/remove.js +3 -7
- package/src/actions/index.js +2 -0
- package/src/actions/marks/area/actions.js +3 -1
- package/src/actions/marks/area/materialize.js +12 -3
- package/src/actions/marks/bar/index.js +5 -1
- package/src/actions/marks/basic.js +11 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/line/actions.js +5 -1
- package/src/actions/marks/line/index.js +4 -1
- package/src/actions/marks/point/index.js +5 -1
- package/src/actions/marks/point/materialize.js +17 -5
- package/src/actions/marks/rect/actions.js +5 -1
- package/src/actions/marks/rect/index.js +4 -1
- package/src/actions/marks/tick/actions.js +225 -0
- package/src/actions/marks/tick/index.js +1 -0
- package/src/actions/primitives/semanticValidation/layer.js +2 -0
- package/src/actions/scales/consumers/index.js +8 -0
- package/src/actions/scales/consumers/seriesLayout.js +22 -2
- package/src/actions/scales/index.js +5 -1
- package/src/actions/scales/materialize.js +2 -0
- package/src/actions/selection/actions.js +5 -2
- package/src/basic.js +2 -0
- package/src/core/vocabulary.js +7 -3
- package/src/grammar/areaSeries.js +112 -2
- package/src/grammar/categoryOrder.js +138 -0
- package/src/grammar/direction.js +46 -0
- package/src/grammar/facets/index.js +1 -1
- package/src/grammar/pointShapes.js +34 -6
- package/src/grammar/positionCompatibility.js +4 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/seriesLayout.js +10 -2
- package/src/grammar/timeUnit.js +108 -0
- package/src/grammar/transforms.js +6 -0
- package/src/grammar/window.js +73 -7
- package/src/layout/legendLane.js +338 -0
- package/src/materialization/marks/capabilities.js +9 -0
- package/src/materialization/marks/index.js +2 -1
- package/src/materialization/marks/policies.js +13 -0
- package/src/materialization/scales/policies/series.js +9 -0
- package/src/materialization/scales/resolve.js +25 -3
- package/src/materialization/selection/items/index.js +1 -0
- package/src/materialization/selection/items/path.js +4 -1
- package/src/materialization/selection/items/tick.js +42 -0
- package/src/materialization/selection/policies/index.js +2 -0
- package/src/materialization/selection/policies/tick.js +10 -0
- package/src/renderers/canvas/index.js +96 -60
- package/src/renderers/canvas/text.js +3 -14
- package/src/renderers/pdf.js +112 -0
- package/src/renderers/svg.js +559 -0
- package/src/renderers/text.js +11 -0
- package/types/basic.d.ts +107 -0
- package/types/index.d.ts +10 -0
- package/types/pdf.d.ts +26 -0
- package/types/program.d.ts +96 -3
- package/types/svg.d.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,47 @@ All notable changes to `ggaction` are recorded in this file.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [0.0.8] - 2026-08-04
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added immutable UTC time-unit derivation, semantic category ordering and reset, and partitioned moving mean/sum
|
|
12
|
+
windows with matching public actions, strict declarations, examples, and Current contracts.
|
|
13
|
+
- Added the Tick mark lifecycle, point/Tick angle encoding, and non-negative centered area stacking with stable
|
|
14
|
+
Canvas, SVG, PNG, and PDF evidence.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Expanded the public example and documentation corpus with actual-data temporal, ordering, directional Tick,
|
|
19
|
+
centered-area, and multi-legend charts, plus installed-package coverage for their supported workflows.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Replaced independent same-edge legend placement with deterministic shared right/left lanes and left-packed
|
|
24
|
+
top/bottom rows, aligning titles, symbols, and labels while preserving lifecycle convergence and compact margins.
|
|
25
|
+
|
|
26
|
+
## [0.0.7] - 2026-07-23
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Added the browser-safe `ggaction/basic` entry for creation-focused scatter, line, bar, histogram, and heatmap
|
|
31
|
+
programs, with matching TypeScript declarations and an enforced 120,000-byte gzip bundle budget.
|
|
32
|
+
- Added browser-safe `ggaction/svg` output and Node-only `ggaction/pdf` single-page vector files, with matching
|
|
33
|
+
declarations, metadata/accessibility options, installed-consumer coverage, and Canvas/SVG/PNG/PDF visual evidence.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Applied the documented numeric font-weight normalization consistently across Canvas, SVG, PNG, and PDF rendering.
|
|
38
|
+
- Generated the repository example index from the canonical chart catalog and reduced the LLM index to concise,
|
|
39
|
+
non-duplicated routes into exact runtime and support documentation.
|
|
40
|
+
- Simplified Getting Started, grouped the support limitations by topic, and clarified the complete documentation
|
|
41
|
+
verification workflow for contributors.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- Corrected the Canvas target in troubleshooting guidance and documented complete, executable Canvas, SVG, PNG, and
|
|
46
|
+
PDF rendering flows with signatures generated from the public TypeScript declarations.
|
|
47
|
+
|
|
7
48
|
## [0.0.6] - 2026-07-23
|
|
8
49
|
|
|
9
50
|
### Added
|
|
@@ -135,6 +176,8 @@ All notable changes to `ggaction` are recorded in this file.
|
|
|
135
176
|
- Cartesian charts are the complete current path. Polar semantic tokens exist only where explicitly documented and do
|
|
136
177
|
not imply complete polar rendering.
|
|
137
178
|
|
|
179
|
+
[0.0.8]: https://github.com/ggaction/ggaction/releases/tag/v0.0.8
|
|
180
|
+
[0.0.7]: https://github.com/ggaction/ggaction/releases/tag/v0.0.7
|
|
138
181
|
[0.0.6]: https://github.com/ggaction/ggaction/releases/tag/v0.0.6
|
|
139
182
|
[0.0.5]: https://github.com/ggaction/ggaction/releases/tag/v0.0.5
|
|
140
183
|
[0.0.4]: https://github.com/ggaction/ggaction/releases/tag/v0.0.4
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ggaction
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/ggaction) [](./LICENSE) [](https://ggaction.github.io/ggaction/)
|
|
3
|
+
[](https://www.npmjs.com/package/ggaction) [](https://github.com/ggaction/ggaction/actions/workflows/ci.yml) [](./LICENSE) [](https://ggaction.github.io/ggaction/)
|
|
4
4
|
|
|
5
5
|
### A grammar for how charts are made.
|
|
6
6
|
|
|
@@ -12,11 +12,7 @@ Build, inspect, select, and revise charts one meaningful action at a time.
|
|
|
12
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
13
|
</p>
|
|
14
14
|
|
|
15
|
-
Every frame is rendered from an immutable `ChartProgram`. The final R² label is a
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install ggaction
|
|
19
|
-
```
|
|
15
|
+
Every frame is rendered from an immutable `ChartProgram`. The final R² label is a traceable action built from public extension primitives.
|
|
20
16
|
|
|
21
17
|
## A grammar of graphical action
|
|
22
18
|
|
|
@@ -57,12 +53,24 @@ const program = chart()
|
|
|
57
53
|
|
|
58
54
|
## Quick start
|
|
59
55
|
|
|
56
|
+
Install the current public release:
|
|
57
|
+
|
|
60
58
|
```bash
|
|
61
59
|
npm install ggaction
|
|
62
60
|
```
|
|
63
61
|
|
|
62
|
+
Add the Canvas element that the rendering code targets. Its accessible name and
|
|
63
|
+
fallback text summarize the chart for contexts where Canvas pixels are not
|
|
64
|
+
available:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<canvas id="chart" aria-label="Scatterplot of displacement versus acceleration by origin">
|
|
68
|
+
Scatterplot of displacement versus acceleration by origin.
|
|
69
|
+
</canvas>
|
|
70
|
+
```
|
|
71
|
+
|
|
64
72
|
```javascript
|
|
65
|
-
import { chart, render } from "ggaction";
|
|
73
|
+
import { chart, render } from "ggaction/basic";
|
|
66
74
|
|
|
67
75
|
const observations = [
|
|
68
76
|
{ displacement: 97, acceleration: 14.5, origin: "Japan" },
|
|
@@ -80,17 +88,58 @@ const program = chart()
|
|
|
80
88
|
.createScatterPlot({
|
|
81
89
|
x: "displacement",
|
|
82
90
|
y: "acceleration",
|
|
83
|
-
color: "origin"
|
|
91
|
+
color: "origin",
|
|
92
|
+
shape: "origin"
|
|
84
93
|
});
|
|
85
94
|
|
|
86
95
|
const context = document.querySelector("#chart").getContext("2d");
|
|
87
96
|
render(program, context);
|
|
88
97
|
```
|
|
89
98
|
|
|
99
|
+
Matching color and shape encodings give each origin a redundant visual cue and
|
|
100
|
+
create a labeled categorical legend automatically.
|
|
101
|
+
|
|
102
|
+
The Quick Start uses the smaller `ggaction/basic` entry for the common creation
|
|
103
|
+
path. Import from `ggaction` when you need editing, selection, composition,
|
|
104
|
+
alternative coordinates, or statistical layers.
|
|
105
|
+
|
|
106
|
+
### Branch revisions without mutation
|
|
107
|
+
|
|
108
|
+
Start a separate program without a shape encoding, then derive alternatives
|
|
109
|
+
without changing the checkpoint:
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
import { chart as editableChart } from "ggaction";
|
|
113
|
+
|
|
114
|
+
const checkpoint = editableChart()
|
|
115
|
+
.createCanvas({ width: 640, height: 400 })
|
|
116
|
+
.createData({ values: observations })
|
|
117
|
+
.createScatterPlot({
|
|
118
|
+
x: "displacement",
|
|
119
|
+
y: "acceleration",
|
|
120
|
+
color: "origin"
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const outlined = checkpoint.editPointMark({
|
|
124
|
+
stroke: "#0f172a",
|
|
125
|
+
strokeWidth: 2
|
|
126
|
+
});
|
|
127
|
+
const muted = checkpoint.editPointMark({ opacity: 0.35 });
|
|
128
|
+
|
|
129
|
+
console.log(checkpoint === outlined); // false
|
|
130
|
+
console.log(checkpoint.trace.children.at(-1).op); // "createScatterPlot"
|
|
131
|
+
console.log(outlined.trace.children.at(-1).op); // "editPointMark"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
All three remain renderable and inspectable. They are immutable values, not an
|
|
135
|
+
automatic undo stack; the application chooses which to retain or render.
|
|
136
|
+
|
|
90
137
|
Use `createLinePlot`, `createBarPlot`, `createHistogram`, and `createHeatmap` for
|
|
91
|
-
the other basic Cartesian charts.
|
|
92
|
-
|
|
93
|
-
|
|
138
|
+
the other basic Cartesian charts. The `ggaction/basic` entry keeps this common
|
|
139
|
+
creation path below a 120,000-byte gzip bundle budget while each facade still
|
|
140
|
+
records its mark, encoding, and guide actions as trace children. Import from
|
|
141
|
+
`ggaction` when you need editing, selection, composition, alternative
|
|
142
|
+
coordinates, or statistical layers. See the
|
|
94
143
|
[Basic Charts API](https://ggaction.github.io/ggaction/api/basic-charts/).
|
|
95
144
|
|
|
96
145
|
Use `createParallelCoordinates({ dimensions })` to connect each source row
|
|
@@ -115,7 +164,7 @@ open the [runnable Gapminder example](./examples/gapminder-horizon/).
|
|
|
115
164
|
- Statistical layers and intervals
|
|
116
165
|
- Faceting and program composition
|
|
117
166
|
- Mark selection and coordinated highlighting
|
|
118
|
-
- Browser Canvas
|
|
167
|
+
- Browser Canvas, SVG, Node PNG, and vector PDF output
|
|
119
168
|
- TypeScript declarations and traceable extension actions
|
|
120
169
|
|
|
121
170
|
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).
|
|
@@ -127,10 +176,14 @@ The package is ESM-only and requires Node.js 20 or later.
|
|
|
127
176
|
| Entry | Purpose |
|
|
128
177
|
| --- | --- |
|
|
129
178
|
| `ggaction` | Create chart programs and render them to Browser Canvas |
|
|
179
|
+
| `ggaction/basic` | Create and render scatter, line, bar, histogram, and heatmap charts with a smaller browser bundle |
|
|
130
180
|
| `ggaction/extension` | Author wrapped actions with public low-level primitives |
|
|
131
181
|
| `ggaction/png` | Render a completed program to a PNG file in Node.js |
|
|
182
|
+
| `ggaction/pdf` | Render a completed program to a single-page vector PDF file in Node.js |
|
|
183
|
+
| `ggaction/svg` | Serialize a completed program to browser-safe SVG |
|
|
132
184
|
|
|
133
|
-
All entries include TypeScript declarations. The default
|
|
185
|
+
All entries include TypeScript declarations. The default, basic, extension, and
|
|
186
|
+
SVG entries are browser-safe; the PNG and PDF adapters are Node-only.
|
|
134
187
|
|
|
135
188
|
## Documentation
|
|
136
189
|
|
|
@@ -140,9 +193,15 @@ All entries include TypeScript declarations. The default entry is browser-safe;
|
|
|
140
193
|
- [Concepts](https://ggaction.github.io/ggaction/concepts/chart-program/)
|
|
141
194
|
- [Supported Features](https://ggaction.github.io/ggaction/supported-features/)
|
|
142
195
|
|
|
196
|
+
## Contributing
|
|
197
|
+
|
|
198
|
+
Bug reports, documentation improvements, examples, and focused code changes are
|
|
199
|
+
welcome. Read [CONTRIBUTING.md](./CONTRIBUTING.md) for setup, scope, tests, and
|
|
200
|
+
the extra discussion required before public API or architecture changes.
|
|
201
|
+
|
|
143
202
|
## Status and development
|
|
144
203
|
|
|
145
|
-
> **Status:** `0.0.
|
|
204
|
+
> **Status:** `0.0.8` is the current experimental public release. APIs may change before `1.0.0`; changes are recorded in the [changelog](./CHANGELOG.md).
|
|
146
205
|
|
|
147
206
|
```bash
|
|
148
207
|
npm install
|
|
@@ -151,3 +210,7 @@ npm test
|
|
|
151
210
|
npm run test:render
|
|
152
211
|
npm run test:docs
|
|
153
212
|
```
|
|
213
|
+
|
|
214
|
+
## Related Links
|
|
215
|
+
|
|
216
|
+
- Action-trace demo: [Link](https://ggactionbot.github.io/action-trace-demo/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ggaction",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Build charts through immutable, traceable graphical actions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hyeon Jeon",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"data-visualization"
|
|
21
21
|
],
|
|
22
22
|
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
23
24
|
"files": [
|
|
24
25
|
"src/",
|
|
25
26
|
"!src/**/AGENTS.md",
|
|
@@ -34,6 +35,10 @@
|
|
|
34
35
|
"types": "./types/index.d.ts",
|
|
35
36
|
"default": "./src/index.js"
|
|
36
37
|
},
|
|
38
|
+
"./basic": {
|
|
39
|
+
"types": "./types/basic.d.ts",
|
|
40
|
+
"default": "./src/basic.js"
|
|
41
|
+
},
|
|
37
42
|
"./extension": {
|
|
38
43
|
"types": "./types/extension.d.ts",
|
|
39
44
|
"default": "./src/extension.js"
|
|
@@ -41,6 +46,14 @@
|
|
|
41
46
|
"./png": {
|
|
42
47
|
"types": "./types/png.d.ts",
|
|
43
48
|
"default": "./src/renderers/png.js"
|
|
49
|
+
},
|
|
50
|
+
"./pdf": {
|
|
51
|
+
"types": "./types/pdf.d.ts",
|
|
52
|
+
"default": "./src/renderers/pdf.js"
|
|
53
|
+
},
|
|
54
|
+
"./svg": {
|
|
55
|
+
"types": "./types/svg.d.ts",
|
|
56
|
+
"default": "./src/renderers/svg.js"
|
|
44
57
|
}
|
|
45
58
|
},
|
|
46
59
|
"scripts": {
|
|
@@ -56,6 +69,7 @@
|
|
|
56
69
|
"test:coverage": "node scripts/run-tests.js coverage",
|
|
57
70
|
"test:render": "node scripts/run-render-tests.js",
|
|
58
71
|
"assets:readme": "node scripts/generate-readme-demo.js",
|
|
72
|
+
"artifacts:renderers": "node scripts/generate-renderer-comparison.js",
|
|
59
73
|
"artifacts:clean": "node test/support/artifacts.js",
|
|
60
74
|
"artifacts:gallery": "node scripts/generate-artifact-gallery.js",
|
|
61
75
|
"artifacts:gallery:test": "node scripts/test-artifact-gallery.js",
|
|
@@ -74,13 +88,16 @@
|
|
|
74
88
|
"docs:preflight": "node scripts/check-docs-environment.js",
|
|
75
89
|
"docs:signatures": "node scripts/generate-doc-signatures.js",
|
|
76
90
|
"docs:signatures:check": "node scripts/generate-doc-signatures.js --check",
|
|
77
|
-
"
|
|
91
|
+
"examples:index": "node scripts/generate-examples-readme.js",
|
|
92
|
+
"examples:index:check": "node scripts/generate-examples-readme.js --check",
|
|
93
|
+
"docs:generate": "npm run examples:index && npm run docs:actions && npm run docs:reference && npm run docs:signatures && npm run docs:capabilities && npm run docs:metadata && npm run docs:search && npm run docs:images && npm run docs:llms",
|
|
78
94
|
"docs:build": "bundle exec jekyll build --source docs --destination _site --baseurl /ggaction",
|
|
79
95
|
"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",
|
|
80
96
|
"contracts:catalog": "node scripts/generate-action-catalog.js",
|
|
81
97
|
"contracts:catalog:check": "node scripts/generate-action-catalog.js --check",
|
|
82
98
|
"package:check": "node scripts/package-artifact.js --check",
|
|
83
99
|
"package:pack": "node scripts/package-artifact.js --pack",
|
|
100
|
+
"package:bundle": "node scripts/measure-browser-bundle.js",
|
|
84
101
|
"test:package": "node scripts/package-consumer.js",
|
|
85
102
|
"release:notes": "node scripts/release-notes.js"
|
|
86
103
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { registerBasicActions } from "./actions/basic.js";
|
|
2
|
+
import { ChartProgram as CoreChartProgram } from "./core/ChartProgram.js";
|
|
3
|
+
|
|
4
|
+
class BasicChartProgram extends CoreChartProgram {}
|
|
5
|
+
|
|
6
|
+
registerBasicActions(BasicChartProgram);
|
|
7
|
+
|
|
8
|
+
export function chart() {
|
|
9
|
+
return new BasicChartProgram();
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { registerBasicCanvasActions } from "./canvas/index.js";
|
|
2
|
+
import { registerBasicChartActions } from "./charts/basic.js";
|
|
3
|
+
import { registerCoordinateActions } from "./coordinates/index.js";
|
|
4
|
+
import { registerBasicDataActions } from "./data/basic.js";
|
|
5
|
+
import { registerBasicEncodingActions } from "./encodings/basic.js";
|
|
6
|
+
import { registerBasicGuideActions } from "./guides/basic.js";
|
|
7
|
+
import { registerBasicMarkActions } from "./marks/basic.js";
|
|
8
|
+
import { registerPrimitiveActions } from "./primitives/index.js";
|
|
9
|
+
import { registerBasicScaleActions } from "./scales/index.js";
|
|
10
|
+
|
|
11
|
+
export function registerBasicActions(ProgramClass) {
|
|
12
|
+
registerPrimitiveActions(ProgramClass);
|
|
13
|
+
registerBasicCanvasActions(ProgramClass);
|
|
14
|
+
registerBasicDataActions(ProgramClass);
|
|
15
|
+
registerBasicMarkActions(ProgramClass);
|
|
16
|
+
registerBasicScaleActions(ProgramClass);
|
|
17
|
+
registerBasicEncodingActions(ProgramClass);
|
|
18
|
+
registerCoordinateActions(ProgramClass);
|
|
19
|
+
registerBasicGuideActions(ProgramClass);
|
|
20
|
+
registerBasicChartActions(ProgramClass);
|
|
21
|
+
}
|
|
@@ -146,3 +146,45 @@ export const createCanvas = action(
|
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
);
|
|
149
|
+
|
|
150
|
+
export const createBasicCanvas = action(
|
|
151
|
+
{
|
|
152
|
+
op: "createCanvas",
|
|
153
|
+
description: "Create and configure the chart canvas."
|
|
154
|
+
},
|
|
155
|
+
function (args = {}) {
|
|
156
|
+
validateOptions(args, "createCanvas", { allowEmpty: true });
|
|
157
|
+
assertCanvasHierarchyAvailable(this);
|
|
158
|
+
const state = {
|
|
159
|
+
width: Object.hasOwn(args, "width") ? args.width : DEFAULT_CANVAS.width,
|
|
160
|
+
height: Object.hasOwn(args, "height") ? args.height : DEFAULT_CANVAS.height,
|
|
161
|
+
background: Object.hasOwn(args, "background")
|
|
162
|
+
? args.background
|
|
163
|
+
: DEFAULT_CANVAS.background,
|
|
164
|
+
margin: normalizeMargin(args.margin, DEFAULT_MARGIN),
|
|
165
|
+
size: {
|
|
166
|
+
width: Object.hasOwn(args, "width") ? "explicit" : "auto",
|
|
167
|
+
height: Object.hasOwn(args, "height") ? "explicit" : "auto"
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
validateCanvasState(state);
|
|
171
|
+
let next = this
|
|
172
|
+
.createGraphics({
|
|
173
|
+
id: CANVAS_GRAPHIC_ID,
|
|
174
|
+
type: "canvas"
|
|
175
|
+
})
|
|
176
|
+
.createGraphics({
|
|
177
|
+
id: PLOT_GRAPHIC_ID,
|
|
178
|
+
type: "collection",
|
|
179
|
+
parent: CANVAS_GRAPHIC_ID
|
|
180
|
+
});
|
|
181
|
+
for (const property of ["width", "height", "background"]) {
|
|
182
|
+
next = next.editGraphics({
|
|
183
|
+
target: CANVAS_GRAPHIC_ID,
|
|
184
|
+
property,
|
|
185
|
+
value: state[property]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return next._withCanvasConfig({ margin: state.margin, size: state.size });
|
|
189
|
+
}
|
|
190
|
+
);
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { createCanvas, editCanvas } from "./actions.js";
|
|
1
|
+
import { createBasicCanvas, createCanvas, editCanvas } from "./actions.js";
|
|
2
2
|
|
|
3
3
|
export function registerCanvasActions(ProgramClass) {
|
|
4
4
|
ProgramClass.prototype.editCanvas = editCanvas;
|
|
5
5
|
ProgramClass.prototype.createCanvas = createCanvas;
|
|
6
6
|
}
|
|
7
|
+
|
|
8
|
+
export function registerBasicCanvasActions(ProgramClass) {
|
|
9
|
+
ProgramClass.prototype.createCanvas = createBasicCanvas;
|
|
10
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
5
|
+
import { resolveEligibleLayer } from "../../selectors/layers.js";
|
|
6
|
+
import { findScaleConsumers } from "../scales/consumers/index.js";
|
|
7
|
+
import {
|
|
8
|
+
applyMaterializationPlan,
|
|
9
|
+
planScaleGuideRematerialization
|
|
10
|
+
} from "../../materialization/dependencies.js";
|
|
11
|
+
import { getMarkMaterializationStep } from "../../materialization/marks/index.js";
|
|
12
|
+
import { buildMaterializationPlan } from "../../materialization/planner.js";
|
|
13
|
+
import {
|
|
14
|
+
normalizeCategoryOrder,
|
|
15
|
+
resolveCategoryOrder
|
|
16
|
+
} from "../../grammar/categoryOrder.js";
|
|
17
|
+
|
|
18
|
+
const ORDER_OPTIONS = Object.freeze(["target", "channel", "values", "by", "direction"]);
|
|
19
|
+
const REMOVE_OPTIONS = Object.freeze(["target", "channel"]);
|
|
20
|
+
|
|
21
|
+
function resolveAssignment(program, args, operation, { activeOnly = false } = {}) {
|
|
22
|
+
if (!["x", "y"].includes(args.channel)) {
|
|
23
|
+
throw new Error(`${operation} channel must be x or y.`);
|
|
24
|
+
}
|
|
25
|
+
const requested = args.target === undefined
|
|
26
|
+
? undefined
|
|
27
|
+
: validateUserId(args.target, "Mark id");
|
|
28
|
+
const layer = resolveEligibleLayer(program, {
|
|
29
|
+
target: requested,
|
|
30
|
+
label: "categorical position mark",
|
|
31
|
+
predicate: candidate => {
|
|
32
|
+
const encoding = candidate.encoding?.[args.channel];
|
|
33
|
+
return ["nominal", "ordinal"].includes(encoding?.fieldType) &&
|
|
34
|
+
typeof encoding.field === "string" && encoding.scale !== undefined &&
|
|
35
|
+
(!activeOnly || encoding.categoryOrder !== undefined);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const encoding = layer.encoding[args.channel];
|
|
39
|
+
const dataset = findDataset(program, layer.data);
|
|
40
|
+
if (dataset === undefined) {
|
|
41
|
+
throw new Error(`Mark "${layer.id}" requires an existing dataset.`);
|
|
42
|
+
}
|
|
43
|
+
const consumers = findScaleConsumers(program, encoding.scale);
|
|
44
|
+
const incompatible = consumers.find(consumer =>
|
|
45
|
+
consumer.layer.id !== layer.id &&
|
|
46
|
+
(consumer.layer.data !== layer.data || consumer.encoding.field !== encoding.field)
|
|
47
|
+
);
|
|
48
|
+
if (incompatible !== undefined) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Category order scale "${encoding.scale}" has an incompatible shared consumer.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return { layer, encoding, dataset };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function rematerializeAssignment(program, scaleId) {
|
|
57
|
+
const consumers = findScaleConsumers(program, scaleId);
|
|
58
|
+
return applyMaterializationPlan(program, buildMaterializationPlan({
|
|
59
|
+
scales: [{
|
|
60
|
+
op: "rematerializeScale",
|
|
61
|
+
args: { id: scaleId, guides: false, marks: false }
|
|
62
|
+
}],
|
|
63
|
+
marks: consumers.map(consumer =>
|
|
64
|
+
getMarkMaterializationStep(program, consumer.layer)
|
|
65
|
+
),
|
|
66
|
+
guides: planScaleGuideRematerialization(program, scaleId)
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const orderCategories = action(
|
|
71
|
+
{ op: "orderCategories", description: "Assign semantic order to a categorical position." },
|
|
72
|
+
function (args = {}) {
|
|
73
|
+
validateKeys(args, ORDER_OPTIONS, "orderCategories");
|
|
74
|
+
const { layer, encoding, dataset } = resolveAssignment(this, args, "orderCategories");
|
|
75
|
+
const order = normalizeCategoryOrder(args);
|
|
76
|
+
resolveCategoryOrder(dataset.values, encoding.field, order);
|
|
77
|
+
const edited = this.editSemantic({
|
|
78
|
+
property: `layer[${layer.id}].encoding.${args.channel}.categoryOrder`,
|
|
79
|
+
value: order
|
|
80
|
+
});
|
|
81
|
+
return rematerializeAssignment(edited, encoding.scale);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
export const removeCategoryOrder = action(
|
|
86
|
+
{ op: "removeCategoryOrder", description: "Restore automatic category order." },
|
|
87
|
+
function (args = {}) {
|
|
88
|
+
validateKeys(args, REMOVE_OPTIONS, "removeCategoryOrder");
|
|
89
|
+
const { layer, encoding } = resolveAssignment(
|
|
90
|
+
this,
|
|
91
|
+
args,
|
|
92
|
+
"removeCategoryOrder",
|
|
93
|
+
{ activeOnly: args.target === undefined }
|
|
94
|
+
);
|
|
95
|
+
if (encoding.categoryOrder === undefined) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Mark "${layer.id}" ${args.channel} encoding has no category order assignment.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const edited = this.editSemantic({
|
|
101
|
+
property: `layer[${layer.id}].encoding.${args.channel}.categoryOrder`,
|
|
102
|
+
remove: true
|
|
103
|
+
});
|
|
104
|
+
return rematerializeAssignment(edited, encoding.scale);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
export function registerCategoryOrderActions(ProgramClass) {
|
|
109
|
+
ProgramClass.prototype.orderCategories = orderCategories;
|
|
110
|
+
ProgramClass.prototype.removeCategoryOrder = removeCategoryOrder;
|
|
111
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createBarPlot } from "./bar.js";
|
|
2
|
+
import { createHeatmap } from "./heatmap.js";
|
|
3
|
+
import { createHistogram } from "./histogram.js";
|
|
4
|
+
import { createLinePlot } from "./line.js";
|
|
5
|
+
import { createScatterPlot } from "./scatter.js";
|
|
6
|
+
|
|
7
|
+
export function registerBasicChartActions(ProgramClass) {
|
|
8
|
+
ProgramClass.prototype.createBarPlot = createBarPlot;
|
|
9
|
+
ProgramClass.prototype.createHeatmap = createHeatmap;
|
|
10
|
+
ProgramClass.prototype.createHistogram = createHistogram;
|
|
11
|
+
ProgramClass.prototype.createScatterPlot = createScatterPlot;
|
|
12
|
+
ProgramClass.prototype.createLinePlot = createLinePlot;
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createData } from "./create.js";
|
|
2
|
+
import { createDerivedData } from "./derived.js";
|
|
3
|
+
import { createBasicBin2DData } from "./basicBin2d.js";
|
|
4
|
+
import { materializeBin2DData } from "./bin2dMaterialize.js";
|
|
5
|
+
|
|
6
|
+
export function registerBasicDataActions(ProgramClass) {
|
|
7
|
+
ProgramClass.prototype.createData = createData;
|
|
8
|
+
ProgramClass.prototype.createDerivedData = createDerivedData;
|
|
9
|
+
ProgramClass.prototype.createBin2DData = createBasicBin2DData;
|
|
10
|
+
ProgramClass.prototype.materializeBin2DData = materializeBin2DData;
|
|
11
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveBin2DRows,
|
|
6
|
+
normalizeBin2DTransform
|
|
7
|
+
} from "../../grammar/bin2d.js";
|
|
8
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
9
|
+
|
|
10
|
+
const OPTIONS = Object.freeze([
|
|
11
|
+
"id", "source", "x", "y", "bins", "extent", "includeEmpty", "members", "as"
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const createBasicBin2DData = action(
|
|
15
|
+
{
|
|
16
|
+
op: "createBin2DData",
|
|
17
|
+
description: "Create immutable rectangular 2D-bin values."
|
|
18
|
+
},
|
|
19
|
+
function (args = {}) {
|
|
20
|
+
validateKeys(args, OPTIONS, "createBin2DData");
|
|
21
|
+
const owner = validateUserId(args.id, "2D bin dataset id");
|
|
22
|
+
if (this.materializationConfigs.data?.bin2d?.[owner] !== undefined) {
|
|
23
|
+
throw new Error(`2D bin owner "${owner}" already exists.`);
|
|
24
|
+
}
|
|
25
|
+
const source = validateUserId(
|
|
26
|
+
args.source ?? this.context.currentData,
|
|
27
|
+
"Source dataset id"
|
|
28
|
+
);
|
|
29
|
+
const dataset = findDataset(this, source);
|
|
30
|
+
if (dataset?.values === undefined) {
|
|
31
|
+
throw new Error(`Source dataset "${source}" has no values.`);
|
|
32
|
+
}
|
|
33
|
+
const transform = normalizeBin2DTransform({ ...args, id: owner });
|
|
34
|
+
deriveBin2DRows(dataset.values, transform);
|
|
35
|
+
return this
|
|
36
|
+
.createDerivedData({ id: owner, source, transform: [transform] })
|
|
37
|
+
.materializeBin2DData({ id: owner })
|
|
38
|
+
._withMaterializationConfig(
|
|
39
|
+
["data", "bin2d", owner],
|
|
40
|
+
{ current: owner }
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
findDataset,
|
|
15
15
|
findDatasetConsumer
|
|
16
16
|
} from "../../selectors/datasets.js";
|
|
17
|
-
|
|
17
|
+
export { materializeBin2DData } from "./bin2dMaterialize.js";
|
|
18
18
|
|
|
19
19
|
const OPTIONS = Object.freeze([
|
|
20
20
|
"id", "source", "x", "y", "bins", "extent", "includeEmpty", "members", "as"
|
|
@@ -160,31 +160,6 @@ function applyBin2DRevision(program, { owner, previous, source, transform }) {
|
|
|
160
160
|
);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
export const materializeBin2DData = action(
|
|
164
|
-
{
|
|
165
|
-
op: "materializeBin2DData",
|
|
166
|
-
description: "Materialize one immutable rectangular 2D-bin dataset."
|
|
167
|
-
},
|
|
168
|
-
function (args = {}) {
|
|
169
|
-
validateKeys(args, MATERIALIZE_OPTIONS, "materializeBin2DData");
|
|
170
|
-
const { id, source, transform } = requireDerivedDataset(
|
|
171
|
-
this,
|
|
172
|
-
args.id,
|
|
173
|
-
"bin2d"
|
|
174
|
-
);
|
|
175
|
-
const result = deriveBin2DRows(source.values, transform);
|
|
176
|
-
return this
|
|
177
|
-
.editSemantic({
|
|
178
|
-
property: `dataset[${id}].transform`,
|
|
179
|
-
value: [{ ...transform, resolved: result.resolved }]
|
|
180
|
-
})
|
|
181
|
-
.editSemantic({
|
|
182
|
-
property: `dataset[${id}].values`,
|
|
183
|
-
value: result.values
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
);
|
|
187
|
-
|
|
188
163
|
export const createBin2DData = action(
|
|
189
164
|
{
|
|
190
165
|
op: "createBin2DData",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateKeys } from "../../core/validation.js";
|
|
3
|
+
import { deriveBin2DRows } from "../../grammar/bin2d.js";
|
|
4
|
+
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
5
|
+
|
|
6
|
+
export const materializeBin2DData = action(
|
|
7
|
+
{
|
|
8
|
+
op: "materializeBin2DData",
|
|
9
|
+
description: "Materialize one immutable rectangular 2D-bin dataset."
|
|
10
|
+
},
|
|
11
|
+
function (args = {}) {
|
|
12
|
+
validateKeys(args, MATERIALIZE_OPTIONS, "materializeBin2DData");
|
|
13
|
+
const { id, source, transform } = requireDerivedDataset(
|
|
14
|
+
this,
|
|
15
|
+
args.id,
|
|
16
|
+
"bin2d"
|
|
17
|
+
);
|
|
18
|
+
const result = deriveBin2DRows(source.values, transform);
|
|
19
|
+
return this
|
|
20
|
+
.editSemantic({
|
|
21
|
+
property: `dataset[${id}].transform`,
|
|
22
|
+
value: [{ ...transform, resolved: result.resolved }]
|
|
23
|
+
})
|
|
24
|
+
.editSemantic({
|
|
25
|
+
property: `dataset[${id}].values`,
|
|
26
|
+
value: result.values
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
);
|
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
import { createIntervalData, materializeIntervalData } from "./interval.js";
|
|
24
24
|
import { createHorizonData, materializeHorizonData } from "./horizon.js";
|
|
25
25
|
import { createWindowData, materializeWindowData } from "./window.js";
|
|
26
|
+
import {
|
|
27
|
+
createTimeUnitData,
|
|
28
|
+
materializeTimeUnitData
|
|
29
|
+
} from "./timeUnit.js";
|
|
26
30
|
import {
|
|
27
31
|
createBin2DData,
|
|
28
32
|
editBin2DData,
|
|
@@ -54,6 +58,8 @@ export function registerDataActions(ProgramClass) {
|
|
|
54
58
|
ProgramClass.prototype.materializeHorizonData = materializeHorizonData;
|
|
55
59
|
ProgramClass.prototype.createWindowData = createWindowData;
|
|
56
60
|
ProgramClass.prototype.materializeWindowData = materializeWindowData;
|
|
61
|
+
ProgramClass.prototype.createTimeUnitData = createTimeUnitData;
|
|
62
|
+
ProgramClass.prototype.materializeTimeUnitData = materializeTimeUnitData;
|
|
57
63
|
ProgramClass.prototype.createBin2DData = createBin2DData;
|
|
58
64
|
ProgramClass.prototype.editBin2DData = editBin2DData;
|
|
59
65
|
ProgramClass.prototype.materializeBin2DData = materializeBin2DData;
|