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
@@ -0,0 +1,212 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateOptionObject } from "../../core/validation.js";
4
+ import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
5
+ import { findLayer } from "../../selectors/layers.js";
6
+ import { requireSemanticScale } from "../../selectors/scales.js";
7
+ import { findScaleConsumers } from "./consumers/index.js";
8
+
9
+ const DEFINITIONS = Object.freeze({
10
+ editXScale: Object.freeze({
11
+ channel: "x",
12
+ options: Object.freeze([
13
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
14
+ "base", "exponent", "constant", "paddingInner", "paddingOuter",
15
+ "padding", "align", "unknown"
16
+ ])
17
+ }),
18
+ editYScale: Object.freeze({
19
+ channel: "y",
20
+ options: Object.freeze([
21
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
22
+ "base", "exponent", "constant", "paddingInner", "paddingOuter",
23
+ "padding", "align", "unknown"
24
+ ])
25
+ }),
26
+ editThetaScale: Object.freeze({
27
+ channel: "theta",
28
+ options: Object.freeze([
29
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
30
+ "paddingInner", "paddingOuter", "padding", "align"
31
+ ])
32
+ }),
33
+ editRScale: Object.freeze({
34
+ channel: "radius",
35
+ options: Object.freeze([
36
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
37
+ "base", "exponent", "constant", "radialMapping"
38
+ ])
39
+ }),
40
+ editColorScale: Object.freeze({
41
+ channel: "color",
42
+ options: Object.freeze([
43
+ "type", "domain", "range", "clamp", "reverse", "palette",
44
+ "interpolate", "midpoint", "unknown"
45
+ ])
46
+ }),
47
+ editStrokeScale: Object.freeze({
48
+ channel: "stroke",
49
+ targetOnly: true,
50
+ options: Object.freeze([
51
+ "type", "domain", "range", "clamp", "reverse", "palette",
52
+ "interpolate", "midpoint", "unknown"
53
+ ])
54
+ }),
55
+ editSizeScale: Object.freeze({
56
+ channel: "size",
57
+ options: Object.freeze([
58
+ "type", "domain", "range", "unknown", "clamp", "reverse", "base",
59
+ "exponent"
60
+ ])
61
+ }),
62
+ editOpacityScale: Object.freeze({
63
+ channel: "opacity",
64
+ options: Object.freeze([
65
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse", "unknown"
66
+ ])
67
+ }),
68
+ editShapeScale: Object.freeze({
69
+ channel: "shape",
70
+ options: Object.freeze(["type", "domain", "range", "unknown"])
71
+ }),
72
+ editStrokeWidthScale: Object.freeze({
73
+ channel: "strokeWidth",
74
+ options: Object.freeze([
75
+ "type", "domain", "range", "nice", "zero", "clamp", "reverse",
76
+ "base", "exponent", "constant"
77
+ ])
78
+ }),
79
+ editStrokeDashScale: Object.freeze({
80
+ channel: "strokeDash",
81
+ options: Object.freeze(["type", "domain", "range"])
82
+ })
83
+ });
84
+
85
+ function scaleIdsForLayer(program, layer, channel) {
86
+ if (layer === undefined) return [];
87
+ return [...new Set(allConsumers(program)
88
+ .filter(consumer =>
89
+ consumer.layer.id === layer.id &&
90
+ normalizePositionScaleChannel(consumer.channel) === channel
91
+ )
92
+ .map(consumer => consumer.encoding.scale))];
93
+ }
94
+
95
+ function allConsumers(program) {
96
+ return program.semanticSpec.scales.flatMap(scale =>
97
+ findScaleConsumers(program, scale.id)
98
+ );
99
+ }
100
+
101
+ function channelScaleIds(program, channel) {
102
+ return [...new Set(allConsumers(program)
103
+ .filter(consumer => normalizePositionScaleChannel(consumer.channel) === channel)
104
+ .map(consumer => consumer.encoding.scale))];
105
+ }
106
+
107
+ function requireSingleScale(ids, operation, scope) {
108
+ if (ids.length === 1) return ids[0];
109
+ if (ids.length === 0) {
110
+ throw new Error(`${operation} could not find a ${scope} scale.`);
111
+ }
112
+ throw new Error(`${operation} ${scope} scale is ambiguous; provide id or target.`);
113
+ }
114
+
115
+ function validateScaleChannel(program, id, channel, operation) {
116
+ requireSemanticScale(program, id);
117
+ const consumers = findScaleConsumers(program, id);
118
+ if (consumers.length === 0) {
119
+ throw new Error(`${operation} requires a scale bound to the ${channel} channel.`);
120
+ }
121
+ const compatible = ["color", "stroke"].includes(channel)
122
+ ? new Set(["color", "stroke"])
123
+ : new Set([channel]);
124
+ if (!consumers.some(consumer =>
125
+ normalizePositionScaleChannel(consumer.channel) === channel
126
+ ) || consumers.some(consumer =>
127
+ !compatible.has(normalizePositionScaleChannel(consumer.channel))
128
+ )) {
129
+ throw new Error(
130
+ `Scale "${id}" must include a ${channel} channel consumer and no incompatible channel consumers.`
131
+ );
132
+ }
133
+ return id;
134
+ }
135
+
136
+ function resolveScaleId(program, args, channel, operation) {
137
+ const explicitId = args.id === undefined
138
+ ? undefined
139
+ : validateUserId(args.id, "Scale id");
140
+ const target = args.target === undefined
141
+ ? undefined
142
+ : validateUserId(args.target, "Mark id");
143
+ const targetLayer = target === undefined ? undefined : findLayer(program, target);
144
+ if (target !== undefined && targetLayer === undefined) {
145
+ throw new Error(`Unknown mark target "${target}".`);
146
+ }
147
+ const targetId = targetLayer === undefined
148
+ ? undefined
149
+ : requireSingleScale(scaleIdsForLayer(program, targetLayer, channel), operation, `target ${channel}`);
150
+ if (explicitId !== undefined && targetId !== undefined && explicitId !== targetId) {
151
+ throw new Error(`${operation} id "${explicitId}" does not match target "${target}" scale "${targetId}".`);
152
+ }
153
+ if (explicitId !== undefined) {
154
+ return validateScaleChannel(program, explicitId, channel, operation);
155
+ }
156
+ if (targetId !== undefined) return validateScaleChannel(program, targetId, channel, operation);
157
+
158
+ const current = findLayer(program, program.context.currentMark);
159
+ const currentIds = scaleIdsForLayer(program, current, channel);
160
+ if (currentIds.length > 1) {
161
+ throw new Error(`${operation} current-mark ${channel} scale is ambiguous; provide id or target.`);
162
+ }
163
+ if (currentIds.length === 1) {
164
+ return validateScaleChannel(program, currentIds[0], channel, operation);
165
+ }
166
+ return validateScaleChannel(
167
+ program,
168
+ requireSingleScale(channelScaleIds(program, channel), operation, channel),
169
+ channel,
170
+ operation
171
+ );
172
+ }
173
+
174
+ function createChannelScaleEditor(operation, definition) {
175
+ return action(
176
+ {
177
+ op: operation,
178
+ description: `Edit the scale bound to the ${definition.channel} channel.`
179
+ },
180
+ function (args = {}) {
181
+ validateOptionObject(
182
+ args,
183
+ [...(definition.targetOnly === true ? [] : ["id"]), "target", ...definition.options],
184
+ operation
185
+ );
186
+ if (definition.targetOnly === true && args.target === undefined) {
187
+ throw new Error(`${operation} requires target.`);
188
+ }
189
+ if (!definition.options.some(property => Object.hasOwn(args, property))) {
190
+ throw new Error(`${operation} requires at least one editable property.`);
191
+ }
192
+ const id = resolveScaleId(this, args, definition.channel, operation);
193
+ const patch = Object.fromEntries(
194
+ Object.entries(args).filter(([property]) => property !== "target")
195
+ );
196
+ return this.editScale({ ...patch, id });
197
+ }
198
+ );
199
+ }
200
+
201
+ export const channelScaleEditors = Object.freeze(Object.fromEntries(
202
+ Object.entries(DEFINITIONS).map(([operation, definition]) => [
203
+ operation,
204
+ createChannelScaleEditor(operation, definition)
205
+ ])
206
+ ));
207
+
208
+ export function registerChannelScaleActions(ProgramClass) {
209
+ for (const [operation, editor] of Object.entries(channelScaleEditors)) {
210
+ ProgramClass.prototype[operation] = editor;
211
+ }
212
+ }
@@ -1,9 +1,7 @@
1
- import {
2
- readNominalField,
3
- readQuantitativeField,
4
- readScaleField,
5
- readTemporalField
6
- } from "../../../grammar/scales/index.js";
1
+ import { isSourceOwnedText } from "../../../grammar/text.js";
2
+ import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
3
+ import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
4
+ import { readScaleField } from "../../../grammar/scales/index.js";
7
5
  import { findDataset } from "../../../selectors/datasets.js";
