ggaction 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +46 -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/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/edit.js +1 -1
  35. package/src/actions/data/regression.js +2 -1
  36. package/src/actions/data/shared.js +3 -2
  37. package/src/actions/data/stack.js +3 -1
  38. package/src/actions/encodings/appearance.js +37 -29
  39. package/src/actions/encodings/barWidth.js +36 -11
  40. package/src/actions/encodings/channels.js +0 -1
  41. package/src/actions/encodings/color/continuous.js +1 -1
  42. package/src/actions/encodings/color/index.js +1 -1
  43. package/src/actions/encodings/color/policy.js +10 -3
  44. package/src/actions/encodings/density/resolve.js +1 -6
  45. package/src/actions/encodings/position/policies/arc.js +12 -10
  46. package/src/actions/encodings/position/policies/area.js +3 -3
  47. package/src/actions/encodings/position/policies/bar.js +15 -6
  48. package/src/actions/encodings/position/policies/line.js +17 -6
  49. package/src/actions/encodings/position/resolve.js +3 -2
  50. package/src/actions/encodings/ranged.js +17 -0
  51. package/src/actions/encodings/remove.js +3 -2
  52. package/src/actions/encodings/seriesLayout.js +1 -0
  53. package/src/actions/encodings/text.js +6 -3
  54. package/src/actions/errorBands/create.js +2 -2
  55. package/src/actions/errorBands/edit.js +2 -1
  56. package/src/actions/facets/actions.js +158 -240
  57. package/src/actions/gradientPlots/resolve.js +2 -17
  58. package/src/actions/guides/applicability.js +1 -1
  59. package/src/actions/guides/axes/axis.js +16 -4
  60. package/src/actions/guides/axes/edit.js +4 -4
  61. package/src/actions/guides/axes/labels.js +120 -46
  62. package/src/actions/guides/axes/lines.js +6 -5
  63. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  64. package/src/actions/guides/axes/parallel/policy.js +8 -7
  65. package/src/actions/guides/axes/remove.js +3 -2
  66. package/src/actions/guides/axes/tickGroups.js +5 -2
  67. package/src/actions/guides/axes/ticks.js +6 -3
  68. package/src/actions/guides/axes/titles.js +71 -26
  69. package/src/actions/guides/basic.js +2 -0
  70. package/src/actions/guides/facadeLegend.js +10 -2
  71. package/src/actions/guides/grids/grid.js +7 -1
  72. package/src/actions/guides/grids/resolve.js +59 -12
  73. package/src/actions/guides/legends/blocks.js +3 -1
  74. package/src/actions/guides/legends/categorical/actions.js +70 -17
  75. package/src/actions/guides/legends/categorical/components.js +12 -8
  76. package/src/actions/guides/legends/categorical/index.js +6 -0
  77. package/src/actions/guides/legends/categorical/layout.js +37 -8
  78. package/src/actions/guides/legends/categorical/options.js +31 -20
  79. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  80. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  81. package/src/actions/guides/legends/categorical/validation.js +1 -6
  82. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  83. package/src/actions/guides/legends/continuous/common.js +1 -447
  84. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  85. package/src/actions/guides/legends/continuous/interval.js +4 -4
  86. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  87. package/src/actions/guides/legends/edit.js +1 -604
  88. package/src/actions/guides/legends/focused.js +2 -2
  89. package/src/actions/guides/legends/group.js +1 -0
  90. package/src/actions/guides/legends/lane.js +1 -516
  91. package/src/actions/guides/legends/remove.js +3 -0
  92. package/src/actions/guides/legends/sampling.js +1 -1
  93. package/src/actions/guides/legends/size.js +51 -43
  94. package/src/actions/guides/legends/strokeWidth.js +11 -29
  95. package/src/actions/guides/legends/target.js +3 -3
  96. package/src/actions/guides/legends/transition.js +21 -7
  97. package/src/actions/guides/polar/axes/labels.js +4 -2
  98. package/src/actions/guides/polar/axes/titles.js +7 -5
  99. package/src/actions/guides/polar/resolve.js +6 -14
  100. package/src/actions/index.js +16 -16
  101. package/src/actions/marks/arc/actions.js +3 -10
  102. package/src/actions/marks/area/actions.js +8 -5
  103. package/src/actions/marks/area/materialize.js +8 -9
  104. package/src/actions/marks/bar/create.js +10 -4
  105. package/src/actions/marks/bar/edit.js +8 -4
  106. package/src/actions/marks/bar/materialize.js +9 -6
  107. package/src/actions/marks/line/actions.js +11 -4
  108. package/src/actions/marks/line/materialize.js +9 -5
  109. package/src/actions/marks/point/edit.js +1 -1
  110. package/src/actions/marks/rect/actions.js +3 -3
  111. package/src/actions/marks/remove.js +2 -0
  112. package/src/actions/marks/rule/actions.js +7 -4
  113. package/src/actions/marks/text/actions.js +17 -5
  114. package/src/actions/marks/text/layout.js +3 -0
  115. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  116. package/src/actions/regression/create.js +1 -1
  117. package/src/actions/regression/edit.js +3 -3
  118. package/src/actions/regression/resolve.js +2 -6
  119. package/src/actions/scales/channels.js +9 -4
  120. package/src/actions/scales/consumers/families.js +16 -5
  121. package/src/actions/scales/create.js +8 -3
  122. package/src/actions/scales/definitions.js +1 -425
  123. package/src/actions/scales/edit.js +2 -2
  124. package/src/actions/scales/editPolicy.js +9 -3
  125. package/src/actions/scales/materialize.js +1 -1
  126. package/src/actions/scales/preview.js +21 -1
  127. package/src/actions/scales/quantitativeColor.js +3 -3
  128. package/src/actions/selection/actions.js +1 -13
  129. package/src/actions/theme/reconcile.js +81 -27
  130. package/src/actions/theme/state.js +12 -0
  131. package/src/actions/titles/actions.js +5 -18
  132. package/src/actions/titles/resolve.js +41 -19
  133. package/src/actions/violinPlots/edit.js +3 -20
  134. package/src/core/compositionState.js +4 -1
  135. package/src/core/font.js +9 -2
  136. package/src/core/optionRequirements.js +1 -0
  137. package/src/core/resourceReferences.js +3 -0
  138. package/src/core/textMetricProfile.js +3 -2
  139. package/src/core/textMetrics.js +41 -4
  140. package/src/core/validation.js +26 -0
  141. package/src/core/vocabulary.js +4 -0
  142. package/src/grammar/aggregate.js +4 -18
  143. package/src/grammar/arcs.js +1 -386
  144. package/src/grammar/areaPaint.js +10 -0
  145. package/src/grammar/areaSeries.js +18 -10
  146. package/src/grammar/bars/geometry.js +30 -3
  147. package/src/grammar/bars/policy.js +12 -1
  148. package/src/grammar/bin.js +2 -12
  149. package/src/grammar/bin2d.js +1 -6
  150. package/src/grammar/boxPlot.js +56 -9
  151. package/src/grammar/categoryOrder.js +1 -6
  152. package/src/grammar/complete.js +15 -19
  153. package/src/grammar/computed.js +92 -25
  154. package/src/grammar/curveCommands.js +29 -16
  155. package/src/grammar/datasetSchema.js +3 -7
  156. package/src/grammar/displayLabels.js +28 -0
  157. package/src/grammar/ecdf.js +2 -9
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/guides.js +5 -0
  160. package/src/grammar/facets/index.js +2 -5
  161. package/src/grammar/fold.js +2 -12
  162. package/src/grammar/gradientProfile.js +1 -6
  163. package/src/grammar/impute.js +21 -48
  164. package/src/grammar/interval.js +2 -9
  165. package/src/grammar/legendGradient.js +18 -0
  166. package/src/grammar/legendGroups.js +1 -0
  167. package/src/grammar/legendTitleStyle.js +22 -0
  168. package/src/grammar/lineSeries.js +32 -17
  169. package/src/grammar/markLabels.js +5 -2
  170. package/src/grammar/normalize.js +4 -54
  171. package/src/grammar/numeric.js +1 -290
  172. package/src/grammar/parallelCoordinates.js +1 -6
  173. package/src/grammar/parseTime.js +41 -0
  174. package/src/grammar/pathCommands.js +11 -4
  175. package/src/grammar/polar.js +1 -184
  176. package/src/grammar/positionCompatibility.js +3 -3
  177. package/src/grammar/regression/derive.js +7 -1
  178. package/src/grammar/regression/models.js +83 -55
  179. package/src/grammar/regression/parameters.js +14 -3
  180. package/src/grammar/roundedRect.js +42 -54
  181. package/src/grammar/scales/appearance.js +1 -206
  182. package/src/grammar/scales/color.js +1 -382
  183. package/src/grammar/scales/colorConsumers.js +5 -2
  184. package/src/grammar/scales/definition.js +1 -197
  185. package/src/grammar/scales/discretized.js +1 -195
  186. package/src/grammar/scales/size.js +1 -456
  187. package/src/grammar/scales/temporal.js +2 -13
  188. package/src/grammar/scales/transformed.js +1 -383
  189. package/src/grammar/scales/types.js +1 -1
  190. package/src/grammar/scales/validation.js +1 -66
  191. package/src/grammar/schemas/concreteGraphic.js +11 -0
  192. package/src/grammar/schemas/graphic.js +3 -0
  193. package/src/grammar/schemas/semanticPath.js +2 -1
  194. package/src/grammar/stack.js +2 -10
  195. package/src/grammar/statisticalReference.js +1 -6
  196. package/src/grammar/summary.js +3 -26
  197. package/src/grammar/text.js +19 -0
  198. package/src/grammar/timeUnit.js +16 -7
  199. package/src/grammar/transformKeys.js +47 -0
  200. package/src/grammar/transforms.js +4 -1
  201. package/src/grammar/valueFormat.js +17 -5
  202. package/src/grammar/weightedStatistics.js +1 -6
  203. package/src/grammar/window.js +4 -58
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -110
  206. package/src/layout/facets.js +1 -244
  207. package/src/layout/labels.js +1 -0
  208. package/src/layout/legend.js +13 -0
  209. package/src/layout/legendGroup.js +1 -0
  210. package/src/layout/legendItems.js +59 -15
  211. package/src/layout/legendLane.js +1 -441
  212. package/src/layout/legendText.js +1 -0
  213. package/src/layout/title.js +35 -13
  214. package/src/materialization/bars/aggregate.js +11 -5
  215. package/src/materialization/bars/histogram.js +4 -1
  216. package/src/materialization/bars/ranged.js +19 -6
  217. package/src/materialization/bars/resolve.js +22 -1
  218. package/src/materialization/coordinateBounds.js +8 -0
  219. package/src/materialization/dependencies.js +4 -44
  220. package/src/materialization/facetGuides/layout.js +1 -0
  221. package/src/materialization/facetGuides/placement.js +19 -8
  222. package/src/materialization/facetGuides/preparation.js +15 -7
  223. package/src/materialization/facetHeaders.js +1 -347
  224. package/src/materialization/facets.js +1 -185
  225. package/src/materialization/graphicHierarchy.js +1 -1
  226. package/src/materialization/guides/layout.js +1 -57
  227. package/src/materialization/guides/resources.js +1 -123
  228. package/src/materialization/layout.js +53 -0
  229. package/src/materialization/legendGroups.js +1 -0
  230. package/src/materialization/legendText.js +1 -0
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/marks/capabilities.js +5 -6
  233. package/src/materialization/rect.js +1 -1
  234. package/src/materialization/scaleGuideDependencies.js +1 -1
  235. package/src/materialization/scales/map.js +3 -0
  236. package/src/materialization/scales/policies/arc.js +8 -7
  237. package/src/materialization/scales/policies/bar.js +19 -7
  238. package/src/materialization/scales/policies/series.js +2 -1
  239. package/src/materialization/scales/resolve.js +5 -3
  240. package/src/materialization/selection/items/arc.js +1 -1
  241. package/src/materialization/selection/items/bar.js +7 -6
  242. package/src/materialization/selection/items/path.js +7 -6
  243. package/src/materialization/selection/items/rect.js +2 -2
  244. package/src/materialization/text.js +13 -4
  245. package/src/materialization/textLines.js +15 -0
  246. package/src/persistence/codec.js +37 -6
  247. package/src/persistence/validation.js +47 -0
  248. package/src/persistence.js +18 -5
  249. package/src/renderers/canvas/circle.js +6 -3
  250. package/src/renderers/canvas/fill.js +7 -1
  251. package/src/renderers/canvas/index.js +2 -1
  252. package/src/renderers/canvas/line.js +1 -0
  253. package/src/renderers/canvas/path.js +1 -1
  254. package/src/renderers/canvas/rect.js +11 -8
  255. package/src/renderers/canvas/stroke.js +2 -1
  256. package/src/renderers/canvas/text.js +5 -3
  257. package/src/renderers/svg.js +4 -1
  258. package/src/selectors/layers.js +19 -0
  259. package/src/theme/defaults.js +8 -2
  260. package/src/version.js +1 -1
  261. package/types/basic.d.ts +1 -0
  262. package/types/index.d.ts +7 -0
  263. package/types/program.d.ts +347 -85
