ggaction 0.0.21 → 0.0.23

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 (265) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +83 -90
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/package.json +11 -4
  8. package/src/BasicChartProgram.js +1 -1
  9. package/src/accessibility.js +1 -0
  10. package/src/actions/boxPlots/components.js +1 -96
  11. package/src/actions/boxPlots/create.js +6 -1
  12. package/src/actions/boxPlots/edit.js +8 -0
  13. package/src/actions/boxPlots/materialize.js +3 -0
  14. package/src/actions/boxPlots/options.js +1 -1
  15. package/src/actions/canvas/actions.js +2 -1
  16. package/src/actions/canvas/index.js +3 -0
  17. package/src/actions/canvas/plotBorder.js +2 -1
  18. package/src/actions/canvas/plotOrder.js +14 -0
  19. package/src/actions/charts/area.js +1 -1
  20. package/src/actions/charts/bar.js +4 -4
  21. package/src/actions/charts/density.js +1 -1
  22. package/src/actions/charts/ecdf.js +1 -1
  23. package/src/actions/charts/endpoints.js +54 -5
  24. package/src/actions/charts/heatmap.js +1 -1
  25. package/src/actions/charts/histogram.js +1 -1
  26. package/src/actions/charts/horizon.js +1 -1
  27. package/src/actions/charts/index.js +2 -1
  28. package/src/actions/charts/interval-regression.js +3 -3
  29. package/src/actions/charts/line.js +1 -1
  30. package/src/actions/charts/radar.js +63 -3
  31. package/src/actions/charts/raincloud.js +37 -6
  32. package/src/actions/charts/rug-strip.js +2 -2
  33. package/src/actions/charts/scatter.js +3 -3
  34. package/src/actions/charts/text.js +1 -1
  35. package/src/actions/composition/actions.js +10 -3
  36. package/src/actions/coordinates/actions.js +2 -0
  37. package/src/actions/data/basic.js +6 -0
  38. package/src/actions/data/derived.js +6 -2
  39. package/src/actions/data/edit.js +22 -36
  40. package/src/actions/data/filter.js +1 -376
  41. package/src/actions/data/flatten.js +19 -0
  42. package/src/actions/data/index.js +6 -0
  43. package/src/actions/data/lookup.js +50 -0
  44. package/src/actions/data/raincloudFilter.js +56 -0
  45. package/src/actions/data/shared.js +10 -0
  46. package/src/actions/data/timeUnit.js +1 -1
  47. package/src/actions/distributions/revision.js +3 -1
  48. package/src/actions/encodings/angle.js +2 -1
  49. package/src/actions/encodings/appearance.js +38 -13
  50. package/src/actions/encodings/channels.js +14 -3
  51. package/src/actions/encodings/color/continuous.js +26 -10
  52. package/src/actions/encodings/color/index.js +50 -20
  53. package/src/actions/encodings/color/policy.js +57 -4
  54. package/src/actions/encodings/parallel.js +4 -0
  55. package/src/actions/encodings/position/policies/bar.js +10 -6
  56. package/src/actions/encodings/position/policies/line.js +1 -152
  57. package/src/actions/encodings/position/resolve.js +4 -1
  58. package/src/actions/encodings/ranged.js +46 -0
  59. package/src/actions/encodings/remove.js +1 -1
  60. package/src/actions/encodings/ruleAppearance.js +7 -6
  61. package/src/actions/encodings/shared.js +14 -2
  62. package/src/actions/encodings/stroke.js +20 -14
  63. package/src/actions/encodings/strokeDash.js +9 -11
  64. package/src/actions/encodings/text.js +30 -2
  65. package/src/actions/errorBars/edit.js +24 -4
  66. package/src/actions/facets/actions.js +1 -793
  67. package/src/actions/facets/derive.js +34 -5
  68. package/src/actions/facets/replay.js +1 -38
  69. package/src/actions/gradientPlots/components.js +21 -9
  70. package/src/actions/gradientPlots/create.js +4 -2
  71. package/src/actions/gradientPlots/edit.js +18 -1
  72. package/src/actions/gradientPlots/materialize.js +35 -36
  73. package/src/actions/gradientPlots/options.js +1 -1
  74. package/src/actions/gradientPlots/paint.js +13 -7
  75. package/src/actions/guides/applicability.js +2 -0
  76. package/src/actions/guides/axes/axes.js +39 -0
  77. package/src/actions/guides/axes/axis.js +9 -5
  78. package/src/actions/guides/axes/edit.js +11 -8
  79. package/src/actions/guides/axes/labels.js +24 -8
  80. package/src/actions/guides/axes/parallel/policy.js +3 -5
  81. package/src/actions/guides/axes/parallel/resolve.js +4 -3
  82. package/src/actions/guides/axes/policy.js +22 -0
  83. package/src/actions/guides/axes/tickGroups.js +4 -2
  84. package/src/actions/guides/axes/ticks.js +22 -4
  85. package/src/actions/guides/axes/titles.js +19 -6
  86. package/src/actions/guides/facadeAxes.js +3 -0
  87. package/src/actions/guides/grids/grid.js +3 -1
  88. package/src/actions/guides/grids/resolve.js +7 -11
  89. package/src/actions/guides/guides.js +21 -1
  90. package/src/actions/guides/legends/categorical/actions.js +1 -515
  91. package/src/actions/guides/legends/categorical/components.js +1 -4
  92. package/src/actions/guides/legends/categorical/layout.js +5 -2
  93. package/src/actions/guides/legends/categorical/options.js +4 -0
  94. package/src/actions/guides/legends/categorical/resolve.js +8 -3
  95. package/src/actions/guides/legends/categorical/symbols.js +3 -3
  96. package/src/actions/guides/legends/colorSize.js +2 -0
  97. package/src/actions/guides/legends/continuous/common.js +1 -1
  98. package/src/actions/guides/legends/continuous/gradient.js +1 -360
  99. package/src/actions/guides/legends/continuous/interval.js +1 -1
  100. package/src/actions/guides/legends/continuous/opacity.js +15 -1
  101. package/src/actions/guides/legends/edit.js +1 -1
  102. package/src/actions/guides/legends/focused.js +1 -1
  103. package/src/actions/guides/legends/lane.js +1 -1
  104. package/src/actions/guides/legends/sampling.js +1 -140
  105. package/src/actions/guides/legends/strokeWidth.js +3 -2
  106. package/src/actions/guides/legends/transition.js +19 -2
  107. package/src/actions/guides/polar/axes/facade.js +5 -4
  108. package/src/actions/guides/polar/axes/labels.js +5 -4
  109. package/src/actions/guides/polar/axes/titles.js +5 -4
  110. package/src/actions/guides/polar/resolve.js +2 -1
  111. package/src/actions/guides/tickValues.js +7 -7
  112. package/src/actions/marks/arc/actions.js +5 -7
  113. package/src/actions/marks/area/actions.js +9 -3
  114. package/src/actions/marks/bar/create.js +4 -2
  115. package/src/actions/marks/bar/edit.js +9 -6
  116. package/src/actions/marks/bar/materialize.js +5 -3
  117. package/src/actions/marks/dependentReferences.js +87 -0
  118. package/src/actions/marks/index.js +2 -0
  119. package/src/actions/marks/lifecycle.js +2 -1
  120. package/src/actions/marks/line/actions.js +24 -6
  121. package/src/actions/marks/line/materialize.js +7 -3
  122. package/src/actions/marks/point/create.js +5 -4
  123. package/src/actions/marks/point/edit.js +6 -3
  124. package/src/actions/marks/point/jitter.js +6 -6
  125. package/src/actions/marks/point/materialize.js +36 -39
  126. package/src/actions/marks/rect/actions.js +16 -4
  127. package/src/actions/marks/references.js +183 -35
  128. package/src/actions/marks/rule/actions.js +20 -9
  129. package/src/actions/marks/shared.js +2 -1
  130. package/src/actions/marks/text/actions.js +10 -3
  131. package/src/actions/marks/tick/actions.js +11 -2
  132. package/src/actions/primitives/editGraphics.js +28 -3
  133. package/src/actions/primitives/index.js +9 -0
  134. package/src/actions/primitives/semanticAction.js +13 -7
  135. package/src/actions/primitives/semanticValidation/layer.js +20 -0
  136. package/src/actions/primitives/semanticValidation/scale.js +8 -1
  137. package/src/actions/regression/create.js +6 -1
  138. package/src/actions/regression/edit.js +10 -1
  139. package/src/actions/scales/channels.js +1 -1
  140. package/src/actions/scales/consumers/common.js +19 -1
  141. package/src/actions/scales/consumers/families.js +2 -2
  142. package/src/actions/scales/consumers/seriesLayout.js +4 -3
  143. package/src/actions/scales/edit.js +11 -0
  144. package/src/actions/scales/editPolicy.js +5 -3
  145. package/src/actions/scales/preview.js +2 -1
  146. package/src/actions/scales/quantitativeColor.js +2 -4
  147. package/src/actions/selection/actions.js +10 -1
  148. package/src/actions/theme/reconcile.js +37 -9
  149. package/src/core/compositionState.js +26 -7
  150. package/src/core/datasetReferences.js +17 -0
  151. package/src/core/facetAxisPolicy.js +30 -0
  152. package/src/core/immutable.js +1 -0
  153. package/src/core/resourceReferences.js +5 -3
  154. package/src/core/validation.js +4 -0
  155. package/src/core/vocabulary.js +24 -2
  156. package/src/grammar/aggregate.js +1 -0
  157. package/src/grammar/arcs.js +1 -1
  158. package/src/grammar/areaSeries.js +1 -1
  159. package/src/grammar/axisTextPlacement.js +1 -1
  160. package/src/grammar/bars/policy.js +2 -2
  161. package/src/grammar/curveCommands.js +35 -2
  162. package/src/grammar/datasetDependencies.js +97 -0
  163. package/src/grammar/datasetSchema.js +32 -2
  164. package/src/grammar/facetHeaders.js +1 -0
  165. package/src/grammar/facets/dependencies.js +6 -2
  166. package/src/grammar/facets/guides.js +7 -5
  167. package/src/grammar/facets/legendIntent.js +6 -2
  168. package/src/grammar/facets/scales.js +27 -7
  169. package/src/grammar/flatten.js +85 -0
  170. package/src/grammar/histogram.js +13 -1
  171. package/src/grammar/horizon.js +1 -494
  172. package/src/grammar/jitter.js +2 -2
  173. package/src/grammar/legendSpacing.js +45 -0
  174. package/src/grammar/lineMissing.js +90 -0
  175. package/src/grammar/lineSeries.js +1 -376
  176. package/src/grammar/lookup.js +97 -0
  177. package/src/grammar/markFilter.js +4 -2
  178. package/src/grammar/markLabels.js +8 -3
  179. package/src/grammar/numeric.js +1 -1
  180. package/src/grammar/pathCommands.js +1 -1
  181. package/src/grammar/pathSeries.js +1 -109
  182. package/src/grammar/pointPolygon.js +81 -0
  183. package/src/grammar/pointShapes.js +15 -15
  184. package/src/grammar/rects.js +1 -1
  185. package/src/grammar/regression/derive.js +1 -195
  186. package/src/grammar/regression/parameters.js +7 -0
  187. package/src/grammar/scales/appearance.js +1 -1
  188. package/src/grammar/scales/color.js +1 -1
  189. package/src/grammar/scales/colorConsumers.js +1 -63
  190. package/src/grammar/scales/continuous.js +58 -1
  191. package/src/grammar/scales/definition.js +1 -1
  192. package/src/grammar/scales/ordinal.js +5 -2
  193. package/src/grammar/scales/policies.js +3 -3
  194. package/src/grammar/schemas/graphicBounds.js +40 -12
  195. package/src/grammar/schemas/graphicClip.js +23 -0
  196. package/src/grammar/schemas/graphicTree.js +16 -0
  197. package/src/grammar/statisticalReference.js +22 -0
  198. package/src/grammar/text.js +65 -1
  199. package/src/grammar/ticks.js +92 -1
  200. package/src/grammar/timeUnit.js +9 -3
  201. package/src/grammar/transformTopology.js +3 -1
  202. package/src/grammar/transforms.js +24 -0
  203. package/src/inspection.js +1 -495
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -1
  206. package/src/layout/facetContentFlow.js +101 -0
  207. package/src/layout/facetHeaderTitles.js +1 -0
  208. package/src/layout/facetTopLane.js +29 -0
  209. package/src/layout/facets.js +1 -1
  210. package/src/layout/guideCollisions.js +9 -3
  211. package/src/layout/legendGroup.js +1 -1
  212. package/src/layout/legendItems.js +26 -10
  213. package/src/layout/legendSpacing.js +66 -0
  214. package/src/layout/plotDrawOrder.js +17 -0
  215. package/src/materialization/bars/histogram.js +8 -2
  216. package/src/materialization/bars/resolve.js +1 -1
  217. package/src/materialization/composition.js +1 -0
  218. package/src/materialization/compositionSnapshot.js +22 -2
  219. package/src/materialization/dataProvenance.js +1 -1
  220. package/src/materialization/facetContentBounds.js +96 -0
  221. package/src/materialization/facetGuides/layout.js +1 -1
  222. package/src/materialization/facetGuides/placement.js +39 -11
  223. package/src/materialization/facetGuides/preparation.js +43 -24
  224. package/src/materialization/facetHeaders.js +1 -1
  225. package/src/materialization/facetTopLane.js +25 -0
  226. package/src/materialization/facets.js +1 -1
  227. package/src/materialization/graphicHierarchy.js +30 -1
  228. package/src/materialization/graphicTranslation.js +2 -0
  229. package/src/materialization/guides/layout.js +1 -1
  230. package/src/materialization/guides/resources.js +1 -1
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/markClip.js +26 -0
  233. package/src/materialization/marks/capabilities.js +3 -2
  234. package/src/materialization/marks/grouping.js +7 -5
  235. package/src/materialization/marks/index.js +4 -2
  236. package/src/materialization/marks/policies.js +4 -0
  237. package/src/materialization/plotDrawOrder.js +44 -0
  238. package/src/materialization/rect.js +11 -3
  239. package/src/materialization/scaleGuideDependencies.js +13 -5
  240. package/src/materialization/selection/items/common.js +1 -1
  241. package/src/materialization/selection/items/path.js +1 -0
  242. package/src/materialization/selection/policies/index.js +2 -1
  243. package/src/materialization/text.js +10 -5
  244. package/src/mcp/cli.js +0 -0
  245. package/src/persistence/validation.js +114 -18
  246. package/src/persistence.js +4 -2
  247. package/src/renderers/canvas/index.js +16 -0
  248. package/src/renderers/canvas/isolated.js +11 -1
  249. package/src/renderers/canvas/native.js +7 -0
  250. package/src/renderers/canvas/scene.js +66 -0
  251. package/src/renderers/canvas/text.js +3 -2
  252. package/src/renderers/svg.js +1 -617
  253. package/src/renderers/svgSerialization.js +643 -0
  254. package/src/renderers/transitionScene.js +67 -0
  255. package/src/scene.js +89 -0
  256. package/src/selectors/axes.js +1 -1
  257. package/src/selectors/datasets.js +1 -0
  258. package/src/selectors/grids.js +9 -0
  259. package/src/selectors/markClip.js +28 -0
  260. package/src/selectors/raincloud.js +7 -0
  261. package/src/version.js +1 -1
  262. package/types/basic.d.ts +7 -0
  263. package/types/index.d.ts +8 -0
  264. package/types/program.d.ts +286 -70
  265. package/types/scene.d.ts +35 -0
