ggaction 0.0.2 → 0.0.3

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 (199) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +12 -1
  3. package/package.json +6 -1
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/encodings/appearance.js +11 -0
  16. package/src/actions/encodings/barWidth.js +5 -3
  17. package/src/actions/encodings/color.js +120 -25
  18. package/src/actions/encodings/index.js +2 -0
  19. package/src/actions/encodings/offset.js +90 -72
  20. package/src/actions/encodings/position/apply.js +66 -0
  21. package/src/actions/encodings/position/policies/arc.js +26 -0
  22. package/src/actions/encodings/position/policies/bar.js +9 -5
  23. package/src/actions/encodings/position/policies/index.js +5 -1
  24. package/src/actions/encodings/position/policies/line.js +20 -3
  25. package/src/actions/encodings/position/resolve.js +57 -8
  26. package/src/actions/encodings/position.js +44 -90
  27. package/src/actions/encodings/ranged.js +28 -6
  28. package/src/actions/encodings/shared.js +17 -21
  29. package/src/actions/encodings/text.js +73 -0
  30. package/src/actions/errorBands/create.js +37 -29
  31. package/src/actions/errorBands/edit.js +206 -0
  32. package/src/actions/errorBands/index.js +9 -0
  33. package/src/actions/errorBands/options.js +39 -0
  34. package/src/actions/errorBars/create.js +50 -43
  35. package/src/actions/errorBars/edit.js +130 -0
  36. package/src/actions/errorBars/index.js +3 -0
  37. package/src/actions/errorBars/options.js +52 -0
  38. package/src/actions/facets/actions.js +162 -0
  39. package/src/actions/facets/derive.js +200 -0
  40. package/src/actions/facets/guides.js +16 -0
  41. package/src/actions/facets/index.js +1 -0
  42. package/src/actions/facets/replay.js +69 -0
  43. package/src/actions/guides/applicability.js +113 -0
  44. package/src/actions/guides/axes/axes.js +70 -16
  45. package/src/actions/guides/axes/edit.js +126 -0
  46. package/src/actions/guides/axes/index.js +12 -0
  47. package/src/actions/guides/axes/labels.js +21 -18
  48. package/src/actions/guides/axes/remove.js +60 -0
  49. package/src/actions/guides/axes/tickGroups.js +7 -5
  50. package/src/actions/guides/axes/ticks.js +9 -4
  51. package/src/actions/guides/axes/titles.js +7 -5
  52. package/src/actions/guides/grids/grid.js +144 -15
  53. package/src/actions/guides/grids/resolve.js +5 -13
  54. package/src/actions/guides/guides.js +17 -75
  55. package/src/actions/guides/index.js +6 -0
  56. package/src/actions/guides/legends/categorical/actions.js +48 -9
  57. package/src/actions/guides/legends/categorical/components.js +16 -16
  58. package/src/actions/guides/legends/categorical/index.js +14 -14
  59. package/src/actions/guides/legends/categorical/layout.js +3 -1
  60. package/src/actions/guides/legends/categorical/options.js +2 -5
  61. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  62. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  63. package/src/actions/guides/legends/continuous/common.js +7 -7
  64. package/src/actions/guides/legends/continuous/interval.js +5 -5
  65. package/src/actions/guides/legends/edit.js +2 -6
  66. package/src/actions/guides/legends/focused.js +63 -0
  67. package/src/actions/guides/legends/index.js +4 -0
  68. package/src/actions/guides/legends/remove.js +64 -0
  69. package/src/actions/guides/legends/size.js +12 -14
  70. package/src/actions/guides/polar/axes/facade.js +219 -0
  71. package/src/actions/guides/polar/axes/labels.js +188 -0
  72. package/src/actions/guides/polar/axes/lines.js +113 -0
  73. package/src/actions/guides/polar/axes/shared.js +112 -0
  74. package/src/actions/guides/polar/axes/ticks.js +164 -0
  75. package/src/actions/guides/polar/axes/titles.js +165 -0
  76. package/src/actions/guides/polar/axes.js +55 -0
  77. package/src/actions/guides/polar/grids.js +252 -0
  78. package/src/actions/guides/polar/index.js +2 -0
  79. package/src/actions/guides/polar/resolve.js +269 -0
  80. package/src/actions/index.js +4 -0
  81. package/src/actions/marks/arc/actions.js +239 -0
  82. package/src/actions/marks/arc/index.js +1 -0
  83. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  84. package/src/actions/marks/area/index.js +4 -0
  85. package/src/actions/marks/bar/create.js +26 -5
  86. package/src/actions/marks/bar/materialize.js +25 -31
  87. package/src/actions/marks/index.js +12 -4
  88. package/src/actions/marks/lifecycle.js +27 -0
  89. package/src/actions/marks/line/actions.js +373 -0
  90. package/src/actions/marks/line/index.js +1 -0
  91. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  92. package/src/actions/marks/point/index.js +1 -0
  93. package/src/actions/marks/rect/actions.js +165 -0
  94. package/src/actions/marks/rect/index.js +1 -0
  95. package/src/actions/marks/remove.js +214 -0
  96. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  97. package/src/actions/marks/rule/index.js +1 -0
  98. package/src/actions/marks/shared.js +156 -25
  99. package/src/actions/marks/text/actions.js +192 -0
  100. package/src/actions/marks/text/index.js +1 -0
  101. package/src/actions/primitives/createGraphics.js +13 -6
  102. package/src/actions/primitives/editGraphics.js +10 -3
  103. package/src/actions/primitives/semantic.js +26 -3
  104. package/src/actions/primitives/semanticValue.js +25 -3
  105. package/src/actions/regression/components.js +1 -1
  106. package/src/actions/regression/create.js +18 -1
  107. package/src/actions/regression/edit.js +247 -0
  108. package/src/actions/regression/index.js +3 -0
  109. package/src/actions/regression/resolve.js +3 -1
  110. package/src/actions/scales/consumers.js +15 -6
  111. package/src/actions/scales/definitions.js +13 -4
  112. package/src/actions/scales/edit.js +42 -19
  113. package/src/actions/scales/materialize.js +57 -408
  114. package/src/actions/selection/actions.js +38 -24
  115. package/src/actions/selection/index.js +2 -0
  116. package/src/actions/titles/actions.js +63 -2
  117. package/src/actions/titles/index.js +2 -0
  118. package/src/actions/titles/resolve.js +12 -17
  119. package/src/composition.js +10 -0
  120. package/src/core/ChartProgram.js +200 -2
  121. package/src/core/action.js +10 -0
  122. package/src/core/validation.js +16 -0
  123. package/src/core/vocabulary.js +154 -4
  124. package/src/grammar/arcs.js +154 -0
  125. package/src/grammar/bars/aggregate.js +2 -2
  126. package/src/grammar/bars/geometry.js +3 -3
  127. package/src/grammar/bars/policy.js +7 -1
  128. package/src/grammar/coordinates.js +3 -7
  129. package/src/grammar/density.js +20 -1
  130. package/src/grammar/facets/dependencies.js +185 -0
  131. package/src/grammar/facets/guides.js +257 -0
  132. package/src/grammar/facets/scales.js +254 -0
  133. package/src/grammar/facets.js +122 -0
  134. package/src/grammar/lineSeries.js +111 -3
  135. package/src/grammar/polar.js +102 -0
  136. package/src/grammar/polarGuides.js +243 -0
  137. package/src/grammar/polarLineCommands.js +43 -0
  138. package/src/grammar/polarPaths.js +161 -0
  139. package/src/grammar/positionCompatibility.js +18 -2
  140. package/src/grammar/rects.js +26 -0
  141. package/src/grammar/scales/continuous.js +6 -1
  142. package/src/grammar/scales/fields.js +7 -0
  143. package/src/grammar/scales/ordinal.js +5 -2
  144. package/src/grammar/scales/policies.js +1 -1
  145. package/src/grammar/schemas/graphic.js +1 -1
  146. package/src/grammar/schemas/graphicBounds.js +64 -9
  147. package/src/grammar/schemas/graphicTree.js +44 -10
  148. package/src/grammar/schemas/semanticPath.js +31 -9
  149. package/src/grammar/seriesLayout.js +13 -2
  150. package/src/grammar/text.js +71 -0
  151. package/src/grammar/ticks.js +50 -13
  152. package/src/index.js +1 -0
  153. package/src/layout/composition.js +310 -0
  154. package/src/layout/facets.js +131 -0
  155. package/src/layout/title.js +6 -0
  156. package/src/materialization/bars/aggregate.js +5 -2
  157. package/src/materialization/bars/grouped.js +75 -37
  158. package/src/materialization/composition.js +152 -0
  159. package/src/materialization/compositionSnapshot.js +114 -0
  160. package/src/materialization/dependencies.js +71 -16
  161. package/src/materialization/encodings.js +14 -28
  162. package/src/materialization/facetGuides.js +426 -0
  163. package/src/materialization/facets.js +214 -0
  164. package/src/materialization/graphicHierarchy.js +15 -5
  165. package/src/materialization/guides/resources.js +46 -0
  166. package/src/materialization/marks.js +220 -14
  167. package/src/materialization/planner.js +105 -12
  168. package/src/materialization/rect.js +138 -0
  169. package/src/materialization/rectConfig.js +38 -0
  170. package/src/materialization/rowEncoding.js +33 -0
  171. package/src/materialization/scales/map.js +40 -0
  172. package/src/materialization/scales/policies/arc.js +37 -0
  173. package/src/materialization/scales/policies/bar.js +132 -0
  174. package/src/materialization/scales/policies/series.js +76 -0
  175. package/src/materialization/scales/resolve.js +326 -0
  176. package/src/materialization/selection/items/arc.js +29 -0
  177. package/src/materialization/selection/items/bar.js +255 -0
  178. package/src/materialization/selection/items/common.js +131 -0
  179. package/src/materialization/selection/items/index.js +6 -0
  180. package/src/materialization/selection/items/path.js +59 -0
  181. package/src/materialization/selection/items/point.js +55 -0
  182. package/src/materialization/selection/items/rect.js +23 -0
  183. package/src/materialization/selection/items/rule.js +25 -0
  184. package/src/materialization/selection/policies/arc.js +10 -0
  185. package/src/materialization/selection/policies/area.js +1 -1
  186. package/src/materialization/selection/policies/bar.js +1 -1
  187. package/src/materialization/selection/policies/index.js +5 -1
  188. package/src/materialization/selection/policies/line.js +1 -1
  189. package/src/materialization/selection/policies/point.js +1 -1
  190. package/src/materialization/selection/policies/rect.js +10 -0
  191. package/src/materialization/selection/policies/rule.js +1 -1
  192. package/src/materialization/selection/styles.js +24 -16
  193. package/src/materialization/text.js +156 -0
  194. package/src/renderers/canvas/index.js +68 -22
  195. package/types/extension.d.ts +1 -0
  196. package/types/index.d.ts +56 -1
  197. package/types/program.d.ts +664 -27
  198. package/src/actions/marks/line.js +0 -248
  199. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,247 @@
