ggaction 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +10 -3
  11. package/src/actions/basic.js +4 -2
  12. package/src/actions/boxPlots/components.js +14 -4
  13. package/src/actions/boxPlots/edit.js +31 -5
  14. package/src/actions/boxPlots/materialize.js +13 -4
  15. package/src/actions/boxPlots/options.js +48 -16
  16. package/src/actions/canvas/actions.js +3 -1
  17. package/src/actions/canvas/fitting.js +211 -0
  18. package/src/actions/canvas/index.js +2 -0
  19. package/src/actions/categoryOrder/index.js +18 -6
  20. package/src/actions/charts/area.js +51 -0
  21. package/src/actions/charts/bar.js +13 -6
  22. package/src/actions/charts/beeswarm.js +70 -0
  23. package/src/actions/charts/density.js +48 -0
  24. package/src/actions/charts/ecdf.js +186 -0
  25. package/src/actions/charts/endpoints.js +473 -0
  26. package/src/actions/charts/heatmap.js +4 -3
  27. package/src/actions/charts/histogram.js +5 -4
  28. package/src/actions/charts/horizon.js +73 -0
  29. package/src/actions/charts/index.js +37 -0
  30. package/src/actions/charts/interval-regression.js +172 -0
  31. package/src/actions/charts/line.js +8 -6
  32. package/src/actions/charts/parallel.js +8 -2
  33. package/src/actions/charts/pie.js +52 -0
  34. package/src/actions/charts/polar.js +134 -0
  35. package/src/actions/charts/radar.js +305 -0
  36. package/src/actions/charts/radial.js +50 -0
  37. package/src/actions/charts/raincloud.js +422 -0
  38. package/src/actions/charts/rug-strip.js +283 -0
  39. package/src/actions/charts/scatter.js +11 -3
  40. package/src/actions/charts/shared.js +69 -10
  41. package/src/actions/composition/actions.js +129 -3
  42. package/src/actions/coordinates/actions.js +1 -1
  43. package/src/actions/coordinates/edit.js +76 -0
  44. package/src/actions/data/bin.js +29 -0
  45. package/src/actions/data/bin2d.js +57 -185
  46. package/src/actions/data/complete.js +26 -0
  47. package/src/actions/data/computed.js +24 -0
  48. package/src/actions/data/create.js +2 -2
  49. package/src/actions/data/density.js +12 -20
  50. package/src/actions/data/derived.js +111 -15
  51. package/src/actions/data/ecdf.js +38 -0
  52. package/src/actions/data/edit.js +539 -0
  53. package/src/actions/data/filter.js +226 -30
  54. package/src/actions/data/fold.js +24 -0
  55. package/src/actions/data/impute.js +26 -0
  56. package/src/actions/data/index.js +38 -0
  57. package/src/actions/data/interval.js +2 -0
  58. package/src/actions/data/intervalEdit.js +86 -4
  59. package/src/actions/data/normalize.js +27 -0
  60. package/src/actions/data/regression.js +19 -21
  61. package/src/actions/data/revisionBindings.js +145 -0
  62. package/src/actions/data/shared.js +20 -4
  63. package/src/actions/data/stack.js +26 -0
  64. package/src/actions/data/summary.js +26 -0
  65. package/src/actions/data/timeUnit.js +4 -1
  66. package/src/actions/data/window.js +1 -1
  67. package/src/actions/encodings/appearance.js +32 -13
  68. package/src/actions/encodings/barWidth.js +8 -0
  69. package/src/actions/encodings/basic.js +2 -0
  70. package/src/actions/encodings/channels.js +415 -0
  71. package/src/actions/encodings/color/continuous.js +19 -59
  72. package/src/actions/encodings/color/index.js +30 -23
  73. package/src/actions/encodings/color/layout.js +3 -97
  74. package/src/actions/encodings/color/policy.js +5 -8
  75. package/src/actions/encodings/density/resolve.js +37 -5
  76. package/src/actions/encodings/density.js +124 -34
  77. package/src/actions/encodings/histogram.js +3 -2
  78. package/src/actions/encodings/horizon/resolve.js +8 -4
  79. package/src/actions/encodings/horizon.js +18 -8
  80. package/src/actions/encodings/index.js +6 -0
  81. package/src/actions/encodings/offset.js +30 -29
  82. package/src/actions/encodings/parallel.js +2 -8
  83. package/src/actions/encodings/position/apply.js +63 -11
  84. package/src/actions/encodings/position/index.js +52 -6
  85. package/src/actions/encodings/position/policies/arc.js +14 -0
  86. package/src/actions/encodings/position/policies/area.js +4 -42
  87. package/src/actions/encodings/position/policies/bar.js +57 -13
  88. package/src/actions/encodings/position/resolve.js +88 -26
  89. package/src/actions/encodings/ranged.js +123 -97
  90. package/src/actions/encodings/remove.js +44 -34
  91. package/src/actions/encodings/ruleAppearance.js +24 -32
  92. package/src/actions/encodings/seriesLayout.js +105 -0
  93. package/src/actions/encodings/shared.js +107 -37
  94. package/src/actions/encodings/stroke.js +226 -0
  95. package/src/actions/encodings/strokeDash.js +9 -6
  96. package/src/actions/encodings/temporal.js +7 -0
  97. package/src/actions/encodings/text.js +30 -15
  98. package/src/actions/errorBands/create.js +41 -8
  99. package/src/actions/errorBands/edit.js +360 -25
  100. package/src/actions/errorBars/create.js +56 -17
  101. package/src/actions/errorBars/edit.js +372 -21
  102. package/src/actions/facets/actions.js +619 -41
  103. package/src/actions/facets/derive.js +139 -25
  104. package/src/actions/gradientPlots/components.js +43 -13
  105. package/src/actions/gradientPlots/edit.js +14 -4
  106. package/src/actions/gradientPlots/materialize.js +8 -8
  107. package/src/actions/gradientPlots/options.js +10 -2
  108. package/src/actions/guides/applicability.js +28 -19
  109. package/src/actions/guides/axes/axes.js +4 -3
  110. package/src/actions/guides/axes/axis.js +18 -18
  111. package/src/actions/guides/axes/components.js +43 -0
  112. package/src/actions/guides/axes/edit.js +16 -51
  113. package/src/actions/guides/axes/labels.js +184 -28
  114. package/src/actions/guides/axes/lines.js +13 -7
  115. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  116. package/src/actions/guides/axes/parallel/policy.js +137 -0
  117. package/src/actions/guides/axes/parallel/resolve.js +50 -25
  118. package/src/actions/guides/axes/parallel.js +93 -217
  119. package/src/actions/guides/axes/policy.js +21 -67
  120. package/src/actions/guides/axes/tickGroups.js +15 -8
  121. package/src/actions/guides/axes/ticks.js +11 -6
  122. package/src/actions/guides/axes/titles.js +32 -11
  123. package/src/actions/guides/basic.js +2 -0
  124. package/src/actions/guides/facade.js +106 -0
  125. package/src/actions/guides/facadeAxes.js +200 -0
  126. package/src/actions/guides/facadeLegend.js +63 -0
  127. package/src/actions/guides/grids/grid.js +37 -33
  128. package/src/actions/guides/grids/resolve.js +17 -4
  129. package/src/actions/guides/guides.js +54 -31
  130. package/src/actions/guides/legends/blocks.js +426 -0
  131. package/src/actions/guides/legends/categorical/actions.js +309 -279
  132. package/src/actions/guides/legends/categorical/components.js +21 -17
  133. package/src/actions/guides/legends/categorical/index.js +37 -33
  134. package/src/actions/guides/legends/categorical/layout.js +123 -354
  135. package/src/actions/guides/legends/categorical/options.js +32 -16
  136. package/src/actions/guides/legends/categorical/recipes.js +114 -9
  137. package/src/actions/guides/legends/categorical/resolve.js +30 -15
  138. package/src/actions/guides/legends/categorical/symbols.js +87 -33
  139. package/src/actions/guides/legends/continuous/common.js +99 -26
  140. package/src/actions/guides/legends/continuous/gradient.js +120 -89
  141. package/src/actions/guides/legends/continuous/index.js +8 -2
  142. package/src/actions/guides/legends/continuous/interval.js +79 -95
  143. package/src/actions/guides/legends/continuous/opacity.js +113 -89
  144. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  145. package/src/actions/guides/legends/creation.js +40 -0
  146. package/src/actions/guides/legends/edit.js +321 -90
  147. package/src/actions/guides/legends/focused.js +2 -2
  148. package/src/actions/guides/legends/index.js +2 -0
  149. package/src/actions/guides/legends/lane.js +149 -102
  150. package/src/actions/guides/legends/lifecycle.js +143 -0
  151. package/src/actions/guides/legends/remove.js +43 -78
  152. package/src/actions/guides/legends/sampling.js +140 -0
  153. package/src/actions/guides/legends/size.js +213 -116
  154. package/src/actions/guides/legends/strokeWidth.js +121 -73
  155. package/src/actions/guides/legends/target.js +123 -0
  156. package/src/actions/guides/legends/transition.js +192 -0
  157. package/src/actions/guides/polar/axes/facade.js +81 -66
  158. package/src/actions/guides/polar/axes/labels.js +28 -8
  159. package/src/actions/guides/polar/axes/lines.js +12 -5
  160. package/src/actions/guides/polar/axes/shared.js +22 -0
  161. package/src/actions/guides/polar/axes/ticks.js +6 -5
  162. package/src/actions/guides/polar/axes/titles.js +6 -5
  163. package/src/actions/guides/polar/grids.js +8 -8
  164. package/src/actions/guides/polar/resolve.js +12 -8
  165. package/src/actions/guides/reuse.js +44 -0
  166. package/src/actions/guides/tickValues.js +19 -21
  167. package/src/actions/index.js +15 -2
  168. package/src/actions/intervals/resolve.js +35 -15
  169. package/src/actions/marks/arc/actions.js +78 -20
  170. package/src/actions/marks/area/actions.js +51 -12
  171. package/src/actions/marks/area/materialize.js +22 -3
  172. package/src/actions/marks/bar/create.js +11 -2
  173. package/src/actions/marks/bar/edit.js +20 -3
  174. package/src/actions/marks/bar/materialize.js +33 -9
  175. package/src/actions/marks/index.js +14 -0
  176. package/src/actions/marks/line/actions.js +71 -113
  177. package/src/actions/marks/line/materialize.js +57 -56
  178. package/src/actions/marks/point/create.js +8 -2
  179. package/src/actions/marks/point/edit.js +29 -21
  180. package/src/actions/marks/point/index.js +1 -0
  181. package/src/actions/marks/point/jitter.js +3 -0
  182. package/src/actions/marks/point/materialize.js +154 -16
  183. package/src/actions/marks/point/packing.js +68 -0
  184. package/src/actions/marks/rect/actions.js +33 -12
  185. package/src/actions/marks/references.js +658 -0
  186. package/src/actions/marks/remove.js +87 -4
  187. package/src/actions/marks/rule/actions.js +77 -9
  188. package/src/actions/marks/rule/appearance.js +26 -0
  189. package/src/actions/marks/shared.js +13 -13
  190. package/src/actions/marks/text/actions.js +296 -13
  191. package/src/actions/marks/text/index.js +4 -0
  192. package/src/actions/marks/text/layout.js +156 -8
  193. package/src/actions/marks/text/removal.js +173 -0
  194. package/src/actions/marks/text/selection.js +73 -0
  195. package/src/actions/marks/tick/actions.js +22 -5
  196. package/src/actions/primitives/createGraphics.js +3 -1
  197. package/src/actions/primitives/editGraphics.js +26 -0
  198. package/src/actions/primitives/semantic.js +3 -2
  199. package/src/actions/primitives/semanticAction.js +120 -10
  200. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  201. package/src/actions/primitives/semanticValidation/index.js +9 -1
  202. package/src/actions/primitives/semanticValidation/layer.js +23 -1
  203. package/src/actions/primitives/semanticValidation/scale.js +38 -8
  204. package/src/actions/regression/components.js +26 -11
  205. package/src/actions/regression/create.js +11 -3
  206. package/src/actions/regression/edit.js +40 -13
  207. package/src/actions/regression/resolve.js +1 -1
  208. package/src/actions/resources/index.js +7 -0
  209. package/src/actions/resources/remove.js +172 -0
  210. package/src/actions/scales/channels.js +212 -0
  211. package/src/actions/scales/consumers/common.js +21 -54
  212. package/src/actions/scales/consumers/families.js +28 -14
  213. package/src/actions/scales/consumers/index.js +42 -4
  214. package/src/actions/scales/consumers/seriesLayout.js +57 -12
  215. package/src/actions/scales/create.js +58 -26
  216. package/src/actions/scales/definitions.js +109 -39
  217. package/src/actions/scales/edit.js +101 -34
  218. package/src/actions/scales/editPolicy.js +95 -35
  219. package/src/actions/scales/index.js +6 -0
  220. package/src/actions/scales/materialize.js +14 -49
  221. package/src/actions/scales/offset.js +48 -0
  222. package/src/actions/scales/parallel.js +71 -0
  223. package/src/actions/scales/patch.js +1 -1
  224. package/src/actions/scales/preview.js +140 -0
  225. package/src/actions/scales/quantitativeColor.js +9 -5
  226. package/src/actions/selection/actions.js +154 -30
  227. package/src/actions/selection/index.js +3 -0
  228. package/src/actions/theme/actions.js +98 -0
  229. package/src/actions/theme/composition.js +238 -0
  230. package/src/actions/theme/index.js +16 -0
  231. package/src/actions/theme/reconcile.js +1130 -0
  232. package/src/actions/theme/state.js +160 -0
  233. package/src/actions/titles/actions.js +11 -10
  234. package/src/actions/titles/resolve.js +1 -40
  235. package/src/actions/violinPlots/create.js +90 -43
  236. package/src/actions/violinPlots/edit.js +192 -0
  237. package/src/actions/violinPlots/index.js +2 -0
  238. package/src/core/ChartProgram.js +7 -0
  239. package/src/core/action.js +32 -2
  240. package/src/core/compositionState.js +141 -8
  241. package/src/core/programState.js +20 -0
  242. package/src/core/resourceReferences.js +332 -0
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/grammar/aggregate.js +66 -23
  245. package/src/grammar/arcs.js +58 -1
  246. package/src/grammar/areaEndpoints.js +46 -0
  247. package/src/grammar/areaSeries.js +87 -41
  248. package/src/grammar/bars/aggregate.js +37 -13
  249. package/src/grammar/bars/geometry.js +44 -0
  250. package/src/grammar/bars/policy.js +9 -7
  251. package/src/grammar/bin.js +226 -0
  252. package/src/grammar/categoryOrder.js +34 -0
  253. package/src/grammar/complete.js +279 -0
  254. package/src/grammar/computed.js +351 -0
  255. package/src/grammar/coordinates.js +37 -0
  256. package/src/grammar/density.js +239 -37
  257. package/src/grammar/displayLabels.js +57 -0
  258. package/src/grammar/ecdf.js +198 -0
  259. package/src/grammar/facets/dependencies.js +133 -1
  260. package/src/grammar/facets/guides.js +3 -0
  261. package/src/grammar/facets/index.js +123 -52
  262. package/src/grammar/facets/scales.js +15 -18
  263. package/src/grammar/fold.js +123 -0
  264. package/src/grammar/horizon.js +6 -4
  265. package/src/grammar/impute.js +340 -0
  266. package/src/grammar/interval.js +37 -20
  267. package/src/grammar/lineSeries.js +20 -63
  268. package/src/grammar/markFilter.js +22 -4
  269. package/src/grammar/markLabelSelection.js +57 -0
  270. package/src/grammar/markLabels.js +110 -0
  271. package/src/grammar/normalize.js +366 -0
  272. package/src/grammar/parallelAxisTitles.js +16 -0
  273. package/src/grammar/pathSeries.js +109 -0
  274. package/src/grammar/pointPacking.js +183 -0
  275. package/src/grammar/pointShapes.js +13 -4
  276. package/src/grammar/polar.js +90 -8
  277. package/src/grammar/positionDatum.js +12 -0
  278. package/src/grammar/rects.js +9 -1
  279. package/src/grammar/regression/derive.js +4 -0
  280. package/src/grammar/regression/index.js +8 -2
  281. package/src/grammar/regression/models.js +22 -7
  282. package/src/grammar/regression/parameters.js +63 -13
  283. package/src/grammar/rotation.js +29 -0
  284. package/src/grammar/roundedRect.js +180 -0
  285. package/src/grammar/rules.js +8 -21
  286. package/src/grammar/scales/color.js +23 -1
  287. package/src/grammar/scales/colorConsumers.js +57 -0
  288. package/src/grammar/scales/continuous.js +2 -2
  289. package/src/grammar/scales/definition.js +42 -5
  290. package/src/grammar/scales/discretized.js +11 -5
  291. package/src/grammar/scales/fields.js +38 -8
  292. package/src/grammar/scales/index.js +1 -0
  293. package/src/grammar/scales/mapping.js +2 -0
  294. package/src/grammar/scales/ordinal.js +12 -3
  295. package/src/grammar/scales/policies.js +2 -2
  296. package/src/grammar/scales/radial.js +55 -0
  297. package/src/grammar/scales/size.js +456 -0
  298. package/src/grammar/scales/types.js +1 -0
  299. package/src/grammar/schemas/concreteGraphic.js +14 -2
  300. package/src/grammar/schemas/graphic.js +15 -0
  301. package/src/grammar/schemas/graphicBounds.js +179 -30
  302. package/src/grammar/schemas/semanticPath.js +23 -4
  303. package/src/grammar/stack.js +170 -0
  304. package/src/grammar/statisticalReference.js +103 -0
  305. package/src/grammar/statistics/confidenceInterval.js +101 -0
  306. package/src/grammar/strokeStyle.js +77 -0
  307. package/src/grammar/summary.js +245 -0
  308. package/src/grammar/text.js +22 -16
  309. package/src/grammar/timeUnit.js +234 -13
  310. package/src/grammar/transformTopology.js +10 -0
  311. package/src/grammar/transforms.js +418 -16
  312. package/src/grammar/valueFormat.js +112 -0
  313. package/src/grammar/weightedStatistics.js +374 -0
  314. package/src/grammar/window.js +232 -21
  315. package/src/layout/aspect.js +80 -0
  316. package/src/layout/facets.js +100 -12
  317. package/src/layout/guideCollisions.js +12 -0
  318. package/src/layout/labels.js +343 -0
  319. package/src/layout/legend.js +2 -10
  320. package/src/layout/legendItems.js +102 -0
  321. package/src/layout/legendLane.js +100 -11
  322. package/src/materialization/bars/aggregate.js +56 -147
  323. package/src/materialization/bars/histogram.js +104 -20
  324. package/src/materialization/bars/ranged.js +19 -3
  325. package/src/materialization/bars/resolve.js +7 -4
  326. package/src/materialization/categorySlotOffset.js +40 -0
  327. package/src/materialization/composition.js +3 -1
  328. package/src/materialization/compositionSnapshot.js +1 -1
  329. package/src/materialization/coordinateBounds.js +204 -0
  330. package/src/materialization/dependencies.js +93 -16
  331. package/src/materialization/encodings.js +1 -1
  332. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  333. package/src/materialization/facetGuides/placement.js +8 -2
  334. package/src/materialization/facetGuides/preparation.js +12 -7
  335. package/src/materialization/facetHeaders.js +346 -0
  336. package/src/materialization/facets.js +23 -66
  337. package/src/materialization/guides/layout.js +57 -0
  338. package/src/materialization/guides/resources.js +28 -2
  339. package/src/materialization/legends.js +11 -2
  340. package/src/materialization/marks/capabilities.js +20 -5
  341. package/src/materialization/marks/grouping.js +52 -0
  342. package/src/materialization/marks/index.js +68 -27
  343. package/src/materialization/marks/policies.js +4 -1
  344. package/src/materialization/rect.js +55 -42
  345. package/src/materialization/rectConfig.js +5 -1
  346. package/src/materialization/rowEncoding.js +13 -13
  347. package/src/materialization/scaleGuideDependencies.js +8 -5
  348. package/src/materialization/scales/map.js +6 -3
  349. package/src/materialization/scales/policies/arc.js +42 -0
  350. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  351. package/src/materialization/scales/policies/offset.js +50 -8
  352. package/src/materialization/scales/policies/series.js +5 -1
  353. package/src/materialization/scales/resolve.js +65 -17
  354. package/src/materialization/selection/compatibility.js +11 -0
  355. package/src/materialization/selection/items/arc.js +28 -4
  356. package/src/materialization/selection/items/bar.js +42 -71
  357. package/src/materialization/selection/items/common.js +27 -6
  358. package/src/materialization/selection/items/path.js +3 -13
  359. package/src/materialization/selection/items/rect.js +35 -8
  360. package/src/materialization/selection/path.js +1 -1
  361. package/src/materialization/selection/point.js +3 -1
  362. package/src/materialization/text.js +324 -14
  363. package/src/mcp/adapter.js +2 -2
  364. package/src/renderers/canvas/circle.js +2 -3
  365. package/src/renderers/canvas/line.js +2 -3
  366. package/src/renderers/canvas/native.js +4 -4
  367. package/src/renderers/canvas/path.js +2 -3
  368. package/src/renderers/canvas/rect.js +2 -3
  369. package/src/renderers/canvas/stroke.js +11 -0
  370. package/src/renderers/svg.js +19 -0
  371. package/src/selectors/datasets.js +44 -0
  372. package/src/selectors/layers.js +7 -2
  373. package/src/selectors/scales.js +6 -0
  374. package/src/theme/defaults.js +140 -0
  375. package/types/basic.d.ts +17 -2
  376. package/types/index.d.ts +211 -0
  377. package/types/program.d.ts +2182 -204
  378. package/src/materialization/bars/grouped.js +0 -144
