ggaction 0.0.15 → 0.0.16

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 (236) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +22 -2
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/knowledge/task-packet.schema.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +33 -6
  9. package/src/BasicChartProgram.js +3 -1
  10. package/src/ChartProgram.js +2 -0
  11. package/src/accessibility.js +157 -0
  12. package/src/actions/boxPlots/components.js +6 -8
  13. package/src/actions/boxPlots/create.js +4 -5
  14. package/src/actions/boxPlots/edit.js +3 -4
  15. package/src/actions/boxPlots/materialize.js +1 -1
  16. package/src/actions/canvas/actions.js +3 -3
  17. package/src/actions/canvas/fitting.js +1 -1
  18. package/src/actions/categoryOrder/index.js +6 -8
  19. package/src/actions/charts/area.js +1 -1
  20. package/src/actions/charts/bar.js +1 -1
  21. package/src/actions/charts/beeswarm.js +1 -1
  22. package/src/actions/charts/density.js +1 -1
  23. package/src/actions/charts/ecdf.js +2 -2
  24. package/src/actions/charts/endpoints.js +4 -4
  25. package/src/actions/charts/heatmap.js +1 -1
  26. package/src/actions/charts/histogram.js +1 -1
  27. package/src/actions/charts/horizon.js +1 -1
  28. package/src/actions/charts/interval-regression.js +2 -2
  29. package/src/actions/charts/line.js +1 -1
  30. package/src/actions/charts/parallel.js +1 -1
  31. package/src/actions/charts/pie.js +1 -1
  32. package/src/actions/charts/polar.js +2 -2
  33. package/src/actions/charts/radar.js +1 -1
  34. package/src/actions/charts/radial.js +2 -2
  35. package/src/actions/charts/raincloud.js +2 -2
  36. package/src/actions/charts/rug-strip.js +2 -2
  37. package/src/actions/charts/scatter.js +1 -1
  38. package/src/actions/composition/actions.js +20 -24
  39. package/src/actions/coordinates/actions.js +1 -1
  40. package/src/actions/coordinates/edit.js +1 -1
  41. package/src/actions/data/basicBin2d.js +4 -5
  42. package/src/actions/data/bin.js +2 -2
  43. package/src/actions/data/bin2d.js +6 -8
  44. package/src/actions/data/bin2dMaterialize.js +4 -5
  45. package/src/actions/data/box.js +4 -4
  46. package/src/actions/data/complete.js +2 -2
  47. package/src/actions/data/computed.js +2 -2
  48. package/src/actions/data/create.js +5 -12
  49. package/src/actions/data/density.js +3 -3
  50. package/src/actions/data/derived.js +10 -14
  51. package/src/actions/data/ecdf.js +2 -2
  52. package/src/actions/data/edit.js +35 -23
  53. package/src/actions/data/filter.js +14 -20
  54. package/src/actions/data/fold.js +2 -2
  55. package/src/actions/data/gradientProfile.js +2 -2
  56. package/src/actions/data/horizon.js +2 -2
  57. package/src/actions/data/impute.js +2 -2
  58. package/src/actions/data/index.js +55 -54
  59. package/src/actions/data/interval.js +2 -2
  60. package/src/actions/data/intervalEdit.js +26 -18
  61. package/src/actions/data/normalize.js +2 -2
  62. package/src/actions/data/regression.js +5 -6
  63. package/src/actions/data/revise.js +48 -0
  64. package/src/actions/data/revisionBindings.js +13 -33
  65. package/src/actions/data/stack.js +2 -2
  66. package/src/actions/data/summary.js +2 -2
  67. package/src/actions/data/timeUnit.js +2 -2
  68. package/src/actions/data/window.js +2 -2
  69. package/src/actions/encodings/angle.js +1 -1
  70. package/src/actions/encodings/appearance.js +4 -4
  71. package/src/actions/encodings/barWidth.js +1 -1
  72. package/src/actions/encodings/channels.js +2 -2
  73. package/src/actions/encodings/color/index.js +1 -1
  74. package/src/actions/encodings/density.js +2 -2
  75. package/src/actions/encodings/histogram.js +1 -1
  76. package/src/actions/encodings/horizon.js +6 -8
  77. package/src/actions/encodings/offset.js +2 -2
  78. package/src/actions/encodings/parallel.js +4 -5
  79. package/src/actions/encodings/pathOrder.js +6 -11
  80. package/src/actions/encodings/position/index.js +4 -4
  81. package/src/actions/encodings/ranged.js +5 -5
  82. package/src/actions/encodings/remove.js +4 -5
  83. package/src/actions/encodings/ruleAppearance.js +1 -1
  84. package/src/actions/encodings/seriesLayout.js +1 -1
  85. package/src/actions/encodings/stroke.js +1 -1
  86. package/src/actions/encodings/strokeDash.js +2 -2
  87. package/src/actions/encodings/text.js +1 -1
  88. package/src/actions/errorBands/create.js +11 -17
  89. package/src/actions/errorBands/edit.js +13 -131
  90. package/src/actions/errorBars/create.js +8 -13
  91. package/src/actions/errorBars/edit.js +10 -121
  92. package/src/actions/facets/actions.js +30 -16
  93. package/src/actions/facets/derive.js +8 -2
  94. package/src/actions/facets/guides.js +4 -5
  95. package/src/actions/facets/replay.js +4 -5
  96. package/src/actions/gradientPlots/components.js +5 -6
  97. package/src/actions/gradientPlots/create.js +4 -5
  98. package/src/actions/gradientPlots/edit.js +4 -5
  99. package/src/actions/gradientPlots/materialize.js +2 -2
  100. package/src/actions/gradientPlots/rebind.js +4 -5
  101. package/src/actions/guides/axes/axes.js +23 -4
  102. package/src/actions/guides/axes/axis.js +2 -2
  103. package/src/actions/guides/axes/edit.js +2 -2
  104. package/src/actions/guides/axes/labels.js +9 -9
  105. package/src/actions/guides/axes/lines.js +9 -7
  106. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  107. package/src/actions/guides/axes/parallel.js +5 -5
  108. package/src/actions/guides/axes/remove.js +4 -4
  109. package/src/actions/guides/axes/tickGroups.js +4 -4
  110. package/src/actions/guides/axes/ticks.js +13 -4
  111. package/src/actions/guides/axes/titles.js +10 -10
  112. package/src/actions/guides/grids/grid.js +18 -29
  113. package/src/actions/guides/guides.js +1 -1
  114. package/src/actions/guides/legends/blocks.js +1 -1
  115. package/src/actions/guides/legends/categorical/actions.js +4 -4
  116. package/src/actions/guides/legends/categorical/components.js +38 -33
  117. package/src/actions/guides/legends/categorical/layout.js +1 -1
  118. package/src/actions/guides/legends/categorical/symbols.js +27 -30
  119. package/src/actions/guides/legends/continuous/common.js +46 -8
  120. package/src/actions/guides/legends/continuous/gradient.js +89 -155
  121. package/src/actions/guides/legends/continuous/interval.js +13 -13
  122. package/src/actions/guides/legends/continuous/opacity.js +30 -79
  123. package/src/actions/guides/legends/continuous/stroke.js +22 -322
  124. package/src/actions/guides/legends/edit.js +1 -1
  125. package/src/actions/guides/legends/focused.js +5 -5
  126. package/src/actions/guides/legends/lane.js +6 -6
  127. package/src/actions/guides/legends/remove.js +4 -5
  128. package/src/actions/guides/legends/size.js +21 -82
  129. package/src/actions/guides/legends/strokeWidth.js +17 -77
  130. package/src/actions/guides/polar/axes/facade.js +4 -4
  131. package/src/actions/guides/polar/axes/labels.js +5 -5
  132. package/src/actions/guides/polar/axes/lines.js +9 -15
  133. package/src/actions/guides/polar/axes/ticks.js +9 -15
  134. package/src/actions/guides/polar/axes/titles.js +5 -5
  135. package/src/actions/guides/polar/grids.js +11 -13
  136. package/src/actions/index.js +2 -0
  137. package/src/actions/intervals/revision.js +126 -0
  138. package/src/actions/marks/arc/actions.js +3 -3
  139. package/src/actions/marks/area/actions.js +4 -4
  140. package/src/actions/marks/bar/create.js +1 -1
  141. package/src/actions/marks/bar/edit.js +1 -1
  142. package/src/actions/marks/bar/materialize.js +12 -38
  143. package/src/actions/marks/line/actions.js +3 -3
  144. package/src/actions/marks/point/create.js +1 -1
  145. package/src/actions/marks/point/edit.js +1 -1
  146. package/src/actions/marks/point/jitter.js +6 -8
  147. package/src/actions/marks/point/materialize.js +16 -24
  148. package/src/actions/marks/point/packing.js +6 -8
  149. package/src/actions/marks/rect/actions.js +3 -3
  150. package/src/actions/marks/references.js +8 -10
  151. package/src/actions/marks/remove.js +5 -49
  152. package/src/actions/marks/rule/actions.js +4 -4
  153. package/src/actions/marks/text/actions.js +5 -5
  154. package/src/actions/marks/text/layout.js +12 -20
  155. package/src/actions/marks/text/removal.js +1 -1
  156. package/src/actions/marks/text/selection.js +4 -5
  157. package/src/actions/marks/tick/actions.js +3 -3
  158. package/src/actions/primitives/createGraphics.js +5 -4
  159. package/src/actions/primitives/editGraphics.js +5 -4
  160. package/src/actions/primitives/graphicProperties.js +6 -0
  161. package/src/actions/primitives/semantic.js +10 -3
  162. package/src/actions/primitives/semanticAction.js +7 -4
  163. package/src/actions/primitives/semanticValidation/dataset.js +2 -4
  164. package/src/actions/regression/components.js +10 -14
  165. package/src/actions/regression/create.js +3 -4
  166. package/src/actions/regression/edit.js +3 -4
  167. package/src/actions/resources/remove.js +12 -11
  168. package/src/actions/scales/create.js +1 -1
  169. package/src/actions/scales/edit.js +1 -1
  170. package/src/actions/scales/materialize.js +1 -1
  171. package/src/actions/scales/offset.js +2 -2
  172. package/src/actions/scales/parallel.js +1 -1
  173. package/src/actions/scales/quantitativeColor.js +4 -5
  174. package/src/actions/selection/actions.js +25 -36
  175. package/src/actions/textMetrics/index.js +56 -0
  176. package/src/actions/theme/actions.js +2 -2
  177. package/src/actions/theme/reconcile.js +65 -121
  178. package/src/actions/titles/actions.js +8 -8
  179. package/src/actions/titles/resolve.js +3 -3
  180. package/src/actions/violinPlots/create.js +1 -1
  181. package/src/actions/violinPlots/edit.js +1 -1
  182. package/src/core/action.js +107 -35
  183. package/src/core/diagnostics.js +16 -0
  184. package/src/core/extensionRegistry.js +27 -0
  185. package/src/core/font.js +18 -0
  186. package/src/core/identifiers.js +5 -4
  187. package/src/core/immutable.js +10 -3
  188. package/src/core/optionRequirements.js +8 -0
  189. package/src/core/programState.js +1 -1
  190. package/src/core/resourceReferences.js +15 -6
  191. package/src/core/textMetricProfile.js +28 -0
  192. package/src/core/textMetrics.js +24 -3
  193. package/src/core/validation.js +22 -10
  194. package/src/diagnostics.js +1 -0
  195. package/src/grammar/impute.js +3 -2
  196. package/src/grammar/scales/types.js +8 -0
  197. package/src/grammar/schemas/graphicBounds.js +11 -11
  198. package/src/layout/labels.js +11 -10
  199. package/src/layout/legend.js +4 -4
  200. package/src/layout/legendItems.js +6 -6
  201. package/src/layout/text.js +10 -9
  202. package/src/layout/title.js +9 -9
  203. package/src/materialization/facetGuides/placement.js +1 -1
  204. package/src/materialization/facetGuides/preparation.js +1 -1
  205. package/src/materialization/facetHeaders.js +11 -10
  206. package/src/materialization/guides/layout.js +3 -3
  207. package/src/materialization/scaleGuideDependencies.js +10 -0
  208. package/src/materialization/selection/items/common.js +1 -1
  209. package/src/materialization/text.js +2 -2
  210. package/src/materialization/typography.js +31 -0
  211. package/src/mcp/cli.js +5 -3
  212. package/src/persistence/codec.js +65 -0
  213. package/src/persistence/validation.js +185 -0
  214. package/src/persistence.js +74 -0
  215. package/src/renderers/canvas/index.js +5 -4
  216. package/src/renderers/nativeBackend.js +10 -0
  217. package/src/renderers/options.js +13 -0
  218. package/src/renderers/pdf.js +25 -54
  219. package/src/renderers/png.js +22 -17
  220. package/src/renderers/svg.js +10 -14
  221. package/src/renderers/text.js +1 -11
  222. package/src/selectors/coordinates.js +3 -1
  223. package/src/selectors/datasets.js +8 -6
  224. package/src/selectors/layers.js +6 -4
  225. package/src/selectors/markOwners.js +46 -0
  226. package/src/selectors/scales.js +6 -4
  227. package/src/theme/defaults.js +2 -1
  228. package/src/version.js +2 -0
  229. package/types/accessibility.d.ts +37 -0
  230. package/types/basic.d.ts +4 -2
  231. package/types/diagnostics.d.ts +15 -0
  232. package/types/index.d.ts +5 -0
  233. package/types/pdf.d.ts +9 -0
  234. package/types/persistence.d.ts +7 -0
  235. package/types/png.d.ts +9 -0
  236. package/types/program.d.ts +34 -1
