ggaction 0.0.20 → 0.0.22

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 (254) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +3 -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/package.json +6 -2
  7. package/src/BasicChartProgram.js +1 -1
  8. package/src/accessibility.js +1 -0
  9. package/src/actions/boxPlots/components.js +1 -96
  10. package/src/actions/boxPlots/create.js +6 -1
  11. package/src/actions/boxPlots/edit.js +8 -0
  12. package/src/actions/boxPlots/materialize.js +3 -0
  13. package/src/actions/boxPlots/options.js +1 -1
  14. package/src/actions/canvas/actions.js +2 -1
  15. package/src/actions/canvas/index.js +3 -0
  16. package/src/actions/canvas/plotBorder.js +2 -1
  17. package/src/actions/canvas/plotOrder.js +14 -0
  18. package/src/actions/charts/area.js +1 -1
  19. package/src/actions/charts/bar.js +4 -4
  20. package/src/actions/charts/density.js +1 -1
  21. package/src/actions/charts/ecdf.js +1 -1
  22. package/src/actions/charts/endpoints.js +54 -5
  23. package/src/actions/charts/heatmap.js +1 -1
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +1 -1
  26. package/src/actions/charts/index.js +2 -1
  27. package/src/actions/charts/interval-regression.js +1 -1
  28. package/src/actions/charts/line.js +1 -1
  29. package/src/actions/charts/radar.js +63 -3
  30. package/src/actions/charts/raincloud.js +37 -6
  31. package/src/actions/charts/rug-strip.js +2 -2
  32. package/src/actions/charts/scatter.js +3 -3
  33. package/src/actions/charts/text.js +1 -1
  34. package/src/actions/composition/actions.js +10 -3
  35. package/src/actions/coordinates/actions.js +2 -0
  36. package/src/actions/data/basic.js +6 -0
  37. package/src/actions/data/derived.js +6 -2
  38. package/src/actions/data/edit.js +21 -35
  39. package/src/actions/data/filter.js +1 -376
  40. package/src/actions/data/flatten.js +19 -0
  41. package/src/actions/data/index.js +6 -0
  42. package/src/actions/data/lookup.js +50 -0
  43. package/src/actions/data/raincloudFilter.js +56 -0
  44. package/src/actions/data/shared.js +10 -0
  45. package/src/actions/distributions/revision.js +3 -1
  46. package/src/actions/encodings/angle.js +2 -1
  47. package/src/actions/encodings/appearance.js +38 -13
  48. package/src/actions/encodings/channels.js +14 -3
  49. package/src/actions/encodings/color/continuous.js +26 -10
  50. package/src/actions/encodings/color/index.js +50 -20
  51. package/src/actions/encodings/color/policy.js +57 -4
  52. package/src/actions/encodings/parallel.js +4 -0
  53. package/src/actions/encodings/position/policies/bar.js +10 -6
  54. package/src/actions/encodings/position/policies/line.js +1 -152
  55. package/src/actions/encodings/position/resolve.js +4 -1
  56. package/src/actions/encodings/ranged.js +46 -0
  57. package/src/actions/encodings/remove.js +1 -1
  58. package/src/actions/encodings/ruleAppearance.js +7 -6
  59. package/src/actions/encodings/shared.js +14 -2
  60. package/src/actions/encodings/stroke.js +20 -14
  61. package/src/actions/encodings/strokeDash.js +9 -11
  62. package/src/actions/encodings/text.js +30 -2
  63. package/src/actions/errorBars/edit.js +24 -4
  64. package/src/actions/facets/actions.js +1 -793
  65. package/src/actions/facets/derive.js +34 -5
  66. package/src/actions/facets/replay.js +1 -38
  67. package/src/actions/gradientPlots/components.js +21 -9
  68. package/src/actions/gradientPlots/create.js +4 -2
  69. package/src/actions/gradientPlots/edit.js +18 -1
  70. package/src/actions/gradientPlots/materialize.js +35 -36
  71. package/src/actions/gradientPlots/options.js +1 -1
  72. package/src/actions/gradientPlots/paint.js +13 -7
  73. package/src/actions/guides/applicability.js +2 -0
  74. package/src/actions/guides/axes/axes.js +39 -0
  75. package/src/actions/guides/axes/axis.js +9 -5
  76. package/src/actions/guides/axes/edit.js +11 -8
  77. package/src/actions/guides/axes/labels.js +24 -8
  78. package/src/actions/guides/axes/parallel/policy.js +3 -5
  79. package/src/actions/guides/axes/parallel/resolve.js +4 -3
  80. package/src/actions/guides/axes/policy.js +22 -0
  81. package/src/actions/guides/axes/tickGroups.js +4 -2
  82. package/src/actions/guides/axes/ticks.js +22 -4
  83. package/src/actions/guides/axes/titles.js +19 -6
  84. package/src/actions/guides/facadeAxes.js +3 -0
  85. package/src/actions/guides/grids/grid.js +3 -1
  86. package/src/actions/guides/grids/resolve.js +7 -11
  87. package/src/actions/guides/guides.js +21 -1
  88. package/src/actions/guides/legends/categorical/actions.js +1 -515
  89. package/src/actions/guides/legends/categorical/layout.js +5 -2
  90. package/src/actions/guides/legends/categorical/options.js +4 -0
  91. package/src/actions/guides/legends/categorical/resolve.js +8 -3
  92. package/src/actions/guides/legends/categorical/symbols.js +3 -3
  93. package/src/actions/guides/legends/colorSize.js +2 -0
  94. package/src/actions/guides/legends/continuous/common.js +1 -1
  95. package/src/actions/guides/legends/continuous/gradient.js +1 -360
  96. package/src/actions/guides/legends/continuous/interval.js +1 -1
  97. package/src/actions/guides/legends/continuous/opacity.js +15 -1
  98. package/src/actions/guides/legends/edit.js +1 -1
  99. package/src/actions/guides/legends/focused.js +1 -1
  100. package/src/actions/guides/legends/lane.js +1 -1
  101. package/src/actions/guides/legends/sampling.js +1 -140
  102. package/src/actions/guides/legends/strokeWidth.js +3 -2
  103. package/src/actions/guides/legends/transition.js +19 -2
  104. package/src/actions/guides/polar/axes/facade.js +5 -4
  105. package/src/actions/guides/polar/axes/labels.js +5 -4
  106. package/src/actions/guides/polar/axes/titles.js +5 -4
  107. package/src/actions/guides/polar/resolve.js +2 -1
  108. package/src/actions/guides/tickValues.js +7 -7
  109. package/src/actions/marks/arc/actions.js +5 -7
  110. package/src/actions/marks/area/actions.js +9 -3
  111. package/src/actions/marks/bar/create.js +4 -2
  112. package/src/actions/marks/bar/edit.js +9 -6
  113. package/src/actions/marks/bar/materialize.js +5 -3
  114. package/src/actions/marks/dependentReferences.js +87 -0
  115. package/src/actions/marks/index.js +2 -0
  116. package/src/actions/marks/lifecycle.js +2 -1
  117. package/src/actions/marks/line/actions.js +24 -6
  118. package/src/actions/marks/line/materialize.js +7 -3
  119. package/src/actions/marks/point/create.js +5 -4
  120. package/src/actions/marks/point/edit.js +6 -3
  121. package/src/actions/marks/point/jitter.js +6 -6
  122. package/src/actions/marks/point/materialize.js +36 -39
  123. package/src/actions/marks/rect/actions.js +16 -4
  124. package/src/actions/marks/references.js +183 -35
  125. package/src/actions/marks/rule/actions.js +20 -9
  126. package/src/actions/marks/shared.js +2 -1
  127. package/src/actions/marks/text/actions.js +10 -3
  128. package/src/actions/marks/tick/actions.js +11 -2
  129. package/src/actions/primitives/editGraphics.js +28 -3
  130. package/src/actions/primitives/index.js +9 -0
  131. package/src/actions/primitives/semanticAction.js +13 -7
  132. package/src/actions/primitives/semanticValidation/layer.js +20 -0
  133. package/src/actions/primitives/semanticValidation/scale.js +8 -1
  134. package/src/actions/scales/channels.js +1 -1
  135. package/src/actions/scales/consumers/common.js +19 -1
  136. package/src/actions/scales/consumers/families.js +2 -2
  137. package/src/actions/scales/consumers/seriesLayout.js +4 -3
  138. package/src/actions/scales/edit.js +11 -0
  139. package/src/actions/scales/editPolicy.js +5 -3
  140. package/src/actions/scales/preview.js +2 -1
  141. package/src/actions/scales/quantitativeColor.js +2 -4
  142. package/src/actions/selection/actions.js +10 -1
  143. package/src/actions/theme/reconcile.js +37 -9
  144. package/src/core/compositionState.js +21 -4
  145. package/src/core/datasetReferences.js +17 -0
  146. package/src/core/immutable.js +1 -0
  147. package/src/core/resourceReferences.js +5 -3
  148. package/src/core/validation.js +4 -0
  149. package/src/core/vocabulary.js +24 -2
  150. package/src/grammar/aggregate.js +1 -0
  151. package/src/grammar/arcs.js +1 -1
  152. package/src/grammar/areaSeries.js +1 -1
  153. package/src/grammar/axisTextPlacement.js +1 -1
  154. package/src/grammar/bars/policy.js +2 -2
  155. package/src/grammar/datasetDependencies.js +97 -0
  156. package/src/grammar/datasetSchema.js +31 -1
  157. package/src/grammar/facetHeaders.js +1 -0
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/legendIntent.js +6 -2
  160. package/src/grammar/facets/scales.js +27 -7
  161. package/src/grammar/flatten.js +85 -0
  162. package/src/grammar/histogram.js +13 -1
  163. package/src/grammar/horizon.js +1 -494
  164. package/src/grammar/interval.js +4 -3
  165. package/src/grammar/jitter.js +2 -2
  166. package/src/grammar/legendSpacing.js +45 -0
  167. package/src/grammar/lineMissing.js +90 -0
  168. package/src/grammar/lineSeries.js +1 -376
  169. package/src/grammar/lookup.js +97 -0
  170. package/src/grammar/markFilter.js +4 -2
  171. package/src/grammar/markLabels.js +8 -3
  172. package/src/grammar/numeric.js +1 -1
  173. package/src/grammar/pathCommands.js +1 -1
  174. package/src/grammar/pathSeries.js +1 -109
  175. package/src/grammar/pointPolygon.js +81 -0
  176. package/src/grammar/pointShapes.js +15 -15
  177. package/src/grammar/rects.js +1 -1
  178. package/src/grammar/regression/derive.js +1 -195
  179. package/src/grammar/scales/appearance.js +1 -1
  180. package/src/grammar/scales/color.js +1 -1
  181. package/src/grammar/scales/colorConsumers.js +1 -63
  182. package/src/grammar/scales/continuous.js +58 -1
  183. package/src/grammar/scales/definition.js +1 -1
  184. package/src/grammar/scales/ordinal.js +5 -2
  185. package/src/grammar/scales/policies.js +3 -3
  186. package/src/grammar/schemas/graphicBounds.js +40 -12
  187. package/src/grammar/schemas/graphicClip.js +23 -0
  188. package/src/grammar/schemas/graphicTree.js +16 -0
  189. package/src/grammar/statisticalReference.js +22 -0
  190. package/src/grammar/text.js +65 -1
  191. package/src/grammar/ticks.js +92 -1
  192. package/src/grammar/transformTopology.js +3 -1
  193. package/src/grammar/transforms.js +24 -0
  194. package/src/inspection.js +1 -495
  195. package/src/inspectionDescriptors.js +1 -1
  196. package/src/layout/canvas.js +1 -1
  197. package/src/layout/facetContentFlow.js +101 -0
  198. package/src/layout/facetHeaderTitles.js +1 -0
  199. package/src/layout/facetTopLane.js +29 -0
  200. package/src/layout/facets.js +1 -1
  201. package/src/layout/legendGroup.js +1 -1
  202. package/src/layout/legendItems.js +26 -10
  203. package/src/layout/legendSpacing.js +66 -0
  204. package/src/layout/plotDrawOrder.js +17 -0
  205. package/src/materialization/bars/histogram.js +8 -2
  206. package/src/materialization/bars/resolve.js +1 -1
  207. package/src/materialization/composition.js +1 -0
  208. package/src/materialization/compositionSnapshot.js +22 -2
  209. package/src/materialization/dataProvenance.js +1 -1
  210. package/src/materialization/facetGuides/layout.js +1 -1
  211. package/src/materialization/facetGuides/placement.js +23 -11
  212. package/src/materialization/facetGuides/preparation.js +43 -24
  213. package/src/materialization/facetHeaders.js +1 -1
  214. package/src/materialization/facetTopLane.js +25 -0
  215. package/src/materialization/facets.js +1 -1
  216. package/src/materialization/graphicHierarchy.js +5 -0
  217. package/src/materialization/graphicTranslation.js +2 -0
  218. package/src/materialization/guides/layout.js +1 -1
  219. package/src/materialization/guides/resources.js +1 -1
  220. package/src/materialization/legends.js +1 -1
  221. package/src/materialization/markClip.js +26 -0
  222. package/src/materialization/marks/capabilities.js +3 -2
  223. package/src/materialization/marks/grouping.js +7 -5
  224. package/src/materialization/marks/index.js +4 -2
  225. package/src/materialization/marks/policies.js +4 -0
  226. package/src/materialization/plotDrawOrder.js +44 -0
  227. package/src/materialization/rect.js +11 -3
  228. package/src/materialization/scaleGuideDependencies.js +13 -5
  229. package/src/materialization/selection/items/common.js +1 -1
  230. package/src/materialization/selection/items/path.js +1 -0
  231. package/src/materialization/selection/policies/index.js +2 -1
  232. package/src/materialization/text.js +10 -5
  233. package/src/mcp/cli.js +0 -0
  234. package/src/persistence/validation.js +108 -18
  235. package/src/persistence.js +4 -2
  236. package/src/renderers/canvas/index.js +16 -0
  237. package/src/renderers/canvas/isolated.js +11 -1
  238. package/src/renderers/canvas/native.js +7 -0
  239. package/src/renderers/canvas/scene.js +66 -0
  240. package/src/renderers/canvas/text.js +3 -2
  241. package/src/renderers/svg.js +1 -617
  242. package/src/renderers/svgSerialization.js +643 -0
  243. package/src/renderers/transitionScene.js +67 -0
  244. package/src/scene.js +89 -0
  245. package/src/selectors/axes.js +1 -1
  246. package/src/selectors/datasets.js +1 -0
  247. package/src/selectors/grids.js +9 -0
  248. package/src/selectors/markClip.js +28 -0
  249. package/src/selectors/raincloud.js +7 -0
  250. package/src/version.js +1 -1
  251. package/types/basic.d.ts +7 -0
  252. package/types/index.d.ts +8 -0
  253. package/types/program.d.ts +270 -60
  254. package/types/scene.d.ts +35 -0