@@ -0,0 +1,332 @@
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
+ if ((key === "scale" || key.endsWith("Scale")) && typeof value === "string") {
152
+ append(references, reference("scale", value, "markConfig", ownerId, path));
153
+ } else if (key === "scale" && isPlainObject(value)) {
154
+ append(references, reference(
155
+ "scale", value.id, "markConfig", ownerId, [...path, "id"]
156
+ ));
157
+ }
158
+ if (MARK_ID_KEYS.has(key)) {
159
+ append(references, reference("mark", value, "markConfig", ownerId, path));
160
+ }
161
+ if (
162
+ (root === "errorBandBoundary" && key === "owner") ||
163
+ (parentPath.length === 0 && key === "boxSpanOwner") ||
164
+ (root === "raincloudPlot" && (
165
+ key === "ownerId" || parentPath[1] === "childIds" ||
166
+ parentPath[1] === "ownedChildIds"
167
+ ))
168
+ ) {
169
+ append(references, reference("mark", value, "markConfig", ownerId, path));
170
+ }
171
+ });
172
+ append(references, reference(
173
+ "mark", config.statisticalReference?.source,
174
+ "markConfig", ownerId, ["statisticalReference", "source"]
175
+ ));
176
+ for (const [index, id] of (
177
+ config.raincloudPlot?.ownedChildIds ?? []
178
+ ).entries()) {
179
+ append(references, reference(
180
+ "mark", id, "markConfig", ownerId,
181
+ ["raincloudPlot", "ownedChildIds", index]
182
+ ));
183
+ }
184
+ const selection = config.labelAuthoring?.selection;
185
+ if (selection?.kind === "named") {
186
+ append(references, reference(
187
+ "selection", selection.id, "markConfig", ownerId,
188
+ ["labelAuthoring", "selection", "id"]
189
+ ));
190
+ }
191
+ }
192
+ return freezeOwned(references);
193
+ }
194
+
195
+ export function collectGuideConfigReferences(program) {
196
+ const references = [];
197
+ for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
198
+ collectKnownKeys(configs, [], (key, value, path, parentPath) => {
199
+ const ownerId = parentPath.length === 0 ? family : parentPath.join(".");
200
+ const relativePath = parentPath.length === 0
201
+ ? path
202
+ : path.slice(parentPath.length);
203
+ if (key === "scale") {
204
+ append(references, reference(
205
+ "scale", value, family, ownerId, relativePath
206
+ ));
207
+ } else if (key === "scales" && Array.isArray(value)) {
208
+ value.forEach((id, index) => append(references, reference(
209
+ "scale", id, family, ownerId, [...relativePath, index]
210
+ )));
211
+ } else if (key === "coordinate") {
212
+ append(references, reference(
213
+ "coordinate", value, family, ownerId, relativePath
214
+ ));
215
+ } else if (key === "target") {
216
+ append(references, reference(
217
+ "mark", value, family, ownerId, relativePath
218
+ ));
219
+ }
220
+ });
221
+ }
222
+ return freezeOwned(references);
223
+ }
224
+
225
+ export function collectSelectionReferences(program) {
226
+ const references = [];
227
+ for (const [id, config] of Object.entries(
228
+ program.materializationConfigs.selections ?? {}
229
+ )) {
230
+ append(references, reference(
231
+ "mark", config.target, "selection", id, ["target"]
232
+ ));
233
+ }
234
+ for (const [id, config] of Object.entries(
235
+ program.materializationConfigs.highlights ?? {}
236
+ )) {
237
+ append(references, reference(
238
+ "mark", config.target, "highlight", id, ["target"]
239
+ ));
240
+ append(references, reference(
241
+ "selection", config.selection, "highlight", id, ["selection"]
242
+ ));
243
+ }
244
+ return freezeOwned(references);
245
+ }
246
+
247
+ export function collectDataOwnerReferences(program) {
248
+ const references = [];
249
+ for (const [family, owners] of Object.entries(
250
+ program.materializationConfigs.data ?? {}
251
+ )) {
252
+ for (const [owner, config] of Object.entries(owners ?? {})) {
253
+ for (const key of ["current", "source", "previous"]) {
254
+ append(references, reference(
255
+ "data", config?.[key], "dataOwner", owner,
256
+ ["data", family, owner, key]
257
+ ));
258
+ }
259
+ }
260
+ }
261
+ return freezeOwned(references);
262
+ }
263
+
264
+ export function collectCompositionReferences(program) {
265
+ const references = [];
266
+ const composition = program.compositionSpec;
267
+ if (composition?.type !== "facet") return freezeOwned(references);
268
+ append(references, reference(
269
+ "data", composition.facet.data, "composition", composition.id,
270
+ ["facet", "data"]
271
+ ));
272
+ append(references, reference(
273
+ "mark", composition.facet.repeat?.target, "composition", composition.id,
274
+ ["facet", "repeat", "target"]
275
+ ));
276
+ return freezeOwned(references);
277
+ }
278
+
279
+ export function collectContextReferences(program) {
280
+ const references = [];
281
+ for (const [key, kind] of Object.entries({
282
+ currentData: "data",
283
+ currentScale: "scale",
284
+ currentCoordinate: "coordinate",
285
+ currentMark: "mark",
286
+ currentSelection: "selection"
287
+ })) {
288
+ append(references, reference(
289
+ kind, program.context[key], "context", "program", [key], "context"
290
+ ));
291
+ }
292
+ return freezeOwned(references);
293
+ }
294
+
295
+ function referenceKey(value) {
296
+ return [
297
+ value.kind, value.id, value.ownerKind, value.ownerId,
298
+ canonicalResourcePath(value.path), value.strength
299
+ ].join("\u0000");
300
+ }
301
+
302
+ function compareReferences(left, right) {
303
+ const leftKey = [
304
+ left.ownerKind, left.ownerId, canonicalResourcePath(left.path),
305
+ left.kind, left.id, left.strength
306
+ ].join("\u0000");
307
+ const rightKey = [
308
+ right.ownerKind, right.ownerId, canonicalResourcePath(right.path),
309
+ right.kind, right.id, right.strength
310
+ ].join("\u0000");
311
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
312
+ }
313
+
314
+ export function collectResourceReferences(program, { kind, id }) {
315
+ if (!RESOURCE_KINDS.includes(kind)) {
316
+ throw new Error(`Unknown resource kind "${kind}".`);
317
+ }
318
+ const target = validateUserId(id, "Resource id");
319
+ const combined = [
320
+ ...collectSemanticDataReferences(program),
321
+ ...collectSemanticScaleReferences(program),
322
+ ...collectSemanticCoordinateReferences(program),
323
+ ...collectMarkConfigReferences(program),
324
+ ...collectGuideConfigReferences(program),
325
+ ...collectSelectionReferences(program),
326
+ ...collectDataOwnerReferences(program),
327
+ ...collectCompositionReferences(program),
328
+ ...collectContextReferences(program)
329
+ ].filter(value => value.kind === kind && value.id === target);
330
+ const unique = new Map(combined.map(value => [referenceKey(value), value]));
331
+ return freezeOwned([...unique.values()].sort(compareReferences));
332
+ }
@@ -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
  ]);
