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,4 +1,5 @@
1
- import { studentTCriticalValue } from "../statistics/studentT.js";
1
+ import { confidenceCriticalValue } from
2
+ "../statistics/confidenceInterval.js";
2
3
  import {
3
4
  maximumMagnitude,
4
5
  requireFiniteResult,
@@ -56,7 +57,9 @@ function linearPrediction(model, xValue) {
56
57
  );
57
58
  }
58
59
 
59
- function fitLinearGroup(rows, { x, y, group, confidence }) {
60
+ function fitLinearGroup(rows, {
61
+ x, y, group, confidenceMethod, level
62
+ }) {
60
63
  const count = rows.length;
61
64
  const groupLabel = group === undefined ? "all" : String(group);
62
65
  const label = `Regression group "${groupLabel}"`;
@@ -152,7 +155,11 @@ function fitLinearGroup(rows, { x, y, group, confidence }) {
152
155
  return Object.assign(model, {
153
156
  residualSumSquares,
154
157
  residualStandardError: Math.sqrt(residualSumSquares / degreesOfFreedom),
155
- critical: studentTCriticalValue(confidence, degreesOfFreedom)
158
+ critical: confidenceCriticalValue({
159
+ method: confidenceMethod,
160
+ level,
161
+ degreesOfFreedom
162
+ })
156
163
  });
157
164
  }
158
165
 
@@ -230,7 +237,9 @@ function polynomialResponse(design, values, size, scale = 1) {
230
237
  ));
231
238
  }
232
239
 
233
- function fitPolynomialGroup(rows, { x, y, group, confidence, degree }) {
240
+ function fitPolynomialGroup(rows, {
241
+ x, y, group, confidenceMethod, level, degree
242
+ }) {
234
243
  const count = rows.length;
235
244
  const parameterCount = degree + 1;
236
245
  const groupLabel = group === undefined ? "all" : String(group);
@@ -310,7 +319,11 @@ function fitPolynomialGroup(rows, { x, y, group, confidence, degree }) {
310
319
  inverse,
311
320
  residualSumSquares,
312
321
  residualStandardError: Math.sqrt(residualVariance),
313
- critical: studentTCriticalValue(confidence, degreesOfFreedom)
322
+ critical: confidenceCriticalValue({
323
+ method: confidenceMethod,
324
+ level,
325
+ degreesOfFreedom
326
+ })
314
327
  };
315
328
  if (!normalizedXByDifference) model.normalizedXByDifference = false;
316
329
  if (stableResponse) Object.assign(model, { responseScale, scaledCoefficients });
@@ -416,7 +429,8 @@ export function fitRegressionGroup(rows, { x, y, group, parameters }) {
416
429
  x,
417
430
  y,
418
431
  group,
419
- confidence: parameters.confidence
432
+ confidenceMethod: parameters.confidenceMethod,
433
+ level: parameters.level
420
434
  });
421
435
  }
422
436
  if (parameters.method === "polynomial") {
@@ -424,7 +438,8 @@ export function fitRegressionGroup(rows, { x, y, group, parameters }) {
424
438
  x,
425
439
  y,
426
440
  group,
427
- confidence: parameters.confidence,
441
+ confidenceMethod: parameters.confidenceMethod,
442
+ level: parameters.level,
428
443
  degree: parameters.degree
429
444
  });
430
445
  }
@@ -1,5 +1,7 @@
1
1
  import { cloneAndFreeze } from "../../core/immutable.js";
2
2
  import { validateGeneratedItemLimit } from "../../core/validation.js";
3
+ import { normalizeConfidenceInterval } from
4
+ "../statistics/confidenceInterval.js";
3
5
 
4
6
  const MAX_POLYNOMIAL_DEGREE = 32;
5
7
 