8
6
  import { requireSemanticScale } from "../../../selectors/scales.js";
9
7
  import { SCALED_ENCODING_CHANNELS } from "../../../core/vocabulary.js";
@@ -15,6 +13,7 @@ export function findScale(program, id) {
15
13
  export function findScaleConsumers(program, id) {
16
14
  const consumers = [];
17
15
  for (const layer of program.semanticSpec.layers) {
16
+ if (isSourceOwnedText(layer)) continue;
18
17
  for (const channel of SCALED_ENCODING_CHANNELS) {
19
18
  const encoding = layer.encoding?.[channel];
20
19
  if (encoding?.scale === id) consumers.push({ layer, channel, encoding });
@@ -44,9 +43,9 @@ export function requireConsumerDataset(program, consumer) {
44
43
  }
45
44
 
46
45
  export function isDirectCategoricalConsumer(consumer) {
47
- return ["color", "strokeDash", "xOffset", "yOffset", "shape"].includes(
46
+ return ["color", "stroke", "strokeDash", "xOffset", "yOffset", "shape"].includes(
48
47
  consumer.channel
49
- ) && consumer.encoding.fieldType === "nominal";
48
+ ) && ["nominal", "ordinal"].includes(consumer.encoding.fieldType);
50
49
  }
51
50
 
52
51
  export function readConsumerFieldValues(
@@ -55,55 +54,23 @@ export function readConsumerFieldValues(
55
54
  dataset,
56
55
  scale = findScale(program, consumer.encoding.scale)
57
56
  ) {
58
- const allowUnknown = Object.hasOwn(scale, "unknown");
59
- if (consumer.role === "parallelDimension") {
60
- return readScaleField(
61
- dataset.values,
62
- consumer.encoding.field,
63
- consumer.encoding.fieldType,
64
- { allowUnknown: true }
65
- );
66
- }
67
- if (isDirectCategoricalConsumer(consumer)) {
68
- return allowUnknown
69
- ? readScaleField(
70
- dataset.values,
71
- consumer.encoding.field,
72
- consumer.encoding.fieldType,
73
- { allowUnknown: true }
74
- )
75
- : readNominalField(dataset.values, consumer.encoding.field);
76
- }
77
- if (consumer.encoding.fieldType === "temporal") {
78
- return allowUnknown
79
- ? readScaleField(dataset.values, consumer.encoding.field, "temporal", {
80
- allowUnknown: true
81
- })
82
- : readTemporalField(dataset.values, consumer.encoding.field);
57
+ const { field, fieldType, temporalUnit } = consumer.encoding;
58
+ if (consumer.layer.mark.type === "bar" && consumer.layer.encoding?.group !== undefined && ["xOffset", "yOffset"].includes(consumer.channel)) {
59
+ return readSeriesIdentity(dataset.values, consumer.layer).values;
83
60
  }
84
- if (["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
85
- if (!["ordinal", "band", "point"].includes(scale.type)) {
86
- throw new Error(
87
- `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
88
- );
89
- }
90
- return allowUnknown
91
- ? readScaleField(
92
- dataset.values,
93
- consumer.encoding.field,
94
- consumer.encoding.fieldType,
95
- { allowUnknown: true }
96
- )
97
- : readNominalField(dataset.values, consumer.encoding.field);
98
- }
99
- if (consumer.encoding.fieldType !== "quantitative") {
61
+ const parallel = consumer.role === "parallelDimension";
62
+ if (!parallel && !isDirectCategoricalConsumer(consumer) &&
63
+ fieldType !== "quantitative" && fieldType !== "temporal" &&
64
+ (!["nominal", "ordinal"].includes(fieldType) ||
65
+ !["ordinal", "band", "point"].includes(scale.type))) {
100
66
  throw new Error(
101
67
  `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
102
68
  );
103
69
  }
104
- return allowUnknown
105
- ? readScaleField(dataset.values, consumer.encoding.field, "quantitative", {
106
- allowUnknown: true
107
- })
108
- : readQuantitativeField(dataset.values, consumer.encoding.field);
70
+ if (consumer.layer.mark.type === "area" && ["x", "y", "x2", "y2"].includes(consumer.channel) && fieldType === "quantitative") {
71
+ return readAreaEndpoint(dataset.values, consumer.encoding, consumer.layer.mark.missing).filter(value => value != null);
72
+ }
73
+ return readScaleField(dataset.values, field, fieldType, {
74
+ allowUnknown: parallel || Object.hasOwn(scale, "unknown"), temporalUnit
75
+ });
109
76
  }
@@ -1,3 +1,5 @@
1
+ import { isPendingMeasuredRadiusConsumer } from "../../../materialization/scales/policies/arc.js";
2
+ import { deriveMeasuredArcValues } from "../../../grammar/arcs.js";
1
3
  import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
4
  import {
3
5
  BAR_GRAINS,
@@ -5,9 +7,9 @@ import {
5
7
  } from "../../../grammar/bars/policy.js";
6
8
  import {
7
9
  deriveLineSeries,
8
- deriveLineSeriesFieldValues,
9
10
  resolveLineBins
10
11
  } from "../../../grammar/lineSeries.js";
12
+ import { derivePathSeriesFieldValues } from "../../../grammar/pathSeries.js";
11
13
  import { isAggregate } from "../../../grammar/aggregate.js";
12
14
  import {
13
15
  resolveRectConsumerValues
@@ -26,6 +28,11 @@ function lineDerivationOptions(program, consumer, dataset) {
26
28
  }
27
29
 
28
30
  export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
31
+ if (consumer.layer.mark?.type === "arc" && consumer.channel === "radius" &&
32
+ findScale(program, consumer.encoding.scale).radialMapping !== undefined) {
33
+ return { matched: true, values: isPendingMeasuredRadiusConsumer(consumer) ? [] : deriveMeasuredArcValues(dataset.values, consumer.layer).map(item => item.radius) };
34
+ }
35
+
29
36
  if (consumer.layer.mark?.type === "rect") {
30
37
  return {
31
38
  matched: true,
@@ -36,29 +43,36 @@ export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
36
43
  )
37
44
  };
38
45
  }
46
+ const { layer, channel } = consumer;
47
+ const encoding = layer.encoding ?? {};
48
+ const appearance = ["stroke", "strokeWidth", "opacity"].includes(channel);
39
49
  if (
40
- consumer.layer.mark?.type === "line" &&
41
- ((consumer.layer.encoding?.x !== undefined &&
42
- isAggregate(consumer.layer.encoding?.y?.aggregate) &&
43
- !(consumer.channel === "x" &&
44
- consumer.layer.encoding?.x?.bin !== undefined)) ||
45
- consumer.channel === "strokeWidth")
50
+ layer.mark?.type === "line" &&
51
+ ((["x", "y"].includes(channel) && encoding.x !== undefined &&
52
+ isAggregate(encoding.y?.aggregate) &&
53
+ !(channel === "x" &&
54
+ encoding.x?.bin !== undefined)) ||
55
+ appearance) &&
56
+ encoding.parallel === undefined &&
57
+ ((encoding.x !== undefined && encoding.y !== undefined) ||
58
+ (encoding.theta !== undefined && encoding.radius !== undefined))
46
59
  ) {
47
60
  const derived = deriveLineSeries(
48
61
  dataset.values,
49
- consumer.layer,
62
+ layer,
50
63
  lineDerivationOptions(program, consumer, dataset)
51
64
  );
52
65
  return {
53
66
  matched: true,
54
- values: consumer.channel === "strokeWidth"
55
- ? deriveLineSeriesFieldValues(
67
+ values: appearance
68
+ ? derivePathSeriesFieldValues(
56
69
  dataset.values,
57
- consumer.layer,
58
- derived,
59
- consumer.encoding.field
70
+ derived.series,
71
+ consumer.encoding.field,
72
+ channel,
73
+ consumer.encoding
60
74
  )
61
- : consumer.channel === "x" ? derived.xValues : derived.yValues
75
+ : channel === "x" ? derived.xValues : derived.yValues
62
76
  };
63
77
  }
64
78
  if (resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate) {
@@ -1,4 +1,6 @@
1
+ import { readAreaEndpoint } from "../../../grammar/areaEndpoints.js";
1
2
  import { normalizeRuleDatum } from "../../../grammar/rules.js";
3
+ import { normalizePositionDatum } from "../../../grammar/positionDatum.js";
2
4
  import {
3
5
  findScale,
4
6
  isDirectCategoricalConsumer,
@@ -6,7 +8,9 @@ import {
6
8
  requireConsumerDataset
7
9
  } from "./common.js";
8
10
  import { resolveMarkFamilyConsumerValues } from "./families.js";
9
- import { resolveCategoryOrder } from "../../../grammar/categoryOrder.js";
11
+ import { CATEGORY_ORDER_CHANNELS, resolveCategoryOrder } from "../../../grammar/categoryOrder.js";
12
+ import { resolveHistogramInput } from
13
+ "../../../materialization/bars/histogram.js";
10
14
 
11
15
  export { findScale, findScaleConsumers } from "./common.js";
12
16
  export {
@@ -14,13 +18,27 @@ export {
14
18
  resolveSeriesLayoutScaleValues
15
19
  } from "./seriesLayout.js";
16
20
 
17
- export function resolveConsumerValues(program, consumer) {
18
- const dataset = requireConsumerDataset(program, consumer);
21
+ export function resolveConsumerValuesForDataset(program, consumer, dataset) {
22
+ if (consumer.layer.mark.type === "rect") return resolveMarkFamilyConsumerValues(program, consumer, dataset).values;
19
23
  if (Object.hasOwn(consumer.encoding, "datum")) {
24
+ if (consumer.layer.mark.type === "area") {
25
+ readAreaEndpoint(dataset.values, consumer.encoding, consumer.layer.mark.missing);
26
+ return [consumer.encoding.datum];
27
+ }
28
+ if (consumer.layer.mark.type === "text") {
29
+ return [normalizePositionDatum(
30
+ consumer.encoding.datum,
31
+ consumer.encoding.fieldType,
32
+ consumer.channel,
33
+ consumer.encoding.temporalUnit,
34
+ "Text"
35
+ )];
36
+ }
20
37
  return [normalizeRuleDatum(
21
38
  consumer.encoding.datum,
22
39
  consumer.encoding.fieldType,
23
- consumer.channel
40
+ consumer.channel,
41
+ consumer.encoding.temporalUnit
24
42
  )];
25
43
  }
26
44
  if (
@@ -32,6 +50,14 @@ export function resolveConsumerValues(program, consumer) {
32
50
  );
33
51
  }
34
52
  const scale = findScale(program, consumer.encoding.scale);
53
+ if (
54
+ consumer.layer.mark.type === "bar" &&
55
+ consumer.channel === "x" &&
56
+ consumer.encoding.bin !== undefined &&
57
+ consumer.encoding.weight !== undefined
58
+ ) {
59
+ return resolveHistogramInput(dataset, consumer.encoding).values;
60
+ }
35
61
  if (isDirectCategoricalConsumer(consumer)) {
36
62
  return readConsumerFieldValues(program, consumer, dataset, scale);
37
63
  }
@@ -41,9 +67,21 @@ export function resolveConsumerValues(program, consumer) {
41
67
  : readConsumerFieldValues(program, consumer, dataset, scale);
42
68
  }
43
69
 
70
+ export function resolveConsumerValues(program, consumer) {
71
+ return resolveConsumerValuesForDataset(
72
+ program,
73
+ consumer,
74
+ requireConsumerDataset(program, consumer)
75
+ );
76
+ }
77
+
44
78
  export function resolveConsumerCategoryOrder(program, consumer) {
45
79
  const order = consumer.encoding.categoryOrder;
46
80
  if (order === undefined) return undefined;
81
+ if (!CATEGORY_ORDER_CHANNELS.includes(consumer.channel) ||
82
+ !["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
83
+ throw new Error("Remove category order before assigning a non-categorical position.");
84
+ }
47
85
  const dataset = requireConsumerDataset(program, consumer);
48
86
  return resolveCategoryOrder(dataset.values, consumer.encoding.field, order);
49
87
  }
@@ -1,3 +1,5 @@
1
+ import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
2
+ import { deriveAreaSeries } from "../../../grammar/areaSeries.js";
1
3
  import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
4
  import { validateGeneratedItemLimit } from "../../../core/validation.js";
3
5
  import {
@@ -7,7 +9,6 @@ import {
7
9
  resolveBarGrain
8
10
  } from "../../../grammar/bars/policy.js";
9
11
  import {
10
- countHistogramBins,
11
12
  findHistogramBinIndex,
12
13
  resolveHistogramBins
13
14
  } from "../../../grammar/histogram.js";
@@ -20,11 +21,12 @@ import {
20
21
  } from "../../../grammar/seriesLayout.js";
21
22
  import {
22
23
  readNominalField,
23
- readQuantitativeField,
24
24
  resolveOrdinalDomain
25
25
  } from "../../../grammar/scales/index.js";
26
26
  import { findDataset } from "../../../selectors/datasets.js";
27
27
  import { findScale } from "./common.js";
28
+ import { resolveHistogramInput } from
29
+ "../../../materialization/bars/histogram.js";
28
30
 
29
31
  export function resolveHistogramCountValues(program, consumer) {
30
32
  const xEncoding = consumer.layer.encoding?.x;
@@ -42,7 +44,8 @@ export function resolveHistogramCountValues(program, consumer) {
42
44
  `Histogram mark "${consumer.layer.id}" requires an existing dataset.`
43
45
  );
44
46
  }
45
- const xValues = readQuantitativeField(dataset.values, xEncoding.field);
47
+ const input = resolveHistogramInput(dataset, xEncoding);
48
+ const xValues = input.values;
46
49
  const bins = resolveHistogramBins({
47
50
  values: xValues,
48
51
  bin: xEncoding.bin,
@@ -50,7 +53,12 @@ export function resolveHistogramCountValues(program, consumer) {
50
53
  nice: xScale.nice ?? true,
51
54
  zero: xScale.zero ?? false
52
55
  });
53
- return countHistogramBins(xValues, bins.boundaries);
56
+ const cells = bins.boundaries.slice(0, -1).map(() => []);
57
+ xValues.forEach((value, index) => {
58
+ const bin = findHistogramBinIndex(value, bins.boundaries);
59
+ if (bin !== -1) cells[bin].push(input.entries[index]);
60
+ });
61
+ return cells.map(input.mass);
54
62
  }
55
63
 
56
64
  function resolveHistogramPartitions(program, consumer) {
@@ -58,7 +66,8 @@ function resolveHistogramPartitions(program, consumer) {
58
66
  const dataset = findDataset(program, layer.data);
59
67
  const xEncoding = layer.encoding.x;
60
68
  const xScale = findScale(program, xEncoding.scale);
61
- const xValues = readQuantitativeField(dataset.values, xEncoding.field);
69
+ const input = resolveHistogramInput(dataset, xEncoding);
70
+ const xValues = input.values;
62
71
  const bins = resolveHistogramBins({
63
72
  values: xValues,
64
73
  bin: xEncoding.bin,
@@ -67,11 +76,25 @@ function resolveHistogramPartitions(program, consumer) {
67
76
  zero: xScale.zero ?? false
68
77
  });
69
78
  const colorEncoding = layer.encoding?.color;
79
+ if (layer.encoding?.group !== undefined) {
80
+ const identity = readSeriesIdentity(input.rows, layer);
81
+ validateGeneratedItemLimit((bins.boundaries.length - 1) * identity.domain.length, "Histogram layout cell count");
82
+ const cells = bins.boundaries.slice(0, -1).map(() => identity.domain.map(() => []));
83
+ const groups = new Map(identity.domain.map((value, index) => [value, index]));
84
+ xValues.forEach((value, index) => { const bin = findHistogramBinIndex(value, bins.boundaries);
85
+ if (bin !== -1) cells[bin][groups.get(identity.values[index])].push(input.entries[index]); });
86
+ return cells.map(partition => partition.map(input.mass));
87
+ }
70
88
  if (colorEncoding?.scale === undefined) {
71
- return countHistogramBins(xValues, bins.boundaries).map(value => [value]);
89
+ const cells = bins.boundaries.slice(0, -1).map(() => []);
90
+ xValues.forEach((value, index) => {
91
+ const bin = findHistogramBinIndex(value, bins.boundaries);
92
+ if (bin !== -1) cells[bin].push(input.entries[index]);
93
+ });
94
+ return cells.map(entries => [input.mass(entries)]);
72
95
  }
73
96
  const colorScale = findScale(program, colorEncoding.scale);
74
- const colorValues = readNominalField(dataset.values, colorEncoding.field);
97
+ const colorValues = readNominalField(input.rows, colorEncoding.field);
75
98
  const colorDomain = resolveOrdinalDomain(colorScale.domain, colorValues);
76
99
  validateGeneratedItemLimit(
77
100
  (bins.boundaries.length - 1) * colorDomain.length,
@@ -79,14 +102,16 @@ function resolveHistogramPartitions(program, consumer) {
79
102
  );
80
103
  const colorIndex = new Map(colorDomain.map((value, index) => [value, index]));
81
104
  const partitions = bins.boundaries.slice(0, -1).map(() =>
82
- colorDomain.map(() => 0)
105
+ colorDomain.map(() => [])
83
106
  );
84
107
  for (let index = 0; index < xValues.length; index += 1) {
85
108
  const bin = findHistogramBinIndex(xValues[index], bins.boundaries);
86
109
  const color = colorIndex.get(colorValues[index]);
87
- if (bin !== -1 && color !== undefined) partitions[bin][color] += 1;
110
+ if (bin !== -1 && color !== undefined) {
111
+ partitions[bin][color].push(input.entries[index]);
112
+ }
88
113
  }
89
- return partitions;
114
+ return partitions.map(partition => partition.map(input.mass));
90
115
  }
91
116
 
92
117
  function resolveAggregatePartitions(program, consumer) {
@@ -101,6 +126,12 @@ function resolveAggregatePartitions(program, consumer) {
101
126
  : derived.yValues;
102
127
  const categoryDomain = resolveOrdinalDomain(categoryScale.domain, categoryValues);
103
128
  const colorEncoding = layer.encoding?.color;
129
+ if (layer.encoding?.group !== undefined) {
130
+ const series = readSeriesIdentity(dataset.values, layer).domain;
131
+ validateGeneratedItemLimit(categoryDomain.length * series.length, "Aggregate layout cell count");
132
+ const cells = new Map(derived.values.map(value => [JSON.stringify([value[channels.category], value.series]), value[channels.measure]]));
133
+ return categoryDomain.map(category => series.map(key => cells.get(JSON.stringify([category, key])) ?? 0));
134
+ }
104
135
  if (
105
136
  colorEncoding?.scale === undefined ||
106
137
  colorEncoding.fieldType === "quantitative"
@@ -181,11 +212,25 @@ export function resolveSeriesLayoutScaleValues(program, consumer) {
181
212
  values: resolveSeriesLayoutDomainValues(partitions, layout)
182
213
  };
183
214
  }
215
+ if (consumer.layer.mark?.type === "area" && consumer.layer.layout?.mode !== undefined) {
216
+ const layer = consumer.layer;
217
+ const dataset = findDataset(program, layer.data);
218
+ const density = dataset.transform?.some(transform => transform.type === "density");
219
+ if (!density && (layer.encoding?.x === undefined || layer.encoding?.y === undefined || (layer.encoding?.x2 === undefined && layer.encoding?.y2 === undefined))) return undefined;
220
+ if (!density && layer.encoding?.x !== undefined && layer.encoding?.y !== undefined && (layer.encoding?.x2 !== undefined || layer.encoding?.y2 !== undefined)) {
221
+ const derived = deriveAreaSeries(dataset.values, layer);
222
+ const measure = derived.orientation === "vertical" ? "y" : "x";
223
+ if (![measure, `${measure}2`].includes(consumer.channel)) return undefined;
224
+ const layout = layer.layout.mode;
225
+ if (layout === "overlay") return undefined;
226
+ return { layout, values: layout === "fill" ? [0, 1] : derived[`${measure}Values`] };
227
+ }
228
+ }
184
229
  if (consumer.channel !== "y") return undefined;
185
230
  if (consumer.layer.mark?.type === "area") {
186
- const layout = consumer.layer.encoding?.y?.stack === "center"
231
+ const layout = consumer.layer.layout?.mode ?? (consumer.layer.encoding?.y?.stack === "center"
187
232
  ? "center"
188
- : consumer.layer.encoding?.color?.layout;
233
+ : consumer.layer.encoding?.color?.layout);
189
234
  if (layout === undefined || layout === "overlay") return undefined;
190
235
  const partitions = resolveAreaPartitions(program, consumer);
191
236
  if (partitions === undefined) {