ggaction 0.0.12 → 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 (378) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +20 -11
  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 +10 -3
  11. package/src/actions/basic.js +4 -2
  12. package/src/actions/boxPlots/components.js +14 -4
  13. package/src/actions/boxPlots/edit.js +31 -5
  14. package/src/actions/boxPlots/materialize.js +13 -4
  15. package/src/actions/boxPlots/options.js +48 -16
  16. package/src/actions/canvas/actions.js +3 -1
  17. package/src/actions/canvas/fitting.js +211 -0
  18. package/src/actions/canvas/index.js +2 -0
  19. package/src/actions/categoryOrder/index.js +18 -6
  20. package/src/actions/charts/area.js +51 -0
  21. package/src/actions/charts/bar.js +13 -6
  22. package/src/actions/charts/beeswarm.js +70 -0
  23. package/src/actions/charts/density.js +48 -0
  24. package/src/actions/charts/ecdf.js +186 -0
  25. package/src/actions/charts/endpoints.js +473 -0
  26. package/src/actions/charts/heatmap.js +4 -3
  27. package/src/actions/charts/histogram.js +5 -4
  28. package/src/actions/charts/horizon.js +73 -0
  29. package/src/actions/charts/index.js +37 -0
  30. package/src/actions/charts/interval-regression.js +172 -0
  31. package/src/actions/charts/line.js +8 -6
  32. package/src/actions/charts/parallel.js +8 -2
  33. package/src/actions/charts/pie.js +52 -0
  34. package/src/actions/charts/polar.js +134 -0
  35. package/src/actions/charts/radar.js +305 -0
  36. package/src/actions/charts/radial.js +50 -0
  37. package/src/actions/charts/raincloud.js +422 -0
  38. package/src/actions/charts/rug-strip.js +283 -0
  39. package/src/actions/charts/scatter.js +11 -3
  40. package/src/actions/charts/shared.js +69 -10
  41. package/src/actions/composition/actions.js +129 -3
  42. package/src/actions/coordinates/actions.js +1 -1
  43. package/src/actions/coordinates/edit.js +76 -0
  44. package/src/actions/data/bin.js +29 -0
  45. package/src/actions/data/bin2d.js +57 -185
  46. package/src/actions/data/complete.js +26 -0
  47. package/src/actions/data/computed.js +24 -0
  48. package/src/actions/data/create.js +2 -2
  49. package/src/actions/data/density.js +12 -20
  50. package/src/actions/data/derived.js +111 -15
  51. package/src/actions/data/ecdf.js +38 -0
  52. package/src/actions/data/edit.js +539 -0
  53. package/src/actions/data/filter.js +226 -30
  54. package/src/actions/data/fold.js +24 -0
  55. package/src/actions/data/impute.js +26 -0
  56. package/src/actions/data/index.js +38 -0
  57. package/src/actions/data/interval.js +2 -0
  58. package/src/actions/data/intervalEdit.js +86 -4
  59. package/src/actions/data/normalize.js +27 -0
  60. package/src/actions/data/regression.js +19 -21
  61. package/src/actions/data/revisionBindings.js +145 -0
  62. package/src/actions/data/shared.js +20 -4
  63. package/src/actions/data/stack.js +26 -0
  64. package/src/actions/data/summary.js +26 -0
  65. package/src/actions/data/timeUnit.js +4 -1
  66. package/src/actions/data/window.js +1 -1
  67. package/src/actions/encodings/appearance.js +32 -13
  68. package/src/actions/encodings/barWidth.js +8 -0
  69. package/src/actions/encodings/basic.js +2 -0
  70. package/src/actions/encodings/channels.js +415 -0
  71. package/src/actions/encodings/color/continuous.js +19 -59
  72. package/src/actions/encodings/color/index.js +30 -23
  73. package/src/actions/encodings/color/layout.js +3 -97
  74. package/src/actions/encodings/color/policy.js +5 -8
  75. package/src/actions/encodings/density/resolve.js +37 -5
  76. package/src/actions/encodings/density.js +124 -34
  77. package/src/actions/encodings/histogram.js +3 -2
  78. package/src/actions/encodings/horizon/resolve.js +8 -4
  79. package/src/actions/encodings/horizon.js +18 -8
  80. package/src/actions/encodings/index.js +6 -0
  81. package/src/actions/encodings/offset.js +30 -29
  82. package/src/actions/encodings/parallel.js +2 -8
  83. package/src/actions/encodings/position/apply.js +63 -11
  84. package/src/actions/encodings/position/index.js +52 -6
  85. package/src/actions/encodings/position/policies/arc.js +14 -0
  86. package/src/actions/encodings/position/policies/area.js +4 -42
  87. package/src/actions/encodings/position/policies/bar.js +57 -13
  88. package/src/actions/encodings/position/resolve.js +88 -26
  89. package/src/actions/encodings/ranged.js +123 -97
  90. package/src/actions/encodings/remove.js +44 -34
  91. package/src/actions/encodings/ruleAppearance.js +24 -32
  92. package/src/actions/encodings/seriesLayout.js +105 -0
  93. package/src/actions/encodings/shared.js +107 -37
  94. package/src/actions/encodings/stroke.js +226 -0
  95. package/src/actions/encodings/strokeDash.js +9 -6
  96. package/src/actions/encodings/temporal.js +7 -0
  97. package/src/actions/encodings/text.js +30 -15
  98. package/src/actions/errorBands/create.js +41 -8
  99. package/src/actions/errorBands/edit.js +360 -25
  100. package/src/actions/errorBars/create.js +56 -17
  101. package/src/actions/errorBars/edit.js +372 -21
  102. package/src/actions/facets/actions.js +619 -41
  103. package/src/actions/facets/derive.js +139 -25
  104. package/src/actions/gradientPlots/components.js +43 -13
  105. package/src/actions/gradientPlots/edit.js +14 -4
  106. package/src/actions/gradientPlots/materialize.js +8 -8
  107. package/src/actions/gradientPlots/options.js +10 -2
  108. package/src/actions/guides/applicability.js +28 -19
  109. package/src/actions/guides/axes/axes.js +4 -3
  110. package/src/actions/guides/axes/axis.js +18 -18
  111. package/src/actions/guides/axes/components.js +43 -0
  112. package/src/actions/guides/axes/edit.js +16 -51
  113. package/src/actions/guides/axes/labels.js +184 -28
  114. package/src/actions/guides/axes/lines.js +13 -7
  115. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  116. package/src/actions/guides/axes/parallel/policy.js +137 -0
  117. package/src/actions/guides/axes/parallel/resolve.js +50 -25
  118. package/src/actions/guides/axes/parallel.js +93 -217
  119. package/src/actions/guides/axes/policy.js +21 -67
  120. package/src/actions/guides/axes/tickGroups.js +15 -8
  121. package/src/actions/guides/axes/ticks.js +11 -6
  122. package/src/actions/guides/axes/titles.js +32 -11
  123. package/src/actions/guides/basic.js +2 -0
  124. package/src/actions/guides/facade.js +106 -0
  125. package/src/actions/guides/facadeAxes.js +200 -0
  126. package/src/actions/guides/facadeLegend.js +63 -0
  127. package/src/actions/guides/grids/grid.js +37 -33
  128. package/src/actions/guides/grids/resolve.js +17 -4
  129. package/src/actions/guides/guides.js +54 -31
  130. package/src/actions/guides/legends/blocks.js +426 -0
  131. package/src/actions/guides/legends/categorical/actions.js +309 -279
  132. package/src/actions/guides/legends/categorical/components.js +21 -17
  133. package/src/actions/guides/legends/categorical/index.js +37 -33
  134. package/src/actions/guides/legends/categorical/layout.js +123 -354
  135. package/src/actions/guides/legends/categorical/options.js +32 -16
  136. package/src/actions/guides/legends/categorical/recipes.js +114 -9
  137. package/src/actions/guides/legends/categorical/resolve.js +30 -15
  138. package/src/actions/guides/legends/categorical/symbols.js +87 -33
  139. package/src/actions/guides/legends/continuous/common.js +99 -26
  140. package/src/actions/guides/legends/continuous/gradient.js +120 -89
  141. package/src/actions/guides/legends/continuous/index.js +8 -2
  142. package/src/actions/guides/legends/continuous/interval.js +79 -95
  143. package/src/actions/guides/legends/continuous/opacity.js +113 -89
  144. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  145. package/src/actions/guides/legends/creation.js +40 -0
  146. package/src/actions/guides/legends/edit.js +321 -90
  147. package/src/actions/guides/legends/focused.js +2 -2
  148. package/src/actions/guides/legends/index.js +2 -0
  149. package/src/actions/guides/legends/lane.js +149 -102
  150. package/src/actions/guides/legends/lifecycle.js +143 -0
  151. package/src/actions/guides/legends/remove.js +43 -78
  152. package/src/actions/guides/legends/sampling.js +140 -0
  153. package/src/actions/guides/legends/size.js +213 -116
  154. package/src/actions/guides/legends/strokeWidth.js +121 -73
  155. package/src/actions/guides/legends/target.js +123 -0
  156. package/src/actions/guides/legends/transition.js +192 -0
  157. package/src/actions/guides/polar/axes/facade.js +81 -66
  158. package/src/actions/guides/polar/axes/labels.js +28 -8
  159. package/src/actions/guides/polar/axes/lines.js +12 -5
  160. package/src/actions/guides/polar/axes/shared.js +22 -0
  161. package/src/actions/guides/polar/axes/ticks.js +6 -5
  162. package/src/actions/guides/polar/axes/titles.js +6 -5
  163. package/src/actions/guides/polar/grids.js +8 -8
  164. package/src/actions/guides/polar/resolve.js +12 -8
  165. package/src/actions/guides/reuse.js +44 -0
  166. package/src/actions/guides/tickValues.js +19 -21
  167. package/src/actions/index.js +15 -2
  168. package/src/actions/intervals/resolve.js +35 -15
  169. package/src/actions/marks/arc/actions.js +78 -20
  170. package/src/actions/marks/area/actions.js +51 -12
  171. package/src/actions/marks/area/materialize.js +22 -3
  172. package/src/actions/marks/bar/create.js +11 -2
  173. package/src/actions/marks/bar/edit.js +20 -3
  174. package/src/actions/marks/bar/materialize.js +33 -9
  175. package/src/actions/marks/index.js +14 -0
  176. package/src/actions/marks/line/actions.js +71 -113
  177. package/src/actions/marks/line/materialize.js +57 -56
  178. package/src/actions/marks/point/create.js +8 -2
  179. package/src/actions/marks/point/edit.js +29 -21
  180. package/src/actions/marks/point/index.js +1 -0
  181. package/src/actions/marks/point/jitter.js +3 -0
  182. package/src/actions/marks/point/materialize.js +154 -16
  183. package/src/actions/marks/point/packing.js +68 -0
  184. package/src/actions/marks/rect/actions.js +33 -12
  185. package/src/actions/marks/references.js +658 -0
  186. package/src/actions/marks/remove.js +87 -4
  187. package/src/actions/marks/rule/actions.js +77 -9
  188. package/src/actions/marks/rule/appearance.js +26 -0
  189. package/src/actions/marks/shared.js +13 -13
  190. package/src/actions/marks/text/actions.js +296 -13
  191. package/src/actions/marks/text/index.js +4 -0
  192. package/src/actions/marks/text/layout.js +156 -8
  193. package/src/actions/marks/text/removal.js +173 -0
  194. package/src/actions/marks/text/selection.js +73 -0
  195. package/src/actions/marks/tick/actions.js +22 -5
  196. package/src/actions/primitives/createGraphics.js +3 -1
  197. package/src/actions/primitives/editGraphics.js +26 -0
  198. package/src/actions/primitives/semantic.js +3 -2
  199. package/src/actions/primitives/semanticAction.js +120 -10
  200. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  201. package/src/actions/primitives/semanticValidation/index.js +9 -1
  202. package/src/actions/primitives/semanticValidation/layer.js +23 -1
  203. package/src/actions/primitives/semanticValidation/scale.js +38 -8
  204. package/src/actions/regression/components.js +26 -11
  205. package/src/actions/regression/create.js +11 -3
  206. package/src/actions/regression/edit.js +40 -13
  207. package/src/actions/regression/resolve.js +1 -1
  208. package/src/actions/resources/index.js +7 -0
  209. package/src/actions/resources/remove.js +172 -0
  210. package/src/actions/scales/channels.js +212 -0
  211. package/src/actions/scales/consumers/common.js +21 -54
  212. package/src/actions/scales/consumers/families.js +28 -14
  213. package/src/actions/scales/consumers/index.js +42 -4
  214. package/src/actions/scales/consumers/seriesLayout.js +57 -12
  215. package/src/actions/scales/create.js +58 -26
  216. package/src/actions/scales/definitions.js +109 -39
  217. package/src/actions/scales/edit.js +101 -34
  218. package/src/actions/scales/editPolicy.js +95 -35
  219. package/src/actions/scales/index.js +6 -0
  220. package/src/actions/scales/materialize.js +14 -49
  221. package/src/actions/scales/offset.js +48 -0
  222. package/src/actions/scales/parallel.js +71 -0
  223. package/src/actions/scales/patch.js +1 -1
  224. package/src/actions/scales/preview.js +140 -0
  225. package/src/actions/scales/quantitativeColor.js +9 -5
  226. package/src/actions/selection/actions.js +154 -30
  227. package/src/actions/selection/index.js +3 -0
  228. package/src/actions/theme/actions.js +98 -0
  229. package/src/actions/theme/composition.js +238 -0
  230. package/src/actions/theme/index.js +16 -0
  231. package/src/actions/theme/reconcile.js +1130 -0
  232. package/src/actions/theme/state.js +160 -0
  233. package/src/actions/titles/actions.js +11 -10
  234. package/src/actions/titles/resolve.js +1 -40
  235. package/src/actions/violinPlots/create.js +90 -43
  236. package/src/actions/violinPlots/edit.js +192 -0
  237. package/src/actions/violinPlots/index.js +2 -0
  238. package/src/core/ChartProgram.js +7 -0
  239. package/src/core/action.js +32 -2
  240. package/src/core/compositionState.js +141 -8
  241. package/src/core/programState.js +20 -0
  242. package/src/core/resourceReferences.js +332 -0
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/grammar/aggregate.js +66 -23
  245. package/src/grammar/arcs.js +58 -1
  246. package/src/grammar/areaEndpoints.js +46 -0
  247. package/src/grammar/areaSeries.js +87 -41
  248. package/src/grammar/bars/aggregate.js +37 -13
  249. package/src/grammar/bars/geometry.js +44 -0
  250. package/src/grammar/bars/policy.js +9 -7
  251. package/src/grammar/bin.js +226 -0
  252. package/src/grammar/categoryOrder.js +34 -0
  253. package/src/grammar/complete.js +279 -0
  254. package/src/grammar/computed.js +351 -0
  255. package/src/grammar/coordinates.js +37 -0
  256. package/src/grammar/density.js +239 -37
  257. package/src/grammar/displayLabels.js +57 -0
  258. package/src/grammar/ecdf.js +198 -0
  259. package/src/grammar/facets/dependencies.js +133 -1
  260. package/src/grammar/facets/guides.js +3 -0
  261. package/src/grammar/facets/index.js +123 -52
  262. package/src/grammar/facets/scales.js +15 -18
  263. package/src/grammar/fold.js +123 -0
  264. package/src/grammar/horizon.js +6 -4
  265. package/src/grammar/impute.js +340 -0
  266. package/src/grammar/interval.js +37 -20
  267. package/src/grammar/lineSeries.js +20 -63
  268. package/src/grammar/markFilter.js +22 -4
  269. package/src/grammar/markLabelSelection.js +57 -0
  270. package/src/grammar/markLabels.js +110 -0
  271. package/src/grammar/normalize.js +366 -0
  272. package/src/grammar/parallelAxisTitles.js +16 -0
  273. package/src/grammar/pathSeries.js +109 -0
  274. package/src/grammar/pointPacking.js +183 -0
  275. package/src/grammar/pointShapes.js +13 -4
  276. package/src/grammar/polar.js +90 -8
  277. package/src/grammar/positionDatum.js +12 -0
  278. package/src/grammar/rects.js +9 -1
  279. package/src/grammar/regression/derive.js +4 -0
  280. package/src/grammar/regression/index.js +8 -2
  281. package/src/grammar/regression/models.js +22 -7
  282. package/src/grammar/regression/parameters.js +63 -13
  283. package/src/grammar/rotation.js +29 -0
  284. package/src/grammar/roundedRect.js +180 -0
  285. package/src/grammar/rules.js +8 -21
  286. package/src/grammar/scales/color.js +23 -1
  287. package/src/grammar/scales/colorConsumers.js +57 -0
  288. package/src/grammar/scales/continuous.js +2 -2
  289. package/src/grammar/scales/definition.js +42 -5
  290. package/src/grammar/scales/discretized.js +11 -5
  291. package/src/grammar/scales/fields.js +38 -8
  292. package/src/grammar/scales/index.js +1 -0
  293. package/src/grammar/scales/mapping.js +2 -0
  294. package/src/grammar/scales/ordinal.js +12 -3
  295. package/src/grammar/scales/policies.js +2 -2
  296. package/src/grammar/scales/radial.js +55 -0
  297. package/src/grammar/scales/size.js +456 -0
  298. package/src/grammar/scales/types.js +1 -0
  299. package/src/grammar/schemas/concreteGraphic.js +14 -2
  300. package/src/grammar/schemas/graphic.js +15 -0
  301. package/src/grammar/schemas/graphicBounds.js +179 -30
  302. package/src/grammar/schemas/semanticPath.js +23 -4
  303. package/src/grammar/stack.js +170 -0
  304. package/src/grammar/statisticalReference.js +103 -0
  305. package/src/grammar/statistics/confidenceInterval.js +101 -0
  306. package/src/grammar/strokeStyle.js +77 -0
  307. package/src/grammar/summary.js +245 -0
  308. package/src/grammar/text.js +22 -16
  309. package/src/grammar/timeUnit.js +234 -13
  310. package/src/grammar/transformTopology.js +10 -0
  311. package/src/grammar/transforms.js +418 -16
  312. package/src/grammar/valueFormat.js +112 -0
  313. package/src/grammar/weightedStatistics.js +374 -0
  314. package/src/grammar/window.js +232 -21
  315. package/src/layout/aspect.js +80 -0
  316. package/src/layout/facets.js +100 -12
  317. package/src/layout/guideCollisions.js +12 -0
  318. package/src/layout/labels.js +343 -0
  319. package/src/layout/legend.js +2 -10
  320. package/src/layout/legendItems.js +102 -0
  321. package/src/layout/legendLane.js +100 -11
  322. package/src/materialization/bars/aggregate.js +56 -147
  323. package/src/materialization/bars/histogram.js +104 -20
  324. package/src/materialization/bars/ranged.js +19 -3
  325. package/src/materialization/bars/resolve.js +7 -4
  326. package/src/materialization/categorySlotOffset.js +40 -0
  327. package/src/materialization/composition.js +3 -1
  328. package/src/materialization/compositionSnapshot.js +1 -1
  329. package/src/materialization/coordinateBounds.js +204 -0
  330. package/src/materialization/dependencies.js +93 -16
  331. package/src/materialization/encodings.js +1 -1
  332. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  333. package/src/materialization/facetGuides/placement.js +8 -2
  334. package/src/materialization/facetGuides/preparation.js +12 -7
  335. package/src/materialization/facetHeaders.js +346 -0
  336. package/src/materialization/facets.js +23 -66
  337. package/src/materialization/guides/layout.js +57 -0
  338. package/src/materialization/guides/resources.js +28 -2
  339. package/src/materialization/legends.js +11 -2
  340. package/src/materialization/marks/capabilities.js +20 -5
  341. package/src/materialization/marks/grouping.js +52 -0
  342. package/src/materialization/marks/index.js +68 -27
  343. package/src/materialization/marks/policies.js +4 -1
  344. package/src/materialization/rect.js +55 -42
  345. package/src/materialization/rectConfig.js +5 -1
  346. package/src/materialization/rowEncoding.js +13 -13
  347. package/src/materialization/scaleGuideDependencies.js +8 -5
  348. package/src/materialization/scales/map.js +6 -3
  349. package/src/materialization/scales/policies/arc.js +42 -0
  350. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  351. package/src/materialization/scales/policies/offset.js +50 -8
  352. package/src/materialization/scales/policies/series.js +5 -1
  353. package/src/materialization/scales/resolve.js +65 -17
  354. package/src/materialization/selection/compatibility.js +11 -0
  355. package/src/materialization/selection/items/arc.js +28 -4
  356. package/src/materialization/selection/items/bar.js +42 -71
  357. package/src/materialization/selection/items/common.js +27 -6
  358. package/src/materialization/selection/items/path.js +3 -13
  359. package/src/materialization/selection/items/rect.js +35 -8
  360. package/src/materialization/selection/path.js +1 -1
  361. package/src/materialization/selection/point.js +3 -1
  362. package/src/materialization/text.js +324 -14
  363. package/src/mcp/adapter.js +2 -2
  364. package/src/renderers/canvas/circle.js +2 -3
  365. package/src/renderers/canvas/line.js +2 -3
  366. package/src/renderers/canvas/native.js +4 -4
  367. package/src/renderers/canvas/path.js +2 -3
  368. package/src/renderers/canvas/rect.js +2 -3
  369. package/src/renderers/canvas/stroke.js +11 -0
  370. package/src/renderers/svg.js +19 -0
  371. package/src/selectors/datasets.js +44 -0
  372. package/src/selectors/layers.js +7 -2
  373. package/src/selectors/scales.js +6 -0
  374. package/src/theme/defaults.js +140 -0
  375. package/types/basic.d.ts +17 -2
  376. package/types/index.d.ts +211 -0
  377. package/types/program.d.ts +2182 -204
  378. package/src/materialization/bars/grouped.js +0 -144