@@ -14,6 +16,8 @@ export function normalizeRegressionParameters({
14
16
  method = "linear",
15
17
  degree,
16
18
  span,
19
+ confidenceMethod,
20
+ level,
17
21
  confidence,
18
22
  interval
19
23
  } = {}) {
@@ -24,7 +28,10 @@ export function normalizeRegressionParameters({
24
28
  if (degree !== undefined) {
25
29
  throw new Error("Regression degree requires the polynomial method.");
26
30
  }
27
- if (confidence !== undefined || interval !== undefined) {
31
+ if (
32
+ confidenceMethod !== undefined || level !== undefined ||
33
+ confidence !== undefined || interval !== undefined
34
+ ) {
28
35
  throw new Error("LOESS regression does not support confidence intervals.");
29
36
  }
30
37
  const resolvedSpan = span ?? 0.75;
@@ -38,14 +45,18 @@ export function normalizeRegressionParameters({
38
45
  if (span !== undefined) {
39
46
  throw new Error("Regression span requires the loess method.");
40
47
  }
41
- const resolvedConfidence = confidence ?? 0.95;
42
- if (
43
- !Number.isFinite(resolvedConfidence) ||
44
- resolvedConfidence <= 0 ||
45
- resolvedConfidence >= 1
46
- ) {
47
- throw new RangeError("Regression confidence must be between 0 and 1.");
48
+ if (level !== undefined && confidence !== undefined && level !== confidence) {
49
+ throw new Error(
50
+ "Regression level and confidence alias must match when both are provided."
51
+ );
48
52
  }
53
+ const confidenceInterval = normalizeConfidenceInterval({
54
+ method: confidenceMethod,
55
+ level: level ?? confidence
56
+ }, {
57
+ defaultMethod: "student-t",
58
+ label: "Regression confidence interval"
59
+ });
49
60
  const resolvedInterval = interval ?? "mean";
50
61
  if (!["mean", "prediction"].includes(resolvedInterval)) {
51
62
  throw new Error(`Unsupported regression interval "${resolvedInterval}".`);
@@ -65,7 +76,8 @@ export function normalizeRegressionParameters({
65
76
  return cloneAndFreeze({
66
77
  method,
67
78
  degree: resolvedDegree,
68
- confidence: resolvedConfidence,
79
+ confidenceMethod: confidenceInterval.method,
80
+ level: confidenceInterval.level,
69
81
  interval: resolvedInterval
70
82
  });
71
83
  }
@@ -74,14 +86,16 @@ export function normalizeRegressionParameters({
74
86
  }
75
87
  return cloneAndFreeze({
76
88
  method,
77
- confidence: resolvedConfidence,
89
+ confidenceMethod: confidenceInterval.method,
90
+ level: confidenceInterval.level,
78
91
  interval: resolvedInterval
79
92
  });
80
93
  }
81
94
 
82
95
  export function validateRegressionTransform(transform) {
83
96
  const supported = [
84
- "type", "method", "x", "y", "groupBy", "confidence", "interval",
97
+ "type", "method", "x", "y", "groupBy", "confidenceMethod", "level",
98
+ "confidence", "interval",
85
99
  "degree", "span"
86
100
  ];
87
101
  const unknown = Object.keys(transform).find(key => !supported.includes(key));
@@ -96,16 +110,29 @@ export function validateRegressionTransform(transform) {
96
110
  if (transform.groupBy !== undefined) {
97
111
  requireRegressionField(transform.groupBy, "Regression groupBy field");
98
112
  }
113
+ if (
114
+ transform.confidence !== undefined &&
115
+ (transform.confidenceMethod !== undefined || transform.level !== undefined)
116
+ ) {
117
+ throw new Error(
118
+ "Regression confidence provenance must use either the legacy confidence field or method and level."
119
+ );
120
+ }
99
121
  const normalized = normalizeRegressionParameters({
100
122
  method: transform.method,
101
123
  degree: transform.degree,
102
124
  span: transform.span,
125
+ confidenceMethod: transform.confidenceMethod,
126
+ level: transform.level,
103
127
  confidence: transform.confidence,
104
128
  interval: transform.interval
105
129
  });
106
130
  if (normalized.method === "loess") return transform;
107
- if (!Number.isFinite(transform.confidence)) {
108
- throw new RangeError("Regression confidence must be between 0 and 1.");
131
+ if (
132
+ transform.confidence === undefined &&
133
+ (transform.confidenceMethod === undefined || transform.level === undefined)
134
+ ) {
135
+ throw new Error("Regression confidence provenance requires method and level.");
109
136
  }
110
137
  if (transform.interval === undefined) {
111
138
  throw new Error(`Unsupported regression interval "${transform.interval}".`);
@@ -0,0 +1,29 @@
1
+ const UNITS = new Set(["radians", "degrees"]);
2
+
3
+ function toRadians(value, unit) {
4
+ return unit === "degrees" ? value * Math.PI / 180 : value;
5
+ }
6
+
7
+ export function resolveRotation(value, label = "Rotation", {
8
+ legacyUnit = "radians"
9
+ } = {}) {
10
+ if (!UNITS.has(legacyUnit)) {
11
+ throw new TypeError(`${label} legacy unit must be radians or degrees.`);
12
+ }
13
+ if (Number.isFinite(value)) return toRadians(value, legacyUnit);
14
+ if (
15
+ value !== null &&
16
+ typeof value === "object" &&
17
+ Object.getPrototypeOf(value) === Object.prototype &&
18
+ Object.keys(value).length === 2 &&
19
+ Object.hasOwn(value, "value") &&
20
+ Object.hasOwn(value, "unit") &&
21
+ Number.isFinite(value.value) &&
22
+ UNITS.has(value.unit)
23
+ ) {
24
+ return toRadians(value.value, value.unit);
25
+ }
26
+ throw new TypeError(
27
+ `${label} must be a finite legacy number or { value, unit: "radians" | "degrees" }.`
28
+ );
29
+ }
@@ -1,11 +1,9 @@
1
+ import { normalizePositionDatum } from "./positionDatum.js";
1
2
  import { cloneAndFreeze } from "../core/immutable.js";
2
3
  import {
3
- isNominalValue,
4
- normalizeTemporalValue,
5
4
  readNominalField,
6
5
  readQuantitativeField,
7
- readTemporalField,
8
- validateSemanticFieldType
6
+ readTemporalField
9
7
  } from "./scales/index.js";
10
8
 
11
9
  const ENDPOINT_CHANNELS = Object.freeze(["x", "y", "x2", "y2"]);
@@ -39,26 +37,13 @@ function readField(rows, encoding) {
39
37
  return readNominalField(rows, encoding.field);
40
38
  }
41
39
  if (encoding.fieldType === "temporal") {
42
- return readTemporalField(rows, encoding.field);
40
+ return readTemporalField(rows, encoding.field, encoding.temporalUnit);
43
41
  }
44
42
  return readQuantitativeField(rows, encoding.field);
45
43
  }
46
44
 
47
- export function normalizeRuleDatum(value, fieldType, channel) {
48
- validateSemanticFieldType(fieldType);
49
- if (fieldType === "quantitative") {
50
- if (!Number.isFinite(value)) {
51
- throw new TypeError(`Rule ${channel} datum must be a finite number.`);
52
- }
53
- return value;
54
- }
55
- if (fieldType === "temporal") {
56
- return normalizeTemporalValue(value, `${channel} datum`, 0);
57
- }
58
- if (!isNominalValue(value)) {
59
- throw new TypeError(`Rule ${channel} datum must be a nominal value.`);
60
- }
61
- return value;
45
+ export function normalizeRuleDatum(value, fieldType, channel, temporalUnit) {
46
+ return normalizePositionDatum(value, fieldType, channel, temporalUnit, "Rule");
62
47
  }
63
48
 
64
49
  export function deriveRuleValues(rows, layer) {
@@ -83,7 +68,8 @@ export function deriveRuleValues(rows, layer) {
83
68
  const datum = normalizeRuleDatum(
84
69
  encoding.datum,
85
70
  encoding.fieldType,
86
- channel
71
+ channel,
72
+ encoding.temporalUnit
87
73
  );
88
74
  values[channel] = Array.from({ length }, () => datum);
89
75
  }
@@ -325,6 +325,23 @@ export function interpolateColorStops(stops, position, interpolation = "rgb") {
325
325
  return interpolateColors(colors, position, method, true);
326
326
  }
327
327
 
328
+ export function validateSequentialMidpoint(value, type, domain = "auto") {
329
+ if (value === undefined || value === "auto") return undefined;
330
+ if (type !== "sequential") {
331
+ throw new Error(`Scale type "${type}" does not support midpoint.`);
332
+ }
333
+ if (!Number.isFinite(value)) {
334
+ throw new TypeError("Scale midpoint must be finite or auto.");
335
+ }
336
+ if (domain !== "auto" && (
337
+ !Array.isArray(domain) || domain.length !== 2 || !domain.every(Number.isFinite) ||
338
+ !(value > Math.min(...domain) && value < Math.max(...domain))
339
+ )) {
340
+ throw new RangeError("Scale midpoint must lie strictly inside its resolved domain.");
341
+ }
342
+ return value;
343
+ }
344
+
328
345
  export function mapSequentialColors(
329
346
  values,
330
347
  domain,
@@ -342,6 +359,7 @@ export function mapSequentialColors(
342
359
  if (typeof shouldClamp !== "boolean") {
343
360
  throw new TypeError("Sequential color clamp must be a boolean.");
344
361
  }
362
+ const midpoint = validateSequentialMidpoint(options.midpoint, "sequential", domain);
345
363
  const [start, end] = domain;
346
364
  const constant = start === end;
347
365
  const method = validateContinuousColorInterpolation(interpolation);
@@ -354,7 +372,11 @@ export function mapSequentialColors(
354
372
  if (hasUnknown) return options.unknown;
355
373
  throw new TypeError("Sequential color values must be finite numbers.");
356
374
  }
357
- const raw = constant ? 0.5 : inverseLerp(value, start, end);
375
+ const raw = midpoint === undefined
376
+ ? constant ? 0.5 : inverseLerp(value, start, end)
377
+ : (end > start ? value <= midpoint : value >= midpoint)
378
+ ? inverseLerp(value, start, midpoint) / 2
379
+ : 0.5 + inverseLerp(value, midpoint, end) / 2;
358
380
  return interpolateColors(colors, raw, method, shouldClamp);
359
381
  }));
360
382
  }
@@ -0,0 +1,38 @@
1
+ import { BAR_GRAINS, resolveBarChannels, resolveBarGrain } from "../bars/policy.js";
2
+ import { validateAggregate, validateAggregateFieldType } from "../aggregate.js";
3
+ import { isDiscretizedColorScaleType } from "./types.js";
4
+
5
+ export function validateContinuousColorConsumer(layer, encoding, scale, { inferAggregate = false } = {}) {
6
+ const kind = layer.mark?.type;
7
+ if (!["point", "bar", "rect"].includes(kind)) {
8
+ throw new Error("Continuous color requires a Point, aggregate Bar, or Rect consumer.");
9
+ }
10
+ if (!["quantitative", "temporal"].includes(encoding.fieldType) ||
11
+ (isDiscretizedColorScaleType(scale.type) && encoding.fieldType !== "quantitative")) {
12
+ throw new Error(`Scale type "${scale.type}" has an incompatible color field type.`);
13
+ }
14
+ if (Object.hasOwn(scale, "unknown") && kind !== "point") {
15
+ throw new Error("Continuous color scale unknown currently requires a row-owned point mark.");
16
+ }
17
+ if (kind !== "bar") {
18
+ if (encoding.aggregate !== undefined) throw new Error(`${kind} continuous color does not support aggregate.`);
19
+ return undefined;
20
+ }
21
+ if (["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)) {
22
+ throw new Error("Continuous bar color cannot replace an existing nominal color layout.");
23
+ }
24
+ if (encoding.fieldType !== "quantitative") {
25
+ throw new Error("Aggregate bar color currently requires a quantitative field.");
26
+ }
27
+ if (resolveBarGrain(layer) !== BAR_GRAINS.aggregate) {
28
+ throw new Error("Continuous bar color requires a complete categorical aggregate bar.");
29
+ }
30
+ const measure = layer.encoding?.[resolveBarChannels(layer).measure];
31
+ const aggregate = encoding.aggregate ?? (inferAggregate && measure?.field === encoding.field ? measure.aggregate : undefined);
32
+ if (aggregate === undefined) {
33
+ throw new Error("Continuous bar color requires aggregate when its field differs from the measure field.");
34
+ }
35
+ validateAggregate(aggregate);
36
+ validateAggregateFieldType(aggregate, encoding.fieldType);
37
+ return aggregate;
38
+ }
@@ -1,4 +1,5 @@
1
- import { validateContinuousColorInterpolation } from "./color.js";
1
+ import { validateMeasuredRadiusScale } from "./radial.js";
2
+ import { validateContinuousColorInterpolation, validateSequentialMidpoint } from "./color.js";
2
3
  import { isTransformedScaleType } from "./mapping.js";
3
4
  import {
4
5
  normalizeTransformParameters,
@@ -137,6 +138,11 @@ export function normalizeScaleDefinition({
137
138
  throw new Error(`Scale type "${type}" does not support interpolate.`);
138
139
  }
139
140
 
141
+ const midpoint = validateSequentialMidpoint(
142
+ retainedValue(previous, patch, defaults, "midpoint", typeChanged), type, definition.domain
143
+ );
144
+ if (midpoint !== undefined) definition.midpoint = midpoint;
145
+
140
146
  if (type === "band") {
141
147
  validateBandParameters(definition, previous, patch, defaults, typeChanged);
142
148
  } else if (type === "point") {
@@ -149,5 +155,13 @@ export function normalizeScaleDefinition({
149
155
  }
150
156
  }
151
157
 
158
+ const radialMapping = Object.hasOwn(patch, "radialMapping") ? patch.radialMapping : previous.radialMapping;
159
+ if (radialMapping !== undefined) {
160
+ definition.radialMapping = radialMapping;
161
+ validateMeasuredRadiusScale({ ...definition,
162
+ ...(Object.hasOwn(patch, "unknown") || Object.hasOwn(previous, "unknown")
163
+ ? { unknown: patch.unknown ?? previous.unknown } : {})
164
+ }, { allowAuto: true });
165
+ }
152
166
  return definition;
153
167
  }
@@ -1,6 +1,7 @@
1
1
  import { cloneAndFreeze } from "../../core/immutable.js";
2
2
  import { validateGeneratedItemLimit } from "../../core/validation.js";
3
3
  import { interpolateNumber } from "../numeric.js";
4
+ import { formatValue, validateValueFormat } from "../valueFormat.js";
4
5
  import { resolveColorRange, validateColorRange } from "./appearance.js";
5
6
  import { SCALE_ROLES, validateScaleTypeForRole } from "./types.js";
6
7
 
@@ -162,17 +163,22 @@ export function mapDiscretizedColors(values, scale) {
162
163
  }));
163
164
  }
164
165
 
165
- export function formatDiscretizedIntervals(thresholds) {
166
+ export function formatDiscretizedIntervals(thresholds, format = "auto") {
166
167
  nondecreasing(thresholds, "Discretized color thresholds");
168
+ const resolvedFormat = validateValueFormat(format, "Legend label format");
167
169
  let resolution = Infinity;
168
170
  for (let index = 1; index < thresholds.length; index += 1) {
169
171
  const gap = thresholds[index] - thresholds[index - 1];
170
172
  if (Number.isFinite(gap) && gap > 0) resolution = Math.min(resolution, gap);
171
173
  }
172
- let labels = thresholds.map(value =>
173
- Number.isInteger(value) ? String(value) : value.toFixed(1)
174
- );
175
- if (thresholds.some((value, index) => {
174
+ let labels = resolvedFormat === "auto"
175
+ ? thresholds.map(value => Number.isInteger(value) ? String(value) : value.toFixed(1))
176
+ : thresholds.map(value => formatValue(value, {
177
+ format: resolvedFormat,
178
+ valueType: "quantitative",
179
+ label: "Legend label format"
180
+ }));
181
+ if (resolvedFormat === "auto" && thresholds.some((value, index) => {
176
182
  const rounded = Number(labels[index]);
177
183
  return value !== 0 && rounded === 0 ||
178
184
  index > 0 && value !== thresholds[index - 1] &&
@@ -81,9 +81,37 @@ function validTimestamp(value) {
81
81
  return Number.isFinite(value) && Number.isFinite(new Date(value).getTime());
82
82
  }
83
83
 
84
- export function normalizeTemporalValue(value, field = "value", index = 0) {
84
+ export function validateTemporalUnit(unit) {
85
+ if (unit !== undefined && !["auto", "year", "timestamp"].includes(unit)) {
86
+ throw new Error(`Unsupported temporalUnit "${unit}"; use auto, year, or timestamp.`);
87
+ }
88
+ return unit;
89
+ }
90
+
91
+ export function resolveTemporalUnit(options, fieldType, previous) {
92
+ if (fieldType !== "temporal") {
93
+ if (Object.hasOwn(options, "temporalUnit")) {
94
+ throw new Error("temporalUnit requires a temporal fieldType.");
95
+ }
96
+ return undefined;
97
+ }
98
+ validateTemporalUnit(options.temporalUnit);
99
+ const sameBinding = ["field", "datum"].some(key =>
100
+ Object.hasOwn(options, key) && Object.hasOwn(previous ?? {}, key) &&
101
+ options[key] === previous[key]);
102
+ return options.temporalUnit ?? (sameBinding && previous?.fieldType === "temporal"
103
+ ? previous.temporalUnit : undefined);
104
+ }
105
+
106
+ export function normalizeTemporalValue(value, field = "value", index = 0, temporalUnit = "auto") {
107
+ validateTemporalUnit(temporalUnit);
85
108
  let timestamp;
86
- if (typeof value === "number") {
109
+ if (temporalUnit === "year") {
110
+ const year = typeof value === "string" && /^\d{4}$/.test(value) ? Number(value) : value;
111
+ if (Number.isInteger(year) && year >= 0 && year <= 9999) timestamp = utcDate(year);
112
+ } else if (temporalUnit === "timestamp") {
113
+ if (typeof value === "number") timestamp = value;
114
+ } else if (typeof value === "number") {
87
115
  timestamp = Number.isInteger(value) && value >= 1000 && value <= 9999
88
116
  ? utcDate(value)
89
117
  : value;
@@ -99,21 +127,22 @@ export function normalizeTemporalValue(value, field = "value", index = 0) {
99
127
  if (!validTimestamp(timestamp)) {
100
128
  throw new TypeError(
101
129
  `Field "${field}" must contain a temporal string or finite timestamp ` +
102
- `(including a valid date or four-digit year) at row ${index}.`
130
+ `(including a valid date or four-digit year) at row ${index}; temporalUnit is ${temporalUnit}.`
103
131
  );
104
132
  }
105
133
  return timestamp;
106
134
  }
107
135
 
108
- export function readTemporalField(rows, field) {
136
+ export function readTemporalField(rows, field, temporalUnit) {
109
137
  validateField(field);
138
+ validateTemporalUnit(temporalUnit);
110
139
  return cloneAndFreeze(rows.map((row, index) => {
111
140
  if (!Object.hasOwn(row, field)) {
112
141
  throw new TypeError(
113
142
  `Field "${field}" must contain a temporal string or finite timestamp at row ${index}.`
114
143
  );
115
144
  }
116
- return normalizeTemporalValue(row[field], field, index);
145
+ return normalizeTemporalValue(row[field], field, index, temporalUnit);
117
146
  }));
118
147
  }
119
148
 
@@ -129,10 +158,11 @@ export function readNominalField(rows, field) {
129
158
  }));
130
159
  }
131
160
 
132
- export function readScaleField(rows, field, fieldType, { allowUnknown = false } = {}) {
161
+ export function readScaleField(rows, field, fieldType, { allowUnknown = false, temporalUnit } = {}) {
133
162
  validateSemanticFieldType(fieldType);
163
+ validateTemporalUnit(temporalUnit);
134
164
  if (!allowUnknown) {
135
- if (fieldType === "temporal") return readTemporalField(rows, field);
165
+ if (fieldType === "temporal") return readTemporalField(rows, field, temporalUnit);
136
166
  if (["nominal", "ordinal"].includes(fieldType)) {
137
167
  return readNominalField(rows, field);
138
168
  }
@@ -149,7 +179,7 @@ export function readScaleField(rows, field, fieldType, { allowUnknown = false }
149
179
  return isNominalValue(value) ? value : undefined;
150
180
  }
151
181
  try {
152
- return normalizeTemporalValue(value, field, index);
182
+ return normalizeTemporalValue(value, field, index, temporalUnit);
153
183
  } catch {
154
184
  return undefined;
155
185
  }
@@ -1,3 +1,4 @@
1
+ import { mapMeasuredRadiusValues } from "./radial.js";
1
2
  import { mapLinearValues } from "./continuous.js";
2
3
  import { mapTransformedValues } from "./transformed.js";
3
4
  import { SCALE_ROLES, validateScaleTypeForRole } from "./types.js";
@@ -9,6 +10,7 @@ export function isTransformedScaleType(type) {
9
10
  }
10
11
 
11
12
  export function mapContinuousScaleValues(values, scale) {
13
+ if (scale?.radialMapping !== undefined) return mapMeasuredRadiusValues(values, scale);
12
14
  if (isTransformedScaleType(scale?.type)) {
13
15
  validateScaleTypeForRole(scale.type, SCALE_ROLES.quantitativePosition);
14
16
  return mapTransformedValues(values, scale.domain, scale.range, {
@@ -0,0 +1,55 @@
1
+ import { cloneAndFreeze } from "../../core/immutable.js";
2
+ import { interpolateNumber } from "../numeric.js";
3
+
4
+ export function validateRadialMapping(mapping) {
5
+ if (!["area", "radius-length"].includes(mapping)) {
6
+ throw new Error('Radial mapping must be "area" or "radius-length".');
7
+ }
8
+ return mapping;
9
+ }
10
+
11
+ export function validateMeasuredRadiusScale(scale, { allowAuto = false } = {}) {
12
+ validateRadialMapping(scale.radialMapping);
13
+ if (scale.type !== "linear" || (scale.nice !== undefined && scale.nice !== false) ||
14
+ (scale.zero !== undefined && scale.zero !== true) ||
15
+ (scale.reverse !== undefined && scale.reverse !== false) || Object.hasOwn(scale, "unknown")) {
16
+ throw new Error("Measured radius requires a linear, zero-based, non-reversed scale without nice or unknown.");
17
+ }
18
+ if (!(allowAuto && scale.domain === "auto") && (!Array.isArray(scale.domain) || scale.domain.length !== 2 ||
19
+ scale.domain[0] !== 0 || !Number.isFinite(scale.domain[1]) || scale.domain[1] <= 0)) {
20
+ throw new RangeError("Measured radius domain must be [0, positive finite maximum].");
21
+ }
22
+ if (!(allowAuto && scale.range === "auto") && (!Array.isArray(scale.range) || scale.range.length !== 2 ||
23
+ !scale.range.every(Number.isFinite) || scale.range[0] < 0 || scale.range[0] >= scale.range[1])) {
24
+ throw new RangeError("Measured radius range must satisfy 0 <= inner < outer with finite radii.");
25
+ }
26
+ if (scale.clamp !== undefined && typeof scale.clamp !== "boolean") {
27
+ throw new TypeError("Measured radius clamp must be a boolean.");
28
+ }
29
+ return scale;
30
+ }
31
+
32
+ export function mapMeasuredRadiusValues(values, scale) {
33
+ validateMeasuredRadiusScale(scale);
34
+ const [inner, outer] = scale.range;
35
+ const ratio = inner / outer;
36
+ // Normalize before squaring so finite large radii do not overflow.
37
+ const annulus = (1 - ratio) * (1 + ratio);
38
+ return cloneAndFreeze(values.map(value => {
39
+ if (!Number.isFinite(value)) throw new TypeError("Measured radius values must be finite.");
40
+ const proportion = value / scale.domain[1];
41
+ const t = scale.clamp ? Math.max(0, Math.min(1, proportion)) : proportion;
42
+ if (!Number.isFinite(t) || t < 0 || t > 1) {
43
+ throw new RangeError("Measured radius value must lie inside its zero-based domain.");
44
+ }
45
+ if (t === 0 && value <= 0) return inner;
46
+ if (t === 1) return outer;
47
+ const radius = scale.radialMapping === "area"
48
+ ? outer * Math.sqrt(Math.min(1, ratio * ratio + t * annulus))
49
+ : interpolateNumber(inner, outer, t);
50
+ if (radius <= inner) {
51
+ throw new RangeError("Positive measured radius cannot be represented distinctly from the inner radius at this numeric precision.");
52
+ }
53
+ return Math.min(outer, radius);
54
+ }));
55
+ }
@@ -27,6 +27,7 @@ export const COMPLETE_SCALE_TYPES = Object.freeze([
27
27
  ]);
28
28
 
29
29
  const SCALE_PROPERTY_TYPES = Object.freeze({
30
+ midpoint: Object.freeze(["sequential"]),
30
31
  nice: Object.freeze(["linear", "log", "pow", "sqrt", "symlog", "time"]),
31
32
  zero: Object.freeze(["linear", "pow", "sqrt", "symlog"]),
32
33
  clamp: Object.freeze([
@@ -20,12 +20,21 @@ const ENCODING_PATHS = Object.freeze([
20
20
  "encoding.x.bin.boundaries",
21
21
  "encoding.color.layout",
22
22
  "encoding.color.aggregate",
23
+ "encoding.group.fields",
24
+ "encoding.group.inferredFrom",
25
+ ...["x", "y", "x2", "y2", "theta", "color"].map(channel =>
26
+ `encoding.${channel}.temporalUnit`
27
+ ),
23
28
  "encoding.pathOrder.field",
24
29
  "encoding.pathOrder.fieldType",
25
30
  "encoding.pathOrder.order",
26
31
  "encoding.theta.aggregate",
27
32
  "encoding.theta.weight",
33
+ "encoding.radius.aggregate",
34
+ "encoding.theta.categoryOrder",
28
35
  "encoding.text.format",
36
+ "encoding.text.content",
37
+ "encoding.text.normalizeBy",
29
38
  "encoding.parallel.dimensions",
30
39
  "encoding.parallel.key",
31
40
  "encoding.parallel.missing",
@@ -45,7 +54,7 @@ const ENTITY_PATHS = Object.freeze({
45
54
  collection: "layers",
46
55
  removableContainers: new Set(
47
56
  [...ENCODING_CHANNELS.map(channel => `encoding.${channel}`),
48
- "encoding.parallel"]
57
+ "encoding.parallel", "layout"]
49
58
  ),
50
59
  properties: new Set([
51
60
  "data",
@@ -53,6 +62,8 @@ const ENTITY_PATHS = Object.freeze({
53
62
  "coordinate",
54
63
  "transform",
55
64
  "mark.type",
65
+ "mark.missing",
66
+ "layout.mode",
56
67
  ...ENCODING_PATHS
57
68
  ])
58
69
  },
@@ -60,7 +71,7 @@ const ENTITY_PATHS = Object.freeze({
60
71
  collection: "scales",
61
72
  properties: new Set([
62
73
  "type", "domain", "range", "nice", "zero", "clamp", "reverse",
63
- "unknown", "base", "exponent", "constant", "interpolate",
74
+ "unknown", "base", "exponent", "constant", "interpolate", "radialMapping", "midpoint",
64
75
  "paddingInner", "paddingOuter", "padding", "align"
65
76
  ])
66
77
  },
@@ -86,8 +97,10 @@ const GUIDE_PATHS = new Set([
86
97
  "axis.parallel.target",
87
98
  "axis.parallel.coordinate",
88
99
  "axis.parallel.scales",
100
+ "axis.parallel.titles",
89
101
  "legend.color.scale",
90
102
  "legend.color.title",
103
+ "legend.color.order",
91
104
  "legend.size.scale",
92
105
  "legend.size.title",
93
106
  "legend.strokeWidth.scale",
@@ -97,6 +110,7 @@ const GUIDE_PATHS = new Set([
97
110
  "legend.series.channels",
98
111
  "legend.series.scales",
99
112
  "legend.series.title",
113
+ "legend.series.order",
100
114
  "grid.horizontal.scale",
101
115
  "grid.horizontal.coordinate",
102
116
  "grid.vertical.scale",
@@ -137,7 +151,7 @@ export function parseSemanticPath(property, { allowContainer = false } = {}) {
137
151
 
138
152
  if (allowContainer) {
139
153
  const entityMatch = property.match(
140
- new RegExp(`^(dataset|layer)\\[(${USER_ID_SOURCE})\\]$`)
154
+ new RegExp(`^(dataset|layer|scale)\\[(${USER_ID_SOURCE})\\]$`)
141
155
  );
142
156
  if (entityMatch) {
143
157
  const [, kind, id] = entityMatch;