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,22 +1,28 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
3
  import { validateKeys } from "../../core/validation.js";
4
+ import { withPreviewDatasetValues } from
5
+ "../primitives/semanticAction.js";
4
6
  import {
5
7
  deriveFilteredRows,
6
8
  normalizeFilterTransform
7
9
  } from "../../grammar/filter.js";
8
10
  import {
9
11
  deriveMarkFilteredRows,
12
+ markFilterSelectors,
10
13
  normalizeMarkFilterTransform
11
14
  } from "../../grammar/markFilter.js";
12
15
  import { resolveMarkFilterSelection } from "../../materialization/selection/filter.js";
13
16
  import { findSelectionPolicy } from
14
17
  "../../materialization/selection/policies/index.js";
15
18
  import {
16
- applyLayerDataRematerialization
19
+ applyLayerDataRematerialization,
20
+ applyLayerEmptyDataView
17
21
  } from "../../materialization/dependencies.js";
18
22
  import {
19
23
  hasDataset,
24
+ findDataset,
25
+ resolveDatasetReference,
20
26
  requireLayer,
21
27
  resolveEligibleLayer
22
28
  } from "../../selectors/index.js";
@@ -29,7 +35,9 @@ const MARK_SELECTOR_KEYS = Object.freeze([
29
35
  "grain", "field", "channel", "property", "op", "value", "values",
30
36
  "min", "max", "inclusive", "count", "groupBy", "ties"
31
37
  ]);
32
- const MARK_OPTIONS = Object.freeze(["target", ...MARK_SELECTOR_KEYS]);
38
+ const MARK_OPTIONS = Object.freeze(["target", "mode", ...MARK_SELECTOR_KEYS]);
39
+ const REMOVE_MARK_FILTER_OPTIONS = Object.freeze(["target"]);
40
+ const EMPTY_MARK_OPTIONS = Object.freeze(["id"]);
33
41
 