1
+ import { action } from "../../core/action.js";
2
+ import { ChartProgram as CoreChartProgram } from "../../core/ChartProgram.js";
3
+ import { freezeOwned, isPlainObject } from "../../core/immutable.js";
4
+ import { validateUserId } from "../../core/identifiers.js";
5
+ import { validateOptionObject } from "../../core/validation.js";
6
+ import {
7
+ normalizeCompositionPadding,
8
+ resolveCompositionLayout
9
+ } from "../../layout/composition.js";
10
+ import { resolveFacetLayout } from "../../layout/facets.js";
11
+ import { namespaceGraphicSnapshot } from "../../materialization/compositionSnapshot.js";
12
+ import { materializeCompositionGraphics } from
13
+ "../../materialization/composition.js";
14
+ import { materializeFacetGraphics } from "../../materialization/facets.js";
15
+
16
+ const CONCAT_OPTIONS = Object.freeze([
17
+ "id", "programs", "gap", "align", "padding"
18
+ ]);
19
+ const LAYOUT_EDIT_OPTIONS = Object.freeze(["gap", "align", "padding"]);
20
+ const REPLACEMENT_OPTIONS = Object.freeze(["target", "program"]);
21
+
22
+ function normalizeChildEntry(entry, index) {
23
+ const fallbackId = `view-${index + 1}`;
24
+ if (entry instanceof CoreChartProgram) {
25
+ return { id: fallbackId, program: entry };
26
+ }
27
+ if (!isPlainObject(entry)) {
28
+ throw new TypeError(`Composition program ${index} must be a ChartProgram or wrapper.`);
29
+ }
30
+ validateOptionObject(entry, ["id", "program"], `Composition program ${index}`);
31
+ if (!(entry.program instanceof CoreChartProgram)) {
32
+ throw new TypeError(`Composition program ${index}.program must be a ChartProgram.`);
33
+ }
34
+ return {
35
+ id: entry.id === undefined
36
+ ? fallbackId
37
+ : validateUserId(entry.id, "Composition child ID"),
38
+ program: entry.program
39
+ };
40
+ }
41
+
42
+ function childDescriptor({ id, program }) {
43
+ if (program.actionStack.length !== 0) {
44
+ throw new Error(`Composition child "${id}" has an unfinished action stack.`);
45
+ }
46
+ namespaceGraphicSnapshot(program.graphicSpec, { namespace: id });
47
+ const canvasId = program.graphicSpec.order.find(
48
+ graphicId => program.graphicSpec.objects[graphicId]?.type === "canvas"
49
+ );
50
+ const canvas = program.graphicSpec.objects[canvasId];
51
+ const size = program.materializationConfigs.canvas?.size ?? {
52
+ width: "explicit",
53
+ height: "explicit"
54
+ };
55
+ return {
56
+ id,
57
+ width: canvas.properties.width,
58
+ height: canvas.properties.height,
59
+ widthMode: size.width,
60
+ heightMode: size.height
61
+ };
62
+ }
63
+
64
+ function normalizeCompositionInput(args, direction) {
65
+ validateOptionObject(args, CONCAT_OPTIONS, direction === "horizontal" ? "hconcat" : "vconcat");
66
+ if (!Array.isArray(args.programs) || args.programs.length < 2) {
67
+ throw new TypeError("Composition requires at least two programs.");
68
+ }
69
+ const entries = args.programs.map(normalizeChildEntry);
70
+ const ids = entries.map(entry => entry.id);
71
+ if (new Set(ids).size !== ids.length) {
72
+ throw new Error("Composition child IDs must be unique.");
73
+ }
74
+ const id = args.id === undefined
75
+ ? "composition"
76
+ : validateUserId(args.id, "Composition ID");
77
+ const layout = resolveCompositionLayout({
78
+ direction,
79
+ children: entries.map(childDescriptor),
80
+ ...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
81
+ ...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
82
+ ...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {})
83
+ });
84
+ return {
85
+ children: freezeOwned(Object.fromEntries(
86
+ entries.map(entry => [entry.id, entry.program])
87
+ )),
88
+ compositionSpec: {
89
+ id,
90
+ direction,
91
+ children: ids,
92
+ gap: layout.gap,
93
+ align: layout.align,
94
+ padding: layout.padding
95
+ }
96
+ };
97
+ }
98
+
99
+ const useProgram = action(
100
+ {
101
+ op: "useProgram",
102
+ description: "Retain one named child program in a composition.",
103
+ scope: "composition"
104
+ },
105
+ function ({ id } = {}) {
106
+ validateUserId(id, "Composition child ID");
107
+ if (!Object.hasOwn(this.children, id)) {
108
+ throw new Error(`Unknown composition child "${id}".`);
109
+ }
110
+ return this;
111
+ }
112
+ );
113
+
114
+ const materializeComposition = action(
115
+ {
116
+ op: "materializeComposition",
117
+ description: "Materialize retained child programs into one graphic tree.",
118
+ scope: "composition"
119
+ },
120
+ function () {
121
+ return this.compositionSpec.type === "facet"
122
+ ? materializeFacetGraphics(this)
123
+ : materializeCompositionGraphics(this);
124
+ }
125
+ );
126
+
127
+ function concatAction(direction, op) {
128
+ return action(
129
+ {
130
+ op,
131
+ description: direction === "horizontal"
132
+ ? "Compose complete programs horizontally."
133
+ : "Compose complete programs vertically."
134
+ },
135
+ function (args = {}) {
136
+ const state = normalizeCompositionInput(args, direction);
137
+ let next = this._withCompositionState(state);
138
+ for (const id of state.compositionSpec.children) {
139
+ next = next.useProgram({ id });
140
+ }
141
+ return next.materializeComposition();
142
+ }
143
+ );
144
+ }
145
+
146
+ export const hconcatAction = concatAction("horizontal", "hconcat");
147
+ export const vconcatAction = concatAction("vertical", "vconcat");
148
+
149
+ const editCompositionLayout = action(
150
+ {
151
+ op: "editCompositionLayout",
152
+ description: "Edit composition spacing and alignment.",
153
+ scope: "composition"
154
+ },
155
+ function (args = {}) {
156
+ validateOptionObject(args, LAYOUT_EDIT_OPTIONS, "editCompositionLayout");
157
+ if (!LAYOUT_EDIT_OPTIONS.some(option => Object.hasOwn(args, option))) {
158
+ throw new TypeError("editCompositionLayout requires at least one layout option.");
159
+ }
160
+ const current = this.compositionSpec;
161
+ if (current.type === "facet") {
162
+ const layout = resolveFacetLayout({
163
+ children: current.children.map((id, index) => ({
164
+ ...childDescriptor({ id, program: this.children[id] }),
165
+ value: current.facet.values[index]
166
+ })),
167
+ columns: current.columns,
168
+ gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
169
+ align: Object.hasOwn(args, "align") ? args.align : current.align,
170
+ padding: Object.hasOwn(args, "padding")
171
+ ? normalizeCompositionPadding(args.padding, current.padding)
172
+ : current.padding,
173
+ sharedLegend: current.facet.guides.legend === "shared"
174
+ });
175
+ return this._withCompositionState({
176
+ children: this.children,
177
+ compositionSpec: {
178
+ ...current,
179
+ gap: layout.gap,
180
+ align: layout.align,
181
+ padding: layout.padding
182
+ }
183
+ }).materializeComposition();
184
+ }
185
+ const padding = Object.hasOwn(args, "padding")
186
+ ? normalizeCompositionPadding(args.padding, current.padding)
187
+ : current.padding;
188
+ const layout = resolveCompositionLayout({
189
+ direction: current.direction,
190
+ children: current.children.map(id => childDescriptor({
191
+ id,
192
+ program: this.children[id]
193
+ })),
194
+ gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
195
+ align: Object.hasOwn(args, "align") ? args.align : current.align,
196
+ padding
197
+ });
198
+ return this._withCompositionState({
199
+ children: this.children,
200
+ compositionSpec: {
201
+ ...current,
202
+ gap: layout.gap,
203
+ align: layout.align,
204
+ padding: layout.padding
205
+ }
206
+ }).materializeComposition();
207
+ }
208
+ );
209
+
210
+ const replaceCompositionChild = action(
211
+ {
212
+ op: "replaceCompositionChild",
213
+ description: "Replace one composition child without changing its slot.",
214
+ scope: "composition"
215
+ },
216
+ function (args = {}) {
217
+ validateOptionObject(args, REPLACEMENT_OPTIONS, "replaceCompositionChild");
218
+ if (this.compositionSpec.type === "facet") {
219
+ throw new Error("replaceCompositionChild is not available on a facet composition.");
220
+ }
221
+ const target = validateUserId(args.target, "Composition child target");
222
+ if (!Object.hasOwn(this.children, target)) {
223
+ throw new Error(`Unknown composition child "${target}".`);
224
+ }
225
+ if (!(args.program instanceof CoreChartProgram)) {
226
+ throw new TypeError("replaceCompositionChild program must be a ChartProgram.");
227
+ }
228
+ childDescriptor({ id: target, program: args.program });
229
+ const children = freezeOwned({
230
+ ...this.children,
231
+ [target]: args.program
232
+ });
233
+ return this._withCompositionState({
234
+ children,
235
+ compositionSpec: this.compositionSpec
236
+ })
237
+ .useProgram({ id: target })
238
+ .materializeComposition();
239
+ }
240
+ );
241
+
242
+ export function registerCompositionActions(ProgramClass) {
243
+ ProgramClass.prototype.useProgram = useProgram;
244
+ ProgramClass.prototype.materializeComposition = materializeComposition;
245
+ ProgramClass.prototype.editCompositionLayout = editCompositionLayout;
246
+ ProgramClass.prototype.replaceCompositionChild = replaceCompositionChild;
247
+ }
@@ -0,0 +1 @@
1
+ export { registerCompositionActions } from "./actions.js";
@@ -3,15 +3,12 @@ import { validateCoordinateType } from "../../grammar/coordinates.js";
3
3
  import { validateUserId } from "../../core/identifiers.js";
