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,3 +1,6 @@
1
+ import { isSourceOwnedText } from "../../../grammar/text.js";
2
+ import { findSemanticScale } from "../../../selectors/scales.js";
3
+ import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
1
4
  import { validateUserId } from "../../../core/identifiers.js";
2
5
  import { getPositionCoordinateDefaults } from "../../../grammar/coordinates.js";
3
6
  import {
@@ -6,6 +9,7 @@ import {
6
9
  readQuantitativeField,
7
10
  readScaleField,
8
11
  readTemporalField,
12
+ resolveTemporalUnit,
9
13
  validateFieldType,
10
14
  validatePositionChannel
11
15
  } from "../../../grammar/scales/index.js";
@@ -21,7 +25,7 @@ import {
21
25
  validateAggregateFieldType,
22
26
  validateAggregateFieldValues,
23
27
  } from "../../../grammar/aggregate.js";
24
- import { normalizeRuleDatum } from "../../../grammar/rules.js";
28
+ import { normalizePositionDatum } from "../../../grammar/positionDatum.js";
25
29
  import {
26
30
  readArcThetaValues,
27
31
  readArcThetaWeights
@@ -34,10 +38,10 @@ import {
34
38
 
35
39
  const POSITION_ENCODING_OPTIONS = Object.freeze([
36
40
  "field", "datum", "target", "fieldType", "scale", "coordinate",
37
- "aggregate", "bin", "stack", "weight"
41
+ "aggregate", "bin", "stack", "weight", "temporalUnit", "mapping"
38
42
  ]);
39
43
 
40
- function inferRuleDatumFieldType(datum, operation) {
44
+ function inferDatumFieldType(datum, operation) {
41
45
  if (Number.isFinite(datum)) return "quantitative";
42
46
  if (isNominalValue(datum)) return "nominal";
43
47
  throw new Error(`${operation} datum requires an explicit fieldType.`);
@@ -93,29 +97,53 @@ export function resolvePositionEncoding(program, channel, args, operation) {
93
97
  args.target,
94
98
  getPositionChannelDefinition(channel).markTypes
95
99
  );
100
+ const previous = layer.encoding?.[channel];
101
+ if (isSourceOwnedText(layer)) {
102
+ throw new Error(`${operation} cannot replace source-owned Text positions; edit the source, use dx/dy, or create independent Text with data.`);
103
+ }
96
104
  if (
97
105
  Object.hasOwn(args, "weight") &&
98
- !(layer.mark.type === "arc" && channel === "theta")
106
+ !(layer.mark.type === "arc" && channel === "theta") &&
107
+ !(layer.mark.type === "bar" && channel === "x" &&
108
+ (args.bin !== undefined || previous?.bin !== undefined))
99
109
  ) {
100
- throw new Error(`${operation} weight is supported only for arc theta encoding.`);
110
+ throw new Error(
111
+ `${operation} weight is supported only for arc theta encoding or histogram x encoding.`
112
+ );
113
+ }
114
+ if (Object.hasOwn(args, "mapping") && (layer.mark.type !== "arc" || channel !== "radius")) {
115
+ throw new Error(`${operation} mapping requires Arc radius.`);
101
116
  }
102
117
  validateCoordinateFamily(layer, channel, operation);
118
+ const clearRadialMapping =
119
+ layer.mark.type === "arc" && channel === "radius" && args.mapping === false;
103
120
  const hasField = Object.hasOwn(args, "field");
104
121
  const hasDatum = Object.hasOwn(args, "datum");
105
- if (layer.mark.type === "rule") {
122
+ if (["rule", "rect", "text", "point", "tick"].includes(layer.mark.type)) {
106
123
  if (hasField === hasDatum) {
107
- throw new Error(`${operation} requires exactly one of field or datum for a rule mark.`);
124
+ throw new Error(`${operation} requires exactly one of field or datum for a ${layer.mark.type} mark.`);
108
125
  }
109
- if (hasField && args.fieldType === undefined) {
126
+ if (layer.mark.type === "rule" && hasField && args.fieldType === undefined) {
110
127
  throw new Error(`${operation} requires fieldType for a rule mark.`);
111
128
  }
129
+ } else if (layer.mark.type === "area") {
130
+ if (hasField === hasDatum) throw new Error(`${operation} requires exactly one of field or datum for an area mark.`);
131
+ if (hasDatum && (!Number.isFinite(args.datum) || (args.fieldType ?? "quantitative") !== "quantitative" ||
132
+ ["aggregate", "bin", "stack", "temporalUnit"].some(key => Object.hasOwn(args, key)))) {
133
+ throw new Error("Area datum requires a finite quantitative constant without aggregate, bin, stack, or temporalUnit.");
134
+ }
112
135
  } else if (hasDatum) {
113
136
  throw new Error(`${operation} does not support datum for a ${layer.mark.type} mark.`);
114
137
  }
115
- const previous = layer.encoding?.[channel];
138
+ const mapping = channel === "radius" && layer.mark.type === "arc"
139
+ ? clearRadialMapping
140
+ ? undefined
141
+ : args.mapping ?? findSemanticScale(program, previous?.scale)?.radialMapping
142
+ : undefined;
143
+ const countRadius = mapping !== undefined && (args.aggregate ?? previous?.aggregate) === "count";
116
144
  const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
117
- layer.mark.type === "rule" && hasDatum
118
- ? inferRuleDatumFieldType(args.datum, operation)
145
+ ["rule", "rect", "text", "point", "tick"].includes(layer.mark.type) && hasDatum
146
+ ? inferDatumFieldType(args.datum, operation)
119
147
  : layer.mark.type === "arc" && channel === "theta" &&
120
148
  ["count", "sum"].includes(args.aggregate)
121
149
  ? "nominal"
@@ -130,7 +158,24 @@ export function resolvePositionEncoding(program, channel, args, operation) {
130
158
  ? xEncoding.field
131
159
  : args.field;
132
160
  const datum = args.datum;
161
+ const temporalUnit = resolveTemporalUnit({ ...args, ...(hasDatum ? {} : { field }) }, fieldType, previous);
162
+ const usesField = !countRadius && (!["rule", "area", "rect", "text", "point", "tick"].includes(layer.mark.type) || hasField);
163
+ if (usesField && (typeof field !== "string" || field.length === 0)) {
164
+ throw new TypeError(`${operation} field must be a non-empty string.`);
165
+ }
166
+ // A missing field is invalid even before the Bar's measure/category role is known.
167
+ if (layer.mark.type === "bar" && program.markConfigs[target]?.boxPlot === undefined) {
168
+ if (fieldType === "quantitative" && dataset.values.length > 0 &&
169
+ !dataset.values.some(row => Object.hasOwn(row, field))) {
170
+ throw new Error(`${operation} field "${field}" does not exist in the dataset.`);
171
+ }
172
+ if (fieldType === "quantitative") {
173
+ validateAggregateFieldValues(dataset.values, field, fieldType);
174
+ } else readScaleField(dataset.values, field, fieldType, { temporalUnit });
175
+ }
133
176
  const effectiveArgs = { ...args };
177
+ if (clearRadialMapping) delete effectiveArgs.mapping;
178
+ else if (mapping !== undefined) effectiveArgs.mapping = mapping;
134
179
  const directQuantitativeArcTheta =
135
180
  layer.mark.type === "arc" &&
136
181
  channel === "theta" &&
@@ -138,7 +183,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
138
183
  for (const property of ["aggregate", "bin", "stack"]) {
139
184
  if (!Object.hasOwn(effectiveArgs, property) && previous !== undefined &&
140
185
  Object.hasOwn(previous, property) &&
141
- !(directQuantitativeArcTheta && property === "aggregate")) {
186
+ !(directQuantitativeArcTheta && property === "aggregate") &&
187
+ !(clearRadialMapping && property === "aggregate")) {
142
188
  effectiveArgs[property] = previous[property];
143
189
  }
144
190
  }
@@ -159,11 +205,6 @@ export function resolvePositionEncoding(program, channel, args, operation) {
159
205
  fieldType,
160
206
  field
161
207
  });
162
- const usesField = layer.mark.type !== "rule" || hasField;
163
- if (usesField && (typeof field !== "string" || field.length === 0)) {
164
- throw new TypeError(`${operation} field must be a non-empty string.`);
165
- }
166
-
167
208
  const aggregateOutput = isAggregate(policy.aggregate) && !(
168
209
  layer.mark.type === "arc" && channel === "theta"
169
210
  );
@@ -171,16 +212,19 @@ export function resolvePositionEncoding(program, channel, args, operation) {
171
212
  previous,
172
213
  Object.hasOwn(args, "scale") ? args.scale : {}
173
214
  );
174
- const scale = resolvePositionScaleDefinition(
215
+ const resolvedScale = resolvePositionScaleDefinition(
175
216
  program,
176
217
  channel,
177
218
  aggregateOutput ? "quantitative" : fieldType,
178
219
  requestedScale,
179
- layer.mark.type === "bar"
220
+ mapping !== undefined ? { radialMapping: mapping, zero: true, nice: false }
221
+ : layer.mark.type === "bar"
180
222
  ? program.markConfigs[target]?.boxPlot !== undefined && fieldType === "quantitative"
181
223
  ? { nice: true, zero: false }
182
224
  : fieldType === "quantitative"
183
- ? policy.bin !== undefined || policy.stack === null
225
+ ? policy.bin === undefined && policy.stack === undefined
226
+ ? { nice: true }
227
+ : policy.bin !== undefined || policy.stack === null
184
228
  ? { nice: true, zero: false }
185
229
  : { nice: true, zero: true }
186
230
  : fieldType === "temporal"
@@ -196,6 +240,11 @@ export function resolvePositionEncoding(program, channel, args, operation) {
196
240
  }
197
241
  : {}
198
242
  );
243
+ const scale = clearRadialMapping
244
+ ? Object.fromEntries(
245
+ Object.entries(resolvedScale).filter(([property]) => property !== "radialMapping")
246
+ )
247
+ : resolvedScale;
199
248
  if (
200
249
  layer.mark.type === "bar" &&
201
250
  program.markConfigs[target]?.boxPlot === undefined &&
@@ -217,8 +266,13 @@ export function resolvePositionEncoding(program, channel, args, operation) {
217
266
  "Position scale unknown currently requires a row-owned point mark."
218
267
  );
219
268
  }
220
- if (layer.mark.type === "rule" && hasDatum) {
221
- normalizeRuleDatum(datum, fieldType, channel);
269
+ if (layer.mark.type === "area" && fieldType === "quantitative") {
270
+ readAreaEndpoint(dataset.values, { ...(hasDatum ? { datum } : { field }), fieldType }, layer.mark.missing);
271
+ } else if (["rule", "rect", "text", "point", "tick"].includes(layer.mark.type) && hasDatum) {
272
+ const mark = layer.mark.type[0].toUpperCase() + layer.mark.type.slice(1);
273
+ normalizePositionDatum(datum, fieldType, channel, temporalUnit, mark);
274
+ } else if (countRadius) {
275
+ // Count has no source measure field. Category-grain validation runs once theta exists.
222
276
  } else if (aggregateOutput) {
223
277
  validateAggregateFieldType(policy.aggregate, fieldType);
224
278
  validateAggregateFieldValues(dataset.values, field, fieldType);
@@ -239,14 +293,20 @@ export function resolvePositionEncoding(program, channel, args, operation) {
239
293
  }
240
294
  }
241
295
  } else if (layer.mark.type === "rect") {
242
- readScaleField(dataset.values, field, fieldType, { allowUnknown: true });
296
+ readScaleField(dataset.values, field, fieldType, { allowUnknown: true, temporalUnit });
243
297
  } else if (Object.hasOwn(scale, "unknown")) {
244
- readScaleField(dataset.values, field, fieldType, { allowUnknown: true });
245
- } else if (fieldType === "temporal") readTemporalField(dataset.values, field);
298
+ readScaleField(dataset.values, field, fieldType, { allowUnknown: true, temporalUnit });
299
+ } else if (fieldType === "temporal") readTemporalField(dataset.values, field, temporalUnit);
246
300
  else if (["ordinal", "nominal"].includes(fieldType)) {
247
301
  readNominalField(dataset.values, field);
248
302
  } else readQuantitativeField(dataset.values, field);
249
303
 
304
+ if (mapping !== undefined) {
305
+ const values = countRadius ? dataset.values.map(() => 1) : readQuantitativeField(dataset.values, field);
306
+ if (values.length === 0 || values.some(value => value < 0) || !values.some(value => value > 0)) {
307
+ throw new Error("Measured radius requires non-negative values and a positive aggregate.");
308
+ }
309
+ }
250
310
  if (
251
311
  ["bar", "rect"].includes(layer.mark.type) &&
252
312
  ["ordinal", "nominal"].includes(fieldType) &&
@@ -258,13 +318,15 @@ export function resolvePositionEncoding(program, channel, args, operation) {
258
318
  target,
259
319
  layer,
260
320
  previous,
261
- requestedScale,
321
+ requestedScale: mapping === undefined ? requestedScale : { ...requestedScale, radialMapping: mapping },
262
322
  field,
263
323
  datum,
264
324
  hasField: usesField,
265
325
  fieldType,
326
+ temporalUnit,
266
327
  scale,
267
328
  coordinate: resolveCoordinate(program, channel, layer, args.coordinate),
329
+ clearRadialMapping,
268
330
  ...policy
269
331
  };
270
332
  }
@@ -1,34 +1,27 @@
1
+ import { readAreaEndpoint, validateAreaEndpointPair, normalizeAreaBound } from "../../grammar/areaEndpoints.js";
1
2
  import { action } from "../../core/action.js";
2
3
  import { validateUserId } from "../../core/identifiers.js";
3
4
  import { isPlainObject } from "../../core/immutable.js";
4
- import {
5
- readNominalField,
6
- readQuantitativeField,
7
- readScaleField,
8
- readTemporalField,
9
- validateSemanticFieldType
10
- } from "../../grammar/scales/index.js";
11
- import { normalizeRuleDatum } from "../../grammar/rules.js";
12
- import {
13
- canMaterializeArea,
14
- canMaterializeLine,
15
- getPositionEncodingMaterializationSteps
16
- } from "../../materialization/marks/index.js";
5
+ import { readNominalField, readQuantitativeField, readScaleField, readTemporalField, resolveTemporalUnit, validateSemanticFieldType } from "../../grammar/scales/index.js";
6
+ import { normalizePositionDatum } from "../../grammar/positionDatum.js";
7
+ import { normalizeGroupFields } from "../../grammar/pathSeries.js";
8
+ import { assertPathGroupCompatible, validatePathGroupAppearance } from "../../materialization/marks/grouping.js";
9
+ import { canMaterializeArea, canMaterializeLine, getPositionEncodingMaterializationSteps } from "../../materialization/marks/index.js";
17
10
  import { findLayer } from "../../selectors/layers.js";
18
- import {
19
- resolveTarget,
20
- setEncodingProperties,
21
- validateLineSeriesCompatibility,
22
- validateOptions
23
- } from "./shared.js";
11
+ import { resolveTarget, setEncodingProperties, validateOptions } from "./shared.js";
12
+ import { applyTemporalUnit } from "./temporal.js";
13
+ import { deriveAreaSeries } from "../../grammar/areaSeries.js";
14
+ import { resolvePositionEncoding } from "./position/resolve.js";
15
+ import { resolveScalePreview } from "../scales/preview.js";
16
+ import { findUpstreamTransform } from "../../materialization/dataProvenance.js";
24
17
 
25
18
  const SECONDARY_OPTIONS = Object.freeze([
26
- "field", "datum", "target", "fieldType", "scale", "coordinate"
19
+ "field", "datum", "target", "fieldType", "scale", "coordinate", "temporalUnit"
27
20
  ]);
28
21
  const RANGE_OPTIONS = Object.freeze([
29
- "lower", "upper", "target", "fieldType", "coordinate", "scale"
22
+ "lower", "upper", "target", "fieldType", "coordinate", "scale", "temporalUnit"
30
23
  ]);
31
- const GROUP_OPTIONS = Object.freeze(["field", "target", "fieldType"]);
24
+ const GROUP_OPTIONS = Object.freeze(["field", "fields", "target", "fieldType"]);
32
25
 
33
26
  function validateSecondaryScale(args, primaryScale, axisLabel) {
34
27
  if (args.scale === undefined) return;
@@ -41,11 +34,11 @@ function validateSecondaryScale(args, primaryScale, axisLabel) {
41
34
  }
42
35
  }
43
36
 
44
- function validateSecondaryField(dataset, field, fieldType) {
37
+ function validateSecondaryField(dataset, field, fieldType, temporalUnit) {
45
38
  if (["nominal", "ordinal"].includes(fieldType)) {
46
39
  readNominalField(dataset.values, field);
47
40
  } else if (fieldType === "temporal") {
48
- readTemporalField(dataset.values, field);
41
+ readTemporalField(dataset.values, field, temporalUnit);
49
42
  } else {
50
43
  readQuantitativeField(dataset.values, field);
51
44
  }
@@ -75,12 +68,14 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
75
68
  validateSecondaryScale(args, primary.scale, primaryChannel);
76
69
 
77
70
  const rule = layer.mark.type === "rule";
71
+ const area = layer.mark.type === "area";
72
+ const rect = layer.mark.type === "rect";
78
73
  const hasField = Object.hasOwn(args, "field");
79
74
  const hasDatum = Object.hasOwn(args, "datum");
80
- if (rule && hasField === hasDatum) {
81
- throw new Error(`${operation} requires exactly one of field or datum for a rule mark.`);
75
+ if ((rule || area || rect) && hasField === hasDatum) {
76
+ throw new Error(`${operation} requires exactly one of field or datum for a ${layer.mark.type} mark.`);
82
77
  }
83
- if (!rule && (!hasField || hasDatum)) {
78
+ if (!rule && !area && !rect && (!hasField || hasDatum)) {
84
79
  throw new Error(`${operation} requires a field for a ranged mark.`);
85
80
  }
86
81
  if (rule && args.fieldType === undefined) {
@@ -114,14 +109,20 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
114
109
  `${operation} fieldType must match the primary ${primaryChannel} fieldType.`
115
110
  );
116
111
  }
117
- if (hasField && layer.mark.type === "rect") {
118
- readScaleField(dataset.values, args.field, fieldType, { allowUnknown: true });
119
- } else if (hasField) validateSecondaryField(dataset, args.field, fieldType);
120
- else normalizeRuleDatum(args.datum, fieldType, channel);
121
-
122
112
  const previous = layer.encoding?.[channel];
113
+ const temporalUnit = resolveTemporalUnit(args, fieldType, previous);
114
+ if (area) {
115
+ const endpoint = { ...(hasField ? { field: args.field } : { datum: args.datum }), fieldType };
116
+ validateAreaEndpointPair(primary, endpoint);
117
+ readAreaEndpoint(dataset.values, endpoint, layer.mark.missing);
118
+ } else if (hasField && layer.mark.type === "rect") {
119
+ readScaleField(dataset.values, args.field, fieldType, { allowUnknown: true, temporalUnit });
120
+ } else if (hasField) validateSecondaryField(dataset, args.field, fieldType, temporalUnit);
121
+ else normalizePositionDatum(args.datum, fieldType, channel, temporalUnit, rule ? "Rule" : "Rect");
122
+
123
123
  let next = program;
124
124
  if (layer.mark.type === "bar") {
125
+ if (layer.layout?.mode !== undefined && layer.layout.mode !== "overlay") throw new Error("Ranged bars support only overlay layout; clear series layout before assigning endpoints.");
125
126
  for (const property of ["aggregate", "stack", "bin"]) {
126
127
  if (layer.encoding?.[primaryChannel]?.[property] !== undefined) {
127
128
  next = next.editSemantic({
@@ -146,6 +147,7 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
146
147
  scale: primary.scale
147
148
  });
148
149
 
150
+ next = applyTemporalUnit(next, target, channel, temporalUnit, previous);
149
151
  const updated = findLayer(next, target);
150
152
  for (const step of getPositionEncodingMaterializationSteps(
151
153
  next,
@@ -189,91 +191,91 @@ const encodeY2 = action(
189
191
  }
190
192
  );
191
193
 
192
- const encodeYRange = action(
193
- {
194
- op: "encodeYRange",
195
- description: "Atomically encode lower and upper area bounds."
196
- },
197
- function (args = {}) {
198
- validateOptions(args, RANGE_OPTIONS, "encodeYRange");
199
- const target = args.target;
200
- const lower = this.encodeY({
201
- field: args.lower,
202
- ...(target === undefined ? {} : { target }),
203
- fieldType: args.fieldType ?? "quantitative",
204
- ...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
205
- ...(args.scale === undefined ? {} : { scale: args.scale })
206
- });
207
- return lower.encodeY2({
208
- field: args.upper,
209
- ...(target === undefined ? {} : { target }),
194
+ function rangeAction(channel) {
195
+ const primary = channel === "x" ? "encodeX" : "encodeY";
196
+ const secondary = channel === "x" ? "encodeX2" : "encodeY2";
197
+ const op = channel === "x" ? "encodeXRange" : "encodeYRange";
198
+ return action({ op, description: "Atomically encode lower and upper bounds." }, function (args = {}) {
199
+ validateOptions(args, RANGE_OPTIONS, op);
200
+ const common = {
201
+ ...(args.target === undefined ? {} : { target: args.target }),
202
+ ...(Object.hasOwn(args, "temporalUnit") ? { temporalUnit: args.temporalUnit } : {}),
210
203
  fieldType: args.fieldType ?? "quantitative"
211
- });
212
- }
213
- );
214
-
215
- const encodeXRange = action(
216
- {
217
- op: "encodeXRange",
218
- description: "Atomically encode lower and upper horizontal area bounds."
219
- },
220
- function (args = {}) {
221
- validateOptions(args, RANGE_OPTIONS, "encodeXRange");
222
- const target = args.target;
223
- const lower = this.encodeX({
224
- field: args.lower,
225
- ...(target === undefined ? {} : { target }),
226
- fieldType: args.fieldType ?? "quantitative",
204
+ };
205
+ const { layer, dataset } = resolveTarget(this, args.target, ["area", "bar", "rect", "rule"], "ranged mark");
206
+ let next = this;
207
+ let lower = { field: args.lower }, upper = { field: args.upper };
208
+ if (layer.mark.type === "area") {
209
+ common.target = layer.id;
210
+ const prepared = prepareAreaRange(this, layer, dataset, channel, args, common);
211
+ lower = prepared.lower; upper = prepared.upper;
212
+ const secondaryChannel = `${channel}2`;
213
+ if (layer.encoding?.[secondaryChannel] !== undefined) next = next.editSemantic({ property: `layer[${layer.id}].encoding.${secondaryChannel}`, remove: true });
214
+ next = setEncodingProperties(next, layer.id, secondaryChannel, prepared.secondary);
215
+ common.target = layer.id;
216
+ }
217
+ return next[primary]({
218
+ ...common, ...lower,
227
219
  ...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
228
220
  ...(args.scale === undefined ? {} : { scale: args.scale })
229
- });
230
- return lower.encodeX2({
231
- field: args.upper,
232
- ...(target === undefined ? {} : { target }),
233
- fieldType: args.fieldType ?? "quantitative"
234
- });
235
- }
236
- );
221
+ })[secondary]({ ...common, ...upper });
222
+ });
223
+ }
224
+
225
+ const encodeYRange = rangeAction("y");
226
+ const encodeXRange = rangeAction("x");
237
227
 
238
228
  const encodeGroup = action(
239
229
  {
240
230
  op: "encodeGroup",
241
- description: "Split path geometry by a nominal field without a scale."
231
+ description: "Split path geometry by nominal identity fields without a scale."
242
232
  },
243
233
  function (args = {}) {
244
234
  validateOptions(args, GROUP_OPTIONS, "encodeGroup");
245
235
  const { id: target, dataset, layer } = resolveTarget(
246
236
  this,
247
237
  args.target,
248
- ["line", "area"],
249
- "path mark"
238
+ ["line", "area", "bar"],
239
+ "series mark"
250
240
  );
251
241
  if ((args.fieldType ?? "nominal") !== "nominal") {
252
242
  throw new Error("encodeGroup requires a nominal field.");
253
243
  }
254
- const densityTransform = dataset.transform?.length === 1 &&
255
- dataset.transform[0].type === "density"
256
- ? dataset.transform[0]
257
- : undefined;
258
- if (densityTransform !== undefined && densityTransform.groupBy !== args.field) {
259
- throw new Error(
260
- densityTransform.groupBy === undefined
261
- ? `Ungrouped density area mark "${target}" cannot encode group.`
262
- : `Density area mark "${target}" must group by "${densityTransform.groupBy}".`
263
- );
244
+ const hasFields = Object.hasOwn(args, "fields");
245
+ if (hasFields === Object.hasOwn(args, "field")) {
246
+ throw new Error("encodeGroup requires exactly one of field or fields.");
264
247
  }
265
- readNominalField(dataset.values, args.field);
266
- validateLineSeriesCompatibility(layer, "group", args.field);
267
- const next = this
268
- .editSemantic({
269
- property: `layer[${target}].encoding.group.field`,
270
- value: args.field
271
- })
272
- .editSemantic({
273
- property: `layer[${target}].encoding.group.fieldType`,
274
- value: "nominal"
248
+ if (hasFields ? !Array.isArray(args.fields) : typeof args.field !== "string") {
249
+ throw new TypeError("encodeGroup field must be a string and fields must be an array.");
250
+ }
251
+ const fields = normalizeGroupFields(hasFields ? args.fields : args.field);
252
+ const group = {
253
+ ...(fields.length === 1 ? { field: fields[0] } : { fields }),
254
+ fieldType: "nominal"
255
+ };
256
+ assertPathGroupCompatible(this, layer, dataset, group);
257
+ for (const field of fields) readNominalField(dataset.values, field);
258
+ if (layer.mark.type !== "bar") validatePathGroupAppearance(this, {
259
+ ...layer, encoding: { ...layer.encoding, group }
260
+ }, dataset);
261
+ let next = this;
262
+ const alternate = fields.length === 1 ? "fields" : "field";
263
+ if (layer.encoding?.group?.[alternate] !== undefined) {
264
+ next = next.editSemantic({
265
+ property: `layer[${target}].encoding.group.${alternate}`, remove: true
275
266
  });
267
+ }
268
+ if (layer.encoding?.group?.inferredFrom !== undefined) next = next.editSemantic({ property: `layer[${target}].encoding.group.inferredFrom`, remove: true });
269
+ next = setEncodingProperties(next, target, "group", group);
270
+ if (layer.mark.type === "bar") {
271
+ const mode = layer.layout?.mode;
272
+ if (mode !== undefined) return next.layoutSeries({ target, mode });
273
+ if (layer.encoding?.x?.scale === undefined || layer.encoding?.y?.scale === undefined) return next;
274
+ for (const step of getPositionEncodingMaterializationSteps(next, findLayer(next, target), layer.encoding?.y?.scale)) next = next[step.op](step.args);
275
+ return next;
276
+ }
276
277
  if (layer.mark.type === "area") {
278
+ if (layer.layout?.mode !== undefined) return next.layoutSeries({ target, mode: layer.layout.mode });
277
279
  const updated = findLayer(next, target);
278
280
  return canMaterializeArea(next, updated)
279
281
  ? next.rematerializeAreaMark({ id: target })
@@ -297,3 +299,27 @@ export function registerRangedEncodingActions(ProgramClass) {
297
299
  ProgramClass.prototype.encodeXRange = encodeXRange;
298
300
  ProgramClass.prototype.encodeYRange = encodeYRange;
299
301
  }
302
+
303
+ export function prepareAreaRange(program, layer, dataset, channel, args, common) {
304
+ const lower = normalizeAreaBound(args.lower), upper = normalizeAreaBound(args.upper);
305
+ validateAreaEndpointPair(lower, upper);
306
+ if (common.fieldType !== "quantitative") throw new Error("Area range requires quantitative endpoints.");
307
+ readAreaEndpoint(dataset.values, { ...upper, fieldType: "quantitative" }, layer.mark.missing);
308
+ const resolved = resolvePositionEncoding(program, channel, { ...common, ...lower,
309
+ ...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
310
+ ...(args.scale === undefined ? {} : { scale: args.scale }) }, `encode${channel.toUpperCase()}Range`);
311
+ const secondary = { ...upper, fieldType: "quantitative", scale: resolved.scale.id };
312
+ const primary = { ...lower, fieldType: "quantitative", scale: resolved.scale.id };
313
+ const updated = { ...layer, coordinate: resolved.coordinate.id,
314
+ encoding: { ...layer.encoding, [channel]: primary, [`${channel}2`]: secondary } };
315
+ const scales = program.semanticSpec.scales.filter(scale => scale.id !== resolved.scale.id);
316
+ const preview = { ...program, markConfigs: program.markConfigs, canvasConfig: program.canvasConfig,
317
+ semanticSpec: { ...program.semanticSpec,
318
+ layers: program.semanticSpec.layers.map(item => item.id === layer.id ? updated : item),
319
+ scales: [...scales, resolved.scale] } };
320
+ resolveScalePreview(preview, resolved.scale.id);
321
+ if (canMaterializeArea(preview, updated) && !findUpstreamTransform(program, dataset, "density")) {
322
+ deriveAreaSeries(dataset.values, updated);
323
+ }
324
+ return { lower, upper, secondary };
325
+ }