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
@@ -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.`);
@@ -118,6 +121,12 @@ const createRuleMark = /* @__PURE__ */ 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({
@@ -154,11 +163,18 @@ const editRuleMark = /* @__PURE__ */ 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
@@ -282,13 +298,16 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
282
298
  properties: item?.properties
283
299
  });
284
300
  if (box === undefined) throw new Error(`Rule mark "${id}" requires box span owner "${boxSpan}".`);
301
+ const pixels = resolved.markConfigs[boxSpan]?.boxPlot?.median?.width?.pixels;
285
302
  if (layer.encoding?.x?.fieldType === "quantitative") {
286
303
  startX = endX = mapped.x[index];
287
- startY = box.y;
288
- endY = box.y + box.height;
304
+ const height = pixels ?? box.height;
305
+ startY = box.y + (box.height - height) / 2;
306
+ endY = startY + height;
289
307
  } else {
290
- startX = box.x;
291
- endX = box.x + box.width;
308
+ const width = pixels ?? box.width;
309
+ startX = box.x + (box.width - width) / 2;
310
+ endX = startX + width;
292
311
  startY = endY = mapped.y[index];
293
312
  }
294
313
  } else if (mode === "fixed-span") {
@@ -3,6 +3,7 @@ import { action } from "../../../core/action.js";
3
3
  import { validateUserId } from "../../../core/identifiers.js";
4
4
  import {
5
5
  DEFAULT_TEXT_MARK,
6
+ TEXT_MARK_STYLE_OPTIONS as STYLE_OPTIONS,
6
7
  isSourceOwnedText,
7
8
  normalizeTextMarkConfig
8
9
  } from "../../../grammar/text.js";
@@ -39,13 +40,10 @@ import {
39
40
  } from "../../charts/shared.js";
40
41
  import { resolveMarkGraphicPlacement } from
41
42
  "../../../materialization/graphicHierarchy.js";
43
+ import { validateItemMissing } from "../../../grammar/itemMissing.js";
42
44
 
43
- const STYLE_OPTIONS = Object.freeze([
44
- "fill", "opacity", "fontSize", "fontFamily", "fontWeight",
45
- "align", "baseline", "rotation", "dx", "dy"
46
- ]);
47
- const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", ...STYLE_OPTIONS]);
48
- const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
45
+ const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", "missing", ...STYLE_OPTIONS]);
46
+ const EDIT_OPTIONS = Object.freeze(["target", "missing", ...STYLE_OPTIONS]);
49
47
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
50
48
 
51
49
  function sourceMatchesData(program, layer, requestedData) {
@@ -139,6 +137,9 @@ const createTextMark = /* @__PURE__ */ action(
139
137
  operation: "createTextMark"
140
138
  });
141
139
  const inherited = resolveTextInheritance(this, args);
140
+ if (Object.hasOwn(args, "missing") && inherited?.source !== undefined) {
141
+ throw new Error("Text missing policy requires a row-backed text mark, not source-owned labels.");
142
+ }
142
143
  const { data } = resolveMarkData(this, {
143
144
  ...args,
144
145
  ...(args.data === undefined && inherited?.data !== undefined
@@ -150,6 +151,12 @@ const createTextMark = /* @__PURE__ */ action(
150
151
  let next = this
151
152
  .editSemantic({ property: `layer[${id}].mark.type`, value: "text" })
152
153
  .editSemantic({ property: `layer[${id}].data`, value: data });
154
+ if (Object.hasOwn(args, "missing")) {
155
+ next = next.editSemantic({
156
+ property: `layer[${id}].mark.missing`,
157
+ value: validateItemMissing(args.missing, "Text missing")
158
+ });
159
+ }
153
160
  if (inherited?.source !== undefined) {
154
161
  next = next.editSemantic({
155
162
  property: `layer[${id}].source`,
@@ -185,7 +192,7 @@ const LABEL_OPTIONS = Object.freeze([
185
192
  ]);
186
193
 
187
194
  const ANNOTATION_OPTIONS = Object.freeze([
188
- "id", "text", "format", "x", "y", "space", "source", "data", "coordinate", "layout",
195
+ "id", "text", "format", "anchor", "x", "y", "space", "source", "data", "coordinate", "layout",
189
196
  ...STYLE_OPTIONS
190
197
  ]);
191
198
 
@@ -261,6 +268,15 @@ const createAnnotation = /* @__PURE__ */ action(
261
268
  },
262
269
  function (args = {}) {
263
270
  validateMarkOptions(args, ANNOTATION_OPTIONS, "createAnnotation");
271
+ if (Object.hasOwn(args, "anchor")) {
272
+ if (args.anchor !== "plot-center") {
273
+ throw new Error('createAnnotation anchor must be "plot-center".');
274
+ }
275
+ if (["x", "y", "space", "source", "align", "baseline"].some(key => Object.hasOwn(args, key))) {
276
+ throw new Error("createAnnotation plot-center owns x, y, space, align, and baseline and does not accept source.");
277
+ }
278
+ args = { ...args, space: "plot", x: 0.5, y: 0.5, align: "center", baseline: "middle" };
279
+ }
264
280
  if (!Object.hasOwn(args, "text")) {
265
281
  throw new Error("createAnnotation requires text.");
266
282
  }
@@ -465,20 +481,35 @@ const editTextMark = /* @__PURE__ */ action(
465
481
  },
466
482
  function (args = {}) {
467
483
  validateMarkOptions(args, EDIT_OPTIONS, "editTextMark");
468
- if (!STYLE_OPTIONS.some(option => Object.hasOwn(args, option))) {
484
+ if (!["missing", ...STYLE_OPTIONS].some(option => Object.hasOwn(args, option))) {
469
485
  throw new Error("editTextMark requires at least one editable property.");
470
486
  }
471
487
  const layer = requireTextLayer(this, args.target, "editTextMark");
472
- const next = this._withMarkConfig(
488
+ if (Object.hasOwn(args, "missing") && layer.source !== undefined) {
489
+ throw new Error("Text missing policy requires a row-backed text mark, not source-owned labels.");
490
+ }
491
+ if (args.inheritColor && layer.source === undefined) {
492
+ throw new Error("Text inheritColor requires a source-owned label.");
493
+ }
494
+ if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
495
+ throw new Error("Text fill conflicts with its color encoding.");
496
+ }
497
+ const semantic = Object.hasOwn(args, "missing")
498
+ ? this.editSemantic({
499
+ property: `layer[${layer.id}].mark.missing`,
500
+ value: validateItemMissing(args.missing, "Text missing")
501
+ })
502
+ : this;
503
+ const next = semantic._withMarkConfig(
473
504
  layer.id,
474
505
  {
475
506
  ...normalizeTextMarkConfig(
476
507
  args,
477
- this.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
508
+ semantic.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
478
509
  ),
479
510
  fillExplicit: Object.hasOwn(args, "fill")
480
511
  ? true
481
- : this.markConfigs[layer.id]?.fillExplicit ?? false
512
+ : semantic.markConfigs[layer.id]?.fillExplicit ?? false
482
513
  }
483
514
  );
484
515
  return canMaterializeText(next, layer)
@@ -144,6 +144,9 @@ function layoutInputs(program, layer) {
144
144
  sourceX: entry.anchor.x,
145
145
  sourceY: entry.anchor.y,
146
146
  text: entry.graphic.properties.text,
147
+ lines: entry.graphic.properties.lines,
148
+ fontFamily: entry.graphic.properties.fontFamily,
149
+ fontWeight: entry.graphic.properties.fontWeight,
147
150
  fontSize: entry.graphic.properties.fontSize,
148
151
  textAlign: entry.graphic.properties.textAlign,
149
152
  textBaseline: entry.graphic.properties.textBaseline,
@@ -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({
@@ -114,6 +116,12 @@ export const createTickMark = /* @__PURE__ */ 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,
@@ -141,9 +149,9 @@ export const editTickMark = /* @__PURE__ */ 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,11 +160,17 @@ export const editTickMark = /* @__PURE__ */ 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);
@@ -190,10 +204,11 @@ export const rematerializeTickMark = /* @__PURE__ */ 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
@@ -7,6 +7,7 @@ import {
7
7
  removeOwnedPath
8
8
  } from "../../core/immutable.js";
9
9
  import { parseSemanticPath } from "../../grammar/schemas/semanticPath.js";
10
+ import { deriveTransformSchema, inferDatasetSchema, validateRowsAgainstSchema } from "../../grammar/datasetSchema.js";
10
11
 
11
12
 
12
13
  const CONTEXT_KEYS = Object.freeze({
@@ -44,7 +45,39 @@ function updateEntity(spec, parsed, value) {
44
45
  }
45
46
 
46
47
  const current = index === -1 ? { id: parsed.id } : collection[index];
47
- const updated = setNestedProperty(current, parsed.path, value);
48
+ let updated = setNestedProperty(current, parsed.path, value);
49
+ if (
50
+ parsed.kind === "dataset" && parsed.path.length === 1 &&
51
+ parsed.path[0] === "transform" && current.schema === undefined &&
52
+ current.source !== undefined && value.length === 1
53
+ ) {
54
+ const source = collection.find(dataset => dataset.id === current.source);
55
+ if (source?.schema !== undefined) {
56
+ updated = setNestedProperty(updated, ["schema"], deriveTransformSchema(
57
+ source.schema,
58
+ value[0],
59
+ undefined,
60
+ { sourceId: source.id, ownerId: parsed.id }
61
+ ));
62
+ }
63
+ }
64
+ if (
65
+ parsed.kind === "dataset" && parsed.path.length === 1 &&
66
+ parsed.path[0] === "values" &&
67
+ (current.schema === undefined || current.schema.origin === "derived")
68
+ ) {
69
+ const source = current.source === undefined
70
+ ? undefined
71
+ : collection.find(dataset => dataset.id === current.source);
72
+ const schema = source?.schema !== undefined && current.transform?.length === 1
73
+ ? deriveTransformSchema(source.schema, current.transform[0], value, {
74
+ sourceId: source.id,
75
+ ownerId: parsed.id
76
+ })
77
+ : inferDatasetSchema(value);
78
+ validateRowsAgainstSchema(value, schema, `Dataset "${parsed.id}"`);
79
+ updated = setNestedProperty(updated, ["schema"], schema);
80
+ }
48
81
  const nextCollection = [...collection];
49
82
 
50
83
  if (index === -1) {
@@ -155,13 +188,14 @@ export function withRematerializedDerivedDataset(program, {
155
188
  id,
156
189
  source,
157
190
  transform,
158
- values
191
+ values,
192
+ schema
159
193
  }) {
160
194
  const semanticSpec = {
161
195
  ...program.semanticSpec,
162
196
  datasets: program.semanticSpec.datasets.map(dataset =>
163
197
  dataset.id === id
164
- ? { ...dataset, source, transform: [transform], values }
198
+ ? { ...dataset, source, transform: [transform], values, ...(schema === undefined ? {} : { schema }) }
165
199
  : dataset
166
200
  )
167
201
  };
@@ -1,6 +1,10 @@
1
1
  import { validateUserId } from "../../../core/identifiers.js";
2
2
  import { validateDataRows } from "../../../core/validation.js";
3
- import { hasDataset } from "../../../selectors/datasets.js";
3
+ import { findDataset, hasDataset } from "../../../selectors/datasets.js";
4
+ import {
5
+ validateDatasetSchema,
6
+ validateRowsAgainstSchema
7
+ } from "../../../grammar/datasetSchema.js";
4
8
 
5
9
  export function validateDatasetSemanticValue(
6
10
  program,
@@ -11,6 +15,14 @@ export function validateDatasetSemanticValue(
11
15
  const property = parsed.path[0];
12
16
  if (property === "values") {
13
17
  validateDataRows(value, `Dataset "${parsed.id}"`);
18
+ const schema = findDataset(program, parsed.id)?.schema;
19
+ if (schema !== undefined && schema.origin !== "derived") {
20
+ validateRowsAgainstSchema(value, schema, `Dataset "${parsed.id}"`);
21
+ }
22
+ return;
23
+ }
24
+ if (property === "schema") {
25
+ validateDatasetSchema(value);
14
26
  return;
15
27
  }
16
28
  if (property === "source") {
@@ -19,6 +19,8 @@ import { findLayer } from "../../../selectors/layers.js";
19
19
  import { validateNonEmptySemanticString } from "./shared.js";
20
20
  import { normalizeStatisticalWeight } from
21
21
  "../../../grammar/weightedStatistics.js";
22
+ import { ITEM_MISSING_MARK_TYPES, validateItemMissing } from
23
+ "../../../grammar/itemMissing.js";
22
24
 
23
25
  function validateLayerSource(program, parsed, value, sourceMarkTypes) {
24
26
  validateUserId(value, "Layer source id");
@@ -52,8 +54,30 @@ export function validateLayerSemanticValue(
52
54
  if (property === "mark.type" && !MARK_TYPES.includes(value)) {
53
55
  throw new Error(`Unknown mark type "${value}".`);
54
56
  }
55
- if (property === "mark.missing" && !["error", "break"].includes(value)) {
56
- throw new Error(`Unsupported area missing policy "${value}".`);
57
+ if (property === "mark.missing") {
58
+ const markType = findLayer(program, parsed.id)?.mark?.type;
59
+ if (markType === "area") {
60
+ if (!["error", "break"].includes(value)) {
61
+ throw new Error(`Unsupported area missing policy "${value}".`);
62
+ }
63
+ } else if (ITEM_MISSING_MARK_TYPES.includes(markType)) {
64
+ validateItemMissing(value, `${markType} missing`);
65
+ } else {
66
+ throw new Error(`Mark type "${markType}" does not support a missing policy.`);
67
+ }
68
+ }
69
+ if (property === "mark.orientation" && (findLayer(program, parsed.id)?.mark?.type !== "bar" ||
70
+ !["vertical", "horizontal"].includes(value))) {
71
+ throw new Error("Bar orientation must be vertical or horizontal.");
72
+ }
73
+ if (property === "derivedBindings.regression") {
74
+ if (!value || typeof value !== "object" || Array.isArray(value) ||
75
+ Object.keys(value).length !== 2 || value.mode !== "follow" ||
76
+ !Array.isArray(value.roles) || value.roles.length !== 3 ||
77
+ value.roles.join(",") !== "data,x,y") {
78
+ throw new Error("Regression derived binding must follow data, x, and y roles.");
79
+ }
80
+ return;
57
81
  }
58
82
  if (property === "layout.mode" && !COLOR_LAYOUTS.includes(value)) {
59
83
  throw new Error(`Unsupported series layout "${value}".`);
@@ -55,6 +55,12 @@ export function validateScaleSemanticValue(program, parsed, value) {
55
55
  return;
56
56
  }
57
57
  if (property === "domain") return validateSemanticScaleDomain(value);
58
+ if (property === "emptyDomain") {
59
+ if (!["preserve", "require-explicit"].includes(value)) {
60
+ throw new Error('Scale emptyDomain must be "preserve" or "require-explicit".');
61
+ }
62
+ return;
63
+ }
58
64
  if (property === "range") return validateSemanticScaleRange(value);
59
65
  if (["nice", "zero", "clamp", "reverse"].includes(property)) {
60
66
  if (typeof value !== "boolean") {
@@ -1,6 +1,6 @@
1
1
  import { closedAction } from "../../core/action.js";
2
2
  import { validateKeys } from "../../core/validation.js";
3
- import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
3
+ import { normalizeRegressionParameters, normalizeRegressionPredict } from "../../grammar/regression/index.js";
4
4
  import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
5
5
  import {
6
6
  findRegressionPoint,
@@ -10,8 +10,9 @@ import {
10
10
  } from "./resolve.js";
11
11
 
12
12
  const REGRESSION_OPTIONS = Object.freeze([
13
- "target", "x", "y", "groupBy", "method", "degree", "span",
13
+ "target", "x", "y", "groupBy", "method", "degree", "span", "robustIterations",
14
14
  "confidenceMethod", "level", "confidence", "interval", "band", "line"
15
+ , "predict", "sourceBinding", "missing"
15
16
  ]);
16
17
 
17
18
  export const createRegression = /* @__PURE__ */ closedAction(
@@ -20,6 +21,10 @@ export const createRegression = /* @__PURE__ */ closedAction(
20
21
  description: "Fit and layer regression lines with optional interval bands."
21
22
  }, REGRESSION_OPTIONS,
22
23
  function (args = {}) {
24
+ const sourceBinding = args.sourceBinding ?? "fixed";
25
+ if (!["fixed", "follow"].includes(sourceBinding)) {
26
+ throw new Error('Regression sourceBinding must be "fixed" or "follow".');
27
+ }
23
28
  const point = findRegressionPoint(this, args.target);
24
29
  const x = args.x === undefined
25
30
  ? point.encoding.x.field
@@ -37,11 +42,21 @@ export const createRegression = /* @__PURE__ */ closedAction(
37
42
  );
38
43
  }
39
44
  const parameters = normalizeRegressionParameters(args);
45
+ const predict = normalizeRegressionPredict(args.predict);
46
+ const regressionDefinition = {
47
+ ...parameters,
48
+ ...(predict === undefined ? {} : { predict }),
49
+ ...(args.missing === undefined ? {} : { missing: args.missing })
50
+ };
40
51
  let band = false;
41
52
  if (parameters.method === "loess") {
42
53
  if (args.band !== undefined && args.band !== false) {
43
54
  throw new Error("LOESS regression does not support a band object.");
44
55
  }
56
+ } else if (parameters.interval === false) {
57
+ if (args.band !== undefined && args.band !== false) {
58
+ throw new Error("Regression interval false cannot include a band object.");
59
+ }
45
60
  } else if (args.band !== false) {
46
61
  band = requireRegressionObject(args.band ?? {}, "Regression band");
47
62
  }
@@ -78,7 +93,7 @@ export const createRegression = /* @__PURE__ */ closedAction(
78
93
  x,
79
94
  y,
80
95
  ...(groupBy === undefined ? {} : { groupBy }),
81
- ...parameters
96
+ ...regressionDefinition
82
97
  });
83
98
  if (band !== false) {
84
99
  next = next.createRegressionBand({
@@ -105,7 +120,7 @@ export const createRegression = /* @__PURE__ */ closedAction(
105
120
  yScale,
106
121
  ...line
107
122
  });
108
- return next._withMarkConfig(point.id, {
123
+ next = next._withMarkConfig(point.id, {
109
124
  ...next.markConfigs[point.id],
110
125
  regression: {
111
126
  source: point.data,
@@ -119,8 +134,14 @@ export const createRegression = /* @__PURE__ */ closedAction(
119
134
  dataId,
120
135
  bandId: band === false ? undefined : bandId,
121
136
  lineId,
122
- parameters
137
+ parameters: regressionDefinition
123
138
  }
124
139
  });
140
+ return sourceBinding === "follow"
141
+ ? next.editSemantic({
142
+ property: `layer[${point.id}].derivedBindings.regression`,
143
+ value: { mode: "follow", roles: ["data", "x", "y"] }
144
+ })
145
+ : next;
125
146
  }
126
147
  );
@@ -7,7 +7,7 @@ import {
7
7
  validateNonNegativeFinite,
8
8
  validateUnitInterval
9
9
  } from "../../core/validation.js";
10
- import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
10
+ import { normalizeRegressionParameters, normalizeRegressionPredict } from "../../grammar/regression/index.js";
11
11
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
12
12
  import {
13
13
  requestedStrokeDetails,
@@ -21,8 +21,9 @@ import { removeOwnedColorLegends } from "../guides/legends/remove.js";
21
21
  import { requireRegressionField } from "./resolve.js";
22
22
 
23
23
  const OPTIONS = Object.freeze([
24
- "target", "data", "x", "y", "groupBy", "method", "degree", "span",
24
+ "target", "data", "x", "y", "groupBy", "method", "degree", "span", "robustIterations",
25
25
  "confidenceMethod", "level", "confidence", "interval", "band", "line"
26
+ , "predict", "sourceBinding", "missing"
26
27
  ]);
27
28
  const BAND_OPTIONS = Object.freeze([
28
29
  "color", "opacity", "stroke", "strokeWidth", "curve",
@@ -110,14 +111,14 @@ function resolveParameters(previous, args) {
110
111
  const method = args.method ?? previous.method;
111
112
  const raw = { method };
112
113
  for (const key of [
113
- "degree", "span", "confidenceMethod", "level", "confidence", "interval"
114
+ "degree", "span", "robustIterations", "confidenceMethod", "level", "confidence", "interval"
114
115
  ]) {
115
116
  if (Object.hasOwn(args, key)) raw[key] = args[key];
116
117
  }
117
118
  const inheritsLevel = !Object.hasOwn(raw, "confidence");
118
- if (method === previous.method) {
119
+ if (method === previous.method && args.interval !== false) {
119
120
  for (const key of [
120
- "degree", "span", "confidenceMethod", "level", "interval"
121
+ "degree", "span", "robustIterations", "confidenceMethod", "level", "interval"
121
122
  ]) {
122
123
  if (
123
124
  !Object.hasOwn(raw, key) &&
@@ -127,7 +128,7 @@ function resolveParameters(previous, args) {
127
128
  raw[key] = previous[key];
128
129
  }
129
130
  }
130
- } else if (method !== "loess" && previous.method !== "loess") {
131
+ } else if (method !== "loess" && previous.method !== "loess" && args.interval !== false) {
131
132
  for (const key of ["confidenceMethod", "level", "interval"]) {
132
133
  if (
133
134
  !Object.hasOwn(raw, key) &&
@@ -138,7 +139,19 @@ function resolveParameters(previous, args) {
138
139
  }
139
140
  }
140
141
  }
141
- return normalizeRegressionParameters(raw);
142
+ const parameters = normalizeRegressionParameters(raw);
143
+ const predict = Object.hasOwn(args, "predict")
144
+ ? args.predict === false ? undefined : normalizeRegressionPredict(args.predict)
145
+ : previous.predict;
146
+ const missing = Object.hasOwn(args, "missing") ? args.missing : previous.missing;
147
+ if (missing !== undefined && !["error", "drop"].includes(missing)) {
148
+ throw new Error('Regression missing must be "error" or "drop".');
149
+ }
150
+ return {
151
+ ...parameters,
152
+ ...(predict === undefined ? {} : { predict }),
153
+ ...(missing === undefined ? {} : { missing })
154
+ };
142
155
  }
143
156
 
144
157
  function regressionColorScale(owner, current, groupBy) {
@@ -245,15 +258,31 @@ export const editRegression = /* @__PURE__ */ closedAction(
245
258
  }
246
259
  const owner = resolveRegressionOwner(this, args.target);
247
260
  const current = this.markConfigs[owner.id].regression;
248
- const source = Object.hasOwn(args, "data")
261
+ const currentBinding = owner.derivedBindings?.regression?.mode === "follow" ? "follow" : "fixed";
262
+ const sourceBinding = args.sourceBinding ?? currentBinding;
263
+ if (!["fixed", "follow"].includes(sourceBinding)) throw new Error('Regression sourceBinding must be "fixed" or "follow".');
264
+ const followed = sourceBinding === "follow" ? {
265
+ data: owner.data,
266
+ x: owner.encoding?.x?.fieldType === "quantitative" ? owner.encoding.x.field : undefined,
267
+ y: owner.encoding?.y?.fieldType === "quantitative" ? owner.encoding.y.field : undefined
268
+ } : undefined;
269
+ if (sourceBinding === "follow" && (!followed.data || !followed.x || !followed.y)) {
270
+ throw new Error(`Following regression source "${owner.id}" requires quantitative x/y fields and data.`);
271
+ }
272
+ for (const key of ["data", "x", "y"]) {
273
+ if (followed !== undefined && Object.hasOwn(args, key) && args[key] !== followed[key]) {
274
+ throw new Error(`Following regression ${key} must match source mark "${owner.id}".`);
275
+ }
276
+ }
277
+ const source = followed?.data ?? (Object.hasOwn(args, "data")
249
278
  ? validateUserId(args.data, "Regression data id")
250
- : current.source;
251
- const x = Object.hasOwn(args, "x")
279
+ : current.source);
280
+ const x = followed?.x ?? (Object.hasOwn(args, "x")
252
281
  ? requireRegressionField(args.x, "Regression x")
253
- : current.x;
254
- const y = Object.hasOwn(args, "y")
282
+ : current.x);
283
+ const y = followed?.y ?? (Object.hasOwn(args, "y")
255
284
  ? requireRegressionField(args.y, "Regression y")
256
- : current.y;
285
+ : current.y);
257
286
  const groupBy = Object.hasOwn(args, "groupBy")
258
287
  ? args.groupBy === false
259
288
  ? undefined
@@ -272,12 +301,12 @@ export const editRegression = /* @__PURE__ */ closedAction(
272
301
  const linePatch = Object.hasOwn(args, "line")
273
302
  ? validateLinePatch(args.line)
274
303
  : undefined;
275
- if (parameters.method === "loess" && bandPatch !== undefined && bandPatch !== false) {
304
+ if ((parameters.method === "loess" || parameters.interval === false) && bandPatch !== undefined && bandPatch !== false) {
276
305
  throw new Error("LOESS regression does not support a band object.");
277
306
  }
278
307
 
279
308
  const hadBand = current.bandId !== undefined;
280
- const wantsBand = parameters.method !== "loess" &&
309
+ const wantsBand = parameters.method !== "loess" && parameters.interval !== false &&
281
310
  (bandPatch === false
282
311
  ? false
283
312
  : hadBand || bandPatch !== undefined || current.parameters.method === "loess");
@@ -385,6 +414,12 @@ export const editRegression = /* @__PURE__ */ closedAction(
385
414
  if (changesStatistics) {
386
415
  next = next.releaseDerivedData(revision.release);
387
416
  }
417
+ const binding = findLayer(next, owner.id)?.derivedBindings?.regression;
418
+ if (sourceBinding === "follow" && binding?.mode !== "follow") {
419
+ next = next.editSemantic({ property: `layer[${owner.id}].derivedBindings.regression`, value: { mode: "follow", roles: ["data", "x", "y"] } });
420
+ } else if (sourceBinding === "fixed" && binding !== undefined) {
421
+ next = next.editSemantic({ property: `layer[${owner.id}].derivedBindings.regression`, remove: true });
422
+ }
388
423
  return next;
389
424
  };
390
425
  return applyEdit(this);