ggaction 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +8 -3
  7. package/src/actions/basic.js +2 -2
  8. package/src/actions/boxPlots/components.js +14 -4
  9. package/src/actions/boxPlots/edit.js +31 -5
  10. package/src/actions/boxPlots/materialize.js +11 -3
  11. package/src/actions/boxPlots/options.js +48 -16
  12. package/src/actions/categoryOrder/index.js +15 -4
  13. package/src/actions/charts/area.js +5 -1
  14. package/src/actions/charts/bar.js +2 -1
  15. package/src/actions/charts/density.js +6 -2
  16. package/src/actions/charts/ecdf.js +4 -1
  17. package/src/actions/charts/endpoints.js +25 -3
  18. package/src/actions/charts/heatmap.js +2 -1
  19. package/src/actions/charts/histogram.js +4 -3
  20. package/src/actions/charts/horizon.js +3 -1
  21. package/src/actions/charts/interval-regression.js +5 -2
  22. package/src/actions/charts/line.js +3 -1
  23. package/src/actions/charts/parallel.js +8 -2
  24. package/src/actions/charts/pie.js +10 -2
  25. package/src/actions/charts/polar.js +15 -4
  26. package/src/actions/charts/radar.js +8 -2
  27. package/src/actions/charts/radial.js +10 -2
  28. package/src/actions/charts/rug-strip.js +6 -2
  29. package/src/actions/charts/scatter.js +3 -1
  30. package/src/actions/composition/actions.js +13 -4
  31. package/src/actions/coordinates/actions.js +1 -1
  32. package/src/actions/coordinates/edit.js +76 -0
  33. package/src/actions/data/bin.js +1 -1
  34. package/src/actions/data/bin2d.js +57 -185
  35. package/src/actions/data/complete.js +26 -0
  36. package/src/actions/data/computed.js +2 -2
  37. package/src/actions/data/create.js +2 -2
  38. package/src/actions/data/density.js +12 -20
  39. package/src/actions/data/derived.js +47 -17
  40. package/src/actions/data/edit.js +539 -0
  41. package/src/actions/data/filter.js +14 -2
  42. package/src/actions/data/impute.js +26 -0
  43. package/src/actions/data/index.js +14 -0
  44. package/src/actions/data/normalize.js +27 -0
  45. package/src/actions/data/regression.js +16 -21
  46. package/src/actions/data/revisionBindings.js +145 -0
  47. package/src/actions/data/shared.js +20 -4
  48. package/src/actions/data/summary.js +1 -1
  49. package/src/actions/data/timeUnit.js +4 -1
  50. package/src/actions/data/window.js +1 -1
  51. package/src/actions/encodings/channels.js +415 -0
  52. package/src/actions/encodings/color/index.js +2 -1
  53. package/src/actions/encodings/density.js +5 -2
  54. package/src/actions/encodings/histogram.js +3 -2
  55. package/src/actions/encodings/index.js +4 -0
  56. package/src/actions/encodings/offset.js +20 -13
  57. package/src/actions/encodings/position/apply.js +53 -7
  58. package/src/actions/encodings/position/index.js +10 -2
  59. package/src/actions/encodings/position/policies/bar.js +9 -0
  60. package/src/actions/encodings/position/resolve.js +24 -7
  61. package/src/actions/encodings/remove.js +9 -4
  62. package/src/actions/encodings/ruleAppearance.js +1 -23
  63. package/src/actions/encodings/seriesLayout.js +1 -0
  64. package/src/actions/encodings/shared.js +82 -14
  65. package/src/actions/encodings/stroke.js +226 -0
  66. package/src/actions/errorBands/create.js +23 -3
  67. package/src/actions/errorBands/edit.js +21 -6
  68. package/src/actions/errorBars/create.js +35 -7
  69. package/src/actions/errorBars/edit.js +19 -4
  70. package/src/actions/facets/actions.js +237 -57
  71. package/src/actions/facets/derive.js +91 -26
  72. package/src/actions/gradientPlots/components.js +10 -2
  73. package/src/actions/gradientPlots/edit.js +14 -4
  74. package/src/actions/gradientPlots/materialize.js +3 -1
  75. package/src/actions/gradientPlots/options.js +10 -2
  76. package/src/actions/guides/applicability.js +8 -2
  77. package/src/actions/guides/axes/labels.js +58 -12
  78. package/src/actions/guides/axes/lines.js +6 -2
  79. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  80. package/src/actions/guides/axes/tickGroups.js +2 -1
  81. package/src/actions/guides/axes/ticks.js +6 -2
  82. package/src/actions/guides/axes/titles.js +11 -3
  83. package/src/actions/guides/grids/resolve.js +7 -2
  84. package/src/actions/guides/guides.js +19 -3
  85. package/src/actions/guides/legends/blocks.js +426 -0
  86. package/src/actions/guides/legends/categorical/actions.js +134 -48
  87. package/src/actions/guides/legends/categorical/components.js +17 -12
  88. package/src/actions/guides/legends/categorical/index.js +37 -33
  89. package/src/actions/guides/legends/categorical/layout.js +97 -15
  90. package/src/actions/guides/legends/categorical/options.js +2 -2
  91. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  92. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  93. package/src/actions/guides/legends/categorical/symbols.js +85 -30
  94. package/src/actions/guides/legends/continuous/common.js +30 -12
  95. package/src/actions/guides/legends/continuous/gradient.js +10 -7
  96. package/src/actions/guides/legends/continuous/index.js +2 -0
  97. package/src/actions/guides/legends/continuous/interval.js +8 -3
  98. package/src/actions/guides/legends/continuous/opacity.js +8 -5
  99. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  100. package/src/actions/guides/legends/creation.js +14 -1
  101. package/src/actions/guides/legends/edit.js +158 -59
  102. package/src/actions/guides/legends/index.js +2 -0
  103. package/src/actions/guides/legends/lane.js +54 -19
  104. package/src/actions/guides/legends/lifecycle.js +19 -8
  105. package/src/actions/guides/legends/remove.js +13 -3
  106. package/src/actions/guides/legends/sampling.js +140 -0
  107. package/src/actions/guides/legends/size.js +93 -40
  108. package/src/actions/guides/legends/strokeWidth.js +34 -28
  109. package/src/actions/guides/legends/target.js +112 -1
  110. package/src/actions/guides/legends/transition.js +165 -16
  111. package/src/actions/guides/polar/axes/facade.js +4 -4
  112. package/src/actions/guides/polar/axes/labels.js +24 -5
  113. package/src/actions/guides/polar/axes/lines.js +10 -4
  114. package/src/actions/guides/polar/axes/shared.js +16 -0
  115. package/src/actions/guides/polar/axes/ticks.js +1 -1
  116. package/src/actions/guides/polar/axes/titles.js +1 -1
  117. package/src/actions/guides/polar/grids.js +1 -1
  118. package/src/actions/guides/polar/resolve.js +12 -8
  119. package/src/actions/index.js +14 -3
  120. package/src/actions/marks/arc/actions.js +77 -19
  121. package/src/actions/marks/area/actions.js +34 -9
  122. package/src/actions/marks/area/materialize.js +17 -1
  123. package/src/actions/marks/bar/create.js +11 -2
  124. package/src/actions/marks/bar/edit.js +20 -3
  125. package/src/actions/marks/bar/materialize.js +22 -2
  126. package/src/actions/marks/index.js +10 -1
  127. package/src/actions/marks/line/actions.js +37 -14
  128. package/src/actions/marks/line/materialize.js +29 -10
  129. package/src/actions/marks/point/create.js +8 -2
  130. package/src/actions/marks/point/edit.js +18 -6
  131. package/src/actions/marks/point/materialize.js +49 -15
  132. package/src/actions/marks/rect/actions.js +33 -12
  133. package/src/actions/marks/references.js +601 -30
  134. package/src/actions/marks/remove.js +71 -1
  135. package/src/actions/marks/rule/actions.js +47 -10
  136. package/src/actions/marks/shared.js +1 -1
  137. package/src/actions/marks/text/actions.js +74 -9
  138. package/src/actions/marks/text/index.js +4 -0
  139. package/src/actions/marks/text/layout.js +154 -6
  140. package/src/actions/marks/text/removal.js +173 -0
  141. package/src/actions/marks/text/selection.js +73 -0
  142. package/src/actions/marks/tick/actions.js +22 -5
  143. package/src/actions/primitives/editGraphics.js +26 -0
  144. package/src/actions/primitives/semantic.js +2 -1
  145. package/src/actions/primitives/semanticAction.js +83 -10
  146. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  147. package/src/actions/primitives/semanticValidation/index.js +9 -1
  148. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  149. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  150. package/src/actions/regression/components.js +26 -11
  151. package/src/actions/regression/create.js +10 -2
  152. package/src/actions/regression/edit.js +21 -7
  153. package/src/actions/resources/index.js +7 -0
  154. package/src/actions/resources/remove.js +172 -0
  155. package/src/actions/scales/channels.js +212 -0
  156. package/src/actions/scales/consumers/common.js +1 -1
  157. package/src/actions/scales/consumers/families.js +3 -2
  158. package/src/actions/scales/consumers/index.js +19 -2
  159. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  160. package/src/actions/scales/create.js +54 -22
  161. package/src/actions/scales/definitions.js +94 -40
  162. package/src/actions/scales/edit.js +66 -11
  163. package/src/actions/scales/editPolicy.js +81 -11
  164. package/src/actions/scales/index.js +6 -0
  165. package/src/actions/scales/materialize.js +4 -3
  166. package/src/actions/scales/offset.js +48 -0
  167. package/src/actions/scales/parallel.js +71 -0
  168. package/src/actions/scales/preview.js +111 -10
  169. package/src/actions/selection/actions.js +154 -30
  170. package/src/actions/selection/index.js +3 -0
  171. package/src/actions/theme/actions.js +68 -13
  172. package/src/actions/theme/composition.js +238 -0
  173. package/src/actions/theme/index.js +9 -2
  174. package/src/actions/theme/reconcile.js +315 -67
  175. package/src/actions/theme/state.js +160 -0
  176. package/src/actions/violinPlots/create.js +5 -2
  177. package/src/actions/violinPlots/edit.js +1 -0
  178. package/src/core/action.js +10 -0
  179. package/src/core/compositionState.js +6 -1
  180. package/src/core/programState.js +20 -0
  181. package/src/core/resourceReferences.js +332 -0
  182. package/src/core/vocabulary.js +16 -3
  183. package/src/grammar/bars/aggregate.js +4 -1
  184. package/src/grammar/bars/geometry.js +44 -0
  185. package/src/grammar/bin.js +54 -9
  186. package/src/grammar/complete.js +279 -0
  187. package/src/grammar/computed.js +265 -64
  188. package/src/grammar/coordinates.js +37 -0
  189. package/src/grammar/density.js +239 -37
  190. package/src/grammar/displayLabels.js +57 -0
  191. package/src/grammar/facets/dependencies.js +133 -1
  192. package/src/grammar/facets/guides.js +3 -0
  193. package/src/grammar/facets/index.js +7 -47
  194. package/src/grammar/facets/scales.js +15 -18
  195. package/src/grammar/impute.js +340 -0
  196. package/src/grammar/markLabelSelection.js +57 -0
  197. package/src/grammar/markLabels.js +18 -1
  198. package/src/grammar/normalize.js +366 -0
  199. package/src/grammar/pathSeries.js +26 -8
  200. package/src/grammar/pointShapes.js +13 -4
  201. package/src/grammar/polar.js +90 -8
  202. package/src/grammar/regression/index.js +1 -0
  203. package/src/grammar/regression/parameters.js +23 -0
  204. package/src/grammar/roundedRect.js +180 -0
  205. package/src/grammar/rules.js +1 -0
  206. package/src/grammar/scales/colorConsumers.js +20 -1
  207. package/src/grammar/scales/continuous.js +2 -2
  208. package/src/grammar/scales/definition.js +27 -4
  209. package/src/grammar/scales/index.js +1 -0
  210. package/src/grammar/scales/ordinal.js +12 -3
  211. package/src/grammar/scales/policies.js +2 -2
  212. package/src/grammar/scales/size.js +456 -0
  213. package/src/grammar/schemas/concreteGraphic.js +14 -2
  214. package/src/grammar/schemas/graphic.js +15 -0
  215. package/src/grammar/schemas/graphicBounds.js +179 -30
  216. package/src/grammar/schemas/semanticPath.js +7 -2
  217. package/src/grammar/statisticalReference.js +103 -0
  218. package/src/grammar/strokeStyle.js +77 -0
  219. package/src/grammar/summary.js +78 -16
  220. package/src/grammar/timeUnit.js +216 -5
  221. package/src/grammar/transformTopology.js +4 -0
  222. package/src/grammar/transforms.js +394 -28
  223. package/src/grammar/weightedStatistics.js +374 -0
  224. package/src/grammar/window.js +221 -16
  225. package/src/layout/aspect.js +80 -0
  226. package/src/layout/facets.js +59 -5
  227. package/src/layout/labels.js +343 -0
  228. package/src/materialization/bars/aggregate.js +25 -2
  229. package/src/materialization/bars/histogram.js +84 -24
  230. package/src/materialization/bars/ranged.js +19 -3
  231. package/src/materialization/bars/resolve.js +7 -4
  232. package/src/materialization/composition.js +3 -1
  233. package/src/materialization/coordinateBounds.js +204 -0
  234. package/src/materialization/dependencies.js +36 -0
  235. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  236. package/src/materialization/facetGuides/placement.js +8 -2
  237. package/src/materialization/facetGuides/preparation.js +12 -7
  238. package/src/materialization/facetHeaders.js +346 -0
  239. package/src/materialization/facets.js +15 -65
  240. package/src/materialization/guides/resources.js +26 -0
  241. package/src/materialization/legends.js +1 -1
  242. package/src/materialization/marks/index.js +11 -5
  243. package/src/materialization/marks/policies.js +2 -1
  244. package/src/materialization/rect.js +25 -12
  245. package/src/materialization/rectConfig.js +5 -1
  246. package/src/materialization/scales/map.js +5 -3
  247. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  248. package/src/materialization/scales/policies/offset.js +50 -8
  249. package/src/materialization/scales/policies/series.js +4 -1
  250. package/src/materialization/scales/resolve.js +22 -6
  251. package/src/materialization/selection/items/arc.js +28 -4
  252. package/src/materialization/selection/items/bar.js +30 -3
  253. package/src/materialization/selection/items/common.js +19 -4
  254. package/src/materialization/selection/items/rect.js +28 -4
  255. package/src/materialization/selection/path.js +1 -1
  256. package/src/materialization/selection/point.js +3 -1
  257. package/src/materialization/text.js +265 -14
  258. package/src/renderers/canvas/circle.js +2 -3
  259. package/src/renderers/canvas/line.js +2 -3
  260. package/src/renderers/canvas/native.js +4 -4
  261. package/src/renderers/canvas/path.js +2 -3
  262. package/src/renderers/canvas/rect.js +2 -3
  263. package/src/renderers/canvas/stroke.js +11 -0
  264. package/src/renderers/svg.js +19 -0
  265. package/src/selectors/datasets.js +32 -1
  266. package/src/selectors/layers.js +4 -0
  267. package/src/theme/defaults.js +101 -0
  268. package/types/basic.d.ts +6 -0
  269. package/types/index.d.ts +85 -0
  270. package/types/program.d.ts +910 -117
