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,4 +1,4 @@
1
- import { normalizeOffsetPadding } from "../../../grammar/bars/geometry.js";
1
+ import { normalizeOffsetScalePolicy } from "../../../grammar/bars/geometry.js";
2
2
 
3
3
  function resolveParentSlot(consumer, resolvedScales, parentChannel) {
4
4
  if (parentChannel === "x" && consumer.layer.encoding?.x?.bin !== undefined) {
@@ -16,6 +16,7 @@ function resolveParentSlot(consumer, resolvedScales, parentChannel) {
16
16
  }
17
17
 
18
18
  export function resolveOffsetScalePolicy({
19
+ scale,
19
20
  consumers,
20
21
  resolvedScales,
21
22
  markConfigs,
@@ -34,21 +35,62 @@ export function resolveOffsetScalePolicy({
34
35
  `${channel} scale "${id}" requires one shared resolved ${parentChannel} categorical slot.`
35
36
  );
36
37
  }
37
- const paddings = consumers.map(consumer => normalizeOffsetPadding(
38
+ return {
39
+ parentBandwidth: slots[0],
40
+ ...resolveRequestedOffsetPolicy({
41
+ scale,
42
+ consumers,
43
+ markConfigs,
44
+ id,
45
+ channel
46
+ })
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Resolve the requested offset policy independently from its parent slot.
52
+ * Semantic scale fields are canonical. Consistent legacy mark-owned padding is
53
+ * retained as a fallback until the next successful scale/encoding edit migrates
54
+ * it into the semantic scale.
55
+ */
56
+ export function resolveRequestedOffsetPolicy({
57
+ scale,
58
+ consumers = [],
59
+ markConfigs = {},
60
+ id = scale?.id ?? channel,
61
+ channel = "xOffset"
62
+ }) {
63
+ const semanticPatch = scale === undefined
64
+ ? {}
65
+ : Object.fromEntries(
66
+ ["padding", "paddingInner", "paddingOuter", "align"]
67
+ .filter(property => Object.hasOwn(scale, property))
68
+ .map(property => [property, scale[property]])
69
+ );
70
+ if (
71
+ Object.hasOwn(semanticPatch, "padding") ||
72
+ ["paddingInner", "paddingOuter", "align"].every(
73
+ property => Object.hasOwn(semanticPatch, property)
74
+ )
75
+ ) {
76
+ return normalizeOffsetScalePolicy(semanticPatch, undefined, channel);
77
+ }
78
+ const legacyPolicies = consumers.map(consumer => normalizeOffsetScalePolicy(
38
79
  markConfigs[consumer.layer.id]?.[channel],
39
80
  undefined,
40
81
  channel
41
82
  ));
42
83
  const signatures = new Set(
43
- paddings.map(padding => JSON.stringify(padding))
84
+ legacyPolicies.map(policy => JSON.stringify(policy))
44
85
  );
45
- if (signatures.size !== 1) {
86
+ if (signatures.size > 1) {
46
87
  throw new Error(
47
88
  `${channel} scale "${id}" requires one shared padding policy.`
48
89
  );
49
90
  }
50
- return {
51
- parentBandwidth: slots[0],
52
- ...paddings[0]
53
- };
91
+ const legacy = legacyPolicies[0] ?? normalizeOffsetScalePolicy(
92
+ {}, undefined, channel
93
+ );
94
+ if (scale === undefined) return legacy;
95
+ return normalizeOffsetScalePolicy(semanticPatch, legacy, channel);
54
96
  }
@@ -1,3 +1,4 @@
1
+ import { isSourceOwnedText } from "../../../grammar/text.js";
1
2
  import {
2
3
  isTransformedScaleType,
3
4
  resolveContinuousDomain,
@@ -21,7 +22,10 @@ export function resolveSeriesLayoutDomain({
21
22
  (_, index) => seriesLayouts[index] !== undefined
22
23
  );
23
24
  const directConsumers = valuesByConsumer.filter(
24
- (_, index) => seriesLayouts[index] === undefined
25
+ ({ consumer, scalePolicy }, index) =>
26
+ scalePolicy !== false &&
27
+ seriesLayouts[index] === undefined &&
28
+ !isSourceOwnedText(consumer.layer)
25
29
  );
26
30
  const compatibleDirect = directConsumers.every(({ consumer }) =>
27
31
  layoutConsumers.every(({ consumer: layoutConsumer }) => {
@@ -1,3 +1,4 @@
1
+ import { validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
1
2
  import {
2
3
  isContinuousColorScaleType,
3
4
  isDiscretePositionScaleType,
@@ -16,17 +17,19 @@ import {
16
17
  resolveScaleRange,
17
18
  resolveSequentialColorStops,
18
19
  resolveShapeRange,
20
+ resolveSizeScale,
19
21
  resolveSizeRange,
20
22
  resolveStrokeDashRange,
21
23
  resolveStrokeWidthRange,
22
24
  resolveTransformedDomain,
23
25
  SCALE_ROLES,
26
+ validateSequentialMidpoint,
24
27
  validateLinearScaleType,
25
28
  validateOrdinalScaleType,
26
29
  validateScaleTypeForRole,
27
30
  validateTimeScaleType
28
31
  } from "../../grammar/scales/index.js";
29
- import { resolveArcAutoPositionRange } from "./policies/arc.js";
32
+ import { resolveArcAutoPositionRange, resolveMeasuredRadiusDomain, validateMeasuredRadiusConsumers } from "./policies/arc.js";
30
33
  import { resolveTemporalBarBand } from "./policies/bar.js";
31
34
  import { resolveOffsetScalePolicy } from "./policies/offset.js";
32
35
  import { resolveBinnedPositionDomain } from "./policies/binnedPosition.js";
@@ -94,7 +97,8 @@ function resolveRange({
94
97
  isSequentialColor,
95
98
  isDiscretizedColor,
96
99
  isOrdinalOffset,
97
- discretizedScale
100
+ discretizedScale,
101
+ polarFrame
98
102
  }) {
99
103
  if (channel === "color") {
100
104
  if (isDiscretizedColor) return discretizedScale.range;
@@ -112,7 +116,7 @@ function resolveRange({
112
116
  scale,
113
117
  channel,
114
118
  domain,
115
- range: resolveScaleRange(scale.range, channel, bounds),
119
+ range: resolveScaleRange(scale.range, channel, bounds, polarFrame),
116
120
  markConfigs
117
121
  });
118
122
  }
@@ -139,7 +143,9 @@ function resolveContinuousScale({
139
143
  : validateLinearScaleType(scale.type),
140
144
  domain,
141
145
  range,
146
+ ...(scale.midpoint === undefined ? {} : { midpoint: scale.midpoint }),
142
147
  ...(scale.clamp === undefined ? {} : { clamp: scale.clamp }),
148
+ ...(scale.radialMapping === undefined ? {} : { radialMapping: scale.radialMapping }),
143
149
  ...(scale.type === "log"
144
150
  ? {
145
151
  base: normalizeTransformParameters("log", {
@@ -182,19 +188,43 @@ function reverseResolvedScale(scale) {
182
188
  };
183
189
  }
184
190
 
185
- export function resolveScaleMaterialization({
186
- id,
187
- scale,
188
- channel,
189
- consumers,
190
- valuesByConsumer,
191
- bounds,
192
- resolvedScales,
193
- markConfigs
194
- }) {
191
+ export function resolveScaleMaterialization(options) {
192
+ const {
193
+ id,
194
+ scale,
195
+ channel: requestedChannel,
196
+ consumers,
197
+ valuesByConsumer,
198
+ bounds,
199
+ resolvedScales,
200
+ markConfigs,
201
+ thetaScales,
202
+ polarFrame
203
+ } = options;
204
+ const channel = requestedChannel === "stroke" ? "color" : requestedChannel;
195
205
  const allValues = valuesByConsumer
196
206
  .flatMap(item => item.values)
197
207
  .filter(value => value !== undefined);
208
+ const preserved = resolvedScales[id]?.domain;
209
+ if (
210
+ scale.domain === "auto" &&
211
+ Array.isArray(preserved) &&
212
+ consumers.some(({ layer }) =>
213
+ markConfigs?.[layer.id]?.markFilter?.empty === true
214
+ )
215
+ ) {
216
+ return resolveScaleMaterialization({
217
+ ...options,
218
+ scale: { ...scale, domain: preserved },
219
+ valuesByConsumer: valuesByConsumer.map(item => ({
220
+ ...item,
221
+ categoryOrder: undefined
222
+ }))
223
+ });
224
+ }
225
+ if (channel === "size") {
226
+ return resolveSizeScale({ ...scale, values: allValues });
227
+ }
198
228
  const isSequentialColor = channel === "color" &&
199
229
  isContinuousColorScaleType(scale.type);
200
230
  const isDiscretizedColor = channel === "color" &&
@@ -210,6 +240,16 @@ export function resolveScaleMaterialization({
210
240
  !isOrdinalOffset &&
211
241
  isDiscretePositionScaleType(scale.type);
212
242
 
243
+ if (isSequentialColor || isDiscretizedColor) {
244
+ for (const consumer of consumers) {
245
+ validateContinuousColorConsumer(
246
+ consumer.layer,
247
+ consumer.encoding,
248
+ scale,
249
+ { channel: consumer.channel }
250
+ );
251
+ }
252
+ }
213
253
  let discretizedScale;
214
254
  if (isDiscretizedColor) {
215
255
  discretizedScale = resolveDiscretizedColorScale({
@@ -225,8 +265,7 @@ export function resolveScaleMaterialization({
225
265
  valuesByConsumer,
226
266
  channel,
227
267
  scale,
228
- id,
229
- allValues
268
+ id
230
269
  });
231
270
  const seriesLayouts = valuesByConsumer.map(item => item.seriesLayout);
232
271
  const seriesDomain = isDiscretizedColor || binnedDomain !== undefined
@@ -247,7 +286,7 @@ export function resolveScaleMaterialization({
247
286
  }
248
287
  const domain = isDiscretizedColor
249
288
  ? discretizedScale.domain
250
- : categoryOrderDomain ?? binnedDomain ?? seriesDomain ?? resolveDefaultDomain({
289
+ : resolveMeasuredRadiusDomain({ scale, channel, allValues }) ?? categoryOrderDomain ?? binnedDomain ?? seriesDomain ?? resolveDefaultDomain({
251
290
  scale,
252
291
  allValues,
253
292
  isOrdinalAppearance,
@@ -265,8 +304,16 @@ export function resolveScaleMaterialization({
265
304
  isSequentialColor,
266
305
  isDiscretizedColor,
267
306
  isOrdinalOffset,
268
- discretizedScale
307
+ discretizedScale,
308
+ polarFrame
269
309
  });
310
+ validateSequentialMidpoint(scale.midpoint, scale.type, domain);
311
+ if (scale.midpoint !== undefined && (channel !== "color" || consumers.some(
312
+ consumer => consumer.encoding.fieldType !== "quantitative"
313
+ ))) {
314
+ throw new Error("Scale midpoint requires quantitative color consumers.");
315
+ }
316
+ validateMeasuredRadiusConsumers({ scale, domain, range, consumers, markConfigs, thetaScales });
270
317
  if (channel === "shape" && domain.length > range.length) {
271
318
  throw new Error(
272
319
  `Shape scale "${id}" requires at least one distinct shape per domain value.`
@@ -281,6 +328,7 @@ export function resolveScaleMaterialization({
281
328
  values: allValues,
282
329
  range: scale.range,
283
330
  ...resolveOffsetScalePolicy({
331
+ scale,
284
332
  consumers,
285
333
  resolvedScales,
286
334
  markConfigs,
@@ -0,0 +1,11 @@
1
+ export function assertEncodingSelectionCompatibility(program, target, channels) {
2
+ for (const [id, selection] of Object.entries(
3
+ program.materializationConfigs.selections ?? {}
4
+ )) {
5
+ if (selection.target === target && channels.includes(selection.selector?.channel)) {
6
+ throw new Error(
7
+ `Cannot remove ${selection.selector.channel} encoding while selection "${id}" references that channel.`
8
+ );
9
+ }
10
+ }
11
+ }
@@ -1,7 +1,7 @@
1
1
  import { deriveArcSectors } from "../../../grammar/arcs.js";
2
2
  import { resolvePolarPoint } from "../../../grammar/polarPaths.js";
3
- import { resolvePolarFrame } from "../../../grammar/polar.js";
4
- import { resolveGraphicBounds } from "../../../layout/canvas.js";
3
+ import { readScaleField } from "../../../grammar/scales/index.js";
4
+ import { resolveCoordinatePolarFrame } from "../../coordinateBounds.js";
5
5
  import {
6
6
  concreteProperties,
7
7
  finalizeItems,
@@ -11,7 +11,7 @@ import {
11
11
  export function resolveArcItems(program, layer, dataset) {
12
12
  const thetaScale = program.resolvedScales[layer.encoding?.theta?.scale];
13
13
  const radiusScale = program.resolvedScales[layer.encoding?.radius?.scale];
14
- const frame = resolvePolarFrame(resolveGraphicBounds(program));
14
+ const frame = resolveCoordinatePolarFrame(program, layer.coordinate);
15
15
  const derived = deriveArcSectors(dataset.values, layer, {
16
16
  thetaScale,
17
17
  ...(radiusScale === undefined ? {} : { radiusScale }),
@@ -19,14 +19,25 @@ export function resolveArcItems(program, layer, dataset) {
19
19
  innerRadiusRatio: program.markConfigs[layer.id]?.innerRadius ?? 0
20
20
  });
21
21
  const graphic = program.graphicSpec.objects[layer.id];
22
+ const stroke = layer.encoding?.stroke;
22
23
  const definitions = derived.sectors.map((sector, index) => {
23
24
  const members = sector.sourceIndices.map(index => dataset.values[index]);
25
+ const strokeValues = stroke === undefined ? undefined : readScaleField(
26
+ members,
27
+ stroke.field,
28
+ stroke.fieldType,
29
+ { temporalUnit: stroke.temporalUnit }
30
+ );
31
+ if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
32
+ throw new Error("Arc stroke requires one value within each sector.");
33
+ }
24
34
  return {
25
35
  fields: uniqueFields(members),
26
36
  channels: {
27
37
  theta: sector.theta,
28
38
  ...(sector.radius === undefined ? {} : { radius: sector.radius }),
29
- ...(sector.color === undefined ? {} : { color: sector.color })
39
+ ...(sector.color === undefined ? {} : { color: sector.color }),
40
+ ...(strokeValues === undefined ? {} : { stroke: strokeValues[0] })
30
41
  },
31
42
  properties: {
32
43
  ...concreteProperties(graphic.items[index]?.properties),
@@ -36,6 +47,19 @@ export function resolveArcItems(program, layer, dataset) {
36
47
  (sector.innerRadius + sector.outerRadius) / 2
37
48
  )
38
49
  },
50
+ geometry: {
51
+ kind: "arc",
52
+ centerX: frame.centerX,
53
+ centerY: frame.centerY,
54
+ startTheta: sector.startTheta +
55
+ Math.sign(sector.endTheta - sector.startTheta) *
56
+ (program.markConfigs[layer.id]?.padAngle ?? 0) / 2,
57
+ endTheta: sector.endTheta -
58
+ Math.sign(sector.endTheta - sector.startTheta) *
59
+ (program.markConfigs[layer.id]?.padAngle ?? 0) / 2,
60
+ innerRadius: sector.innerRadius,
61
+ outerRadius: sector.outerRadius
62
+ },
39
63
  members
40
64
  };
41
65
  });
@@ -1,4 +1,5 @@
1
- import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
1
+ import { deriveSeriesBarCells } from "../../../grammar/bars/aggregate.js";
2
+ import { readSeriesIdentity } from "../../../grammar/pathSeries.js";
2
3
  import {
3
4
  BAR_GRAINS,
4
5
  resolveBarChannels,
@@ -7,9 +8,9 @@ import {
7
8
  resolveBarGrain
8
9
  } from "../../../grammar/bars/policy.js";
9
10
  import { numericExtent } from "../../../grammar/numeric.js";
10
- import { layoutSeriesPartition } from "../../../grammar/seriesLayout.js";
11
11
  import {
12
12
  readNominalField,
13
+ readScaleField,
13
14
  readTemporalField
14
15
  } from "../../../grammar/scales/index.js";
15
16
  import { findSemanticScale } from "../../../selectors/scales.js";
@@ -24,7 +25,7 @@ import {
24
25
 
25
26
  function categoryValues(rows, encoding) {
26
27
  return encoding.fieldType === "temporal"
27
- ? readTemporalField(rows, encoding.field)
28
+ ? readTemporalField(rows, encoding.field, encoding.temporalUnit)
28
29
  : readNominalField(rows, encoding.field);
29
30
  }
30
31
 
@@ -36,28 +37,35 @@ function aggregateMembers(rows, layer, cell, channels) {
36
37
  const colors = !["nominal", "ordinal"].includes(color?.fieldType)
37
38
  ? undefined
38
39
  : readNominalField(rows, color.field);
40
+ const identity = layer.encoding?.group === undefined ? undefined : readSeriesIdentity(rows, layer);
39
41
  return rows.filter((_, index) =>
40
42
  categories[index] === categoryValue &&
41
- (colors === undefined || colors[index] === cell.color)
43
+ (identity === undefined ? colors === undefined || colors[index] === cell.color : identity.values[index] === cell.series)
42
44
  );
43
45
  }
44
46
 
45
47
  function aggregateCellDefinitions(program, layer, dataset) {
46
48
  const channels = resolveBarChannels(layer);
47
- const derived = deriveBarAggregates(dataset.values, layer).values;
48
49
  const categoryEncoding = layer.encoding[channels.category];
49
50
  const categoryScale = program.resolvedScales[categoryEncoding.scale];
50
- const measureScale = program.resolvedScales[
51
- layer.encoding[channels.measure].scale
52
- ];
53
51
  const colorEncoding = layer.encoding?.color;
54
52
  const colorScale = program.resolvedScales[colorEncoding?.scale];
55
53
  const offsetChannel = resolveBarOffsetChannel(layer);
56
54
  const offsetScale = program.resolvedScales[layer.encoding?.[offsetChannel]?.scale];
57
55
  const layout = resolveBarColorLayout(layer);
56
+ const stroke = layer.encoding?.stroke;
58
57
 
59
58
  function definition(cell, start, end) {
60
59
  const members = aggregateMembers(dataset.values, layer, cell, channels);
60
+ const strokeValues = stroke === undefined ? undefined : readScaleField(
61
+ members,
62
+ stroke.field,
63
+ stroke.fieldType,
64
+ { temporalUnit: stroke.temporalUnit }
65
+ );
66
+ if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
67
+ throw new Error("Bar stroke requires one value within each aggregate cell.");
68
+ }
61
69
  return {
62
70
  fields: uniqueFields(members),
63
71
  channels: {
@@ -65,72 +73,20 @@ function aggregateCellDefinitions(program, layer, dataset) {
65
73
  [channels.measure]: start,
66
74
  [`${channels.measure}2`]: end,
67
75
  ...(cell.color === undefined ? {} : { color: cell.color }),
76
+ ...(strokeValues === undefined ? {} : { stroke: strokeValues[0] }),
68
77
  ...(layer.encoding?.[offsetChannel] === undefined
69
78
  ? {}
70
- : { [offsetChannel]: cell.color })
79
+ : { [offsetChannel]: cell.series ?? cell.color })
71
80
  },
72
81
  members
73
82
  };
74
83
  }
75
84
 
76
- if (colorEncoding?.fieldType === "quantitative") {
77
- const cells = new Map(derived.map(cell => [
78
- cell[channels.category],
79
- cell
80
- ]));
81
- const categories = ["ordinal", "band", "point"].includes(categoryScale.type)
82
- ? categoryScale.domain
83
- : [...new Set(derived.map(cell => cell[channels.category]))]
84
- .sort((left, right) => left - right);
85
- return categories.flatMap(category => {
86
- const cell = cells.get(category);
87
- return cell === undefined
88
- ? []
89
- : [definition(cell, measureScale.domain[0], cell[channels.measure])];
90
- });
91
- }
92
-
93
- if (layout === "group" && offsetScale !== undefined) {
94
- const categoryIndex = new Map(
95
- categoryScale.domain.map((value, index) => [value, index])
96
- );
97
- const offsetIndex = new Map(
98
- offsetScale.domain.map((value, index) => [value, index])
99
- );
100
- const cells = [...derived].sort((left, right) =>
101
- categoryIndex.get(left[channels.category]) -
102
- categoryIndex.get(right[channels.category]) ||
103
- offsetIndex.get(left.color) - offsetIndex.get(right.color)
104
- );
105
- return cells.map(cell => definition(
106
- cell,
107
- measureScale.domain[0],
108
- cell[channels.measure]
109
- ));
110
- }
111
-
112
- const categories = ["ordinal", "band", "point"].includes(categoryScale.type)
113
- ? categoryScale.domain
114
- : [...new Set(derived.map(cell => cell[channels.category]))]
115
- .sort((left, right) => left - right);
116
- const series = colorScale?.domain ?? [undefined];
117
- const lookup = new Map(derived.map(cell => [
118
- JSON.stringify([cell[channels.category], cell.color]),
119
- cell
120
- ]));
121
- const baseline = layout === "overlay" ? measureScale.domain[0] : 0;
122
- return categories.flatMap(category => {
123
- const cells = series.map(color =>
124
- lookup.get(JSON.stringify([category, color]))
125
- );
126
- const values = cells.map(cell => cell?.[channels.measure] ?? 0);
127
- return layoutSeriesPartition(values, layout, { baseline }).flatMap(segment => {
128
- const cell = cells[segment.index];
129
- return cell === undefined
130
- ? []
131
- : [definition(cell, segment.start, segment.end)];
132
- });
133
- });
85
+ const categories = categoryScale.type === "time" ? undefined : categoryScale.domain;
86
+ const series = layout === "group" ? offsetScale?.domain
87
+ : layer.encoding.group === undefined && colorEncoding?.fieldType !== "quantitative" ? colorScale?.domain : undefined;
88
+ return deriveSeriesBarCells(dataset.values, layer, categories, series)
89
+ .map(({ cell, start, end }) => definition(cell, start, end));
134
90
  }
135
91
 
136
92
  function histogramDefinitions(program, layer, dataset) {
@@ -144,8 +100,18 @@ function histogramDefinitions(program, layer, dataset) {
144
100
  resolvedScales: program.resolvedScales
145
101
  });
146
102
  const colorScale = program.resolvedScales[colorEncoding?.scale];
103
+ const stroke = layer.encoding?.stroke;
147
104
  return segments.map(segment => {
148
- const colorValue = colorScale?.domain[segment.category];
105
+ const colorValue = segment.colorValue ?? colorScale?.domain[segment.category];
106
+ const strokeValues = stroke === undefined ? undefined : readScaleField(
107
+ segment.members,
108
+ stroke.field,
109
+ stroke.fieldType,
110
+ { temporalUnit: stroke.temporalUnit }
111
+ );
112
+ if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
113
+ throw new Error("Histogram stroke requires one value within each bin/series cell.");
114
+ }
149
115
  return {
150
116
  fields: uniqueFields(segment.members),
151
117
  channels: {
@@ -153,7 +119,8 @@ function histogramDefinitions(program, layer, dataset) {
153
119
  x2: segment.end,
154
120
  y: segment.stackStart,
155
121
  y2: segment.stackEnd,
156
- ...(colorValue === undefined ? {} : { color: colorValue })
122
+ ...(colorValue === undefined ? {} : { color: colorValue }),
123
+ ...(strokeValues === undefined ? {} : { stroke: strokeValues[0] })
157
124
  },
158
125
  members: segment.members
159
126
  };
@@ -244,14 +211,18 @@ export function resolveBarItems(program, layer, dataset, selectionGrain) {
244
211
  if (definitions === undefined) {
245
212
  throw new Error(`Bar mark "${layer.id}" is incomplete for selection.`);
246
213
  }
214
+ const graphicTypes = Object.hasOwn(
215
+ program.markConfigs[layer.id]?.barAppearance ?? {},
216
+ "cornerRadius"
217
+ ) ? ["rect", "path"] : "rect";
247
218
  if (selectionGrain === "stack") {
248
219
  return finalizeItems(
249
220
  program,
250
221
  layer,
251
222
  "stack",
252
223
  barStackDefinitions(layer, grain, definitions),
253
- "rect"
224
+ graphicTypes
254
225
  );
255
226
  }
256
- return finalizeItems(program, layer, grain, definitions, "rect");
227
+ return finalizeItems(program, layer, grain, definitions, graphicTypes);
257
228
  }
@@ -1,3 +1,4 @@
1
+ import { normalizeTemporalValue } from "../../../grammar/scales/fields.js";
1
2
  import { cloneAndFreeze, isPlainObject } from "../../../core/immutable.js";
2
3
  import {
3
4
  MARK_GRAPHIC_PROPERTIES,
@@ -5,6 +6,7 @@ import {
5
6
  } from "../../../grammar/markSelection.js";
6
7
  import { unionConcreteGraphicBounds } from
7
8
  "../../../grammar/schemas/graphicBounds.js";
9
+ import { readRectItemGeometry } from "../../../grammar/roundedRect.js";
8
10
 
9
11
  export function itemKey(layer, grain, index) {
10
12
  return `${layer.id}/${grain}/${index}`;
@@ -16,8 +18,13 @@ function graphicId(layer, index) {
16
18
 
17
19
  function encodingValue(row, encoding) {
18
20
  if (encoding === undefined) return undefined;
19
- if (Object.hasOwn(encoding, "field")) return row?.[encoding.field];
20
- return encoding.datum;
21
+ const value = Object.hasOwn(encoding, "field") ? row?.[encoding.field] : encoding.datum;
22
+ if (encoding.fieldType !== "temporal") return value;
23
+ try {
24
+ return normalizeTemporalValue(value, encoding.field, 0, encoding.temporalUnit);
25
+ } catch {
26
+ return undefined;
27
+ }
21
28
  }
22
29
 
23
30
  export function channelMapFromRow(row, layer) {
@@ -48,14 +55,15 @@ export function uniqueFields(rows) {
48
55
 
49
56
  function requireResolvedGraphic(program, layer, type) {
50
57
  const graphic = program.graphicSpec.objects[layer.id];
58
+ const types = Array.isArray(type) ? type : [type];
51
59
  const compatibleCollection = graphic?.type === "collection" &&
52
- graphic.items?.every(child => child.type === type);
60
+ graphic.items?.every(child => types.includes(child.type));
53
61
  if (
54
- (graphic?.type !== type && !compatibleCollection) ||
62
+ (!types.includes(graphic?.type) && !compatibleCollection) ||
55
63
  !Array.isArray(graphic.items)
56
64
  ) {
57
65
  throw new Error(
58
- `Mark "${layer.id}" requires a materialized ${type} collection for selection.`
66
+ `Mark "${layer.id}" requires a materialized ${types.join("/")} collection for selection.`
59
67
  );
60
68
  }
61
69
  return graphic;
@@ -72,6 +80,13 @@ export function concreteProperties(properties) {
72
80
  );
73
81
  }
74
82
 
83
+ function selectableGraphicProperties(item) {
84
+ return {
85
+ ...concreteProperties(item?.properties),
86
+ ...(readRectItemGeometry(item) ?? {})
87
+ };
88
+ }
89
+
75
90
  function sharedConcreteProperties(items) {
76
91
  if (items.length === 0) return {};
77
92
  const shared = concreteProperties(items[0].properties);
@@ -112,7 +127,10 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
112
127
  const items = indices.map(childIndex => graphic.items[childIndex]);
113
128
  const properties = definition.properties ?? (
114
129
  items.length === 1
115
- ? concreteProperties(items[0].properties)
130
+ ? selectableGraphicProperties({
131
+ type: items[0].type ?? graphic.type,
132
+ properties: items[0].properties
133
+ })
116
134
  : {
117
135
  ...sharedConcreteProperties(items),
118
136
  ...collectionBounds(program, items)
@@ -125,6 +143,9 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
125
143
  fields: cloneAndFreeze(definition.fields),
126
144
  channels: cloneAndFreeze(definition.channels),
127
145
  properties: cloneAndFreeze(properties),
146
+ ...(definition.geometry === undefined
147
+ ? {}
148
+ : { geometry: cloneAndFreeze(definition.geometry) }),
128
149
  members: Object.freeze([...definition.members]),
129
150
  graphicIds: Object.freeze(items.map((child, childOffset) =>
130
151
  child.id ?? graphicId(layer, indices[childOffset])
@@ -25,20 +25,10 @@ function rowsForSeries(rows, key) {
25
25
 
26
26
  function seriesDefinitions(layer, rows, series) {
27
27
  return series.map(item => {
28
- const members = rowsForSeries(rows, item.key);
28
+ const members = item.sourceIndices === undefined ? rowsForSeries(rows, item.key)
29
+ : item.sourceIndices.map(index => rows[index]);
29
30
  const fields = { ...uniqueFields(members), ...item.key };
30
- const channels = Object.fromEntries(
31
- Object.entries(layer.encoding ?? {}).flatMap(([channel, encoding]) => {
32
- if (encoding.field === undefined) {
33
- return Object.hasOwn(encoding, "datum")
34
- ? [[channel, encoding.datum]]
35
- : [];
36
- }
37
- return Object.hasOwn(fields, encoding.field)
38
- ? [[channel, fields[encoding.field]]]
39
- : [];
40
- })
41
- );
31
+ const channels = channelMapFromRow(fields, layer);
42
32
  return { fields, channels, members };
43
33
  });
44
34
  }