ggaction 0.0.15 → 0.0.17

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 (270) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +23 -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 +40 -7
  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 +3 -3
  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 +23 -17
  49. package/src/actions/data/density.js +4 -4
  50. package/src/actions/data/derived.js +25 -15
  51. package/src/actions/data/ecdf.js +2 -2
  52. package/src/actions/data/edit.js +54 -29
  53. package/src/actions/data/filter.js +29 -24
  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 +58 -54
  59. package/src/actions/data/interval.js +4 -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 +9 -7
  63. package/src/actions/data/revise.js +80 -0
  64. package/src/actions/data/revisionBindings.js +13 -33
  65. package/src/actions/data/shared.js +30 -2
  66. package/src/actions/data/sort.js +21 -0
  67. package/src/actions/data/stack.js +2 -2
  68. package/src/actions/data/summary.js +3 -3
  69. package/src/actions/data/timeUnit.js +2 -2
  70. package/src/actions/data/window.js +2 -2
  71. package/src/actions/encodings/angle.js +5 -2
  72. package/src/actions/encodings/appearance.js +10 -6
  73. package/src/actions/encodings/barWidth.js +1 -1
  74. package/src/actions/encodings/channels.js +2 -2
  75. package/src/actions/encodings/color/continuous.js +4 -1
  76. package/src/actions/encodings/color/index.js +5 -2
  77. package/src/actions/encodings/density.js +2 -2
  78. package/src/actions/encodings/histogram.js +1 -1
  79. package/src/actions/encodings/horizon.js +6 -8
  80. package/src/actions/encodings/offset.js +6 -3
  81. package/src/actions/encodings/parallel.js +4 -5
  82. package/src/actions/encodings/pathOrder.js +6 -11
  83. package/src/actions/encodings/position/index.js +4 -4
  84. package/src/actions/encodings/position/resolve.js +6 -1
  85. package/src/actions/encodings/ranged.js +11 -6
  86. package/src/actions/encodings/remove.js +4 -5
  87. package/src/actions/encodings/ruleAppearance.js +5 -2
  88. package/src/actions/encodings/seriesLayout.js +1 -1
  89. package/src/actions/encodings/stroke.js +5 -2
  90. package/src/actions/encodings/strokeDash.js +8 -3
  91. package/src/actions/encodings/text.js +7 -2
  92. package/src/actions/errorBands/create.js +11 -17
  93. package/src/actions/errorBands/edit.js +13 -131
  94. package/src/actions/errorBars/create.js +8 -13
  95. package/src/actions/errorBars/edit.js +10 -121
  96. package/src/actions/facets/actions.js +30 -16
  97. package/src/actions/facets/derive.js +8 -2
  98. package/src/actions/facets/guides.js +4 -5
  99. package/src/actions/facets/replay.js +4 -5
  100. package/src/actions/gradientPlots/components.js +5 -6
  101. package/src/actions/gradientPlots/create.js +4 -5
  102. package/src/actions/gradientPlots/edit.js +4 -5
  103. package/src/actions/gradientPlots/materialize.js +2 -2
  104. package/src/actions/gradientPlots/rebind.js +4 -5
  105. package/src/actions/guides/axes/axes.js +23 -4
  106. package/src/actions/guides/axes/axis.js +2 -2
  107. package/src/actions/guides/axes/edit.js +2 -2
  108. package/src/actions/guides/axes/labels.js +9 -9
  109. package/src/actions/guides/axes/lines.js +9 -7
  110. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  111. package/src/actions/guides/axes/parallel.js +5 -5
  112. package/src/actions/guides/axes/remove.js +4 -4
  113. package/src/actions/guides/axes/tickGroups.js +4 -4
  114. package/src/actions/guides/axes/ticks.js +13 -4
  115. package/src/actions/guides/axes/titles.js +10 -10
  116. package/src/actions/guides/grids/grid.js +18 -29
  117. package/src/actions/guides/guides.js +1 -1
  118. package/src/actions/guides/legends/blocks.js +1 -1
  119. package/src/actions/guides/legends/categorical/actions.js +4 -4
  120. package/src/actions/guides/legends/categorical/components.js +38 -33
  121. package/src/actions/guides/legends/categorical/layout.js +1 -1
  122. package/src/actions/guides/legends/categorical/symbols.js +27 -30
  123. package/src/actions/guides/legends/continuous/common.js +46 -8
  124. package/src/actions/guides/legends/continuous/gradient.js +89 -155
  125. package/src/actions/guides/legends/continuous/interval.js +13 -13
  126. package/src/actions/guides/legends/continuous/opacity.js +30 -79
  127. package/src/actions/guides/legends/continuous/stroke.js +22 -322
  128. package/src/actions/guides/legends/edit.js +1 -1
  129. package/src/actions/guides/legends/focused.js +5 -5
  130. package/src/actions/guides/legends/lane.js +6 -6
  131. package/src/actions/guides/legends/remove.js +4 -5
  132. package/src/actions/guides/legends/size.js +21 -82
  133. package/src/actions/guides/legends/strokeWidth.js +17 -77
  134. package/src/actions/guides/polar/axes/facade.js +4 -4
  135. package/src/actions/guides/polar/axes/labels.js +5 -5
  136. package/src/actions/guides/polar/axes/lines.js +9 -15
  137. package/src/actions/guides/polar/axes/ticks.js +9 -15
  138. package/src/actions/guides/polar/axes/titles.js +5 -5
  139. package/src/actions/guides/polar/grids.js +11 -13
  140. package/src/actions/index.js +2 -0
  141. package/src/actions/intervals/revision.js +126 -0
  142. package/src/actions/marks/arc/actions.js +3 -3
  143. package/src/actions/marks/area/actions.js +4 -4
  144. package/src/actions/marks/bar/create.js +9 -2
  145. package/src/actions/marks/bar/edit.js +13 -6
  146. package/src/actions/marks/bar/materialize.js +12 -38
  147. package/src/actions/marks/line/actions.js +3 -3
  148. package/src/actions/marks/point/create.js +9 -2
  149. package/src/actions/marks/point/edit.js +13 -6
  150. package/src/actions/marks/point/jitter.js +6 -8
  151. package/src/actions/marks/point/materialize.js +20 -26
  152. package/src/actions/marks/point/packing.js +6 -8
  153. package/src/actions/marks/rect/actions.js +26 -11
  154. package/src/actions/marks/references.js +8 -10
  155. package/src/actions/marks/remove.js +5 -49
  156. package/src/actions/marks/rule/actions.js +28 -12
  157. package/src/actions/marks/text/actions.js +30 -11
  158. package/src/actions/marks/text/layout.js +12 -20
  159. package/src/actions/marks/text/removal.js +1 -1
  160. package/src/actions/marks/text/selection.js +4 -5
  161. package/src/actions/marks/tick/actions.js +26 -11
  162. package/src/actions/primitives/createGraphics.js +5 -4
  163. package/src/actions/primitives/editGraphics.js +5 -4
  164. package/src/actions/primitives/graphicProperties.js +6 -0
  165. package/src/actions/primitives/semantic.js +10 -3
  166. package/src/actions/primitives/semanticAction.js +44 -7
  167. package/src/actions/primitives/semanticValidation/dataset.js +14 -4
  168. package/src/actions/primitives/semanticValidation/layer.js +22 -2
  169. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  170. package/src/actions/regression/components.js +10 -14
  171. package/src/actions/regression/create.js +28 -8
  172. package/src/actions/regression/edit.js +50 -16
  173. package/src/actions/regression/follow.js +22 -0
  174. package/src/actions/resources/remove.js +12 -11
  175. package/src/actions/scales/channels.js +11 -11
  176. package/src/actions/scales/consumers/common.js +2 -1
  177. package/src/actions/scales/create.js +3 -2
  178. package/src/actions/scales/definitions.js +1 -1
  179. package/src/actions/scales/edit.js +2 -2
  180. package/src/actions/scales/materialize.js +1 -1
  181. package/src/actions/scales/offset.js +2 -2
  182. package/src/actions/scales/parallel.js +2 -2
  183. package/src/actions/scales/patch.js +1 -1
  184. package/src/actions/scales/preview.js +28 -1
  185. package/src/actions/scales/quantitativeColor.js +4 -5
  186. package/src/actions/selection/actions.js +25 -36
  187. package/src/actions/textMetrics/index.js +56 -0
  188. package/src/actions/theme/actions.js +2 -2
  189. package/src/actions/theme/reconcile.js +67 -121
  190. package/src/actions/titles/actions.js +8 -8
  191. package/src/actions/titles/resolve.js +3 -3
  192. package/src/actions/violinPlots/create.js +1 -1
  193. package/src/actions/violinPlots/edit.js +1 -1
  194. package/src/core/action.js +109 -36
  195. package/src/core/diagnostics.js +16 -0
  196. package/src/core/extensionRegistry.js +27 -0
  197. package/src/core/font.js +18 -0
  198. package/src/core/identifiers.js +5 -4
  199. package/src/core/immutable.js +10 -3
  200. package/src/core/optionRequirements.js +8 -0
  201. package/src/core/programState.js +6 -2
  202. package/src/core/resourceReferences.js +15 -6
  203. package/src/core/textMetricProfile.js +28 -0
  204. package/src/core/textMetrics.js +24 -3
  205. package/src/core/validation.js +22 -10
  206. package/src/diagnostics.js +1 -0
  207. package/src/grammar/bin.js +39 -10
  208. package/src/grammar/datasetSchema.js +386 -0
  209. package/src/grammar/density.js +53 -5
  210. package/src/grammar/filter.js +133 -39
  211. package/src/grammar/impute.js +3 -2
  212. package/src/grammar/interval.js +32 -1
  213. package/src/grammar/itemMissing.js +59 -0
  214. package/src/grammar/regression/derive.js +57 -16
  215. package/src/grammar/regression/index.js +1 -0
  216. package/src/grammar/regression/models.js +51 -22
  217. package/src/grammar/regression/parameters.js +56 -4
  218. package/src/grammar/scales/definition.js +7 -0
  219. package/src/grammar/scales/types.js +8 -0
  220. package/src/grammar/schemas/graphicBounds.js +11 -11
  221. package/src/grammar/schemas/semanticPath.js +3 -2
  222. package/src/grammar/sort.js +88 -0
  223. package/src/grammar/summary.js +117 -19
  224. package/src/grammar/transformTopology.js +1 -0
  225. package/src/grammar/transforms.js +21 -11
  226. package/src/inspection.js +495 -0
  227. package/src/inspectionDescriptors.js +3 -0
  228. package/src/layout/labels.js +11 -10
  229. package/src/layout/legend.js +4 -4
  230. package/src/layout/legendItems.js +6 -6
  231. package/src/layout/text.js +10 -9
  232. package/src/layout/title.js +9 -9
  233. package/src/materialization/bars/resolve.js +4 -2
  234. package/src/materialization/facetGuides/placement.js +1 -1
  235. package/src/materialization/facetGuides/preparation.js +1 -1
  236. package/src/materialization/facetHeaders.js +11 -10
  237. package/src/materialization/guides/layout.js +3 -3
  238. package/src/materialization/revisionIdentity.js +19 -0
  239. package/src/materialization/scaleGuideDependencies.js +10 -0
  240. package/src/materialization/scales/resolve.js +67 -4
  241. package/src/materialization/selection/items/common.js +1 -1
  242. package/src/materialization/text.js +6 -4
  243. package/src/materialization/typography.js +31 -0
  244. package/src/mcp/cli.js +5 -3
  245. package/src/persistence/codec.js +65 -0
  246. package/src/persistence/validation.js +185 -0
  247. package/src/persistence.js +147 -0
  248. package/src/renderers/canvas/index.js +5 -4
  249. package/src/renderers/nativeBackend.js +10 -0
  250. package/src/renderers/options.js +13 -0
  251. package/src/renderers/pdf.js +25 -54
  252. package/src/renderers/png.js +22 -17
  253. package/src/renderers/svg.js +10 -14
  254. package/src/renderers/text.js +1 -11
  255. package/src/selectors/coordinates.js +3 -1
  256. package/src/selectors/datasets.js +8 -6
  257. package/src/selectors/layers.js +6 -4
  258. package/src/selectors/markOwners.js +46 -0
  259. package/src/selectors/scales.js +6 -4
  260. package/src/theme/defaults.js +2 -1
  261. package/src/version.js +2 -0
  262. package/types/accessibility.d.ts +37 -0
  263. package/types/basic.d.ts +4 -2
  264. package/types/diagnostics.d.ts +15 -0
  265. package/types/index.d.ts +5 -0
  266. package/types/inspection.d.ts +17 -0
  267. package/types/pdf.d.ts +9 -0
  268. package/types/persistence.d.ts +7 -0
  269. package/types/png.d.ts +9 -0
  270. package/types/program.d.ts +181 -28