@@ -1,15 +1,56 @@
1
1
  import { isSourceOwnedText } from "../../grammar/text.js";
2
2
  import { action } from "../../core/action.js";
3
+ import { isPlainObject } from "../../core/immutable.js";
3
4
  import { validateUserId } from "../../core/identifiers.js";
5
+ import { validateKeys } from "../../core/validation.js";
4
6
  import { findCoordinate } from "../../selectors/coordinates.js";
5
- import { resolveEligibleLayer } from "../../selectors/layers.js";
6
- import { resolveFacadeData, resolveFacadeId, validateFacadeOptions } from "../charts/shared.js";
7
+ import {
8
+ findDataset,
9
+ hasDataset,
10
+ hasDatasetOwner
11
+ } from "../../selectors/datasets.js";
12
+ import { findLayer, resolveEligibleLayer } from "../../selectors/layers.js";
13
+ import { resolveConsumerValuesForDataset } from
14
+ "../scales/consumers/index.js";
15
+ import { resolveMarkItems } from
16
+ "../../materialization/selection/policies/index.js";
17
+ import { getMarkMaterializationPolicy } from
18
+ "../../materialization/marks/policies.js";
19
+ import { requireDerivedDataset } from "../data/shared.js";
20
+ import { withRematerializedDerivedDataset } from
21
+ "../primitives/semanticAction.js";
22
+ import {
23
+ deriveStatisticalReferenceValues,
24
+ normalizeReferenceField,
25
+ normalizeReferencePopulation,
26
+ normalizeReferenceStatistics,
27
+ normalizeStatisticalReferenceTransform,
28
+ validateStatisticalReferenceTransform
29
+ } from "../../grammar/statisticalReference.js";
30
+ import {
31
+ resolveFacadeData,
32
+ resolveFacadeId,
33
+ validateFacadeOptions
34
+ } from "../charts/shared.js";
35
+ import { RECT_STYLE_PROPERTIES } from "../../grammar/roundedRect.js";
36
+ import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
7
37
 