@@ -1,4 +1,5 @@
1
- import { validateGraphicItemLimit } from "../../../core/validation.js";
1
+ import { materializeMarkClip } from "../../../materialization/markClip.js";
2
+ import { validateGraphicItemLimit, validateWorkLimit } from "../../../core/validation.js";
2
3
  import { editGraphicProperties } from "../../primitives/graphicProperties.js";
3
4
  import { action } from "../../../core/action.js";
4
5
  import { validateUserId } from "../../../core/identifiers.js";
@@ -187,47 +188,40 @@ function applyPointJitter(program, {
187
188
  const stored = program.materializationConfigs.jitters?.[id];
188
189
  if (stored === undefined) return { program, x, y };
189
190
  const policy = normalizePointJitterPolicy(stored);
190
- const encoding = layer.encoding?.[policy.channel];
191
- const scale = program.resolvedScales[encoding?.scale];
192
- if (scale === undefined) {
193
- throw new Error(
194
- `Point jitter on "${id}" requires a resolved ${policy.channel} scale.`
195
- );
196
- }
197
191
  const bounds = resolveCoordinateBounds(program, layer.coordinate);
198
- const entries = resolveJitterEntries({
199
- policy,
200
- dataset,
201
- x,
202
- y,
203
- area,
204
- shapes,
205
- angles,
206
- config,
207
- graphic,
208
- existingChildren
209
- });
210
- const resolution = resolvePointJitter({
211
- target: id,
212
- policy,
213
- scale,
214
- entries,
215
- plotMinimum: policy.channel === "x" ? bounds.x : bounds.y,
216
- plotMaximum: policy.channel === "x"
217
- ? bounds.x + bounds.width
218
- : bounds.y + bounds.height
219
- });
220
- const values = [...(policy.channel === "x" ? x : y)];
221
- for (const item of resolution.items) {
222
- values[item.index] += item.finalOffset;
192
+ const channels = policy.channel === "both" ? ["x", "y"] : [policy.channel];
193
+ const resolutions = {}, positions = { x, y };
194
+ for (const channel of channels) {
195
+ const scale = program.resolvedScales[layer.encoding?.[channel]?.scale];
196
+ if (scale === undefined) {
197
+ throw new Error(`Point jitter on "${id}" requires a resolved ${channel} scale.`);
198
+ }
199
+ // Resolve from the same unjittered positions, using each axis in the stable
200
+ // hash. Both is exactly the independent X and Y assignments together.
201
+ const axisPolicy = { ...policy, channel };
202
+ const entries = resolveJitterEntries({
203
+ policy: axisPolicy, dataset, x, y, area, shapes, angles,
204
+ config, graphic, existingChildren
205
+ });
206
+ const resolution = resolvePointJitter({
207
+ target: id, policy: axisPolicy, scale, entries,
208
+ plotMinimum: channel === "x" ? bounds.x : bounds.y,
209
+ plotMaximum: channel === "x" ? bounds.x + bounds.width : bounds.y + bounds.height
210
+ });
211
+ resolutions[channel] = resolution;
212
+ positions[channel] = [...positions[channel]];
213
+ for (const item of resolution.items) positions[channel][item.index] += item.finalOffset;
223
214
  }
215
+ const resolved = policy.channel === "both"
216
+ ? { algorithm: policy.algorithm, itemCount: resolutions.x.itemCount, axes: resolutions }
217
+ : resolutions[policy.channel];
224
218
  return {
225
219
  program: program._withMaterializationConfig(["jitters", id], {
226
220
  ...policy,
227
- resolved: resolution
221
+ resolved
228
222
  }),
229
- x: policy.channel === "x" ? values : x,
230
- y: policy.channel === "y" ? values : y
223
+ x: positions.x,
224
+ y: positions.y
231
225
  };
232
226
  }
233
227
 
@@ -368,6 +362,9 @@ export const rematerializePointMark = /* @__PURE__ */ action(
368
362
  const strokeDetails = requestedStrokeDetails(config, "Point mark");
369
363
  const fill = mappedFill ?? config.fill ?? DEFAULT_POINT_FILL;
370
364
  const shapes = encodedShape ?? dataset.values.map(() => config.shape ?? "circle");
365
+ validateWorkLimit(shapes.reduce((sum, shape) => sum +
366
+ (typeof shape === "object" ? validatePointShape(shape).polygon.length + 1 : 0), 0),
367
+ "Point polygon materialization");
371
368
  const existingChildren = graphic.items ?? [];
372
369
  const constantShape = validatePointShape(config.shape ?? "circle");
373
370
  const requiresMixedCollection =
@@ -456,11 +453,11 @@ export const rematerializePointMark = /* @__PURE__ */ action(
456
453
  ...strokeDetails
457
454
  });
458
455
  });