@@ -1,7 +1,8 @@
1
+ import { ownedChildren, ownership } from "../../selectors/markOwners.js";
1
2
  import { isSourceOwnedText } from "../../grammar/text.js";
2
- import { action } from "../../core/action.js";
3
+ import { closedAction } from "../../core/action.js";
3
4
  import { validateUserId } from "../../core/identifiers.js";
4
- import { validateKeys } from "../../core/validation.js";
5
+
5
6
  import { findDataset } from "../../selectors/datasets.js";
6
7
  import { findLayer } from "../../selectors/layers.js";
7
8
  import { applyDetachedScaleRematerialization } from "../../materialization/dependencies.js";
@@ -23,50 +24,6 @@ const REMOVE_AXIS = Object.freeze({
23
24
  radius: "removeRadialAxis"
24
25
  });
25
26
 
26
- function ownedChildren(program, id) {
27
- const config = program.markConfigs[id] ?? {};
28
- return [
29
- config.errorBar?.lowerCapId,
30
- config.errorBar?.upperCapId,
31
- config.errorBand?.lowerBoundaryId,
32
- config.errorBand?.upperBoundaryId,
33
- config.regression?.bandId,
34
- config.regression?.lineId,
35
- config.boxPlot?.whiskerId,
36
- config.boxPlot?.medianId,
37
- config.boxPlot?.outlierId,
38
- config.gradientPlot?.centerId,
39
- config.intervalPlot?.intervalId,
40
- config.endpointPlot?.roles?.stemId,
41
- config.endpointPlot?.roles?.startId,
42
- config.endpointPlot?.roles?.connectorId,
43
- ...(config.raincloudPlot?.ownedChildIds ?? [])
44
- ].concat(
45
- program.semanticSpec.layers
46
- .filter(layer => layer.source === id)
47
- .map(layer => layer.id)
48
- ).concat(
49
- program.semanticSpec.layers
50
- .filter(layer =>
51
- program.markConfigs[layer.id]?.statisticalReference?.source === id
52
- )
53
- .map(layer => layer.id)
54
- ).filter(child => child !== undefined && child !== id && findLayer(program, child) !== undefined);
55
- }
56
-
57
- function ownership(program) {
58
- const ownerByChild = new Map();
59
- for (const layer of program.semanticSpec.layers) {
60
- for (const child of ownedChildren(program, layer.id)) {
61
- if (program.markConfigs[child]?.statisticalReference !== undefined) {
62
- continue;
63
- }
64
- ownerByChild.set(child, layer.id);
65
- }
66
- }
67
- return ownerByChild;
68
- }
69
-
70
27
  function resolveOwner(program, requested) {
71
28
  const ownerByChild = ownership(program);
72
29
  const stable = program.semanticSpec.layers.filter(
@@ -248,10 +205,9 @@ function cleanupPositionGuides(program, scales) {
248
205
  return next;
249
206
  }
250
207
 
251
- export const removeMark = action(
252
- { op: "removeMark", description: "Remove one stable mark owner and owned state." },
208
+ export const removeMark = /* @__PURE__ */ closedAction(
209
+ { op: "removeMark", description: "Remove one stable mark owner and owned state." }, OPTIONS,
253
210
  function (args = {}) {
254
- validateKeys(args, OPTIONS, "removeMark");
255
211
  const owner = resolveOwner(this, args.target);
256
212
  const ids = collectClosure(this, owner.id);
257
213
  const external = externalMarkReferences(this, ids);
@@ -39,13 +39,15 @@ import {
39
39
  requestedStrokeDetails,
40
40
  STROKE_STYLE_PROPERTIES
41
41
  } from "../../../grammar/strokeStyle.js";
42
+ import { applyItemMissingPolicy, validateItemMissing } from
43
+ "../../../grammar/itemMissing.js";
42
44
 
43
45
  const APPEARANCE_OPTIONS = Object.freeze([
44
46
  "stroke", "strokeWidth", "strokeDash", "opacity",
45
47
  ...STROKE_STYLE_PROPERTIES
46
48
  ]);
47
- const CREATE_OPTIONS = Object.freeze(["id", "data", ...APPEARANCE_OPTIONS]);
48
- const EDIT_OPTIONS = Object.freeze(["target", ...APPEARANCE_OPTIONS]);
49
+ const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...APPEARANCE_OPTIONS]);
50
+ const EDIT_OPTIONS = Object.freeze(["target", "missing", ...APPEARANCE_OPTIONS]);
49
51
  const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
50
52
  const SPAN_OPTIONS = Object.freeze(["id", "orientation", "size"]);
51
53
  const DEFAULT_RULE_CONFIG = Object.freeze({
@@ -66,10 +68,11 @@ function requireRule(program, id) {
66
68
  if (layer?.mark?.type !== "rule") {
67
69
  throw new Error(`Unknown rule mark "${id}".`);
68
70
  }
69
- const dataset = findDataset(program, layer.data);
70
- if (dataset === undefined) {
71
+ const sourceDataset = findDataset(program, layer.data);
72
+ if (sourceDataset === undefined) {
71
73
  throw new Error(`Rule mark "${id}" requires an existing dataset.`);
72
74
  }
75
+ const dataset = applyItemMissingPolicy(layer, sourceDataset);
73
76
  const graphic = program.graphicSpec.objects[id];
74
77
  if (graphic?.type !== "line" || graphic.items === undefined) {
75
78
  throw new Error(`Rule mark "${id}" requires line collection graphics.`);
@@ -92,7 +95,7 @@ function validateEndpointBindings(layer) {
92
95
  }
93
96
  }
94
97
 
95
- const createRuleMark = action(
98
+ const createRuleMark = /* @__PURE__ */ action(
96
99
  {
97
100
  op: "createRuleMark",
98
101
  description: "Create a semantic rule mark and empty line collection."
@@ -118,6 +121,12 @@ const createRuleMark = action(
118
121
  let created = this
119
122
  .editSemantic({ property: `layer[${id}].mark.type`, value: "rule" })
120
123
  .editSemantic({ property: `layer[${id}].data`, value: data });
124
+ if (Object.hasOwn(args, "missing")) {
125
+ created = created.editSemantic({
126
+ property: `layer[${id}].mark.missing`,
127
+ value: validateItemMissing(args.missing, "Rule missing")
128
+ });
129
+ }
121
130
  created = applyLayeredMarkInheritance(created, id, inherited);
122
131
  created = created
123
132
  .createGraphics({
@@ -142,7 +151,7 @@ const createRuleMark = action(
142
151
  }
143
152
  );
144
153
 
145
- const editRuleMark = action(
154
+ const editRuleMark = /* @__PURE__ */ action(
146
155
  { op: "editRuleMark", description: "Edit constant rule appearance through its encoding owners." },
147
156
  function (args = {}) {
148
157
  validateMarkOptions(args, EDIT_OPTIONS, "editRuleMark");
@@ -154,11 +163,18 @@ const editRuleMark = action(
154
163
  label: "rule mark"
155
164
  });
156
165
  const appearance = planRuleAppearance(args, layer);
157
- if (appearance.length === 0 && Object.keys(strokeDetails).length === 0) {
158
- throw new Error("editRuleMark requires an appearance change.");
166
+ if (appearance.length === 0 && Object.keys(strokeDetails).length === 0 &&
167
+ !Object.hasOwn(args, "missing")) {
168
+ throw new Error("editRuleMark requires missing or an appearance change.");
159
169
  }
160
- const configured = this._withMarkConfig(layer.id, {
161
- ...this.markConfigs[layer.id],
170
+ const semantic = Object.hasOwn(args, "missing")
171
+ ? this.editSemantic({
172
+ property: `layer[${layer.id}].mark.missing`,
173
+ value: validateItemMissing(args.missing, "Rule missing")
174
+ })
175
+ : this;
176
+ const configured = semantic._withMarkConfig(layer.id, {
177
+ ...semantic.markConfigs[layer.id],
162
178
  ...strokeDetails
163
179
  });
164
180
  const materialized = appearance.length === 0
@@ -168,7 +184,7 @@ const editRuleMark = action(
168
184
  }
169
185
  );
170
186
 
171
- const rematerializeRuleMark = action(
187
+ const rematerializeRuleMark = /* @__PURE__ */ action(
172
188
  {
173
189
  op: "rematerializeRuleMark",
174
190
  description: "Recompute concrete rule endpoints and appearance."
@@ -377,7 +393,7 @@ const rematerializeRuleMark = action(
377
393
  }
378
394
  );
379
395
 
380
- export const materializeRuleSpan = action(
396
+ export const materializeRuleSpan = /* @__PURE__ */ action(
381
397
  {
382
398
  op: "materializeRuleSpan",
383
399
  description: "Materialize a fixed-pixel span around a rule anchor."
@@ -39,13 +39,14 @@ import {
39
39
  } from "../../charts/shared.js";
40
40
  import { resolveMarkGraphicPlacement } from
41
41
  "../../../materialization/graphicHierarchy.js";
42
+ import { validateItemMissing } from "../../../grammar/itemMissing.js";
42
43
 
43
44
  const STYLE_OPTIONS = Object.freeze([
44
45
  "fill", "opacity", "fontSize", "fontFamily", "fontWeight",
45
46
  "align", "baseline", "rotation", "dx", "dy"
46
47
  ]);
47
- const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", ...STYLE_OPTIONS]);
48
- const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
48
+ const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", "missing", ...STYLE_OPTIONS]);
49
+ const EDIT_OPTIONS = Object.freeze(["target", "missing", ...STYLE_OPTIONS]);
49
50
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
50
51
 
51
52
  function sourceMatchesData(program, layer, requestedData) {
@@ -125,7 +126,7 @@ function requireTextLayer(program, requested, operation) {
125
126
  });
126
127
  }
127
128
 
128
- const createTextMark = action(
129
+ const createTextMark = /* @__PURE__ */ action(
129
130
  {
130
131
  op: "createTextMark",
131
132
  description: "Create a semantic text annotation layer."
@@ -139,6 +140,9 @@ const createTextMark = action(
139
140
  operation: "createTextMark"
140
141
  });
141
142
  const inherited = resolveTextInheritance(this, args);
143
+ if (Object.hasOwn(args, "missing") && inherited?.source !== undefined) {
144
+ throw new Error("Text missing policy requires a row-backed text mark, not source-owned labels.");
145
+ }
142
146
  const { data } = resolveMarkData(this, {
143
147
  ...args,
144
148
  ...(args.data === undefined && inherited?.data !== undefined
@@ -150,6 +154,12 @@ const createTextMark = action(
150
154
  let next = this
151
155
  .editSemantic({ property: `layer[${id}].mark.type`, value: "text" })
152
156
  .editSemantic({ property: `layer[${id}].data`, value: data });
157
+ if (Object.hasOwn(args, "missing")) {
158
+ next = next.editSemantic({
159
+ property: `layer[${id}].mark.missing`,
160
+ value: validateItemMissing(args.missing, "Text missing")
161
+ });
162
+ }
153
163
  if (inherited?.source !== undefined) {
154
164
  next = next.editSemantic({
155
165
  property: `layer[${id}].source`,
@@ -254,7 +264,7 @@ function resolveAnnotationBinding(program, args) {
254
264
  };
255
265
  }
256
266
 
257
- const createAnnotation = action(
267
+ const createAnnotation = /* @__PURE__ */ action(
258
268
  {
259
269
  op: "createAnnotation",
260
270
  description: "Create text at a final mark, data, or plot anchor."
@@ -327,7 +337,7 @@ const createAnnotation = action(
327
337
  }
328
338
  );
329
339
 
330
- const createMarkLabels = action(
340
+ const createMarkLabels = /* @__PURE__ */ action(
331
341
  {
332
342
  op: "createMarkLabels",
333
343
  description: "Create final-item text labels attached to an existing mark."
@@ -412,7 +422,7 @@ const createMarkLabels = action(
412
422
  }
413
423
  );
414
424
 
415
- const rematerializeTextMark = action(
425
+ const rematerializeTextMark = /* @__PURE__ */ action(
416
426
  {
417
427
  op: "rematerializeTextMark",
418
428
  description: "Recompute concrete text content, anchors, and typography."
@@ -458,27 +468,36 @@ const rematerializeTextMark = action(
458
468
  }
459
469
  );
460
470
 
461
- const editTextMark = action(
471
+ const editTextMark = /* @__PURE__ */ action(
462
472
  {
463
473
  op: "editTextMark",
464
474
  description: "Edit text typography, alignment, rotation, and offsets."
465
475
  },
466
476
  function (args = {}) {
467
477
  validateMarkOptions(args, EDIT_OPTIONS, "editTextMark");
468
- if (!STYLE_OPTIONS.some(option => Object.hasOwn(args, option))) {
478
+ if (!["missing", ...STYLE_OPTIONS].some(option => Object.hasOwn(args, option))) {
469
479
  throw new Error("editTextMark requires at least one editable property.");
470
480
  }
471
481
  const layer = requireTextLayer(this, args.target, "editTextMark");
472
- const next = this._withMarkConfig(
482
+ if (Object.hasOwn(args, "missing") && layer.source !== undefined) {
483
+ throw new Error("Text missing policy requires a row-backed text mark, not source-owned labels.");
484
+ }
485
+ const semantic = Object.hasOwn(args, "missing")
486
+ ? this.editSemantic({
487
+ property: `layer[${layer.id}].mark.missing`,
488
+ value: validateItemMissing(args.missing, "Text missing")
489
+ })
490
+ : this;
491
+ const next = semantic._withMarkConfig(
473
492
  layer.id,
474
493
  {
475
494
  ...normalizeTextMarkConfig(
476
495
  args,
477
- this.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
496
+ semantic.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
478
497
  ),
479
498
  fillExplicit: Object.hasOwn(args, "fill")
480
499
  ? true
481
- : this.markConfigs[layer.id]?.fillExplicit ?? false
500
+ : semantic.markConfigs[layer.id]?.fillExplicit ?? false
482
501
  }
483
502
  );
484
503
  return canMaterializeText(next, layer)
@@ -1,4 +1,4 @@
1
- import { action } from "../../../core/action.js";
1
+ import { action, closedAction } from "../../../core/action.js";
2
2
  import { validateUserId } from "../../../core/identifiers.js";
3
3
  import { validateOptionObject } from "../../../core/validation.js";
4
4
  import { DEFAULT_TEXT_MARK } from "../../../grammar/text.js";
@@ -221,22 +221,17 @@ function placementLeaderItems(program, layer) {
221
221
  sourceY: entry.anchor.y,
222
222
  dx: properties.x - entry.anchor.x,
223
223
  dy: properties.y - entry.anchor.y,
224
- bounds: resolveTextBounds(properties)
224
+ bounds: resolveTextBounds(properties, program.materializationConfigs.textMetrics)
225
225
  };
226
226
  });
227
227
  }
228
228
 
229
- const materializeMarkLabelPlacement = action(
229
+ const materializeMarkLabelPlacement = /* @__PURE__ */ closedAction(
230
230
  {
231
231
  op: "materializeMarkLabelPlacement",
232
232
  description: "Reconcile semantic attached-label placement leaders."
233
- },
233
+ }, MATERIALIZE_PLACEMENT_OPTIONS,
234
234
  function (args = {}) {
235
- validateOptionObject(
236
- args,
237
- MATERIALIZE_PLACEMENT_OPTIONS,
238
- "materializeMarkLabelPlacement"
239
- );
240
235
  const id = validateUserId(args.id, "Attached label id");
241
236
  const layer = findLayer(this, id);
242
237
  if (!isSourceOwnedText(layer)) {
@@ -267,13 +262,12 @@ const materializeMarkLabelPlacement = action(
267
262
  }
268
263
  );
269
264
 
270
- const materializeLabelLayout = action(
265
+ const materializeLabelLayout = /* @__PURE__ */ closedAction(
271
266
  {
272
267
  op: "materializeLabelLayout",
273
268
  description: "Resolve concrete label positions and optional leader lines."
274
- },
269
+ }, MATERIALIZE_OPTIONS,
275
270
  function (args = {}) {
276
- validateOptionObject(args, MATERIALIZE_OPTIONS, "materializeLabelLayout");
277
271
  const id = validateUserId(args.id, "Text mark id");
278
272
  const rematerializeBase = args.rematerializeBase ?? true;
279
273
  if (typeof rematerializeBase !== "boolean") {
@@ -294,7 +288,7 @@ const materializeLabelLayout = action(
294
288
  axis: config.axis,
295
289
  padding: config.padding,
296
290
  maxDisplacement: config.maxDisplacement
297
- });
291
+ }, next.materializationConfigs.textMetrics);
298
292
  next = editMarkGraphic(next, id, {
299
293
  x: resolved.items.map(item => item.x),
300
294
  y: resolved.items.map(item => item.y)
@@ -326,13 +320,12 @@ const materializeLabelLayout = action(
326
320
  }
327
321
  );
328
322
 
329
- const layoutLabels = action(
323
+ const layoutLabels = /* @__PURE__ */ closedAction(
330
324
  {
331
325
  op: "layoutLabels",
332
326
  description: "Assign deterministic collision-aware layout to a text mark."
333
- },
327
+ }, TEXT_LABEL_LAYOUT_OPTIONS,
334
328
  function (args = {}) {
335
- validateOptionObject(args, TEXT_LABEL_LAYOUT_OPTIONS, "layoutLabels");
336
329
  const layer = requireCompleteText(this, args.target, "layoutLabels");
337
330
  const policy = normalizePolicy(args);
338
331
  const placement = this.markConfigs[layer.id]?.labelAuthoring?.placement;
@@ -358,7 +351,7 @@ const layoutLabels = action(
358
351
  }
359
352
  );
360
353
 
361
- const editMarkLabelPlacement = action(
354
+ const editMarkLabelPlacement = /* @__PURE__ */ action(
362
355
  {
363
356
  op: "editMarkLabelPlacement",
364
357
  description: "Replace or reset semantic placement for one attached label layer."
@@ -418,13 +411,12 @@ const editMarkLabelPlacement = action(
418
411
  }
419
412
  );
420
413
 
421
- const removeLabelLayout = action(
414
+ const removeLabelLayout = /* @__PURE__ */ closedAction(
422
415
  {
423
416
  op: "removeLabelLayout",
424
417
  description: "Remove label layout and restore semantic base text positions."
425
- },
418
+ }, REMOVE_OPTIONS,
426
419
  function (args = {}) {
427
- validateOptionObject(args, REMOVE_OPTIONS, "removeLabelLayout");
428
420
  const layer = requireConfiguredText(this, args.target);
429
421
  const config = this.materializationConfigs.labelLayouts[layer.id];
430
422
  return removeLeaderGraphic(
@@ -140,7 +140,7 @@ function removeLabelState(program, plan) {
140
140
  return Object.keys(context).length === 0 ? next : next._withContext(context);
141
141
  }
142
142
 
143
- export const removeMarkLabels = action(
143
+ export const removeMarkLabels = /* @__PURE__ */ action(
144
144
  {
145
145
  op: "removeMarkLabels",
146
146
  description: "Remove attached label layers while preserving their source mark."
@@ -1,6 +1,6 @@
1
- import { action } from "../../../core/action.js";
1
+ import { closedAction } from "../../../core/action.js";
2
2
  import { validateUserId } from "../../../core/identifiers.js";
3
- import { validateOptionObject } from "../../../core/validation.js";
3
+
4
4
  import { normalizeLabelSelectionEdit } from
5
5
  "../../../grammar/markLabelSelection.js";
6
6
  import { isSourceOwnedText } from "../../../grammar/text.js";
@@ -40,13 +40,12 @@ function validateSelectionSource(program, source, requested) {
40
40
  resolveMarkSelection(program, source, definition.selector);
41
41
  }
42
42
 
43
- const editMarkLabelSelection = action(
43
+ const editMarkLabelSelection = /* @__PURE__ */ closedAction(
44
44
  {
45
45
  op: "editMarkLabelSelection",
46
46
  description: "Replace the final-item membership requested by an attached label."
47
- },
47
+ }, OPTIONS,
48
48
  function (args = {}) {
49
- validateOptionObject(args, OPTIONS, "editMarkLabelSelection");
50
49
  if (!Object.hasOwn(args, "target")) {
51
50
  throw new Error("editMarkLabelSelection requires target.");
52
51
  }
@@ -21,6 +21,8 @@ import {
21
21
  requestedStrokeDetails,
22
22
  STROKE_STYLE_PROPERTIES
23
23
  } from "../../../grammar/strokeStyle.js";
24
+ import { applyItemMissingPolicy, validateItemMissing } from
25
+ "../../../grammar/itemMissing.js";
24
26
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
25
27
  import { rematerializeExistingLegend } from "../../encodings/shared.js";
26
28
  import {
@@ -37,8 +39,8 @@ import {
37
39
  const APPEARANCE_OPTIONS = Object.freeze([
38
40
  "length", "stroke", "strokeWidth", "opacity", ...STROKE_STYLE_PROPERTIES
39
41
  ]);
40
- const CREATE_OPTIONS = Object.freeze(["id", "data", ...APPEARANCE_OPTIONS]);
41
- const EDIT_OPTIONS = Object.freeze(["target", ...APPEARANCE_OPTIONS]);
42
+ const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...APPEARANCE_OPTIONS]);
43
+ const EDIT_OPTIONS = Object.freeze(["target", "missing", ...APPEARANCE_OPTIONS]);
42
44
  const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
43
45
 
44
46
  export const DEFAULT_TICK_CONFIG = Object.freeze({
@@ -88,7 +90,7 @@ function validateTickConfig(args, current = DEFAULT_TICK_CONFIG) {
88
90
  };
89
91
  }
90
92
 
91
- export const createTickMark = action(
93
+ export const createTickMark = /* @__PURE__ */ action(
92
94
  {
93
95
  op: "createTickMark",
94
96
  description: "Create a centered fixed-length Tick mark."
@@ -114,6 +116,12 @@ export const createTickMark = action(
114
116
  let created = this
115
117
  .editSemantic({ property: `layer[${id}].mark.type`, value: "tick" })
116
118
  .editSemantic({ property: `layer[${id}].data`, value: data });
119
+ if (Object.hasOwn(args, "missing")) {
120
+ created = created.editSemantic({
121
+ property: `layer[${id}].mark.missing`,
122
+ value: validateItemMissing(args.missing, "Tick missing")
123
+ });
124
+ }
117
125
  created = applyLayeredMarkInheritance(created, id, inherited)
118
126
  .createGraphics({
119
127
  id,
@@ -133,7 +141,7 @@ export const createTickMark = action(
133
141
  }
134
142
  );
135
143
 
136
- export const editTickMark = action(
144
+ export const editTickMark = /* @__PURE__ */ action(
137
145
  {
138
146
  op: "editTickMark",
139
147
  description: "Edit Tick length and constant line appearance."
@@ -141,9 +149,9 @@ export const editTickMark = action(
141
149
  function (args = {}) {
142
150
  validateMarkOptions(args, EDIT_OPTIONS, "editTickMark");
143
151
  requestedStrokeDetails(args, "editTickMark");
144
- if (!APPEARANCE_OPTIONS.some(property => Object.hasOwn(args, property))) {
152
+ if (!["missing", ...APPEARANCE_OPTIONS].some(property => Object.hasOwn(args, property))) {
145
153
  throw new Error(
146
- "editTickMark requires length, stroke, strokeWidth, or opacity."
154
+ "editTickMark requires length, stroke, strokeWidth, opacity, or missing."
147
155
  );
148
156
  }
149
157
  const layer = resolveTick(this, args.target, "editTickMark");
@@ -152,18 +160,24 @@ export const editTickMark = action(
152
160
  "editTickMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
153
161
  );
154
162
  }
163
+ const semantic = Object.hasOwn(args, "missing")
164
+ ? this.editSemantic({
165
+ property: `layer[${layer.id}].mark.missing`,
166
+ value: validateItemMissing(args.missing, "Tick missing")
167
+ })
168
+ : this;
155
169
  const config = validateTickConfig(args, {
156
170
  ...DEFAULT_TICK_CONFIG,
157
- ...this.markConfigs[layer.id]
171
+ ...semantic.markConfigs[layer.id]
158
172
  });
159
- const materialized = this
173
+ const materialized = semantic
160
174
  ._withMarkConfig(layer.id, config)
161
175
  .rematerializeTickMark({ id: layer.id });
162
176
  return rematerializeExistingLegend(materialized);
163
177
  }
164
178
  );
165
179
 
166
- export const rematerializeTickMark = action(
180
+ export const rematerializeTickMark = /* @__PURE__ */ action(
167
181
  {
168
182
  op: "rematerializeTickMark",
169
183
  description: "Recompute concrete centered Tick endpoints and appearance."
@@ -190,10 +204,11 @@ export const rematerializeTickMark = action(
190
204
  if (graphic?.type !== "line" || !Array.isArray(graphic.items)) {
191
205
  throw new Error(`Tick mark "${id}" requires line collection graphics.`);
192
206
  }
193
- const dataset = findDataset(this, layer.data);
194
- if (dataset === undefined) {
207
+ const sourceDataset = findDataset(this, layer.data);
208
+ if (sourceDataset === undefined) {
195
209
  throw new Error(`Tick mark "${id}" requires an existing dataset.`);
196
210
  }
211
+ const dataset = applyItemMissingPolicy(layer, sourceDataset);
197
212
  if (
198
213
  layer.encoding?.x?.scale === undefined ||
199
214
  layer.encoding?.y?.scale === undefined
@@ -1,4 +1,4 @@
1
- import { action } from "../../core/action.js";
1
+ import { closedAction } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
3
  import { freezeOwned } from "../../core/immutable.js";
4
4
  import { validateGeneratedItemLimit } from "../../core/validation.js";
@@ -100,13 +100,14 @@ function insertSibling(siblings, id, { before, after }) {
100
100
  return freezeOwned(next);
101
101
  }
102
102
 
103
- const createGraphics = action(
103
+ const createGraphics = /* @__PURE__ */ closedAction(
104
104
  {
105
105
  op: "createGraphics",
106
106
  description: "Create and optionally attach a concrete graphic.",
107
107
  scope: "any"
108
- },
109
- function ({ id, type, length, parent, before, after } = {}) {
108
+ }, ["id", "type", "length", "parent", "before", "after"],
109
+ function (args = {}) {
110
+ const { id, type, length, parent, before, after } = args;
110
111
  if (typeof id !== "string" || !GRAPHIC_ID_PATTERN.test(id)) {
111
112
  throw new TypeError(
112
113
  "createGraphics requires an id containing only letters, numbers, _ or -."
@@ -1,4 +1,4 @@
1
- import { action } from "../../core/action.js";
1
+ import { closedAction } from "../../core/action.js";
2
2
  import { cloneAndFreeze, freezeOwned, isPlainObject } from "../../core/immutable.js";
3
3
  import { validateGeneratedItemLimit } from "../../core/validation.js";
4
4
  import {
@@ -162,13 +162,14 @@ function removeGraphicTree(graphicSpec, id) {
162
162
  return freezeOwned({ objects: freezeOwned(objects), order });
163
163
  }
164
164
 
165
- const editGraphics = action(
165
+ const editGraphics = /* @__PURE__ */ closedAction(
166
166
  {
167
167
  op: "editGraphics",
168
168
  description: "Replace or remove concrete graphic state.",
169
169
  scope: "any"
170
- },
171
- function ({ target, property, value, remove = false } = {}) {
170
+ }, ["target", "property", "value", "remove"],
171
+ function (args = {}) {
172
+ const { target, property, value, remove = false } = args;
172
173
  if (typeof target !== "string" || target.length === 0) {
173
174
  throw new TypeError("editGraphics requires a non-empty target string.");
174
175
  }
@@ -0,0 +1,6 @@
1
+ export function editGraphicProperties(program, target, properties) {
2
+ for (const [property, value] of Object.entries(properties)) {
3
+ program = program.editGraphics({ target, property, value });
4
+ }
5
+ return program;
6
+ }
@@ -18,12 +18,19 @@ function validateParallel(property, value) {
18
18
  }
19
19
  }
20
20
 
21
- export function registerSemanticPrimitiveAction(ProgramClass) {
22
- const validateSemanticValue = createSemanticValueValidator({
21
+ function fullSemanticValidator() {
22
+ return createSemanticValueValidator({
23
23
  validateDatasetTransforms,
24
24
  validateParallel,
25
25
  sourceMarkTypes: ["point", "bar", "line", "rule", "rect", "arc"]
26
26
  });
27
+ }
28
+
29
+ export function validateStoredSemanticValue(program, parsed, value) {
30
+ return fullSemanticValidator()(program, parsed, value);
31
+ }
32
+
33
+ export function registerSemanticPrimitiveAction(ProgramClass) {
27
34
  ProgramClass.prototype.editSemantic =
28
- createSemanticPrimitiveAction(validateSemanticValue, planResourceRemoval);
35
+ createSemanticPrimitiveAction(fullSemanticValidator(), planResourceRemoval);
29
36
  }