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,3 +1,4 @@
1
+ import { rematerializeTypography } from "../../materialization/typography.js";
1
2
  import { themeTokens } from "../../theme/defaults.js";
2
3
  import {
3
4
  normalizeThemeState,
@@ -5,6 +6,7 @@ import {
5
6
  setThemeStateOverrides
6
7
  } from "./state.js";
7
8
  import { findLayer } from "../../selectors/layers.js";
9
+ import { reconcileFollowingRegressions } from "../regression/follow.js";
8
10
 
9
11
  let rematerializeThemeHighlights = program => program;
10
12
 
@@ -46,10 +48,7 @@ function tokenFor(value, sourceTokens, targetTokens) {
46
48
  if (matches.length === 0) return undefined;
47
49
  const targets = new Set(matches.map(token => targetTokens[token]));
48
50
  if (targets.size !== 1) return undefined;
49
- for (const token of matches) {
50
- if (targetTokens[token] === targets.values().next().value) return token;
51
- }
52
- return undefined;
51
+ return matches[0];
53
52
  }
54
53
 
55
54
  function roleValues(role, sourceTokens, legacy = []) {
@@ -411,6 +410,11 @@ function recolorConfig(
411
410
  return { changed, value: changed ? next : value };
412
411
  }
413
412
 
413
+ function addMarkPropertyOverrides(overrides, id, property) {
414
+ overrides.add(`c:marks.${id}.${property}`);
415
+ overrides.add(`g:${id}.${property}`);
416
+ }
417
+
414
418
  function addMarkOverrides(overrides, program, op, args) {
415
419
  const match = /^(?:create|edit)(Point|Line|Area|Arc|Bar|Rect|Rule|Tick|Text)Mark$/u
416
420
  .exec(op);
@@ -425,42 +429,43 @@ function addMarkOverrides(overrides, program, op, args) {
425
429
  if (typeof id !== "string") return;
426
430
  for (const property of ["fill", "stroke", "fontFamily"]) {
427
431
  if (!Object.hasOwn(args, property)) continue;
428
- overrides.add(`c:marks.${id}.${property}`);
429
- overrides.add(`g:${id}.${property}`);
432
+ addMarkPropertyOverrides(overrides, id, property);
430
433
  }
431
434
  }
432
435
 
433
436
  function addFacadeMarkOverrides(overrides, op, args) {
434
437
  const facades = {
435
- createScatterPlot: ["scatterPlot", "point", ["fill", "stroke"]],
436
- createLinePlot: ["linePlot", "line", ["stroke"]],
437
- createPolarScatterPlot: ["polarScatterPlot", "point", ["fill", "stroke"]],
438
- createPolarLinePlot: ["polarLinePlot", "line", ["stroke"]],
439
- createRadarPlot: ["radarPlot", "line", ["stroke"]],
440
- createRugPlot: ["rugPlot", "tick", ["stroke"]],
441
- createStripPlot: ["stripPlot", "point", ["fill", "stroke"]],
442
- createBeeswarmPlot: ["beeswarmPlot", "point", ["fill", "stroke"]],
443
- createBarPlot: ["barPlot", "bar", ["fill", "stroke"]],
444
- createAreaPlot: ["areaPlot", "area", ["fill", "stroke"]],
445
- createPiePlot: ["piePlot", "arc", ["fill", "stroke"]],
446
- createRosePlot: ["rosePlot", "arc", ["fill", "stroke"]],
447
- createRadialBarPlot: ["radialBarPlot", "arc", ["fill", "stroke"]],
448
- createDensityPlot: ["densityPlot", "area", ["fill", "stroke"]],
449
- createParallelCoordinates: ["parallelCoordinates", "line", ["stroke"]],
450
- createHistogram: ["histogram", "bar", ["fill", "stroke"]],
451
- createHeatmap: ["heatmap", "rect", ["stroke"]],
452
- createHorizonPlot: ["horizonPlot", "area", ["stroke"]],
453
- editHorizon: ["horizonPlot", "area", ["stroke"]],
454
- createViolinPlot: ["violinPlot", "area", ["fill", "stroke"]]
438
+ createScatterPlot: ["point", true],
439
+ createLinePlot: ["line", false],
440
+ createPolarScatterPlot: ["point", true],
441
+ createPolarLinePlot: ["line", false],
442
+ createRadarPlot: ["line", false],
443
+ createRugPlot: ["tick", false],
444
+ createStripPlot: ["point", true],
445
+ createBeeswarmPlot: ["point", true],
446
+ createBarPlot: ["bar", true],
447
+ createAreaPlot: ["area", true],
448
+ createPiePlot: ["arc", true],
449
+ createRosePlot: ["arc", true],
450
+ createRadialBarPlot: ["arc", true],
451
+ createDensityPlot: ["area", true],
452
+ createParallelCoordinates: ["line", false],
453
+ createHistogram: ["bar", true],
454
+ createHeatmap: ["rect", false],
455
+ createHorizonPlot: ["area", false],
456
+ editHorizon: ["area", false],
457
+ createViolinPlot: ["area", true]
455
458
  };
456
459
  const facade = facades[op];
457
460
  if (facade === undefined) return;
458
- const [defaultId, option, properties] = facade;
461
+ const [option, includesFill] = facade;
462
+ const subject = op === "editHorizon" ? "HorizonPlot" : op.slice(6);
463
+ const defaultId = subject[0].toLowerCase() + subject.slice(1);
464
+ const properties = includesFill ? ["fill", "stroke"] : ["stroke"];
459
465
  const id = args.id ?? defaultId;
460
466
  for (const property of properties) {
461
467
  if (!Object.hasOwn(args[option] ?? {}, property)) continue;
462
- overrides.add(`c:marks.${id}.${property}`);
463
- overrides.add(`g:${id}.${property}`);
468
+ addMarkPropertyOverrides(overrides, id, property);
464
469
  }
465
470
  }
466
471
 
@@ -492,8 +497,7 @@ function addTextFacadeOverrides(overrides, node) {
492
497
  if (typeof id !== "string") return;
493
498
  for (const property of ["fill", "fontFamily"]) {
494
499
  if (!Object.hasOwn(node.args, property)) continue;
495
- overrides.add(`c:marks.${id}.${property}`);
496
- overrides.add(`g:${id}.${property}`);
500
+ addMarkPropertyOverrides(overrides, id, property);
497
501
  }
498
502
  }
499
503
 
@@ -509,8 +513,7 @@ function addErrorBarOverrides(overrides, program, node) {
509
513
  if (id === undefined) return;
510
514
  const config = program.markConfigs[id].errorBar;
511
515
  for (const target of [id, config.lowerCapId, config.upperCapId]) {
512
- overrides.add(`c:marks.${target}.stroke`);
513
- overrides.add(`g:${target}.stroke`);
516
+ addMarkPropertyOverrides(overrides, target, "stroke");
514
517
  }
515
518
  }
516
519
 
@@ -536,8 +539,7 @@ function addErrorBandOverrides(overrides, program, node) {
536
539
  const targets = errorBandTargets(program, node);
537
540
  if (targets === undefined) return;
538
541
  if (Object.hasOwn(node.args, "fill") && node.args.fill !== false) {
539
- overrides.add(`c:marks.${targets.id}.fill`);
540
- overrides.add(`g:${targets.id}.fill`);
542
+ addMarkPropertyOverrides(overrides, targets.id, "fill");
541
543
  }
542
544
  const boundary = node.op === "editErrorBandBoundary"
543
545
  ? node.args
@@ -548,8 +550,7 @@ function addErrorBandOverrides(overrides, program, node) {
548
550
  ? [targets.lower, targets.upper]
549
551
  : [(node.args.boundary === "lower" ? targets.lower : targets.upper)];
550
552
  for (const id of ids) {
551
- overrides.add(`c:marks.${id}.stroke`);
552
- overrides.add(`g:${id}.stroke`);
553
+ addMarkPropertyOverrides(overrides, id, "stroke");
553
554
  }
554
555
  }
555
556
 
@@ -589,8 +590,7 @@ function addReferenceOverrides(overrides, op, args) {
589
590
  const id = args.id ?? defaultId;
590
591
  for (const property of properties) {
591
592
  if (!Object.hasOwn(args, property)) continue;
592
- overrides.add(`c:marks.${id}.${property}`);
593
- overrides.add(`g:${id}.${property}`);
593
+ addMarkPropertyOverrides(overrides, id, property);
594
594
  }
595
595
  }
596
596
 
@@ -611,12 +611,10 @@ function addRegressionOverrides(overrides, node) {
611
611
  : child?.args.id ?? child?.args.target;
612
612
  if (typeof id !== "string") return;
613
613
  if (Object.hasOwn(style ?? {}, "color")) {
614
- overrides.add(`c:marks.${id}.fill`);
615
- overrides.add(`g:${id}.fill`);
614
+ addMarkPropertyOverrides(overrides, id, "fill");
616
615
  }
617
616
  if (Object.hasOwn(style ?? {}, "stroke")) {
618
- overrides.add(`c:marks.${id}.stroke`);
619
- overrides.add(`g:${id}.stroke`);
617
+ addMarkPropertyOverrides(overrides, id, "stroke");
620
618
  }
621
619
  }
622
620
 
@@ -835,39 +833,18 @@ function addParallelOverrides(overrides, program, op, args) {
835
833
  }
836
834
 
837
835
  function addLegendOverrides(overrides, program, args) {
838
- const legend = program.guideConfigs.legend ?? {};
839
- const prefixes = {
840
- series: "seriesLegend",
841
- color: "colorLegend",
842
- stroke: "strokeLegend",
843
- interval: "colorLegend",
844
- gradient: "colorGradient",
845
- strokeInterval: "strokeInterval",
846
- strokeGradient: "strokeGradient",
847
- size: "sizeLegend",
848
- strokeWidth: "strokeWidthLegend",
849
- opacity: "opacityLegend"
850
- };
851
- for (const kind of Object.keys(legend)) {
852
- if (Object.hasOwn(args.labels ?? {}, "color")) {
853
- overrides.add(`c:guides.legend.${kind}.labels.color`);
854
- if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Labels.fill`);
855
- }
856
- if (Object.hasOwn(args.labels ?? {}, "fontFamily")) {
857
- overrides.add(`c:guides.legend.${kind}.labels.fontFamily`);
858
- if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Labels.fontFamily`);
859
- }
860
- if (Object.hasOwn(args.titleStyle ?? {}, "color")) {
861
- overrides.add(`c:guides.legend.${kind}.titleStyle.color`);
862
- if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Title.fill`);
863
- }
864
- if (Object.hasOwn(args.titleStyle ?? {}, "fontFamily")) {
865
- overrides.add(`c:guides.legend.${kind}.titleStyle.fontFamily`);
866
- if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Title.fontFamily`);
867
- }
868
- if (Object.hasOwn(args.border ?? {}, "color")) {
869
- overrides.add(`c:guides.legend.${kind}.border.color`);
870
- if (prefixes[kind]) overrides.add(`g:${prefixes[kind]}Background.stroke`);
836
+ for (const kind of Object.keys(program.guideConfigs.legend ?? {})) {
837
+ const prefix = legendPrefix(kind);
838
+ for (const [option, suffix, property, graphicProperty] of [
839
+ ["labels", "Labels", "color", "fill"],
840
+ ["labels", "Labels", "fontFamily", "fontFamily"],
841
+ ["titleStyle", "Title", "color", "fill"],
842
+ ["titleStyle", "Title", "fontFamily", "fontFamily"],
843
+ ["border", "Background", "color", "stroke"]
844
+ ]) {
845
+ if (!Object.hasOwn(args[option] ?? {}, property)) continue;
846
+ overrides.add(`c:guides.legend.${kind}.${option}.${property}`);
847
+ if (prefix) overrides.add(`g:${prefix}${suffix}.${graphicProperty}`);
871
848
  }
872
849
  }
873
850
  }
@@ -1002,51 +979,19 @@ function collectOverrides(program) {
1002
979
  return overrides;
1003
980
  }
1004
981
 
1005
- function callExisting(program, graphicId, operation) {
1006
- return program.graphicSpec.objects[graphicId] !== undefined &&
1007
- typeof program[operation] === "function"
1008
- ? program[operation]()
1009
- : program;
1010
- }
1011
-
1012
- function rematerializeThemeTypography(program) {
1013
- if (program.compositionSpec !== undefined) {
1014
- return program.compositionSpec.type === "facet"
1015
- ? program.materializeComposition()
1016
- : program;
1017
- }
1018
-
1019
- let next = program;
1020
- for (const layer of next.semanticSpec.layers) {
1021
- if (layer.mark?.type === "text" &&
1022
- next.graphicSpec.objects[layer.id] !== undefined &&
1023
- typeof next.rematerializeTextMark === "function") {
1024
- next = next.rematerializeTextMark({ id: layer.id });
1025
- }
1026
- }
1027
- for (const [id, operation] of [
1028
- ["xAxisLabels", "editXAxisLabels"],
1029
- ["yAxisLabels", "editYAxisLabels"],
1030
- ["xAxisTitle", "editXAxisTitle"],
1031
- ["yAxisTitle", "editYAxisTitle"],
1032
- ["thetaAxisLabels", "editThetaAxisLabels"],
1033
- ["radialAxisLabels", "editRadialAxisLabels"],
1034
- ["thetaAxisTitle", "editThetaAxisTitle"],
1035
- ["radialAxisTitle", "editRadialAxisTitle"]
1036
- ]) {
1037
- next = callExisting(next, id, operation);
1038
- }
1039
- if (Object.keys(next.guideConfigs.legend ?? {}).length > 0 &&
1040
- typeof next.rematerializeLegend === "function") {
1041
- next = next.rematerializeLegend();
1042
- }
1043
- next = callExisting(next, "chartTitle", "rematerializeTitle");
1044
- return next;
1045
- }
1046
-
1047
982
  export function reconcileProgramTheme(program, { source, metadata }) {
983
+ program = reconcileFollowingRegressions(program);
1048
984
  const storedState = program.materializationConfigs.theme;
1049
985
  if (storedState === undefined) return program;
986
+ // Data/context/trace-only transitions cannot change authored appearance.
987
+ // Preserve all visual identities instead of rescanning every existing mark.
988
+ if (program.graphicSpec === source.graphicSpec &&
989
+ program.materializationConfigs === source.materializationConfigs &&
990
+ program.children === source.children &&
991
+ program.compositionSpec === source.compositionSpec &&
992
+ ["layers", "scales", "coordinates", "guides", "title"].every(
993
+ key => program.semanticSpec[key] === source.semanticSpec[key]
994
+ )) return program;
1050
995
  const composition = program.compositionSpec !== undefined;
1051
996
  const state = normalizeThemeState(storedState, { composition });
1052
997
  const sourceState = normalizeThemeState(
@@ -1088,15 +1033,16 @@ export function reconcileProgramTheme(program, { source, metadata }) {
1088
1033
 
1089
1034
  const facetsChanged = configs.value.facets !==
1090
1035
  themed.materializationConfigs.facets;
1036
+ const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily;
1091
1037
  if (facetsChanged && next.compositionSpec?.type === "facet") {
1092
1038
  next = next.materializeComposition();
1093
- } else if (sourceTokens.fontFamily !== targetTokens.fontFamily) {
1094
- next = rematerializeThemeTypography(next);
1039
+ } else if (typographyChanged) {
1040
+ next = rematerializeTypography(next);
1095
1041
  }
1096
1042
 
1097
1043
  const parallelChanged = configs.value.guides.axis?.parallel !==
1098
1044
  themed.guideConfigs.axis?.parallel;
1099
- if (parallelChanged && typeof next.rematerializeParallelAxes === "function") {
1045
+ if (parallelChanged && !typographyChanged && typeof next.rematerializeParallelAxes === "function") {
1100
1046
  next = next.rematerializeParallelAxes();
1101
1047
  }
1102
1048
 
@@ -115,12 +115,12 @@ function titleTextAction(kind, create) {
115
115
  }));
116
116
  }
117
117
 
118
- export const editTitleText = titleTextAction("title", false);
119
- export const createTitleText = titleTextAction("title", true);
120
- export const editSubtitleText = titleTextAction("subtitle", false);
121
- export const createSubtitleText = titleTextAction("subtitle", true);
118
+ export const editTitleText = /* @__PURE__ */ titleTextAction("title", false);
119
+ export const createTitleText = /* @__PURE__ */ titleTextAction("title", true);
120
+ export const editSubtitleText = /* @__PURE__ */ titleTextAction("subtitle", false);
121
+ export const createSubtitleText = /* @__PURE__ */ titleTextAction("subtitle", true);
122
122
 
123
- export const rematerializeTitle = action(
123
+ export const rematerializeTitle = /* @__PURE__ */ action(
124
124
  { op: "rematerializeTitle", description: "Rematerialize chart title graphics." },
125
125
  withGuideLayoutValidation(function (args = {}) {
126
126
  noOptions(args, "rematerializeTitle");
@@ -142,7 +142,7 @@ export const rematerializeTitle = action(
142
142
  })
143
143
  );
144
144
 
145
- export const createTitle = action(
145
+ export const createTitle = /* @__PURE__ */ action(
146
146
  {
147
147
  op: "createTitle",
148
148
  description: "Create a chart title and optional subtitle.",
@@ -176,7 +176,7 @@ export const createTitle = action(
176
176
  })
177
177
  );
178
178
 
179
- export const editTitle = action(
179
+ export const editTitle = /* @__PURE__ */ action(
180
180
  {
181
181
  op: "editTitle",
182
182
  description: "Edit one stable chart title resource.",
@@ -214,7 +214,7 @@ export const editTitle = action(
214
214
  })
215
215
  );
216
216
 
217
- export const removeTitle = action(
217
+ export const removeTitle = /* @__PURE__ */ action(
218
218
  {
219
219
  op: "removeTitle",
220
220
  description: "Remove the complete chart title resource.",
@@ -212,7 +212,7 @@ export function resolveTitleLayout(program, config) {
212
212
  program.semanticSpec.title.subtitle,
213
213
  "Chart subtitle"
214
214
  )
215
- }, config);
215
+ }, config, program.materializationConfigs.textMetrics);
216
216
  const horizontal = ["top", "bottom"].includes(config.position);
217
217
  let component;
218
218
  if (horizontal) {
@@ -249,10 +249,10 @@ export function resolveTitleLayout(program, config) {
249
249
  ? undefined
250
250
  : component(block.subtitleLines, block.subtitleCenters);
251
251
  const titleBounds = unionTitleBounds([
252
- resolveTitleComponentBounds(title, config.titleStyle),
252
+ resolveTitleComponentBounds(title, config.titleStyle, program.materializationConfigs.textMetrics),
253
253
  ...(subtitle === undefined
254
254
  ? []
255
- : [resolveTitleComponentBounds(subtitle, config.subtitleStyle)])
255
+ : [resolveTitleComponentBounds(subtitle, config.subtitleStyle, program.materializationConfigs.textMetrics)])
256
256
  ]);
257
257
  validateLayout(
258
258
  config.position,
@@ -132,7 +132,7 @@ function guideOptions(guides, x, y, category, color) {
132
132
  };
133
133
  }
134
134
 
135
- export const createViolinPlot = action(
135
+ export const createViolinPlot = /* @__PURE__ */ action(
136
136
  {
137
137
  op: "createViolinPlot",
138
138
  description: "Create a categorical kernel-density violin plot."
@@ -74,7 +74,7 @@ function assertSplitSelectionCompatibility(program, owner, changesSplit) {
74
74
  }
75
75
  }
76
76
 
77
- export const editViolinPlot = action(
77
+ export const editViolinPlot = /* @__PURE__ */ action(
78
78
  {
79
79
  op: OPERATION,
80
80
  description: "Revise one violin plot's data and statistical roles."
@@ -1,4 +1,6 @@
1
- import { cloneAndFreeze, freezeOwned, isPlainObject } from "./immutable.js";
1
+ import { validateKeys } from "./validation.js";
2
+ import { annotateError } from "./diagnostics.js";
3
+ import { cloneAndFreeze, freezeOwned, isOwned, isPlainObject } from "./immutable.js";
2
4
 
3
5
  const metadataByWrappedAction = new WeakMap();
4
6
  const implementationByWrappedAction = new WeakMap();
@@ -35,6 +37,8 @@ function summarizeObject(value, ancestors = new WeakSet()) {
35
37
  summary[`${key}Count`] = item.length;
36
38
  } else if (isPlainObject(item)) {
37
39
  summary[key] = summarizeObject(item, ancestors);
40
+ } else if (typeof item === "function") {
41
+ summary[`${key}Type`] = "function";
38
42
  } else if (item !== null && typeof item === "object") {
39
43
  summary[`${key}Type`] = item.constructor?.name ?? "object";
40
44
  } else {
@@ -71,13 +75,59 @@ export function createActionNode({ id, op, description, args }) {
71
75
  });
72
76
  }
73
77
 
78
+ // Persistent child tails make appending the active (last) branch independent
79
+ // of sibling count. The public children property remains a stable frozen Array.
80
+ const childLists = new WeakMap();
81
+
82
+ function childList(node) {
83
+ let list = childLists.get(node);
84
+ if (list !== undefined) return list;
85
+ let tail;
86
+ for (const child of node.children) tail = { value: child, previous: tail };
87
+ list = { tail, length: node.children.length };
88
+ if (isOwned(node)) childLists.set(node, list);
89
+ return list;
90
+ }
91
+
92
+ function withChildList(node, list) {
93
+ let children;
94
+ const next = {};
95
+ for (const key of Object.keys(node)) {
96
+ if (key !== "children") Object.defineProperty(next, key, {
97
+ value: node[key], enumerable: true
98
+ });
99
+ }
100
+ Object.defineProperty(next, "children", {
101
+ enumerable: true,
102
+ get() {
103
+ if (children === undefined) {
104
+ const values = new Array(list.length);
105
+ let item = list.tail;
106
+ for (let index = list.length - 1; index >= 0; index -= 1) {
107
+ values[index] = item.value;
108
+ item = item.previous;
109
+ }
110
+ children = freezeOwned(values);
111
+ }
112
+ return children;
113
+ }
114
+ });
115
+ childLists.set(next, list);
116
+ return freezeOwned(next);
117
+ }
118
+
119
+ function childAt(node, index) {
120
+ const list = childList(node);
121
+ return index === list.length - 1 ? list.tail.value : node.children[index];
122
+ }
123
+
74
124
  function nodeAtPath(root, path) {
75
125
  let node = root;
76
126
  for (const index of path) {
77
- if (!Number.isInteger(index) || index < 0 || index >= node.children.length) {
127
+ if (!Number.isInteger(index) || index < 0 || index >= childList(node).length) {
78
128
  throw new Error(`Unknown parent action path "${path.join(".")}".`);
79
129
  }
80
- node = node.children[index];
130
+ node = childAt(node, index);
81
131
  }
82
132
  return node;
83
133
  }
@@ -87,19 +137,28 @@ export function appendActionNodeAtPath(root, parentPath, actionNode) {
87
137
  throw new TypeError("Parent action path must be an array.");
88
138
  }
89
139
  const parent = nodeAtPath(root, parentPath);
90
- const path = [...parentPath, parent.children.length];
140
+ const path = [...parentPath, childList(parent).length];
91
141
 
92
142
  function append(node, depth) {
143
+ const list = childList(node);
93
144
  if (depth === parentPath.length) {
94
- return freezeOwned({
95
- ...node,
96
- children: freezeOwned([...node.children, actionNode])
145
+ return withChildList(node, {
146
+ tail: { value: actionNode, previous: list.tail }, length: list.length + 1
97
147
  });
98
148
  }
99
149
  const index = parentPath[depth];
100
- const children = [...node.children];
101
- children[index] = append(children[index], depth + 1);
102
- return freezeOwned({ ...node, children: freezeOwned(children) });
150
+ const child = append(childAt(node, index), depth + 1);
151
+ if (index === list.length - 1) {
152
+ return withChildList(node, {
153
+ tail: { value: child, previous: list.tail.previous }, length: list.length
154
+ });
155
+ }
156
+ // Restored/extension traces may address an older sibling explicitly.
157
+ let tail;
158
+ for (let position = 0; position < list.length; position += 1) {
159
+ tail = { value: position === index ? child : node.children[position], previous: tail };
160
+ }
161
+ return withChildList(node, { tail, length: list.length });
103
162
  }
104
163
 
105
164
  return { root: append(root, 0), path };
@@ -133,43 +192,57 @@ export function action(metadata, implementation) {
133
192
  const ownedMetadata = freezeOwned({
134
193
  op: metadata.op,
135
194
  description: metadata.description,
136
- scope
195
+ scope,
196
+ ...(Array.isArray(metadata.options) ? { options: [...metadata.options] } : {})
137
197
  });
138
198
 
139
199
  const wrappedAction = function wrappedAction(args = {}) {
140
- if (!isPlainObject(args)) {
141
- throw new TypeError("Action arguments must be a plain object.");
142
- }
143
-
144
- if (scope === "unit") this._assertUnitProgram(ownedMetadata.op);
145
- if (scope === "composition") this._assertCompositionProgram(ownedMetadata.op);
146
-
147
- const summarizedArgs = summarizeArgs(args);
148
- const entered = this._enterAction({
149
- ...ownedMetadata,
150
- args: summarizedArgs
151
- });
152
- let result = implementation.call(entered, args);
153
-
154
- if (!(result instanceof this.constructor)) {
155
- throw new TypeError(`${ownedMetadata.op} must return a ChartProgram.`);
156
- }
157
-
158
- if (this.actionStack.length === 0 && actionCompletionHook !== undefined) {
159
- result = actionCompletionHook(result, {
160
- source: this,
161
- metadata: ownedMetadata,
200
+ try {
201
+ if (!isPlainObject(args)) {
202
+ throw new TypeError("Action arguments must be a plain object.");
203
+ }
204
+
205
+ if (scope === "unit") this._assertUnitProgram(ownedMetadata.op);
206
+ if (scope === "composition") this._assertCompositionProgram(ownedMetadata.op);
207
+
208
+ const summarizedArgs = summarizeArgs(args);
209
+ const entered = this._enterAction({
210
+ ...ownedMetadata,
162
211
  args: summarizedArgs
163
212
  });
213
+ let result = implementation.call(entered, args);
214
+
164
215
  if (!(result instanceof this.constructor)) {
165
- throw new TypeError("Action completion hook must return a ChartProgram.");
216
+ throw new TypeError(`${ownedMetadata.op} must return a ChartProgram.`);
217
+ }
218
+
219
+ if (this.actionStack.length === 0 && actionCompletionHook !== undefined) {
220
+ result = actionCompletionHook(result, {
221
+ source: this,
222
+ metadata: ownedMetadata,
223
+ args: summarizedArgs
224
+ });
225
+ if (!(result instanceof this.constructor)) {
226
+ throw new TypeError("Action completion hook must return a ChartProgram.");
227
+ }
166
228
  }
229
+
230
+ return result._exitAction();
231
+ } catch (error) {
232
+ throw annotateError(error, { code: "action-failed", operation: ownedMetadata.op });
167
233
  }
168
-
169
- return result._exitAction();
170
234
  };
171
235
 
172
236
  metadataByWrappedAction.set(wrappedAction, ownedMetadata);
173
237
  implementationByWrappedAction.set(wrappedAction, implementation);
174
238
  return wrappedAction;
175
239
  }
240
+
241
+ // Private built-in convenience: action() owns the object boundary and trace,
242
+ // while each definition supplies its closed option vocabulary exactly once.
243
+ export function closedAction(metadata, options, implementation) {
244
+ return action({ ...metadata, options }, function (args = {}) {
245
+ if (options !== undefined) validateKeys(args, options, metadata.op);
246
+ return implementation.call(this, args);
247
+ });
248
+ }
@@ -0,0 +1,16 @@
1
+ const detailsByError = new WeakMap();
2
+
3
+ export function getErrorDetails(error) {
4
+ return error !== null && (typeof error === "object" || typeof error === "function")
5
+ ? detailsByError.get(error) : undefined;
6
+ }
7
+
8
+ // Attach metadata without modifying the thrown value or its existing class.
9
+ export function annotateError(error, details) {
10
+ if (!(error instanceof Error)) return error;
11
+ const previous = getErrorDetails(error);
12
+ const merged = { ...details, ...previous };
13
+ if (merged.candidates !== undefined) merged.candidates = Object.freeze([...merged.candidates]);
14
+ detailsByError.set(error, Object.freeze(merged));
15
+ return error;
16
+ }
@@ -4,6 +4,32 @@ import { isPlainObject } from "./immutable.js";
4
4
  const ACTION_NAME_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
5
5
  const DEFINITION_KEYS = new Set(["name", "actions"]);
6
6
  const registeredExtensionNames = new Set();
7
+ const extensionByAction = new Map();
8
+ const builtinOpsByClass = new WeakMap();
9
+
10
+ export function recordBuiltinActions(ProgramClass, additional = []) {
11
+ builtinOpsByClass.set(ProgramClass, new Set([
12
+ ...Object.getOwnPropertyNames(ProgramClass.prototype).flatMap(name => {
13
+ const metadata = getWrappedActionMetadata(ProgramClass.prototype[name]);
14
+ return metadata ? [metadata.op] : [];
15
+ }), ...additional
16
+ ]));
17
+ }
18
+
19
+ export function isBuiltinProgramClass(ProgramClass) {
20
+ return builtinOpsByClass.has(ProgramClass);
21
+ }
22
+
23
+ export function requiredTraceExtension(ProgramClass, op) {
24
+ if (op === "program" || builtinOpsByClass.get(ProgramClass)?.has(op)) return undefined;
25
+ const extension = extensionByAction.get(op);
26
+ if (extension !== undefined) return extension;
27
+ throw new TypeError(`Cannot persist unregistered action "${op}".`);
28
+ }
29
+
30
+ export function hasRegisteredExtension(name) {
31
+ return registeredExtensionNames.has(name);
32
+ }
7
33
 
8
34
  function readDataProperty(object, key, owner) {
9
35
  const descriptor = Object.getOwnPropertyDescriptor(object, key);
@@ -101,4 +127,5 @@ export function registerProgramExtension(ProgramClass, definition) {
101
127
 
102
128
  Object.defineProperties(ProgramClass.prototype, descriptors);
103
129
  registeredExtensionNames.add(name);
130
+ for (const actionName of actionNames) extensionByAction.set(actionName, name);
104
131
  }
@@ -0,0 +1,18 @@
1
+ export const DEFAULT_FONT_FAMILY = "sans-serif";
2
+
3
+ export function normalizeRendererFontWeight(fontWeight) {
4
+ if (typeof fontWeight !== "number") return fontWeight;
5
+ return Math.min(900, Math.max(100, Math.round(fontWeight / 100) * 100));
6
+ }
7
+
8
+ export function textMetricKey({ text, fontFamily, fontSize, fontWeight }) {
9
+ return JSON.stringify([text, fontFamily, fontSize, fontWeight]);
10
+ }
11
+
12
+ export function textMetricFontWeight(value = "normal") {
13
+ if (typeof value === "number") return normalizeRendererFontWeight(value);
14
+ if (value === "normal") return 400;
15
+ if (value === "bold") return 700;
16
+ if (/^[1-9]00$/u.test(value)) return Number(value);
17
+ return undefined;
18
+ }