@@ -1,9 +1,11 @@
1
+ import { materializeMarkClip } from "../../../materialization/markClip.js";
2
+ import { requestedMarkClip, validateMarkClipCoordinate } from "../../../selectors/markClip.js";
1
3
  import { TEXT_LABEL_LAYOUT_OPTIONS } from "./layout.js";
2
4
  import { action } from "../../../core/action.js";
3
5
  import { validateUserId } from "../../../core/identifiers.js";
4
6
  import {
5
7
  DEFAULT_TEXT_MARK,
6
- TEXT_MARK_STYLE_OPTIONS as STYLE_OPTIONS,
8
+ TEXT_MARK_STYLE_OPTIONS,
7
9
  isSourceOwnedText,
8
10
  normalizeTextMarkConfig
9
11
  } from "../../../grammar/text.js";
@@ -42,6 +44,7 @@ import { resolveMarkGraphicPlacement } from
42
44
  "../../../materialization/graphicHierarchy.js";
43
45
  import { validateItemMissing } from "../../../grammar/itemMissing.js";
44
46
 
47
+ const STYLE_OPTIONS = Object.freeze(["clip", ...TEXT_MARK_STYLE_OPTIONS]);
45
48
  const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", "missing", ...STYLE_OPTIONS]);
46
49
  const EDIT_OPTIONS = Object.freeze(["target", "missing", ...STYLE_OPTIONS]);
