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,192 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateKeys } from "../../core/validation.js";
4
+ import { findDataset } from "../../selectors/datasets.js";
5
+ import { findLayer } from "../../selectors/layers.js";
6
+ import { rebindDistributionGuides } from "../distributions/revision.js";
7
+ import {
8
+ resolveViolinDensity,
9
+ resolveViolinRoles,
10
+ resolveViolinSplit
11
+ } from "./create.js";
12
+
13
+ const OPERATION = "editViolinPlot";
14
+ const OPTIONS = Object.freeze([
15
+ "target", "data", "x", "y", "split", "density"
16
+ ]);
17
+
18
+ function resolveOwner(program, requested) {
19
+ const eligible = program.semanticSpec.layers.filter(
20
+ layer => program.markConfigs[layer.id]?.violinPlot?.materialized === true
21
+ );
22
+ if (requested !== undefined) {
23
+ const id = validateUserId(requested, "Violin-plot owner id");
24
+ const layer = findLayer(program, id);
25
+ if (layer === undefined || !eligible.includes(layer)) {
26
+ throw new Error(`Unknown violin-plot owner "${id}".`);
27
+ }
28
+ return layer;
29
+ }
30
+ const current = findLayer(program, program.context.currentMark);
31
+ if (current !== undefined && eligible.includes(current)) return current;
32
+ if (eligible.length === 1) return eligible[0];
33
+ if (eligible.length === 0) throw new Error(`${OPERATION} requires a violin plot.`);
34
+ throw new Error(`${OPERATION} target is ambiguous; provide target.`);
35
+ }
36
+
37
+ function currentPosition(owner, current, channel) {
38
+ const categorical = current.orientation === "vertical"
39
+ ? channel === "x"
40
+ : channel === "y";
41
+ return {
42
+ field: categorical ? current.category : current.value,
43
+ fieldType: categorical ? current.categoryType : "quantitative",
44
+ scale: { id: owner.encoding[channel].scale }
45
+ };
46
+ }
47
+
48
+ function requestedScale(args, channel, expectedId) {
49
+ if (!Object.hasOwn(args, channel) || typeof args[channel] !== "object") {
50
+ return undefined;
51
+ }
52
+ const scale = args[channel].scale;
53
+ if (scale === undefined) return undefined;
54
+ if (scale.id !== undefined && scale.id !== expectedId) {
55
+ throw new Error(`${OPERATION} ${channel} scale cannot change its id.`);
56
+ }
57
+ const { id: _id, ...definition } = scale;
58
+ void _id;
59
+ return definition;
60
+ }
61
+
62
+ function assertSplitSelectionCompatibility(program, owner, changesSplit) {
63
+ if (!changesSplit) return;
64
+ const referenced = Object.entries(
65
+ program.materializationConfigs.selections ?? {}
66
+ ).find(([, selection]) =>
67
+ selection.target === owner.id && selection.selector?.channel === "color"
68
+ );
69
+ if (referenced !== undefined) {
70
+ throw new Error(
71
+ `${OPERATION} cannot change split while selection "${referenced[0]}" ` +
72
+ "references color."
73
+ );
74
+ }
75
+ }
76
+
77
+ export const editViolinPlot = action(
78
+ {
79
+ op: OPERATION,
80
+ description: "Revise one violin plot's data and statistical roles."
81
+ },
82
+ function (args = {}) {
83
+ validateKeys(args, OPTIONS, OPERATION);
84
+ if (!OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
85
+ throw new Error(`${OPERATION} requires at least one violin-plot option.`);
86
+ }
87
+ const owner = resolveOwner(this, args.target);
88
+ const current = this.markConfigs[owner.id].violinPlot;
89
+ const source = Object.hasOwn(args, "data")
90
+ ? validateUserId(args.data, "Violin-plot data id")
91
+ : current.source;
92
+ const dataset = findDataset(this, source);
93
+ if (dataset === undefined) {
94
+ throw new Error(`Unknown violin-plot data "${source}".`);
95
+ }
96
+ const roles = resolveViolinRoles(
97
+ dataset,
98
+ Object.hasOwn(args, "x") ? args.x : currentPosition(owner, current, "x"),
99
+ Object.hasOwn(args, "y") ? args.y : currentPosition(owner, current, "y"),
100
+ OPERATION
101
+ );
102
+ const split = Object.hasOwn(args, "split")
103
+ ? resolveViolinSplit(args.split, roles.category, OPERATION, {
104
+ removable: true
105
+ })
106
+ : current.split;
107
+ if (split?.field === roles.category.field) {
108
+ throw new Error(
109
+ `${OPERATION} split field must differ from its category field.`
110
+ );
111
+ }
112
+ const density = resolveViolinDensity(args.density, current.density, OPERATION);
113
+ const changesSplit = split?.field !== current.split?.field;
114
+ assertSplitSelectionCompatibility(this, owner, changesSplit);
115
+ if (current.colorRole === "split" && split === undefined) {
116
+ throw new Error(
117
+ `${OPERATION} cannot remove split while color encodes the split field.`
118
+ );
119
+ }
120
+
121
+ const categoryChannel = roles.orientation === "vertical" ? "x" : "y";
122
+ const valueChannel = roles.orientation === "vertical" ? "y" : "x";
123
+ const categoryScale = requestedScale(
124
+ args, categoryChannel, owner.encoding[categoryChannel].scale
125
+ );
126
+ const valueScale = requestedScale(
127
+ args, valueChannel, owner.encoding[valueChannel].scale
128
+ );
129
+ const { width, side, ...densityOptions } = density;
130
+ let next = this;
131
+ if (current.colorRole === "split" && changesSplit) {
132
+ next = next.editSemantic({
133
+ property: `layer[${owner.id}].encoding.color.field`,
134
+ value: split.field
135
+ });
136
+ }
137
+ next = next.editDensity({
138
+ target: owner.id,
139
+ source,
140
+ field: roles.value.field,
141
+ groupBy: roles.category.field,
142
+ densityChannel: roles.densityChannel,
143
+ ...densityOptions,
144
+ ...(valueScale === undefined ? {} : { valueScale }),
145
+ placement: {
146
+ type: "category",
147
+ ...(side === undefined ? {} : { side }),
148
+ width,
149
+ ...(split === undefined ? {} : { split }),
150
+ ...(categoryScale === undefined ? {} : { scale: categoryScale })
151
+ }
152
+ });
153
+ next = rebindDistributionGuides(next, {
154
+ oldXScale: owner.encoding.x.scale,
155
+ oldYScale: owner.encoding.y.scale,
156
+ newXScale: owner.encoding.x.scale,
157
+ newYScale: owner.encoding.y.scale,
158
+ oldXTitle: current.orientation === "vertical" ? current.category : current.value,
159
+ oldYTitle: current.orientation === "vertical" ? current.value : current.category,
160
+ newXTitle: roles.x.field,
161
+ newYTitle: roles.y.field,
162
+ oldMeasureChannel: current.orientation === "vertical" ? "y" : "x",
163
+ newMeasureChannel: valueChannel
164
+ });
165
+ const revisedLayer = findLayer(next, owner.id);
166
+ const transform = findDataset(next, revisedLayer.data).transform[0];
167
+ next = next._withMarkConfig(owner.id, {
168
+ ...next.markConfigs[owner.id],
169
+ violinPlot: {
170
+ ...current,
171
+ materialized: true,
172
+ source,
173
+ orientation: roles.orientation,
174
+ category: roles.category.field,
175
+ categoryType: roles.category.fieldType,
176
+ value: roles.value.field,
177
+ split: transform.placement.split,
178
+ density: {
179
+ bandwidth: transform.bandwidth,
180
+ extent: transform.extent,
181
+ steps: transform.steps,
182
+ kernel: transform.kernel,
183
+ normalization: transform.normalization,
184
+ ...(transform.weight === undefined ? {} : { weight: transform.weight }),
185
+ width: transform.placement.width,
186
+ side: transform.placement.side
187
+ }
188
+ }
189
+ });
190
+ return next._withContext({ currentMark: owner.id, currentData: source });
191
+ }
192
+ );
@@ -1,5 +1,7 @@
1
1
  import { createViolinPlot } from "./create.js";