@@ -1,3 +1,4 @@
1
+ import { requireStringValue as requireField } from "../core/validation.js";
1
2
  import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
3
  import { normalizeTemporalValue, validateTemporalUnit } from "./scales/fields.js";
3
4
 
@@ -75,12 +76,6 @@ function validateTimeZone(timeZone) {
75
76
  return timeZone;
76
77
  }
77
78
 
78
- function requireField(value, label) {
79
- if (typeof value !== "string" || value.length === 0) {
80
- throw new TypeError(`${label} must be a non-empty string.`);
81
- }
82
- return value;
83
- }
84
79
 
85
80
  function rejectUnknownKeys(value) {
86
81
  const unknown = Object.keys(value).find(key => !TRANSFORM_KEYS.includes(key));
@@ -127,7 +122,8 @@ export function validateTimeUnitTransform(transform) {
127
122
  }
128
123
 
129
124
  export function normalizeTimeUnitTransform(args = {}) {
130
- const { field, unit, as, temporalUnit, timeZone } = args;
125
+ const { field, unit, temporalUnit, timeZone } = args;
126
+ const as = args.as === undefined ? `${field}_${unit}` : args.as;
131
127
  const transform = { type: "timeUnit", field, unit, as,
132
128
  ...(temporalUnit === undefined ? {} : { temporalUnit }),
133
129
  ...(timeZone === undefined ? {} : { timeZone }) };
@@ -337,3 +333,16 @@ export function deriveTimeUnitRows(rows, transform) {
337
333
  });
338
334
  return cloneAndFreeze(values);
339
335
  }