459
- return jittered.program.editGraphics({
456
+ return materializeMarkClip(jittered.program.editGraphics({
460
457
  target: id,
461
458
  property: "items",
462
459
  value: items
463
- });
460
+ }), id);
464
461
  }
465
462
 
466
463
  let next = graphic.items.length === dataset.values.length
@@ -555,6 +552,6 @@ export const rematerializePointMark = /* @__PURE__ */ action(
555
552
  for (const [property, value] of Object.entries(strokeDetails)) {
556
553
  next = next.editGraphics({ target: id, property, value });
557
554
  }
558
- return next;
555
+ return materializeMarkClip(next, id);
559
556
  }
560
557
  );
@@ -1,3 +1,5 @@
1
+ import { materializeMarkClip } from "../../../materialization/markClip.js";
2
+ import { requestedMarkClip, validateMarkClipCoordinate } from "../../../selectors/markClip.js";
1
3
  import { action } from "../../../core/action.js";
2
4
  import { validateUserId } from "../../../core/identifiers.js";
3
5
  import {
@@ -33,7 +35,7 @@ import { applyItemMissingPolicy, validateItemMissing } from
33
35
  "../../../grammar/itemMissing.js";
34
36
 
35
37
  const STYLE_OPTIONS = Object.freeze([
36
- "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
38
+ "clip", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
37
39
  ...STROKE_STYLE_PROPERTIES
38
40
  ]);
39
41
  const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...STYLE_OPTIONS]);
