ggaction 0.0.2 → 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 (242) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +95 -72
  3. package/package.json +15 -5
  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/data/regression.js +1 -1
  16. package/src/actions/encodings/appearance.js +12 -1
  17. package/src/actions/encodings/barWidth.js +5 -3
  18. package/src/actions/encodings/color/continuous.js +137 -0
  19. package/src/actions/encodings/color/index.js +151 -0
  20. package/src/actions/encodings/color/layout.js +91 -0
  21. package/src/actions/encodings/color/policy.js +88 -0
  22. package/src/actions/encodings/index.js +4 -2
  23. package/src/actions/encodings/offset.js +91 -73
  24. package/src/actions/encodings/position/apply.js +66 -0
  25. package/src/actions/encodings/position/index.js +130 -0
  26. package/src/actions/encodings/position/policies/arc.js +26 -0
  27. package/src/actions/encodings/position/policies/bar.js +9 -5
  28. package/src/actions/encodings/position/policies/index.js +5 -1
  29. package/src/actions/encodings/position/policies/line.js +20 -3
  30. package/src/actions/encodings/position/resolve.js +58 -9
  31. package/src/actions/encodings/ranged.js +30 -8
  32. package/src/actions/encodings/shared.js +17 -21
  33. package/src/actions/encodings/strokeDash.js +1 -1
  34. package/src/actions/encodings/text.js +73 -0
  35. package/src/actions/errorBands/create.js +37 -29
  36. package/src/actions/errorBands/edit.js +206 -0
  37. package/src/actions/errorBands/index.js +9 -0
  38. package/src/actions/errorBands/options.js +39 -0
  39. package/src/actions/errorBars/create.js +50 -43
  40. package/src/actions/errorBars/edit.js +130 -0
  41. package/src/actions/errorBars/index.js +3 -0
  42. package/src/actions/errorBars/options.js +52 -0
  43. package/src/actions/facets/actions.js +162 -0
  44. package/src/actions/facets/derive.js +200 -0
  45. package/src/actions/facets/guides.js +16 -0
  46. package/src/actions/facets/index.js +1 -0
  47. package/src/actions/facets/replay.js +69 -0
  48. package/src/actions/guides/applicability.js +113 -0
  49. package/src/actions/guides/axes/axes.js +70 -16
  50. package/src/actions/guides/axes/edit.js +126 -0
  51. package/src/actions/guides/axes/index.js +12 -0
  52. package/src/actions/guides/axes/labels.js +22 -19
  53. package/src/actions/guides/axes/remove.js +60 -0
  54. package/src/actions/guides/axes/tickGroups.js +7 -5
  55. package/src/actions/guides/axes/ticks.js +10 -5
  56. package/src/actions/guides/axes/titles.js +8 -6
  57. package/src/actions/guides/grids/grid.js +144 -15
  58. package/src/actions/guides/grids/resolve.js +6 -14
  59. package/src/actions/guides/guides.js +17 -75
  60. package/src/actions/guides/index.js +6 -0
  61. package/src/actions/guides/legends/categorical/actions.js +48 -9
  62. package/src/actions/guides/legends/categorical/components.js +16 -16
  63. package/src/actions/guides/legends/categorical/index.js +14 -14
  64. package/src/actions/guides/legends/categorical/layout.js +7 -3
  65. package/src/actions/guides/legends/categorical/options.js +2 -5
  66. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  67. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  68. package/src/actions/guides/legends/continuous/common.js +7 -7
  69. package/src/actions/guides/legends/continuous/interval.js +6 -6
  70. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  71. package/src/actions/guides/legends/edit.js +4 -26
  72. package/src/actions/guides/legends/focused.js +63 -0
  73. package/src/actions/guides/legends/index.js +4 -0
  74. package/src/actions/guides/legends/remove.js +46 -0
  75. package/src/actions/guides/legends/size.js +13 -15
  76. package/src/actions/guides/legends/target.js +25 -0
  77. package/src/actions/guides/polar/axes/facade.js +219 -0
  78. package/src/actions/guides/polar/axes/index.js +55 -0
  79. package/src/actions/guides/polar/axes/labels.js +188 -0
  80. package/src/actions/guides/polar/axes/lines.js +113 -0
  81. package/src/actions/guides/polar/axes/shared.js +112 -0
  82. package/src/actions/guides/polar/axes/ticks.js +164 -0
  83. package/src/actions/guides/polar/axes/titles.js +165 -0
  84. package/src/actions/guides/polar/grids.js +252 -0
  85. package/src/actions/guides/polar/index.js +2 -0
  86. package/src/actions/guides/polar/resolve.js +269 -0
  87. package/src/actions/guides/tickValues.js +1 -1
  88. package/src/actions/index.js +4 -0
  89. package/src/actions/intervals/resolve.js +1 -1
  90. package/src/actions/marks/arc/actions.js +239 -0
  91. package/src/actions/marks/arc/index.js +1 -0
  92. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  93. package/src/actions/marks/area/index.js +4 -0
  94. package/src/actions/marks/bar/create.js +26 -5
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/bar/materialize.js +25 -31
  97. package/src/actions/marks/index.js +12 -4
  98. package/src/actions/marks/lifecycle.js +27 -0
  99. package/src/actions/marks/line/actions.js +373 -0
  100. package/src/actions/marks/line/index.js +1 -0
  101. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  102. package/src/actions/marks/point/index.js +1 -0
  103. package/src/actions/marks/rect/actions.js +165 -0
  104. package/src/actions/marks/rect/index.js +1 -0
  105. package/src/actions/marks/remove.js +214 -0
  106. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  107. package/src/actions/marks/rule/index.js +1 -0
  108. package/src/actions/marks/shared.js +156 -25
  109. package/src/actions/marks/text/actions.js +192 -0
  110. package/src/actions/marks/text/index.js +1 -0
  111. package/src/actions/primitives/createGraphics.js +13 -6
  112. package/src/actions/primitives/editGraphics.js +10 -3
  113. package/src/actions/primitives/semantic.js +26 -3
  114. package/src/actions/primitives/semanticValue.js +26 -4
  115. package/src/actions/regression/components.js +2 -2
  116. package/src/actions/regression/create.js +19 -2
  117. package/src/actions/regression/edit.js +247 -0
  118. package/src/actions/regression/index.js +3 -0
  119. package/src/actions/regression/resolve.js +3 -1
  120. package/src/actions/scales/consumers/common.js +91 -0
  121. package/src/actions/scales/consumers/families.js +47 -0
  122. package/src/actions/scales/consumers/index.js +41 -0
  123. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
  124. package/src/actions/scales/create.js +1 -1
  125. package/src/actions/scales/definitions.js +15 -6
  126. package/src/actions/scales/edit.js +45 -22
  127. package/src/actions/scales/materialize.js +58 -409
  128. package/src/actions/selection/actions.js +38 -24
  129. package/src/actions/selection/index.js +2 -0
  130. package/src/actions/titles/actions.js +63 -2
  131. package/src/actions/titles/index.js +2 -0
  132. package/src/actions/titles/resolve.js +12 -17
  133. package/src/composition.js +10 -0
  134. package/src/core/ChartProgram.js +49 -65
  135. package/src/core/action.js +10 -0
  136. package/src/core/compositionState.js +169 -0
  137. package/src/core/materializationState.js +65 -0
  138. package/src/core/programState.js +28 -0
  139. package/src/core/validation.js +16 -0
  140. package/src/core/vocabulary.js +154 -4
  141. package/src/grammar/arcs.js +154 -0
  142. package/src/grammar/areaSeries.js +1 -1
  143. package/src/grammar/bars/aggregate.js +3 -3
  144. package/src/grammar/bars/geometry.js +3 -3
  145. package/src/grammar/bars/policy.js +7 -1
  146. package/src/grammar/coordinates.js +3 -7
  147. package/src/grammar/density.js +21 -2
  148. package/src/grammar/facets/dependencies.js +185 -0
  149. package/src/grammar/facets/guides.js +257 -0
  150. package/src/grammar/facets/index.js +122 -0
  151. package/src/grammar/facets/scales.js +254 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/lineSeries.js +112 -4
  154. package/src/grammar/palettes.js +5 -7
  155. package/src/grammar/polar.js +102 -0
  156. package/src/grammar/polarGuides.js +243 -0
  157. package/src/grammar/polarLineCommands.js +43 -0
  158. package/src/grammar/polarPaths.js +161 -0
  159. package/src/grammar/positionCompatibility.js +18 -2
  160. package/src/grammar/rects.js +26 -0
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +1 -1
  166. package/src/grammar/scales/color.js +4 -2
  167. package/src/grammar/scales/continuous.js +6 -1
  168. package/src/grammar/scales/fields.js +7 -0
  169. package/src/grammar/scales/ordinal.js +5 -2
  170. package/src/grammar/scales/policies.js +1 -1
  171. package/src/grammar/schemas/graphic.js +1 -1
  172. package/src/grammar/schemas/graphicBounds.js +64 -9
  173. package/src/grammar/schemas/graphicTree.js +44 -10
  174. package/src/grammar/schemas/semanticPath.js +31 -9
  175. package/src/grammar/seriesLayout.js +13 -2
  176. package/src/grammar/statistics/studentT.js +98 -0
  177. package/src/grammar/text.js +71 -0
  178. package/src/grammar/ticks.js +50 -13
  179. package/src/grammar/transforms.js +1 -1
  180. package/src/index.js +1 -0
  181. package/src/layout/composition.js +310 -0
  182. package/src/layout/facets.js +131 -0
  183. package/src/layout/title.js +6 -0
  184. package/src/materialization/bars/aggregate.js +6 -3
  185. package/src/materialization/bars/grouped.js +75 -37
  186. package/src/materialization/bars/histogram.js +1 -1
  187. package/src/materialization/bars/ranged.js +1 -1
  188. package/src/materialization/composition.js +152 -0
  189. package/src/materialization/compositionSnapshot.js +114 -0
  190. package/src/materialization/density.js +1 -1
  191. package/src/materialization/dependencies.js +47 -73
  192. package/src/materialization/encodings.js +14 -28
  193. package/src/materialization/facetGuides/index.js +2 -0
  194. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  195. package/src/materialization/facetGuides/placement.js +126 -0
  196. package/src/materialization/facetGuides/preparation.js +188 -0
  197. package/src/materialization/facets.js +214 -0
  198. package/src/materialization/graphicHierarchy.js +15 -5
  199. package/src/materialization/guides/resources.js +46 -0
  200. package/src/materialization/marks/capabilities.js +141 -0
  201. package/src/materialization/marks/index.js +105 -0
  202. package/src/materialization/marks/policies.js +87 -0
  203. package/src/materialization/planner.js +105 -12
  204. package/src/materialization/rect.js +138 -0
  205. package/src/materialization/rectConfig.js +38 -0
  206. package/src/materialization/rowEncoding.js +33 -0
  207. package/src/materialization/scaleGuideDependencies.js +94 -0
  208. package/src/materialization/scales/map.js +40 -0
  209. package/src/materialization/scales/policies/arc.js +37 -0
  210. package/src/materialization/scales/policies/bar.js +132 -0
  211. package/src/materialization/scales/policies/series.js +76 -0
  212. package/src/materialization/scales/resolve.js +326 -0
  213. package/src/materialization/selection/items/arc.js +29 -0
  214. package/src/materialization/selection/items/bar.js +255 -0
  215. package/src/materialization/selection/items/common.js +131 -0
  216. package/src/materialization/selection/items/index.js +6 -0
  217. package/src/materialization/selection/items/path.js +59 -0
  218. package/src/materialization/selection/items/point.js +55 -0
  219. package/src/materialization/selection/items/rect.js +23 -0
  220. package/src/materialization/selection/items/rule.js +25 -0
  221. package/src/materialization/selection/policies/arc.js +10 -0
  222. package/src/materialization/selection/policies/area.js +1 -1
  223. package/src/materialization/selection/policies/bar.js +1 -1
  224. package/src/materialization/selection/policies/index.js +5 -1
  225. package/src/materialization/selection/policies/line.js +1 -1
  226. package/src/materialization/selection/policies/point.js +1 -1
  227. package/src/materialization/selection/policies/rect.js +10 -0
  228. package/src/materialization/selection/policies/rule.js +1 -1
  229. package/src/materialization/selection/styles.js +25 -17
  230. package/src/materialization/text.js +156 -0
  231. package/src/renderers/canvas/index.js +68 -22
  232. package/src/renderers/canvas/text.js +14 -1
  233. package/types/extension.d.ts +5 -1
  234. package/types/index.d.ts +56 -1
  235. package/types/program.d.ts +664 -27
  236. package/src/actions/encodings/color.js +0 -329
  237. package/src/actions/encodings/position.js +0 -176
  238. package/src/actions/marks/line.js +0 -248
  239. package/src/grammar/regression.js +0 -578
  240. package/src/grammar/scales.js +0 -1
  241. package/src/materialization/marks.js +0 -110
  242. package/src/materialization/selection/items.js +0 -503
