ggaction 0.0.12 → 0.0.13

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 (281) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +2 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +4 -2
  11. package/src/actions/basic.js +2 -0
  12. package/src/actions/boxPlots/materialize.js +2 -1
  13. package/src/actions/canvas/actions.js +3 -1
  14. package/src/actions/canvas/fitting.js +211 -0
  15. package/src/actions/canvas/index.js +2 -0
  16. package/src/actions/categoryOrder/index.js +3 -2
  17. package/src/actions/charts/area.js +47 -0
  18. package/src/actions/charts/bar.js +11 -5
  19. package/src/actions/charts/beeswarm.js +70 -0
  20. package/src/actions/charts/density.js +44 -0
  21. package/src/actions/charts/ecdf.js +183 -0
  22. package/src/actions/charts/endpoints.js +451 -0
  23. package/src/actions/charts/heatmap.js +2 -2
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +71 -0
  26. package/src/actions/charts/index.js +37 -0
  27. package/src/actions/charts/interval-regression.js +169 -0
  28. package/src/actions/charts/line.js +5 -5
  29. package/src/actions/charts/parallel.js +1 -1
  30. package/src/actions/charts/pie.js +44 -0
  31. package/src/actions/charts/polar.js +123 -0
  32. package/src/actions/charts/radar.js +299 -0
  33. package/src/actions/charts/radial.js +42 -0
  34. package/src/actions/charts/raincloud.js +422 -0
  35. package/src/actions/charts/rug-strip.js +279 -0
  36. package/src/actions/charts/scatter.js +9 -3
  37. package/src/actions/charts/shared.js +69 -10
  38. package/src/actions/composition/actions.js +118 -1
  39. package/src/actions/data/bin.js +29 -0
  40. package/src/actions/data/computed.js +24 -0
  41. package/src/actions/data/derived.js +66 -0
  42. package/src/actions/data/ecdf.js +38 -0
  43. package/src/actions/data/filter.js +212 -28
  44. package/src/actions/data/fold.js +24 -0
  45. package/src/actions/data/index.js +24 -0
  46. package/src/actions/data/interval.js +2 -0
  47. package/src/actions/data/intervalEdit.js +86 -4
  48. package/src/actions/data/regression.js +5 -2
  49. package/src/actions/data/stack.js +26 -0
  50. package/src/actions/data/summary.js +26 -0
  51. package/src/actions/data/timeUnit.js +1 -1
  52. package/src/actions/encodings/appearance.js +32 -13
  53. package/src/actions/encodings/barWidth.js +8 -0
  54. package/src/actions/encodings/basic.js +2 -0
  55. package/src/actions/encodings/color/continuous.js +19 -59
  56. package/src/actions/encodings/color/index.js +28 -22
  57. package/src/actions/encodings/color/layout.js +3 -97
  58. package/src/actions/encodings/color/policy.js +5 -8
  59. package/src/actions/encodings/density/resolve.js +37 -5
  60. package/src/actions/encodings/density.js +120 -33
  61. package/src/actions/encodings/horizon/resolve.js +8 -4
  62. package/src/actions/encodings/horizon.js +18 -8
  63. package/src/actions/encodings/index.js +2 -0
  64. package/src/actions/encodings/offset.js +10 -16
  65. package/src/actions/encodings/parallel.js +2 -8
  66. package/src/actions/encodings/position/apply.js +10 -4
  67. package/src/actions/encodings/position/index.js +43 -5
  68. package/src/actions/encodings/position/policies/arc.js +14 -0
  69. package/src/actions/encodings/position/policies/area.js +4 -42
  70. package/src/actions/encodings/position/policies/bar.js +48 -13
  71. package/src/actions/encodings/position/resolve.js +67 -22
  72. package/src/actions/encodings/ranged.js +123 -97
  73. package/src/actions/encodings/remove.js +37 -32
  74. package/src/actions/encodings/ruleAppearance.js +23 -9
  75. package/src/actions/encodings/seriesLayout.js +104 -0
  76. package/src/actions/encodings/shared.js +25 -23
  77. package/src/actions/encodings/strokeDash.js +9 -6
  78. package/src/actions/encodings/temporal.js +7 -0
  79. package/src/actions/encodings/text.js +30 -15
  80. package/src/actions/errorBands/create.js +18 -5
  81. package/src/actions/errorBands/edit.js +341 -21
  82. package/src/actions/errorBars/create.js +21 -10
  83. package/src/actions/errorBars/edit.js +355 -19
  84. package/src/actions/facets/actions.js +410 -12
  85. package/src/actions/facets/derive.js +57 -8
  86. package/src/actions/gradientPlots/components.js +33 -11
  87. package/src/actions/gradientPlots/materialize.js +5 -7
  88. package/src/actions/guides/applicability.js +20 -17
  89. package/src/actions/guides/axes/axes.js +4 -3
  90. package/src/actions/guides/axes/axis.js +18 -18
  91. package/src/actions/guides/axes/components.js +43 -0
  92. package/src/actions/guides/axes/edit.js +16 -51
  93. package/src/actions/guides/axes/labels.js +129 -19
  94. package/src/actions/guides/axes/lines.js +7 -5
  95. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  96. package/src/actions/guides/axes/parallel/policy.js +137 -0
  97. package/src/actions/guides/axes/parallel/resolve.js +44 -23
  98. package/src/actions/guides/axes/parallel.js +93 -217
  99. package/src/actions/guides/axes/policy.js +21 -67
  100. package/src/actions/guides/axes/tickGroups.js +14 -8
  101. package/src/actions/guides/axes/ticks.js +5 -4
  102. package/src/actions/guides/axes/titles.js +21 -8
  103. package/src/actions/guides/basic.js +2 -0
  104. package/src/actions/guides/facade.js +106 -0
  105. package/src/actions/guides/facadeAxes.js +200 -0
  106. package/src/actions/guides/facadeLegend.js +63 -0
  107. package/src/actions/guides/grids/grid.js +37 -33
  108. package/src/actions/guides/grids/resolve.js +10 -2
  109. package/src/actions/guides/guides.js +35 -28
  110. package/src/actions/guides/legends/categorical/actions.js +193 -249
  111. package/src/actions/guides/legends/categorical/components.js +4 -5
  112. package/src/actions/guides/legends/categorical/layout.js +36 -349
  113. package/src/actions/guides/legends/categorical/options.js +30 -14
  114. package/src/actions/guides/legends/categorical/recipes.js +14 -0
  115. package/src/actions/guides/legends/categorical/resolve.js +21 -10
  116. package/src/actions/guides/legends/categorical/symbols.js +2 -3
  117. package/src/actions/guides/legends/continuous/common.js +69 -14
  118. package/src/actions/guides/legends/continuous/gradient.js +111 -83
  119. package/src/actions/guides/legends/continuous/index.js +6 -2
  120. package/src/actions/guides/legends/continuous/interval.js +71 -92
  121. package/src/actions/guides/legends/continuous/opacity.js +105 -84
  122. package/src/actions/guides/legends/creation.js +27 -0
  123. package/src/actions/guides/legends/edit.js +198 -66
  124. package/src/actions/guides/legends/focused.js +2 -2
  125. package/src/actions/guides/legends/lane.js +98 -86
  126. package/src/actions/guides/legends/lifecycle.js +132 -0
  127. package/src/actions/guides/legends/remove.js +31 -76
  128. package/src/actions/guides/legends/size.js +149 -105
  129. package/src/actions/guides/legends/strokeWidth.js +106 -64
  130. package/src/actions/guides/legends/target.js +12 -0
  131. package/src/actions/guides/legends/transition.js +43 -0
  132. package/src/actions/guides/polar/axes/facade.js +77 -62
  133. package/src/actions/guides/polar/axes/labels.js +5 -4
  134. package/src/actions/guides/polar/axes/lines.js +2 -1
  135. package/src/actions/guides/polar/axes/shared.js +6 -0
  136. package/src/actions/guides/polar/axes/ticks.js +5 -4
  137. package/src/actions/guides/polar/axes/titles.js +5 -4
  138. package/src/actions/guides/polar/grids.js +7 -7
  139. package/src/actions/guides/reuse.js +44 -0
  140. package/src/actions/guides/tickValues.js +19 -21
  141. package/src/actions/index.js +2 -0
  142. package/src/actions/intervals/resolve.js +35 -15
  143. package/src/actions/marks/arc/actions.js +1 -1
  144. package/src/actions/marks/area/actions.js +18 -4
  145. package/src/actions/marks/area/materialize.js +5 -2
  146. package/src/actions/marks/bar/materialize.js +11 -7
  147. package/src/actions/marks/index.js +5 -0
  148. package/src/actions/marks/line/actions.js +43 -108
  149. package/src/actions/marks/line/materialize.js +34 -52
  150. package/src/actions/marks/point/edit.js +11 -15
  151. package/src/actions/marks/point/index.js +1 -0
  152. package/src/actions/marks/point/jitter.js +3 -0
  153. package/src/actions/marks/point/materialize.js +106 -2
  154. package/src/actions/marks/point/packing.js +68 -0
  155. package/src/actions/marks/references.js +87 -0
  156. package/src/actions/marks/remove.js +17 -4
  157. package/src/actions/marks/rule/actions.js +34 -3
  158. package/src/actions/marks/rule/appearance.js +26 -0
  159. package/src/actions/marks/shared.js +12 -12
  160. package/src/actions/marks/text/actions.js +224 -6
  161. package/src/actions/marks/text/layout.js +2 -2
  162. package/src/actions/primitives/createGraphics.js +3 -1
  163. package/src/actions/primitives/semantic.js +1 -1
  164. package/src/actions/primitives/semanticAction.js +39 -2
  165. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  166. package/src/actions/primitives/semanticValidation/layer.js +18 -1
  167. package/src/actions/primitives/semanticValidation/scale.js +10 -1
  168. package/src/actions/regression/create.js +1 -1
  169. package/src/actions/regression/edit.js +19 -6
  170. package/src/actions/regression/resolve.js +1 -1
  171. package/src/actions/scales/consumers/common.js +20 -53
  172. package/src/actions/scales/consumers/families.js +27 -14
  173. package/src/actions/scales/consumers/index.js +23 -2
  174. package/src/actions/scales/consumers/seriesLayout.js +33 -2
  175. package/src/actions/scales/create.js +4 -4
  176. package/src/actions/scales/definitions.js +19 -3
  177. package/src/actions/scales/edit.js +46 -34
  178. package/src/actions/scales/editPolicy.js +21 -31
  179. package/src/actions/scales/materialize.js +10 -46
  180. package/src/actions/scales/patch.js +1 -1
  181. package/src/actions/scales/preview.js +39 -0
  182. package/src/actions/scales/quantitativeColor.js +9 -5
  183. package/src/actions/theme/actions.js +43 -0
  184. package/src/actions/theme/index.js +9 -0
  185. package/src/actions/theme/reconcile.js +882 -0
  186. package/src/actions/titles/actions.js +11 -10
  187. package/src/actions/titles/resolve.js +1 -40
  188. package/src/actions/violinPlots/create.js +86 -42
  189. package/src/actions/violinPlots/edit.js +191 -0
  190. package/src/actions/violinPlots/index.js +2 -0
  191. package/src/core/ChartProgram.js +7 -0
  192. package/src/core/action.js +22 -2
  193. package/src/core/compositionState.js +136 -8
  194. package/src/grammar/aggregate.js +66 -23
  195. package/src/grammar/arcs.js +58 -1
  196. package/src/grammar/areaEndpoints.js +46 -0
  197. package/src/grammar/areaSeries.js +87 -41
  198. package/src/grammar/bars/aggregate.js +33 -12
  199. package/src/grammar/bars/policy.js +9 -7
  200. package/src/grammar/bin.js +181 -0
  201. package/src/grammar/categoryOrder.js +34 -0
  202. package/src/grammar/computed.js +150 -0
  203. package/src/grammar/ecdf.js +198 -0
  204. package/src/grammar/facets/index.js +117 -6
  205. package/src/grammar/fold.js +123 -0
  206. package/src/grammar/horizon.js +6 -4
  207. package/src/grammar/interval.js +37 -20
  208. package/src/grammar/lineSeries.js +20 -63
  209. package/src/grammar/markFilter.js +22 -4
  210. package/src/grammar/markLabels.js +93 -0
  211. package/src/grammar/parallelAxisTitles.js +16 -0
  212. package/src/grammar/pathSeries.js +91 -0
  213. package/src/grammar/pointPacking.js +183 -0
  214. package/src/grammar/positionDatum.js +12 -0
  215. package/src/grammar/rects.js +9 -1
  216. package/src/grammar/regression/derive.js +4 -0
  217. package/src/grammar/regression/index.js +7 -2
  218. package/src/grammar/regression/models.js +22 -7
  219. package/src/grammar/regression/parameters.js +40 -13
  220. package/src/grammar/rotation.js +29 -0
  221. package/src/grammar/rules.js +7 -21
  222. package/src/grammar/scales/color.js +23 -1
  223. package/src/grammar/scales/colorConsumers.js +38 -0
  224. package/src/grammar/scales/definition.js +15 -1
  225. package/src/grammar/scales/discretized.js +11 -5
  226. package/src/grammar/scales/fields.js +38 -8
  227. package/src/grammar/scales/mapping.js +2 -0
  228. package/src/grammar/scales/radial.js +55 -0
  229. package/src/grammar/scales/types.js +1 -0
  230. package/src/grammar/schemas/semanticPath.js +17 -3
  231. package/src/grammar/stack.js +170 -0
  232. package/src/grammar/statistics/confidenceInterval.js +101 -0
  233. package/src/grammar/summary.js +183 -0
  234. package/src/grammar/text.js +22 -16
  235. package/src/grammar/timeUnit.js +21 -11
  236. package/src/grammar/transformTopology.js +6 -0
  237. package/src/grammar/transforms.js +36 -0
  238. package/src/grammar/valueFormat.js +112 -0
  239. package/src/grammar/window.js +13 -7
  240. package/src/layout/facets.js +41 -7
  241. package/src/layout/guideCollisions.js +12 -0
  242. package/src/layout/legend.js +2 -10
  243. package/src/layout/legendItems.js +102 -0
  244. package/src/layout/legendLane.js +100 -11
  245. package/src/materialization/bars/aggregate.js +38 -152
  246. package/src/materialization/bars/histogram.js +24 -0
  247. package/src/materialization/categorySlotOffset.js +40 -0
  248. package/src/materialization/compositionSnapshot.js +1 -1
  249. package/src/materialization/dependencies.js +57 -16
  250. package/src/materialization/encodings.js +1 -1
  251. package/src/materialization/facets.js +8 -1
  252. package/src/materialization/guides/layout.js +57 -0
  253. package/src/materialization/guides/resources.js +2 -2
  254. package/src/materialization/legends.js +10 -1
  255. package/src/materialization/marks/capabilities.js +20 -5
  256. package/src/materialization/marks/grouping.js +52 -0
  257. package/src/materialization/marks/index.js +58 -23
  258. package/src/materialization/marks/policies.js +2 -0
  259. package/src/materialization/rect.js +33 -33
  260. package/src/materialization/rowEncoding.js +13 -13
  261. package/src/materialization/scaleGuideDependencies.js +8 -5
  262. package/src/materialization/scales/map.js +1 -0
  263. package/src/materialization/scales/policies/arc.js +42 -0
  264. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  265. package/src/materialization/scales/policies/series.js +2 -1
  266. package/src/materialization/scales/resolve.js +46 -14
  267. package/src/materialization/selection/compatibility.js +11 -0
  268. package/src/materialization/selection/items/bar.js +12 -68
  269. package/src/materialization/selection/items/common.js +8 -2
  270. package/src/materialization/selection/items/path.js +3 -13
  271. package/src/materialization/selection/items/rect.js +7 -4
  272. package/src/materialization/text.js +64 -5
  273. package/src/mcp/adapter.js +2 -2
  274. package/src/selectors/datasets.js +13 -0
  275. package/src/selectors/layers.js +3 -2
  276. package/src/selectors/scales.js +6 -0
  277. package/src/theme/defaults.js +39 -0
  278. package/types/basic.d.ts +11 -2
  279. package/types/index.d.ts +126 -0
  280. package/types/program.d.ts +1320 -135
  281. package/src/materialization/bars/grouped.js +0 -144