47
50
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
@@ -130,6 +133,7 @@ const createTextMark = /* @__PURE__ */ action(
130
133
  },
131
134
  function (args = {}) {
132
135
  validateMarkOptions(args, CREATE_OPTIONS, "createTextMark");
136
+ requestedMarkClip(args);
133
137
  const id = resolveMarkId(this, args.id, {
134
138
  defaultId: "text",
135
139
  label: "Text mark id",
@@ -456,7 +460,7 @@ const rematerializeTextMark = /* @__PURE__ */ action(
456
460
  ? next
457
461
  : next.materializeMarkLabelPlacement({ id });
458
462
  }
459
- let next = this.editGraphics({
463
+ let next = materializeMarkClip(this.editGraphics({
460
464
  target: id,
461
465
  property: "items",
462
466
  value: resolveTextGraphicItems(
@@ -464,7 +468,7 @@ const rematerializeTextMark = /* @__PURE__ */ action(
464
468
  layer,
465
469
  this.markConfigs[id] ?? DEFAULT_TEXT_MARK
466
470
  )
467
- });
471
+ }), id);
468
472
  if (replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined) {
469
473
  return next.materializeLabelLayout({ id, rematerializeBase: false });
470
474
  }
@@ -481,6 +485,7 @@ const editTextMark = /* @__PURE__ */ action(
481
485
  },
482
486
  function (args = {}) {
483
487
  validateMarkOptions(args, EDIT_OPTIONS, "editTextMark");
488
+ requestedMarkClip(args);
484
489
  if (!["missing", ...STYLE_OPTIONS].some(option => Object.hasOwn(args, option))) {
485
490
  throw new Error("editTextMark requires at least one editable property.");
486
491
  }
@@ -494,6 +499,7 @@ const editTextMark = /* @__PURE__ */ action(
494
499
  if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
495
500
  throw new Error("Text fill conflicts with its color encoding.");
496
501
  }
502
+ validateMarkClipCoordinate(this, layer, args.clip ?? this.markConfigs[layer.id]?.clip);
497
503
  const semantic = Object.hasOwn(args, "missing")
498
504
  ? this.editSemantic({
499
505
  property: `layer[${layer.id}].mark.missing`,
@@ -507,6 +513,7 @@ const editTextMark = /* @__PURE__ */ action(
507
513
  args,
508
514
  semantic.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
509
515
  ),
516
+ ...requestedMarkClip(args),
510
517
  fillExplicit: Object.hasOwn(args, "fill")
511
518
  ? true
512
519
  : semantic.markConfigs[layer.id]?.fillExplicit ?? false
@@ -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 {
@@ -15,6 +16,7 @@ import { resolveMarkGraphicPlacement } from
15
16
  import { resolveRowEncodingValues } from
16
17
  "../../../materialization/rowEncoding.js";
17
18
  import { findDataset } from "../../../selectors/datasets.js";
19
+ import { validateTickColorChannels } from "../../../grammar/scales/colorConsumers.js";
18
20
  import { findLayer } from "../../../selectors/layers.js";
19
21
  import { DEFAULT_COLORS } from "../../../theme/defaults.js";
20
22
  import {
@@ -37,7 +39,7 @@ import {
37
39
  } from "../shared.js";
38
40
 
39
41
  const APPEARANCE_OPTIONS = Object.freeze([
40
- "length", "stroke", "strokeWidth", "opacity", ...STROKE_STYLE_PROPERTIES
42
+ "clip", "length", "stroke", "strokeWidth", "opacity", ...STROKE_STYLE_PROPERTIES
41
43
  ]);
42
44
  const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...APPEARANCE_OPTIONS]);
43
45
  const EDIT_OPTIONS = Object.freeze(["target", "missing", ...APPEARANCE_OPTIONS]);
@@ -74,6 +76,7 @@ function resolveTick(program, requested, operation) {
74
76
  function validateTickConfig(args, current = DEFAULT_TICK_CONFIG) {
75
77
  return {
76
78
  ...current,
79
+ ...requestedMarkClip(args),
77
80
  length: Object.hasOwn(args, "length")
78
81
  ? validatePositiveFinite(args.length, "Tick length")
79
82
  : current.length,
@@ -97,6 +100,7 @@ export const createTickMark = /* @__PURE__ */ action(
97
100
  },
98
101
  function (args = {}) {
99
102
  validateMarkOptions(args, CREATE_OPTIONS, "createTickMark");
103
+ requestedMarkClip(args);
100
104
  requestedStrokeDetails(args, "createTickMark");
101
105
  const id = resolveMarkId(this, args.id, {
102
106
  defaultId: "tick",
@@ -148,6 +152,7 @@ export const editTickMark = /* @__PURE__ */ action(
148
152
  },
149
153
  function (args = {}) {
150
154
  validateMarkOptions(args, EDIT_OPTIONS, "editTickMark");
155
+ requestedMarkClip(args);
151
156
  requestedStrokeDetails(args, "editTickMark");
152
157
  if (!["missing", ...APPEARANCE_OPTIONS].some(property => Object.hasOwn(args, property))) {
153
158
  throw new Error(
@@ -155,11 +160,13 @@ export const editTickMark = /* @__PURE__ */ action(
155
160
  );
156
161
  }
157
162
  const layer = resolveTick(this, args.target, "editTickMark");
163
+ if (Object.hasOwn(args, "stroke")) validateTickColorChannels(layer, "stroke");
158
164
  if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
159
165
  throw new Error(
160
166
  "editTickMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
161
167
  );
162
168
  }
169
+ validateMarkClipCoordinate(this, layer, args.clip ?? this.markConfigs[layer.id]?.clip);
163
170
  const semantic = Object.hasOwn(args, "missing")
164
171
  ? this.editSemantic({
165
172
  property: `layer[${layer.id}].mark.missing`,
@@ -200,6 +207,7 @@ export const rematerializeTickMark = /* @__PURE__ */ action(
200
207
  if (layer?.mark?.type !== "tick") {
201
208
  throw new Error(`Unknown tick mark "${id}".`);
202
209
  }
210
+ validateTickColorChannels(layer);
203
211
  const graphic = this.graphicSpec.objects[id];
204
212
  if (graphic?.type !== "line" || !Array.isArray(graphic.items)) {
205
213
  throw new Error(`Tick mark "${id}" requires line collection graphics.`);
@@ -220,7 +228,8 @@ export const rematerializeTickMark = /* @__PURE__ */ action(
220
228
 
221
229
  const x = resolveRowEncodingValues(this, layer, dataset, "x");
222
230
  const y = resolveRowEncodingValues(this, layer, dataset, "y");
223
- const stroke = resolveRowEncodingValues(this, layer, dataset, "stroke");
231
+ const stroke = resolveRowEncodingValues(this, layer, dataset,
232
+ layer.encoding?.color === undefined ? "stroke" : "color");
224
233
  const angles = resolveDirectionValues(dataset.values, layer.encoding?.angle);
225
234
  const config = validateTickConfig({}, {
226
235
  ...DEFAULT_TICK_CONFIG,
@@ -1,3 +1,4 @@
1
+ import { validateGraphicClip } from "../../grammar/schemas/graphicClip.js";
1
2
  import { validateGraphicItemLimit } from "../../core/validation.js";
2
3
  import { closedAction } from "../../core/action.js";
3
4
  import { cloneAndFreeze, freezeOwned, isPlainObject } from "../../core/immutable.js";
@@ -13,7 +14,8 @@ import { validateConcreteGraphicValue } from
13
14
  import {
14
15
  collectGraphicSubtreeIds,
15
16
  findGraphicParent,
16
- requireGraphic
17
+ requireGraphic,
18
+ resolveGraphicChildOrder
17
19
  } from "../../grammar/schemas/graphicTree.js";
18
20
 
19
21
  function setGraphicProperty(properties, property, value) {
@@ -96,6 +98,7 @@ function editGeneratedItem(owner, itemIndex, property, value) {
96
98
  const type = item.type ?? owner.type;
97
99
  validateGraphicProperty(type, property);
98
100
  validateConcreteGraphicValue(type, property, value);
101
+ if (Object.is(item.properties[property], value)) return owner;
99
102
  items[itemIndex] = freezeOwned({
100
103
  ...item,
101
104
  properties: setGraphicProperty(item.properties, property, value)
@@ -126,17 +129,20 @@ function editItems(owner, property, value, id, program) {
126
129
  `${owner.type}.${property} requires ${owner.items.length} values, received ${value.length}.`
127
130
  );
128
131
  }
132
+ let changed = false;
129
133
  const items = owner.items.map((item, index) => {
130
134
  const itemValue = Array.isArray(value) ? value[index] : value;
131
135
  const type = item.type ?? owner.type;
132
136
  validateGraphicProperty(type, property);
133
137
  validateConcreteGraphicValue(type, property, itemValue);
138
+ if (Object.is(item.properties[property], itemValue)) return item;
139
+ changed = true;
134
140
  return freezeOwned({
135
141
  ...item,
136
142
  properties: setGraphicProperty(item.properties, property, itemValue)
137
143
  });
138
144
  });
139
- return freezeOwned({ ...owner, items: freezeOwned(items) });
145
+ return changed ? freezeOwned({ ...owner, items: freezeOwned(items) }) : owner;
140
146
  }
141
147
 
142
148
  function removeGraphicTree(graphicSpec, id) {
@@ -188,11 +194,27 @@ const editGraphics = /* @__PURE__ */ closedAction(
188
194
  return this._clone({ graphicSpec: removeGraphicTree(this.graphicSpec, found.id) });
189
195
  }
190
196
 
197
+ if (property === "children") {
198
+ const children = freezeOwned(resolveGraphicChildOrder(this.graphicSpec, target, value));
199
+ const updated = freezeOwned({ ...found.object, children });
200
+ return this._clone({ graphicSpec: freezeOwned({ ...this.graphicSpec,
201
+ objects: freezeOwned({ ...this.graphicSpec.objects, [target]: updated }) }) });
202
+ }
203
+
191
204
  const ownerId = found.kind === "item" ? found.ownerId : found.id;
192
205
  const owner = found.kind === "item" ? found.owner : found.object;
193
206
  if (owner.items !== undefined) validateGraphicItemLimit(owner.items.length, resolveGraphicItemCapacity(this, {
194
207
  id: ownerId, label: "Graphic collection length"
195
208
  }));
209
+ if (property === "clip") {
210
+ if (found.kind !== "object") throw new Error("Graphic clip requires a named owner, not a generated item.");
211
+ if (owner.type === "canvas") validateGraphicClip(value, owner.type);
212
+ if (value !== false) validateGraphicClip(value, owner.type);
213
+ const {clip: previous, ...rest} = owner;
214
+ const updated = value === false ? freezeOwned(rest) : freezeOwned({...rest, clip: cloneAndFreeze(value)});
215
+ return this._clone({graphicSpec: freezeOwned({...this.graphicSpec,
216
+ objects: freezeOwned({...this.graphicSpec.objects, [ownerId]: updated})})});
217
+ }
196
218
  const replacesDrawableItems =
197
219
  found.kind === "object" && property === "items" &&
198
220
  owner.items !== undefined && owner.type !== "collection";
@@ -203,7 +225,8 @@ const editGraphics = /* @__PURE__ */ closedAction(
203
225
  : owner.type, property);
204
226
  let updated;
205
227
  if (replacesDrawableItems) {
206
- updated = replaceDrawableItems(value, ownerId, owner.type, this);
228
+ updated = freezeOwned({...replaceDrawableItems(value, ownerId, owner.type, this),
229
+ ...(owner.clip === undefined ? {} : {clip: owner.clip})});
207
230
  } else if (found.kind === "item") {
208
231
  if (validatedProperty === "length" || validatedProperty === "items") {
209
232
  throw new Error(`${validatedProperty} can only target an owning graphic.`);
@@ -224,6 +247,7 @@ const editGraphics = /* @__PURE__ */ closedAction(
224
247
  validateGraphicItemLimit(value, resolveGraphicItemCapacity(this, { id: ownerId, label: "Graphic collection length" }));
225
248
  updated = freezeOwned({
226
249
  type: owner.type,
250
+ ...(owner.clip === undefined ? {} : {clip: owner.clip}),
227
251
  items: freezeOwned(Array.from({ length: value }, (_, index) => freezeOwned({
228
252
  id: `${ownerId}:${index}`,
229
253
  properties: index === 0 ? owner.properties : freezeOwned({})
@@ -236,6 +260,7 @@ const editGraphics = /* @__PURE__ */ closedAction(
236
260
  properties: setGraphicProperty(owner.properties, validatedProperty, value)
237
261
  });
238
262
  }
263
+ if (updated === owner) return this;
239
264
  return this._clone({
240
265
  graphicSpec: freezeOwned({
241
266
  ...this.graphicSpec,
@@ -1,6 +1,8 @@
1
1
  import { createSemanticValueValidator } from "./semanticValidation/index.js";
2
2
  import { isPlainObject } from "../../core/immutable.js";
3
3
  import { validateBin2DTransform } from "../../grammar/bin2d.js";
4
+ import { validateFlattenTransform } from "../../grammar/flatten.js";
5
+ import {validateLookupTransform} from "../../grammar/lookup.js";
4
6
  import { registerSemanticPrimitiveAction } from "./semantic.js";
5
7
  import { registerEditGraphicsAction } from "./editGraphics.js";
6
8
  import { registerCreateGraphicsAction } from "./createGraphics.js";
@@ -12,6 +14,13 @@ function validateBasicDatasetTransforms(value) {
12
14
  "Dataset transform must contain exactly one plain object."
13
15
  );
14
16
  }
17
+ if (value[0].type === "flatten") {
18
+ validateFlattenTransform(value[0]);
19
+ return;
20
+ }
21
+ if(value[0].type==="lookup"){
22
+ validateLookupTransform(value[0]);return;
23
+ }
15
24
  if (value[0].type !== "bin2d") {
16
25
  throw new Error(`Unsupported dataset transform "${value[0].type}".`);
17
26
  }
@@ -7,7 +7,7 @@ import {
7
7
  removeOwnedPath
8
8
  } from "../../core/immutable.js";
9
9
  import { parseSemanticPath } from "../../grammar/schemas/semanticPath.js";
10
- import { deriveTransformSchema, inferDatasetSchema, validateRowsAgainstSchema } from "../../grammar/datasetSchema.js";
10
+ import { deriveTransformSchema, inferDatasetSchema, validateRowsAgainstSchema,datasetSchemaContext } from "../../grammar/datasetSchema.js";
11
11
 
12
12
 
13
13
  const CONTEXT_KEYS = Object.freeze({
@@ -57,7 +57,7 @@ function updateEntity(spec, parsed, value) {
57
57
  source.schema,
58
58
  value[0],
59
59
  undefined,
60
- { sourceId: source.id, ownerId: parsed.id, sourceTransform: source.transform?.[0] }
60
+ datasetSchemaContext(source,value[0],parsed.id,id=>collection.find(d=>d.id===id))
61
61
  ));
62
62
  }
63
63
  }
@@ -70,11 +70,8 @@ function updateEntity(spec, parsed, value) {
70
70
  ? undefined
71
71
  : collection.find(dataset => dataset.id === current.source);
72
72
  const schema = source?.schema !== undefined && current.transform?.length === 1
73
- ? deriveTransformSchema(source.schema, current.transform[0], value, {
74
- sourceId: source.id,
75
- sourceTransform: source.transform?.[0],
76
- ownerId: parsed.id
77
- })
73
+ ? deriveTransformSchema(source.schema, current.transform[0], value,
74
+ datasetSchemaContext(source,current.transform[0],parsed.id,id=>collection.find(d=>d.id===id)))
78
75
  : inferDatasetSchema(value);
79
76
  validateRowsAgainstSchema(value, schema, `Dataset "${parsed.id}"`);
80
77
  updated = setNestedProperty(updated, ["schema"], schema);
@@ -207,6 +204,15 @@ export function withRematerializedDerivedDataset(program, {
207
204
  // replaying their canonical primitive owners. Keep that speculative clone in
208
205
  // the primitive boundary and preserve the caller's layer for later dependency
209
206
  // and guide reconciliation.
207
+ export function withPreviewLayerEncodings(program, { id, encodings }) {
208
+ return program._clone({ semanticSpec: {
209
+ ...program.semanticSpec,
210
+ layers: program.semanticSpec.layers.map(layer => layer.id === id
211
+ ? { ...layer, encoding: { ...layer.encoding, ...encodings } }
212
+ : layer)
213
+ } });
214
+ }
215
+
210
216
  export function withoutPreviewLayerEncodings(program, { id, channels }) {
211
217
  const removed = new Set(channels);
212
218
  const semanticSpec = {
@@ -21,6 +21,8 @@ import { normalizeStatisticalWeight } from
21
21
  "../../../grammar/weightedStatistics.js";
22
22
  import { ITEM_MISSING_MARK_TYPES, validateItemMissing } from
23
23
  "../../../grammar/itemMissing.js";
24
+ import { isIndependentText, validateTextOffsetEncoding } from "../../../grammar/text.js";
25
+ import { validateLineMissing, validateLineMissingScope } from "../../../grammar/lineMissing.js";
24
26
 
25
27
  function validateLayerSource(program, parsed, value, sourceMarkTypes) {
26
28
  validateUserId(value, "Layer source id");
@@ -51,6 +53,20 @@ export function validateLayerSemanticValue(
51
53
  } = {}
52
54
  ) {
53
55
  const property = parsed.path.join(".");
56
+ if (/^encoding\.(dx|dy)(\.|$)/.test(property)) {
57
+ if (!isIndependentText(findLayer(program, parsed.id))) {
58
+ throw new Error("Numeric text offsets require independent Text marks.");
59
+ }
60
+ const channel = parsed.path[1];
61
+ if (parsed.path.length === 2) validateTextOffsetEncoding(value, channel);
62
+ else if (property.endsWith(".fieldType") && value !== "quantitative") {
63
+ throw new Error(`Text ${channel} field type must be quantitative.`);
64
+ } else if (property.endsWith(".datum") && !Number.isFinite(value)) {
65
+ throw new TypeError(`Text ${channel} datum must be finite logical pixels.`);
66
+ } else if (property.endsWith(".field")) {
67
+ validateNonEmptySemanticString(value, `Text ${channel} field`);
68
+ }
69
+ }
54
70
  if (property === "mark.type" && !MARK_TYPES.includes(value)) {
55
71
  throw new Error(`Unknown mark type "${value}".`);
56
72
  }
@@ -60,6 +76,10 @@ export function validateLayerSemanticValue(
60
76
  if (!["error", "break"].includes(value)) {
61
77
  throw new Error(`Unsupported area missing policy "${value}".`);
62
78
  }
79
+ } else if (markType === "line") {
80
+ validateLineMissing(value);
81
+ const layer = findLayer(program, parsed.id);
82
+ validateLineMissingScope({ ...layer, mark: { ...layer.mark, missing: value } });
63
83
  } else if (ITEM_MISSING_MARK_TYPES.includes(markType)) {
64
84
  validateItemMissing(value, `${markType} missing`);
65
85
  } else {
@@ -8,6 +8,7 @@ import {
8
8
  validateSemanticScaleType
9
9
  } from "../../../grammar/scales/index.js";
10
10
  import { findSemanticScale } from "../../../selectors/scales.js";
11
+ import { normalizeScaleNice } from "../../../grammar/scales/continuous.js";
11
12
  import { findScaleConsumers } from "../../scales/consumers/index.js";
12
13
 
13
14
  function isOffsetScale(program, id, existing) {
@@ -51,6 +52,7 @@ export function validateScaleSemanticValue(program, parsed, value) {
51
52
  ["paddingInner", "paddingOuter", "align"].includes(owned)
52
53
  ) continue;
53
54
  validateScalePropertyForType(value, owned);
55
+ if (owned === "nice") normalizeScaleNice(value, existing[owned]);
54
56
  }
55
57
  return;
56
58
  }
@@ -62,7 +64,12 @@ export function validateScaleSemanticValue(program, parsed, value) {
62
64
  return;
63
65
  }
64
66
  if (property === "range") return validateSemanticScaleRange(value);
65
- if (["nice", "zero", "clamp", "reverse"].includes(property)) {
67
+ if (property === "nice") {
68
+ normalizeScaleNice(existing?.type, value);
69
+ validateOwnedProperty(program, parsed.id, existing, property);
70
+ return;
71
+ }
72
+ if (["zero", "clamp", "reverse"].includes(property)) {
66
73
  if (typeof value !== "boolean") {
67
74
  throw new TypeError(`Scale ${property} must be a boolean.`);
68
75
  }
@@ -79,7 +79,7 @@ const DEFINITIONS = Object.freeze({
79
79
  }),
80
80
  editStrokeDashScale: Object.freeze({
81
81
  channel: "strokeDash",
82
- options: Object.freeze(["type", "domain", "range", "emptyDomain"])
82
+ options: Object.freeze(["type", "domain", "range", "emptyDomain", "unknown"])
83
83
  })
84
84
  });
85
85
 
@@ -1,7 +1,10 @@
1
1
  import { isSourceOwnedText } from "../../../grammar/text.js";
2
2
  import { readSeriesIdentity, validatePathSeriesAppearance } from "../../../grammar/pathSeries.js";
3
3
  import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
4
- import { readScaleField } from "../../../grammar/scales/index.js";
4
+ import { readLinePosition } from "../../../grammar/lineMissing.js";
5
+ import { readNominalField, readScaleField } from "../../../grammar/scales/index.js";
6
+ import { readCategoricalLineColor, validateCategoricalColorConsumer } from "../../../grammar/scales/colorConsumers.js";
7
+ import { readArcColorValues } from "../../../grammar/arcs.js";
5
8
  import { findDataset } from "../../../selectors/datasets.js";
6
9
  import { requireSemanticScale } from "../../../selectors/scales.js";
7
10
  import { SCALED_ENCODING_CHANNELS } from "../../../core/vocabulary.js";
@@ -56,6 +59,21 @@ export function readConsumerFieldValues(
56
59
  scale = findScale(program, consumer.encoding.scale)
57
60
  ) {
58
61
  const { field, fieldType, temporalUnit } = consumer.encoding;
62
+ if (consumer.layer.mark.type === "line" && consumer.layer.mark.missing === "break" &&
63
+ ["x", "y"].includes(consumer.channel)) {
64
+ return readLinePosition(dataset.values, consumer.encoding).filter(value => value !== null);
65
+ }
66
+ if (consumer.channel === "strokeDash") {
67
+ validateCategoricalColorConsumer(consumer.layer, consumer.encoding, scale, "strokeDash");
68
+ return readNominalField(dataset.values, field);
69
+ }
70
+ if (consumer.channel === "color" && ["nominal", "ordinal"].includes(fieldType) &&
71
+ validateCategoricalColorConsumer(consumer.layer, consumer.encoding, scale)) {
72
+ return readCategoricalLineColor(dataset.values, field);
73
+ }
74
+ if (consumer.layer.mark.type === "arc" && consumer.channel === "color") {
75
+ return readArcColorValues(dataset.values, consumer.layer);
76
+ }
59
77
  if (consumer.layer.mark.type === "bar" && consumer.layer.encoding?.group !== undefined && ["xOffset", "yOffset"].includes(consumer.channel)) {
60
78
  return readSeriesIdentity(dataset.values, consumer.layer).values;
61
79
  }
@@ -19,13 +19,13 @@ import { findScale } from "./common.js";
19
19
 
20
20
  function lineDerivationOptions(program, consumer, dataset) {
21
21
  const x = consumer.layer.encoding?.x;
22
- if (x?.bin === undefined) return undefined;
22
+ if (x?.bin === undefined) return { scales: program.semanticSpec.scales };
23
23
  const bins = resolveLineBins(
24
24
  dataset.values,
25
25
  consumer.layer,
26
26
  findScale(program, x.scale)
27
27
  );
28
- return { xBinBoundaries: bins.boundaries };
28
+ return { xBinBoundaries: bins.boundaries, scales: program.semanticSpec.scales };
29
29
  }
30
30
 
31
31
  export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
@@ -1,3 +1,4 @@
1
+ import { applyItemMissingPolicy } from "../../../grammar/itemMissing.js";
1
2
  import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
2
3
  import { deriveAreaSeries } from "../../../grammar/areaSeries.js";
3
4
  import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
@@ -44,7 +45,7 @@ export function resolveHistogramCountValues(program, consumer) {
44
45
  `Histogram mark "${consumer.layer.id}" requires an existing dataset.`
45
46
  );
46
47
  }
47
- const input = resolveHistogramInput(dataset, xEncoding);
48
+ const input = resolveHistogramInput(dataset, xEncoding, consumer.layer.encoding.y);
48
49
  const xValues = input.values;
49
50
  const bins = resolveHistogramBins({
50
51
  values: xValues,
@@ -63,10 +64,10 @@ export function resolveHistogramCountValues(program, consumer) {
63
64
 
64
65
  function resolveHistogramPartitions(program, consumer) {
65
66
  const layer = consumer.layer;
66
- const dataset = findDataset(program, layer.data);
67
+ const dataset = applyItemMissingPolicy(layer, findDataset(program, layer.data));
67
68
  const xEncoding = layer.encoding.x;
68
69
  const xScale = findScale(program, xEncoding.scale);
69
- const input = resolveHistogramInput(dataset, xEncoding);
70
+ const input = resolveHistogramInput(dataset, xEncoding, consumer.layer.encoding.y);
70
71
  const xValues = input.values;
71
72
  const bins = resolveHistogramBins({
72
73
  values: xValues,
@@ -35,6 +35,17 @@ const EDITABLE = Object.freeze(
35
35
  );
36
36
  const REQUESTED_CHANGES = Object.freeze(OPTIONS.filter(option => option !== "id"));
37
37
 
38
+ // Encoding assignment reuses the wrapped owner without exposing Full editors in Basic.
39
+ export function editScaleForEncoding(program, args) {
40
+ const available = program.editScale;
41
+ if (typeof available === "function") return available.call(program, args);
42
+ const existing = requireSemanticScale(program, args.id);
43
+ if (args.type !== undefined && args.type !== existing.type) {
44
+ throw new Error("Color scale type transitions require the Full ChartProgram; use a new scale id in Basic.");
45
+ }
46
+ return editScale.call(program, args);
47
+ }
48
+
38
49
  function resolveScaleId(program, requested) {
39
50
  if (requested !== undefined) {
40
51
  const id = validateUserId(requested, "Scale id");
@@ -1,4 +1,4 @@
1
- import { validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
1
+ import { validateCategoricalColorConsumer, validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
2
2
  import {
3
3
  hasOrdinalDomain,
4
4
  isContinuousColorScaleType,
@@ -249,8 +249,10 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
249
249
  ? normalizedPatch.unknown
250
250
  : typeChanged ? undefined : scale.unknown;
251
251
  if (unknown !== undefined) {
252
- if (consumers.some(consumer => consumer.layer.mark?.type !== "point")) {
253
- throw new Error("Scale unknown currently requires row-owned point consumers.");
252
+ for (const consumer of consumers) {
253
+ if (consumer.layer.mark?.type !== "point") {
254
+ validateCategoricalColorConsumer(consumer.layer, consumer.encoding, { ...definition, unknown }, consumer.channel);
255
+ }
254
256
  }
255
257
  definition.unknown = consumers.length === 0
256
258
  ? unknown
@@ -42,7 +42,8 @@ function resolveScalePreviewAtBounds(program, id, bounds) {
42
42
  const valuesByConsumer = consumers.map(consumer => {
43
43
  if (
44
44
  program.markConfigs?.[consumer.layer.id]?.markFilter?.empty === true ||
45
- program.markConfigs?.[consumer.layer.id]?.statisticalReference !== undefined
45
+ (program.markConfigs?.[consumer.layer.id]?.statisticalReference !== undefined &&
46
+ ["x", "y", "x2", "y2"].includes(consumer.channel))
46
47
  ) {
47
48
  return {
48
49
  consumer,
@@ -1,3 +1,4 @@
1
+ import { editScaleForEncoding } from "./edit.js";
1
2
  import { closedAction } from "../../core/action.js";
2
3
 
3
4
  import { findSemanticScale } from "../../selectors/scales.js";
@@ -24,10 +25,7 @@ export const setQuantitativeColorScale = /* @__PURE__ */ closedAction(
24
25
  }
25
26
  const existing = findSemanticScale(this, args.id);
26
27
  if (existing !== undefined && existing.type !== args.type) {
27
- if (typeof this.editScale !== "function") {
28
- throw new Error("Color scale type transitions require the Full ChartProgram; use a new scale id in Basic.");
29
- }
30
- return this.editScale(args);
28
+ return editScaleForEncoding(this, args);
31
29
  }
32
30
  let next = this;
33
31
  for (const property of ["whitePoint", "gamma"]) {
@@ -1,7 +1,8 @@
1
1
  import { action, closedAction } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
3
  import { isPlainObject } from "../../core/immutable.js";
4
- import { validateKeys } from "../../core/validation.js";
4
+ import { validateKeys, validateWorkLimit } from "../../core/validation.js";
5
+ import { validatePointShape } from "../../grammar/pointShapes.js";
5
6
  import { collectResourceReferences } from "../../core/resourceReferences.js";
6
7
  import { findLayer, resolveEligibleLayer } from "../../selectors/layers.js";
7
8
  import { isSourceOwnedText } from "../../grammar/text.js";
@@ -369,6 +370,14 @@ export const applyPointHighlight = /* @__PURE__ */ closedAction(
369
370
  throw new Error("applyPointHighlight requires a point selection.");
370
371
  }
371
372
  if (keys.length === 0) return this;
373
+ if (isPlainObject(args.style.shape)) {
374
+ const shape = validatePointShape(args.style.shape);
375
+ const { selected, items } = selectedGraphicItems(this, resolved, keys);
376
+ validateWorkLimit(items.reduce((sum, { key, child }) => sum +
377
+ (selected.has(key) ? shape.polygon.length + 1
378
+ : child.type === "path" ? child.properties.commands.length : 0), 0),
379
+ "Point polygon highlight");
380
+ }
372
381
  return editSelectedItems(this, resolved, keys, item =>
373
382
  transformPointHighlightChild(item, args.style)
374
383
  );