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.
Files changed (108) hide show
  1. package/CHANGELOG.md +28 -3
  2. package/README.md +95 -83
  3. package/package.json +11 -6
  4. package/src/actions/data/regression.js +1 -1
  5. package/src/actions/encodings/appearance.js +1 -1
  6. package/src/actions/encodings/color/continuous.js +137 -0
  7. package/src/actions/encodings/color/index.js +151 -0
  8. package/src/actions/encodings/color/layout.js +91 -0
  9. package/src/actions/encodings/color/policy.js +88 -0
  10. package/src/actions/encodings/index.js +2 -2
  11. package/src/actions/encodings/offset.js +1 -1
  12. package/src/actions/encodings/{position.js → position/index.js} +6 -6
  13. package/src/actions/encodings/position/resolve.js +1 -1
  14. package/src/actions/encodings/ranged.js +2 -2
  15. package/src/actions/encodings/strokeDash.js +1 -1
  16. package/src/actions/encodings/text.js +1 -1
  17. package/src/actions/errorBands/options.js +1 -1
  18. package/src/actions/errorBars/options.js +1 -1
  19. package/src/actions/facets/actions.js +1 -1
  20. package/src/actions/facets/derive.js +2 -2
  21. package/src/actions/facets/guides.js +1 -1
  22. package/src/actions/guides/axes/labels.js +1 -1
  23. package/src/actions/guides/axes/ticks.js +1 -1
  24. package/src/actions/guides/axes/titles.js +1 -1
  25. package/src/actions/guides/grids/resolve.js +1 -1
  26. package/src/actions/guides/legends/categorical/layout.js +4 -2
  27. package/src/actions/guides/legends/continuous/interval.js +1 -1
  28. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  29. package/src/actions/guides/legends/edit.js +2 -20
  30. package/src/actions/guides/legends/remove.js +2 -20
  31. package/src/actions/guides/legends/size.js +1 -1
  32. package/src/actions/guides/legends/target.js +25 -0
  33. package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
  34. package/src/actions/guides/polar/index.js +1 -1
  35. package/src/actions/guides/polar/resolve.js +1 -1
  36. package/src/actions/guides/tickValues.js +1 -1
  37. package/src/actions/intervals/resolve.js +1 -1
  38. package/src/actions/marks/arc/actions.js +2 -2
  39. package/src/actions/marks/area/actions.js +2 -2
  40. package/src/actions/marks/bar/edit.js +1 -1
  41. package/src/actions/marks/line/actions.js +3 -3
  42. package/src/actions/marks/rect/actions.js +1 -1
  43. package/src/actions/marks/rule/actions.js +1 -1
  44. package/src/actions/marks/shared.js +1 -1
  45. package/src/actions/marks/text/actions.js +1 -1
  46. package/src/actions/primitives/semanticValue.js +1 -1
  47. package/src/actions/regression/components.js +1 -1
  48. package/src/actions/regression/create.js +1 -1
  49. package/src/actions/regression/edit.js +1 -1
  50. package/src/actions/scales/consumers/common.js +91 -0
  51. package/src/actions/scales/consumers/families.js +47 -0
  52. package/src/actions/scales/consumers/index.js +41 -0
  53. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
  54. package/src/actions/scales/create.js +1 -1
  55. package/src/actions/scales/definitions.js +2 -2
  56. package/src/actions/scales/edit.js +3 -3
  57. package/src/actions/scales/materialize.js +2 -2
  58. package/src/core/ChartProgram.js +22 -236
  59. package/src/core/compositionState.js +169 -0
  60. package/src/core/materializationState.js +65 -0
  61. package/src/core/programState.js +28 -0
  62. package/src/grammar/arcs.js +1 -1
  63. package/src/grammar/areaSeries.js +1 -1
  64. package/src/grammar/bars/aggregate.js +1 -1
  65. package/src/grammar/density.js +1 -1
  66. package/src/grammar/{facets.js → facets/index.js} +5 -5
  67. package/src/grammar/interval.js +2 -96
  68. package/src/grammar/lineSeries.js +1 -1
  69. package/src/grammar/palettes.js +5 -7
  70. package/src/grammar/polarLineCommands.js +1 -1
  71. package/src/grammar/regression/derive.js +97 -0
  72. package/src/grammar/regression/index.js +22 -0
  73. package/src/grammar/regression/models.js +282 -0
  74. package/src/grammar/regression/parameters.js +106 -0
  75. package/src/grammar/rules.js +1 -1
  76. package/src/grammar/scales/color.js +4 -2
  77. package/src/grammar/statistics/studentT.js +98 -0
  78. package/src/grammar/transforms.js +1 -1
  79. package/src/materialization/bars/aggregate.js +1 -1
  80. package/src/materialization/bars/grouped.js +1 -1
  81. package/src/materialization/bars/histogram.js +1 -1
  82. package/src/materialization/bars/ranged.js +1 -1
  83. package/src/materialization/density.js +1 -1
  84. package/src/materialization/dependencies.js +6 -87
  85. package/src/materialization/encodings.js +2 -2
  86. package/src/materialization/facetGuides/index.js +2 -0
  87. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  88. package/src/materialization/facetGuides/placement.js +126 -0
  89. package/src/materialization/facetGuides/preparation.js +188 -0
  90. package/src/materialization/facets.js +1 -1
  91. package/src/materialization/marks/capabilities.js +141 -0
  92. package/src/materialization/marks/index.js +105 -0
  93. package/src/materialization/marks/policies.js +87 -0
  94. package/src/materialization/rect.js +1 -1
  95. package/src/materialization/rowEncoding.js +1 -1
  96. package/src/materialization/scaleGuideDependencies.js +94 -0
  97. package/src/materialization/scales/map.js +1 -1
  98. package/src/materialization/scales/policies/series.js +1 -1
  99. package/src/materialization/scales/resolve.js +1 -1
  100. package/src/materialization/selection/items/bar.js +1 -1
  101. package/src/materialization/selection/styles.js +1 -1
  102. package/src/renderers/canvas/text.js +14 -1
  103. package/types/extension.d.ts +4 -1
  104. package/src/actions/encodings/color.js +0 -424
  105. package/src/grammar/regression.js +0 -578
  106. package/src/grammar/scales.js +0 -1
  107. package/src/materialization/facetGuides.js +0 -426
  108. 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.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
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
- `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
+ .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 cars = [
10
- { horsepower: 88, mpg: 27, origin: "USA" },
11
- { horsepower: 70, mpg: 36, origin: "Japan" },
12
- { horsepower: 110, mpg: 24, origin: "Europe" }
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: 30, bottom: 60, left: 70 }
74
+ margin: { top: 30, right: 130, bottom: 60, left: 70 }
20
75
  })
21
- .createData({ values: cars })
76
+ .createData({ values: observations })
22
77
  .createPointMark()
23
- .encodeX({ field: "horsepower" })
24
- .encodeY({ field: "mpg" })
78
+ .encodeX({ field: "displacement" })
79
+ .encodeY({ field: "acceleration" })
25
80
  .encodeColor({ field: "origin" })
26
- .encodeRadius({ value: 3 })
27
- .createGuides({
28
- axes: {
29
- x: { title: { text: "Horsepower" } },
30
- y: { title: { text: "Miles per Gallon" } }
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
- The renderer reads only fully materialized, backend-neutral graphics. Semantic
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
- > **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).
90
+ ## What it supports
42
91
 
43
- ## Install
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
- ```bash
46
- npm install ggaction
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 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.
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 and use public low-level primitives |
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 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.
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
- - [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
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",
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/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",
@@ -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([
@@ -4,7 +4,7 @@ import {
4
4
  readQuantitativeField,
5
5
  readScaleField,
6
6
  validateOpacityValue
7
- } from "../../grammar/scales.js";
7
+ } from "../../grammar/scales/index.js";
8
8
  import {
9
9
  resolveAppearanceScaleDefinition,
10
10
  resolveOpacityScaleDefinition
@@ -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
+ }