@@ -55,6 +57,7 @@ const createRectMark = /* @__PURE__ */ action(
55
57
  { op: "createRectMark", description: "Create a semantic rectangular cell layer." },
56
58
  function (args = {}) {
57
59
  validateMarkOptions(args, CREATE_OPTIONS, "createRectMark");
60
+ requestedMarkClip(args);
58
61
  requestedRectStyleDetails(args, "createRectMark");
59
62
  const id = resolveMarkId(this, args.id, {
60
63
  defaultId: "rect",
@@ -109,6 +112,7 @@ const rematerializeRectMark = /* @__PURE__ */ action(
109
112
  const highlighted = rematerializeHighlightBaseline(this, {
110
113
  target: id,
111
114
  operation: "rematerializeRectMark",
115
+ scales: args.scales,
112
116
  resetProperty: "items",
113
117
  resetValue: []
114
118
  });
@@ -126,7 +130,7 @@ const rematerializeRectMark = /* @__PURE__ */ action(
126
130
  throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
127
131
  }
128
132
  if (this.markConfigs[id]?.gradientPlot?.materialized === true) {
129
- return this.materializeGradientPlotFill({ id });
133
+ return materializeMarkClip(this.materializeGradientPlotFill({ id, scales: args.scales }), id);
130
134
  }
131
135
  const sourceDataset = findDataset(this, layer.data);
132
136
  if (sourceDataset === undefined) {
@@ -146,12 +150,12 @@ const rematerializeRectMark = /* @__PURE__ */ action(
146
150
  next = next.rematerializeScale({ id: scale, marks: false });
147
151
  }
148
152
  }
149
- return replaceGraphicItems(
153
+ return materializeMarkClip(replaceGraphicItems(
150
154
  next,
151
155
  id,
152
156
  "rect",
153
157
  resolveRectGraphicItems(next, layer, dataset)
154
- );
158
+ ), id);
155
159
  }
156
160
  );
157
161
 
@@ -159,6 +163,7 @@ const editRectMark = /* @__PURE__ */ action(
159
163
  { op: "editRectMark", description: "Edit rectangular cell appearance." },
160
164
  function (args = {}) {
161
165
  validateMarkOptions(args, EDIT_OPTIONS, "editRectMark");
166
+ requestedMarkClip(args);
162
167
  requestedRectStyleDetails(args, "editRectMark");
163
168
  if (!["missing", ...STYLE_OPTIONS].some(option => Object.hasOwn(args, option))) {
164
169
  throw new Error("editRectMark requires at least one editable property.");
@@ -172,6 +177,12 @@ const editRectMark = /* @__PURE__ */ action(
172
177
  "editRectMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
173
178
  );
174
179
  }
180
+ validateMarkClipCoordinate(this, layer, args.clip ?? this.markConfigs[layer.id]?.clip);
181
+ for (const channel of ["opacity", "strokeWidth"]) {
182
+ if (Object.hasOwn(args, channel) && layer.encoding?.[channel] !== undefined) {
183
+ throw new Error(`editRectMark ${channel} conflicts with a field encoding; use ${channel === "opacity" ? "encodeOpacity" : "encodeStrokeWidth"} with value to replace it.`);
184
+ }
185
+ }
175
186
  const semantic = Object.hasOwn(args, "missing")
176
187
  ? this.editSemantic({
177
188
  property: `layer[${layer.id}].mark.missing`,
@@ -185,6 +196,7 @@ const editRectMark = /* @__PURE__ */ action(
185
196
  args,
186
197
  semantic.markConfigs[layer.id] ?? DEFAULT_RECT_MARK
187
198
  ),
199
+ ...requestedMarkClip(args),
188
200
  fillExplicit: Object.hasOwn(args, "fill")
189
201
  ? true
190
202
  : semantic.markConfigs[layer.id]?.fillExplicit ?? false
@@ -1,4 +1,5 @@
1
1
  import { isSourceOwnedText } from "../../grammar/text.js";
2
+ import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
2
3
  import { action, closedAction } from "../../core/action.js";
3
4
  import { isPlainObject } from "../../core/immutable.js";
4
5
  import { validateUserId } from "../../core/identifiers.js";
@@ -12,7 +13,7 @@ import {
12
13
  import { findLayer, resolveEligibleLayer } from "../../selectors/layers.js";
13
14
  import { resolveConsumerValuesForDataset } from
14
15
  "../scales/consumers/index.js";
15
- import { resolveMarkItems } from
16
+ import { findSelectionPolicy, resolveMarkItems } from
16
17
  "../../materialization/selection/policies/index.js";
17
18
  import { getMarkMaterializationPolicy } from
18
19
  "../../materialization/marks/policies.js";
@@ -22,11 +23,17 @@ import { withRematerializedDerivedDataset } from
22
23
  import {
23
24
  deriveStatisticalReferenceValues,
24
25
  normalizeReferenceField,
26
+ normalizeReferenceGroupBy,
25
27
  normalizeReferencePopulation,
26
28
  normalizeReferenceStatistics,
27
29
  normalizeStatisticalReferenceTransform,
28
- validateStatisticalReferenceTransform
30
+ validateStatisticalReferenceTransform,
31
+ partitionReferenceRows
29
32
  } from "../../grammar/statisticalReference.js";
33
+ import { scalarKey } from "../../grammar/transformKeys.js";
34
+ import { deriveLineSeries, resolveLineBins } from "../../grammar/lineSeries.js";
35
+ import { isAggregate } from "../../grammar/aggregate.js";
36
+ import { findSemanticScale } from "../../selectors/scales.js";
30
37
  import {
31
38
  resolveFacadeData,
32
39
  resolveFacadeId,
@@ -39,7 +46,7 @@ const COMMON_OPTIONS = Object.freeze([
39
46
  "id", "x", "y", "space", "source", "data", "coordinate", "temporalUnit"
40
47
  ]);
41
48
  const DYNAMIC_OPTIONS = Object.freeze([
42
- "id", "source", "axis", "population", "field", "statistic", "statistics"
49
+ "id", "source", "axis", "population", "field", "groupBy", "statistic", "statistics"
43
50
  ]);
44
51
  const LINE_STYLE = Object.freeze([
45
52
  "stroke", "strokeWidth", "strokeDash", "opacity", ...STROKE_STYLE_PROPERTIES
@@ -211,6 +218,12 @@ function requireStatisticalSource(program, config, referenceId) {
211
218
  );
212
219
  }
213
220
  const encoding = source.encoding?.[config.axis];
221
+ const sourceTransform = findDataset(program, source.data)?.transform?.[0];
222
+ if (sourceTransform?.type === "horizon" && config.axis === "y") {
223
+ throw new Error(
224
+ `Statistical reference "${referenceId}" cannot summarize Horizon folded Y coordinates. Use a quantitative X axis.`
225
+ );
226
+ }
214
227
  if (
215
228
  encoding?.fieldType !== "quantitative" ||
216
229
  typeof encoding.scale !== "string" ||
@@ -233,14 +246,14 @@ function requireStatisticalSource(program, config, referenceId) {
233
246
  return { source, encoding };
234
247
  }
235
248
 
236
- function resolvePopulationDataset(program, source, population, referenceId) {
249
+ function resolveBoundPopulation(program, source, config, referenceId) {
237
250
  let dataset = findDataset(program, source.data);
238
251
  if (dataset === undefined || !Array.isArray(dataset.values)) {
239
252
  throw new Error(
240
253
  `Statistical reference "${referenceId}" source requires materialized data.`
241
254
  );
242
255
  }
243
- if (population === "visibleItems") return dataset;
256
+ if (config.population === "visibleItems") return { dataset };
244
257
  while (
245
258
  dataset.transform?.length === 1 &&
246
259
  dataset.transform[0].type === "markFilter" &&
@@ -254,7 +267,58 @@ function resolvePopulationDataset(program, source, population, referenceId) {
254
267
  }
255
268
  dataset = parent;
256
269
  }
257
- return dataset;
270
+ const transform = dataset.transform?.length === 1 ? dataset.transform[0] : undefined;
271
+ const axisField = source.encoding[config.axis].field;
272
+ let field;
273
+ if (transform?.type === "density" && axisField === transform.as[0]) {
274
+ field = transform.field;
275
+ } else if (transform?.type === "ecdf" && axisField === transform.as.value) {
276
+ field = transform.field;
277
+ } else if (transform?.type === "boxSummary" && transform.method !== "precomputed" &&
278
+ Object.values(transform.as).includes(axisField)) {
279
+ field = transform.field;
280
+ } else if (transform?.type === "gradientProfile" &&
281
+ [GRADIENT_PROFILE_FIELDS.lower, GRADIENT_PROFILE_FIELDS.upper,
282
+ GRADIENT_PROFILE_FIELDS.center].includes(axisField)) {
283
+ field = transform.field;
284
+ } else if (transform?.type === "summary") {
285
+ const endpointOwner = Object.entries(program.markConfigs).some(([id, config]) =>
286
+ config.endpointPlot && (id === source.id ||
287
+ Object.values(config.endpointPlot.roles).includes(source.id)));
288
+ if (endpointOwner) field = transform.aggregates.find(aggregate => aggregate.as === axisField)?.field;
289
+ } else if (transform?.type === "horizon" && axisField === transform.as.x) {
290
+ field = transform.x.field;
291
+ }
292
+ if (field !== undefined) {
293
+ const observationField = field;
294
+ const parent = findDataset(program, dataset.source);
295
+ if (parent === undefined || !Array.isArray(parent.values)) {
296
+ throw new Error(`Statistical reference "${referenceId}" bound data is unavailable.`);
297
+ }
298
+ if (config.field.kind === "explicit") {
299
+ // Explicit output fields remain available; original observation fields
300
+ // resolve against the statistical owner's input rather than sampled rows.
301
+ if (!parent.values.every(row => Object.hasOwn(row, config.field.field))) return { dataset };
302
+ field = config.field.field;
303
+ }
304
+ const groupFields = transform.category === undefined
305
+ ? [...(Array.isArray(transform.groupBy) ? transform.groupBy
306
+ : transform.groupBy === undefined ? [] : [transform.groupBy]),
307
+ ...(transform.placement?.split?.field === undefined ? [] : [transform.placement.split.field])]
308
+ : [transform.category];
309
+ const groupKey = row => JSON.stringify(groupFields.map(key => row?.[key]));
310
+ const observedGroups = new Set(dataset.values.map(row =>
311
+ transform.type === "horizon" && groupFields.length
312
+ ? JSON.stringify([row[transform.as.group]]) : groupKey(row)));
313
+ return {
314
+ dataset: { ...parent, values: parent.values.filter(row =>
315
+ Number.isFinite(row?.[transform.field ?? observationField]) &&
316
+ (transform.type !== "horizon" || Number.isFinite(row?.[transform.y.field])) &&
317
+ observedGroups.has(groupKey(row))) },
318
+ field
319
+ };
320
+ }
321
+ return { dataset };
258
322
  }
259
323
 
260
324
  function strictFieldValues(rows, field, label) {
@@ -341,6 +405,29 @@ function resolvePopulationValues(
341
405
  `Statistical reference "${referenceId}" boundData`
342
406
  );
343
407
  }
408
+ const gradient = program.markConfigs[source.id]?.gradientPlot;
409
+ if (config.population === "boundData") {
410
+ const { field } = resolveBoundPopulation(program, source, config, referenceId);
411
+ if (field !== undefined) return strictFieldValues(dataset.values, field,
412
+ `Statistical reference "${referenceId}" boundData`);
413
+ if (source.mark.type === "line" && isAggregate(source.encoding?.y?.aggregate)) {
414
+ const x = source.encoding.x;
415
+ const bins = x.bin === undefined ? undefined : resolveLineBins(
416
+ dataset.values, source, findSemanticScale(program, x.scale)
417
+ );
418
+ const derived = deriveLineSeries(dataset.values, source, {
419
+ ...(bins === undefined ? {} : { xBinBoundaries: bins.boundaries }),
420
+ allowSingleton: true
421
+ });
422
+ return config.axis === "x" ? derived.xValues : derived.yValues;
423
+ }
424
+ }
425
+ if (gradient?.materialized &&
426
+ config.axis === (gradient.orientation === "vertical" ? "y" : "x")) {
427
+ return strictFieldValues(dataset.values, config.population === "boundData"
428
+ ? gradient.measure : GRADIENT_PROFILE_FIELDS.center,
429
+ `Statistical reference "${referenceId}" ${config.population}`);
430
+ }
344
431
  return resolveConsumerValuesForDataset(program, {
345
432
  layer: source,
346
433
  channel: config.axis,
@@ -348,7 +435,7 @@ function resolvePopulationValues(
348
435
  }, dataset);
349
436
  }
350
437
 
351
- function statisticalRows(program, referenceId, dataset) {
438
+ function statisticalRows(program, referenceId) {
352
439
  const config = program.markConfigs[referenceId]?.statisticalReference;
353
440
  const { source, encoding } = requireStatisticalSource(
354
441
  program,
@@ -369,27 +456,43 @@ function statisticalRows(program, referenceId, dataset) {
369
456
  config.population,
370
457
  `Statistical reference "${referenceId}" population`
371
458
  );
372
- const values = resolvePopulationValues(
373
- program,
374
- source,
375
- encoding,
376
- { ...config, population, statistics },
377
- dataset,
378
- referenceId
379
- );
380
- const resolved = deriveStatisticalReferenceValues(
381
- values,
382
- statistics,
383
- `Statistical reference "${referenceId}"`
384
- );
385
- if (resolved.length === 2 && resolved[0] > resolved[1]) {
459
+ const { dataset } = resolveBoundPopulation(program, source, { ...config, population }, referenceId);
460
+ const groupBy = normalizeReferenceGroupBy(config.groupBy);
461
+ if (population === "visibleItems" && findSelectionPolicy(source.mark?.type) === undefined) {
386
462
  throw new Error(
387
- `Statistical reference band "${referenceId}" lower statistic exceeds its upper statistic.`
463
+ `Statistical reference "${referenceId}" visibleItems population is unavailable for ${source.mark?.type} marks. Use boundData.`
388
464
  );
389
465
  }
390
- return resolved.length === 1
391
- ? [{ value: resolved[0] }]
392
- : [{ lower: resolved[0], upper: resolved[1] }];
466
+ let visibleGroups;
467
+ if (groupBy !== undefined && population === "visibleItems") {
468
+ if (SERIES_MARKS.has(source.mark?.type) || source.encoding?.parallel !== undefined) {
469
+ throw new Error(`Statistical reference "${referenceId}" visibleItems population is ambiguous for series marks.`);
470
+ }
471
+ visibleGroups = new Map();
472
+ for (const item of resolveMarkItems(program, source.id)) {
473
+ if (!Object.hasOwn(item.fields, groupBy)) {
474
+ throw new Error(`Statistical reference "${referenceId}" groupBy field "${groupBy}" is not scalar at a visible item. Use boundData.`);
475
+ }
476
+ const key = scalarKey(item.fields[groupBy], "Statistical reference groupBy");
477
+ if (!visibleGroups.has(key)) visibleGroups.set(key, []);
478
+ visibleGroups.get(key).push(item.fields);
479
+ }
480
+ }
481
+ const groups = partitionReferenceRows(dataset.values, groupBy)
482
+ .filter(group => !visibleGroups || visibleGroups.has(group.key));
483
+ if (!groups.length) throw new Error(`Statistical reference "${referenceId}" population must contain at least one value.`);
484
+ return groups.map(group => {
485
+ const values = visibleGroups && config.field.kind === "explicit"
486
+ ? strictFieldValues(visibleGroups.get(group.key), config.field.field, `Statistical reference "${referenceId}" visibleItems`)
487
+ : resolvePopulationValues(program, source, encoding,
488
+ { ...config, population, statistics }, { ...dataset, values: group.rows }, referenceId);
489
+ const resolved = deriveStatisticalReferenceValues(values, statistics, `Statistical reference "${referenceId}"`);
490
+ if (resolved.length === 2 && resolved[0] > resolved[1]) {
491
+ throw new Error(`Statistical reference band "${referenceId}" lower statistic exceeds its upper statistic.`);
492
+ }
493
+ return { ...(groupBy === undefined ? {} : { group: group.group }),
494
+ ...(resolved.length === 1 ? { value: resolved[0] } : { lower: resolved[0], upper: resolved[1] }) };
495
+ });
393
496
  }
394
497
 
395
498
  function replaceStatisticalDataset(program, id, source, transform, values) {
@@ -471,12 +574,12 @@ export const materializeStatisticalReferenceData = /* @__PURE__ */ closedAction(
471
574
  description: "Materialize one owned statistical reference value dataset."
472
575
  }, MATERIALIZE_OPTIONS,
473
576
  function (args = {}) {
474
- const { id, source, transform } = requireDerivedDataset(
577
+ const { id, transform } = requireDerivedDataset(
475
578
  this,
476
579
  args.id,
477
580
  "statisticalReference"
478
581
  );
479
- const values = statisticalRows(this, transform.target, source);
582
+ const values = statisticalRows(this, transform.target);
480
583
  return this.editSemantic({
481
584
  property: `dataset[${id}].values`,
482
585
  value: values
@@ -494,8 +597,8 @@ export const rematerializeStatisticalReference = /* @__PURE__ */ closedAction(
494
597
  const config = this.markConfigs[id]?.statisticalReference;
495
598
  const { source } = requireStatisticalSource(this, config, id);
496
599
  const population = normalizeReferencePopulation(config.population);
497
- const dataset = resolvePopulationDataset(this, source, population, id);
498
- const values = statisticalRows(this, id, dataset);
600
+ const { dataset } = resolveBoundPopulation(this, source, { ...config, population }, id);
601
+ const values = statisticalRows(this, id);
499
602
  const transform = normalizeStatisticalReferenceTransform({ target: id });
500
603
  let next = replaceStatisticalDataset(
501
604
  this,
@@ -511,15 +614,23 @@ export const rematerializeStatisticalReference = /* @__PURE__ */ closedAction(
511
614
  });
512
615
  }
513
616
  next = syncReferenceEncoding(next, id, source, config);
617
+ const appearanceScales = new Set(["stroke", "strokeWidth", "strokeDash", "opacity", "color"]
618
+ .map(channel => findLayer(next, id).encoding?.[channel]?.scale).filter(Boolean));
619
+ for (const scale of appearanceScales) {
620
+ next = next.rematerializeScale({ id: scale, marks: false, guides: false });
621
+ }
622
+ // References do not contribute to their source's positional scale domain.
623
+ // Re-entering that scale's mark plan here would rematerialize the source
624
+ // and its dependent reference recursively (notably gradient strips).
514
625
  next = config.statistics.length === 1
515
- ? next.rematerializeRuleMark({ id })
516
- : next.rematerializeRectMark({ id });
626
+ ? next.rematerializeRuleMark({ id, scales: false })
627
+ : next.rematerializeRectMark({ id, scales: false });
517
628
  return next._withContext(this.context);
518
629
  }
519
630
  );
520
631
 
521
632
  function isDynamicReference(args) {
522
- return ["axis", "population", "field", "statistic", "statistics"]
633
+ return ["axis", "population", "field", "groupBy", "statistic", "statistics"]
523
634
  .some(key => Object.hasOwn(args, key));
524
635
  }
525
636
 
@@ -558,18 +669,19 @@ function normalizeDynamicReference(program, args, band) {
558
669
  axis: args.axis,
559
670
  population: normalizeReferencePopulation(args.population, `${operation} population`),
560
671
  field: normalizeReferenceField(args.field, `${operation} field`),
672
+ ...(args.groupBy === undefined ? {} : { groupBy: normalizeReferenceGroupBy(args.groupBy) }),
561
673
  statistics,
562
674
  dataId: `${id}-statistical-reference-data`
563
675
  };
564
676
  const preview = program._withMarkConfig(id, { statisticalReference: config });
565
677
  const { source } = requireStatisticalSource(preview, config, id);
566
- const dataset = resolvePopulationDataset(
678
+ const { dataset } = resolveBoundPopulation(
567
679
  program,
568
680
  source,
569
- config.population,
681
+ config,
570
682
  id
571
683
  );
572
- statisticalRows(preview, id, dataset);
684
+ statisticalRows(preview, id);
573
685
  if (hasDataset(program, config.dataId) || hasDatasetOwner(program, config.dataId)) {
574
686
  throw new Error(`Dataset "${config.dataId}" already exists.`);
575
687
  }
@@ -654,3 +766,39 @@ export const createReferenceBand = /* @__PURE__ */ action(
654
766
  return createReference(this, args, true);
655
767
  }
656
768
  );
769
+
770
+
771
+ export const createStatisticalReference = /* @__PURE__ */ action(
772
+ {
773
+ op: "createStatisticalReference",
774
+ description: "Create a statistical reference line or central interval through one public action."
775
+ },
776
+ function (args = {}) {
777
+ const band = Object.hasOwn(args, "statistics");
778
+ validateFacadeOptions(args, ["id", "source", "axis", "population", "field", "groupBy",
779
+ ...(band ? ["statistics", ...BAND_STYLE] : ["statistic", ...LINE_STYLE])], "createStatisticalReference");
780
+ const id = resolveFacadeId(this, args.id, {
781
+ defaultId: "statisticalReference", operation: "createStatisticalReference"
782
+ });
783
+ // The existing owners retain statistical population, scale exclusion,
784
+ // source replay, persistence and removal semantics, with wrapped trace steps.
785
+ if (band) {
786
+ return this.createReferenceBand({ fill: "#6b7280", opacity: 0.18, ...args, id });
787
+ }
788
+ const next = this.createReferenceLine({ statistic: { op: "mean" }, stroke: "#222222",
789
+ strokeWidth: 2.5, strokeDash: "solid", ...args, id });
790
+ const color = findLayer(this, args.source)?.encoding?.color;
791
+ // Reuse the source scale: a fresh palette would assign different colors
792
+ // after filtering, domain sorting, or a custom range.
793
+ if (args.stroke === undefined && args.groupBy !== undefined &&
794
+ color?.field === args.groupBy && color.scale !== undefined &&
795
+ color.aggregate === undefined) {
796
+ return next.encodeStroke({
797
+ target: id, field: "group", fieldType: color.fieldType,
798
+ ...(color.temporalUnit === undefined ? {} : { temporalUnit: color.temporalUnit }),
799
+ scale: { id: color.scale }
800
+ });
801
+ }
802
+ return next;
803
+ }
804
+ );
@@ -1,3 +1,4 @@
1
+ import { requestedMarkClip, validateMarkClipCoordinate } from "../../../selectors/markClip.js";
1
2
  import { action } from "../../../core/action.js";
2
3
  import { validateUserId } from "../../../core/identifiers.js";
3
4
  import { validatePositiveFinite } from "../../../core/validation.js";
@@ -5,7 +6,6 @@ import {
5
6
  mapContinuousScaleValues,
6
7
  mapOrdinalOffsetValues,
7
8
  mapOrdinalPositionValues,
8
- mapOrdinalValues,
9
9
  normalizeStrokeDashPattern
10
10
  } from "../../../grammar/scales/index.js";
11
11
  import { deriveRuleValues, resolveRuleMode } from "../../../grammar/rules.js";
@@ -43,12 +43,12 @@ import { applyItemMissingPolicy, validateItemMissing } from
43
43
  "../../../grammar/itemMissing.js";
44
44
 
45
45
  const APPEARANCE_OPTIONS = Object.freeze([
46
- "stroke", "strokeWidth", "strokeDash", "opacity",
46
+ "clip", "stroke", "strokeWidth", "strokeDash", "opacity",
47
47
  ...STROKE_STYLE_PROPERTIES
48
48
  ]);
49
49
  const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...APPEARANCE_OPTIONS]);
50
50
  const EDIT_OPTIONS = Object.freeze(["target", "missing", ...APPEARANCE_OPTIONS]);
51
- const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
51
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
52
52
  const SPAN_OPTIONS = Object.freeze(["id", "orientation", "size"]);
53
53
  const DEFAULT_RULE_CONFIG = Object.freeze({
54
54
  stroke: DEFAULT_COLORS.mark,
@@ -102,6 +102,7 @@ const createRuleMark = /* @__PURE__ */ action(
102
102
  },
103
103
  function (args = {}) {
104
104
  validateMarkOptions(args, CREATE_OPTIONS, "createRuleMark");
105
+ requestedMarkClip(args);
105
106
  const strokeDetails = requestedStrokeDetails(args, "createRuleMark");
106
107
  const id = resolveMarkId(this, args.id, {
107
108
  defaultId: "rule",
@@ -137,6 +138,7 @@ const createRuleMark = /* @__PURE__ */ action(
137
138
  })
138
139
  ._withMarkConfig(id, {
139
140
  ...DEFAULT_RULE_CONFIG,
141
+ ...requestedMarkClip(args),
140
142
  ...strokeDetails,
141
143
  ...(inherited === undefined
142
144
  ? {}
@@ -155,6 +157,7 @@ const editRuleMark = /* @__PURE__ */ action(
155
157
  { op: "editRuleMark", description: "Edit constant rule appearance through its encoding owners." },
156
158
  function (args = {}) {
157
159
  validateMarkOptions(args, EDIT_OPTIONS, "editRuleMark");
160
+ requestedMarkClip(args);
158
161
  const strokeDetails = requestedStrokeDetails(args, "editRuleMark");
159
162
  if (args.target !== undefined) validateUserId(args.target, "Rule mark id");
160
163
  const layer = resolveEligibleLayer(this, {
@@ -162,9 +165,10 @@ const editRuleMark = /* @__PURE__ */ action(
162
165
  predicate: candidate => candidate.mark?.type === "rule",
163
166
  label: "rule mark"
164
167
  });
168
+ validateMarkClipCoordinate(this, layer, args.clip ?? this.markConfigs[layer.id]?.clip);
165
169
  const appearance = planRuleAppearance(args, layer);
166
170
  if (appearance.length === 0 && Object.keys(strokeDetails).length === 0 &&
167
- !Object.hasOwn(args, "missing")) {
171
+ !Object.hasOwn(args, "missing") && !Object.hasOwn(args, "clip")) {
168
172
  throw new Error("editRuleMark requires missing or an appearance change.");
169
173
  }
170
174
  const semantic = Object.hasOwn(args, "missing")
@@ -175,6 +179,7 @@ const editRuleMark = /* @__PURE__ */ action(
175
179
  : this;
176
180
  const configured = semantic._withMarkConfig(layer.id, {
177
181
  ...semantic.markConfigs[layer.id],
182
+ ...requestedMarkClip(args),
178
183
  ...strokeDetails
179
184
  });
180
185
  const materialized = appearance.length === 0
@@ -191,10 +196,14 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
191
196
  },
192
197
  function (args = {}) {
193
198
  validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeRuleMark");
199
+ if (args.scales !== undefined && typeof args.scales !== "boolean") {
200
+ throw new TypeError("rematerializeRuleMark scales must be a boolean.");
201
+ }
194
202
  const id = validateUserId(args.id, "Rule mark id");
195
203
  const highlighted = rematerializeHighlightBaseline(this, {
196
204
  target: id,
197
205
  operation: "rematerializeRuleMark",
206
+ scales: args.scales,
198
207
  resetProperty: "length",
199
208
  resetValue: 0
200
209
  });
@@ -226,8 +235,10 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
226
235
  .filter(scale => scale !== undefined)
227
236
  )];
228
237
  let resolved = this;
229
- for (const scaleId of scaleIds) {
230
- resolved = resolved.rematerializeScale({ id: scaleId });
238
+ if (args.scales !== false) {
239
+ for (const scaleId of scaleIds) {
240
+ resolved = resolved.rematerializeScale({ id: scaleId });
241
+ }
231
242
  }
232
243
 
233
244
  const derived = deriveRuleValues(dataset.values, layer);
@@ -356,10 +367,10 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
356
367
  dashEncoding?.datum ?? resolvedConfig.strokeDash
357
368
  )
358
369
  )
359
- : mapOrdinalValues(
370
+ : mapScaleConsumerValues(
360
371
  derived.values.strokeDash,
361
- resolved.resolvedScales[dashEncoding.scale].domain,
362
- resolved.resolvedScales[dashEncoding.scale].range
372
+ resolved.resolvedScales[dashEncoding.scale],
373
+ "strokeDash"
363
374
  );
364
375
  const opacity = opacityEncoding?.scale === undefined
365
376
  ? resolvedConfig.opacity
@@ -1,3 +1,4 @@
1
+ import { materializeMarkClip } from "../../materialization/markClip.js";
1
2
  import { isSourceOwnedText } from "../../grammar/text.js";
2
3
  import {
3
4
  resolveOptionalUserId,
@@ -243,5 +244,5 @@ export function editMarkGraphic(program, target, properties) {
243
244
  for (const [property, value] of Object.entries(properties)) {
244
245
  program = program.editGraphics({ target, property, value });
245
246
  }
246
- return program;
247
+ return materializeMarkClip(program, target);
247
248
  }