@@ -7,6 +7,10 @@ import {
7
7
  stableFiniteMean,
8
8
  stableFiniteSum
9
9
  } from "./numeric.js";
10
+ import {
11
+ confidenceCriticalValue,
12
+ normalizeConfidenceInterval
13
+ } from "./statistics/confidenceInterval.js";
10
14
 
11
15
  export const SCALAR_AGGREGATE_OPERATIONS = Object.freeze([
12
16
  "count", "sum", "mean", "median", "min", "max",
@@ -17,7 +21,9 @@ export const SCALAR_AGGREGATE_OPERATIONS = Object.freeze([
17
21
 
18
22
  const SCALAR_OPERATIONS = new Set(SCALAR_AGGREGATE_OPERATIONS);
19
23
  const NOMINAL_OPERATIONS = ["count", "distinct", "valid", "missing"];
20
- const PARAMETERIZED_OPERATIONS = ["quantile", "first", "last"];
24
+ const PARAMETERIZED_OPERATIONS = [
25
+ "quantile", "first", "last", "ciLower", "ciUpper"
26
+ ];
21
27
 
22
28
  function nonEmptyString(value, label) {
23
29
  if (typeof value !== "string" || value.length === 0) {
@@ -83,6 +89,21 @@ export function validateAggregate(value) {
83
89
  order: value.order ?? "ascending"
84
90
  };
85
91
  }
92
+ if (value.op === "ciLower" || value.op === "ciUpper") {
93
+ const unknown = Object.keys(value).find(
94
+ key => !["op", "method", "level"].includes(key)
95
+ );
96
+ if (unknown !== undefined) {
97
+ throw new Error(`Unknown confidence aggregate property "${unknown}".`);
98
+ }
99
+ return {
100
+ op: value.op,
101
+ ...normalizeConfidenceInterval(value, {
102
+ defaultMethod: "normal",
103
+ label: "Aggregate confidence interval"
104
+ })
105
+ };
106
+ }
86
107
  throw new Error(`Unsupported aggregate "${value.op}".`);
87
108
  }
88
109
 
@@ -194,39 +215,44 @@ export function aggregateScalarValues(values, operation) {
194
215
  if (!Array.isArray(values)) {
195
216
  throw new TypeError("Aggregate values must be an array.");
196
217
  }
197
- validateAggregate(operation);
198
- if (!isScalarAggregate(operation)) {
218
+ const aggregate = validateAggregate(operation);
219
+ const scalarOperation = isScalarAggregate(aggregate)
220
+ ? aggregate
221
+ : ["ciLower", "ciUpper"].includes(aggregate.op)
222
+ ? aggregate.op
223
+ : undefined;
224
+ if (scalarOperation === undefined) {
199
225
  throw new Error("Scalar aggregate calculation requires a scalar operation.");
200
226
  }
201
227
 
202
- if (operation === "count") return values.length;
228
+ if (scalarOperation === "count") return values.length;
203
229
  const valid = values.filter(value => !isMissing(value));
204
- if (operation === "valid") return valid.length;
205
- if (operation === "missing") return values.length - valid.length;
206
- if (operation === "distinct") return new Set(valid).size;
230
+ if (scalarOperation === "valid") return valid.length;
231
+ if (scalarOperation === "missing") return values.length - valid.length;
232
+ if (scalarOperation === "distinct") return new Set(valid).size;
207
233
 
208
234
  const finite = finiteValues(values);
209
235
  if (finite.length === 0) return undefined;
210
- if (operation === "sum") {
236
+ if (scalarOperation === "sum") {
211
237
  return stableFiniteSum(finite, "Aggregate sum");
212
238
  }
213
- if (operation === "min") return numericExtent(finite)[0];
214
- if (operation === "max") return numericExtent(finite)[1];
215
- if (operation === "median") return quantile(finite, 0.5);
216
- if (operation === "q1") return quantile(finite, 0.25);
217
- if (operation === "q3") return quantile(finite, 0.75);
239
+ if (scalarOperation === "min") return numericExtent(finite)[0];
240
+ if (scalarOperation === "max") return numericExtent(finite)[1];
241
+ if (scalarOperation === "median") return quantile(finite, 0.5);
242
+ if (scalarOperation === "q1") return quantile(finite, 0.25);
243
+ if (scalarOperation === "q3") return quantile(finite, 0.75);
218
244
 
219
245
  const mean = stableFiniteMean(finite, "Aggregate mean");
220
- if (operation === "mean") return mean;
221
- if (["variance", "varianceP", "stdev", "stdevP"].includes(operation)) {
222
- const population = operation.endsWith("P");
246
+ if (scalarOperation === "mean") return mean;
247
+ if (["variance", "varianceP", "stdev", "stdevP"].includes(scalarOperation)) {
248
+ const population = scalarOperation.endsWith("P");
223
249
  if (!population && finite.length < 2) return undefined;
224
250
  const kind = population ? "population" : "sample";
225
251
  const deviation = stableFiniteDeviation(finite, {
226
252
  sample: !population,
227
253
  label: `Aggregate ${kind} deviation`
228
254
  });
229
- if (operation.startsWith("stdev")) return deviation.deviation;
255
+ if (scalarOperation.startsWith("stdev")) return deviation.deviation;
230
256
  return deviation.squared === undefined
231
257
  ? requireFiniteResult(
232
258
  deviation.deviation ** 2,
@@ -240,14 +266,25 @@ export function aggregateScalarValues(values, operation) {
240
266
  divisor: Math.sqrt(finite.length),
241
267
  label: "Aggregate standard error"
242
268
  }).deviation;
243
- if (operation === "stderr") return stderr;
244
- if (operation === "ciLower") {
245
- return requireFiniteResult(mean - 1.96 * stderr, "Aggregate lower CI");
269
+ if (scalarOperation === "stderr") return stderr;
270
+ const confidence = isScalarAggregate(aggregate)
271
+ ? normalizeConfidenceInterval({}, {
272
+ defaultMethod: "normal",
273
+ label: "Aggregate confidence interval"
274
+ })
275
+ : aggregate;
276
+ const critical = confidenceCriticalValue({
277
+ method: confidence.method,
278
+ level: confidence.level,
279
+ degreesOfFreedom: finite.length - 1
280
+ });
281
+ if (scalarOperation === "ciLower") {
282
+ return requireFiniteResult(mean - critical * stderr, "Aggregate lower CI");
246
283
  }
247
- if (operation === "ciUpper") {
248
- return requireFiniteResult(mean + 1.96 * stderr, "Aggregate upper CI");
284
+ if (scalarOperation === "ciUpper") {
285
+ return requireFiniteResult(mean + critical * stderr, "Aggregate upper CI");
249
286
  }
250
- throw new Error(`Unsupported scalar aggregate "${operation}".`);
287
+ throw new Error(`Unsupported scalar aggregate "${scalarOperation}".`);
251
288
  }
252
289
 
253
290
  export function aggregateRows(rows, field, operation) {
@@ -265,6 +302,9 @@ export function aggregateRows(rows, field, operation) {
265
302
  ? undefined
266
303
  : quantile(values, aggregate.probability);
267
304
  }
305
+ if (aggregate.op === "ciLower" || aggregate.op === "ciUpper") {
306
+ return aggregateScalarValues(rows.map(row => row[field]), aggregate);
307
+ }
268
308
  return aggregateOrderedRows(rows, field, aggregate);
269
309
  }
270
310
 
@@ -275,5 +315,8 @@ export function formatAggregateTitle(operation, field) {
275
315
  if (aggregate.op === "quantile") {
276
316
  return `quantile(${field}, ${aggregate.probability})`;
277
317
  }
318
+ if (aggregate.op === "ciLower" || aggregate.op === "ciUpper") {
319
+ return `${aggregate.op}(${field}, ${aggregate.method}, ${aggregate.level})`;
320
+ }
278
321
  return `${aggregate.op}(${field}, ${aggregate.orderBy} ${aggregate.order})`;
279
322
  }
@@ -241,6 +241,61 @@ function quantitativeSectors(rows, layer, thetaScale, frame, innerRadiusRatio) {
241
241
  }));
242
242
  }
243
243
 
244
+ export function deriveMeasuredArcValues(rows, layer) {
245
+ const theta = requireArcLayer(layer);
246
+ const radius = layer.encoding?.radius;
247
+ if (!["nominal", "ordinal"].includes(theta.fieldType) || theta.aggregate !== undefined ||
248
+ !["count", "sum"].includes(radius?.aggregate) || radius.fieldType !== "quantitative") {
249
+ throw new Error("Measured Arc requires equal-angle categorical theta and count or sum radius.");
250
+ }
251
+ if (radius.aggregate === "count" && Object.hasOwn(radius, "field")) {
252
+ throw new Error("Measured count radius does not accept a field.");
253
+ }
254
+ const categories = readNominalField(rows, theta.field);
255
+ const measures = radius.aggregate === "count" ? rows.map(() => 1)
256
+ : readQuantitativeField(rows, radius.field);
257
+ if (measures.some(value => value < 0)) {
258
+ throw new RangeError("Measured Arc radius inputs must be non-negative.");
259
+ }
260
+ const colors = colorValues(rows, layer.encoding?.color);
261
+ const groups = new Map();
262
+ for (const [index, category] of categories.entries()) {
263
+ const group = groups.get(category) ?? { key: category, sourceIndices: [], values: [], colors: [] };
264
+ group.sourceIndices.push(index);
265
+ group.values.push(measures[index]);
266
+ group.colors.push(colors[index]);
267
+ groups.set(category, group);
268
+ }
269
+ const result = [...groups.values()].map(group => {
270
+ const distinctColors = [...new Set(group.colors)];
271
+ if (distinctColors.length > 1) {
272
+ throw new Error(`Measured Arc category "${group.key}" must resolve to one color value.`);
273
+ }
274
+ return { key: group.key, theta: group.key, count: group.sourceIndices.length,
275
+ radius: stableFiniteSum(group.values, "Measured Arc radius aggregate"),
276
+ color: distinctColors[0], sourceIndices: group.sourceIndices };
277
+ });
278
+ if (!result.some(group => group.radius > 0)) {
279
+ throw new Error("Measured Arc radius requires at least one positive category aggregate.");
280
+ }
281
+ return cloneAndFreeze(result);
282
+ }
283
+
284
+ function measuredRadialSectors(rows, layer, thetaScale, radiusScale) {
285
+ const items = deriveMeasuredArcValues(rows, layer);
286
+ const centers = mapOrdinalPositionValues(items.map(item => item.theta), thetaScale);
287
+ const radii = mapContinuousScaleValues(items.map(item => item.radius), radiusScale);
288
+ const halfBand = (Math.sign(thetaScale.step) || 1) * thetaScale.bandwidth / 2;
289
+ const byCategory = new Map(items.map((item, index) => [item.theta, {
290
+ ...item, startTheta: centers[index] - halfBand, endTheta: centers[index] + halfBand,
291
+ innerRadius: radiusScale.range[0], outerRadius: radii[index]
292
+ }]));
293
+ return thetaScale.domain.flatMap(category => {
294
+ const item = byCategory.get(category);
295
+ return item === undefined || item.radius === 0 ? [] : [item];
296
+ });
297
+ }
298
+
244
299
  function radialSectors(rows, layer, thetaScale, radiusScale) {
245
300
  const theta = layer.encoding.theta;
246
301
  const radius = layer.encoding?.radius;
@@ -303,7 +358,9 @@ export function deriveArcSectors(rows, layer, {
303
358
  ) {
304
359
  throw new RangeError("Arc innerRadius must be from 0 (inclusive) to 1 (exclusive).");
305
360
  }
306
- const sectors = theta.fieldType === "quantitative"
361
+ const sectors = radiusScale?.radialMapping !== undefined
362
+ ? measuredRadialSectors(rows, layer, requireBandScale(thetaScale, `Arc mark "${layer.id}" theta`), radiusScale)
363
+ : theta.fieldType === "quantitative"
307
364
  ? quantitativeSectors(
308
365
  rows,
309
366
  layer,
@@ -0,0 +1,46 @@
1
+ import { validateKeys, validateNonEmptyString } from "../core/validation.js";
2
+ import { isPlainObject } from "../core/immutable.js";
3
+ import { readQuantitativeField } from "./scales/fields.js";
4
+
5
+ export function validateAreaMissing(value = "error") {
6
+ if (!["error", "break"].includes(value)) {
7
+ throw new Error('Area missing must be "error" or "break".');
8
+ }
9
+ return value;
10
+ }
11
+
12
+ export function normalizeAreaBound(bound) {
13
+ if (typeof bound === "string") return { field: validateNonEmptyString(bound, "Area endpoint field") };
14
+ if (!isPlainObject(bound)) throw new TypeError("Area bound must be a field or datum object.");
15
+ validateKeys(bound, ["datum"], "Area bound");
16
+ if (!Number.isFinite(bound.datum)) throw new TypeError("Area datum must be finite.");
17
+ return { datum: bound.datum };
18
+ }
19
+
20
+ export function readAreaEndpoint(rows, encoding, missing = "error") {
21
+ if (Object.hasOwn(encoding, "datum")) {
22
+ if (Object.hasOwn(encoding, "field") || !Number.isFinite(encoding.datum) || encoding.fieldType !== "quantitative") {
23
+ throw new TypeError("Area endpoint requires exactly one finite quantitative datum or field.");
24
+ }
25
+ return rows.map(() => encoding.datum);
26
+ }
27
+ if (missing !== "break") return readQuantitativeField(rows, encoding.field);
28
+ validateNonEmptyString(encoding.field, "Area endpoint field");
29
+ if (rows.length > 0 && !rows.some(row => Object.hasOwn(row, encoding.field))) {
30
+ throw new Error(`Area endpoint field "${encoding.field}" does not exist.`);
31
+ }
32
+ return rows.map((row, index) => {
33
+ const value = row[encoding.field];
34
+ if (value == null) return null;
35
+ if (!Number.isFinite(value)) {
36
+ throw new TypeError(`Field "${encoding.field}" must contain a finite number at row ${index}.`);
37
+ }
38
+ return value;
39
+ });
40
+ }
41
+
42
+ export function validateAreaEndpointPair(primary, secondary) {
43
+ if (primary?.datum !== undefined && secondary?.datum !== undefined) {
44
+ throw new Error("Area range requires at least one field endpoint.");
45
+ }
46
+ }