8
- const COMMON_OPTIONS = ["id", "x", "y", "space", "source", "data", "coordinate", "temporalUnit"];
9
- const LINE_STYLE = ["stroke", "strokeWidth", "strokeDash", "opacity"];
10
- const BAND_STYLE = ["fill", "opacity", "stroke", "strokeWidth"];
38
+ const COMMON_OPTIONS = Object.freeze([
39
+ "id", "x", "y", "space", "source", "data", "coordinate", "temporalUnit"
40
+ ]);
41
+ const DYNAMIC_OPTIONS = Object.freeze([
42
+ "id", "source", "axis", "population", "field", "statistic", "statistics"
43
+ ]);
44
+ const LINE_STYLE = Object.freeze([
45
+ "stroke", "strokeWidth", "strokeDash", "opacity", ...STROKE_STYLE_PROPERTIES
46
+ ]);
47
+ const BAND_STYLE = Object.freeze([
48
+ "fill", "opacity", "stroke", "strokeWidth", ...RECT_STYLE_PROPERTIES
49
+ ]);
50
+ const MATERIALIZE_OPTIONS = Object.freeze(["id"]);
51
+ const SERIES_MARKS = new Set(["line", "area"]);
11
52
 
12
- function resolveBinding(program, args, axis, operation, band) {
53
+ function resolveLiteralBinding(program, args, axis, operation, band) {
13
54
  const space = args.space === undefined ? "data" : args.space;
14
55
  if (space !== "data" && space !== "plot") {
15
56
  throw new Error(`${operation} space must be data or plot.`);
@@ -18,70 +59,600 @@ function resolveBinding(program, args, axis, operation, band) {
18
59
  if (Object.hasOwn(args, "source") || Object.hasOwn(args, "temporalUnit")) {
19
60
  throw new Error(`${operation} plot space does not accept source or temporalUnit.`);
20
61
  }
21
- return { data: resolveFacadeData(program, args.data, operation),
22
- fieldType: "quantitative", coordinate: args.coordinate, plot: true };
62
+ return {
63
+ data: resolveFacadeData(program, args.data, operation),
64
+ fieldType: "quantitative",
65
+ coordinate: args.coordinate,
66
+ plot: true
67
+ };
23
68
  }
24
69
  if (Object.hasOwn(args, "data") || Object.hasOwn(args, "coordinate")) {
25
70
  throw new Error(`${operation} data space uses source data and coordinate.`);
26
71
  }
27
72
  const source = resolveEligibleLayer(program, {
28
- target: args.source === undefined ? undefined : validateUserId(args.source, `${operation} source`),
29
- predicate: layer => !isSourceOwnedText(layer) && layer.data !== undefined && layer.encoding?.[axis]?.scale !== undefined &&
73
+ target: args.source === undefined
74
+ ? undefined
75
+ : validateUserId(args.source, `${operation} source`),
76
+ predicate: layer =>
77
+ !isSourceOwnedText(layer) &&
78
+ layer.data !== undefined &&
79
+ layer.encoding?.[axis]?.scale !== undefined &&
30
80
  findCoordinate(program, layer.coordinate)?.type === "cartesian" &&
31
- (!band || ["quantitative", "temporal"].includes(layer.encoding[axis].fieldType)),
81
+ (!band || ["quantitative", "temporal"].includes(
82
+ layer.encoding[axis].fieldType
83
+ )),
32
84
  label: `${operation} Cartesian layer`,
33
85
  targetOption: "source"
34
86
  });
35
87
  const encoding = source.encoding[axis];
36
- return { data: source.data, coordinate: source.coordinate, scale: encoding.scale,
37
- fieldType: encoding.fieldType, temporalUnit: args.temporalUnit === undefined ? encoding.temporalUnit : args.temporalUnit };
88
+ return {
89
+ data: source.data,
90
+ coordinate: source.coordinate,
91
+ scale: encoding.scale,
92
+ fieldType: encoding.fieldType,
93
+ temporalUnit: args.temporalUnit === undefined
94
+ ? encoding.temporalUnit
95
+ : args.temporalUnit
96
+ };
38
97
  }
39
98
 
40
- function createReference(program, args, band) {
99
+ function createLiteralReference(program, args, band) {
41
100
  const operation = band ? "createReferenceBand" : "createReferenceLine";
42
101
  const styleOptions = band ? BAND_STYLE : LINE_STYLE;
43
102
  validateFacadeOptions(args, [...COMMON_OPTIONS, ...styleOptions], operation);
44
103
  const axes = ["x", "y"].filter(axis => Object.hasOwn(args, axis));
45
- if (axes.length !== 1) throw new Error(`${operation} requires exactly one of x or y.`);
104
+ if (axes.length !== 1) {
105
+ throw new Error(`${operation} requires exactly one of x or y.`);
106
+ }
46
107
  const axis = axes[0];
47
108
  const values = band ? args[axis] : [args[axis]];
48
109
  if (!Array.isArray(values) || values.length !== (band ? 2 : 1)) {
49
110
  throw new Error(`${operation} ${axis} requires a two-value interval.`);
50
111
  }
51
112
  const id = resolveFacadeId(program, args.id, {
52
- defaultId: band ? "referenceBand" : "referenceLine", operation
113
+ defaultId: band ? "referenceBand" : "referenceLine",
114
+ operation
53
115
  });
54
- const binding = resolveBinding(program, args, axis, operation, band);
55
- if (binding.plot && !values.every(value => Number.isFinite(value) && value >= 0 && value <= 1)) {
56
- throw new Error(`${operation} plot coordinates must be finite numbers in [0, 1].`);
116
+ const binding = resolveLiteralBinding(program, args, axis, operation, band);
117
+ if (
118
+ binding.plot &&
119
+ !values.every(value =>
120
+ Number.isFinite(value) && value >= 0 && value <= 1
121
+ )
122
+ ) {
123
+ throw new Error(
124
+ `${operation} plot coordinates must be finite numbers in [0, 1].`
125
+ );
57
126
  }
58
127
  const scale = binding.plot ? `${id}-${axis}` : binding.scale;
59
- const style = Object.fromEntries(styleOptions.filter(key => args[key] !== undefined).map(key => [key, args[key]]));
128
+ const style = Object.fromEntries(
129
+ styleOptions
130
+ .filter(key => args[key] !== undefined)
131
+ .map(key => [key, args[key]])
132
+ );
60
133
  const encode = axis === "x" ? "encodeX" : "encodeY";
61
- const options = { target: id, fieldType: binding.fieldType, scale: { id: scale },
62
- ...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
63
- ...(binding.temporalUnit === undefined ? {} : { temporalUnit: binding.temporalUnit }) };
134
+ const options = {
135
+ target: id,
136
+ fieldType: binding.fieldType,
137
+ scale: { id: scale },
138
+ ...(binding.coordinate === undefined
139
+ ? {}
140
+ : { coordinate: binding.coordinate }),
141
+ ...(binding.temporalUnit === undefined
142
+ ? {}
143
+ : { temporalUnit: binding.temporalUnit })
144
+ };
64
145
  const apply = initial => {
65
146
  let next = binding.plot
66
147
  ? initial.createScale({ id: scale, type: "linear", domain: [0, 1] })
67
148
  : initial;
68
149
  next = band
69
- ? next.createRectMark({ id, data: binding.data, fill: "#94a3b8", opacity: 0.15, stroke: false, ...style })
70
- : next.createRuleMark({ id, data: binding.data, stroke: "#64748b", strokeWidth: 1, strokeDash: "dashed", ...style });
150
+ ? next.createRectMark({
151
+ id,
152
+ data: binding.data,
153
+ fill: "#94a3b8",
154
+ opacity: 0.15,
155
+ stroke: false,
156
+ ...style
157
+ })
158
+ : next.createRuleMark({
159
+ id,
160
+ data: binding.data,
161
+ stroke: "#64748b",
162
+ strokeWidth: 1,
163
+ strokeDash: "dashed",
164
+ ...style
165
+ });
71
166
  next = next[encode]({ ...options, datum: values[0] });
72
- return band ? next[`${encode}2`]({ ...options, datum: values[1] }) : next;
167
+ if (band) next = next[`${encode}2`]({ ...options, datum: values[1] });
168
+ return next._withMarkConfig(id, {
169
+ ...next.markConfigs[id],
170
+ referenceAuthoring: { kind: "literal" }
171
+ });
73
172
  };
74
173
  // Validate the complete lower-action chain on a discarded immutable branch.
75
174
  apply(program);
76
175
  return apply(program);
77
176
  }
78
177
 
178
+ function requireStatisticalSource(program, config, referenceId) {
179
+ if (!isPlainObject(config)) {
180
+ throw new TypeError(
181
+ `Statistical reference "${referenceId}" requires materialization config.`
182
+ );
183
+ }
184
+ const sourceId = validateUserId(
185
+ config.source,
186
+ `Statistical reference "${referenceId}" source`
187
+ );
188
+ const source = findLayer(program, sourceId);
189
+ if (source === undefined) {
190
+ throw new Error(
191
+ `Statistical reference "${referenceId}" source "${sourceId}" does not exist.`
192
+ );
193
+ }
194
+ if (
195
+ sourceId === referenceId ||
196
+ program.markConfigs[sourceId]?.referenceAuthoring !== undefined ||
197
+ program.markConfigs[sourceId]?.statisticalReference !== undefined
198
+ ) {
199
+ throw new Error(
200
+ `Statistical reference "${referenceId}" source must be a non-reference mark.`
201
+ );
202
+ }
203
+ if (findCoordinate(program, source.coordinate)?.type !== "cartesian") {
204
+ throw new Error(
205
+ `Statistical reference "${referenceId}" source must use Cartesian coordinates.`
206
+ );
207
+ }
208
+ if (!["x", "y"].includes(config.axis)) {
209
+ throw new Error(
210
+ `Statistical reference "${referenceId}" axis must be x or y.`
211
+ );
212
+ }
213
+ const encoding = source.encoding?.[config.axis];
214
+ if (
215
+ encoding?.fieldType !== "quantitative" ||
216
+ typeof encoding.scale !== "string" ||
217
+ encoding.scale.length === 0
218
+ ) {
219
+ throw new Error(
220
+ `Statistical reference "${referenceId}" source axis must have a quantitative scaled encoding.`
221
+ );
222
+ }
223
+ if (
224
+ !isPlainObject(config.field) ||
225
+ !["axis", "explicit"].includes(config.field.kind) ||
226
+ (config.field.kind === "explicit" &&
227
+ (typeof config.field.field !== "string" || config.field.field.length === 0))
228
+ ) {
229
+ throw new Error(
230
+ `Statistical reference "${referenceId}" has an invalid field binding.`
231
+ );
232
+ }
233
+ return { source, encoding };
234
+ }
235
+
236
+ function resolvePopulationDataset(program, source, population, referenceId) {
237
+ let dataset = findDataset(program, source.data);
238
+ if (dataset === undefined || !Array.isArray(dataset.values)) {
239
+ throw new Error(
240
+ `Statistical reference "${referenceId}" source requires materialized data.`
241
+ );
242
+ }
243
+ if (population === "visibleItems") return dataset;
244
+ while (
245
+ dataset.transform?.length === 1 &&
246
+ dataset.transform[0].type === "markFilter" &&
247
+ dataset.transform[0].target === source.id
248
+ ) {
249
+ const parent = findDataset(program, dataset.source);
250
+ if (parent === undefined || !Array.isArray(parent.values)) {
251
+ throw new Error(
252
+ `Statistical reference "${referenceId}" bound data is unavailable.`
253
+ );
254
+ }
255
+ dataset = parent;
256
+ }
257
+ return dataset;
258
+ }
259
+
260
+ function strictFieldValues(rows, field, label) {
261
+ return rows.map((row, index) => {
262
+ if (!isPlainObject(row) || !Object.hasOwn(row, field)) {
263
+ throw new Error(`${label} field "${field}" is missing at item ${index}.`);
264
+ }
265
+ const value = row[field];
266
+ if (!Number.isFinite(value)) {
267
+ throw new TypeError(
268
+ `${label} field "${field}" must contain finite numbers; item ${index} is invalid.`
269
+ );
270
+ }
271
+ return value;
272
+ });
273
+ }
274
+
275
+ function visibleExplicitValues(program, source, dataset, field, referenceId) {
276
+ if (SERIES_MARKS.has(source.mark?.type) || source.encoding?.parallel !== undefined) {
277
+ throw new Error(
278
+ `Statistical reference "${referenceId}" visibleItems population is ambiguous for series marks.`
279
+ );
280
+ }
281
+ let resolvedProgram = program;
282
+ if (source.data !== dataset.id) {
283
+ const policy = getMarkMaterializationPolicy(source);
284
+ if (typeof policy?.op !== "string") {
285
+ throw new Error(
286
+ `Statistical reference "${referenceId}" cannot resolve visible items for its source mark.`
287
+ );
288
+ }
289
+ resolvedProgram = program
290
+ .editSemantic({
291
+ property: `layer[${source.id}].data`,
292
+ value: dataset.id
293
+ })
294
+ [policy.op]({ id: source.id });
295
+ }
296
+ const items = resolveMarkItems(resolvedProgram, source.id);
297
+ return items.map((item, index) => {
298
+ if (!Object.hasOwn(item.fields, field)) {
299
+ throw new Error(
300
+ `Statistical reference "${referenceId}" field "${field}" is not scalar at visible item ${index}.`
301
+ );
302
+ }
303
+ const value = item.fields[field];
304
+ if (!Number.isFinite(value)) {
305
+ throw new TypeError(
306
+ `Statistical reference "${referenceId}" field "${field}" must be finite at visible item ${index}.`
307
+ );
308
+ }
309
+ return value;
310
+ });
311
+ }
312
+
313
+ function resolvePopulationValues(
314
+ program,
315
+ source,
316
+ encoding,
317
+ config,
318
+ dataset,
319
+ referenceId
320
+ ) {
321
+ if (
322
+ config.population === "visibleItems" &&
323
+ (SERIES_MARKS.has(source.mark?.type) || source.encoding?.parallel !== undefined)
324
+ ) {
325
+ throw new Error(
326
+ `Statistical reference "${referenceId}" visibleItems population is ambiguous for series marks.`
327
+ );
328
+ }
329
+ if (config.field.kind === "explicit") {
330
+ return config.population === "visibleItems"
331
+ ? visibleExplicitValues(
332
+ program,
333
+ source,
334
+ dataset,
335
+ config.field.field,
336
+ referenceId
337
+ )
338
+ : strictFieldValues(
339
+ dataset.values,
340
+ config.field.field,
341
+ `Statistical reference "${referenceId}" boundData`
342
+ );
343
+ }
344
+ return resolveConsumerValuesForDataset(program, {
345
+ layer: source,
346
+ channel: config.axis,
347
+ encoding
348
+ }, dataset);
349
+ }
350
+
351
+ function statisticalRows(program, referenceId, dataset) {
352
+ const config = program.markConfigs[referenceId]?.statisticalReference;
353
+ const { source, encoding } = requireStatisticalSource(
354
+ program,
355
+ config,
356
+ referenceId
357
+ );
358
+ if (!Array.isArray(config.statistics) || ![1, 2].includes(config.statistics.length)) {
359
+ throw new Error(
360
+ `Statistical reference "${referenceId}" requires one or two statistics.`
361
+ );
362
+ }
363
+ const statistics = normalizeReferenceStatistics(
364
+ config.statistics,
365
+ config.statistics.length,
366
+ `Statistical reference "${referenceId}"`
367
+ );
368
+ const population = normalizeReferencePopulation(
369
+ config.population,
370
+ `Statistical reference "${referenceId}" population`
371
+ );
372
+ const values = resolvePopulationValues(
373
+ program,
374
+ source,
375
+ encoding,
376
+ { ...config, population, statistics },
377
+ dataset,
378
+ referenceId
379
+ );
380
+ const resolved = deriveStatisticalReferenceValues(
381
+ values,
382
+ statistics,
383
+ `Statistical reference "${referenceId}"`
384
+ );
385
+ if (resolved.length === 2 && resolved[0] > resolved[1]) {
386
+ throw new Error(
387
+ `Statistical reference band "${referenceId}" lower statistic exceeds its upper statistic.`
388
+ );
389
+ }
390
+ return resolved.length === 1
391
+ ? [{ value: resolved[0] }]
392
+ : [{ lower: resolved[0], upper: resolved[1] }];
393
+ }
394
+
395
+ function replaceStatisticalDataset(program, id, source, transform, values) {
396
+ const dataset = findDataset(program, id);
397
+ if (dataset === undefined || dataset.source === undefined) {
398
+ throw new Error(`Unknown statistical reference dataset "${id}".`);
399
+ }
400
+ validateStatisticalReferenceTransform(transform);
401
+ return withRematerializedDerivedDataset(program, {
402
+ id,
403
+ source,
404
+ transform,
405
+ values
406
+ });
407
+ }
408
+
409
+ function syncReferenceEncoding(program, id, source, config) {
410
+ const layer = findLayer(program, id);
411
+ if (layer === undefined) {
412
+ throw new Error(`Unknown statistical reference mark "${id}".`);
413
+ }
414
+ const axis = config.axis;
415
+ const secondary = `${axis}2`;
416
+ const retained = new Set(
417
+ config.statistics.length === 1 ? [axis] : [axis, secondary]
418
+ );
419
+ let next = program;
420
+ for (const channel of ["x", "y", "x2", "y2"]) {
421
+ if (!retained.has(channel) && layer.encoding?.[channel] !== undefined) {
422
+ next = next.editSemantic({
423
+ property: `layer[${id}].encoding.${channel}`,
424
+ remove: true
425
+ });
426
+ }
427
+ }
428
+ const scale = source.encoding[axis].scale;
429
+ const fields = config.statistics.length === 1
430
+ ? [[axis, "value"]]
431
+ : [[axis, "lower"], [secondary, "upper"]];
432
+ for (const [channel, field] of fields) {
433
+ const current = findLayer(next, id).encoding?.[channel];
434
+ if (current?.datum !== undefined) {
435
+ next = next.editSemantic({
436
+ property: `layer[${id}].encoding.${channel}.datum`,
437
+ remove: true
438
+ });
439
+ }
440
+ for (const [property, value] of Object.entries({
441
+ field,
442
+ fieldType: "quantitative",
443
+ scale
444
+ })) {
445
+ if (findLayer(next, id).encoding?.[channel]?.[property] !== value) {
446
+ next = next.editSemantic({
447
+ property: `layer[${id}].encoding.${channel}.${property}`,
448
+ value
449
+ });
450
+ }
451
+ }
452
+ }
453
+ const currentCoordinate = findLayer(next, id).coordinate;
454
+ if (source.coordinate === undefined && currentCoordinate !== undefined) {
455
+ next = next.editSemantic({
456
+ property: `layer[${id}].coordinate`,
457
+ remove: true
458
+ });
459
+ } else if (source.coordinate !== undefined && source.coordinate !== currentCoordinate) {
460
+ next = next.editSemantic({
461
+ property: `layer[${id}].coordinate`,
462
+ value: source.coordinate
463
+ });
464
+ }
465
+ return next;
466
+ }
467
+
468
+ export const materializeStatisticalReferenceData = action(
469
+ {
470
+ op: "materializeStatisticalReferenceData",
471
+ description: "Materialize one owned statistical reference value dataset."
472
+ },
473
+ function (args = {}) {
474
+ validateKeys(args, MATERIALIZE_OPTIONS, "materializeStatisticalReferenceData");
475
+ const { id, source, transform } = requireDerivedDataset(
476
+ this,
477
+ args.id,
478
+ "statisticalReference"
479
+ );
480
+ const values = statisticalRows(this, transform.target, source);
481
+ return this.editSemantic({
482
+ property: `dataset[${id}].values`,
483
+ value: values
484
+ });
485
+ }
486
+ );
487
+
488
+ export const rematerializeStatisticalReference = action(
489
+ {
490
+ op: "rematerializeStatisticalReference",
491
+ description: "Recompute one source-dependent statistical reference."
492
+ },
493
+ function (args = {}) {
494
+ validateKeys(args, MATERIALIZE_OPTIONS, "rematerializeStatisticalReference");
495
+ const id = validateUserId(args.id, "Statistical reference mark id");
496
+ const config = this.markConfigs[id]?.statisticalReference;
497
+ const { source } = requireStatisticalSource(this, config, id);
498
+ const population = normalizeReferencePopulation(config.population);
499
+ const dataset = resolvePopulationDataset(this, source, population, id);
500
+ const values = statisticalRows(this, id, dataset);
501
+ const transform = normalizeStatisticalReferenceTransform({ target: id });
502
+ let next = replaceStatisticalDataset(
503
+ this,
504
+ config.dataId,
505
+ dataset.id,
506
+ transform,
507
+ values
508
+ );
509
+ if (findLayer(next, id)?.data !== config.dataId) {
510
+ next = next.editSemantic({
511
+ property: `layer[${id}].data`,
512
+ value: config.dataId
513
+ });
514
+ }
515
+ next = syncReferenceEncoding(next, id, source, config);
516
+ next = config.statistics.length === 1
517
+ ? next.rematerializeRuleMark({ id })
518
+ : next.rematerializeRectMark({ id });
519
+ return next._withContext(this.context);
520
+ }
521
+ );
522
+
523
+ function isDynamicReference(args) {
524
+ return ["axis", "population", "field", "statistic", "statistics"]
525
+ .some(key => Object.hasOwn(args, key));
526
+ }
527
+
528
+ function normalizeDynamicReference(program, args, band) {
529
+ const operation = band ? "createReferenceBand" : "createReferenceLine";
530
+ const styleOptions = band ? BAND_STYLE : LINE_STYLE;
531
+ validateFacadeOptions(
532
+ args,
533
+ [...DYNAMIC_OPTIONS, ...styleOptions],
534
+ operation
535
+ );
536
+ if (typeof args.source !== "string") {
537
+ throw new TypeError(`${operation} statistical source must be a mark id.`);
538
+ }
539
+ const sourceId = validateUserId(args.source, `${operation} source`);
540
+ if (!["x", "y"].includes(args.axis)) {
541
+ throw new Error(`${operation} statistical axis must be x or y.`);
542
+ }
543
+ if (band && Object.hasOwn(args, "statistic")) {
544
+ throw new Error(`${operation} uses statistics, not statistic.`);
545
+ }
546
+ if (!band && Object.hasOwn(args, "statistics")) {
547
+ throw new Error(`${operation} uses statistic, not statistics.`);
548
+ }
549
+ const statistics = normalizeReferenceStatistics(
550
+ band ? args.statistics : [args.statistic],
551
+ band ? 2 : 1,
552
+ operation
553
+ );
554
+ const id = resolveFacadeId(program, args.id, {
555
+ defaultId: band ? "referenceBand" : "referenceLine",
556
+ operation
557
+ });
558
+ const config = {
559
+ source: sourceId,
560
+ axis: args.axis,
561
+ population: normalizeReferencePopulation(args.population, `${operation} population`),
562
+ field: normalizeReferenceField(args.field, `${operation} field`),
563
+ statistics,
564
+ dataId: `${id}-statistical-reference-data`
565
+ };
566
+ const preview = program._withMarkConfig(id, { statisticalReference: config });
567
+ const { source } = requireStatisticalSource(preview, config, id);
568
+ const dataset = resolvePopulationDataset(
569
+ program,
570
+ source,
571
+ config.population,
572
+ id
573
+ );
574
+ statisticalRows(preview, id, dataset);
575
+ if (hasDataset(program, config.dataId) || hasDatasetOwner(program, config.dataId)) {
576
+ throw new Error(`Dataset "${config.dataId}" already exists.`);
577
+ }
578
+ return { id, config, dataset, styleOptions };
579
+ }
580
+
581
+ function createDynamicReference(program, args, band) {
582
+ const { id, config, dataset, styleOptions } = normalizeDynamicReference(
583
+ program,
584
+ args,
585
+ band
586
+ );
587
+ const style = Object.fromEntries(
588
+ styleOptions
589
+ .filter(key => args[key] !== undefined)
590
+ .map(key => [key, args[key]])
591
+ );
592
+ const transform = normalizeStatisticalReferenceTransform({ target: id });
593
+ const apply = initial => {
594
+ const originalContext = initial.context;
595
+ let next = initial
596
+ .createDerivedData({
597
+ id: config.dataId,
598
+ source: dataset.id,
599
+ transform: [transform]
600
+ })
601
+ ._withMarkConfig(id, { statisticalReference: config })
602
+ .materializeStatisticalReferenceData({ id: config.dataId });
603
+ next = band
604
+ ? next.createRectMark({
605
+ id,
606
+ data: config.dataId,
607
+ fill: "#94a3b8",
608
+ opacity: 0.15,
609
+ stroke: false,
610
+ ...style
611
+ })
612
+ : next.createRuleMark({
613
+ id,
614
+ data: config.dataId,
615
+ stroke: "#64748b",
616
+ strokeWidth: 1,
617
+ strokeDash: "dashed",
618
+ ...style
619
+ });
620
+ next = next._withMarkConfig(id, {
621
+ ...next.markConfigs[id],
622
+ itemFilterable: false,
623
+ statisticalReference: config
624
+ });
625
+ return next
626
+ .rematerializeStatisticalReference({ id })
627
+ ._withContext({ ...originalContext, currentMark: id });
628
+ };
629
+ // Validate every owned data, semantic, scale, and renderer step atomically.
630
+ apply(program);
631
+ return apply(program);
632
+ }
633
+
634
+ function createReference(program, args, band) {
635
+ return isDynamicReference(args)
636
+ ? createDynamicReference(program, args, band)
637
+ : createLiteralReference(program, args, band);
638
+ }
639
+
79
640
  export const createReferenceLine = action(
80
- { op: "createReferenceLine", description: "Create a constant reference line in data or plot coordinates." },
81
- function (args = {}) { return createReference(this, args, false); }
641
+ {
642
+ op: "createReferenceLine",
643
+ description: "Create a constant or statistical reference line."
644
+ },
645
+ function (args = {}) {
646
+ return createReference(this, args, false);
647
+ }
82
648
  );
83
649
 
84
650
  export const createReferenceBand = action(
85
- { op: "createReferenceBand", description: "Create a constant reference interval in data or plot coordinates." },
86
- function (args = {}) { return createReference(this, args, true); }
651
+ {
652
+ op: "createReferenceBand",
653
+ description: "Create a constant or statistical reference interval."
654
+ },
655
+ function (args = {}) {
656
+ return createReference(this, args, true);
657
+ }
87
658
  );