336
+
337
+ export function offsetUtcTimeUnit(timestamp, unit) {
338
+ const date = new Date(timestamp);
339
+ if (unit === "year") return utcTimestamp(date.getUTCFullYear() + 1);
340
+ if (unit === "quarter" || unit === "month") {
341
+ return utcTimestamp(date.getUTCFullYear(), date.getUTCMonth() + (unit === "quarter" ? 3 : 1));
342
+ }
343
+ if (unit === "day") return timestamp + 24 * 60 * 60 * 1000;
344
+ if (unit === "hour") return timestamp + 60 * 60 * 1000;
345
+ if (unit === "minute") return timestamp + 60 * 1000;
346
+ return timestamp + 1000;
347
+ }
348
+
@@ -0,0 +1,47 @@
1
+ import { isPlainObject } from "../core/immutable.js";
2
+ import { requireStringValue, rejectUnknownProperties } from "../core/validation.js";
3
+
4
+ export function scalarKey(value, label) {
5
+ if (value === null) return "null";
6
+ if (typeof value === "string") return `string:${value.length}:${value}`;
7
+ if (typeof value === "boolean") return `boolean:${value}`;
8
+ if (typeof value === "number" && Number.isFinite(value)) {
9
+ return `number:${Object.is(value, -0) ? 0 : value}`;
10
+ }
11
+ throw new TypeError(
12
+ `${label} must contain null, strings, booleans, or finite numbers.`
13
+ );
14
+ }
15
+
16
+ export function normalizeSortBy(value) {
17
+ if (value === undefined) return [];
18
+ if (!Array.isArray(value)) return value;
19
+ return value.map(sort => isPlainObject(sort)
20
+ ? { ...sort, order: sort.order ?? "ascending" }
21
+ : sort
22
+ );
23
+ }
24
+
25
+ export function validateSortBy(sortBy, label) {
26
+ const kind = label.toLowerCase();
27
+ if (!Array.isArray(sortBy)) throw new TypeError(`${label} sortBy must be an array.`);
28
+ const fields = [];
29
+ sortBy.forEach((sort, index) => {
30
+ if (!isPlainObject(sort)) {
31
+ throw new TypeError(`${label} sortBy[${index}] must be a plain object.`);
32
+ }
33
+ rejectUnknownProperties(sort, ["field", "order"], `${kind} sortBy[${index}]`);
34
+ fields.push(requireStringValue(sort.field, `${label} sortBy[${index}].field`));
35
+ if (!["ascending", "descending"].includes(sort.order)) {
36
+ throw new Error(`Unsupported ${kind} sort order "${sort.order}".`);
37
+ }
38
+ });
39
+ if (new Set(fields).size !== fields.length) {
40
+ throw new Error(`${label} sortBy fields must be unique.`);
41
+ }
42
+ }
43
+
44
+ export function isGroupValue(value) {
45
+ return typeof value === "string" || typeof value === "boolean" ||
46
+ (typeof value === "number" && Number.isFinite(value));
47
+ }
@@ -199,7 +199,10 @@ function normalizeRegressionEdit(transform, patch) {
199
199
  const method = patch.method ?? base.method;
200
200
  if (method !== base.method) {
201
201
  if (method !== "polynomial") delete base.degree;
202
- if (method !== "loess") delete base.span;
202
+ if (method !== "loess") {
203
+ delete base.span;
204
+ delete base.robustIterations;
205
+ }
203
206
  if (method === "loess" || base.method === "loess") {
204
207
  for (const key of [
205
208
  "confidenceMethod", "level", "confidence", "interval"
@@ -1,5 +1,7 @@
1
1
  const NUMERIC_FORMAT = /^\.(0?\d|1[0-2])(f|%|e)$/;
2
- const TIME_DIRECTIVES = new Set(["Y", "m", "d", "b", "%"]);
2
+ const GROUPED_NUMERIC_FORMAT = /^,(?:\.(0?\d|1[0-2])f)?$/;
3
+ const WEEKDAY_NAMES = Object.freeze(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]);
4
+ const TIME_DIRECTIVES = new Set(["Y", "m", "d", "b", "a", "H", "M", "S", "L", "%"]);
3
5
  const MONTH_NAMES = Object.freeze([
4
6
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
5
7
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
@@ -33,12 +35,12 @@ export function validateValueFormat(format, label = "Label format", {
33
35
  if (allowDecimalsObject) return format;
34
36
  }
35
37
  if (typeof format === "string" && (
36
- NUMERIC_FORMAT.test(format) || isUtcValueFormat(format)
38
+ NUMERIC_FORMAT.test(format) || GROUPED_NUMERIC_FORMAT.test(format) || isUtcValueFormat(format)
37
39
  )) {
38
40
  return format;
39
41
  }
40
42
  const numericCandidate = typeof format === "string"
41
- ? format.match(/^\.(\d+)(f|%|e)$/)
43
+ ? format.match(/^,?\.(\d+)(f|%|e)$/)
42
44
  : undefined;
43
45
  if (numericCandidate && Number(numericCandidate[1]) > 12) {
44
46
  throw new RangeError(`${label} numeric format supports at most 12 decimals.`);
@@ -61,9 +63,14 @@ function formatUtcValue(value, format, label) {
61
63
  Y: pad(date.getUTCFullYear(), 4),
62
64
  m: pad(date.getUTCMonth() + 1),
63
65
  d: pad(date.getUTCDate()),
64
- b: MONTH_NAMES[date.getUTCMonth()]
66
+ b: MONTH_NAMES[date.getUTCMonth()],
67
+ a: WEEKDAY_NAMES[date.getUTCDay()],
68
+ H: pad(date.getUTCHours()),
69
+ M: pad(date.getUTCMinutes()),
70
+ S: pad(date.getUTCSeconds()),
71
+ L: pad(date.getUTCMilliseconds(), 3)
65
72
  };
66
- return format.replace(/%([Ymdb%])/g, (_, directive) =>
73
+ return format.replace(/%([YmdbaHMSL%])/g, (_, directive) =>
67
74
  directive === "%" ? "%" : values[directive]
68
75
  );
69
76
  }
@@ -98,6 +105,11 @@ export function formatValue(value, {
98
105
  throw new TypeError(`${label} "${typeof resolved === "string" ? resolved : "decimals"}" requires a finite number.`);
99
106
  }
100
107
  if (typeof resolved === "object") return value.toFixed(resolved.decimals);
108
+ const grouped = resolved.match(GROUPED_NUMERIC_FORMAT);
109
+ if (grouped) {
110
+ const precision = grouped[1] === undefined ? 0 : Number(grouped[1]);
111
+ return new Intl.NumberFormat("en-US", { useGrouping: true, minimumFractionDigits: precision, maximumFractionDigits: precision }).format(value);
112
+ }
101
113
  const match = resolved.match(NUMERIC_FORMAT);
102
114
  const precision = Number(match[1]);
103
115
  if (match[2] === "f") return value.toFixed(precision);
@@ -1,3 +1,4 @@
1
+ import { requireStringValue as requireField } from "../core/validation.js";
1
2
  import { isPlainObject } from "../core/immutable.js";
2
3
  import {
3
4
  interpolateNumber,
@@ -15,12 +16,6 @@ export const WEIGHTED_AGGREGATE_OPERATIONS = Object.freeze([
15
16
  "stderr", "median", "q1", "q3", "quantile"
16
17
  ]);
17
18
 
18
- function requireField(value, label) {
19
- if (typeof value !== "string" || value.length === 0) {
20
- throw new TypeError(`${label} must be a non-empty string.`);
21
- }
22
- return value;
23
- }
24
19
 
25
20
  export function normalizeStatisticalWeight(value, label = "Statistical weight") {
26
21
  if (!isPlainObject(value)) {
@@ -1,3 +1,6 @@
1
+ import { scalarKey, normalizeSortBy, validateSortBy } from "./transformKeys.js";
2
+ import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
3
+ import { requireStringValue as requireField } from "../core/validation.js";
1
4
  import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
5
  import {
3
6
  maximumMagnitude,
@@ -11,8 +14,6 @@ import { normalizeTemporalValue, validateTemporalUnit } from "./scales/fields.js
11
14
  const TRANSFORM_KEYS = [
12
15
  "type", "partitionBy", "sortBy", "operations", "temporalUnit"
13
16
  ];
14
- const SORT_KEYS = ["field", "order"];
15
- const ORDER_VALUES = ["ascending", "descending"];
16
17
  const OPERATION_VALUES = [
17
18
  "rowNumber", "rank", "denseRank", "cumulativeSum", "lag", "lead",
18
19
  "movingMean", "movingSum"
@@ -30,20 +31,6 @@ const DURATION_UNITS = Object.freeze({
30
31
  day: 86_400_000
31
32
  });
32
33
 
33
- function requireField(value, label) {
34
- if (typeof value !== "string" || value.length === 0) {
35
- throw new TypeError(`${label} must be a non-empty string.`);
36
- }
37
- return value;
38
- }
39
-
40
- function rejectUnknownKeys(value, supported, label) {
41
- const unknown = Object.keys(value).find(key => !supported.includes(key));
42
- if (unknown !== undefined) {
43
- throw new Error(`Unknown ${label} property "${unknown}".`);
44
- }
45
- }
46
-
47
34
  function validateFieldList(value, label) {
48
35
  if (
49
36
  !Array.isArray(value) ||
@@ -56,26 +43,6 @@ function validateFieldList(value, label) {
56
43
  }
57
44
  }
58
45
 
59
- function validateSortBy(sortBy) {
60
- if (!Array.isArray(sortBy)) {
61
- throw new TypeError("Window sortBy must be an array.");
62
- }
63
- const fields = [];
64
- sortBy.forEach((sort, index) => {
65
- if (!isPlainObject(sort)) {
66
- throw new TypeError(`Window sortBy[${index}] must be a plain object.`);
67
- }
68
- rejectUnknownKeys(sort, SORT_KEYS, `window sortBy[${index}]`);
69
- fields.push(requireField(sort.field, `Window sortBy[${index}].field`));
70
- if (!ORDER_VALUES.includes(sort.order)) {
71
- throw new Error(`Unsupported window sort order "${sort.order}".`);
72
- }
73
- });
74
- if (new Set(fields).size !== fields.length) {
75
- throw new Error("Window sortBy fields must be unique.");
76
- }
77
- }
78
-
79
46
  function operationKeys(operation) {
80
47
  if (POSITION_OPERATIONS.has(operation.op)) return ["op", "as"];
81
48
  if (operation.op === "cumulativeSum") return ["op", "field", "as"];
@@ -177,7 +144,7 @@ export function validateWindowTransform(transform) {
177
144
  throw new Error(`Unsupported window transform "${transform.type}".`);
178
145
  }
179
146
  validateFieldList(transform.partitionBy, "Window partitionBy");
180
- validateSortBy(transform.sortBy);
147
+ validateSortBy(transform.sortBy, "Window");
181
148
  validateOperations(transform.operations, transform.sortBy);
182
149
  const durationOperations = transform.operations.filter(operation =>
183
150
  MOVING_OPERATIONS.has(operation.op) && Object.hasOwn(operation.frame, "duration")
@@ -199,15 +166,6 @@ function normalizePartitionBy(value) {
199
166
  return Array.isArray(value) ? [...value] : [value];
200
167
  }
201
168
 
202
- function normalizeSortBy(value) {
203
- if (value === undefined) return [];
204
- if (!Array.isArray(value)) return value;
205
- return value.map(sort => isPlainObject(sort)
206
- ? { ...sort, order: sort.order ?? "ascending" }
207
- : sort
208
- );
209
- }
210
-
211
169
  function normalizeOperations(value) {
212
170
  if (!Array.isArray(value)) return value;
213
171
  return value.map(operation => {
@@ -259,18 +217,6 @@ export function normalizeWindowTransform({
259
217
  return cloneAndFreeze(transform);
260
218
  }
261
219
 
262
- function scalarKey(value, label) {
263
- if (value === null) return "null";
264
- if (typeof value === "string") return `string:${value.length}:${value}`;
265
- if (typeof value === "boolean") return `boolean:${value}`;
266
- if (typeof value === "number" && Number.isFinite(value)) {
267
- return `number:${Object.is(value, -0) ? 0 : value}`;
268
- }
269
- throw new TypeError(
270
- `${label} must contain null, strings, booleans, or finite numbers.`
271
- );
272
- }
273
-
274
220
  function compareScalar(left, right, label) {
275
221
  const leftMissing = left === null || left === undefined;
276
222
  const rightMissing = right === null || right === undefined;