ggaction 0.0.16 → 0.0.18

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 (299) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +3 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +10 -4
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/bin.js +1 -1
  34. package/src/actions/data/box.js +2 -1
  35. package/src/actions/data/create.js +18 -5
  36. package/src/actions/data/density.js +1 -1
  37. package/src/actions/data/derived.js +15 -1
  38. package/src/actions/data/edit.js +20 -7
  39. package/src/actions/data/filter.js +15 -4
  40. package/src/actions/data/index.js +3 -0
  41. package/src/actions/data/interval.js +2 -0
  42. package/src/actions/data/regression.js +6 -2
  43. package/src/actions/data/revise.js +37 -5
  44. package/src/actions/data/shared.js +33 -4
  45. package/src/actions/data/sort.js +21 -0
  46. package/src/actions/data/stack.js +3 -1
  47. package/src/actions/data/summary.js +1 -1
  48. package/src/actions/encodings/angle.js +4 -1
  49. package/src/actions/encodings/appearance.js +43 -31
  50. package/src/actions/encodings/barWidth.js +36 -11
  51. package/src/actions/encodings/channels.js +0 -1
  52. package/src/actions/encodings/color/continuous.js +5 -2
  53. package/src/actions/encodings/color/index.js +5 -2
  54. package/src/actions/encodings/color/policy.js +10 -3
  55. package/src/actions/encodings/density/resolve.js +1 -6
  56. package/src/actions/encodings/offset.js +4 -1
  57. package/src/actions/encodings/position/policies/arc.js +12 -10
  58. package/src/actions/encodings/position/policies/area.js +3 -3
  59. package/src/actions/encodings/position/policies/bar.js +15 -6
  60. package/src/actions/encodings/position/policies/line.js +17 -6
  61. package/src/actions/encodings/position/resolve.js +9 -3
  62. package/src/actions/encodings/ranged.js +23 -1
  63. package/src/actions/encodings/remove.js +3 -2
  64. package/src/actions/encodings/ruleAppearance.js +4 -1
  65. package/src/actions/encodings/seriesLayout.js +1 -0
  66. package/src/actions/encodings/stroke.js +4 -1
  67. package/src/actions/encodings/strokeDash.js +6 -1
  68. package/src/actions/encodings/text.js +12 -4
  69. package/src/actions/errorBands/create.js +2 -2
  70. package/src/actions/errorBands/edit.js +2 -1
  71. package/src/actions/facets/actions.js +158 -240
  72. package/src/actions/gradientPlots/resolve.js +2 -17
  73. package/src/actions/guides/applicability.js +1 -1
  74. package/src/actions/guides/axes/axis.js +16 -4
  75. package/src/actions/guides/axes/edit.js +4 -4
  76. package/src/actions/guides/axes/labels.js +120 -46
  77. package/src/actions/guides/axes/lines.js +6 -5
  78. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  79. package/src/actions/guides/axes/parallel/policy.js +8 -7
  80. package/src/actions/guides/axes/remove.js +3 -2
  81. package/src/actions/guides/axes/tickGroups.js +5 -2
  82. package/src/actions/guides/axes/ticks.js +6 -3
  83. package/src/actions/guides/axes/titles.js +71 -26
  84. package/src/actions/guides/basic.js +2 -0
  85. package/src/actions/guides/facadeLegend.js +10 -2
  86. package/src/actions/guides/grids/grid.js +7 -1
  87. package/src/actions/guides/grids/resolve.js +59 -12
  88. package/src/actions/guides/legends/blocks.js +3 -1
  89. package/src/actions/guides/legends/categorical/actions.js +70 -17
  90. package/src/actions/guides/legends/categorical/components.js +12 -8
  91. package/src/actions/guides/legends/categorical/index.js +6 -0
  92. package/src/actions/guides/legends/categorical/layout.js +37 -8
  93. package/src/actions/guides/legends/categorical/options.js +31 -20
  94. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  95. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  96. package/src/actions/guides/legends/categorical/validation.js +1 -6
  97. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  98. package/src/actions/guides/legends/continuous/common.js +1 -447
  99. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  100. package/src/actions/guides/legends/continuous/interval.js +4 -4
  101. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  102. package/src/actions/guides/legends/edit.js +1 -604
  103. package/src/actions/guides/legends/focused.js +2 -2
  104. package/src/actions/guides/legends/group.js +1 -0
  105. package/src/actions/guides/legends/lane.js +1 -516
  106. package/src/actions/guides/legends/remove.js +3 -0
  107. package/src/actions/guides/legends/sampling.js +1 -1
  108. package/src/actions/guides/legends/size.js +51 -43
  109. package/src/actions/guides/legends/strokeWidth.js +11 -29
  110. package/src/actions/guides/legends/target.js +3 -3
  111. package/src/actions/guides/legends/transition.js +21 -7
  112. package/src/actions/guides/polar/axes/labels.js +4 -2
  113. package/src/actions/guides/polar/axes/titles.js +7 -5
  114. package/src/actions/guides/polar/resolve.js +6 -14
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +17 -4
  120. package/src/actions/marks/bar/edit.js +18 -7
  121. package/src/actions/marks/bar/materialize.js +9 -6
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/create.js +8 -1
  125. package/src/actions/marks/point/edit.js +13 -6
  126. package/src/actions/marks/point/materialize.js +4 -2
  127. package/src/actions/marks/rect/actions.js +26 -11
  128. package/src/actions/marks/remove.js +2 -0
  129. package/src/actions/marks/rule/actions.js +31 -12
  130. package/src/actions/marks/text/actions.js +42 -11
  131. package/src/actions/marks/text/layout.js +3 -0
  132. package/src/actions/marks/tick/actions.js +23 -8
  133. package/src/actions/primitives/semanticAction.js +37 -3
  134. package/src/actions/primitives/semanticValidation/dataset.js +13 -1
  135. package/src/actions/primitives/semanticValidation/layer.js +26 -2
  136. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  137. package/src/actions/regression/create.js +26 -5
  138. package/src/actions/regression/edit.js +50 -15
  139. package/src/actions/regression/follow.js +22 -0
  140. package/src/actions/regression/resolve.js +2 -6
  141. package/src/actions/scales/channels.js +20 -15
  142. package/src/actions/scales/consumers/common.js +2 -1
  143. package/src/actions/scales/consumers/families.js +16 -5
  144. package/src/actions/scales/create.js +10 -4
  145. package/src/actions/scales/definitions.js +1 -425
  146. package/src/actions/scales/edit.js +3 -3
  147. package/src/actions/scales/editPolicy.js +9 -3
  148. package/src/actions/scales/materialize.js +1 -1
  149. package/src/actions/scales/parallel.js +1 -1
  150. package/src/actions/scales/patch.js +1 -1
  151. package/src/actions/scales/preview.js +49 -2
  152. package/src/actions/scales/quantitativeColor.js +3 -3
  153. package/src/actions/selection/actions.js +1 -13
  154. package/src/actions/theme/reconcile.js +83 -27
  155. package/src/actions/theme/state.js +12 -0
  156. package/src/actions/titles/actions.js +5 -18
  157. package/src/actions/titles/resolve.js +41 -19
  158. package/src/actions/violinPlots/edit.js +3 -20
  159. package/src/core/action.js +3 -2
  160. package/src/core/compositionState.js +4 -1
  161. package/src/core/font.js +9 -2
  162. package/src/core/optionRequirements.js +1 -0
  163. package/src/core/programState.js +5 -1
  164. package/src/core/resourceReferences.js +3 -0
  165. package/src/core/textMetricProfile.js +3 -2
  166. package/src/core/textMetrics.js +41 -4
  167. package/src/core/validation.js +26 -0
  168. package/src/core/vocabulary.js +4 -0
  169. package/src/grammar/aggregate.js +4 -18
  170. package/src/grammar/arcs.js +1 -386
  171. package/src/grammar/areaPaint.js +10 -0
  172. package/src/grammar/areaSeries.js +18 -10
  173. package/src/grammar/bars/geometry.js +30 -3
  174. package/src/grammar/bars/policy.js +12 -1
  175. package/src/grammar/bin.js +41 -22
  176. package/src/grammar/bin2d.js +1 -6
  177. package/src/grammar/boxPlot.js +56 -9
  178. package/src/grammar/categoryOrder.js +1 -6
  179. package/src/grammar/complete.js +15 -19
  180. package/src/grammar/computed.js +92 -25
  181. package/src/grammar/curveCommands.js +29 -16
  182. package/src/grammar/datasetSchema.js +382 -0
  183. package/src/grammar/density.js +53 -5
  184. package/src/grammar/displayLabels.js +28 -0
  185. package/src/grammar/ecdf.js +2 -9
  186. package/src/grammar/facets/dependencies.js +6 -2
  187. package/src/grammar/facets/guides.js +5 -0
  188. package/src/grammar/facets/index.js +2 -5
  189. package/src/grammar/filter.js +133 -39
  190. package/src/grammar/fold.js +2 -12
  191. package/src/grammar/gradientProfile.js +1 -6
  192. package/src/grammar/impute.js +21 -48
  193. package/src/grammar/interval.js +34 -10
  194. package/src/grammar/itemMissing.js +59 -0
  195. package/src/grammar/legendGradient.js +18 -0
  196. package/src/grammar/legendGroups.js +1 -0
  197. package/src/grammar/legendTitleStyle.js +22 -0
  198. package/src/grammar/lineSeries.js +32 -17
  199. package/src/grammar/markLabels.js +5 -2
  200. package/src/grammar/normalize.js +4 -54
  201. package/src/grammar/numeric.js +1 -290
  202. package/src/grammar/parallelCoordinates.js +1 -6
  203. package/src/grammar/parseTime.js +41 -0
  204. package/src/grammar/pathCommands.js +11 -4
  205. package/src/grammar/polar.js +1 -184
  206. package/src/grammar/positionCompatibility.js +3 -3
  207. package/src/grammar/regression/derive.js +63 -16
  208. package/src/grammar/regression/index.js +1 -0
  209. package/src/grammar/regression/models.js +116 -59
  210. package/src/grammar/regression/parameters.js +69 -6
  211. package/src/grammar/roundedRect.js +42 -54
  212. package/src/grammar/scales/appearance.js +1 -206
  213. package/src/grammar/scales/color.js +1 -382
  214. package/src/grammar/scales/colorConsumers.js +5 -2
  215. package/src/grammar/scales/definition.js +1 -190
  216. package/src/grammar/scales/discretized.js +1 -195
  217. package/src/grammar/scales/size.js +1 -456
  218. package/src/grammar/scales/temporal.js +2 -13
  219. package/src/grammar/scales/transformed.js +1 -383
  220. package/src/grammar/scales/types.js +1 -1
  221. package/src/grammar/scales/validation.js +1 -66
  222. package/src/grammar/schemas/concreteGraphic.js +11 -0
  223. package/src/grammar/schemas/graphic.js +3 -0
  224. package/src/grammar/schemas/semanticPath.js +5 -3
  225. package/src/grammar/sort.js +88 -0
  226. package/src/grammar/stack.js +2 -10
  227. package/src/grammar/statisticalReference.js +1 -6
  228. package/src/grammar/summary.js +120 -45
  229. package/src/grammar/text.js +19 -0
  230. package/src/grammar/timeUnit.js +16 -7
  231. package/src/grammar/transformKeys.js +47 -0
  232. package/src/grammar/transformTopology.js +1 -0
  233. package/src/grammar/transforms.js +25 -12
  234. package/src/grammar/valueFormat.js +17 -5
  235. package/src/grammar/weightedStatistics.js +1 -6
  236. package/src/grammar/window.js +4 -58
  237. package/src/inspection.js +495 -0
  238. package/src/inspectionDescriptors.js +3 -0
  239. package/src/layout/canvas.js +1 -110
  240. package/src/layout/facets.js +1 -244
  241. package/src/layout/labels.js +1 -0
  242. package/src/layout/legend.js +13 -0
  243. package/src/layout/legendGroup.js +1 -0
  244. package/src/layout/legendItems.js +59 -15
  245. package/src/layout/legendLane.js +1 -441
  246. package/src/layout/legendText.js +1 -0
  247. package/src/layout/title.js +35 -13
  248. package/src/materialization/bars/aggregate.js +11 -5
  249. package/src/materialization/bars/histogram.js +4 -1
  250. package/src/materialization/bars/ranged.js +19 -6
  251. package/src/materialization/bars/resolve.js +26 -3
  252. package/src/materialization/coordinateBounds.js +8 -0
  253. package/src/materialization/dependencies.js +4 -44
  254. package/src/materialization/facetGuides/layout.js +1 -0
  255. package/src/materialization/facetGuides/placement.js +19 -8
  256. package/src/materialization/facetGuides/preparation.js +15 -7
  257. package/src/materialization/facetHeaders.js +1 -347
  258. package/src/materialization/facets.js +1 -185
  259. package/src/materialization/graphicHierarchy.js +1 -1
  260. package/src/materialization/guides/layout.js +1 -57
  261. package/src/materialization/guides/resources.js +1 -123
  262. package/src/materialization/layout.js +53 -0
  263. package/src/materialization/legendGroups.js +1 -0
  264. package/src/materialization/legendText.js +1 -0
  265. package/src/materialization/legends.js +1 -1
  266. package/src/materialization/marks/capabilities.js +5 -6
  267. package/src/materialization/rect.js +1 -1
  268. package/src/materialization/revisionIdentity.js +19 -0
  269. package/src/materialization/scaleGuideDependencies.js +1 -1
  270. package/src/materialization/scales/map.js +3 -0
  271. package/src/materialization/scales/policies/arc.js +8 -7
  272. package/src/materialization/scales/policies/bar.js +19 -7
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +72 -7
  275. package/src/materialization/selection/items/arc.js +1 -1
  276. package/src/materialization/selection/items/bar.js +7 -6
  277. package/src/materialization/selection/items/path.js +7 -6
  278. package/src/materialization/selection/items/rect.js +2 -2
  279. package/src/materialization/text.js +17 -6
  280. package/src/materialization/textLines.js +15 -0
  281. package/src/persistence/codec.js +37 -6
  282. package/src/persistence/validation.js +47 -0
  283. package/src/persistence.js +94 -8
  284. package/src/renderers/canvas/circle.js +6 -3
  285. package/src/renderers/canvas/fill.js +7 -1
  286. package/src/renderers/canvas/index.js +2 -1
  287. package/src/renderers/canvas/line.js +1 -0
  288. package/src/renderers/canvas/path.js +1 -1
  289. package/src/renderers/canvas/rect.js +11 -8
  290. package/src/renderers/canvas/stroke.js +2 -1
  291. package/src/renderers/canvas/text.js +5 -3
  292. package/src/renderers/svg.js +4 -1
  293. package/src/selectors/layers.js +19 -0
  294. package/src/theme/defaults.js +8 -2
  295. package/src/version.js +1 -1
  296. package/types/basic.d.ts +1 -0
  297. package/types/index.d.ts +7 -0
  298. package/types/inspection.d.ts +17 -0
  299. package/types/program.d.ts +494 -112