@@ -1,5 +1,5 @@
1
1
  import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
- import { normalizeTemporalValue } from "./scales/fields.js";
2
+ import { normalizeTemporalValue, validateTemporalUnit } from "./scales/fields.js";
3
3
 
4
4
  export function utcTimestamp(
5
5
  year,
@@ -18,6 +18,14 @@ export function validTimestamp(value) {
18
18
  return Number.isFinite(value) && Number.isFinite(new Date(value).getTime());
19
19
  }
20
20
 
21
+ function utcBucket(...parts) {
22
+ const timestamp = utcTimestamp(...parts);
23
+ if (!validTimestamp(timestamp)) {
24
+ throw new RangeError("Time-unit bucket start is outside the supported Date range.");
25
+ }
26
+ return timestamp;
27
+ }
28
+
21
29
  export const TIME_UNITS = cloneAndFreeze([
22
30
  "year",
23
31
  "quarter",
@@ -25,10 +33,47 @@ export const TIME_UNITS = cloneAndFreeze([
25
33
  "day",
26
34
  "hour",
27
35
  "minute",
28
- "second"
36
+ "second",
37
+ "week",
38
+ "weekday"
29
39
  ]);
30
40
 
31
- const TRANSFORM_KEYS = Object.freeze(["type", "field", "unit", "as"]);
41
+ const TRANSFORM_KEYS = Object.freeze([
42
+ "type", "field", "unit", "as", "temporalUnit", "timeZone",
43
+ "weekStartsOn", "weekRule"
44
+ ]);
45
+
46
+ const CALENDAR_FORMATTERS = new Map();
47
+
48
+ function formatter(timeZone) {
49
+ if (!CALENDAR_FORMATTERS.has(timeZone)) {
50
+ let value;
51
+ try {
52
+ value = new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
53
+ timeZone,
54
+ year: "numeric",
55
+ month: "2-digit",
56
+ day: "2-digit",
57
+ hour: "2-digit",
58
+ minute: "2-digit",
59
+ second: "2-digit",
60
+ hourCycle: "h23"
61
+ });
62
+ } catch {
63
+ throw new RangeError(`Unsupported time zone "${timeZone}".`);
64
+ }
65
+ CALENDAR_FORMATTERS.set(timeZone, value);
66
+ }
67
+ return CALENDAR_FORMATTERS.get(timeZone);
68
+ }
69
+
70
+ function validateTimeZone(timeZone) {
71
+ if (typeof timeZone !== "string" || timeZone.length === 0) {
72
+ throw new TypeError("Time-unit timeZone must be a non-empty IANA time-zone name.");
73
+ }
74
+ formatter(timeZone);
75
+ return timeZone;
76
+ }
32
77
 
33
78
  function requireField(value, label) {
34
79
  if (typeof value !== "string" || value.length === 0) {
@@ -52,6 +97,7 @@ export function validateTimeUnitTransform(transform) {
52
97
  if (transform.type !== "timeUnit") {
53
98
  throw new Error(`Unsupported time-unit transform "${transform.type}".`);
54
99
  }
100
+ validateTemporalUnit(transform.temporalUnit);
55
101
  const field = requireField(transform.field, "Time-unit field");
56
102
  const output = requireField(transform.as, "Time-unit output field");
57
103
  if (field === output) {
@@ -60,11 +106,38 @@ export function validateTimeUnitTransform(transform) {
60
106
  if (!TIME_UNITS.includes(transform.unit)) {
61
107
  throw new Error(`Unsupported time unit "${transform.unit}".`);
62
108
  }
109
+ if (Object.hasOwn(transform, "timeZone")) validateTimeZone(transform.timeZone);
110
+ const hasWeekStartsOn = Object.hasOwn(transform, "weekStartsOn");
111
+ const hasWeekRule = Object.hasOwn(transform, "weekRule");
112
+ if (transform.unit === "week") {
113
+ if (!Number.isInteger(transform.weekStartsOn) ||
114
+ transform.weekStartsOn < 0 || transform.weekStartsOn > 6) {
115
+ throw new RangeError("Time-unit weekStartsOn must be an integer from 0 through 6.");
116
+ }
117
+ if (!["calendar", "iso"].includes(transform.weekRule)) {
118
+ throw new Error(`Unsupported time-unit weekRule "${transform.weekRule}".`);
119
+ }
120
+ if (transform.weekRule === "iso" && transform.weekStartsOn !== 1) {
121
+ throw new Error("Time-unit ISO weeks require weekStartsOn 1 (Monday).");
122
+ }
123
+ } else if (hasWeekStartsOn || hasWeekRule) {
124
+ throw new Error("Time-unit weekStartsOn and weekRule require unit week.");
125
+ }
63
126
  return transform;
64
127
  }
65
128
 
66
- export function normalizeTimeUnitTransform({ field, unit, as } = {}) {
67
- const transform = { type: "timeUnit", field, unit, as };
129
+ export function normalizeTimeUnitTransform(args = {}) {
130
+ const { field, unit, as, temporalUnit, timeZone } = args;
131
+ const transform = { type: "timeUnit", field, unit, as,
132
+ ...(temporalUnit === undefined ? {} : { temporalUnit }),
133
+ ...(timeZone === undefined ? {} : { timeZone }) };
134
+ if (unit === "week") {
135
+ transform.weekStartsOn = args.weekStartsOn ?? 1;
136
+ transform.weekRule = args.weekRule ?? "calendar";
137
+ } else {
138
+ if (Object.hasOwn(args, "weekStartsOn")) transform.weekStartsOn = args.weekStartsOn;
139
+ if (Object.hasOwn(args, "weekRule")) transform.weekRule = args.weekRule;
140
+ }
68
141
  validateTimeUnitTransform(transform);
69
142
  return cloneAndFreeze(transform);
70
143
  }
@@ -82,18 +155,166 @@ export function floorUtcTimeUnit(timestamp, unit) {
82
155
  }
83
156
  const year = date.getUTCFullYear();
84
157
  const month = date.getUTCMonth();
85
- if (unit === "year") return utcTimestamp(year);
86
- if (unit === "quarter") return utcTimestamp(year, Math.floor(month / 3) * 3);
87
- if (unit === "month") return utcTimestamp(year, month);
158
+ if (unit === "year") return utcBucket(year);
159
+ if (unit === "quarter") return utcBucket(year, Math.floor(month / 3) * 3);
160
+ if (unit === "month") return utcBucket(year, month);
88
161
  const day = date.getUTCDate();
89
- if (unit === "day") return utcTimestamp(year, month, day);
162
+ if (unit === "weekday") return date.getUTCDay();
163
+ if (unit === "week") {
164
+ throw new Error("floorUtcTimeUnit requires a calendar week policy.");
165
+ }
166
+ if (unit === "day") return utcBucket(year, month, day);
90
167
  const hour = date.getUTCHours();
91
- if (unit === "hour") return utcTimestamp(year, month, day, hour);
168
+ if (unit === "hour") return utcBucket(year, month, day, hour);
92
169
  const minute = date.getUTCMinutes();
93
- if (unit === "minute") return utcTimestamp(year, month, day, hour, minute);
170
+ if (unit === "minute") return utcBucket(year, month, day, hour, minute);
94
171
  return Math.floor(timestamp / 1000) * 1000;
95
172
  }
96
173
 
174
+ function weekdayFromDate(year, month, day) {
175
+ return new Date(utcTimestamp(year, month - 1, day)).getUTCDay();
176
+ }
177
+
178
+ export function calendarParts(timestamp, timeZone) {
179
+ if (!validTimestamp(timestamp)) {
180
+ throw new TypeError("Time-unit timestamp must represent a valid date.");
181
+ }
182
+ validateTimeZone(timeZone);
183
+ const parts = Object.fromEntries(formatter(timeZone).formatToParts(timestamp)
184
+ .filter(part => part.type !== "literal")
185
+ .map(part => [part.type, Number(part.value)]));
186
+ const millisecond = ((timestamp % 1000) + 1000) % 1000;
187
+ return {
188
+ year: parts.year,
189
+ month: parts.month,
190
+ day: parts.day,
191
+ hour: parts.hour,
192
+ minute: parts.minute,
193
+ second: parts.second,
194
+ millisecond,
195
+ weekday: weekdayFromDate(parts.year, parts.month, parts.day)
196
+ };
197
+ }
198
+
199
+ function civilTimestamp(parts) {
200
+ const timestamp = utcTimestamp(
201
+ parts.year,
202
+ parts.month - 1,
203
+ parts.day,
204
+ parts.hour ?? 0,
205
+ parts.minute ?? 0
206
+ ) + (parts.second ?? 0) * 1000 + (parts.millisecond ?? 0);
207
+ if (!validTimestamp(timestamp)) {
208
+ throw new RangeError("Time-unit calendar boundary is outside the supported Date range.");
209
+ }
210
+ return timestamp;
211
+ }
212
+
213
+ function sameCivil(left, right) {
214
+ return ["year", "month", "day", "hour", "minute", "second", "millisecond"]
215
+ .every(key => left[key] === right[key]);
216
+ }
217
+
218
+ function sameBucket(candidate, target, unit) {
219
+ const keys = unit === "year" ? ["year"]
220
+ : unit === "quarter" ? ["year", "month"]
221
+ : unit === "month" ? ["year", "month"]
222
+ : unit === "day" || unit === "week" ? ["year", "month", "day"]
223
+ : unit === "hour" ? ["year", "month", "day", "hour"]
224
+ : unit === "minute" ? ["year", "month", "day", "hour", "minute"]
225
+ : ["year", "month", "day", "hour", "minute", "second"];
226
+ if (unit === "quarter") {
227
+ return candidate.year === target.year &&
228
+ Math.floor((candidate.month - 1) / 3) === Math.floor((target.month - 1) / 3);
229
+ }
230
+ return keys.every(key => candidate[key] === target[key]);
231
+ }
232
+
233
+ export function resolveCalendarBoundary(localParts, timeZone, unit) {
234
+ const naive = civilTimestamp(localParts);
235
+ const offsets = new Set();
236
+ for (let delta = -72; delta <= 72; delta += 6) {
237
+ const sample = naive + delta * 3_600_000;
238
+ if (!validTimestamp(sample)) continue;
239
+ offsets.add(civilTimestamp(calendarParts(sample, timeZone)) - sample);
240
+ }
241
+ const exact = [];
242
+ const gaps = [];
243
+ for (const offset of offsets) {
244
+ const candidate = naive - offset;
245
+ if (!validTimestamp(candidate)) continue;
246
+ const roundTrip = calendarParts(candidate, timeZone);
247
+ if (sameCivil(roundTrip, localParts)) exact.push(candidate);
248
+ else if (sameBucket(roundTrip, localParts, unit) &&
249
+ civilTimestamp(roundTrip) >= naive) {
250
+ gaps.push({ candidate, local: civilTimestamp(roundTrip) });
251
+ }
252
+ }
253
+ if (exact.length > 0) return Math.min(...exact);
254
+ if (gaps.length > 0) {
255
+ gaps.sort((left, right) => left.local - right.local || left.candidate - right.candidate);
256
+ return gaps[0].candidate;
257
+ }
258
+ throw new RangeError(`Time-unit ${unit} boundary does not exist in time zone "${timeZone}".`);
259
+ }
260
+
261
+ function addCalendarDays(parts, delta) {
262
+ const date = new Date(utcTimestamp(parts.year, parts.month - 1, parts.day));
263
+ date.setUTCDate(date.getUTCDate() + delta);
264
+ return {
265
+ ...parts,
266
+ year: date.getUTCFullYear(),
267
+ month: date.getUTCMonth() + 1,
268
+ day: date.getUTCDate(),
269
+ weekday: date.getUTCDay()
270
+ };
271
+ }
272
+
273
+ function boundaryParts(parts, transform) {
274
+ const result = {
275
+ year: parts.year,
276
+ month: parts.month,
277
+ day: parts.day,
278
+ hour: parts.hour,
279
+ minute: parts.minute,
280
+ second: parts.second,
281
+ millisecond: 0
282
+ };
283
+ if (transform.unit === "year") Object.assign(result, { month: 1, day: 1, hour: 0, minute: 0, second: 0 });
284
+ else if (transform.unit === "quarter") Object.assign(result, {
285
+ month: Math.floor((parts.month - 1) / 3) * 3 + 1,
286
+ day: 1, hour: 0, minute: 0, second: 0
287
+ });
288
+ else if (transform.unit === "month") Object.assign(result, { day: 1, hour: 0, minute: 0, second: 0 });
289
+ else if (["day", "week"].includes(transform.unit)) Object.assign(result, { hour: 0, minute: 0, second: 0 });
290
+ else if (transform.unit === "hour") Object.assign(result, { minute: 0, second: 0 });
291
+ else if (transform.unit === "minute") result.second = 0;
292
+ if (transform.unit === "week") {
293
+ const distance = (parts.weekday - transform.weekStartsOn + 7) % 7;
294
+ return addCalendarDays(result, -distance);
295
+ }
296
+ return result;
297
+ }
298
+
299
+ export function bucketTime(timestamp, transform) {
300
+ const timeZone = transform.timeZone ?? "UTC";
301
+ if (transform.unit === "weekday") {
302
+ return timeZone === "UTC"
303
+ ? new Date(timestamp).getUTCDay()
304
+ : calendarParts(timestamp, timeZone).weekday;
305
+ }
306
+ if (timeZone === "UTC") {
307
+ if (transform.unit === "week") {
308
+ const date = new Date(timestamp);
309
+ const distance = (date.getUTCDay() - transform.weekStartsOn + 7) % 7;
310
+ return utcBucket(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() - distance);
311
+ }
312
+ return floorUtcTimeUnit(timestamp, transform.unit);
313
+ }
314
+ const parts = calendarParts(timestamp, timeZone);
315
+ return resolveCalendarBoundary(boundaryParts(parts, transform), timeZone, transform.unit);
316
+ }
317
+
97
318
  export function deriveTimeUnitRows(rows, transform) {
98
319
  validateTimeUnitTransform(transform);
99
320
  if (!Array.isArray(rows) || !rows.every(isPlainObject)) {
@@ -108,10 +329,10 @@ export function deriveTimeUnitRows(rows, transform) {
108
329
  if (Object.hasOwn(row, transform.as)) {
109
330
  throw new Error(`Time-unit output field "${transform.as}" already exists.`);
110
331
  }
111
- const timestamp = normalizeTemporalValue(row[transform.field], transform.field, index);
332
+ const timestamp = normalizeTemporalValue(row[transform.field], transform.field, index, transform.temporalUnit);
112
333
  return {
113
334
  ...row,
114
- [transform.as]: floorUtcTimeUnit(timestamp, transform.unit)
335
+ [transform.as]: bucketTime(timestamp, transform)
115
336
  };
116
337
  });
117
338
  return cloneAndFreeze(values);
@@ -1,14 +1,24 @@
1
1
  const TRANSFORM_TOPOLOGY = Object.freeze({
2
+ bin: Object.freeze({ facetTopology: "statistical" }),
2
3
  bin2d: Object.freeze({ facetTopology: "statistical" }),
4
+ computed: Object.freeze({ facetTopology: "rowPreserving" }),
5
+ complete: Object.freeze({ facetTopology: "statistical" }),
3
6
  boxOutlier: Object.freeze({ facetTopology: "statistical" }),
4
7
  boxSummary: Object.freeze({ facetTopology: "statistical" }),
5
8
  density: Object.freeze({ facetTopology: "statistical" }),
9
+ ecdf: Object.freeze({ facetTopology: "statistical" }),
6
10
  filter: Object.freeze({ facetTopology: "rowPreserving" }),
11
+ fold: Object.freeze({ facetTopology: "statistical" }),
7
12
  gradientProfile: Object.freeze({ facetTopology: "statistical" }),
8
13
  horizon: Object.freeze({ facetTopology: "statistical" }),
9
14
  interval: Object.freeze({ facetTopology: "statistical" }),
15
+ impute: Object.freeze({ facetTopology: "statistical" }),
10
16
  markFilter: Object.freeze({ provenanceTransparent: true }),
17
+ normalize: Object.freeze({ facetTopology: "statistical" }),
11
18
  regression: Object.freeze({ facetTopology: "statistical" }),
19
+ summary: Object.freeze({ facetTopology: "statistical" }),
20
+ stack: Object.freeze({ facetTopology: "statistical" }),
21
+ statisticalReference: Object.freeze({ facetTopology: "statistical" }),
12
22
  timeUnit: Object.freeze({ facetTopology: "rowPreserving" }),
13
23
  window: Object.freeze({ facetTopology: "statistical" })
14
24
  });