ggaction 0.0.13 → 0.0.16

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 (359) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +41 -12
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/knowledge/task-packet.schema.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +39 -7
  9. package/src/BasicChartProgram.js +3 -1
  10. package/src/ChartProgram.js +2 -0
  11. package/src/accessibility.js +157 -0
  12. package/src/actions/basic.js +2 -2
  13. package/src/actions/boxPlots/components.js +20 -12
  14. package/src/actions/boxPlots/create.js +4 -5
  15. package/src/actions/boxPlots/edit.js +34 -9
  16. package/src/actions/boxPlots/materialize.js +12 -4
  17. package/src/actions/boxPlots/options.js +48 -16
  18. package/src/actions/canvas/actions.js +3 -3
  19. package/src/actions/canvas/fitting.js +1 -1
  20. package/src/actions/categoryOrder/index.js +21 -12
  21. package/src/actions/charts/area.js +6 -2
  22. package/src/actions/charts/bar.js +3 -2
  23. package/src/actions/charts/beeswarm.js +1 -1
  24. package/src/actions/charts/density.js +7 -3
  25. package/src/actions/charts/ecdf.js +6 -3
  26. package/src/actions/charts/endpoints.js +29 -7
  27. package/src/actions/charts/heatmap.js +3 -2
  28. package/src/actions/charts/histogram.js +5 -4
  29. package/src/actions/charts/horizon.js +4 -2
  30. package/src/actions/charts/interval-regression.js +7 -4
  31. package/src/actions/charts/line.js +4 -2
  32. package/src/actions/charts/parallel.js +9 -3
  33. package/src/actions/charts/pie.js +11 -3
  34. package/src/actions/charts/polar.js +17 -6
  35. package/src/actions/charts/radar.js +9 -3
  36. package/src/actions/charts/radial.js +12 -4
  37. package/src/actions/charts/raincloud.js +2 -2
  38. package/src/actions/charts/rug-strip.js +8 -4
  39. package/src/actions/charts/scatter.js +4 -2
  40. package/src/actions/composition/actions.js +29 -24
  41. package/src/actions/coordinates/actions.js +2 -2
  42. package/src/actions/coordinates/edit.js +76 -0
  43. package/src/actions/data/basicBin2d.js +4 -5
  44. package/src/actions/data/bin.js +3 -3
  45. package/src/actions/data/bin2d.js +63 -193
  46. package/src/actions/data/bin2dMaterialize.js +4 -5
  47. package/src/actions/data/box.js +4 -4
  48. package/src/actions/data/complete.js +26 -0
  49. package/src/actions/data/computed.js +4 -4
  50. package/src/actions/data/create.js +7 -14
  51. package/src/actions/data/density.js +15 -23
  52. package/src/actions/data/derived.js +57 -31
  53. package/src/actions/data/ecdf.js +2 -2
  54. package/src/actions/data/edit.js +551 -0
  55. package/src/actions/data/filter.js +28 -22
  56. package/src/actions/data/fold.js +2 -2
  57. package/src/actions/data/gradientProfile.js +2 -2
  58. package/src/actions/data/horizon.js +2 -2
  59. package/src/actions/data/impute.js +26 -0
  60. package/src/actions/data/index.js +62 -47
  61. package/src/actions/data/interval.js +2 -2
  62. package/src/actions/data/intervalEdit.js +26 -18
  63. package/src/actions/data/normalize.js +27 -0
  64. package/src/actions/data/regression.js +21 -27
  65. package/src/actions/data/revise.js +48 -0
  66. package/src/actions/data/revisionBindings.js +125 -0
  67. package/src/actions/data/shared.js +20 -4
  68. package/src/actions/data/stack.js +2 -2
  69. package/src/actions/data/summary.js +3 -3
  70. package/src/actions/data/timeUnit.js +6 -3
  71. package/src/actions/data/window.js +3 -3
  72. package/src/actions/encodings/angle.js +1 -1
  73. package/src/actions/encodings/appearance.js +4 -4
  74. package/src/actions/encodings/barWidth.js +1 -1
  75. package/src/actions/encodings/channels.js +415 -0
  76. package/src/actions/encodings/color/index.js +3 -2
  77. package/src/actions/encodings/density.js +7 -4
  78. package/src/actions/encodings/histogram.js +4 -3
  79. package/src/actions/encodings/horizon.js +6 -8
  80. package/src/actions/encodings/index.js +4 -0
  81. package/src/actions/encodings/offset.js +22 -15
  82. package/src/actions/encodings/parallel.js +4 -5
  83. package/src/actions/encodings/pathOrder.js +6 -11
  84. package/src/actions/encodings/position/apply.js +53 -7
  85. package/src/actions/encodings/position/index.js +14 -6
  86. package/src/actions/encodings/position/policies/bar.js +9 -0
  87. package/src/actions/encodings/position/resolve.js +24 -7
  88. package/src/actions/encodings/ranged.js +5 -5
  89. package/src/actions/encodings/remove.js +13 -9
  90. package/src/actions/encodings/ruleAppearance.js +2 -24
  91. package/src/actions/encodings/seriesLayout.js +2 -1
  92. package/src/actions/encodings/shared.js +82 -14
  93. package/src/actions/encodings/stroke.js +226 -0
  94. package/src/actions/encodings/strokeDash.js +2 -2
  95. package/src/actions/encodings/text.js +1 -1
  96. package/src/actions/errorBands/create.js +34 -20
  97. package/src/actions/errorBands/edit.js +34 -137
  98. package/src/actions/errorBars/create.js +43 -20
  99. package/src/actions/errorBars/edit.js +29 -125
  100. package/src/actions/facets/actions.js +267 -73
  101. package/src/actions/facets/derive.js +99 -28
  102. package/src/actions/facets/guides.js +4 -5
  103. package/src/actions/facets/replay.js +4 -5
  104. package/src/actions/gradientPlots/components.js +15 -8
  105. package/src/actions/gradientPlots/create.js +4 -5
  106. package/src/actions/gradientPlots/edit.js +18 -9
  107. package/src/actions/gradientPlots/materialize.js +5 -3
  108. package/src/actions/gradientPlots/options.js +10 -2
  109. package/src/actions/gradientPlots/rebind.js +4 -5
  110. package/src/actions/guides/applicability.js +8 -2
  111. package/src/actions/guides/axes/axes.js +23 -4
  112. package/src/actions/guides/axes/axis.js +2 -2
  113. package/src/actions/guides/axes/edit.js +2 -2
  114. package/src/actions/guides/axes/labels.js +67 -21
  115. package/src/actions/guides/axes/lines.js +15 -9
  116. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  117. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  118. package/src/actions/guides/axes/parallel.js +5 -5
  119. package/src/actions/guides/axes/remove.js +4 -4
  120. package/src/actions/guides/axes/tickGroups.js +6 -5
  121. package/src/actions/guides/axes/ticks.js +19 -6
  122. package/src/actions/guides/axes/titles.js +21 -13
  123. package/src/actions/guides/grids/grid.js +18 -29
  124. package/src/actions/guides/grids/resolve.js +7 -2
  125. package/src/actions/guides/guides.js +20 -4
  126. package/src/actions/guides/legends/blocks.js +426 -0
  127. package/src/actions/guides/legends/categorical/actions.js +138 -52
  128. package/src/actions/guides/legends/categorical/components.js +55 -45
  129. package/src/actions/guides/legends/categorical/index.js +37 -33
  130. package/src/actions/guides/legends/categorical/layout.js +98 -16
  131. package/src/actions/guides/legends/categorical/options.js +2 -2
  132. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  133. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  134. package/src/actions/guides/legends/categorical/symbols.js +104 -52
  135. package/src/actions/guides/legends/continuous/common.js +76 -20
  136. package/src/actions/guides/legends/continuous/gradient.js +95 -158
  137. package/src/actions/guides/legends/continuous/index.js +2 -0
  138. package/src/actions/guides/legends/continuous/interval.js +21 -16
  139. package/src/actions/guides/legends/continuous/opacity.js +37 -83
  140. package/src/actions/guides/legends/continuous/stroke.js +225 -0
  141. package/src/actions/guides/legends/creation.js +14 -1
  142. package/src/actions/guides/legends/edit.js +159 -60
  143. package/src/actions/guides/legends/focused.js +5 -5
  144. package/src/actions/guides/legends/index.js +2 -0
  145. package/src/actions/guides/legends/lane.js +59 -24
  146. package/src/actions/guides/legends/lifecycle.js +19 -8
  147. package/src/actions/guides/legends/remove.js +17 -8
  148. package/src/actions/guides/legends/sampling.js +140 -0
  149. package/src/actions/guides/legends/size.js +100 -108
  150. package/src/actions/guides/legends/strokeWidth.js +38 -92
  151. package/src/actions/guides/legends/target.js +112 -1
  152. package/src/actions/guides/legends/transition.js +165 -16
  153. package/src/actions/guides/polar/axes/facade.js +8 -8
  154. package/src/actions/guides/polar/axes/labels.js +29 -10
  155. package/src/actions/guides/polar/axes/lines.js +19 -19
  156. package/src/actions/guides/polar/axes/shared.js +16 -0
  157. package/src/actions/guides/polar/axes/ticks.js +10 -16
  158. package/src/actions/guides/polar/axes/titles.js +6 -6
  159. package/src/actions/guides/polar/grids.js +12 -14
  160. package/src/actions/guides/polar/resolve.js +12 -8
  161. package/src/actions/index.js +16 -3
  162. package/src/actions/intervals/revision.js +126 -0
  163. package/src/actions/marks/arc/actions.js +80 -22
  164. package/src/actions/marks/area/actions.js +38 -13
  165. package/src/actions/marks/area/materialize.js +17 -1
  166. package/src/actions/marks/bar/create.js +12 -3
  167. package/src/actions/marks/bar/edit.js +21 -4
  168. package/src/actions/marks/bar/materialize.js +34 -40
  169. package/src/actions/marks/index.js +10 -1
  170. package/src/actions/marks/line/actions.js +40 -17
  171. package/src/actions/marks/line/materialize.js +29 -10
  172. package/src/actions/marks/point/create.js +9 -3
  173. package/src/actions/marks/point/edit.js +19 -7
  174. package/src/actions/marks/point/jitter.js +6 -8
  175. package/src/actions/marks/point/materialize.js +48 -22
  176. package/src/actions/marks/point/packing.js +6 -8
  177. package/src/actions/marks/rect/actions.js +36 -15
  178. package/src/actions/marks/references.js +602 -33
  179. package/src/actions/marks/remove.js +67 -41
  180. package/src/actions/marks/rule/actions.js +51 -14
  181. package/src/actions/marks/shared.js +1 -1
  182. package/src/actions/marks/text/actions.js +79 -14
  183. package/src/actions/marks/text/index.js +4 -0
  184. package/src/actions/marks/text/layout.js +157 -17
  185. package/src/actions/marks/text/removal.js +173 -0
  186. package/src/actions/marks/text/selection.js +72 -0
  187. package/src/actions/marks/tick/actions.js +25 -8
  188. package/src/actions/primitives/createGraphics.js +5 -4
  189. package/src/actions/primitives/editGraphics.js +31 -4
  190. package/src/actions/primitives/graphicProperties.js +6 -0
  191. package/src/actions/primitives/semantic.js +11 -3
  192. package/src/actions/primitives/semanticAction.js +90 -14
  193. package/src/actions/primitives/semanticValidation/dataset.js +2 -4
  194. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  195. package/src/actions/primitives/semanticValidation/index.js +9 -1
  196. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  197. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  198. package/src/actions/regression/components.js +36 -25
  199. package/src/actions/regression/create.js +13 -6
  200. package/src/actions/regression/edit.js +24 -11
  201. package/src/actions/resources/index.js +7 -0
  202. package/src/actions/resources/remove.js +173 -0
  203. package/src/actions/scales/channels.js +212 -0
  204. package/src/actions/scales/consumers/common.js +1 -1
  205. package/src/actions/scales/consumers/families.js +3 -2
  206. package/src/actions/scales/consumers/index.js +19 -2
  207. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  208. package/src/actions/scales/create.js +55 -23
  209. package/src/actions/scales/definitions.js +94 -40
  210. package/src/actions/scales/edit.js +67 -12
  211. package/src/actions/scales/editPolicy.js +81 -11
  212. package/src/actions/scales/index.js +6 -0
  213. package/src/actions/scales/materialize.js +5 -4
  214. package/src/actions/scales/offset.js +48 -0
  215. package/src/actions/scales/parallel.js +71 -0
  216. package/src/actions/scales/preview.js +111 -10
  217. package/src/actions/scales/quantitativeColor.js +4 -5
  218. package/src/actions/selection/actions.js +179 -66
  219. package/src/actions/selection/index.js +3 -0
  220. package/src/actions/textMetrics/index.js +56 -0
  221. package/src/actions/theme/actions.js +70 -15
  222. package/src/actions/theme/composition.js +238 -0
  223. package/src/actions/theme/index.js +9 -2
  224. package/src/actions/theme/reconcile.js +313 -121
  225. package/src/actions/theme/state.js +160 -0
  226. package/src/actions/titles/actions.js +8 -8
  227. package/src/actions/titles/resolve.js +3 -3
  228. package/src/actions/violinPlots/create.js +6 -3
  229. package/src/actions/violinPlots/edit.js +2 -1
  230. package/src/core/action.js +117 -35
  231. package/src/core/compositionState.js +6 -1
  232. package/src/core/diagnostics.js +16 -0
  233. package/src/core/extensionRegistry.js +27 -0
  234. package/src/core/font.js +18 -0
  235. package/src/core/identifiers.js +5 -4
  236. package/src/core/immutable.js +10 -3
  237. package/src/core/optionRequirements.js +8 -0
  238. package/src/core/programState.js +21 -1
  239. package/src/core/resourceReferences.js +341 -0
  240. package/src/core/textMetricProfile.js +28 -0
  241. package/src/core/textMetrics.js +24 -3
  242. package/src/core/validation.js +22 -10
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/diagnostics.js +1 -0
  245. package/src/grammar/bars/aggregate.js +4 -1
  246. package/src/grammar/bars/geometry.js +44 -0
  247. package/src/grammar/bin.js +54 -9
  248. package/src/grammar/complete.js +279 -0
  249. package/src/grammar/computed.js +265 -64
  250. package/src/grammar/coordinates.js +37 -0
  251. package/src/grammar/density.js +239 -37
  252. package/src/grammar/displayLabels.js +57 -0
  253. package/src/grammar/facets/dependencies.js +133 -1
  254. package/src/grammar/facets/guides.js +3 -0
  255. package/src/grammar/facets/index.js +7 -47
  256. package/src/grammar/facets/scales.js +15 -18
  257. package/src/grammar/impute.js +341 -0
  258. package/src/grammar/markLabelSelection.js +57 -0
  259. package/src/grammar/markLabels.js +18 -1
  260. package/src/grammar/normalize.js +366 -0
  261. package/src/grammar/pathSeries.js +26 -8
  262. package/src/grammar/pointShapes.js +13 -4
  263. package/src/grammar/polar.js +90 -8
  264. package/src/grammar/regression/index.js +1 -0
  265. package/src/grammar/regression/parameters.js +23 -0
  266. package/src/grammar/roundedRect.js +180 -0
  267. package/src/grammar/rules.js +1 -0
  268. package/src/grammar/scales/colorConsumers.js +20 -1
  269. package/src/grammar/scales/continuous.js +2 -2
  270. package/src/grammar/scales/definition.js +27 -4
  271. package/src/grammar/scales/index.js +1 -0
  272. package/src/grammar/scales/ordinal.js +12 -3
  273. package/src/grammar/scales/policies.js +2 -2
  274. package/src/grammar/scales/size.js +456 -0
  275. package/src/grammar/scales/types.js +8 -0
  276. package/src/grammar/schemas/concreteGraphic.js +14 -2
  277. package/src/grammar/schemas/graphic.js +15 -0
  278. package/src/grammar/schemas/graphicBounds.js +190 -41
  279. package/src/grammar/schemas/semanticPath.js +7 -2
  280. package/src/grammar/statisticalReference.js +103 -0
  281. package/src/grammar/strokeStyle.js +77 -0
  282. package/src/grammar/summary.js +78 -16
  283. package/src/grammar/timeUnit.js +216 -5
  284. package/src/grammar/transformTopology.js +4 -0
  285. package/src/grammar/transforms.js +394 -28
  286. package/src/grammar/weightedStatistics.js +374 -0
  287. package/src/grammar/window.js +221 -16
  288. package/src/layout/aspect.js +80 -0
  289. package/src/layout/facets.js +59 -5
  290. package/src/layout/labels.js +351 -7
  291. package/src/layout/legend.js +4 -4
  292. package/src/layout/legendItems.js +6 -6
  293. package/src/layout/text.js +10 -9
  294. package/src/layout/title.js +9 -9
  295. package/src/materialization/bars/aggregate.js +25 -2
  296. package/src/materialization/bars/histogram.js +84 -24
  297. package/src/materialization/bars/ranged.js +19 -3
  298. package/src/materialization/bars/resolve.js +7 -4
  299. package/src/materialization/composition.js +3 -1
  300. package/src/materialization/coordinateBounds.js +204 -0
  301. package/src/materialization/dependencies.js +36 -0
  302. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  303. package/src/materialization/facetGuides/placement.js +9 -3
  304. package/src/materialization/facetGuides/preparation.js +13 -8
  305. package/src/materialization/facetHeaders.js +347 -0
  306. package/src/materialization/facets.js +15 -65
  307. package/src/materialization/guides/layout.js +3 -3
  308. package/src/materialization/guides/resources.js +26 -0
  309. package/src/materialization/legends.js +1 -1
  310. package/src/materialization/marks/index.js +11 -5
  311. package/src/materialization/marks/policies.js +2 -1
  312. package/src/materialization/rect.js +25 -12
  313. package/src/materialization/rectConfig.js +5 -1
  314. package/src/materialization/scaleGuideDependencies.js +10 -0
  315. package/src/materialization/scales/map.js +5 -3
  316. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  317. package/src/materialization/scales/policies/offset.js +50 -8
  318. package/src/materialization/scales/policies/series.js +4 -1
  319. package/src/materialization/scales/resolve.js +22 -6
  320. package/src/materialization/selection/items/arc.js +28 -4
  321. package/src/materialization/selection/items/bar.js +30 -3
  322. package/src/materialization/selection/items/common.js +20 -5
  323. package/src/materialization/selection/items/rect.js +28 -4
  324. package/src/materialization/selection/path.js +1 -1
  325. package/src/materialization/selection/point.js +3 -1
  326. package/src/materialization/text.js +265 -14
  327. package/src/materialization/typography.js +31 -0
  328. package/src/mcp/cli.js +5 -3
  329. package/src/persistence/codec.js +65 -0
  330. package/src/persistence/validation.js +185 -0
  331. package/src/persistence.js +74 -0
  332. package/src/renderers/canvas/circle.js +2 -3
  333. package/src/renderers/canvas/index.js +5 -4
  334. package/src/renderers/canvas/line.js +2 -3
  335. package/src/renderers/canvas/native.js +4 -4
  336. package/src/renderers/canvas/path.js +2 -3
  337. package/src/renderers/canvas/rect.js +2 -3
  338. package/src/renderers/canvas/stroke.js +11 -0
  339. package/src/renderers/nativeBackend.js +10 -0
  340. package/src/renderers/options.js +13 -0
  341. package/src/renderers/pdf.js +25 -54
  342. package/src/renderers/png.js +22 -17
  343. package/src/renderers/svg.js +29 -14
  344. package/src/renderers/text.js +1 -11
  345. package/src/selectors/coordinates.js +3 -1
  346. package/src/selectors/datasets.js +38 -5
  347. package/src/selectors/layers.js +10 -4
  348. package/src/selectors/markOwners.js +46 -0
  349. package/src/selectors/scales.js +6 -4
  350. package/src/theme/defaults.js +103 -1
  351. package/src/version.js +2 -0
  352. package/types/accessibility.d.ts +37 -0
  353. package/types/basic.d.ts +10 -2
  354. package/types/diagnostics.d.ts +15 -0
  355. package/types/index.d.ts +90 -0
  356. package/types/pdf.d.ts +9 -0
  357. package/types/persistence.d.ts +7 -0
  358. package/types/png.d.ts +9 -0
  359. package/types/program.d.ts +944 -118