@@ -1,12 +1,17 @@
1
+ import { TEXT_LABEL_LAYOUT_OPTIONS } from "./layout.js";
1
2
  import { action } from "../../../core/action.js";
2
3
  import { validateUserId } from "../../../core/identifiers.js";
3
4
  import {
4
5
  DEFAULT_TEXT_MARK,
6
+ isSourceOwnedText,
5
7
  normalizeTextMarkConfig
6
8
  } from "../../../grammar/text.js";
9
+ import { findCoordinate } from "../../../selectors/coordinates.js";
7
10
  import {
8
11
  canMaterializeArc,
9
- canMaterializeText
12
+ canMaterializeRect,
13
+ canMaterializeText,
14
+ isTextSource
10
15
  } from "../../../materialization/marks/index.js";
11
16
  import { resolveTextGraphicItems } from "../../../materialization/text.js";
12
17
  import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
@@ -18,6 +23,10 @@ import {
18
23
  resolveMarkId,
19
24
  validateMarkOptions
20
25
  } from "../shared.js";
26
+ import {
27
+ resolveFacadeData,
28
+ resolveFacadeId
29
+ } from "../../charts/shared.js";
21
30
  import { resolveMarkGraphicPlacement } from
22
31
  "../../../materialization/graphicHierarchy.js";