2
+ import { editViolinPlot } from "./edit.js";
2
3
 
3
4
  export function registerViolinPlotActions(ProgramClass) {
4
5
  ProgramClass.prototype.createViolinPlot = createViolinPlot;
6
+ ProgramClass.prototype.editViolinPlot = editViolinPlot;
5
7
  }
@@ -173,6 +173,13 @@ export class ChartProgram {
173
173
  });
174
174
  }
175
175
 
176
+ _withoutResolvedScale(id) {
177
+ if (!Object.hasOwn(this.resolvedScales, id)) return this;
178
+ const { [id]: unused, ...remaining } = this.resolvedScales;
179
+ void unused;
180
+ return this._clone({ resolvedScales: freezeOwned(remaining) });
181
+ }
182
+
176
183
  _withMarkConfig(id, config) {
177
184
  if (typeof id !== "string" || id.length === 0) {
178
185
  throw new TypeError("Mark config id must be a non-empty string.");
@@ -1,11 +1,28 @@
1
1
  import { cloneAndFreeze, freezeOwned, isPlainObject } from "./immutable.js";
2
2
 
3
3
  const metadataByWrappedAction = new WeakMap();
4
+ const implementationByWrappedAction = new WeakMap();
5
+ let actionCompletionHook;
6
+
7
+ export function setActionCompletionHook(hook) {
8
+ if (hook !== undefined && typeof hook !== "function") {
9
+ throw new TypeError("Action completion hook must be a function.");
10
+ }
11
+ actionCompletionHook = hook;
12
+ }
4
13
 
5
14
  export function getWrappedActionMetadata(value) {
6
15
  return metadataByWrappedAction.get(value);
7
16
  }
8
17
 
18
+ export function invokeWrappedActionImplementation(value, program, args = {}) {
19
+ const implementation = implementationByWrappedAction.get(value);
20
+ if (implementation === undefined) {
21
+ throw new TypeError("Expected an action created by action().");
22
+ }
23
+ return implementation.call(program, args);
24
+ }
25
+
9
26
  function summarizeObject(value, ancestors = new WeakSet()) {
10
27
  if (ancestors.has(value)) {
11
28
  throw new TypeError("Action arguments must not contain circular references.");
@@ -127,19 +144,32 @@ export function action(metadata, implementation) {
127
144
  if (scope === "unit") this._assertUnitProgram(ownedMetadata.op);
128
145
  if (scope === "composition") this._assertCompositionProgram(ownedMetadata.op);
129
146
 
147
+ const summarizedArgs = summarizeArgs(args);
130
148
  const entered = this._enterAction({
131
149
  ...ownedMetadata,
132
- args: summarizeArgs(args)
150
+ args: summarizedArgs
133
151
  });
134
- const result = implementation.call(entered, args);
152
+ let result = implementation.call(entered, args);
135
153
 
136
154
  if (!(result instanceof this.constructor)) {
137
155
  throw new TypeError(`${ownedMetadata.op} must return a ChartProgram.`);
138
156
  }
139
157
 
158
+ if (this.actionStack.length === 0 && actionCompletionHook !== undefined) {
159
+ result = actionCompletionHook(result, {
160
+ source: this,
161
+ metadata: ownedMetadata,
162
+ args: summarizedArgs
163
+ });
164
+ if (!(result instanceof this.constructor)) {
165
+ throw new TypeError("Action completion hook must return a ChartProgram.");
166
+ }
167
+ }
168
+
140
169
  return result._exitAction();
141
170
  };
142
171
 
143
172
  metadataByWrappedAction.set(wrappedAction, ownedMetadata);
173
+ implementationByWrappedAction.set(wrappedAction, implementation);
144
174
  return wrappedAction;
145
175
  }
@@ -11,24 +11,23 @@ function validateFacetSpec(compositionSpec) {
11
11
  }
12
12
  if (
13
13
  !Number.isInteger(compositionSpec.columns) ||
14
- compositionSpec.columns <= 0 ||
15
- compositionSpec.columns > compositionSpec.children.length
14
+ compositionSpec.columns <= 0
16
15
  ) {
17
16
  throw new RangeError(
18
- "Facet compositionSpec.columns must be a positive integer no larger than its children."
17
+ "Facet compositionSpec.columns must be a positive integer."
19
18
  );
20
19
  }
21
20
  if (!isPlainObject(compositionSpec.facet)) {
22
21
  throw new TypeError("Facet compositionSpec.facet must be a plain object.");
23
22
  }
24
- const facetKeys = ["data", "field", "values", "scales", "guides"];
23
+ const facetKeys = ["data", "field", "values", "grid", "repeat", "scales", "guides"];
25
24
  const unknownFacet = Object.keys(compositionSpec.facet).find(
26
25
  key => !facetKeys.includes(key)
27
26
  );
28
27
  if (unknownFacet !== undefined) {
29
28
  throw new Error(`Unknown compositionSpec.facet property "${unknownFacet}".`);
30
29
  }
31
- for (const property of ["data", "field"]) {
30
+ for (const property of ["data"]) {
32
31
  if (
33
32
  typeof compositionSpec.facet[property] !== "string" ||
34
33
  compositionSpec.facet[property].length === 0
@@ -36,6 +35,134 @@ function validateFacetSpec(compositionSpec) {
36
35
  throw new TypeError(`compositionSpec.facet.${property} must be a non-empty string.`);
37
36
  }
38
37
  }
38
+ const grid = compositionSpec.facet.grid;
39
+ const repeat = compositionSpec.facet.repeat;
40
+ if (grid !== undefined && repeat !== undefined) {
41
+ throw new Error("Facet compositionSpec cannot combine grid and repeat recipes.");
42
+ }
43
+ if (grid === undefined && repeat === undefined) {
44
+ if (compositionSpec.columns > compositionSpec.children.length) {
45
+ throw new RangeError(
46
+ "Facet compositionSpec.columns must be no larger than its children."
47
+ );
48
+ }
49
+ if (
50
+ typeof compositionSpec.facet.field !== "string" ||
51
+ compositionSpec.facet.field.length === 0
52
+ ) {
53
+ throw new TypeError("compositionSpec.facet.field must be a non-empty string.");
54
+ }
55
+ } else if (grid !== undefined) {
56
+ if (compositionSpec.columns !== grid.columns?.values?.length) {
57
+ throw new RangeError(
58
+ "Grid facet compositionSpec.columns must match its column domain."
59
+ );
60
+ }
61
+ if (compositionSpec.facet.field !== undefined) {
62
+ throw new Error("Grid facet compositionSpec does not use facet.field.");
63
+ }
64
+ if (!isPlainObject(grid)) {
65
+ throw new TypeError("compositionSpec.facet.grid must be a plain object.");
66
+ }
67
+ const gridKeys = ["rows", "columns", "combinations", "cells"];
68
+ const unknownGrid = Object.keys(grid).find(key => !gridKeys.includes(key));
69
+ if (unknownGrid !== undefined) {
70
+ throw new Error(`Unknown compositionSpec.facet.grid property "${unknownGrid}".`);
71
+ }
72
+ for (const role of ["rows", "columns"]) {
73
+ const value = grid[role];
74
+ if (!isPlainObject(value) || Object.keys(value).some(
75
+ key => !["field", "values"].includes(key)
76
+ )) {
77
+ throw new TypeError(
78
+ `compositionSpec.facet.grid.${role} requires field and values.`
79
+ );
80
+ }
81
+ if (typeof value.field !== "string" || value.field.length === 0) {
82
+ throw new TypeError(
83
+ `compositionSpec.facet.grid.${role}.field must be a non-empty string.`
84
+ );
85
+ }
86
+ if (
87
+ !Array.isArray(value.values) || value.values.length === 0 ||
88
+ value.values.some(item => !(
89
+ typeof item === "string" || typeof item === "boolean" ||
90
+ (typeof item === "number" && Number.isFinite(item))
91
+ )) || new Set(value.values).size !== value.values.length
92
+ ) {
93
+ throw new TypeError(
94
+ `compositionSpec.facet.grid.${role}.values must contain unique scalars.`
95
+ );
96
+ }
97
+ }
98
+ if (grid.rows.field === grid.columns.field) {
99
+ throw new Error("compositionSpec.facet.grid fields must be different.");
100
+ }
101
+ if (!["observed", "full"].includes(grid.combinations)) {
102
+ throw new Error(
103
+ 'compositionSpec.facet.grid.combinations must be "observed" or "full".'
104
+ );
105
+ }
106
+ if (!Array.isArray(grid.cells) || grid.cells.length !== compositionSpec.children.length) {
107
+ throw new TypeError(
108
+ "compositionSpec.facet.grid.cells must contain one descriptor per child."
109
+ );
110
+ }
111
+ const cellKeys = ["id", "row", "column", "rowValue", "columnValue", "empty"];
112
+ const coordinates = new Set();
113
+ for (const cell of grid.cells) {
114
+ if (!isPlainObject(cell) || Object.keys(cell).some(key => !cellKeys.includes(key))) {
115
+ throw new TypeError("compositionSpec.facet.grid cells must be canonical objects.");
116
+ }
117
+ if (!compositionSpec.children.includes(cell.id)) {
118
+ throw new Error(`Unknown grid facet child "${cell.id}".`);
119
+ }
120
+ if (!Number.isInteger(cell.row) || cell.row < 0 || cell.row >= grid.rows.values.length ||
121
+ !Number.isInteger(cell.column) || cell.column < 0 || cell.column >= grid.columns.values.length) {
122
+ throw new RangeError("Grid facet cell coordinates must be inside their domains.");
123
+ }
124
+ if (cell.rowValue !== grid.rows.values[cell.row] ||
125
+ cell.columnValue !== grid.columns.values[cell.column]) {
126
+ throw new Error("Grid facet cell values must match their row and column coordinates.");
127
+ }
128
+ if (typeof cell.empty !== "boolean") {
129
+ throw new TypeError("Grid facet cell empty must be a boolean.");
130
+ }
131
+ const key = `${cell.row}:${cell.column}`;
132
+ if (coordinates.has(key)) {
133
+ throw new Error("Grid facet cells must occupy unique coordinates.");
134
+ }
135
+ coordinates.add(key);
136
+ }
137
+ } else {
138
+ if (compositionSpec.facet.field !== undefined) {
139
+ throw new Error("Repeat compositionSpec does not use facet.field.");
140
+ }
141
+ if (compositionSpec.columns > compositionSpec.children.length) {
142
+ throw new RangeError(
143
+ "Repeat compositionSpec.columns must be no larger than its children."
144
+ );
145
+ }
146
+ if (!isPlainObject(repeat) || Object.keys(repeat).some(
147
+ key => !["target", "channel", "fields"].includes(key)
148
+ )) {
149
+ throw new TypeError(
150
+ "compositionSpec.facet.repeat requires target, channel, and fields."
151
+ );
152
+ }
153
+ const repeatChannel = ["x", "y", "theta", "r"].includes(repeat.channel) ||
154
+ (isPlainObject(repeat.channel) &&
155
+ Object.keys(repeat.channel).length === 1 &&
156
+ typeof repeat.channel.parallelDimension === "string" &&
157
+ repeat.channel.parallelDimension.length > 0);
158
+ if (typeof repeat.target !== "string" || repeat.target.length === 0 ||
159
+ !repeatChannel ||
160
+ !Array.isArray(repeat.fields) || repeat.fields.length === 0 ||
161
+ repeat.fields.some(field => typeof field !== "string" || field.length === 0) ||
162
+ new Set(repeat.fields).size !== repeat.fields.length) {
163
+ throw new TypeError("compositionSpec.facet.repeat is not canonical.");
164
+ }
165
+ }
39
166
  const values = compositionSpec.facet.values;
40
167
  if (
41
168
  !Array.isArray(values) ||
@@ -45,12 +172,18 @@ function validateFacetSpec(compositionSpec) {
45
172
  typeof value === "boolean" ||
46
173
  (typeof value === "number" && Number.isFinite(value))
47
174
  )) ||
48
- new Set(values).size !== values.length
175
+ (grid === undefined && new Set(values).size !== values.length)
49
176
  ) {
50
177
  throw new TypeError(
51
178
  "compositionSpec.facet.values must contain one unique scalar per child."
52
179
  );
53
180
  }
181
+ if (repeat !== undefined && (
182
+ values.length !== repeat.fields.length ||
183
+ values.some((value, index) => value !== repeat.fields[index])
184
+ )) {
185
+ throw new Error("compositionSpec.facet.values must match repeat fields.");
186
+ }
54
187
  const scales = compositionSpec.facet.scales;
55
188
  if (
56
189
  !isPlainObject(scales) ||
@@ -80,13 +213,13 @@ function validateFacetSpec(compositionSpec) {
80
213
  function validateCompositionChildren(compositionSpec, childIds, facet) {
81
214
  if (
82
215
  !Array.isArray(compositionSpec.children) ||
83
- compositionSpec.children.length < (facet ? 1 : 2) ||
216
+ compositionSpec.children.length < 1 ||
84
217
  !compositionSpec.children.every(id => typeof id === "string" && id.length > 0)
85
218
  ) {
86
219
  throw new TypeError(
87
220
  facet
88
221
  ? "Facet compositionSpec.children requires at least one child ID."
89
- : "compositionSpec.children requires at least two child IDs."
222
+ : "compositionSpec.children requires at least one child ID."
90
223
  );
91
224
  }
92
225
  if (new Set(compositionSpec.children).size !== compositionSpec.children.length) {
@@ -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);
@@ -26,3 +27,22 @@ export function ownChildPrograms(children, ProgramClass) {
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
+ }