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,161 +1,70 @@
1
- import { deriveBarAggregates } from "../../grammar/bars/aggregate.js";
1
+ import { deriveSeriesBarCells } from "../../grammar/bars/aggregate.js";
2
+ import { resolveBarChannels, resolveBarColorLayout, resolveBarOffsetChannel } from "../../grammar/bars/policy.js";
2
3
  import {
3
- BAR_ORIENTATIONS,
4
- resolveBarChannels,
5
- resolveBarColorLayout
6
- } from "../../grammar/bars/policy.js";
7
- import {
8
- DEFAULT_SERIES_BASELINE,
9
- layoutSeriesPartition
10
- } from "../../grammar/seriesLayout.js";
11
- import {
12
- isDiscretePositionScaleType,
13
4
  mapContinuousScaleValues,
14
- mapOrdinalPositionValues
5
+ mapOrdinalPositionValues,
6
+ readScaleField
15
7
  } from "../../grammar/scales/index.js";
16
8
  import { mapScaleConsumerValues } from "../scales/map.js";
17
- import {
18
- DEFAULT_BAR_FILL,
19
- resolveBarAppearance
20
- } from "./resolve.js";
21
- import { deriveGroupedRectangles } from "./grouped.js";
9
+ import { DEFAULT_BAR_FILL, resolveBarAppearance } from "./resolve.js";
22
10
  import { resolveBarWidth } from "../../grammar/bars/geometry.js";
11
+ import { finiteMidpoint } from "../../grammar/numeric.js";
23
12
 