@@ -20,7 +20,7 @@ const STROKE_DASH_ENCODING_OPTIONS = Object.freeze([
20
20
  "field", "value", "target", "fieldType", "scale"
21
21
  ]);
22
22
 
23
- const clearStrokeDashEncoding = action(
23
+ const clearStrokeDashEncoding = /* @__PURE__ */ action(
24
24
  {
25
25
  op: "clearStrokeDashEncoding",
26
26
  description: "Remove the current semantic stroke-dash assignment."
@@ -58,7 +58,7 @@ function reconcileLegendAfterDashRemoval(program, target) {
58
58
  .rematerializeLegend();
59
59
  }
60
60
 
61
- const encodeStrokeDash = action(
61
+ const encodeStrokeDash = /* @__PURE__ */ action(
62
62
  {
63
63
  op: "encodeStrokeDash",
64
64
  description: "Encode a nominal field as line stroke dash."
@@ -25,7 +25,7 @@ function validateTextField(rows, field, format) {
25
25
  if (!found) throw new Error(`Unknown text field "${field}".`);
26
26
  }
27
27
 
28
- export const encodeText = action(
28
+ export const encodeText = /* @__PURE__ */ action(
29
29
  {
30
30
  op: "encodeText",
31
31
  description: "Assign field, constant, or source semantic text content."
@@ -1,9 +1,7 @@
1
- import { action } from "../../core/action.js";
1
+ import { editGraphicProperties } from "../primitives/graphicProperties.js";
2
+ import { action, closedAction } from "../../core/action.js";
2
3
  import { isPlainObject } from "../../core/immutable.js";
3
- import {
4
- validateKeys,
5
- validateNonEmptyString,
6
- } from "../../core/validation.js";
4
+ import { validateNonEmptyString } from "../../core/validation.js";
7
5
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
8
6
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
9
7
  import {
@@ -82,7 +80,7 @@ function positionOptions({ target, field, fieldType, coordinate, scale, temporal
82
80
  ...(temporalUnit === undefined ? {} : { temporalUnit }) };
83
81
  }
84
82
 
85
- export const createErrorBandBoundary = action(
83
+ export const createErrorBandBoundary = /* @__PURE__ */ action(
86
84
  {
87
85
  op: "createErrorBandBoundary",
88
86
  description: "Create one lower or upper error-band boundary line."
@@ -138,14 +136,11 @@ export const createErrorBandBoundary = action(
138
136
  if (groupBy !== undefined) {
139
137
  next = next.encodeGroup({ target: id, field: groupBy });
140
138
  }
141
- return next
142
- .editGraphics({ target: id, property: "stroke", value: stroke })
143
- .editGraphics({
144
- target: id,
145
- property: "strokeDash",
146
- value: next.graphicSpec.objects[id].items.map(() => strokeDash)
147
- })
148
- .editGraphics({ target: id, property: "opacity", value: opacity });
139
+ return editGraphicProperties(next, id, {
140
+ stroke: stroke,
141
+ strokeDash: next.graphicSpec.objects[id].items.map(() => strokeDash),
142
+ opacity: opacity
143
+ });
149
144
  }
150
145
  );
151
146
 
@@ -171,13 +166,12 @@ function rangeArgs(resolved) {
171
166
  };
172
167
  }
173
168
 
174
- export const createErrorBand = action(
169
+ export const createErrorBand = /* @__PURE__ */ closedAction(
175
170
  {
176
171
  op: "createErrorBand",
177
172
  description: "Create a statistical or explicit interval band."
178
- },
173
+ }, OPTIONS,
179
174
  function (args = {}) {
180
- validateKeys(args, OPTIONS, "createErrorBand");
181
175
  const strokeDetails = requestedStrokeDetails(args, "createErrorBand");
182
176
  const resolved = resolveErrorBand(this, args);
183
177
  const curve = validateCurveInterpolation(args.curve ?? "linear");
@@ -1,13 +1,8 @@
1
- import { action } from "../../core/action.js";
1
+ import { currentIntervalRoleArgs, resolveIntervalRoleScales, applyIntervalStatistics } from "../intervals/revision.js";
2
+ import { closedAction } from "../../core/action.js";
2
3
  import { isPlainObject } from "../../core/immutable.js";
3
4
  import { validateUserId } from "../../core/identifiers.js";
4
- import {
5
- validateOptionObject,
6
- validateKeys,
7
- validateNonEmptyString,
8
- validateNonNegativeFinite,
9
- validateUnitInterval
10
- } from "../../core/validation.js";
5
+ import { validateKeys, validateNonEmptyString, validateNonNegativeFinite, validateUnitInterval } from "../../core/validation.js";
11
6
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
12
7
  import { normalizeStrokeDashPattern } from "../../grammar/scales/index.js";
13
8
  import {
@@ -15,12 +10,10 @@ import {
15
10
  STROKE_STYLE_PROPERTIES
16
11
  } from "../../grammar/strokeStyle.js";
17
12
  import { findLayer } from "../../selectors/layers.js";
18
- import { findSemanticScale } from "../../selectors/scales.js";
19
13
  import { removeOwnedMark } from "../marks/remove.js";
20
14
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
21
15
  import { resolveIntervalComposite } from "../intervals/resolve.js";
22
16
  import {
23
- resolveDistributionScalePlan,
24
17
  setCartesianPosition,
25
18
  updateDistributionPositions
26
19
  } from "../distributions/revision.js";
@@ -68,7 +61,6 @@ const EDIT_OPTIONS = Object.freeze([
68
61
  "target", "data", "x", "y", "groupBy", "fill", "opacity", "curve",
69
62
  "statistics", "boundaries", ...STROKE_STYLE_PROPERTIES
70
63
  ]);
71
- const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "method", "level"]);
72
64
  const EDIT_POLICY = Object.freeze({
73
65
  operation: "editErrorBand",
74
66
  resourceLabel: "error-band",
@@ -116,48 +108,13 @@ function resolveOwner(program, requested) {
116
108
  });
117
109
  }
118
110
 
119
- function currentRoleArgs(program, owner, current) {
120
- const transform = findIntervalTransform(program, current.data);
121
- const intervalChannel = current.orientation === "vertical" ? "y" : "x";
122
- const positionChannel = intervalChannel === "x" ? "y" : "x";
123
- const position = {
124
- field: current.position.field,
125
- fieldType: current.position.fieldType,
126
- ...(current.position.temporalUnit === undefined
127
- ? {}
128
- : { temporalUnit: current.position.temporalUnit }),
129
- scale: { id: current.positionScale }
130
- };
131
- const interval = transform === undefined
132
- ? {
133
- center: current.centerField ?? owner.encoding[intervalChannel].title,
134
- lower: current.lowerField,
135
- upper: current.upperField,
136
- scale: { id: current.intervalScale }
137
- }
138
- : {
139
- field: transform.field,
140
- center: transform.center,
141
- extent: transform.extent,
142
- ...(transform.method === undefined ? {} : { method: transform.method }),
143
- ...(transform.level === undefined ? {} : { level: transform.level }),
144
- scale: { id: current.intervalScale }
145
- };
146
- return {
147
- source: current.source ?? transform?.source ?? current.data,
148
- x: positionChannel === "x" ? position : interval,
149
- y: positionChannel === "y" ? position : interval,
150
- groupBy: current.groupBy
151
- };
152
- }
153
-
154
111
  function channelArgs(requested, fallback) {
155
112
  if (requested === undefined) return fallback;
156
113
  return requested;
157
114
  }
158
115
 
159
116
  function resolveRoleCandidate(program, owner, current, args) {
160
- const previous = currentRoleArgs(program, owner, current);
117
+ const previous = currentIntervalRoleArgs(program, owner, current, false);
161
118
  const full = {
162
119
  data: Object.hasOwn(args, "data") ? args.data : previous.source,
163
120
  x: channelArgs(args.x, previous.x),
@@ -179,75 +136,11 @@ function resolveRoleCandidate(program, owner, current, args) {
179
136
  "editErrorBand groupBy must differ from the independent position field."
180
137
  );
181
138
  }
182
- if (Object.hasOwn(args, "statistics")) {
183
- if (!isPlainObject(args.statistics)) {
184
- throw new TypeError("editErrorBand statistics must be a plain object.");
185
- }
186
- validateKeys(args.statistics, STATISTICS_OPTIONS, "editErrorBand statistics");
187
- if (!STATISTICS_OPTIONS.some(key => Object.hasOwn(args.statistics, key))) {
188
- throw new Error(
189
- "editErrorBand statistics requires center, extent, method, or level."
190
- );
191
- }
192
- full[resolved.interval.channel] = {
193
- ...full[resolved.interval.channel],
194
- ...args.statistics
195
- };
196
- resolved = resolveIntervalComposite(program, full, policy);
197
- }
198
- const positionRole = {
199
- field: resolved.position.field,
200
- fieldType: resolved.position.fieldType,
201
- scale: resolved.position.scale
202
- };
203
- const intervalTitle = resolved.interval.mode === "statistical"
204
- ? resolved.interval.field
205
- : resolved.interval.title;
206
- const intervalRole = {
207
- field: intervalTitle,
208
- fieldType: "quantitative",
209
- scale: resolved.interval.scale
210
- };
211
- const x = resolved.position.channel === "x" ? positionRole : intervalRole;
212
- const y = resolved.position.channel === "y" ? positionRole : intervalRole;
213
- const plan = (channel, role) => {
214
- const fallback = role === intervalRole
215
- ? current.intervalScale
216
- : current.positionScale;
217
- const stored = findSemanticScale(program, fallback);
218
- const compatible = role.fieldType === "temporal"
219
- ? stored?.type === "time"
220
- : ["linear", "log", "pow", "sqrt", "symlog"].includes(stored?.type);
221
- const requested = role.scale.type !== undefined || compatible
222
- ? role.scale
223
- : { ...role.scale, type: role.fieldType === "temporal" ? "time" : "linear" };
224
- return resolveDistributionScalePlan(program, {
225
- channel,
226
- fieldType: role.fieldType,
227
- requested,
228
- fallback,
229
- defaults: role === intervalRole
230
- ? EDIT_POLICY.intervalScaleDefaults
231
- : EDIT_POLICY.scaleDefaults(role.fieldType)
232
- });
233
- };
234
- const xScale = plan("x", x);
235
- const yScale = plan("y", y);
139
+ resolved = applyIntervalStatistics(program, full, policy, args, resolved);
140
+ const roles = resolveIntervalRoleScales(program, resolved, current, EDIT_POLICY, owner, previous);
236
141
  return {
237
142
  ...resolved,
238
- x: { ...x, scale: xScale.id },
239
- y: { ...y, scale: yScale.id },
240
- xScale,
241
- yScale,
242
- category: resolved.position.field,
243
- categoryType: resolved.position.fieldType,
244
- measure: intervalTitle,
245
- previous: {
246
- x: { field: previous.x.center ?? previous.x.field,
247
- fieldType: owner.encoding.x.fieldType, scale: owner.encoding.x.scale },
248
- y: { field: previous.y.center ?? previous.y.field,
249
- fieldType: owner.encoding.y.fieldType, scale: owner.encoding.y.scale }
250
- }
143
+ ...roles
251
144
  };
252
145
  }
253
146
 
@@ -389,17 +282,12 @@ function removeBoundary(program, id) {
389
282
  return removeOwnedMark(program, id);
390
283
  }
391
284
 
392
- export const rematerializeErrorBandBoundary = action(
285
+ export const rematerializeErrorBandBoundary = /* @__PURE__ */ closedAction(
393
286
  {
394
287
  op: "rematerializeErrorBandBoundary",
395
288
  description: "Rematerialize one owned error-band boundary."
396
- },
289
+ }, REMATERIALIZE_OPTIONS,
397
290
  function (args = {}) {
398
- validateOptionObject(
399
- args,
400
- REMATERIALIZE_OPTIONS,
401
- "rematerializeErrorBandBoundary"
402
- );
403
291
  const id = validateUserId(args.id, "Error-band boundary id");
404
292
  currentBoundaryAppearance(this, id);
405
293
  const graphic = this.graphicSpec.objects[id];
@@ -422,13 +310,12 @@ export const rematerializeErrorBandBoundary = action(
422
310
  }
423
311
  );
424
312
 
425
- export const editErrorBand = action(
313
+ export const editErrorBand = /* @__PURE__ */ closedAction(
426
314
  {
427
315
  op: "editErrorBand",
428
316
  description: "Revise one error band's roles, statistics, body, and boundaries."
429
- },
317
+ }, EDIT_OPTIONS,
430
318
  function (args = {}) {
431
- validateOptionObject(args, EDIT_OPTIONS, "editErrorBand");
432
319
  if (!["data", "x", "y", "groupBy", "fill", "opacity", "curve",
433
320
  "statistics", "boundaries", ...STROKE_STYLE_PROPERTIES]
434
321
  .some(key => Object.hasOwn(args, key))) {
@@ -585,17 +472,12 @@ export const editErrorBand = action(
585
472
  }
586
473
  );
587
474
 
588
- export const editErrorBandBoundary = action(
475
+ export const editErrorBandBoundary = /* @__PURE__ */ closedAction(
589
476
  {
590
477
  op: "editErrorBandBoundary",
591
478
  description: "Edit one or both owned error-band boundaries."
592
- },
479
+ }, BOUNDARY_EDIT_OPTIONS,
593
480
  function (args = {}) {
594
- validateOptionObject(
595
- args,
596
- BOUNDARY_EDIT_OPTIONS,
597
- "editErrorBandBoundary"
598
- );
599
481
  if (!ERROR_BAND_BOUNDARY_OPTIONS.some(key => Object.hasOwn(args, key))) {
600
482
  throw new Error("editErrorBandBoundary requires an appearance change.");
601
483
  }
@@ -1,8 +1,5 @@
1
- import { action } from "../../core/action.js";
2
- import {
3
- validateKeys,
4
- validateOptionObject
5
- } from "../../core/validation.js";
1
+ import { closedAction } from "../../core/action.js";
2
+ import { validateOptionObject } from "../../core/validation.js";
6
3
  import { findLayer } from "../../selectors/layers.js";
7
4
  import { findDataset } from "../../selectors/datasets.js";
8
5
  import { findSemanticScale } from "../../selectors/scales.js";
@@ -144,20 +141,19 @@ function resolveAppearance(args) {
144
141
  });
145
142
  }
146
143
 
147
- export const createErrorBarCap = action(
144
+ export const createErrorBarCap = /* @__PURE__ */ closedAction(
148
145
  {
149
146
  op: "createErrorBarCap",
150
147
  description: "Create one fixed-pixel error-bar cap."
151
- },
152
- function (args = {}) {
153
- validateKeys(args, [
148
+ }, [
154
149
  "id", "data", "orientation", "positionField", "positionFieldType",
155
150
  "intervalField", "coordinate", "positionScale", "intervalScale", "positionTemporalUnit",
156
151
  "offsetChannel", "offsetField", "offsetFieldType", "offsetScale",
157
152
  "offsetPaddingInner", "offsetPaddingOuter",
158
153
  "capSize", "stroke", "strokeWidth", "strokeDash", "opacity",
159
154
  ...STROKE_STYLE_PROPERTIES
160
- ], "createErrorBarCap");
155
+ ],
156
+ function (args = {}) {
161
157
  if (!["vertical", "horizontal"].includes(args.orientation)) {
162
158
  throw new Error(`Unsupported error-bar orientation "${args.orientation}".`);
163
159
  }
@@ -256,13 +252,12 @@ function intervalArgs(resolved, field) {
256
252
  };
257
253
  }
258
254
 
259
- export const createErrorBar = action(
255
+ export const createErrorBar = /* @__PURE__ */ closedAction(
260
256
  {
261
257
  op: "createErrorBar",
262
258
  description: "Create a statistical or explicit vertical or horizontal interval."
263
- },
259
+ }, OPTIONS,
264
260
  function (args = {}) {
265
- validateKeys(args, OPTIONS, "createErrorBar");
266
261
  const resolved = resolveErrorBar(this, args);
267
262
  const appearance = resolveAppearance(args);
268
263
  let next = createResolvedIntervalData(this, resolved);
@@ -1,5 +1,5 @@
1
- import { action } from "../../core/action.js";
2
- import { isPlainObject } from "../../core/immutable.js";
1
+ import { currentIntervalRoleArgs, resolveIntervalRoleScales, applyIntervalStatistics } from "../intervals/revision.js";
2
+ import { closedAction } from "../../core/action.js";
3
3
  import { validateUserId } from "../../core/identifiers.js";
4
4
  import {
5
5
  validateKeys,
@@ -14,10 +14,8 @@ import {
14
14
  validateRuleStrokeWidth
15
15
  } from "../../grammar/ruleAppearance.js";
16
16
  import { findLayer } from "../../selectors/layers.js";
17
- import { findSemanticScale } from "../../selectors/scales.js";
18
17
  import { resolveIntervalComposite } from "../intervals/resolve.js";
19
18
  import {
20
- resolveDistributionScalePlan,
21
19
  setCartesianPosition,
22
20
  updateDistributionPositions
23
21
  } from "../distributions/revision.js";
@@ -46,7 +44,6 @@ export const ERROR_BAR_APPEARANCE_OPTIONS = Object.freeze([
46
44
  "caps", "capSize", "stroke", "strokeWidth", "strokeDash", "opacity",
47
45
  ...STROKE_STYLE_PROPERTIES
48
46
  ]);
49
- const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "method", "level"]);
50
47
  const EDIT_POLICY = Object.freeze({
51
48
  operation: "editErrorBar",
52
49
  resourceLabel: "error-bar",
@@ -115,45 +112,8 @@ function resolveOwner(program, requested) {
115
112
  });
116
113
  }
117
114
 
118
- function currentRoleArgs(program, owner, current) {
119
- const transform = findIntervalTransform(program, current.data);
120
- const intervalChannel = current.orientation === "vertical" ? "y" : "x";
121
- const positionChannel = intervalChannel === "x" ? "y" : "x";
122
- const position = {
123
- field: current.positionField,
124
- fieldType: current.positionFieldType,
125
- ...(current.positionTemporalUnit === undefined
126
- ? {}
127
- : { temporalUnit: current.positionTemporalUnit }),
128
- scale: { id: current.positionScale }
129
- };
130
- const interval = transform === undefined
131
- ? {
132
- center: current.centerField ?? owner.encoding[intervalChannel].title,
133
- lower: current.lowerField,
134
- upper: current.upperField,
135
- scale: { id: current.intervalScale }
136
- }
137
- : {
138
- field: transform.field,
139
- center: transform.center,
140
- extent: transform.extent,
141
- ...(transform.method === undefined ? {} : { method: transform.method }),
142
- ...(transform.level === undefined ? {} : { level: transform.level }),
143
- scale: { id: current.intervalScale }
144
- };
145
- return {
146
- source: current.source ?? transform?.source ?? current.data,
147
- x: positionChannel === "x" ? position : interval,
148
- y: positionChannel === "y" ? position : interval,
149
- groupBy: current.groupField ?? transform?.groupBy?.find(
150
- field => field !== current.positionField
151
- )
152
- };
153
- }
154
-
155
115
  function resolveRoleCandidate(program, owner, current, args) {
156
- const previous = currentRoleArgs(program, owner, current);
116
+ const previous = currentIntervalRoleArgs(program, owner, current, true);
157
117
  const full = {
158
118
  data: Object.hasOwn(args, "data") ? args.data : previous.source,
159
119
  x: Object.hasOwn(args, "x") ? args.x : previous.x,
@@ -170,65 +130,8 @@ function resolveRoleCandidate(program, owner, current, args) {
170
130
  generatedFields: findIntervalTransform(program, current.data)?.as
171
131
  };
172
132
  let resolved = resolveIntervalComposite(program, full, policy);
173
- if (Object.hasOwn(args, "statistics")) {
174
- if (!isPlainObject(args.statistics)) {
175
- throw new TypeError("editErrorBar statistics must be a plain object.");
176
- }
177
- validateKeys(args.statistics, STATISTICS_OPTIONS, "editErrorBar statistics");
178
- if (!STATISTICS_OPTIONS.some(key => Object.hasOwn(args.statistics, key))) {
179
- throw new Error(
180
- "editErrorBar statistics requires center, extent, method, or level."
181
- );
182
- }
183
- full[resolved.interval.channel] = {
184
- ...full[resolved.interval.channel],
185
- ...args.statistics
186
- };
187
- resolved = resolveIntervalComposite(program, full, policy);
188
- }
189
- const positionRole = {
190
- field: resolved.position.field,
191
- fieldType: resolved.position.fieldType,
192
- scale: resolved.position.scale
193
- };
194
- const intervalTitle = resolved.interval.mode === "statistical"
195
- ? resolved.interval.field
196
- : resolved.interval.title;
197
- const intervalRole = {
198
- field: intervalTitle,
199
- fieldType: "quantitative",
200
- scale: resolved.interval.scale
201
- };
202
- const x = resolved.position.channel === "x" ? positionRole : intervalRole;
203
- const y = resolved.position.channel === "y" ? positionRole : intervalRole;
204
- const plan = (channel, role) => {
205
- const fallback = role === intervalRole
206
- ? current.intervalScale
207
- : current.positionScale;
208
- const stored = findSemanticScale(program, fallback);
209
- const compatible = role.fieldType === "temporal"
210
- ? stored?.type === "time"
211
- : ["nominal", "ordinal"].includes(role.fieldType)
212
- ? ["band", "point"].includes(stored?.type)
213
- : ["linear", "log", "pow", "sqrt", "symlog"].includes(stored?.type);
214
- const defaultType = role.fieldType === "temporal"
215
- ? "time"
216
- : ["nominal", "ordinal"].includes(role.fieldType) ? "band" : "linear";
217
- const requested = role.scale.type !== undefined || compatible
218
- ? role.scale
219
- : { ...role.scale, type: defaultType };
220
- return resolveDistributionScalePlan(program, {
221
- channel,
222
- fieldType: role.fieldType,
223
- requested,
224
- fallback,
225
- defaults: role === intervalRole
226
- ? EDIT_POLICY.intervalScaleDefaults
227
- : EDIT_POLICY.scaleDefaults(role.fieldType)
228
- });
229
- };
230
- const xScale = plan("x", x);
231
- const yScale = plan("y", y);
133
+ resolved = applyIntervalStatistics(program, full, policy, args, resolved);
134
+ const roles = resolveIntervalRoleScales(program, resolved, current, EDIT_POLICY, owner, previous);
232
135
  const expectedOffset = `${resolved.position.channel}Offset`;
233
136
  const incompatibleOffset = expectedOffset === "xOffset" ? "yOffset" : "xOffset";
234
137
  if (Object.hasOwn(args, incompatibleOffset)) {
@@ -270,19 +173,7 @@ function resolveRoleCandidate(program, owner, current, args) {
270
173
  return {
271
174
  ...resolved,
272
175
  offset: normalizedOffset,
273
- x: { ...x, scale: xScale.id },
274
- y: { ...y, scale: yScale.id },
275
- xScale,
276
- yScale,
277
- category: resolved.position.field,
278
- categoryType: resolved.position.fieldType,
279
- measure: intervalTitle,
280
- previous: {
281
- x: { field: previous.x.center ?? previous.x.field,
282
- fieldType: owner.encoding.x.fieldType, scale: owner.encoding.x.scale },
283
- y: { field: previous.y.center ?? previous.y.field,
284
- fieldType: owner.encoding.y.fieldType, scale: owner.encoding.y.scale }
285
- }
176
+ ...roles
286
177
  };
287
178
  }
288
179
 
@@ -460,13 +351,12 @@ function updateErrorBarRoles(program, owner, current, candidate, dataId, capIds)
460
351
  return next.rematerializeErrorBar({ id: owner.id });
461
352
  }
462
353
 
463
- export const rematerializeErrorBar = action(
354
+ export const rematerializeErrorBar = /* @__PURE__ */ closedAction(
464
355
  {
465
356
  op: "rematerializeErrorBar",
466
357
  description: "Reconcile one error bar and its owned caps."
467
- },
358
+ }, REMATERIALIZE_OPTIONS,
468
359
  function (args = {}) {
469
- validateKeys(args, REMATERIALIZE_OPTIONS, "rematerializeErrorBar");
470
360
  const id = validateUserId(args.id, "Error-bar id");
471
361
  const layer = findLayer(this, id);
472
362
  const config = this.markConfigs[id]?.errorBar;
@@ -494,13 +384,12 @@ export const rematerializeErrorBar = action(
494
384
  }
495
385
  );
496
386
 
497
- export const editErrorBar = action(
387
+ export const editErrorBar = /* @__PURE__ */ closedAction(
498
388
  {
499
389
  op: "editErrorBar",
500
390
  description: "Revise one error bar's roles, statistics, and owned caps."
501
- },
391
+ }, ERROR_BAR_EDIT_OPTIONS,
502
392
  function (args = {}) {
503
- validateKeys(args, ERROR_BAR_EDIT_OPTIONS, "editErrorBar");
504
393
  const editable = ERROR_BAR_EDIT_OPTIONS.filter(option => option !== "target");
505
394
  if (!editable.some(option => Object.hasOwn(args, option))) {
506
395
  throw new Error("editErrorBar requires at least one change.");
@@ -1,4 +1,5 @@
1
- import { action } from "../../core/action.js";
1
+ import { action, closedAction } from "../../core/action.js";
2
+ import { registerFacetDataRevision, reviseUnitData } from "../data/revise.js";
2
3
  import { freezeOwned, isPlainObject } from "../../core/immutable.js";
3
4
  import { validateUserId } from "../../core/identifiers.js";
4
5
  import {
@@ -183,13 +184,12 @@ function rederiveFacet(program, { scales, guides }) {
183
184
  }, compositionSpec.children);
184
185
  }
185
186
 
186
- export const facet = action(
187
+ export const facet = /* @__PURE__ */ closedAction(
187
188
  {
188
189
  op: "facet",
189
190
  description: "Repeat one direct-source chart by field value."
190
- },
191
+ }, FACET_OPTIONS,
191
192
  function (args = {}) {
192
- validateOptionObject(args, FACET_OPTIONS, "facet");
193
193
  const guides = normalizeGuides(args.guides);
194
194
  const definition = resolveFacetDefinition(this.semanticSpec, args);
195
195
  if (definition.family !== "cartesian" && guides.axes === "outer") {
@@ -428,13 +428,12 @@ function deriveRepeatChildren(base, definition, scales, closeInheritedAction) {
428
428
  );
429
429
  }
430
430
 
431
- export const repeatCharts = action(
431
+ export const repeatCharts = /* @__PURE__ */ closedAction(
432
432
  {
433
433
  op: "repeatCharts",
434
434
  description: "Repeat one direct chart across an ordered field-role list."
435
- },
435
+ }, REPEAT_OPTIONS,
436
436
  function (args = {}) {
437
- validateOptionObject(args, REPEAT_OPTIONS, "repeatCharts");
438
437
  const guides = normalizeGuides(args.guides);
439
438
  if (guides.axes === "outer") {
440
439
  throw new Error("repeatCharts does not promote axes across different repeated fields.");
@@ -495,13 +494,12 @@ export const repeatCharts = action(
495
494
  }
496
495
  );
497
496
 
498
- export const facetGrid = action(
497
+ export const facetGrid = /* @__PURE__ */ closedAction(
499
498
  {
500
499
  op: "facetGrid",
501
500
  description: "Repeat one direct-source Cartesian chart across a row and column field grid."
502
- },
501
+ }, FACET_GRID_OPTIONS,
503
502
  function (args = {}) {
504
- validateOptionObject(args, FACET_GRID_OPTIONS, "facetGrid");
505
503
  const guides = normalizeGuides(args.guides);
506
504
  const definition = resolveFacetGridDefinition(this.semanticSpec, args);
507
505
  if (definition.family !== "cartesian" && guides.axes === "outer") {
@@ -565,7 +563,7 @@ export const facetGrid = action(
565
563
  }
566
564
  );
567
565
 
568
- export const editFacetHeaders = action(
566
+ export const editFacetHeaders = /* @__PURE__ */ action(
569
567
  {
570
568
  op: "editFacetHeaders",
571
569
  description: "Edit parent-owned facet header appearance.",
@@ -656,7 +654,7 @@ export const editFacetHeaders = action(
656
654
  }
657
655
  );
658
656
 
659
- export const editFacetScales = action(
657
+ export const editFacetScales = /* @__PURE__ */ action(
660
658
  {
661
659
  op: "editFacetScales",
662
660
  description: "Edit facet scale-resolution policies and rederive every cell.",
@@ -691,7 +689,7 @@ export const editFacetScales = action(
691
689
  }
692
690
  );
693
691
 
694
- export const editFacetGuides = action(
692
+ export const editFacetGuides = /* @__PURE__ */ action(
695
693
  {
696
694
  op: "editFacetGuides",
697
695
  description: "Edit facet guide ownership and rederive every cell.",
@@ -764,14 +762,13 @@ function adoptUnitState(program, actionOwner) {
764
762
  });
765
763
  }
766
764
 
767
- export const editFacetSource = action(
765
+ export const editFacetSource = /* @__PURE__ */ closedAction(
768
766
  {
769
767
  op: "editFacetSource",
770
768
  description: "Reapply one facet, grid, or repeat recipe to a revised complete unit program.",
771
769
  scope: "composition"
772
- },
770
+ }, SOURCE_EDIT_OPTIONS,
773
771
  function (args = {}) {
774
- validateOptionObject(args, SOURCE_EDIT_OPTIONS, "editFacetSource");
775
772
  requireFacetProgram(this, "editFacetSource");
776
773
  const current = this.compositionSpec;
777
774
  const facetConfig = this.materializationConfigs.facets?.[current.id];
@@ -838,6 +835,23 @@ export const editFacetSource = action(
838
835
  );
839
836
 
840
837
  export function registerFacetActions(ProgramClass) {
838
+ registerFacetDataRevision((program, args) => {
839
+ const { program: unit, plan } = reviseUnitData(facetUnitTemplate(program), args);
840
+ const current = program.compositionSpec;
841
+ // Adopt an already-materialized unit revision into the retained parent.
842
+ const revised = new program.constructor({
843
+ ...unit,
844
+ graphicSpec: program.graphicSpec,
845
+ children: program.children,
846
+ materializationConfigs: { ...unit.materializationConfigs,
847
+ facets: program.materializationConfigs.facets,
848
+ ...(program.titleConfig === undefined ? {} : { title: program.titleConfig }) },
849
+ actionSequence: unit._actionSequence,
850
+ compositionSpec: { ...current, facet: { ...current.facet,
851
+ data: plan.replacements.get(current.facet.data) ?? current.facet.data } }
852
+ });
853
+ return rederiveFacet(revised, current.facet);
854
+ });
841
855
  ProgramClass.prototype.replayDerivedData = replayDerivedData;
842
856
  ProgramClass.prototype.composeFacetGuides = composeFacetGuides;
843
857
  ProgramClass.prototype.facet = facet;