@@ -4,6 +4,7 @@ import {
4
4
  isOwned,
5
5
  isPlainObject
6
6
  } from "./immutable.js";
7
+ import { removeMaterializationConfig } from "./materializationState.js";
7
8
 
8
9
  export function ownProgramState(value) {
9
10
  return isOwned(value) ? value : cloneAndFreeze(value);
@@ -22,7 +23,26 @@ export function ownChildPrograms(children, ProgramClass) {
22
23
  if (!(program instanceof ProgramClass)) {
23
24
  throw new TypeError(`ChartProgram child "${id}" must be a ChartProgram.`);
24
25
  }
25
- owned[id] = program;
26
+ Object.defineProperty(owned, id, { value: program, enumerable: true });
26
27
  }
27
28
  return freezeOwned(owned);
28
29
  }
30
+
31
+ // Semantic entries, resolved scale caches, and physical current-resource IDs
32
+ // are removed by editSemantic. A logical data owner also retains its own ID
33
+ // and replay configuration; release those non-semantic references after the
34
+ // domain's dependency validation and before the primitive deletion.
35
+ export function releaseNamedResourceOwnership(program, { kind, id, dataOwner }) {
36
+ if (kind !== "data") return program;
37
+ const materializationConfigs = dataOwner === undefined
38
+ ? program.materializationConfigs
39
+ : removeMaterializationConfig(
40
+ program.materializationConfigs,
41
+ ["data", dataOwner.family, dataOwner.owner]
42
+ ).value;
43
+ const context = program.context.currentData === id
44
+ ? freezeOwned({ ...program.context, currentData: undefined })
45
+ : program.context;
46
+ if (context === program.context && materializationConfigs === program.materializationConfigs) return program;
47
+ return program._clone({ context, materializationConfigs });
48
+ }
@@ -0,0 +1,341 @@
1
+ import { freezeOwned, isPlainObject } from "./immutable.js";
2
+ import { validateUserId } from "./identifiers.js";
3
+
4
+ export const RESOURCE_KINDS = Object.freeze([
5
+ "data", "scale", "coordinate", "mark", "selection"
6
+ ]);
7
+
8
+ const DATA_VALUE_KEYS = new Set([
9
+ "data", "dataset", "dataId", "filteredData", "originalData",
10
+ "outlierDataId", "profileId", "summaryId"
11
+ ]);
12
+ const DATA_SOURCE_CONFIGS = new Set([
13
+ "boxPlot", "density", "ecdfPlot", "endpointPlot", "errorBand", "errorBar",
14
+ "gradientPlot", "horizon", "intervalPlot", "markFilter", "raincloudPlot",
15
+ "regression", "violinPlot"
16
+ ]);
17
+ const MARK_ID_KEYS = new Set([
18
+ "bandId", "bodyId", "centerId", "connectorId", "intervalId", "lineId",
19
+ "lowerBoundaryId", "lowerCapId", "medianId", "outlierId", "startId",
20
+ "stemId", "upperBoundaryId", "upperCapId", "whiskerId"
21
+ ]);
22
+
23
+ function ownedPath(path) {
24
+ return freezeOwned([...path]);
25
+ }
26
+
27
+ function reference(kind, id, ownerKind, ownerId, path, strength = "live") {
28
+ if (typeof id !== "string" || id.length === 0) return undefined;
29
+ return freezeOwned({
30
+ kind,
31
+ id,
32
+ ownerKind,
33
+ ownerId,
34
+ path: ownedPath(path),
35
+ strength
36
+ });
37
+ }
38
+
39
+ function append(references, value) {
40
+ if (value !== undefined) references.push(value);
41
+ }
42
+
43
+ function collectKnownKeys(value, path, visit) {
44
+ if (Array.isArray(value)) {
45
+ value.forEach((item, index) => collectKnownKeys(item, [...path, index], visit));
46
+ return;
47
+ }
48
+ if (!isPlainObject(value)) return;
49
+ for (const [key, child] of Object.entries(value)) {
50
+ visit(key, child, [...path, key], path);
51
+ collectKnownKeys(child, [...path, key], visit);
52
+ }
53
+ }
54
+
55
+ export function canonicalResourcePath(path) {
56
+ if (!Array.isArray(path)) {
57
+ throw new TypeError("Resource reference path must be an array.");
58
+ }
59
+ return path.map(part => {
60
+ if (Number.isInteger(part) && part >= 0) return `[${part}]`;
61
+ if (typeof part !== "string" || part.length === 0) {
62
+ throw new TypeError("Resource reference path parts must be names or indexes.");
63
+ }
64
+ return `.${part}`;
65
+ }).join("");
66
+ }
67
+
68
+ export function collectSemanticDataReferences(program) {
69
+ const references = [];
70
+ for (const layer of program.semanticSpec.layers) {
71
+ append(references, reference("data", layer.data, "layer", layer.id, ["data"]));
72
+ }
73
+ for (const dataset of program.semanticSpec.datasets) {
74
+ append(references, reference(
75
+ "data", dataset.source, "dataset", dataset.id, ["source"]
76
+ ));
77
+ }
78
+ return freezeOwned(references);
79
+ }
80
+
81
+ export function collectSemanticScaleReferences(program) {
82
+ const references = [];
83
+ for (const layer of program.semanticSpec.layers) {
84
+ for (const [channel, encoding] of Object.entries(layer.encoding ?? {})) {
85
+ append(references, reference(
86
+ "scale", encoding?.scale, "layer", layer.id,
87
+ ["encoding", channel, "scale"]
88
+ ));
89
+ if (channel === "parallel") {
90
+ for (const [index, dimension] of (encoding?.dimensions ?? []).entries()) {
91
+ append(references, reference(
92
+ "scale", dimension.scale, "layer", layer.id,
93
+ ["encoding", "parallel", "dimensions", index, "scale"]
94
+ ));
95
+ }
96
+ }
97
+ }
98
+ }
99
+ collectKnownKeys(program.semanticSpec.guides, [], (key, value, path) => {
100
+ if (key === "scale") {
101
+ append(references, reference(
102
+ "scale", value, "semanticGuide", "guides", path
103
+ ));
104
+ } else if (key === "scales" && Array.isArray(value)) {
105
+ value.forEach((id, index) => append(references, reference(
106
+ "scale", id, "semanticGuide", "guides", [...path, index]
107
+ )));
108
+ }
109
+ });
110
+ return freezeOwned(references);
111
+ }
112
+
113
+ export function collectSemanticCoordinateReferences(program) {
114
+ const references = [];
115
+ for (const layer of program.semanticSpec.layers) {
116
+ append(references, reference(
117
+ "coordinate", layer.coordinate, "layer", layer.id, ["coordinate"]
118
+ ));
119
+ }
120
+ collectKnownKeys(program.semanticSpec.guides, [], (key, value, path) => {
121
+ if (key === "coordinate") {
122
+ append(references, reference(
123
+ "coordinate", value, "semanticGuide", "guides", path
124
+ ));
125
+ }
126
+ });
127
+ return freezeOwned(references);
128
+ }
129
+
130
+ export function collectMarkConfigReferences(program) {
131
+ const references = [];
132
+ for (const layer of program.semanticSpec.layers) {
133
+ append(references, reference(
134
+ "mark", layer.source, "layer", layer.id, ["source"]
135
+ ));
136
+ }
137
+ for (const [ownerId, config] of Object.entries(program.markConfigs)) {
138
+ collectKnownKeys(config, [], (key, value, path, parentPath) => {
139
+ if (DATA_VALUE_KEYS.has(key)) {
140
+ append(references, reference("data", value, "markConfig", ownerId, path));
141
+ }
142
+ const root = parentPath[0];
143
+ if (key === "source" && DATA_SOURCE_CONFIGS.has(root)) {
144
+ append(references, reference("data", value, "markConfig", ownerId, path));
145
+ }
146
+ if (key === "coordinate") {
147
+ append(references, reference(
148
+ "coordinate", value, "markConfig", ownerId, path
149
+ ));
150
+ }
151
+ const reservedScale = root === "regression" && key === "colorScale" && config.regression.groupBy === undefined;
152
+ if (!reservedScale && (key === "scale" || key.endsWith("Scale")) && typeof value === "string") {
153
+ append(references, reference("scale", value, "markConfig", ownerId, path));
154
+ } else if (key === "scale" && isPlainObject(value)) {
155
+ append(references, reference(
156
+ "scale", value.id, "markConfig", ownerId, [...path, "id"]
157
+ ));
158
+ }
159
+ const inactiveMark = (root === "boxPlot" && key === "outlierId" && !Object.hasOwn(program.markConfigs, value)) ||
160
+ (root === "errorBar" && ["lowerCapId", "upperCapId"].includes(key) && config.errorBar.caps === false) ||
161
+ (root === "errorBand" && ["lowerBoundaryId", "upperBoundaryId"].includes(key) &&
162
+ !Object.hasOwn(program.markConfigs, value));
163
+ if (MARK_ID_KEYS.has(key) && !inactiveMark) {
164
+ append(references, reference("mark", value, "markConfig", ownerId, path));
165
+ }
166
+ if (
167
+ (root === "errorBandBoundary" && key === "owner") ||
168
+ (parentPath.length === 0 && key === "boxSpanOwner") ||
169
+ (root === "raincloudPlot" && (
170
+ key === "ownerId" || parentPath[1] === "childIds" ||
171
+ parentPath[1] === "ownedChildIds"
172
+ ))
173
+ ) {
174
+ append(references, reference("mark", value, "markConfig", ownerId, path));
175
+ }
176
+ });
177
+ append(references, reference(
178
+ "mark", config.statisticalReference?.source,
179
+ "markConfig", ownerId, ["statisticalReference", "source"]
180
+ ));
181
+ for (const [index, id] of (
182
+ config.raincloudPlot?.ownedChildIds ?? []
183
+ ).entries()) {
184
+ append(references, reference(
185
+ "mark", id, "markConfig", ownerId,
186
+ ["raincloudPlot", "ownedChildIds", index]
187
+ ));
188
+ }
189
+ const selection = config.labelAuthoring?.selection;
190
+ if (selection?.kind === "named") {
191
+ append(references, reference(
192
+ "selection", selection.id, "markConfig", ownerId,
193
+ ["labelAuthoring", "selection", "id"]
194
+ ));
195
+ }
196
+ }
197
+ return freezeOwned(references);
198
+ }
199
+
200
+ export function collectGuideConfigReferences(program) {
201
+ const references = [];
202
+ for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
203
+ collectKnownKeys(configs, [], (key, value, path, parentPath) => {
204
+ const ownerId = parentPath.length === 0 ? family : parentPath.join(".");
205
+ const relativePath = parentPath.length === 0
206
+ ? path
207
+ : path.slice(parentPath.length);
208
+ if (key === "scale") {
209
+ append(references, reference(
210
+ "scale", value, family, ownerId, relativePath
211
+ ));
212
+ } else if (key === "scales" && Array.isArray(value)) {
213
+ value.forEach((id, index) => append(references, reference(
214
+ "scale", id, family, ownerId, [...relativePath, index]
215
+ )));
216
+ } else if (key === "coordinate") {
217
+ append(references, reference(
218
+ "coordinate", value, family, ownerId, relativePath
219
+ ));
220
+ } else if (key === "target") {
221
+ append(references, reference(
222
+ "mark", value, family, ownerId, relativePath
223
+ ));
224
+ }
225
+ });
226
+ }
227
+ return freezeOwned(references);
228
+ }
229
+
230
+ export function collectSelectionReferences(program) {
231
+ const references = [];
232
+ for (const [id, config] of Object.entries(
233
+ program.materializationConfigs.selections ?? {}
234
+ )) {
235
+ append(references, reference(
236
+ "mark", config.target, "selection", id, ["target"]
237
+ ));
238
+ }
239
+ for (const [id, config] of Object.entries(
240
+ program.materializationConfigs.highlights ?? {}
241
+ )) {
242
+ append(references, reference(
243
+ "mark", config.target, "highlight", id, ["target"]
244
+ ));
245
+ append(references, reference(
246
+ "selection", config.selection, "highlight", id, ["selection"]
247
+ ));
248
+ }
249
+ return freezeOwned(references);
250
+ }
251
+
252
+ export function collectDataOwnerReferences(program) {
253
+ const references = [];
254
+ for (const [family, owners] of Object.entries(
255
+ program.materializationConfigs.data ?? {}
256
+ )) {
257
+ for (const [owner, config] of Object.entries(owners ?? {})) {
258
+ for (const key of ["current", "source", "previous"]) {
259
+ append(references, reference(
260
+ "data", config?.[key], "dataOwner", owner,
261
+ ["data", family, owner, key]
262
+ ));
263
+ }
264
+ }
265
+ }
266
+ return freezeOwned(references);
267
+ }
268
+
269
+ export function collectCompositionReferences(program) {
270
+ const references = [];
271
+ const composition = program.compositionSpec;
272
+ if (composition?.type !== "facet") return freezeOwned(references);
273
+ append(references, reference(
274
+ "data", composition.facet.data, "composition", composition.id,
275
+ ["facet", "data"]
276
+ ));
277
+ append(references, reference(
278
+ "mark", composition.facet.repeat?.target, "composition", composition.id,
279
+ ["facet", "repeat", "target"]
280
+ ));
281
+ return freezeOwned(references);
282
+ }
283
+
284
+ export function collectContextReferences(program) {
285
+ const references = [];
286
+ for (const [key, kind] of Object.entries({
287
+ currentData: "data",
288
+ currentScale: "scale",
289
+ currentCoordinate: "coordinate",
290
+ currentMark: "mark",
291
+ currentSelection: "selection"
292
+ })) {
293
+ append(references, reference(
294
+ kind, program.context[key], "context", "program", [key], "context"
295
+ ));
296
+ }
297
+ return freezeOwned(references);
298
+ }
299
+
300
+ function referenceKey(value) {
301
+ return [
302
+ value.kind, value.id, value.ownerKind, value.ownerId,
303
+ canonicalResourcePath(value.path), value.strength
304
+ ].join("\u0000");
305
+ }
306
+
307
+ function compareReferences(left, right) {
308
+ const leftKey = [
309
+ left.ownerKind, left.ownerId, canonicalResourcePath(left.path),
310
+ left.kind, left.id, left.strength
311
+ ].join("\u0000");
312
+ const rightKey = [
313
+ right.ownerKind, right.ownerId, canonicalResourcePath(right.path),
314
+ right.kind, right.id, right.strength
315
+ ].join("\u0000");
316
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
317
+ }
318
+
319
+ export function collectResourceReferences(program, { kind, id }) {
320
+ if (!RESOURCE_KINDS.includes(kind)) {
321
+ throw new Error(`Unknown resource kind "${kind}".`);
322
+ }
323
+ const target = validateUserId(id, "Resource id");
324
+ const combined = collectAllResourceReferences(program).filter(value => value.kind === kind && value.id === target);
325
+ const unique = new Map(combined.map(value => [referenceKey(value), value]));
326
+ return freezeOwned([...unique.values()].sort(compareReferences));
327
+ }
328
+
329
+ export function collectAllResourceReferences(program) {
330
+ return [
331
+ ...collectSemanticDataReferences(program),
332
+ ...collectSemanticScaleReferences(program),
333
+ ...collectSemanticCoordinateReferences(program),
334
+ ...collectMarkConfigReferences(program),
335
+ ...collectGuideConfigReferences(program),
336
+ ...collectSelectionReferences(program),
337
+ ...collectDataOwnerReferences(program),
338
+ ...collectCompositionReferences(program),
339
+ ...collectContextReferences(program)
340
+ ];
341
+ }
@@ -0,0 +1,28 @@
1
+ import { cloneAndFreeze } from "./immutable.js";
2
+ import { validateUserId } from "./identifiers.js";
3
+ import { validateOptionObject, validatePositiveFinite, validateNonNegativeFinite, validateNonEmptyString } from "./validation.js";
4
+
5
+ import { textMetricKey } from "./font.js";
6
+
7
+ export function normalizeTextMetricProfile(profile) {
8
+ validateOptionObject(profile, ["schemaVersion", "id", "measurements"], "Text metrics profile");
9
+ if (profile.schemaVersion !== 1) throw new Error("Text metrics require schemaVersion 1.");
10
+ validateUserId(profile.id, "Text metrics profile id");
11
+ if (!Array.isArray(profile.measurements)) throw new TypeError("Text metrics measurements must be an array.");
12
+ const seen = new Set();
13
+ for (const measurement of profile.measurements) {
14
+ validateOptionObject(measurement, ["text", "fontFamily", "fontSize", "fontWeight", "width"], "Text measurement");
15
+ if (typeof measurement.text !== "string") throw new TypeError("Text measurement text must be a string.");
16
+ validateNonEmptyString(measurement.fontFamily, "Text measurement fontFamily");
17
+ validatePositiveFinite(measurement.fontSize, "Text measurement fontSize");
18
+ validateNonNegativeFinite(measurement.width, "Text measurement width");
19
+ if (!Number.isInteger(measurement.fontWeight) || measurement.fontWeight < 100 ||
20
+ measurement.fontWeight > 900 || measurement.fontWeight % 100 !== 0) {
21
+ throw new RangeError("Text measurement fontWeight must be 100–900 in steps of 100.");
22
+ }
23
+ const key = textMetricKey(measurement);
24
+ if (seen.has(key)) throw new Error("Duplicate text measurement combination.");
25
+ seen.add(key);
26
+ }
27
+ return cloneAndFreeze(profile);
28
+ }
@@ -1,3 +1,21 @@
1
+ import { DEFAULT_FONT_FAMILY, textMetricFontWeight, textMetricKey } from "./font.js";
2
+
3
+ import { isOwned } from "./immutable.js";
4
+
5
+ const measurementIndexes = new WeakMap();
6
+
7
+ function measuredWidth(profile, text, style) {
8
+ if (profile === undefined) return undefined;
9
+ let index = measurementIndexes.get(profile);
10
+ if (index === undefined) {
11
+ index = new Map(profile.measurements.map(measurement =>
12
+ [textMetricKey(measurement), measurement.width]));
13
+ if (isOwned(profile)) measurementIndexes.set(profile, index);
14
+ }
15
+ return index.get(textMetricKey({ text, fontFamily: style.fontFamily ?? DEFAULT_FONT_FAMILY,
16
+ fontSize: style.fontSize, fontWeight: textMetricFontWeight(style.fontWeight) }));
17
+ }
18
+
1
19
  function codePointWidth(codePoint) {
2
20
  if (/\s/u.test(codePoint)) return 0.28;
3
21
  if (/[iIl.,:;!'|]/u.test(codePoint)) return 0.27;
@@ -25,7 +43,8 @@ export function textBoundsFitCanvas(bounds, canvas) {
25
43
 
26
44
  export function measureTextWidth(
27
45
  text,
28
- { fontSize, fontFamily, fontWeight } = {}
46
+ { fontSize, fontFamily, fontWeight } = {},
47
+ profile
29
48
  ) {
30
49
  if (typeof text !== "string") {
31
50
  throw new TypeError("Text measurement requires a string.");
@@ -33,6 +52,8 @@ export function measureTextWidth(
33
52
  if (!Number.isFinite(fontSize) || fontSize <= 0) {
34
53
  throw new RangeError("Text measurement requires a positive fontSize.");
35
54
  }
55
+ const measured = measuredWidth(profile, text, { fontSize, fontFamily, fontWeight });
56
+ if (measured !== undefined) return measured;
36
57
  let joined = false;
37
58
  let width = 0;
38
59
  for (const codePoint of text) {
@@ -76,11 +97,11 @@ export function resolveTextBounds({
76
97
  textAlign = "left",
77
98
  textBaseline = "alphabetic",
78
99
  rotation = 0
79
- } = {}) {
100
+ } = {}, profile) {
80
101
  if (![x, y, rotation].every(Number.isFinite)) {
81
102
  throw new TypeError("Text bounds require finite x, y, and rotation values.");
82
103
  }
83
- const width = measureTextWidth(text, { fontSize, fontFamily, fontWeight });
104
+ const width = measureTextWidth(text, { fontSize, fontFamily, fontWeight }, profile);
84
105
  const [left, right] = textAlign === "center"
85
106
  ? [-width / 2, width / 2]
86
107
  : ["right", "end"].includes(textAlign) ? [-width, 0] : [0, width];
@@ -1,15 +1,27 @@
1
+ import { annotateError } from "./diagnostics.js";
1
2
  import { isPlainObject } from "./immutable.js";
2
3
 
3
4
  export const MAX_GENERATED_ITEMS = 10_000;
4
5
  export const MAX_WORK_ITEMS = 10_000_000;
5
6
 
7
+ export function validateDataRows(values, label = "Dataset") {
8
+ if (!Array.isArray(values)) {
9
+ throw annotateError(new TypeError(`${label} requires values to be an array.`), { code: "invalid-value", optionPath: "values" });
10
+ }
11
+ for (let index = 0; index < values.length; index += 1) {
12
+ if (!Object.hasOwn(values, index) || !isPlainObject(values[index])) {
13
+ throw annotateError(new TypeError(`${label} requires every row to be a plain object; invalid row at index ${index}.`), { code: "invalid-value", optionPath: `values[${index}]` });
14
+ }
15
+ }
16
+ }
17
+
6
18
  export function validateGeneratedItemLimit(
7
19
  value,
8
20
  label,
9
21
  maximum = MAX_GENERATED_ITEMS
10
22
  ) {
11
23
  if (value > maximum) {
12
- throw new RangeError(`${label} must not exceed ${maximum}.`);
24
+ throw annotateError(new RangeError(`${label} must not exceed ${maximum}.`), { code: "resource-limit", optionPath: label, limit: maximum, actual: value });
13
25
  }
14
26
  return value;
15
27
  }
@@ -21,7 +33,7 @@ export function validateWorkLimit(value, label) {
21
33
  export function validateKeys(value, supported, label) {
22
34
  for (const key of Object.keys(value)) {
23
35
  if (!supported.includes(key)) {
24
- throw new Error(`Unknown ${label} option "${key}".`);
36
+ throw annotateError(new Error(`Unknown ${label} option "${key}".`), { code: "invalid-option", optionPath: key });
25
37
  }
26
38
  }
27
39
  }
@@ -33,18 +45,18 @@ export function validateOptionObject(value, supported, label, {
33
45
  emptyError = TypeError
34
46
  } = {}) {
35
47
  if (!isPlainObject(value)) {
36
- throw new TypeError(plainObjectMessage);
48
+ throw annotateError(new TypeError(plainObjectMessage), { code: "invalid-option" });
37
49
  }
38
50
  if (supported !== undefined) validateKeys(value, supported, label);
39
51
  if (!allowEmpty && Object.keys(value).length === 0) {
40
- throw new emptyError(emptyMessage);
52
+ throw annotateError(new emptyError(emptyMessage), { code: "invalid-option" });
41
53
  }
42
54
  return value;
43
55
  }
44
56
 
45
57
  export function noOptions(args, operation) {
46
58
  if (!isPlainObject(args) || Object.keys(args).length > 0) {
47
- throw new Error(`${operation} does not accept options.`);
59
+ throw annotateError(new Error(`${operation} does not accept options.`), { code: "invalid-option", operation });
48
60
  }
49
61
  }
50
62
 
@@ -59,30 +71,30 @@ export function sameOrderedValues(left, right) {
59
71
 
60
72
  export function validateNonEmptyString(value, label) {
61
73
  if (typeof value !== "string" || value.length === 0) {
62
- throw new TypeError(`${label} must be a non-empty string.`);
74
+ throw annotateError(new TypeError(`${label} must be a non-empty string.`), { code: "invalid-value", optionPath: label });
63
75
  }
64
76
  return value;
65
77
  }
66
78
 
67
79
  export function validateUnitInterval(value, label) {
68
80
  if (!Number.isFinite(value) || value < 0 || value > 1) {
69
- throw new RangeError(
81
+ throw annotateError(new RangeError(
70
82
  `${label} must be between 0 and 1 (values from 0 to 1).`
71
- );
83
+ ), { code: "invalid-value", optionPath: label });
72
84
  }
73
85
  return value;
74
86
  }
75
87
 
76
88
  export function validatePositiveFinite(value, label) {
77
89
  if (!Number.isFinite(value) || value <= 0) {
78
- throw new RangeError(`${label} must be a positive finite number.`);
90
+ throw annotateError(new RangeError(`${label} must be a positive finite number.`), { code: "invalid-value", optionPath: label });
79
91
  }
80
92
  return value;
81
93
  }
82
94
 
83
95
  export function validateNonNegativeFinite(value, label) {
84
96
  if (!Number.isFinite(value) || value < 0) {
85
- throw new RangeError(`${label} must be a non-negative finite number.`);
97
+ throw annotateError(new RangeError(`${label} must be a non-negative finite number.`), { code: "invalid-value", optionPath: label });
86
98
  }
87
99
  return value;
88
100
  }
@@ -13,13 +13,13 @@ export const MARK_TYPES = Object.freeze([
13
13
  export const MARK_GRAPHIC_TYPES = Object.freeze({
14
14
  point: Object.freeze(["circle", "rect", "path", "collection"]),
15
15
  line: Object.freeze(["path"]),
16
- bar: Object.freeze(["rect"]),
16
+ bar: Object.freeze(["rect", "collection"]),
17
17
  area: Object.freeze(["path"]),
18
18
  arc: Object.freeze(["path"]),
19
19
  rule: Object.freeze(["line"]),
20
20
  tick: Object.freeze(["line"]),
21
21
  text: Object.freeze(["text"]),
22
- rect: Object.freeze(["rect"])
22
+ rect: Object.freeze(["rect", "collection"])
23
23
  });
24
24
 
25
25
  export const ENCODING_CHANNELS = Object.freeze([
@@ -32,6 +32,7 @@ export const ENCODING_CHANNELS = Object.freeze([
32
32
  "theta",
33
33
  "radius",
34
34
  "color",
35
+ "stroke",
35
36
  "strokeDash",
36
37
  "strokeWidth",
37
38
  "size",
@@ -155,13 +156,21 @@ export const FACET_SCALE_CHANNELS = Object.freeze([
155
156
  "y",
156
157
  "xOffset",
157
158
  "yOffset",
159
+ "theta",
160
+ "r",
158
161
  "color",
162
+ "stroke",
159
163
  "size",
160
164
  "shape",
161
165
  "opacity",
162
- "strokeDash"
166
+ "strokeDash",
167
+ "parallelDimensions"
163
168
  ]);
164
169
 
170
+ export function facetSemanticChannel(channel) {
171
+ return channel === "r" ? "radius" : channel;
172
+ }
173
+
165
174
  export const FACET_SCALE_RESOLUTIONS = Object.freeze([
166
175
  "shared",
167
176
  "independent"
@@ -198,6 +207,7 @@ export const STACK_MODES = Object.freeze(["zero", "normalize", "center"]);
198
207
 
199
208
  export const CATEGORICAL_LEGEND_CHANNELS = Object.freeze([
200
209
  "color",
210
+ "stroke",
201
211
  "strokeDash",
202
212
  "shape"
203
213
  ]);
@@ -212,9 +222,12 @@ export const LEGEND_CHANNELS = Object.freeze([
212
222
  export const LEGEND_CONFIG_KINDS = Object.freeze([
213
223
  "series",
214
224
  "color",
225
+ "stroke",
215
226
  "size",
216
227
  "gradient",
217
228
  "interval",
229
+ "strokeGradient",
230
+ "strokeInterval",
218
231
  "opacity",
219
232
  "strokeWidth"
220
233
  ]);
@@ -0,0 +1 @@
1
+ export { getErrorDetails } from "./core/diagnostics.js";
@@ -58,12 +58,14 @@ export function deriveBarAggregates(rows, layer) {
58
58
  category: categoryValue,
59
59
  ...(identity === undefined ? {} : { series: seriesValue }),
60
60
  ...(colorValues === undefined ? {} : { color: colorValue }),
61
- rows: []
61
+ rows: [],
62
+ sourceIndices: []
62
63
  };
63
64
  if (identity !== undefined && colorValues !== undefined && group.color !== colorValue) {
64
65
  throw new Error("Bar color requires one categorical value within each aggregate cell.");
65
66
  }
66
67
  group.rows.push(rows[index]);
68
+ group.sourceIndices.push(index);
67
69
  groups.set(key, group);
68
70
  }
69
71
 
@@ -83,6 +85,7 @@ export function deriveBarAggregates(rows, layer) {
83
85
  y: channels.category === "y" ? group.category : value,
84
86
  ...(color === undefined ? {} : { color: aggregateColor }),
85
87
  ...(identity === undefined ? {} : { series: group.series }),
88
+ sourceIndices: group.sourceIndices,
86
89
  count: group.rows.length
87
90
  }];
88
91
  });