24
13
  export function deriveAggregateRectangles(required, resolved, widthConfig) {
25
14
  const { dataset, layer } = required;
26
- const layout = resolveBarColorLayout(layer);
27
- if (layout === "group") {
28
- return deriveGroupedRectangles(required, resolved, widthConfig);
29
- }
30
-
31
- const xScale = resolved.resolvedScales[required.xEncoding.scale];
32
- const yScale = resolved.resolvedScales[required.yEncoding.scale];
33
- const channels = resolveBarChannels(layer);
34
- const vertical = channels.orientation === BAR_ORIENTATIONS.vertical;
35
- const categoryScale = vertical ? xScale : yScale;
36
- const measureScale = vertical ? yScale : xScale;
37
- const colorEncoding = layer.encoding?.color;
38
- const colorScale = resolved.resolvedScales[colorEncoding?.scale];
39
- if (colorEncoding !== undefined && colorScale === undefined) {
40
- throw new Error(
41
- `Bar mark "${layer.id}" requires a resolved color scale.`
42
- );
43
- }
44
-
45
- const seriesDomain = colorScale?.domain ?? [undefined];
46
- const seriesIndex = new Map(
47
- seriesDomain.map((value, index) => [value, index])
48
- );
49
- const colors = new Map(seriesDomain.map((value, index) => [
50
- value,
51
- colorScale?.range[index % colorScale.range.length] ?? DEFAULT_BAR_FILL
52
- ]));
53
- const cells = deriveBarAggregates(dataset.values, layer).values;
54
- const discreteCategory = categoryScale.type === "ordinal" ||
55
- isDiscretePositionScaleType(categoryScale.type);
56
- const categoryDomain = discreteCategory
57
- ? categoryScale.domain
58
- : [...new Set(cells.map(cell => cell[channels.category]))]
59
- .sort((left, right) => left - right);
60
- const categoryIndex = new Map(
61
- categoryDomain.map((value, index) => [value, index])
62
- );
63
- const cellMap = new Map(cells.map(cell => [
64
- JSON.stringify([cell[channels.category], cell.color]),
65
- cell
66
- ]));
67
- const thickness = resolveBarWidth(
68
- widthConfig,
69
- Math.abs(categoryScale.bandwidth ?? categoryScale.step)
70
- );
71
- const baseline = DEFAULT_SERIES_BASELINE;
72
- const continuousColor = colorEncoding?.fieldType === "quantitative";
73
- if (continuousColor) {
74
- const mappedColors = mapScaleConsumerValues(
75
- cells.map(cell => cell.color), colorScale, "color"
15
+ const channels = resolveBarChannels(layer), vertical = channels.orientation === "vertical";
16
+ const categoryScale = resolved.resolvedScales[layer.encoding[channels.category].scale];
17
+ const measureScale = resolved.resolvedScales[layer.encoding[channels.measure].scale];
18
+ const color = layer.encoding.color, colorScale = resolved.resolvedScales[color?.scale];
19
+ const grouped = resolveBarColorLayout(layer) === "group";
20
+ const offsetScale = resolved.resolvedScales[layer.encoding[resolveBarOffsetChannel(layer)]?.scale];
21
+ if (grouped && offsetScale === undefined) throw new Error("Grouped bar requires a resolved offset scale.");
22
+ const temporal = categoryScale.type === "time";
23
+ const segments = deriveSeriesBarCells(dataset.values, layer, temporal ? undefined : categoryScale.domain,
24
+ grouped ? offsetScale.domain : layer.encoding.group === undefined && color?.fieldType !== "quantitative" ? colorScale?.domain : undefined);
25
+ const fills = color === undefined ? undefined : mapScaleConsumerValues(segments.map(segment => segment.cell.color), colorScale, "color");
26
+ const stroke = layer.encoding?.stroke;
27
+ const strokeValues = stroke === undefined ? undefined : segments.map(segment => {
28
+ const values = readScaleField(
29
+ segment.cell.sourceIndices.map(index => dataset.values[index]),
30
+ stroke.field,
31
+ stroke.fieldType,
32
+ { temporalUnit: stroke.temporalUnit }
76
33
  );
77
- const cellByCategory = new Map(
78
- cells.map((cell, index) => [cell[channels.category], { cell, index }])
79
- );
80
- const existing = resolved.graphicSpec.objects[layer.id].items;
81
- const config = resolved.markConfigs[layer.id] ?? {};
82
- const appearance = config.barAppearance ?? {};
83
- return categoryDomain.flatMap(categoryValue => {
84
- const entry = cellByCategory.get(categoryValue);
85
- if (entry === undefined) return [];
86
- const { cell, index } = entry;
87
- const categoryCenter = discreteCategory
88
- ? mapOrdinalPositionValues([categoryValue], categoryScale)[0]
89
- : mapContinuousScaleValues(
90
- [categoryValue],
91
- categoryScale
92
- )[0];
93
- const [segment] = layoutSeriesPartition(
94
- [cell[channels.measure]],
95
- layout,
96
- { baseline }
97
- );
98
- if (segment === undefined) return [];
99
- const [start, end] = mapContinuousScaleValues(
100
- [segment.start, segment.end],
101
- measureScale
102
- );
103
- return [{
104
- x: vertical ? categoryCenter - thickness / 2 : Math.min(start, end),
105
- y: vertical ? Math.min(start, end) : categoryCenter - thickness / 2,
106
- width: vertical ? thickness : Math.abs(start - end),
107
- height: vertical ? Math.abs(start - end) : thickness,
108
- fill: mappedColors[index],
109
- ...resolveBarAppearance(config, existing[index]?.properties)
110
- }];
111
- });
112
- }
113
- const segments = [];
114
-
115
- for (const category of categoryDomain) {
116
- const partition = seriesDomain.map(color =>
117
- cellMap.get(JSON.stringify([category, color]))?.[channels.measure] ?? 0
118
- );
119
- for (const segment of layoutSeriesPartition(partition, layout, { baseline })) {
120
- const color = seriesDomain[segment.index];
121
- const cell = cellMap.get(JSON.stringify([category, color]));
122
- if (cell === undefined) continue;
123
- segments.push({ category, color, ...segment });
34
+ if (new Set(values).size !== 1) {
35
+ throw new Error("Bar stroke requires one value within each aggregate cell.");
124
36
  }
125
- }
126
-
127
- const existing = resolved.graphicSpec.objects[layer.id].items;
37
+ return values[0];
38
+ });
39
+ const strokes = strokeValues === undefined ? undefined : mapScaleConsumerValues(
40
+ strokeValues,
41
+ resolved.resolvedScales[stroke.scale],
42
+ "stroke"
43
+ );
128
44
  const config = resolved.markConfigs[layer.id] ?? {};
129
- const appearance = config.barAppearance ?? {};
130
- return segments.map((segment, index) => {
131
- const category = categoryIndex.get(segment.category);
132
- const color = seriesIndex.get(segment.color);
133
- if (category === undefined || color === undefined) {
134
- throw new Error("Bar value is outside a resolved ordinal domain.");
45
+ const existing = resolved.graphicSpec.objects[layer.id].items;
46
+ const width = resolveBarWidth(widthConfig, grouped ? offsetScale.bandwidth : Math.abs(categoryScale.bandwidth ?? categoryScale.step));
47
+ const indices = new Map(categoryScale.domain.map((value, index) => [value, index]));
48
+ return segments.map(({ cell, start, end, seriesIndex }, index) => {
49
+ const categoryValue = cell[channels.category];
50
+ const categoryCenter = (temporal ? mapContinuousScaleValues : mapOrdinalPositionValues)([categoryValue], categoryScale)[0];
51
+ let position = !grouped && layer.encoding.group !== undefined && layer.encoding.group.inferredFrom === undefined && !temporal && categoryScale.step > 0
52
+ ? (categoryScale.start ?? categoryScale.range[0]) + indices.get(categoryValue) * categoryScale.step + categoryScale.bandwidth / 2 - width / 2
53
+ : categoryCenter - width / 2;
54
+ if (grouped) {
55
+ const direction = Math.sign(categoryScale.step ?? categoryScale.range[1] - categoryScale.range[0]) || 1;
56
+ const offsetCenter = offsetScale.start + seriesIndex * offsetScale.step + (Math.sign(offsetScale.step) || 1) * offsetScale.bandwidth / 2;
57
+ if (vertical && direction > 0 && offsetScale.step > 0) {
58
+ const categoryStart = temporal ? categoryCenter - categoryScale.bandwidth / 2
59
+ : (categoryScale.start ?? categoryScale.range[0]) + indices.get(categoryValue) * categoryScale.step;
60
+ position = categoryStart + offsetScale.start + seriesIndex * offsetScale.step + (offsetScale.bandwidth - width) / 2;
61
+ } else position = categoryCenter + direction * (offsetCenter - finiteMidpoint(...offsetScale.range)) - width / 2;
135
62
  }
136
- const categoryCenter = discreteCategory
137
- ? mapOrdinalPositionValues([segment.category], categoryScale)[0]
138
- : mapContinuousScaleValues(
139
- [segment.category],
140
- categoryScale
141
- )[0];
142
- const [start, end] = mapContinuousScaleValues(
143
- [segment.start, segment.end],
144
- measureScale
145
- );
146
- return {
147
- x: vertical
148
- ? categoryCenter - thickness / 2
149
- : Math.min(start, end),
150
- y: vertical
151
- ? Math.min(start, end)
152
- : categoryCenter - thickness / 2,
153
- width: vertical ? thickness : Math.abs(start - end),
154
- height: vertical ? Math.abs(start - end) : thickness,
155
- fill: segment.color === undefined
156
- ? appearance.fill ?? config.fill ?? colors.get(segment.color)
157
- : colors.get(segment.color),
158
- ...resolveBarAppearance(config, existing[index]?.properties)
159
- };
63
+ const [a, b] = mapContinuousScaleValues([start, end], measureScale);
64
+ return { x: vertical ? position : Math.min(a, b), y: vertical ? Math.min(a, b) : position,
65
+ width: vertical ? width : Math.abs(a - b), height: vertical ? Math.abs(a - b) : width,
66
+ fill: fills?.[index] ?? config.barAppearance?.fill ?? config.fill ?? DEFAULT_BAR_FILL,
67
+ ...resolveBarAppearance(config, existing[index]?.properties),
68
+ ...(strokes === undefined ? {} : { stroke: strokes[index] }) };
160
69
  });
161
70
  }
@@ -1,5 +1,5 @@
1
+ import { readSeriesIdentity } from "../../grammar/pathSeries.js";
1
2
  import {
2
- countHistogramBins,
3
3
  findHistogramBinIndex,
4
4
  resolveHistogramBins
5
5
  } from "../../grammar/histogram.js";
@@ -7,14 +7,55 @@ import {
7
7
  mapContinuousScaleValues,
8
8
  mapOrdinalValues,
9
9
  readNominalField,
10
- readQuantitativeField
10
+ readQuantitativeField,
11
+ readScaleField
11
12
  } from "../../grammar/scales/index.js";
13
+ import { mapScaleConsumerValues } from "../scales/map.js";
12
14
  import { resolveBarColorLayout } from "../../grammar/bars/policy.js";
13
15
  import { layoutSeriesPartition } from "../../grammar/seriesLayout.js";
14
16
  import {
15
17
  DEFAULT_BAR_FILL,
16
18
  resolveBarAppearance
17
19
  } from "./resolve.js";
20
+ import {
21
+ readStatisticalWeights,
22
+ sumEntryWeights,
23
+ summarizeStatisticalWeights,
24
+ validateWeightedNumericFields
25
+ } from "../../grammar/weightedStatistics.js";
26
+
27
+ export function resolveHistogramInput(dataset, xEncoding) {
28
+ const allValues = readQuantitativeField(dataset.values, xEncoding.field);
29
+ if (xEncoding.weight === undefined) {
30
+ return {
31
+ rows: dataset.values,
32
+ values: allValues,
33
+ entries: dataset.values.map((row, index) => ({ row, index })),
34
+ mass(entries) {
35
+ return entries.length;
36
+ }
37
+ };
38
+ }
39
+ const weights = readStatisticalWeights(
40
+ dataset.values,
41
+ xEncoding.weight,
42
+ "Histogram"
43
+ );
44
+ validateWeightedNumericFields(weights.entries, [xEncoding.field], "Histogram");
45
+ const summary = summarizeStatisticalWeights(
46
+ weights.entries,
47
+ weights.definition.kind,
48
+ "Histogram data"
49
+ );
50
+ return {
51
+ rows: summary.positive.map(entry => entry.row),
52
+ values: summary.positive.map(entry => entry.row[xEncoding.field]),
53
+ entries: summary.positive,
54
+ mass(entries) {
55
+ return sumEntryWeights(entries, summary, "Histogram bin mass");
56
+ }
57
+ };
58
+ }
18
59
 
19
60
  export function deriveHistogramSegments({
20
61
  dataset,
@@ -23,7 +64,8 @@ export function deriveHistogramSegments({
23
64
  xScale,
24
65
  resolvedScales
25
66
  }) {
26
- const xValues = readQuantitativeField(dataset.values, xEncoding.field);
67
+ const input = resolveHistogramInput(dataset, xEncoding);
68
+ const { rows, values: xValues, entries } = input;
27
69
  const bins = resolveHistogramBins({
28
70
  values: xValues,
29
71
  bin: xEncoding.bin,
@@ -34,9 +76,37 @@ export function deriveHistogramSegments({
34
76
  const colorEncoding = layer.encoding?.color;
35
77
  const layout = resolveBarColorLayout(layer);
36
78
 
79
+ if (layer.encoding?.group !== undefined) {
80
+ const identity = readSeriesIdentity(rows, layer);
81
+ const cellRows = bins.boundaries.slice(0, -1).map(() => identity.domain.map(() => []));
82
+ const index = new Map(identity.domain.map((value, i) => [value, i]));
83
+ xValues.forEach((value, i) => { const bin = findHistogramBinIndex(value, bins.boundaries);
84
+ if (bin !== -1) cellRows[bin][index.get(identity.values[i])].push(entries[i]); });
85
+ return cellRows.flatMap((cells, bin) => layoutSeriesPartition(cells.map(input.mass), layout).map(segment => {
86
+ const members = cells[segment.index].map(entry => entry.row);
87
+ let color, colorValue;
88
+ if (colorEncoding !== undefined) {
89
+ const values = readNominalField(members, colorEncoding.field);
90
+ if (new Set(values).size !== 1) throw new Error("Histogram color requires one value within each bin/series cell.");
91
+ colorValue = values[0];
92
+ const scale = resolvedScales[colorEncoding.scale];
93
+ color = mapOrdinalValues([colorValue], scale.domain, scale.range)[0];
94
+ }
95
+ return { bin, start: bins.boundaries[bin], end: bins.boundaries[bin + 1],
96
+ category: segment.index, categoryCount: identity.domain.length,
97
+ stackStart: segment.start, stackEnd: segment.end, members,
98
+ ...(color === undefined ? {} : { color, colorValue }) };
99
+ }));
100
+ }
101
+
37
102
  if (colorEncoding?.scale === undefined) {
38
- return countHistogramBins(xValues, bins.boundaries).flatMap((count, bin) =>
39
- layoutSeriesPartition([count], layout).map(segment => ({
103
+ const cellRows = bins.boundaries.slice(0, -1).map(() => []);
104
+ xValues.forEach((value, index) => {
105
+ const bin = findHistogramBinIndex(value, bins.boundaries);
106
+ if (bin !== -1) cellRows[bin].push(entries[index]);
107
+ });
108
+ return cellRows.flatMap((cell, bin) =>
109
+ layoutSeriesPartition([input.mass(cell)], layout).map(segment => ({
40
110
  bin,
41
111
  start: bins.boundaries[bin],
42
112
  end: bins.boundaries[bin + 1],
@@ -44,9 +114,7 @@ export function deriveHistogramSegments({
44
114
  categoryCount: 1,
45
115
  stackStart: segment.start,
46
116
  stackEnd: segment.end,
47
- members: dataset.values.filter((_, index) =>
48
- findHistogramBinIndex(xValues[index], bins.boundaries) === bin
49
- )
117
+ members: cell.map(entry => entry.row)
50
118
  }))
51
119
  );
52
120
  }
@@ -57,24 +125,24 @@ export function deriveHistogramSegments({
57
125
  `Bar mark "${layer.id}" requires a resolved color scale.`
58
126
  );
59
127
  }
60
- const colorValues = readNominalField(dataset.values, colorEncoding.field);
128
+ const colorValues = readNominalField(rows, colorEncoding.field);
61
129
  mapOrdinalValues(colorValues, colorScale.domain, colorScale.range);
62
130
  const categoryIndex = new Map(
63
131
  colorScale.domain.map((value, index) => [value, index])
64
132
  );
65
- const counts = bins.boundaries.slice(0, -1).map(() =>
66
- colorScale.domain.map(() => 0)
133
+ const cells = bins.boundaries.slice(0, -1).map(() =>
134
+ colorScale.domain.map(() => [])
67
135
  );
68
136
 
69
137
  for (let index = 0; index < xValues.length; index += 1) {
70
138
  const bin = findHistogramBinIndex(xValues[index], bins.boundaries);
71
139
  const category = categoryIndex.get(colorValues[index]);
72
- if (bin !== -1 && category !== undefined) counts[bin][category] += 1;
140
+ if (bin !== -1 && category !== undefined) cells[bin][category].push(entries[index]);
73
141
  }
74
142
 
75
143
  const segments = [];
76
- for (let bin = 0; bin < counts.length; bin += 1) {
77
- const partition = layoutSeriesPartition(counts[bin], layout);
144
+ for (let bin = 0; bin < cells.length; bin += 1) {
145
+ const partition = layoutSeriesPartition(cells[bin].map(input.mass), layout);
78
146
  for (const segment of partition) {
79
147
  const category = segment.index;
80
148
  segments.push({
@@ -82,13 +150,10 @@ export function deriveHistogramSegments({
82
150
  start: bins.boundaries[bin],
83
151
  end: bins.boundaries[bin + 1],
84
152
  category,
85
- categoryCount: counts[bin].length,
153
+ categoryCount: cells[bin].length,
86
154
  stackStart: segment.start,
87
155
  stackEnd: segment.end,
88
- members: dataset.values.filter((_, index) =>
89
- findHistogramBinIndex(xValues[index], bins.boundaries) === bin &&
90
- categoryIndex.get(colorValues[index]) === category
91
- ),
156
+ members: cells[bin][category].map(entry => entry.row),
92
157
  color: mapOrdinalValues(
93
158
  [colorScale.domain[category]],
94
159
  colorScale.domain,
@@ -111,6 +176,24 @@ export function deriveHistogramRectangles(required, resolved) {
111
176
  const layout = resolveBarColorLayout(required.layer);
112
177
  const config = resolved.markConfigs[required.layer.id] ?? {};
113
178
  const appearance = config.barAppearance ?? {};
179
+ const stroke = required.layer.encoding?.stroke;
180
+ const strokeValues = stroke === undefined ? undefined : segments.map(segment => {
181
+ const values = readScaleField(
182
+ segment.members,
183
+ stroke.field,
184
+ stroke.fieldType,
185
+ { temporalUnit: stroke.temporalUnit }
186
+ );
187
+ if (new Set(values).size !== 1) {
188
+ throw new Error("Histogram stroke requires one value within each bin/series cell.");
189
+ }
190
+ return values[0];
191
+ });
192
+ const strokes = strokeValues === undefined ? undefined : mapScaleConsumerValues(
193
+ strokeValues,
194
+ resolved.resolvedScales[stroke.scale],
195
+ "stroke"
196
+ );
114
197
 
115
198
  return segments.map((segment, index) => {
116
199
  const [x1, x2] = mapContinuousScaleValues(
@@ -140,7 +223,8 @@ export function deriveHistogramRectangles(required, resolved) {
140
223
  config.fill ??
141
224
  existing[index]?.properties.fill ??
142
225
  DEFAULT_BAR_FILL,
143
- ...resolveBarAppearance(config, existing[index]?.properties)
226
+ ...resolveBarAppearance(config, existing[index]?.properties),
227
+ ...(strokes === undefined ? {} : { stroke: strokes[index] })
144
228
  };
145
229
  });
146
230
  }
@@ -1,4 +1,10 @@
1
- import { mapContinuousScaleValues, mapOrdinalPositionValues, mapOrdinalValues } from "../../grammar/scales/index.js";
1
+ import {
2
+ mapContinuousScaleValues,
3
+ mapOrdinalPositionValues,
4
+ mapOrdinalValues,
5
+ readScaleField
6
+ } from "../../grammar/scales/index.js";
7
+ import { mapScaleConsumerValues } from "../scales/map.js";
2
8
  import { resolveBarWidth } from "../../grammar/bars/geometry.js";
3
9
  import {
4
10
  DEFAULT_BAR_FILL,
@@ -36,6 +42,14 @@ export function deriveRangedRectangles(required, program, width) {
36
42
  const appearanceConfig = color === undefined
37
43
  ? config
38
44
  : { ...config, stroke: undefined };
45
+ const stroke = layer.encoding?.stroke;
46
+ const strokes = stroke === undefined ? undefined : mapScaleConsumerValues(
47
+ readScaleField(dataset.values, stroke.field, stroke.fieldType, {
48
+ temporalUnit: stroke.temporalUnit
49
+ }),
50
+ program.resolvedScales[stroke.scale],
51
+ "stroke"
52
+ );
39
53
  return dataset.values.map((_, index) => vertical ? {
40
54
  x: centers[index] - band / 2, y: Math.min(first[index], second[index]), width: band,
41
55
  height: Math.abs(second[index] - first[index]), fill: fills[index],
@@ -44,7 +58,8 @@ export function deriveRangedRectangles(required, program, width) {
44
58
  undefined,
45
59
  color === undefined ? DEFAULT_BAR_STROKE : fills[index],
46
60
  1
47
- )
61
+ ),
62
+ ...(strokes === undefined ? {} : { stroke: strokes[index] })
48
63
  } : {
49
64
  x: Math.min(first[index], second[index]), y: centers[index] - band / 2,
50
65
  width: Math.abs(second[index] - first[index]), height: band, fill: fills[index],
@@ -53,6 +68,7 @@ export function deriveRangedRectangles(required, program, width) {
53
68
  undefined,
54
69
  color === undefined ? DEFAULT_BAR_STROKE : fills[index],
55
70
  1
56
- )
71
+ ),
72
+ ...(strokes === undefined ? {} : { stroke: strokes[index] })
57
73
  });
58
74
  }
@@ -3,6 +3,7 @@ import { findDataset } from "../../selectors/datasets.js";
3
3
  import { findLayer } from "../../selectors/layers.js";
4
4
  import { findSemanticScale } from "../../selectors/scales.js";
5
5
  import { BAR_GRAINS, resolveBarGrain } from "../../grammar/bars/policy.js";
6
+ import { requestedStrokeDetails } from "../../grammar/strokeStyle.js";
6
7
 
7
8
  export const DEFAULT_BAR_FILL = DEFAULT_COLORS.mark;
8
9
  export const DEFAULT_BAR_STROKE = "white";
@@ -25,7 +26,8 @@ export function resolveBarAppearance(
25
26
  ? 0
26
27
  : appearance.strokeWidth ?? config.strokeWidth ??
27
28
  existing?.strokeWidth ?? DEFAULT_BAR_STROKE_WIDTH,
28
- ...(opacity === undefined ? {} : { opacity })
29
+ ...(opacity === undefined ? {} : { opacity }),
30
+ ...requestedStrokeDetails(appearance, "Bar mark")
29
31
  };
30
32
  }
31
33
 
@@ -40,9 +42,10 @@ export function requireCompleteBar(program, id) {
40
42
  throw new Error(`Bar mark "${id}" requires an existing dataset.`);
41
43
  }
42
44
  const graphic = program.graphicSpec.objects[id];
43
- const rectCollection = graphic?.type === "collection" &&
44
- graphic.items?.every(child => child.type === "rect");
45
- if (graphic?.type !== "rect" && !rectCollection) {
45
+ const roundedCollection = graphic?.type === "collection" &&
46
+ Object.hasOwn(program.markConfigs[id]?.barAppearance ?? {}, "cornerRadius") &&
47
+ graphic.items?.every(child => ["rect", "path"].includes(child.type));
48
+ if (graphic?.type !== "rect" && !roundedCollection) {
46
49
  throw new Error(`Bar mark "${id}" requires rect graphics.`);
47
50
  }
48
51
 
@@ -0,0 +1,40 @@
1
+ export function resolveCategorySlotOffset(program, layer) {
2
+ const policy = program.markConfigs[layer.id]?.categorySlotOffset;
3
+ if (policy === undefined) return undefined;
4
+ if (!["x", "y"].includes(policy.channel)) {
5
+ throw new Error(`Category slot offset for "${layer.id}" requires channel x or y.`);
6
+ }
7
+ if (!Number.isFinite(policy.band) || policy.band < -0.5 || policy.band > 0.5) {
8
+ throw new RangeError(`Category slot offset for "${layer.id}" must be within [-0.5, 0.5] band.`);
9
+ }
10
+ const encoding = layer.encoding?.[policy.channel];
11
+ if (!["nominal", "ordinal"].includes(encoding?.fieldType)) {
12
+ throw new Error(`Category slot offset for "${layer.id}" requires a categorical position.`);
13
+ }
14
+ const scale = program.resolvedScales[encoding.scale];
15
+ if (scale?.type !== "band" || !Number.isFinite(scale.bandwidth) || scale.bandwidth <= 0) {
16
+ throw new Error(`Category slot offset for "${layer.id}" requires a positive band scale.`);
17
+ }
18
+ return { channel: policy.channel, pixels: scale.bandwidth * policy.band };
19
+ }
20
+
21
+ export function offsetCategoryPositions(program, layer, positions) {
22
+ const offset = resolveCategorySlotOffset(program, layer);
23
+ if (offset === undefined || offset.pixels === 0) return positions;
24
+ const shift = values => values === undefined
25
+ ? undefined
26
+ : values.map(value => Number.isFinite(value) ? value + offset.pixels : value);
27
+ return {
28
+ ...positions,
29
+ [offset.channel]: shift(positions[offset.channel])
30
+ };
31
+ }
32
+
33
+ export function offsetCategoryRectangles(program, layer, rectangles) {
34
+ const offset = resolveCategorySlotOffset(program, layer);
35
+ if (offset === undefined || offset.pixels === 0) return rectangles;
36
+ return rectangles.map(rectangle => ({
37
+ ...rectangle,
38
+ [offset.channel]: rectangle[offset.channel] + offset.pixels
39
+ }));
40
+ }
@@ -121,6 +121,8 @@ export function clearCompositionChildren(program) {
121
121
  export function materializeCompositionGraphics(program) {
122
122
  program._assertCompositionProgram("materializeCompositionGraphics");
123
123
  const layout = resolveCompositionProgramLayout(program);
124
+ const background = program.graphicSpec.objects.canvas?.properties
125
+ ?.background ?? "white";
124
126
  let next = clearCompositionChildren(program);
125
127
  if (next.graphicSpec.objects.canvas === undefined) {
126
128
  next = next.createGraphics({ id: "canvas", type: "canvas" });
@@ -128,7 +130,7 @@ export function materializeCompositionGraphics(program) {
128
130
  for (const [property, value] of Object.entries({
129
131
  width: layout.width,
130
132
  height: layout.height,
131
- background: "white"
133
+ background
132
134
  })) {
133
135
  next = next.editGraphics({ target: "canvas", property, value });
134
136
  }
@@ -32,7 +32,7 @@ export function namespaceGraphicId(namespace, id) {
32
32
  const encode = value => Array.from(value, character =>
33
33
  character.codePointAt(0).toString(16).padStart(6, "0")
34
34
  ).join("");
35
- return `g${encode(namespace)}_${encode(id)}`;
35
+ return `g${encode(namespace)}_${id}`;
36
36
  }
37
37
 
38
38
  function rewriteItem(item, namespace) {