4
4
  import { findCoordinate } from "../../selectors/coordinates.js";
5
5
  import { findLayer, hasLayer } from "../../selectors/layers.js";
6
+ import { validateOptionObject } from "../../core/validation.js";
6
7
 
7
8
  const COORDINATE_OPTIONS = Object.freeze(["id", "type", "layers"]);
8
9
 
9
10
  function validateOptions(args) {
10
- for (const key of Object.keys(args)) {
11
- if (!COORDINATE_OPTIONS.includes(key)) {
12
- throw new Error(`Unknown createCoordinate option "${key}".`);
13
- }
14
- }
11
+ validateOptionObject(args, COORDINATE_OPTIONS, "createCoordinate");
15
12
  }
16
13
 
17
14
  function validateLayers(program, layers) {
@@ -22,7 +22,13 @@ export const materializeDensityData = action(
22
22
  return this
23
23
  .editSemantic({
24
24
  property: `dataset[${id}].transform`,
25
- value: [{ ...transform, bandwidth: result.bandwidth }]
25
+ value: [{
26
+ ...transform,
27
+ resolved: {
28
+ bandwidth: result.bandwidth,
29
+ extent: result.extent
30
+ }
31
+ }]
26
32
  })
27
33
  .editSemantic({
28
34
  property: `dataset[${id}].values`,
@@ -10,6 +10,8 @@ import {
10
10
  normalizeMarkFilterTransform
11
11
  } from "../../grammar/markFilter.js";
12
12
  import { resolveMarkFilterSelection } from "../../materialization/selection/filter.js";
13
+ import { findSelectionPolicy } from
14
+ "../../materialization/selection/policies/index.js";
13
15
  import {
14
16
  applyLayerDataRematerialization
15
17
  } from "../../materialization/dependencies.js";
@@ -121,7 +123,7 @@ export const filterMarks = action(
121
123
  label: "filter mark",
122
124
  predicate: candidate =>
123
125
  candidate.data !== undefined &&
124
- ["point", "bar", "line", "area", "rule"].includes(candidate.mark?.type)
126
+ findSelectionPolicy(candidate.mark?.type) !== undefined
125
127
  });
126
128
  const derivedId = `${layer.id}FilteredData`;
127
129
  if (hasDataset(this, derivedId)) {
@@ -119,6 +119,16 @@ const encodeRadius = action(
119
119
  }
120
120
  );
121
121
 
122
+ const encodePointRadius = action(
123
+ {
124
+ op: "encodePointRadius",
125
+ description: "Set a constant graphical radius on a point mark."
126
+ },
127
+ function (args = {}) {
128
+ return this.encodeRadius(args);
129
+ }
130
+ );
131
+
122
132
  const encodeSize = action(
123
133
  {
124
134
  op: "encodeSize",
@@ -236,6 +246,7 @@ const encodeOpacity = action(
236
246
 
237
247
  export function registerAppearanceEncodingAction(ProgramClass) {
238
248
  ProgramClass.prototype.encodeRadius = encodeRadius;
249
+ ProgramClass.prototype.encodePointRadius = encodePointRadius;
239
250
  ProgramClass.prototype.encodeSize = encodeSize;
240
251
  ProgramClass.prototype.encodeShape = encodeShape;
241
252
  ProgramClass.prototype.encodeOpacity = encodeOpacity;
@@ -3,6 +3,7 @@ import { resolveTarget, validateOptions } from "./shared.js";
3
3
  import {
4
4
  BAR_GRAINS,
5
5
  resolveBarColorLayout,
6
+ resolveBarOffsetChannel,
6
7
  resolveBarGrain
7
8
  } from "../../grammar/bars/policy.js";
8
9
  import { normalizeBarWidth } from "../../grammar/bars/geometry.js";
@@ -12,7 +13,7 @@ const OPTIONS = Object.freeze(["band", "pixels", "target"]);
12
13
  const encodeBarWidth = action(
13
14
  {
14
15
  op: "encodeBarWidth",
15
- description: "Set aggregate bar width within each x band or xOffset slot."
16
+ description: "Override aggregate or ranged bar width within its category slot."
16
17
  },
17
18
  function (args = {}) {
18
19
  validateOptions(args, OPTIONS, "encodeBarWidth");
@@ -28,6 +29,7 @@ const encodeBarWidth = action(
28
29
  this.markConfigs[target]?.barWidth
29
30
  );
30
31
  const grouped = layout === "group";
32
+ const offsetChannel = resolveBarOffsetChannel(layer);
31
33
  const grain = resolveBarGrain(layer);
32
34
  if (grain === BAR_GRAINS.ranged) {
33
35
  return this._withMarkConfig(target, { ...this.markConfigs[target], barWidth: width })
@@ -37,11 +39,11 @@ const encodeBarWidth = action(
37
39
  grain !== BAR_GRAINS.aggregate ||
38
40
  (grouped &&
39
41
  layer.encoding?.color?.field !== undefined &&
40
- layer.encoding?.xOffset?.field !== layer.encoding.color.field)
42
+ layer.encoding?.[offsetChannel]?.field !== layer.encoding.color.field)
41
43
  ) {
42
44
  throw new Error(
43
45
  grouped
44
- ? "encodeBarWidth requires complete grouped bar x, y, color, and xOffset encodings."
46
+ ? `encodeBarWidth requires complete grouped bar x, y, color, and ${offsetChannel} encodings.`
45
47
  : "encodeBarWidth requires complete aggregate bar x and y encodings."
46
48
  );
47
49
  }
@@ -4,12 +4,13 @@ import {
4
4
  readQuantitativeField,
5
5
  readScaleField,
6
6
  readTemporalField,
7
- validateNominalFieldType
7
+ validateCategoricalFieldType
8
8
  } from "../../grammar/scales.js";
9
9
  import {
10
10
  BAR_GRAINS,
11
11
  inferBarColorLayout,
12
12
  resolveBarChannels,
13
+ resolveBarOffsetChannel,
13
14
  resolveBarGrain
14
15
  } from "../../grammar/bars/policy.js";
15
16
  import { validateColorLayout } from "../../grammar/seriesLayout.js";
@@ -20,6 +21,7 @@ import {
20
21
  } from "../../grammar/aggregate.js";
21
22
  import {
22
23
  resolveColorScaleDefinition,
24
+ resolveOffsetScaleDefinition,
23
25
  resolveQuantitativeColorScaleDefinition
24
26
  } from "../scales/definitions.js";
25
27
  import {
@@ -33,9 +35,43 @@ import { applyMaterializationPlan } from "../../materialization/dependencies.js"
33
35
  import { planEncodingRematerialization } from "../../materialization/encodings.js";
34
36
 
35
37
  const COLOR_ENCODING_OPTIONS = Object.freeze([
36
- "field", "target", "fieldType", "scale", "layout", "aggregate"
38
+ "field", "target", "fieldType", "scale", "palette", "layout", "aggregate"
37
39
  ]);
38
40
 
41
+ function resolveColorScaleOptions(args) {
42
+ const options = args.scale ?? {};
43
+ if (args.palette === undefined) return options;
44
+ if (options.palette !== undefined || options.range !== undefined) {
45
+ throw new Error(
46
+ "encodeColor palette cannot be combined with scale.palette or scale.range."
47
+ );
48
+ }
49
+ return { ...options, palette: args.palette };
50
+ }
51
+
52
+ function assertNoConstantColor(program, layer) {
53
+ const config = program.markConfigs[layer.id];
54
+ const hasConstant = {
55
+ point: config?.fill !== undefined,
56
+ line: config?.stroke !== undefined,
57
+ bar: config?.barAppearance?.fill !== undefined,
58
+ arc: config?.fill !== undefined,
59
+ rect: config?.fillExplicit === true
60
+ }[layer.mark.type] ?? false;
61
+ if (hasConstant) {
62
+ throw new Error(
63
+ `encodeColor cannot replace constant appearance on ${layer.mark.type} mark "${layer.id}".`
64
+ );
65
+ }
66
+ }
67
+
68
+ function isRangedArea(layer) {
69
+ return layer.mark.type === "area" && (
70
+ layer.encoding?.x2 !== undefined ||
71
+ layer.encoding?.y2 !== undefined
72
+ );
73
+ }
74
+
39
75
  function resolveColorLayout(layer, requested, barGrain) {
40
76
  const existing = layer.encoding?.color === undefined
41
77
  ? undefined
@@ -60,15 +96,21 @@ function resolveColorLayout(layer, requested, barGrain) {
60
96
  : undefined
61
97
  );
62
98
 
63
- if (["point", "line"].includes(layer.mark.type) && layout !== undefined) {
99
+ if (["point", "line", "rect"].includes(layer.mark.type) && layout !== undefined) {
64
100
  throw new Error(`Color layout is not supported for ${layer.mark.type} marks.`);
65
101
  }
66
102
  if (layer.mark.type === "area" && layout === "group") {
67
103
  throw new Error('Area color layout does not support "group".');
68
104
  }
69
105
  if (
70
- layer.mark.type === "area" &&
71
- layer.encoding?.y2 !== undefined &&
106
+ layer.mark.type === "arc" &&
107
+ layout !== undefined &&
108
+ layout !== "overlay"
109
+ ) {
110
+ throw new Error('Arc color layout currently supports only "overlay".');
111
+ }
112
+ if (
113
+ isRangedArea(layer) &&
72
114
  layout !== "overlay"
73
115
  ) {
74
116
  throw new Error('Ranged area color encoding supports only "overlay" layout.');
@@ -81,8 +123,9 @@ function applyColorLayoutCompanion(
81
123
  { target, layer, layout, scale, field }
82
124
  ) {
83
125
  if (layout === undefined) return program;
126
+ if (layer.mark.type === "arc") return program;
84
127
  if (layer.mark.type === "bar" && resolveBarGrain(layer) === BAR_GRAINS.ranged) return program;
85
- if (layer.mark.type === "area" && layer.encoding?.y2 !== undefined) {
128
+ if (isRangedArea(layer)) {
86
129
  return program;
87
130
  }
88
131
  const channels = layer.mark.type === "bar" ? resolveBarChannels(layer) : undefined;
@@ -98,16 +141,15 @@ function applyColorLayoutCompanion(
98
141
  : "zero";
99
142
  let next = program;
100
143
  if (layout === "group") {
101
- if (channels?.orientation === "horizontal") {
102
- throw new Error('Horizontal bars do not support color layout "group" until yOffset is available.');
103
- }
104
- next = next.encodeXOffset({
144
+ const offsetChannel = resolveBarOffsetChannel(layer);
145
+ const method = offsetChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset";
146
+ next = next[method]({
105
147
  field,
106
148
  target,
107
149
  scale: {
108
- ...(layer.encoding?.xOffset?.scale === undefined
150
+ ...(layer.encoding?.[offsetChannel]?.scale === undefined
109
151
  ? {}
110
- : { id: layer.encoding.xOffset.scale }),
152
+ : { id: layer.encoding[offsetChannel].scale }),
111
153
  domain: scale.domain
112
154
  }
113
155
  });
@@ -121,6 +163,40 @@ function applyColorLayoutCompanion(
121
163
  });
122
164
  }
123
165
 
166
+ function preSynchronizeGroupedOffset(
167
+ program,
168
+ { target, layer, layout, field, fieldType, scale, requestedScale }
169
+ ) {
170
+ if (
171
+ layer.mark.type !== "bar" ||
172
+ layout !== "group" ||
173
+ layer.encoding?.color?.scale !== scale.id ||
174
+ !Object.keys(requestedScale).some(key => key !== "id")
175
+ ) {
176
+ return program;
177
+ }
178
+ const offsetChannel = resolveBarOffsetChannel(layer);
179
+ const offset = layer.encoding?.[offsetChannel];
180
+ if (offset?.scale === undefined) return program;
181
+ const offsetScale = resolveOffsetScaleDefinition(program, {
182
+ id: offset.scale,
183
+ domain: scale.domain
184
+ }, offsetChannel);
185
+ return program
186
+ .editSemantic({
187
+ property: `layer[${target}].encoding.${offsetChannel}.field`,
188
+ value: field
189
+ })
190
+ .editSemantic({
191
+ property: `layer[${target}].encoding.${offsetChannel}.fieldType`,
192
+ value: fieldType
193
+ })
194
+ .editSemantic({
195
+ property: `scale[${offsetScale.id}].domain`,
196
+ value: offsetScale.domain
197
+ });
198
+ }
199
+
124
200
  function encodeContinuousColor(program, args) {
125
201
  if (!["quantitative", "temporal"].includes(args.fieldType)) {
126
202
  throw new Error(`Unsupported color field type "${args.fieldType}".`);
@@ -131,12 +207,13 @@ function encodeContinuousColor(program, args) {
131
207
  const { id: target, dataset, layer } = resolveTarget(
132
208
  program,
133
209
  args.target,
134
- ["point", "bar"],
210
+ ["point", "bar", "rect"],
135
211
  "continuous color mark"
136
212
  );
213
+ assertNoConstantColor(program, layer);
137
214
  const requestedScale = resolveReassignmentScaleOptions(
138
215
  layer.encoding?.color,
139
- args.scale ?? {}
216
+ resolveColorScaleOptions(args)
140
217
  );
141
218
  const scale = resolveQuantitativeColorScaleDefinition(
142
219
  program,
@@ -150,16 +227,16 @@ function encodeContinuousColor(program, args) {
150
227
  }
151
228
  if (
152
229
  layer.mark.type === "bar" &&
153
- layer.encoding?.color?.fieldType === "nominal"
230
+ ["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)
154
231
  ) {
155
232
  throw new Error(
156
233
  "Continuous bar color cannot replace an existing nominal color layout."
157
234
  );
158
235
  }
159
236
  let aggregate;
160
- if (layer.mark.type === "point") {
237
+ if (["point", "rect"].includes(layer.mark.type)) {
161
238
  if (args.aggregate !== undefined) {
162
- throw new Error("Point continuous color does not support aggregate.");
239
+ throw new Error(`${layer.mark.type} continuous color does not support aggregate.`);
163
240
  }
164
241
  } else {
165
242
  if (args.fieldType !== "quantitative") {
@@ -184,7 +261,11 @@ function encodeContinuousColor(program, args) {
184
261
  validateAggregateFieldType(aggregate, args.fieldType);
185
262
  validateAggregateFieldValues(dataset.values, args.field, args.fieldType);
186
263
  }
187
- if (Object.hasOwn(scale, "unknown")) {
264
+ if (layer.mark.type === "rect") {
265
+ readScaleField(dataset.values, args.field, args.fieldType, {
266
+ allowUnknown: true
267
+ });
268
+ } else if (Object.hasOwn(scale, "unknown")) {
188
269
  readScaleField(dataset.values, args.field, args.fieldType, {
189
270
  allowUnknown: true
190
271
  });
@@ -231,22 +312,23 @@ const encodeColor = action(
231
312
  function (args = {}) {
232
313
  validateOptions(args, COLOR_ENCODING_OPTIONS, "encodeColor");
233
314
  const requestedFieldType = args.fieldType ?? "nominal";
234
- if (requestedFieldType !== "nominal") {
315
+ if (!["nominal", "ordinal"].includes(requestedFieldType)) {
235
316
  return encodeContinuousColor(this, {
236
317
  ...args,
237
318
  fieldType: requestedFieldType
238
319
  });
239
320
  }
240
321
  if (args.aggregate !== undefined) {
241
- throw new Error("Nominal color does not support aggregate.");
322
+ throw new Error("Categorical color does not support aggregate.");
242
323
  }
243
- const fieldType = validateNominalFieldType(requestedFieldType);
324
+ const fieldType = validateCategoricalFieldType(requestedFieldType);
244
325
  const { id: target, dataset, layer } = resolveTarget(
245
326
  this,
246
327
  args.target,
247
- ["point", "line", "bar", "area"],
328
+ ["point", "line", "bar", "area", "arc", "rect"],
248
329
  "color mark"
249
330
  );
331
+ assertNoConstantColor(this, layer);
250
332
  if (
251
333
  layer.mark.type === "area" &&
252
334
  (layer.encoding?.group?.field === undefined ||
@@ -267,15 +349,19 @@ const encodeColor = action(
267
349
  const layout = resolveColorLayout(layer, args.layout, barGrain);
268
350
  const requestedScale = resolveReassignmentScaleOptions(
269
351
  layer.encoding?.color,
270
- args.scale ?? {}
352
+ resolveColorScaleOptions(args)
271
353
  );
272
354
  const scale = resolveColorScaleDefinition(this, requestedScale);
273
355
  if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
274
356
  throw new Error(
275
- "Nominal color scale unknown currently requires a row-owned point mark."
357
+ "Categorical color scale unknown currently requires a row-owned point mark."
276
358
  );
277
359
  }
278
- if (Object.hasOwn(scale, "unknown")) {
360
+ if (layer.mark.type === "rect") {
361
+ readScaleField(dataset.values, args.field, fieldType, {
362
+ allowUnknown: true
363
+ });
364
+ } else if (Object.hasOwn(scale, "unknown")) {
279
365
  readScaleField(dataset.values, args.field, fieldType, {
280
366
  allowUnknown: true
281
367
  });
@@ -302,6 +388,15 @@ const encodeColor = action(
302
388
  value: layout
303
389
  });
304
390
  }
391
+ next = preSynchronizeGroupedOffset(next, {
392
+ target,
393
+ layer,
394
+ layout,
395
+ field: args.field,
396
+ fieldType,
397
+ scale,
398
+ requestedScale
399
+ });
305
400
  next = applyEncodingScale(next, scale, requestedScale, {
306
401
  reassignment: layer.encoding?.color?.scale === scale.id
307
402
  });
@@ -8,6 +8,7 @@ import { registerPositionEncodingActions } from "./position.js";
8
8
  import { registerRangedEncodingActions } from "./ranged.js";
9
9
  import { registerStrokeDashEncodingActions } from "./strokeDash.js";
10
10
  import { registerRuleAppearanceEncodingActions } from "./ruleAppearance.js";
11
+ import { registerTextEncodingAction } from "./text.js";
11
12
 
12
13
  export function registerEncodingActions(ProgramClass) {
13
14
  registerPositionEncodingActions(ProgramClass);
@@ -20,4 +21,5 @@ export function registerEncodingActions(ProgramClass) {
20
21
  registerStrokeDashEncodingActions(ProgramClass);
21
22
  registerAppearanceEncodingAction(ProgramClass);
22
23
  registerRuleAppearanceEncodingActions(ProgramClass);
24
+ registerTextEncodingAction(ProgramClass);
23
25
  }