@@ -27,12 +27,12 @@ const DEFAULT_OUTLIER = Object.freeze({
27
27
  opacity: 0.75
28
28
  });
29
29
 
30
- function plainOptions(value, keys, label) {
30
+ function plainOptions(value, keys, label, operation = "createBoxPlot") {
31
31
  if (value === undefined) return {};
32
32
  if (!isPlainObject(value)) {
33
- throw new TypeError(`createBoxPlot ${label} must be a plain object.`);
33
+ throw new TypeError(`${operation} ${label} must be a plain object.`);
34
34
  }
35
- validateKeys(value, keys, `createBoxPlot ${label}`);
35
+ validateKeys(value, keys, `${operation} ${label}`);
36
36
  return value;
37
37
  }
38
38
 
@@ -52,12 +52,12 @@ export function boxEncodingArgs(value) {
52
52
  };
53
53
  }
54
54
 
55
- export function resolveBoxWhisker(value) {
55
+ export function resolveBoxWhisker(value, operation = "createBoxPlot") {
56
56
  if (value === undefined) return Object.freeze({ type: "tukey", factor: 1.5 });
57
57
  if (!isPlainObject(value)) {
58
- throw new TypeError("createBoxPlot whisker must be a plain object.");
58
+ throw new TypeError(`${operation} whisker must be a plain object.`);
59
59
  }
60
- validateKeys(value, ["type", "factor"], "createBoxPlot whisker");
60
+ validateKeys(value, ["type", "factor"], `${operation} whisker`);
61
61
  const type = value.type ?? "tukey";
62
62
  if (!["tukey", "minmax"].includes(type)) {
63
63
  throw new Error(`Unsupported createBoxPlot whisker type "${type}".`);
@@ -77,8 +77,8 @@ export function resolveBoxWhisker(value) {
77
77
  return Object.freeze({ type, factor });
78
78
  }
79
79
 
80
- export function resolveBoxWidth(value) {
81
- const options = plainOptions(value, ["band"], "width");
80
+ export function resolveBoxWidth(value, operation = "createBoxPlot") {
81
+ const options = plainOptions(value, ["band"], "width", operation);
82
82
  const band = options.band ?? 0.7;
83
83
  if (!Number.isFinite(band) || band <= 0 || band >= 1) {
84
84
  throw new RangeError(
@@ -88,11 +88,12 @@ export function resolveBoxWidth(value) {
88
88
  return band;
89
89
  }
90
90
 
91
- export function resolveBoxAppearance(value) {
91
+ export function resolveBoxAppearance(value, operation = "createBoxPlot") {
92
92
  const options = plainOptions(
93
93
  value,
94
94
  ["fill", "opacity", "stroke", "strokeWidth"],
95
- "box"
95
+ "box",
96
+ operation
96
97
  );
97
98
  return Object.freeze({
98
99
  fill: options.fill === undefined
@@ -113,8 +114,13 @@ export function resolveBoxAppearance(value) {
113
114
  });
114
115
  }
115
116
 
116
- export function resolveBoxMedianAppearance(value) {
117
- const options = plainOptions(value, ["stroke", "strokeWidth"], "median");
117
+ export function resolveBoxMedianAppearance(value, operation = "createBoxPlot") {
118
+ const options = plainOptions(
119
+ value,
120
+ ["stroke", "strokeWidth"],
121
+ "median",
122
+ operation
123
+ );
118
124
  return Object.freeze({
119
125
  stroke: options.stroke === undefined
120
126
  ? DEFAULT_MEDIAN.stroke
@@ -128,8 +134,13 @@ export function resolveBoxMedianAppearance(value) {
128
134
  });
129
135
  }
130
136
 
131
- export function resolveBoxOutlierAppearance(value) {
132
- const options = plainOptions(value, ["shape", "radius", "opacity"], "outlier");
137
+ export function resolveBoxOutlierAppearance(value, operation = "createBoxPlot") {
138
+ const options = plainOptions(
139
+ value,
140
+ ["shape", "radius", "opacity"],
141
+ "outlier",
142
+ operation
143
+ );
133
144
  return Object.freeze({
134
145
  shape: options.shape === undefined
135
146
  ? DEFAULT_OUTLIER.shape
@@ -6,6 +6,7 @@ import {
6
6
  validateCanvasState
7
7
  } from "../../layout/canvas.js";
8
8
  import { cloneAndFreeze } from "../../core/immutable.js";
9
+ import { validateOptionObject } from "../../core/validation.js";
9
10
  import {
10
11
  applyMaterializationPlan,
11
12
  planCanvasRematerialization
@@ -13,6 +14,7 @@ import {
13
14
  import {
14
15
  assertCanvasHierarchyAvailable,
15
16
  CANVAS_GRAPHIC_ID,
17
+ findCanvasGraphic,
16
18
  PLOT_GRAPHIC_ID
17
19
  } from "../../materialization/graphicHierarchy.js";
18
20
 
@@ -24,21 +26,11 @@ const CANVAS_OPTIONS = Object.freeze([
24
26
  ]);
25
27
 
26
28
  function validateOptions(args, operation, { allowEmpty = false } = {}) {
27
- const keys = Object.keys(args);
28
-
29
- if (!allowEmpty && keys.length === 0) {
30
- throw new TypeError(`${operation} requires at least one option.`);
31
- }
32
-
33
- for (const key of keys) {
34
- if (!CANVAS_OPTIONS.includes(key)) {
35
- throw new Error(`Unknown ${operation} option "${key}".`);
36
- }
37
- }
29
+ validateOptionObject(args, CANVAS_OPTIONS, operation, { allowEmpty });
38
30
  }
39
31
 
40
32
  function requireCanvas(program) {
41
- const canvas = program.graphicSpec.objects.canvas;
33
+ const canvas = findCanvasGraphic(program);
42
34
 
43
35
  if (canvas?.type !== "canvas") {
44
36
  throw new Error("editCanvas requires an existing canvas.");
@@ -49,8 +41,9 @@ function requireCanvas(program) {
49
41
 
50
42
  function resolveCanvasState(program, args) {
51
43
  const canvas = requireCanvas(program);
52
- const baseMargin =
53
- program.materializationConfigs.canvas?.margin ?? DEFAULT_MARGIN;
44
+ const canvasConfig = program.materializationConfigs.canvas;
45
+ const baseMargin = canvasConfig?.margin ?? DEFAULT_MARGIN;
46
+ const baseSize = canvasConfig?.size ?? { width: "explicit", height: "explicit" };
54
47
  const state = {
55
48
  width: Object.hasOwn(args, "width")
56
49
  ? args.width
@@ -63,7 +56,11 @@ function resolveCanvasState(program, args) {
63
56
  : canvas.properties.background,
64
57
  margin: Object.hasOwn(args, "margin")
65
58
  ? normalizeMargin(args.margin, baseMargin)
66
- : baseMargin
59
+ : baseMargin,
60
+ size: {
61
+ width: Object.hasOwn(args, "width") ? "explicit" : baseSize.width,
62
+ height: Object.hasOwn(args, "height") ? "explicit" : baseSize.height
63
+ }
67
64
  };
68
65
 
69
66
  validateCanvasState(state);
@@ -90,7 +87,7 @@ export const editCanvas = action(
90
87
  }
91
88
  }
92
89
 
93
- next = next._withCanvasConfig({ margin: state.margin });
90
+ next = next._withCanvasConfig({ margin: state.margin, size: state.size });
94
91
 
95
92
  if (
96
93
  Object.hasOwn(args, "width") ||
@@ -132,7 +129,7 @@ export const createCanvas = action(
132
129
  : DEFAULT_CANVAS.margin
133
130
  };
134
131
 
135
- return this
132
+ const next = this
136
133
  .createGraphics({ id: CANVAS_GRAPHIC_ID, type: "canvas" })
137
134
  .createGraphics({
138
135
  id: PLOT_GRAPHIC_ID,
@@ -140,5 +137,12 @@ export const createCanvas = action(
140
137
  parent: CANVAS_GRAPHIC_ID
141
138
  })
142
139
  .editCanvas(options);
140
+ return next._withCanvasConfig({
141
+ ...next.materializationConfigs.canvas,
142
+ size: {
143
+ width: Object.hasOwn(args, "width") ? "explicit" : "auto",
144
+ height: Object.hasOwn(args, "height") ? "explicit" : "auto"
145
+ }
146
+ });
143
147
  }
144
148
  );
@@ -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)) {
@@ -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
@@ -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
  }