34
42
  function retainedHistogramBoundaries(layer, items) {
35
43
  if (layer.mark?.type !== "bar" || layer.encoding?.x?.bin === undefined) {
@@ -63,18 +71,29 @@ export const filterData = action(
63
71
  function (args = {}) {
64
72
  validateKeys(args, OPTIONS, "filterData");
65
73
  const id = validateUserId(args.id, "Filtered dataset id");
66
- const source = validateUserId(
74
+ const requestedSource = validateUserId(
67
75
  args.source ?? this.context.currentData,
68
76
  "Source dataset id"
69
77
  );
78
+ const source = resolveDatasetReference(
79
+ this,
80
+ requestedSource,
81
+ "Filter source dataset"
82
+ ).id;
70
83
  const transform = normalizeFilterTransform(args);
71
- return this
84
+ const next = this
72
85
  .createDerivedData({
73
86
  id,
74
87
  source,
75
88
  transform: [transform]
76
89
  })
77
90
  .materializeFilteredData({ id });
91
+ return this.actionStack.length === 1
92
+ ? next._withMaterializationConfig(
93
+ ["data", "filter", id],
94
+ { current: id }
95
+ )
96
+ : next;
78
97
  }
79
98
  );
80
99
 
@@ -96,15 +115,168 @@ export const materializeMarkFilteredData = action(
96
115
  `Mark filter target "${transform.target}" must still use source dataset "${dataset.source}".`
97
116
  );
98
117
  }
99
- const resolved = resolveMarkFilterSelection(
100
- this,
101
- transform.target,
102
- transform.selector
103
- );
104
- return this.editSemantic({
118
+ let preview = this;
119
+ let currentRows = source.values;
120
+ const selectors = markFilterSelectors(transform);
121
+ for (const [index, selector] of selectors.entries()) {
122
+ const resolved = resolveMarkFilterSelection(
123
+ preview,
124
+ transform.target,
125
+ selector
126
+ );
127
+ currentRows = deriveMarkFilteredRows(
128
+ currentRows,
129
+ resolved.items,
130
+ resolved.keys
131
+ );
132
+ if (index < selectors.length - 1) {
133
+ preview = withPreviewDatasetValues(preview, {
134
+ id,
135
+ values: currentRows,
136
+ target: transform.target
137
+ });
138
+ preview = currentRows.length === 0
139
+ ? applyLayerEmptyDataView(preview, transform.target)
140
+ : applyLayerDataRematerialization(preview, transform.target);
141
+ currentRows = findDataset(preview, id).values;
142
+ }
143
+ }
144
+ if (selectors.length > 1) {
145
+ preview = withPreviewDatasetValues(preview, {
146
+ id,
147
+ omitValues: true
148
+ });
149
+ }
150
+ let next = preview.editSemantic({
105
151
  property: `dataset[${id}].values`,
106
- value: deriveMarkFilteredRows(source.values, resolved.items, resolved.keys)
152
+ value: currentRows
153
+ });
154
+ if (requireLayer(next, transform.target).data !== id) {
155
+ next = next.editSemantic({
156
+ property: `layer[${transform.target}].data`,
157
+ value: id
158
+ });
159
+ }
160
+ next = next._withMarkConfig(transform.target, {
161
+ ...next.markConfigs[transform.target],
162
+ markFilter: {
163
+ ...next.markConfigs[transform.target].markFilter,
164
+ empty: currentRows.length === 0
165
+ }
107
166
  });
167
+ return currentRows.length === 0
168
+ ? next.materializeEmptyMark({ id: transform.target })
169
+ : applyLayerDataRematerialization(next, transform.target);
170
+ }
171
+ );
172
+
173
+ export const materializeEmptyMark = action(
174
+ {
175
+ op: "materializeEmptyMark",
176
+ description: "Clear one active empty-filter mark without changing its domains."
177
+ },
178
+ function (args = {}) {
179
+ validateKeys(args, EMPTY_MARK_OPTIONS, "materializeEmptyMark");
180
+ const id = validateUserId(args.id, "Empty mark id");
181
+ const layer = requireLayer(this, id);
182
+ const owner = this.markConfigs[id]?.markFilter;
183
+ const dataset = findDataset(this, layer.data);
184
+ if (owner?.empty !== true || dataset?.values?.length !== 0) {
185
+ throw new Error(`Mark "${id}" does not own an active empty filter.`);
186
+ }
187
+ return applyLayerEmptyDataView(this, id);
188
+ }
189
+ );
190
+
191
+ function activeMarkFilter(program, layer) {
192
+ const configured = program.markConfigs[layer.id]?.markFilter;
193
+ const dataset = findDataset(program, layer.data);
194
+ const transform = dataset?.transform?.[0];
195
+ if (
196
+ dataset?.source === undefined ||
197
+ transform?.type !== "markFilter" ||
198
+ transform.target !== layer.id
199
+ ) {
200
+ return undefined;
201
+ }
202
+ return {
203
+ dataset: dataset.id,
204
+ source: dataset.source,
205
+ selectors: markFilterSelectors(transform),
206
+ ...(configured?.originalBin === undefined
207
+ ? {}
208
+ : { originalBin: configured.originalBin })
209
+ };
210
+ }
211
+
212
+ function withoutMarkFilterConfig(program, id) {
213
+ const config = program.markConfigs[id] ?? {};
214
+ const { markFilter: unused, ...remaining } = config;
215
+ void unused;
216
+ return program._withMarkConfig(id, remaining);
217
+ }
218
+
219
+ function restoreHistogramBin(program, layer, originalBin) {
220
+ let next = program;
221
+ const current = layer.encoding?.x?.bin ?? {};
222
+ for (const property of ["maxBins", "step", "boundaries"]) {
223
+ if (Object.hasOwn(originalBin, property)) {
224
+ next = next.editSemantic({
225
+ property: `layer[${layer.id}].encoding.x.bin.${property}`,
226
+ value: originalBin[property]
227
+ });
228
+ } else if (Object.hasOwn(current, property)) {
229
+ next = next.editSemantic({
230
+ property: `layer[${layer.id}].encoding.x.bin.${property}`,
231
+ remove: true
232
+ });
233
+ }
234
+ }
235
+ return next;
236
+ }
237
+
238
+ function removeActiveMarkFilter(program, layer, owner) {
239
+ let next = program.editSemantic({
240
+ property: `layer[${layer.id}].data`,
241
+ value: owner.source
242
+ });
243
+ if (owner.originalBin !== undefined) {
244
+ next = restoreHistogramBin(next, requireLayer(next, layer.id), owner.originalBin);
245
+ }
246
+ next = withoutMarkFilterConfig(next, layer.id)
247
+ .releaseDerivedData({ id: owner.dataset })
248
+ ._withContext({ currentData: owner.source });
249
+ return applyLayerDataRematerialization(next, layer.id);
250
+ }
251
+
252
+ function nextMarkFilterDatasetId(program, layerId) {
253
+ const base = `${layerId}FilteredData`;
254
+ if (!hasDataset(program, base)) return base;
255
+ let revision = 2;
256
+ while (hasDataset(program, `${base}${revision}`)) revision += 1;
257
+ return `${base}${revision}`;
258
+ }
259
+
260
+ function sameSelector(left, right) {
261
+ return JSON.stringify(left) === JSON.stringify(right);
262
+ }
263
+
264
+ export const removeMarkFilter = action(
265
+ {
266
+ op: "removeMarkFilter",
267
+ description: "Remove one active final-item filter and restore its source."
268
+ },
269
+ function (args = {}) {
270
+ validateKeys(args, REMOVE_MARK_FILTER_OPTIONS, "removeMarkFilter");
271
+ const target = args.target === undefined
272
+ ? undefined
273
+ : validateUserId(args.target, "Mark filter target id");
274
+ const layer = resolveEligibleLayer(this, {
275
+ target,
276
+ label: "mark filter",
277
+ predicate: candidate => activeMarkFilter(this, candidate) !== undefined
278
+ });
279
+ return removeActiveMarkFilter(this, layer, activeMarkFilter(this, layer));
108
280
  }
109
281
  );
110
282
 
@@ -115,6 +287,9 @@ export const filterMarks = action(
115
287
  },
116
288
  function (args = {}) {
117
289
  validateKeys(args, MARK_OPTIONS, "filterMarks");
290
+ if (args.mode !== undefined && !["replace", "compose"].includes(args.mode)) {
291
+ throw new Error(`Unknown mark filter mode "${args.mode}".`);
292
+ }
118
293
  const target = args.target === undefined
119
294
  ? undefined
120
295
  : validateUserId(args.target, "Filter mark target id");
@@ -130,46 +305,67 @@ export const filterMarks = action(
130
305
  `filterMarks does not support composite mark "${layer.id}"; filter its source dataset first.`
131
306
  );
132
307
  }
133
- const derivedId = `${layer.id}FilteredData`;
134
- if (hasDataset(this, derivedId)) {
135
- throw new Error(`Dataset "${derivedId}" already exists.`);
136
- }
308
+ const owner = activeMarkFilter(this, layer);
137
309
  const selector = Object.fromEntries(
138
310
  MARK_SELECTOR_KEYS.flatMap(key =>
139
311
  Object.hasOwn(args, key) ? [[key, args[key]]] : []
140
312
  )
141
313
  );
142
314
  const resolved = resolveMarkFilterSelection(this, layer.id, selector);
143
- if (resolved.keys.length === 0) {
144
- throw new Error("filterMarks requires at least one matching mark item.");
315
+ const previousSelector = owner?.selectors.at(-1);
316
+ if (previousSelector !== undefined && sameSelector(previousSelector, resolved.selector)) {
317
+ return this;
318
+ }
319
+ if (owner !== undefined && args.mode === undefined) {
320
+ throw new Error(
321
+ "Repeated filterMarks requires mode \"replace\" or \"compose\"."
322
+ );
145
323
  }
146
- const transform = normalizeMarkFilterTransform(layer.id, resolved.selector);
147
- const boundaries = retainedHistogramBoundaries(layer, resolved.items);
148
- let next = this
324
+ const selectors = owner === undefined || args.mode === "replace"
325
+ ? [resolved.selector]
326
+ : [...owner.selectors, resolved.selector];
327
+ let next = owner === undefined
328
+ ? this
329
+ : removeActiveMarkFilter(this, layer, owner);
330
+ const baseLayer = requireLayer(next, layer.id);
331
+ const first = resolveMarkFilterSelection(next, layer.id, selectors[0]);
332
+ const transform = normalizeMarkFilterTransform(layer.id, selectors);
333
+ const boundaries = retainedHistogramBoundaries(baseLayer, first.items);
334
+ const originalBin = baseLayer.mark?.type === "bar" &&
335
+ baseLayer.encoding?.x?.bin !== undefined
336
+ ? baseLayer.encoding.x.bin
337
+ : undefined;
338
+ const derivedId = nextMarkFilterDatasetId(next, layer.id);
339
+ next = next
149
340
  .createDerivedData({
150
341
  id: derivedId,
151
- source: layer.data,
342
+ source: baseLayer.data,
152
343
  transform: [transform]
153
- })
154
- .materializeMarkFilteredData({ id: derivedId });
344
+ });
155
345
  if (boundaries !== undefined) {
156
346
  for (const property of ["maxBins", "step"]) {
157
- if (Object.hasOwn(layer.encoding.x.bin, property)) {
347
+ if (Object.hasOwn(baseLayer.encoding.x.bin, property)) {
158
348
  next = next.editSemantic({
159
- property: `layer[${layer.id}].encoding.x.bin.${property}`,
349
+ property: `layer[${baseLayer.id}].encoding.x.bin.${property}`,
160
350
  remove: true
161
351
  });
162
352
  }
163
353
  }
164
354
  next = next.editSemantic({
165
- property: `layer[${layer.id}].encoding.x.bin.boundaries`,
355
+ property: `layer[${baseLayer.id}].encoding.x.bin.boundaries`,
166
356
  value: boundaries
167
357
  });
168
358
  }
169
- next = next.editSemantic({
170
- property: `layer[${layer.id}].data`,
171
- value: derivedId
359
+ next = next._withMarkConfig(baseLayer.id, {
360
+ ...next.markConfigs[baseLayer.id],
361
+ markFilter: {
362
+ dataset: derivedId,
363
+ source: baseLayer.data,
364
+ selectors,
365
+ empty: false,
366
+ ...(originalBin === undefined ? {} : { originalBin })
367
+ }
172
368
  });
173
- return applyLayerDataRematerialization(next, layer.id);
369
+ return next.materializeMarkFilteredData({ id: derivedId });
174
370
  }
175
371
  );
@@ -0,0 +1,24 @@
1
+ import {
2
+ deriveFoldRows,
3
+ normalizeFoldTransform
4
+ } from "../../grammar/fold.js";
5
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
6
+
7
+ const OPTIONS = Object.freeze(["id", "source", "fields", "as"]);
8
+
9
+ export const materializeFoldData = derivedMaterializer(
10
+ "materializeFoldData",
11
+ "Materialize one immutable wide-to-long dataset.",
12
+ "fold",
13
+ deriveFoldRows
14
+ );
15
+
16
+ export const createFoldData = derivedCreator(
17
+ "createFoldData",
18
+ "Create reusable key/value rows from selected wide fields.",
19
+ OPTIONS,
20
+ "Fold dataset id",
21
+ "Fold source dataset id",
22
+ normalizeFoldTransform,
23
+ "materializeFoldData"
24
+ );
@@ -0,0 +1,26 @@
1
+ import {
2
+ deriveImputedRows,
3
+ normalizeImputeTransform
4
+ } from "../../grammar/impute.js";
5
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
6
+
7
+ const OPTIONS = Object.freeze([
8
+ "id", "source", "fields", "groupBy", "sortBy", "method", "value", "edges", "maxGap"
9
+ ]);
10
+
11
+ export const materializeImputedData = derivedMaterializer(
12
+ "materializeImputedData",
13
+ "Materialize one immutable missing-value dataset.",
14
+ "impute",
15
+ deriveImputedRows
16
+ );
17
+
18
+ export const createImputedData = derivedCreator(
19
+ "createImputedData",
20
+ "Replace explicit missing cells within independent groups.",
21
+ OPTIONS,
22
+ "Imputed dataset id",
23
+ "Imputed source dataset id",
24
+ normalizeImputeTransform,
25
+ "materializeImputedData"
26
+ );
@@ -4,12 +4,15 @@ import {
4
4
  } from "./timeUnit.js";
5
5
  import {
6
6
  createDerivedData,
7
+ bindMarkData,
7
8
  rebindLayerData,
8
9
  releaseDerivedData
9
10
  } from "./derived.js";
10
11
  import {
11
12
  filterData,
12
13
  filterMarks,
14
+ materializeEmptyMark,
15
+ removeMarkFilter,
13
16
  materializeFilteredData,
14
17
  materializeMarkFilteredData
15
18
  } from "./filter.js";
@@ -24,6 +27,7 @@ import {
24
27
  materializeDensityData
25
28
  } from "./density.js";
26
29
  import { createIntervalData, materializeIntervalData } from "./interval.js";
30
+ import { createECDFData, materializeECDFData } from "./ecdf.js";
27
31
  import { createHorizonData, materializeHorizonData } from "./horizon.js";
28
32
  import {
29
33
  createBin2DData,
@@ -32,11 +36,25 @@ import {
32
36
  } from "./bin2d.js";
33
37
  import { createData } from "./create.js";
34
38
  import { createWindowData, materializeWindowData } from "./window.js";
39
+ import { createSummaryData, materializeSummaryData } from "./summary.js";
40
+ import { createBinData, materializeBinData } from "./bin.js";
41
+ import { createFoldData, materializeFoldData } from "./fold.js";
42
+ import { createComputedData, materializeComputedData } from "./computed.js";
43
+ import {
44
+ createNormalizedData,
45
+ materializeNormalizedData
46
+ } from "./normalize.js";
47
+ import { createCompleteData, materializeCompleteData } from "./complete.js";
48
+ import { createImputedData, materializeImputedData } from "./impute.js";
49
+ import { createStackData, materializeStackData } from "./stack.js";
50
+ import { EDIT_DERIVED_DATA_ACTIONS } from "./edit.js";
35
51
  import { createBoxSummaryData, createBoxOutlierData, materializeBoxSummaryData, materializeBoxOutlierData } from "./box.js";
36
52
 
37
53
  export function registerDataActions(ProgramClass) {
54
+ Object.assign(ProgramClass.prototype, EDIT_DERIVED_DATA_ACTIONS);
38
55
  ProgramClass.prototype.createData = createData;
39
56
  ProgramClass.prototype.createDerivedData = createDerivedData;
57
+ ProgramClass.prototype.bindMarkData = bindMarkData;
40
58
  ProgramClass.prototype.releaseDerivedData = releaseDerivedData;
41
59
  ProgramClass.prototype.rebindLayerData = rebindLayerData;
42
60
  ProgramClass.prototype.createDensityData = createDensityData;
@@ -46,6 +64,8 @@ export function registerDataActions(ProgramClass) {
46
64
  ProgramClass.prototype.materializeMarkFilteredData = materializeMarkFilteredData;
47
65
  ProgramClass.prototype.filterData = filterData;
48
66
  ProgramClass.prototype.filterMarks = filterMarks;
67
+ ProgramClass.prototype.materializeEmptyMark = materializeEmptyMark;
68
+ ProgramClass.prototype.removeMarkFilter = removeMarkFilter;
49
69
  ProgramClass.prototype.materializeRegressionData = materializeRegressionData;
50
70
  ProgramClass.prototype.materializeDensityData = materializeDensityData;
51
71
  ProgramClass.prototype.createGradientProfileData = createGradientProfileData;
@@ -54,10 +74,28 @@ export function registerDataActions(ProgramClass) {
54
74
  ProgramClass.prototype.createRegressionData = createRegressionData;
55
75
  ProgramClass.prototype.materializeIntervalData = materializeIntervalData;
56
76
  ProgramClass.prototype.createIntervalData = createIntervalData;
77
+ ProgramClass.prototype.createECDFData = createECDFData;
78
+ ProgramClass.prototype.materializeECDFData = materializeECDFData;
57
79
  ProgramClass.prototype.createHorizonData = createHorizonData;
58
80
  ProgramClass.prototype.materializeHorizonData = materializeHorizonData;
59
81
  ProgramClass.prototype.createWindowData = createWindowData;
60
82
  ProgramClass.prototype.materializeWindowData = materializeWindowData;
83
+ ProgramClass.prototype.createSummaryData = createSummaryData;
84
+ ProgramClass.prototype.materializeSummaryData = materializeSummaryData;
85
+ ProgramClass.prototype.createBinData = createBinData;
86
+ ProgramClass.prototype.materializeBinData = materializeBinData;
87
+ ProgramClass.prototype.createFoldData = createFoldData;
88
+ ProgramClass.prototype.materializeFoldData = materializeFoldData;
89
+ ProgramClass.prototype.createComputedData = createComputedData;
90
+ ProgramClass.prototype.materializeComputedData = materializeComputedData;
91
+ ProgramClass.prototype.createNormalizedData = createNormalizedData;
92
+ ProgramClass.prototype.materializeNormalizedData = materializeNormalizedData;
93
+ ProgramClass.prototype.createCompleteData = createCompleteData;
94
+ ProgramClass.prototype.materializeCompleteData = materializeCompleteData;
95
+ ProgramClass.prototype.createImputedData = createImputedData;
96
+ ProgramClass.prototype.materializeImputedData = materializeImputedData;
97
+ ProgramClass.prototype.createStackData = createStackData;
98
+ ProgramClass.prototype.materializeStackData = materializeStackData;
61
99
  ProgramClass.prototype.createTimeUnitData = createTimeUnitData;
62
100
  ProgramClass.prototype.materializeTimeUnitData = materializeTimeUnitData;
63
101
  ProgramClass.prototype.createBin2DData = createBin2DData;
@@ -11,6 +11,7 @@ const OPTIONS = Object.freeze([
11
11
  "groupBy",
12
12
  "center",
13
13
  "extent",
14
+ "method",
14
15
  "level",
15
16
  "as"
16
17
  ]);
@@ -39,6 +40,7 @@ export const createIntervalData = derivedCreator(
39
40
  groupBy: args.groupBy,
40
41
  center: args.center,
41
42
  extent: args.extent,
43
+ method: args.method,
42
44
  level: args.level,
43
45
  as
44
46
  });
@@ -7,7 +7,7 @@ import { planDerivedDataRevision } from
7
7
  import { findDataset } from "../../selectors/datasets.js";
8
8
  import { resolveEligibleLayer } from "../../selectors/layers.js";
9
9
 
10
- const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "level"]);
10
+ const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "method", "level"]);
11
11
 
12
12
  export function ownOptions(value, options) {
13
13
  return Object.fromEntries(
@@ -42,6 +42,7 @@ export function createResolvedIntervalData(program, resolved) {
42
42
  groupBy: resolved.groupBy,
43
43
  center: resolved.interval.center,
44
44
  extent: resolved.interval.extent,
45
+ method: resolved.interval.method,
45
46
  level: resolved.interval.level,
46
47
  as: resolved.fields
47
48
  });
@@ -49,7 +50,9 @@ export function createResolvedIntervalData(program, resolved) {
49
50
 
50
51
  export function applyIntervalRevision(program, interval) {
51
52
  if (!interval.changed) return program;
52
- let next = program.createIntervalData(interval.dataArgs);
53
+ let next = interval.dataArgs === undefined
54
+ ? program
55
+ : program.createIntervalData(interval.dataArgs);
53
56
  for (const rebind of interval.revision.rebinds) {
54
57
  next = next.rebindLayerData(rebind);
55
58
  }
@@ -57,11 +60,84 @@ export function applyIntervalRevision(program, interval) {
57
60
  }
58
61
 
59
62
  export function releaseIntervalRevision(program, interval) {
60
- return interval.changed
63
+ return interval.changed && interval.revision.release !== undefined
61
64
  ? program.releaseDerivedData(interval.revision.release)
62
65
  : program;
63
66
  }
64
67
 
68
+ export function findIntervalTransform(program, data) {
69
+ const dataset = findDataset(program, data);
70
+ return dataset?.transform?.length === 1 &&
71
+ dataset.transform[0].type === "interval"
72
+ ? dataset.transform[0]
73
+ : undefined;
74
+ }
75
+
76
+ export function collectIntervalConsumers(program, owners) {
77
+ const consumers = new Set(owners);
78
+ let changed = true;
79
+ while (changed) {
80
+ changed = false;
81
+ for (const layer of program.semanticSpec.layers ?? []) {
82
+ if (
83
+ layer.source !== undefined &&
84
+ consumers.has(layer.source) &&
85
+ !consumers.has(layer.id)
86
+ ) {
87
+ consumers.add(layer.id);
88
+ changed = true;
89
+ }
90
+ }
91
+ }
92
+ return [...consumers];
93
+ }
94
+
95
+ export function planIntervalRoleData(program, {
96
+ owner,
97
+ currentData,
98
+ candidate,
99
+ consumers
100
+ }) {
101
+ const previousTransform = findIntervalTransform(program, currentData);
102
+ if (candidate.interval.mode === "statistical") {
103
+ const revision = planDerivedDataRevision(program, {
104
+ owner,
105
+ role: "IntervalData",
106
+ ...(previousTransform === undefined ? {} : { previous: currentData }),
107
+ consumers
108
+ });
109
+ return {
110
+ changed: true,
111
+ revision,
112
+ dataId: revision.id,
113
+ dataArgs: {
114
+ id: revision.id,
115
+ source: candidate.source,
116
+ field: candidate.interval.field,
117
+ groupBy: candidate.groupBy,
118
+ center: candidate.interval.center,
119
+ extent: candidate.interval.extent,
120
+ method: candidate.interval.method,
121
+ level: candidate.interval.level,
122
+ as: candidate.fields
123
+ }
124
+ };
125
+ }
126
+ const changed = currentData !== candidate.source;
127
+ return {
128
+ changed,
129
+ dataId: candidate.source,
130
+ revision: {
131
+ rebinds: changed
132
+ ? consumers.map(id => ({ id, data: candidate.source }))
133
+ : [],
134
+ ...(previousTransform === undefined
135
+ ? {}
136
+ : { release: { id: currentData } })
137
+ }
138
+ };
139
+ }
140
+
65
141
  export function planIntervalEdit(program, {
66
142
  owner,
67
143
  data,
@@ -75,7 +151,7 @@ export function planIntervalEdit(program, {
75
151
  validateKeys(statistics, STATISTICS_OPTIONS, `${operation} statistics`);
76
152
  if (!STATISTICS_OPTIONS.some(key => Object.hasOwn(statistics, key))) {
77
153
  throw new Error(
78
- `${operation} statistics requires center, extent, or level.`
154
+ `${operation} statistics requires center, extent, method, or level.`
79
155
  );
80
156
  }
81
157
  const previous = findDataset(program, data);
@@ -95,6 +171,11 @@ export function planIntervalEdit(program, {
95
171
  ? statistics.extent
96
172
  : transform.extent;
97
173
  const raw = { center, extent };
174
+ if (Object.hasOwn(statistics, "method")) {
175
+ raw.method = statistics.method;
176
+ } else if (extent === "ci" && transform.extent === "ci") {
177
+ raw.method = transform.method;
178
+ }
98
179
  if (Object.hasOwn(statistics, "level")) {
99
180
  raw.level = statistics.level;
100
181
  } else if (extent === "ci" && transform.extent === "ci") {
@@ -104,6 +185,7 @@ export function planIntervalEdit(program, {
104
185
  const current = {
105
186
  center: transform.center,
106
187
  extent: transform.extent,
188
+ ...(transform.method === undefined ? {} : { method: transform.method }),
107
189
  ...(transform.level === undefined ? {} : { level: transform.level })
108
190
  };
109
191
  const changed = JSON.stringify(parameters) !== JSON.stringify(current);
@@ -0,0 +1,27 @@
1
+ import {
2
+ deriveNormalizedRows,
3
+ normalizeNormalizeTransform
4
+ } from "../../grammar/normalize.js";
5
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
6
+
7
+ const OPTIONS = Object.freeze([
8
+ "id", "source", "field", "as", "groupBy", "method",
9
+ "variance", "zeroDenominator", "baseline", "sortBy"
10
+ ]);
11
+
12
+ export const materializeNormalizedData = derivedMaterializer(
13
+ "materializeNormalizedData",
14
+ "Materialize one immutable grouped normalization dataset.",
15
+ "normalize",
16
+ deriveNormalizedRows
17
+ );
18
+
19
+ export const createNormalizedData = derivedCreator(
20
+ "createNormalizedData",
21
+ "Create a row-preserving grouped normalization dataset.",
22
+ OPTIONS,
23
+ "Normalized dataset id",
24
+ "Normalized source dataset id",
25
+ normalizeNormalizeTransform,
26
+ "materializeNormalizedData"
27
+ );