23
32
 
@@ -25,10 +34,9 @@ const STYLE_OPTIONS = Object.freeze([
25
34
  "fill", "opacity", "fontSize", "fontFamily", "fontWeight",
26
35
  "align", "baseline", "rotation", "dx", "dy"
27
36
  ]);
28
- const CREATE_OPTIONS = Object.freeze(["id", "data", "text", ...STYLE_OPTIONS]);
37
+ const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", ...STYLE_OPTIONS]);
29
38
  const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
30
39
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
31
- const SOURCE_TYPES = new Set(["point", "bar", "rule", "rect", "arc"]);
32
40
 
33
41
  function sourceMatchesData(program, layer, requestedData) {
34
42
  if (requestedData === undefined || layer?.data === requestedData) return true;
@@ -37,16 +45,27 @@ function sourceMatchesData(program, layer, requestedData) {
37
45
 
38
46
  function eligibleSource(program, layer, requestedData) {
39
47
  if (
40
- !SOURCE_TYPES.has(layer?.mark?.type) ||
41
- layer.data === undefined ||
48
+ !isTextSource(layer) ||
42
49
  !sourceMatchesData(program, layer, requestedData)
43
50
  ) return false;
44
51
  if (layer.mark.type === "arc") return canMaterializeArc(program, layer);
52
+ if (layer.mark.type === "rect" && (canMaterializeRect(program, layer) ||
53
+ program.markConfigs[layer.id]?.gradientPlot?.materialized === true)) return true;
45
54
  const encodings = resolveCompatibleEncodings(program, layer, "text");
46
55
  return encodings.x !== undefined && encodings.y !== undefined;
47
56
  }
48
57
 
49
58
  function resolveTextInheritance(program, args) {
59
+ if (Object.hasOwn(args, "source")) {
60
+ if (Object.hasOwn(args, "data")) {
61
+ throw new Error("createTextMark source and data are mutually exclusive.");
62
+ }
63
+ return textInheritance(program, resolveEligibleLayer(program, {
64
+ target: validateUserId(args.source, "Text source id"),
65
+ predicate: isTextSource,
66
+ label: "text source"
67
+ }));
68
+ }
50
69
  if (Object.hasOwn(args, "data")) return undefined;
51
70
  const requestedData = program.context.currentData;
52
71
  const candidates = program.semanticSpec.layers.filter(layer =>
@@ -60,10 +79,14 @@ function resolveTextInheritance(program, args) {
60
79
  : undefined;
61
80
  if (source === undefined && candidates.length > 1) {
62
81
  throw new Error(
63
- "Text source inference is ambiguous; provide data and encode its position explicitly."
82
+ "Text source inference is ambiguous; provide source, or data and encode its position explicitly."
64
83
  );
65
84
  }
66
85
  if (source === undefined) return undefined;
86
+ return textInheritance(program, source);
87
+ }
88
+
89
+ function textInheritance(program, source) {
67
90
  return {
68
91
  source: source.id,
69
92
  data: source.data,
@@ -136,6 +159,199 @@ const createTextMark = action(
136
159
  }
137
160
  );
138
161
 
162
+ const LABEL_OPTIONS = Object.freeze([
163
+ "id", "source", "field", "value", "content", "normalizeBy", "format", "layout",
164
+ ...STYLE_OPTIONS
165
+ ]);
166
+
167
+ const ANNOTATION_OPTIONS = Object.freeze([
168
+ "id", "text", "format", "x", "y", "space", "source", "data", "coordinate", "layout",
169
+ ...STYLE_OPTIONS
170
+ ]);
171
+
172
+ function annotationLayout(value) {
173
+ const layout = value === undefined || value === false ? undefined : value;
174
+ if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
175
+ throw new Error("createAnnotation layout target is owned by the created text layer.");
176
+ }
177
+ if (layout !== undefined) {
178
+ validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createAnnotation layout");
179
+ }
180
+ return layout;
181
+ }
182
+
183
+ function resolveAnnotationBinding(program, args) {
184
+ const hasX = Object.hasOwn(args, "x");
185
+ const hasY = Object.hasOwn(args, "y");
186
+ if (!hasX && !hasY) {
187
+ if (["space", "data", "coordinate"].some(option => Object.hasOwn(args, option))) {
188
+ throw new Error("createAnnotation mark anchor accepts source but not space, data, or coordinate.");
189
+ }
190
+ return { mode: "mark" };
191
+ }
192
+ if (!hasX || !hasY) {
193
+ throw new Error("createAnnotation coordinate anchor requires both x and y.");
194
+ }
195
+ const space = args.space ?? "data";
196
+ if (space !== "data" && space !== "plot") {
197
+ throw new Error("createAnnotation space must be data or plot.");
198
+ }
199
+ if (space === "plot") {
200
+ if (Object.hasOwn(args, "source")) {
201
+ throw new Error("createAnnotation plot anchor does not accept source.");
202
+ }
203
+ if (![args.x, args.y].every(value => Number.isFinite(value) && value >= 0 && value <= 1)) {
204
+ throw new Error("createAnnotation plot coordinates must be finite numbers in [0, 1].");
205
+ }
206
+ return {
207
+ mode: "plot",
208
+ data: resolveFacadeData(program, args.data, "createAnnotation"),
209
+ coordinate: args.coordinate
210
+ };
211
+ }
212
+ if (Object.hasOwn(args, "data") || Object.hasOwn(args, "coordinate")) {
213
+ throw new Error("createAnnotation data anchor uses source data and coordinate.");
214
+ }
215
+ const source = resolveEligibleLayer(program, {
216
+ target: args.source === undefined
217
+ ? undefined
218
+ : validateUserId(args.source, "createAnnotation source"),
219
+ predicate: layer =>
220
+ !isSourceOwnedText(layer) &&
221
+ layer.data !== undefined &&
222
+ layer.encoding?.x?.scale !== undefined &&
223
+ layer.encoding?.y?.scale !== undefined &&
224
+ findCoordinate(program, layer.coordinate)?.type === "cartesian",
225
+ label: "createAnnotation Cartesian layer",
226
+ targetOption: "source"
227
+ });
228
+ return {
229
+ mode: "data",
230
+ data: source.data,
231
+ coordinate: source.coordinate,
232
+ x: source.encoding.x,
233
+ y: source.encoding.y
234
+ };
235
+ }
236
+
237
+ const createAnnotation = action(
238
+ {
239
+ op: "createAnnotation",
240
+ description: "Create text at a final mark, data, or plot anchor."
241
+ },
242
+ function (args = {}) {
243
+ validateMarkOptions(args, ANNOTATION_OPTIONS, "createAnnotation");
244
+ if (!Object.hasOwn(args, "text")) {
245
+ throw new Error("createAnnotation requires text.");
246
+ }
247
+ const id = resolveFacadeId(this, args.id, {
248
+ defaultId: "annotation",
249
+ operation: "createAnnotation"
250
+ });
251
+ const binding = resolveAnnotationBinding(this, args);
252
+ const layout = annotationLayout(args.layout);
253
+ const style = Object.fromEntries(STYLE_OPTIONS
254
+ .filter(option => Object.hasOwn(args, option))
255
+ .map(option => [option, args[option]]));
256
+ const text = {
257
+ value: args.text,
258
+ ...(args.format === undefined ? {} : { format: args.format })
259
+ };
260
+ const apply = program => {
261
+ if (binding.mode === "mark") {
262
+ return program.createMarkLabels({
263
+ id,
264
+ ...(args.source === undefined ? {} : { source: args.source }),
265
+ ...style,
266
+ ...text,
267
+ ...(layout === undefined ? {} : { layout })
268
+ });
269
+ }
270
+ const xScale = binding.mode === "plot" ? `${id}-x` : binding.x.scale;
271
+ const yScale = binding.mode === "plot" ? `${id}-y` : binding.y.scale;
272
+ let next = binding.mode === "plot"
273
+ ? program
274
+ .createScale({ id: xScale, type: "linear", domain: [0, 1] })
275
+ .createScale({ id: yScale, type: "linear", domain: [0, 1] })
276
+ : program;
277
+ next = next
278
+ .createTextMark({ id, data: binding.data, ...style })
279
+ .encodeText({ target: id, ...text })
280
+ .encodeX({
281
+ target: id,
282
+ datum: args.x,
283
+ fieldType: binding.mode === "plot" ? "quantitative" : binding.x.fieldType,
284
+ scale: { id: xScale },
285
+ ...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
286
+ ...(binding.mode === "data" && binding.x.temporalUnit !== undefined
287
+ ? { temporalUnit: binding.x.temporalUnit }
288
+ : {})
289
+ })
290
+ .encodeY({
291
+ target: id,
292
+ datum: args.y,
293
+ fieldType: binding.mode === "plot" ? "quantitative" : binding.y.fieldType,
294
+ scale: { id: yScale },
295
+ ...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
296
+ ...(binding.mode === "data" && binding.y.temporalUnit !== undefined
297
+ ? { temporalUnit: binding.y.temporalUnit }
298
+ : {})
299
+ });
300
+ return layout === undefined
301
+ ? next
302
+ : next.layoutLabels({ ...layout, target: id });
303
+ };
304
+ // Validate the full lower action chain before committing any child effect.
305
+ apply(this);
306
+ return apply(this);
307
+ }
308
+ );
309
+
310
+ const createMarkLabels = action(
311
+ {
312
+ op: "createMarkLabels",
313
+ description: "Create final-item text labels attached to an existing mark."
314
+ },
315
+ function (args = {}) {
316
+ validateMarkOptions(args, LABEL_OPTIONS, "createMarkLabels");
317
+ const inherited = resolveTextInheritance(this, args);
318
+ if (inherited === undefined) {
319
+ throw new Error("createMarkLabels requires an eligible source mark; provide source explicitly.");
320
+ }
321
+ const id = validateUserId(args.id === undefined ? `${inherited.source}-labels` : args.id, "Text mark id");
322
+ assertMarkAvailable(this, id);
323
+ const style = Object.fromEntries(STYLE_OPTIONS
324
+ .filter(option => Object.hasOwn(args, option))
325
+ .map(option => [option, args[option]]));
326
+ normalizeTextMarkConfig(style);
327
+ const encoding = Object.fromEntries(
328
+ ["field", "value", "content", "normalizeBy", "format"]
329
+ .filter(option => Object.hasOwn(args, option))
330
+ .map(option => [option, args[option]])
331
+ );
332
+ if (!["field", "value", "content"].some(option => Object.hasOwn(args, option))) {
333
+ encoding.content = "value";
334
+ }
335
+ const layout = args.layout === undefined || args.layout === false ? undefined : args.layout;
336
+ if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
337
+ throw new Error("createMarkLabels layout target is owned by the created label layer.");
338
+ }
339
+ const apply = program => {
340
+ const next = program.createTextMark({
341
+ id, source: inherited.source, align: "center", baseline: "middle", ...style
342
+ }).encodeText({ target: id, ...encoding });
343
+ return layout === undefined ? next : next.layoutLabels({ ...layout, target: id });
344
+ };
345
+ if (layout !== undefined) {
346
+ // Validate object shape before spreading it into the child action's options.
347
+ validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createMarkLabels layout");
348
+ }
349
+ // Validate all child effects on a discarded immutable branch, as scale edits do.
350
+ apply(this);
351
+ return apply(this);
352
+ }
353
+ );
354
+
139
355
  const rematerializeTextMark = action(
140
356
  {
141
357
  op: "rematerializeTextMark",
@@ -209,6 +425,8 @@ const editTextMark = action(
209
425
  export function registerTextMarkActions(ProgramClass) {
210
426
  Object.assign(ProgramClass.prototype, {
211
427
  createTextMark,
428
+ createMarkLabels,
429
+ createAnnotation,
212
430
  editTextMark,
213
431
  rematerializeTextMark
214
432
  });
@@ -16,7 +16,7 @@ import { resolveTextGraphicEntries } from "../../../materialization/text.js";
16
16
  import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
17
17
  import { editMarkGraphic } from "../shared.js";
18
18
 
19
- const LAYOUT_OPTIONS = Object.freeze([
19
+ export const TEXT_LABEL_LAYOUT_OPTIONS = Object.freeze([
20
20
  "target", "axis", "padding", "maxDisplacement", "bounds", "leader"
21
21
  ]);
22
22
  const REMOVE_OPTIONS = Object.freeze(["target"]);
@@ -252,7 +252,7 @@ const layoutLabels = action(
252
252
  description: "Assign deterministic collision-aware layout to a text mark."
253
253
  },
254
254
  function (args = {}) {
255
- validateOptionObject(args, LAYOUT_OPTIONS, "layoutLabels");
255
+ validateOptionObject(args, TEXT_LABEL_LAYOUT_OPTIONS, "layoutLabels");
256
256
  const layer = requireCompleteText(this, args.target, "layoutLabels");
257
257
  const policy = normalizePolicy(args);
258
258
  const generatedId = leaderId(layer.id);
@@ -129,7 +129,9 @@ const createGraphics = action(
129
129
  id, type: validatedType, parent, before, after
130
130
  });
131
131
 
132
- const existing = this.graphicSpec.objects[id];
132
+ const existing = Object.hasOwn(this.graphicSpec.objects, id)
133
+ ? this.graphicSpec.objects[id]
134
+ : undefined;
133
135
  if (existing !== undefined) {
134
136
  if (
135
137
  sameDefinition(existing, { type: validatedType, length }) &&
@@ -21,7 +21,7 @@ export function registerSemanticPrimitiveAction(ProgramClass) {
21
21
  const validateSemanticValue = createSemanticValueValidator({
22
22
  validateDatasetTransforms,
23
23
  validateParallel,
24
- sourceMarkTypes: ["point", "bar", "rule", "rect", "arc"]
24
+ sourceMarkTypes: ["point", "bar", "line", "rule", "rect", "arc"]
25
25
  });
26
26
  ProgramClass.prototype.editSemantic =
27
27
  createSemanticPrimitiveAction(validateSemanticValue);
@@ -1,3 +1,4 @@
1
+ import { hasSemanticScaleReferences } from "../../selectors/scales.js";
1
2
  import { action } from "../../core/action.js";
2
3
  import {
3
4
  cloneAndFreeze,
@@ -75,7 +76,10 @@ function removeEntity(spec, parsed) {
75
76
  const collection = spec[parsed.collection];
76
77
  const index = collection.findIndex(item => item.id === parsed.id);
77
78
  if (index === -1) return spec;
78
- if (parsed.kind === "layer" && parsed.path.length === 0) {
79
+ if (parsed.kind === "scale" && parsed.path.length === 0) {
80
+ if (hasSemanticScaleReferences(spec, parsed.id)) throw new Error(`Scale "${parsed.id}" is still referenced.`);
81
+ }
82
+ if (["layer", "scale"].includes(parsed.kind) && parsed.path.length === 0) {
79
83
  const nextCollection = collection.filter((_, itemIndex) => itemIndex !== index);
80
84
  return freezeOwned({
81
85
  ...spec,
@@ -118,6 +122,35 @@ function removeRootProperty(spec, root, path) {
118
122
  : spec;
119
123
  }
120
124
 
125
+ // Materializers use this only for an immutable speculative branch while they
126
+ // replay a multi-step recipe. The public semantic primitive deliberately keeps
127
+ // materialized dataset values immutable after creation.
128
+ export function withPreviewDatasetValues(program, {
129
+ id,
130
+ values,
131
+ target,
132
+ omitValues = false
133
+ }) {
134
+ const semanticSpec = {
135
+ ...program.semanticSpec,
136
+ datasets: program.semanticSpec.datasets.map(dataset => {
137
+ if (dataset.id !== id) return dataset;
138
+ if (omitValues) {
139
+ const { values: unused, ...definition } = dataset;
140
+ void unused;
141
+ return definition;
142
+ }
143
+ return { ...dataset, values };
144
+ }),
145
+ layers: target === undefined
146
+ ? program.semanticSpec.layers
147
+ : program.semanticSpec.layers.map(layer =>
148
+ layer.id === target ? { ...layer, data: id } : layer
149
+ )
150
+ };
151
+ return program._clone({ semanticSpec });
152
+ }
153
+
121
154
  export function createSemanticPrimitiveAction(validateSemanticValue) {
122
155
  return action(
123
156
  {
@@ -159,11 +192,15 @@ export function createSemanticPrimitiveAction(validateSemanticValue) {
159
192
  parsed.kind === "layer" &&
160
193
  parsed.path.length === 0 &&
161
194
  this.context.currentMark === parsed.id;
195
+ const removesScale = parsed.kind === "scale" && parsed.path.length === 0;
196
+ const resolvedScales = removesScale ? Object.fromEntries(Object.entries(this.resolvedScales).filter(([id]) => id !== parsed.id)) : this.resolvedScales;
162
197
  return this._clone({
163
198
  semanticSpec,
164
- ...(clearsCurrentData || clearsCurrentMark
199
+ ...(removesScale ? { resolvedScales: freezeOwned(resolvedScales) } : {}),
200
+ ...(clearsCurrentData || clearsCurrentMark || (removesScale && this.context.currentScale === parsed.id)
165
201
  ? { context: freezeOwned({
166
202
  ...this.context,
203
+ ...(removesScale && this.context.currentScale === parsed.id ? { currentScale: undefined } : {}),
167
204
  ...(clearsCurrentData ? { currentData: undefined } : {}),
168
205
  ...(clearsCurrentMark ? { currentMark: undefined } : {})
169
206
  }) }
@@ -1,6 +1,8 @@
1
+ import { normalizeLegendOrder } from "../../../grammar/categoryOrder.js";
1
2
  import { validateUserId } from "../../../core/identifiers.js";
2
3
  import { CATEGORICAL_LEGEND_CHANNELS } from "../../../core/vocabulary.js";
3
4
  import { validateNonEmptySemanticString } from "./shared.js";
5
+ import { validateParallelAxisTitles } from "../../../grammar/parallelAxisTitles.js";
4
6
 
5
7
  function validateLegend(property, value) {
6
8
  if (property === "title") {
@@ -24,6 +26,14 @@ function validateLegend(property, value) {
24
26
 
25
27
  export function validateGuideSemanticValue(_program, parsed, value) {
26
28
  const property = parsed.path.at(-1);
29
+ if (parsed.id === "axis.parallel" && property === "titles") {
30
+ validateParallelAxisTitles(value);
31
+ return;
32
+ }
33
+ if (["legend.series", "legend.color"].includes(parsed.id) && property === "order") {
34
+ normalizeLegendOrder(value);
35
+ return;
36
+ }
27
37
  if (parsed.id === "legend.series") {
28
38
  validateLegend(property, value);
29
39
  return;
@@ -4,6 +4,7 @@ import {
4
4
  MARK_TYPES,
5
5
  STACK_MODES
6
6
  } from "../../../core/vocabulary.js";
7
+ import { validateMarkLabelContent, validateMarkLabelNormalization } from "../../../grammar/markLabels.js";
7
8
  import { validateAggregate } from "../../../grammar/aggregate.js";
8
9
  import {
9
10
  normalizeHistogramBin,
@@ -11,6 +12,7 @@ import {
11
12
  validateHistogramBinStep
12
13
  } from "../../../grammar/histogram.js";
13
14
  import { validatePathOrderDirection } from "../../../grammar/pathOrder.js";
15
+ import { normalizeGroupFields } from "../../../grammar/pathSeries.js";
14
16
  import { normalizeCategoryOrder } from "../../../grammar/categoryOrder.js";
15
17
  import { validateSemanticFieldType } from "../../../grammar/scales/index.js";
16
18
  import { findLayer } from "../../../selectors/layers.js";
@@ -40,7 +42,7 @@ export function validateLayerSemanticValue(
40
42
  parsed,
41
43
  value,
42
44
  {
43
- sourceMarkTypes = ["point", "bar", "rule", "rect", "arc"],
45
+ sourceMarkTypes = ["point", "bar", "line", "rule", "rect", "arc"],
44
46
  validateParallel
45
47
  } = {}
46
48
  ) {
@@ -48,6 +50,17 @@ export function validateLayerSemanticValue(
48
50
  if (property === "mark.type" && !MARK_TYPES.includes(value)) {
49
51
  throw new Error(`Unknown mark type "${value}".`);
50
52
  }
53
+ if (property === "mark.missing" && !["error", "break"].includes(value)) {
54
+ throw new Error(`Unsupported area missing policy "${value}".`);
55
+ }
56
+ if (property === "layout.mode" && !COLOR_LAYOUTS.includes(value)) {
57
+ throw new Error(`Unsupported series layout "${value}".`);
58
+ }
59
+ if (property === "encoding.group.inferredFrom" && !["color", "offset"].includes(value)) {
60
+ throw new Error(`Unsupported group inference origin "${value}".`);
61
+ }
62
+ if (property === "encoding.text.content") validateMarkLabelContent(value);
63
+ if (property === "encoding.text.normalizeBy") validateMarkLabelNormalization(value);
51
64
  if (property === "source") {
52
65
  validateLayerSource(program, parsed, value, sourceMarkTypes);
53
66
  }
@@ -58,6 +71,10 @@ export function validateLayerSemanticValue(
58
71
  throw new Error("Path order field type must be quantitative.");
59
72
  }
60
73
  if (property.endsWith(".fieldType")) validateSemanticFieldType(value);
74
+ if (property === "encoding.group.fields") {
75
+ if (!Array.isArray(value)) throw new TypeError("Group fields must be an array.");
76
+ normalizeGroupFields(value);
77
+ }
61
78
  if (property === "encoding.pathOrder.order") validatePathOrderDirection(value);
62
79
  if (property.endsWith(".categoryOrder")) normalizeCategoryOrder(value);
63
80
  if (property.startsWith("encoding.parallel.")) {
@@ -1,5 +1,7 @@
1
+ import { validateRadialMapping } from "../../../grammar/scales/radial.js";
1
2
  import {
2
3
  validateContinuousColorInterpolation,
4
+ validateSequentialMidpoint,
3
5
  validateScalePropertyForType,
4
6
  validateSemanticScaleDomain,
5
7
  validateSemanticScaleRange,
@@ -16,10 +18,17 @@ function validateOwnedProperty(existing, property) {
16
18
  export function validateScaleSemanticValue(program, parsed, value) {
17
19
  const property = parsed.path.join(".");
18
20
  const existing = findSemanticScale(program, parsed.id);
21
+ if (property === "midpoint") {
22
+ if (value === "auto" || value === undefined) {
23
+ throw new TypeError("Semantic midpoint must be a finite number; remove the property to reset it.");
24
+ }
25
+ return validateSequentialMidpoint(value, existing?.type ?? "sequential");
26
+ }
27
+ if (property === "radialMapping") return validateRadialMapping(value);
19
28
  if (property === "type") {
20
29
  validateSemanticScaleType(value);
21
30
  for (const owned of [
22
- "nice", "zero", "clamp", "base", "exponent", "constant",
31
+ "nice", "zero", "clamp", "base", "exponent", "constant", "midpoint",
23
32
  "paddingInner", "paddingOuter", "padding", "align"
24
33
  ]) {
25
34
  if (existing?.[owned] !== undefined) validateScalePropertyForType(value, owned);
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  const REGRESSION_OPTIONS = Object.freeze([
12
12
  "target", "x", "y", "groupBy", "method", "degree", "span",
13
- "confidence", "interval", "band", "line"
13
+ "confidenceMethod", "level", "confidence", "interval", "band", "line"
14
14
  ]);
15
15
 
16
16
  export const createRegression = action(
@@ -18,7 +18,7 @@ import { requireRegressionField } from "./resolve.js";
18
18
 
19
19
  const OPTIONS = Object.freeze([
20
20
  "target", "data", "x", "y", "groupBy", "method", "degree", "span",
21
- "confidence", "interval", "band", "line"
21
+ "confidenceMethod", "level", "confidence", "interval", "band", "line"
22
22
  ]);
23
23
  const BAND_OPTIONS = Object.freeze([
24
24
  "color", "opacity", "stroke", "strokeWidth", "curve"
@@ -100,18 +100,31 @@ function validateLinePatch(value) {
100
100
  function resolveParameters(previous, args) {
101
101
  const method = args.method ?? previous.method;
102
102
  const raw = { method };
103
- for (const key of ["degree", "span", "confidence", "interval"]) {
103
+ for (const key of [
104
+ "degree", "span", "confidenceMethod", "level", "confidence", "interval"
105
+ ]) {
104
106
  if (Object.hasOwn(args, key)) raw[key] = args[key];
105
107
  }
108
+ const inheritsLevel = !Object.hasOwn(raw, "confidence");
106
109
  if (method === previous.method) {
107
- for (const key of ["degree", "span", "confidence", "interval"]) {
108
- if (!Object.hasOwn(raw, key) && Object.hasOwn(previous, key)) {
110
+ for (const key of [
111
+ "degree", "span", "confidenceMethod", "level", "interval"
112
+ ]) {
113
+ if (
114
+ !Object.hasOwn(raw, key) &&
115
+ (key !== "level" || inheritsLevel) &&
116
+ Object.hasOwn(previous, key)
117
+ ) {
109
118
  raw[key] = previous[key];
110
119
  }
111
120
  }
112
121
  } else if (method !== "loess" && previous.method !== "loess") {
113
- for (const key of ["confidence", "interval"]) {
114
- if (!Object.hasOwn(raw, key) && Object.hasOwn(previous, key)) {
122
+ for (const key of ["confidenceMethod", "level", "interval"]) {
123
+ if (
124
+ !Object.hasOwn(raw, key) &&
125
+ (key !== "level" || inheritsLevel) &&
126
+ Object.hasOwn(previous, key)
127
+ ) {
115
128
  raw[key] = previous[key];
116
129
  }
117
130
  }
@@ -41,7 +41,7 @@ export function findRegressionPoint(program, requested) {
41
41
 
42
42
  export function inferRegressionGroup(layer, args) {
43
43
  if (Object.hasOwn(args, "groupBy")) {
44
- return args.groupBy === undefined
44
+ return args.groupBy === undefined || args.groupBy === false
45
45
  ? undefined
46
46
  : requireRegressionField(args.groupBy, "Regression groupBy");
47
47
  }