@@ -19,7 +19,6 @@ import {
19
19
  findSelectionPolicy,
20
20
  requireSelectionPolicy
21
21
  } from "../../materialization/selection/policies/index.js";
22
- import { legendGraphicIds } from "../../materialization/guides/resources.js";
23
22
  import {
24
23
  normalizeDimOthers,
25
24
  validateUnitInterval
@@ -84,18 +83,7 @@ function categoricalLegendKinds(program, target) {
84
83
  function resetCategoricalLegendSymbols(program, target) {
85
84
  const kinds = categoricalLegendKinds(program, target);
86
85
  if (kinds.length === 0) return program;
87
- let next = program;
88
- for (const kind of kinds) {
89
- for (const id of legendGraphicIds(kind).filter(id => id.includes("Symbol"))) {
90
- const graphic = next.graphicSpec.objects[id];
91
- if (graphic === undefined) continue;
92
- next = graphic.type === "collection"
93
- ? next.editGraphics({ target: id, property: "items", value: [] })
94
- : next.editGraphics({ target: id, property: "length", value: 0 });
95
- }
96
- next = next.rematerializeLegendSymbols({ kind });
97
- }
98
- return next;
86
+ return program.rematerializeLegend();
99
87
  }
100
88
 
101
89
  function targetHighlightEntries(program, target) {
@@ -1,11 +1,12 @@
1
1
  import { rematerializeTypography } from "../../materialization/typography.js";
2
- import { themeTokens } from "../../theme/defaults.js";
2
+ import { AXIS_LABEL_FONT_SIZES, AXIS_TITLE_FONT_SIZE, themeTokens } from "../../theme/defaults.js";
3
3
  import {
4
4
  normalizeThemeState,
5
5
  resolveEffectiveThemeTokens,
6
6
  setThemeStateOverrides
7
7
  } from "./state.js";
8
8
  import { findLayer } from "../../selectors/layers.js";
9
+ import { reconcileFollowingRegressions } from "../regression/follow.js";
9
10
 
10
11
  let rematerializeThemeHighlights = program => program;
11
12
 
@@ -20,7 +21,7 @@ const THEME_STYLE_PROPERTIES = Object.freeze([
20
21
  "background",
21
22
  "fill",
22
23
  "stroke",
23
- "fontFamily"
24
+ "fontFamily", "fontSize"
24
25
  ]);
25
26
  const COLOR_TOKENS = Object.freeze([
26
27
  "mark",
@@ -50,15 +51,38 @@ function tokenFor(value, sourceTokens, targetTokens) {
50
51
  return matches[0];
51
52
  }
52
53
 
53
- function roleValues(role, sourceTokens, legacy = []) {
54
+ function axisFamily(name) {
55
+ return name.startsWith("parallel") ? "parallel"
56
+ : /^(theta|radial|radius)/u.test(name) ? "polar" : "cartesian";
57
+ }
58
+
59
+ function axisTextRole(property, component) {
60
+ if (!["labels", "title"].includes(component)) return undefined;
61
+ const role = component === "labels" ? "axisLabel" : "axisTitle";
62
+ if (property === "fontFamily") return `${role}FontFamily`;
63
+ if (property === "fontSize") return `${role}FontSize`;
64
+ if (["fill", "color"].includes(property)) return role;
65
+ return undefined;
66
+ }
67
+
68
+ function roleToken(tokens, role, family = "cartesian") {
69
+ if (tokens[role] !== undefined) return tokens[role];
70
+ if (role === "axisLabel") return family === "parallel" ? tokens.axis : tokens.text;
71
+ if (["axisLabelFontFamily", "axisTitleFontFamily"].includes(role)) return tokens.fontFamily;
72
+ if (role === "axisLabelFontSize") return AXIS_LABEL_FONT_SIZES[family];
73
+ if (role === "axisTitleFontSize") return AXIS_TITLE_FONT_SIZE;
74
+ return undefined;
75
+ }
76
+
77
+ function roleValues(role, sourceTokens, legacy = [], family) {
54
78
  return new Set([
55
- sourceTokens[role],
56
- themeTokens("light")[role],
79
+ roleToken(sourceTokens, role, family),
80
+ roleToken(themeTokens("light"), role, family),
57
81
  ...legacy.map(token => themeTokens("light")[token] ?? token)
58
82
  ]);
59
83
  }
60
84
 
61
- function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
85
+ function mapRoleValue(value, role, sourceTokens, targetTokens, legacy, family) {
62
86
  if (Array.isArray(value)) {
63
87
  let changed = false;
64
88
  const next = value.map(item => {
@@ -67,17 +91,18 @@ function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
67
91
  role,
68
92
  sourceTokens,
69
93
  targetTokens,
70
- legacy
94
+ legacy, family
71
95
  );
72
96
  changed ||= mapped.changed;
73
97
  return mapped.value;
74
98
  });
75
99
  return { changed, value: changed ? next : value };
76
100
  }
77
- if (!roleValues(role, sourceTokens, legacy).has(value)) {
101
+ if (!roleValues(role, sourceTokens, legacy, family).has(value)) {
78
102
  return { changed: false, value };
79
103
  }
80
- return { changed: value !== targetTokens[role], value: targetTokens[role] };
104
+ const target = roleToken(targetTokens, role, family);
105
+ return { changed: value !== target, value: target };
81
106
  }
82
107
 
83
108
  function mapColorValue(value, sourceTokens, targetTokens) {
@@ -157,6 +182,9 @@ function componentRole(program, id, property) {
157
182
  }
158
183
 
159
184
  function graphicRole(program, id, property) {
185
+ const axisPart = /Axis(Labels|Titles?)$/u.exec(id);
186
+ const scoped = axisPart === null ? undefined : axisTextRole(property, axisPart[1] === "Labels" ? "labels" : "title");
187
+ if (scoped !== undefined) return scoped;
160
188
  if (property === "fontFamily") return "fontFamily";
161
189
  if (id === "canvas" && property === "background") return "background";
162
190
  if (id === `${program.compositionSpec?.id}-headers` && property === "fill") {
@@ -197,6 +225,7 @@ function graphicRole(program, id, property) {
197
225
  }
198
226
 
199
227
  function roleLegacy(role) {
228
+ if (role === "axisLabel") return ["text", "axis"];
200
229
  if (role === "axis") return ["text"];
201
230
  if (role === "axisTitle") return ["text", "strongText"];
202
231
  if (role === "strongText") return ["text"];
@@ -237,7 +266,7 @@ function mapGraphicProperties(
237
266
  role,
238
267
  sourceTokens,
239
268
  targetTokens,
240
- roleLegacy(role)
269
+ roleLegacy(role), axisFamily(id)
241
270
  );
242
271
  if (mapped.changed) {
243
272
  next[property] = mapped.value;
@@ -303,6 +332,11 @@ function recolorGraphic(
303
332
 
304
333
  function configRole(program, path) {
305
334
  const property = path.at(-1);
335
+ if (path[0] === "guides" && path[1] === "axis") {
336
+ const component = path[2] === "parallel" ? path.at(-2) : path[3];
337
+ const scoped = axisTextRole(property, component);
338
+ if (scoped !== undefined) return scoped;
339
+ }
306
340
  if (property === "fontFamily") return "fontFamily";
307
341
  if (path[0] === "facets" && path.includes("headers") &&
308
342
  property === "color") {
@@ -368,7 +402,7 @@ function recolorConfig(
368
402
  }
369
403
  if (value === null || typeof value !== "object") {
370
404
  const property = path.at(-1);
371
- if (!["color", "fill", "stroke", "background", "fontFamily"]
405
+ if (!["color", "fill", "stroke", "background", "fontFamily", "fontSize"]
372
406
  .includes(property)) {
373
407
  return { changed: false, value };
374
408
  }
@@ -386,7 +420,7 @@ function recolorConfig(
386
420
  role,
387
421
  sourceTokens,
388
422
  targetTokens,
389
- roleLegacy(role)
423
+ roleLegacy(role), axisFamily(String(path[2] ?? ""))
390
424
  );
391
425
  }
392
426
  if (["theme", "highlights", "selections"].includes(path[0])) {
@@ -721,10 +755,11 @@ function addAxisOverrides(overrides, op, args) {
721
755
  `g:${idPrefix}Axis${match[2]}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
722
756
  );
723
757
  }
724
- if (["labels", "title"].includes(component) &&
725
- Object.hasOwn(args, "fontFamily")) {
726
- overrides.add(`c:guides.axis.${channel}.${component}.fontFamily`);
727
- overrides.add(`g:${idPrefix}Axis${match[2]}.fontFamily`);
758
+ for (const property of ["fontFamily", "fontSize"]) {
759
+ if (["labels", "title"].includes(component) && Object.hasOwn(args, property)) {
760
+ overrides.add(`c:guides.axis.${channel}.${component}.${property}`);
761
+ overrides.add(`g:${idPrefix}Axis${match[2]}.${property}`);
762
+ }
728
763
  }
729
764
  }
730
765
 
@@ -749,15 +784,22 @@ function addCompleteAxisOverride(overrides, name, args) {
749
784
  `g:${idPrefix}Axis${suffix}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
750
785
  );
751
786
  }
752
- if (["labels", "title"].includes(component) &&
753
- Object.hasOwn(options ?? {}, "fontFamily")) {
754
- overrides.add(`c:guides.axis.${channel}.${component}.fontFamily`);
755
- overrides.add(`g:${idPrefix}Axis${suffix}.fontFamily`);
787
+ for (const property of ["fontFamily", "fontSize"]) {
788
+ if (["labels", "title"].includes(component) && Object.hasOwn(options ?? {}, property)) {
789
+ overrides.add(`c:guides.axis.${channel}.${component}.${property}`);
790
+ overrides.add(`g:${idPrefix}Axis${suffix}.${property}`);
791
+ }
756
792
  }
757
793
  }
758
794
  }
759
795
 
760
796
  function addCompleteAxisOverrides(overrides, op, args) {
797
+ if (args.guides?.axes !== null && typeof args.guides?.axes === "object") {
798
+ addCompleteAxisOverrides(overrides, "createAxes", args.guides.axes);
799
+ }
800
+ if (op === "createGuides" && args.axes !== null && typeof args.axes === "object") {
801
+ addCompleteAxisOverrides(overrides, "createAxes", args.axes);
802
+ }
761
803
  const match = /^(?:create|edit)(X|Y|Theta|Radial)Axis$/u.exec(op);
762
804
  if (match !== null) addCompleteAxisOverride(overrides, match[1], args);
763
805
  const group = /^(?:create|edit)(X|Y)AxisTicksAndLabels$/u.exec(op);
@@ -822,11 +864,10 @@ function addParallelOverrides(overrides, program, op, args) {
822
864
  `c:guides.axis.parallel.axes.dimensions.${index}.${component}.color`
823
865
  );
824
866
  }
825
- if (["labels", "title"].includes(component) &&
826
- Object.hasOwn(options ?? {}, "fontFamily")) {
827
- overrides.add(
828
- `c:guides.axis.parallel.axes.dimensions.${index}.${component}.fontFamily`
829
- );
867
+ for (const property of ["fontFamily", "fontSize"]) {
868
+ if (["labels", "title"].includes(component) && Object.hasOwn(options ?? {}, property)) {
869
+ overrides.add(`c:guides.axis.parallel.axes.dimensions.${index}.${component}.${property}`);
870
+ }
830
871
  }
831
872
  }
832
873
  }
@@ -920,7 +961,7 @@ function explicitOverrides(program, node) {
920
961
  const overrides = new Set();
921
962
  if (op === "editGraphics" &&
922
963
  typeof args.target === "string" &&
923
- ["background", "fill", "stroke", "fontFamily"].includes(args.property)) {
964
+ ["background", "fill", "stroke", "fontFamily", "fontSize"].includes(args.property)) {
924
965
  overrides.add(`g:${args.target}.${args.property}`);
925
966
  }
926
967
  if (["createCanvas", "editCanvas"].includes(op) &&
@@ -961,6 +1002,13 @@ function explicitOverrides(program, node) {
961
1002
  if (op === "editFacetHeaders") {
962
1003
  addFacetOverrides(overrides, program, args);
963
1004
  }
1005
+ if (["facet", "facetGrid", "repeatCharts"].includes(op) && args.headers !== undefined) {
1006
+ const { row, column, ...common } = args.headers;
1007
+ addFacetOverrides(overrides, program, common);
1008
+ for (const [role, patch] of [["row", row], ["column", column]]) {
1009
+ if (patch !== undefined) addFacetOverrides(overrides, program, { ...patch, role });
1010
+ }
1011
+ }
964
1012
  return overrides;
965
1013
  }
966
1014
 
@@ -978,7 +1026,14 @@ function collectOverrides(program) {
978
1026
  return overrides;
979
1027
  }
980
1028
 
1029
+ function axisTypographyChanged(before, after) {
1030
+ if (before === after || after === null || typeof after !== "object") return false;
1031
+ return Object.entries(after).some(([key, value]) => ["fontFamily", "fontSize"].includes(key)
1032
+ ? value !== before?.[key] : axisTypographyChanged(before?.[key], value));
1033
+ }
1034
+
981
1035
  export function reconcileProgramTheme(program, { source, metadata }) {
1036
+ program = reconcileFollowingRegressions(program);
982
1037
  const storedState = program.materializationConfigs.theme;
983
1038
  if (storedState === undefined) return program;
984
1039
  // Data/context/trace-only transitions cannot change authored appearance.
@@ -1031,7 +1086,8 @@ export function reconcileProgramTheme(program, { source, metadata }) {
1031
1086
 
1032
1087
  const facetsChanged = configs.value.facets !==
1033
1088
  themed.materializationConfigs.facets;
1034
- const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily;
1089
+ const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily ||
1090
+ axisTypographyChanged(themed.guideConfigs.axis, configs.value.guides.axis);
1035
1091
  if (facetsChanged && next.compositionSpec?.type === "facet") {
1036
1092
  next = next.materializeComposition();
1037
1093
  } else if (typographyChanged) {
@@ -158,3 +158,15 @@ export function setThemeStateOverrides(state, overrides) {
158
158
  overrides: cloneAndFreeze([...overrides].sort())
159
159
  });
160
160
  }
161
+
162
+ // Resolve active font defaults before guide layout validates their occupied bounds.
163
+ export function axisThemeTypography(state, component) {
164
+ if (resolveEffectiveThemeFrame(state) === undefined) return {};
165
+ const tokens = resolveEffectiveThemeTokens(state);
166
+ const role = component === "labels" ? "axisLabel" : "axisTitle";
167
+ const fontSize = tokens[`${role}FontSize`];
168
+ return {
169
+ fontFamily: tokens[`${role}FontFamily`] ?? tokens.fontFamily,
170
+ ...(fontSize === undefined ? {} : { fontSize })
171
+ };
172
+ }
@@ -1,3 +1,4 @@
1
+ import { paintedTitleComponent, titleTextProperties } from "../../layout/title.js";
1
2
  import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
2
3
  import { action } from "../../core/action.js";
3
4
  import { noOptions } from "../../core/validation.js";
@@ -56,24 +57,9 @@ function ensureTextShape(program, id, component) {
56
57
  return program;
57
58
  }
58
59
 
59
- function distributed(value, count) {
60
- return count === 1 && Array.isArray(value) ? value[0] : value;
61
- }
62
-
63
60
  function editTextGraphic(program, id, component, style) {
64
- const count = component.lines.length;
65
61
  let next = ensureTextShape(program, id, component);
66
- for (const [property, value] of Object.entries({
67
- x: distributed(component.x, count),
68
- y: distributed(component.y, count),
69
- text: distributed(component.lines, count),
70
- fill: style.color,
71
- fontSize: style.fontSize,
72
- fontFamily: style.fontFamily,
73
- fontWeight: style.fontWeight,
74
- textAlign: component.textAlign,
75
- textBaseline: "middle"
76
- })) {
62
+ for (const [property, value] of Object.entries(titleTextProperties(component, style))) {
77
63
  next = next.editGraphics({ target: id, property, value });
78
64
  }
79
65
  if (component.explicitRotation) {
@@ -99,10 +85,11 @@ function titleTextAction(kind, create) {
99
85
  if (create && this.graphicSpec.objects[id] !== undefined) {
100
86
  throw new Error(`${op} requires a missing chart ${kind} graphic.`);
101
87
  }
102
- const component = resolveTitleLayout(this, config)[kind];
103
- if (component === undefined) {
88
+ const layout = resolveTitleLayout(this, config)[kind];
89
+ if (layout === undefined) {
104
90
  throw new Error(`${op} requires semantic subtitle text.`);
105
91
  }
92
+ const component = paintedTitleComponent(layout);
106
93
  if (!create) {
107
94
  return editTextGraphic(this, id, component, config[`${kind}Style`]);
108
95
  }
@@ -1,3 +1,7 @@
1
+ import { textBoundsFitCanvas } from "../../core/textMetrics.js";
2
+ import { resolveGuideCollisionBlocks } from "../../materialization/guides/resources.js";
3
+ import { canvasOverflowError } from "../../layout/canvas.js";
4
+ import { validateFontStyle } from "../../core/font.js";
1
5
  import { isPlainObject } from "../../core/immutable.js";
2
6
  import {
3
7
  validateKeys,
@@ -19,11 +23,11 @@ import { findCanvasGraphic } from
19
23
  "../../materialization/graphicHierarchy.js";
20
24
 
21
25
  const OPTIONS = Object.freeze([
22
- "text", "subtitle", "position", "align", "offset", "gap", "titleStyle",
26
+ "text", "subtitle", "position", "align", "offset", "alongOffset", "gap", "titleStyle",
23
27
  "subtitleStyle", "maxWidth", "wrap", "lineHeight"
24
28
  ]);
25
29
  const STYLE_OPTIONS = Object.freeze([
26
- "color", "fontSize", "fontFamily", "fontWeight"
30
+ "color", "fontSize", "fontFamily", "fontWeight", "fontStyle"
27
31
  ]);
28
32
  const POSITIONS = Object.freeze(["top", "bottom", "left", "right"]);
29
33
  const ALIGNS = Object.freeze(["left", "center", "right"]);
@@ -32,6 +36,7 @@ const DEFAULTS = Object.freeze({
32
36
  position: "top",
33
37
  align: "left",
34
38
  offset: 0,
39
+ alongOffset: 0,
35
40
  gap: 8,
36
41
  titleStyle: Object.freeze({
37
42
  color: DEFAULT_COLORS.strongText,
@@ -47,9 +52,12 @@ const DEFAULTS = Object.freeze({
47
52
  })
48
53
  });
49
54
 
50
- export function validateTitleString(value, label) {
55
+ export function validateTitleString(value, label, multiline = false) {
51
56
  validateNonEmptyString(value, label);
52
- if (/\r|\n/u.test(value)) {
57
+ if (multiline && value.split(/\r\n|\r|\n/u).every(line => line === "")) {
58
+ throw new Error(`${label} requires at least one non-empty line.`);
59
+ }
60
+ if (!multiline && /\r|\n/u.test(value)) {
53
61
  throw new Error(`${label} does not accept explicit newlines.`);
54
62
  }
55
63
  return value;
@@ -70,6 +78,7 @@ function normalizeStyle(value, defaults, label) {
70
78
  )) {
71
79
  throw new TypeError(`${label} fontWeight must be a non-empty string or number.`);
72
80
  }
81
+ if (style.fontStyle !== undefined) validateFontStyle(style.fontStyle, label);
73
82
  return style;
74
83
  }
75
84
 
@@ -78,6 +87,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
78
87
  const align = args.align ?? defaults.align;
79
88
  const offset = args.offset ?? defaults.offset;
80
89
  const gap = args.gap ?? defaults.gap;
90
+ const alongOffset = args.alongOffset ?? defaults.alongOffset ?? 0;
81
91
  const titleStyle = normalizeStyle(
82
92
  args.titleStyle,
83
93
  defaults.titleStyle,
@@ -100,6 +110,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
100
110
  if (!Number.isFinite(offset)) {
101
111
  throw new TypeError("Chart title offset must be a finite number.");
102
112
  }
113
+ if (!Number.isFinite(alongOffset)) throw new TypeError("Chart title alongOffset must be a finite number.");
103
114
  validateNonNegativeFinite(gap, "Chart title gap");
104
115
  if (maxWidth !== undefined) {
105
116
  validatePositiveFinite(maxWidth, "Chart title maxWidth");
@@ -121,6 +132,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
121
132
  position,
122
133
  align,
123
134
  offset,
135
+ alongOffset,
124
136
  gap,
125
137
  titleStyle,
126
138
  subtitleStyle,
@@ -132,10 +144,10 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
132
144
 
133
145
  export function normalizeTitleOptions(args) {
134
146
  validateOptionObject(args, OPTIONS, "createTitle");
135
- const text = validateTitleString(args.text, "Chart title text");
147
+ const text = validateTitleString(args.text, "Chart title text", true);
136
148
  const subtitle = args.subtitle === undefined
137
149
  ? undefined
138
- : validateTitleString(args.subtitle, "Chart subtitle");
150
+ : validateTitleString(args.subtitle, "Chart subtitle", true);
139
151
  return {
140
152
  text,
141
153
  subtitle,
@@ -151,12 +163,12 @@ export function normalizeTitleEditOptions(args, previous, semanticTitle) {
151
163
  });
152
164
  const text = args.text === undefined
153
165
  ? semanticTitle.text
154
- : validateTitleString(args.text, "Chart title text");
166
+ : validateTitleString(args.text, "Chart title text", true);
155
167
  const subtitle = args.subtitle === false
156
168
  ? undefined
157
169
  : args.subtitle === undefined
158
170
  ? semanticTitle.subtitle
159
- : validateTitleString(args.subtitle, "Chart subtitle");
171
+ : validateTitleString(args.subtitle, "Chart subtitle", true);
160
172
  if (args.subtitle !== undefined && args.subtitle !== false && typeof args.subtitle !== "string") {
161
173
  throw new TypeError("Chart subtitle must be a non-empty string or false.");
162
174
  }
@@ -175,11 +187,8 @@ export function requireTitleConfig(program) {
175
187
  }
176
188
 
177
189
  function validateLayout(position, titleBounds, plot, canvas) {
178
- if (
179
- titleBounds.left < 0 || titleBounds.top < 0 ||
180
- titleBounds.right > canvas.width || titleBounds.bottom > canvas.height
181
- ) {
182
- throw new Error(`Chart title requires more ${position}-margin space.`);
190
+ if (!textBoundsFitCanvas(titleBounds, canvas)) {
191
+ throw canvasOverflowError(`Chart title requires more ${position}-margin space.`, [titleBounds], canvas);
183
192
  }
184
193
  const outsidePlot = {
185
194
  top: titleBounds.bottom <= plot.y,
@@ -205,19 +214,31 @@ export function resolveTitleLayout(program, config) {
205
214
  throw new Error("Chart title layout requires Canvas bounds and dimensions.");
206
215
  }
207
216
  const block = buildTitleReadingBlock({
208
- text: validateTitleString(program.semanticSpec.title.text, "Chart title text"),
217
+ text: validateTitleString(program.semanticSpec.title.text, "Chart title text", true),
209
218
  subtitle: program.semanticSpec.title.subtitle === undefined
210
219
  ? undefined
211
220
  : validateTitleString(
212
221
  program.semanticSpec.title.subtitle,
213
- "Chart subtitle"
222
+ "Chart subtitle", true
214
223
  )
215
224
  }, config, program.materializationConfigs.textMetrics);
225
+ const automatic = program.materializationConfigs.canvas?.plot !== undefined;
226
+ const near = ["top", "left"].includes(config.position);
216
227
  const horizontal = ["top", "bottom"].includes(config.position);
228
+ let titleEdge = horizontal ? plot.y + (near ? 0 : plot.height)
229
+ : plot.x + (near ? 0 : plot.width);
230
+ if (automatic) {
231
+ const occupied = resolveGuideCollisionBlocks(program.graphicSpec, program.guideConfigs,
232
+ undefined, program.materializationConfigs.textMetrics).filter(item => item.position === config.position);
233
+ const key = horizontal ? (near ? "top" : "bottom") : (near ? "left" : "right");
234
+ for (const item of occupied) titleEdge = near ? Math.min(titleEdge, item.bounds[key]) : Math.max(titleEdge, item.bounds[key]);
235
+ titleEdge += (near ? -1 : 1) * (16 + config.offset);
236
+ }
217
237
  let component;
218
238
  if (horizontal) {
219
- const x = alignedTextAnchor(plot.x, plot.width, config.align);
220
- const blockTop = config.position === "top"
239
+ const x = alignedTextAnchor(plot.x, plot.width, config.align) + (config.alongOffset ?? 0);
240
+ const blockTop = automatic ? titleEdge - (near ? block.height : 0)
241
+ : config.position === "top"
221
242
  ? 16 + config.offset
222
243
  : plot.y + plot.height + config.offset;
223
244
  component = (lines, centers) => ({
@@ -230,8 +251,9 @@ export function resolveTitleLayout(program, config) {
230
251
  });
231
252
  } else {
232
253
  const rotation = config.position === "left" ? -Math.PI / 2 : Math.PI / 2;
233
- const y = alignedTitleAnchor(plot.y, plot.height, block.width, config.align);
234
- const edge = config.position === "left"
254
+ const y = alignedTitleAnchor(plot.y, plot.height, block.width, config.align) + (config.alongOffset ?? 0);
255
+ const edge = automatic ? titleEdge + (near ? -block.height : block.height)
256
+ : config.position === "left"
235
257
  ? 16 + config.offset
236
258
  : canvas.properties.width - 16 + config.offset;
237
259
  const mapX = value => config.position === "left" ? edge + value : edge - value;
@@ -2,7 +2,7 @@ import { action } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
3
  import { validateKeys } from "../../core/validation.js";
4
4
  import { findDataset } from "../../selectors/datasets.js";
5
- import { findLayer } from "../../selectors/layers.js";
5
+ import { findLayer, resolveConfiguredOwner } from "../../selectors/layers.js";
6
6
  import { rebindDistributionGuides } from "../distributions/revision.js";
7
7
  import {
8
8
  resolveViolinDensity,
@@ -15,24 +15,7 @@ const OPTIONS = Object.freeze([
15
15
  "target", "data", "x", "y", "split", "density"
16
16
  ]);
17
17
 
18
- function resolveOwner(program, requested) {
19
- const eligible = program.semanticSpec.layers.filter(
20
- layer => program.markConfigs[layer.id]?.violinPlot?.materialized === true
21
- );
22
- if (requested !== undefined) {
23
- const id = validateUserId(requested, "Violin-plot owner id");
24
- const layer = findLayer(program, id);
25
- if (layer === undefined || !eligible.includes(layer)) {
26
- throw new Error(`Unknown violin-plot owner "${id}".`);
27
- }
28
- return layer;
29
- }
30
- const current = findLayer(program, program.context.currentMark);
31
- if (current !== undefined && eligible.includes(current)) return current;
32
- if (eligible.length === 1) return eligible[0];
33
- if (eligible.length === 0) throw new Error(`${OPERATION} requires a violin plot.`);
34
- throw new Error(`${OPERATION} target is ambiguous; provide target.`);
35
- }
18
+
36
19
 
37
20
  function currentPosition(owner, current, channel) {
38
21
  const categorical = current.orientation === "vertical"
@@ -84,7 +67,7 @@ export const editViolinPlot = /* @__PURE__ */ action(
84
67
  if (!OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
85
68
  throw new Error(`${OPERATION} requires at least one violin-plot option.`);
86
69
  }
87
- const owner = resolveOwner(this, args.target);
70
+ const owner = resolveConfiguredOwner(this, args.target, { config: "violinPlot", operation: OPERATION, kind: "violin", materialized: true }, validateUserId);
88
71
  const current = this.markConfigs[owner.id].violinPlot;
89
72
  const source = Object.hasOwn(args, "data")
90
73
  ? validateUserId(args.data, "Violin-plot data id")
@@ -192,7 +192,8 @@ export function action(metadata, implementation) {
192
192
  const ownedMetadata = freezeOwned({
193
193
  op: metadata.op,
194
194
  description: metadata.description,
195
- scope
195
+ scope,
196
+ ...(Array.isArray(metadata.options) ? { options: [...metadata.options] } : {})
196
197
  });
197
198
 
198
199
  const wrappedAction = function wrappedAction(args = {}) {
@@ -240,7 +241,7 @@ export function action(metadata, implementation) {
240
241
  // Private built-in convenience: action() owns the object boundary and trace,
241
242
  // while each definition supplies its closed option vocabulary exactly once.
242
243
  export function closedAction(metadata, options, implementation) {
243
- return action(metadata, function (args = {}) {
244
+ return action({ ...metadata, options }, function (args = {}) {
244
245
  if (options !== undefined) validateKeys(args, options, metadata.op);
245
246
  return implementation.call(this, args);
246
247
  });
@@ -272,7 +272,7 @@ export function ownCompositionSpec(compositionSpec, children) {
272
272
  }
273
273
  const allowed = [
274
274
  "id", "type", "direction", "children", "columns", "gap", "align",
275
- "padding", "facet"
275
+ "padding", "facet", "spacing"
276
276
  ];
277
277
  const unknown = Object.keys(compositionSpec).find(key => !allowed.includes(key));
278
278
  if (unknown !== undefined) {
@@ -282,6 +282,9 @@ export function ownCompositionSpec(compositionSpec, children) {
282
282
  throw new TypeError("compositionSpec.id must be a non-empty string.");
283
283
  }
284
284
  const facet = compositionSpec.type === "facet";
285
+ if (compositionSpec.spacing !== undefined && (!facet || !["plot", "canvas"].includes(compositionSpec.spacing))) {
286
+ throw new Error("compositionSpec.spacing requires a facet and canvas or plot spacing.");
287
+ }
285
288
  if (compositionSpec.type !== undefined && !facet) {
286
289
  throw new Error(`Unknown compositionSpec type "${compositionSpec.type}".`);
287
290
  }
package/src/core/font.js CHANGED
@@ -5,8 +5,15 @@ export function normalizeRendererFontWeight(fontWeight) {
5
5
  return Math.min(900, Math.max(100, Math.round(fontWeight / 100) * 100));
6
6
  }
7
7
 
8
- export function textMetricKey({ text, fontFamily, fontSize, fontWeight }) {
9
- return JSON.stringify([text, fontFamily, fontSize, fontWeight]);
8
+ export function validateFontStyle(value, label = "Text") {
9
+ if (!["normal", "italic"].includes(value)) {
10
+ throw new Error(`${label} fontStyle must be normal or italic.`);
11
+ }
12
+ return value;
13
+ }
14
+
15
+ export function textMetricKey({ text, fontFamily, fontSize, fontWeight, fontStyle = "normal" }) {
16
+ return JSON.stringify([text, fontFamily, fontSize, fontWeight, fontStyle]);
10
17
  }
11
18
 
12
19
  export function textMetricFontWeight(value = "normal") {
@@ -1,6 +1,7 @@
1
1
  // The method's value-producing contract is shared by runtime validation and
2
2
  // authoring knowledge; editing can inherit these values from stored state.
3
3
  export const IMPUTE_REQUIRED_OPTIONS = Object.freeze({
4
+ mean: Object.freeze([]),
4
5
  constant: Object.freeze(["value"]),
5
6
  forward: Object.freeze(["sortBy"]),
6
7
  backward: Object.freeze(["sortBy"]),
@@ -34,12 +34,16 @@ export function ownChildPrograms(children, ProgramClass) {
34
34
  // domain's dependency validation and before the primitive deletion.
35
35
  export function releaseNamedResourceOwnership(program, { kind, id, dataOwner }) {
36
36
  if (kind !== "data") return program;
37
- const materializationConfigs = dataOwner === undefined
37
+ let materializationConfigs = dataOwner === undefined
38
38
  ? program.materializationConfigs
39
39
  : removeMaterializationConfig(
40
40
  program.materializationConfigs,
41
41
  ["data", dataOwner.family, dataOwner.owner]
42
42
  ).value;
43
+ materializationConfigs = removeMaterializationConfig(
44
+ materializationConfigs,
45
+ ["calculations", "datasets", dataOwner?.current ?? id]
46
+ ).value;
43
47
  const context = program.context.currentData === id
44
48
  ? freezeOwned({ ...program.context, currentData: undefined })
45
49
  : program.context;
@@ -199,6 +199,9 @@ export function collectMarkConfigReferences(program) {
199
199
 
200
200
  export function collectGuideConfigReferences(program) {
201
201
  const references = [];
202
+ for (const target of Object.keys(program.guideConfigs?.legendGroups ?? {})) {
203
+ append(references, reference("mark", target, "legend", target, ["legendGroups", target]));
204
+ }
202
205
  for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
203
206
  collectKnownKeys(configs, [], (key, value, path, parentPath) => {
204
207
  const ownerId = parentPath.length === 0 ? family : parentPath.join(".");