ggaction 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +10 -3
  11. package/src/actions/basic.js +4 -2
  12. package/src/actions/boxPlots/components.js +14 -4
  13. package/src/actions/boxPlots/edit.js +31 -5
  14. package/src/actions/boxPlots/materialize.js +13 -4
  15. package/src/actions/boxPlots/options.js +48 -16
  16. package/src/actions/canvas/actions.js +3 -1
  17. package/src/actions/canvas/fitting.js +211 -0
  18. package/src/actions/canvas/index.js +2 -0
  19. package/src/actions/categoryOrder/index.js +18 -6
  20. package/src/actions/charts/area.js +51 -0
  21. package/src/actions/charts/bar.js +13 -6
  22. package/src/actions/charts/beeswarm.js +70 -0
  23. package/src/actions/charts/density.js +48 -0
  24. package/src/actions/charts/ecdf.js +186 -0
  25. package/src/actions/charts/endpoints.js +473 -0
  26. package/src/actions/charts/heatmap.js +4 -3
  27. package/src/actions/charts/histogram.js +5 -4
  28. package/src/actions/charts/horizon.js +73 -0
  29. package/src/actions/charts/index.js +37 -0
  30. package/src/actions/charts/interval-regression.js +172 -0
  31. package/src/actions/charts/line.js +8 -6
  32. package/src/actions/charts/parallel.js +8 -2
  33. package/src/actions/charts/pie.js +52 -0
  34. package/src/actions/charts/polar.js +134 -0
  35. package/src/actions/charts/radar.js +305 -0
  36. package/src/actions/charts/radial.js +50 -0
  37. package/src/actions/charts/raincloud.js +422 -0
  38. package/src/actions/charts/rug-strip.js +283 -0
  39. package/src/actions/charts/scatter.js +11 -3
  40. package/src/actions/charts/shared.js +69 -10
  41. package/src/actions/composition/actions.js +129 -3
  42. package/src/actions/coordinates/actions.js +1 -1
  43. package/src/actions/coordinates/edit.js +76 -0
  44. package/src/actions/data/bin.js +29 -0
  45. package/src/actions/data/bin2d.js +57 -185
  46. package/src/actions/data/complete.js +26 -0
  47. package/src/actions/data/computed.js +24 -0
  48. package/src/actions/data/create.js +2 -2
  49. package/src/actions/data/density.js +12 -20
  50. package/src/actions/data/derived.js +111 -15
  51. package/src/actions/data/ecdf.js +38 -0
  52. package/src/actions/data/edit.js +539 -0
  53. package/src/actions/data/filter.js +226 -30
  54. package/src/actions/data/fold.js +24 -0
  55. package/src/actions/data/impute.js +26 -0
  56. package/src/actions/data/index.js +38 -0
  57. package/src/actions/data/interval.js +2 -0
  58. package/src/actions/data/intervalEdit.js +86 -4
  59. package/src/actions/data/normalize.js +27 -0
  60. package/src/actions/data/regression.js +19 -21
  61. package/src/actions/data/revisionBindings.js +145 -0
  62. package/src/actions/data/shared.js +20 -4
  63. package/src/actions/data/stack.js +26 -0
  64. package/src/actions/data/summary.js +26 -0
  65. package/src/actions/data/timeUnit.js +4 -1
  66. package/src/actions/data/window.js +1 -1
  67. package/src/actions/encodings/appearance.js +32 -13
  68. package/src/actions/encodings/barWidth.js +8 -0
  69. package/src/actions/encodings/basic.js +2 -0
  70. package/src/actions/encodings/channels.js +415 -0
  71. package/src/actions/encodings/color/continuous.js +19 -59
  72. package/src/actions/encodings/color/index.js +30 -23
  73. package/src/actions/encodings/color/layout.js +3 -97
  74. package/src/actions/encodings/color/policy.js +5 -8
  75. package/src/actions/encodings/density/resolve.js +37 -5
  76. package/src/actions/encodings/density.js +124 -34
  77. package/src/actions/encodings/histogram.js +3 -2
  78. package/src/actions/encodings/horizon/resolve.js +8 -4
  79. package/src/actions/encodings/horizon.js +18 -8
  80. package/src/actions/encodings/index.js +6 -0
  81. package/src/actions/encodings/offset.js +30 -29
  82. package/src/actions/encodings/parallel.js +2 -8
  83. package/src/actions/encodings/position/apply.js +63 -11
  84. package/src/actions/encodings/position/index.js +52 -6
  85. package/src/actions/encodings/position/policies/arc.js +14 -0
  86. package/src/actions/encodings/position/policies/area.js +4 -42
  87. package/src/actions/encodings/position/policies/bar.js +57 -13
  88. package/src/actions/encodings/position/resolve.js +88 -26
  89. package/src/actions/encodings/ranged.js +123 -97
  90. package/src/actions/encodings/remove.js +44 -34
  91. package/src/actions/encodings/ruleAppearance.js +24 -32
  92. package/src/actions/encodings/seriesLayout.js +105 -0
  93. package/src/actions/encodings/shared.js +107 -37
  94. package/src/actions/encodings/stroke.js +226 -0
  95. package/src/actions/encodings/strokeDash.js +9 -6
  96. package/src/actions/encodings/temporal.js +7 -0
  97. package/src/actions/encodings/text.js +30 -15
  98. package/src/actions/errorBands/create.js +41 -8
  99. package/src/actions/errorBands/edit.js +360 -25
  100. package/src/actions/errorBars/create.js +56 -17
  101. package/src/actions/errorBars/edit.js +372 -21
  102. package/src/actions/facets/actions.js +619 -41
  103. package/src/actions/facets/derive.js +139 -25
  104. package/src/actions/gradientPlots/components.js +43 -13
  105. package/src/actions/gradientPlots/edit.js +14 -4
  106. package/src/actions/gradientPlots/materialize.js +8 -8
  107. package/src/actions/gradientPlots/options.js +10 -2
  108. package/src/actions/guides/applicability.js +28 -19
  109. package/src/actions/guides/axes/axes.js +4 -3
  110. package/src/actions/guides/axes/axis.js +18 -18
  111. package/src/actions/guides/axes/components.js +43 -0
  112. package/src/actions/guides/axes/edit.js +16 -51
  113. package/src/actions/guides/axes/labels.js +184 -28
  114. package/src/actions/guides/axes/lines.js +13 -7
  115. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  116. package/src/actions/guides/axes/parallel/policy.js +137 -0
  117. package/src/actions/guides/axes/parallel/resolve.js +50 -25
  118. package/src/actions/guides/axes/parallel.js +93 -217
  119. package/src/actions/guides/axes/policy.js +21 -67
  120. package/src/actions/guides/axes/tickGroups.js +15 -8
  121. package/src/actions/guides/axes/ticks.js +11 -6
  122. package/src/actions/guides/axes/titles.js +32 -11
  123. package/src/actions/guides/basic.js +2 -0
  124. package/src/actions/guides/facade.js +106 -0
  125. package/src/actions/guides/facadeAxes.js +200 -0
  126. package/src/actions/guides/facadeLegend.js +63 -0
  127. package/src/actions/guides/grids/grid.js +37 -33
  128. package/src/actions/guides/grids/resolve.js +17 -4
  129. package/src/actions/guides/guides.js +54 -31
  130. package/src/actions/guides/legends/blocks.js +426 -0
  131. package/src/actions/guides/legends/categorical/actions.js +309 -279
  132. package/src/actions/guides/legends/categorical/components.js +21 -17
  133. package/src/actions/guides/legends/categorical/index.js +37 -33
  134. package/src/actions/guides/legends/categorical/layout.js +123 -354
  135. package/src/actions/guides/legends/categorical/options.js +32 -16
  136. package/src/actions/guides/legends/categorical/recipes.js +114 -9
  137. package/src/actions/guides/legends/categorical/resolve.js +30 -15
  138. package/src/actions/guides/legends/categorical/symbols.js +87 -33
  139. package/src/actions/guides/legends/continuous/common.js +99 -26
  140. package/src/actions/guides/legends/continuous/gradient.js +120 -89
  141. package/src/actions/guides/legends/continuous/index.js +8 -2
  142. package/src/actions/guides/legends/continuous/interval.js +79 -95
  143. package/src/actions/guides/legends/continuous/opacity.js +113 -89
  144. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  145. package/src/actions/guides/legends/creation.js +40 -0
  146. package/src/actions/guides/legends/edit.js +321 -90
  147. package/src/actions/guides/legends/focused.js +2 -2
  148. package/src/actions/guides/legends/index.js +2 -0
  149. package/src/actions/guides/legends/lane.js +149 -102
  150. package/src/actions/guides/legends/lifecycle.js +143 -0
  151. package/src/actions/guides/legends/remove.js +43 -78
  152. package/src/actions/guides/legends/sampling.js +140 -0
  153. package/src/actions/guides/legends/size.js +213 -116
  154. package/src/actions/guides/legends/strokeWidth.js +121 -73
  155. package/src/actions/guides/legends/target.js +123 -0
  156. package/src/actions/guides/legends/transition.js +192 -0
  157. package/src/actions/guides/polar/axes/facade.js +81 -66
  158. package/src/actions/guides/polar/axes/labels.js +28 -8
  159. package/src/actions/guides/polar/axes/lines.js +12 -5
  160. package/src/actions/guides/polar/axes/shared.js +22 -0
  161. package/src/actions/guides/polar/axes/ticks.js +6 -5
  162. package/src/actions/guides/polar/axes/titles.js +6 -5
  163. package/src/actions/guides/polar/grids.js +8 -8
  164. package/src/actions/guides/polar/resolve.js +12 -8
  165. package/src/actions/guides/reuse.js +44 -0
  166. package/src/actions/guides/tickValues.js +19 -21
  167. package/src/actions/index.js +15 -2
  168. package/src/actions/intervals/resolve.js +35 -15
  169. package/src/actions/marks/arc/actions.js +78 -20
  170. package/src/actions/marks/area/actions.js +51 -12
  171. package/src/actions/marks/area/materialize.js +22 -3
  172. package/src/actions/marks/bar/create.js +11 -2
  173. package/src/actions/marks/bar/edit.js +20 -3
  174. package/src/actions/marks/bar/materialize.js +33 -9
  175. package/src/actions/marks/index.js +14 -0
  176. package/src/actions/marks/line/actions.js +71 -113
  177. package/src/actions/marks/line/materialize.js +57 -56
  178. package/src/actions/marks/point/create.js +8 -2
  179. package/src/actions/marks/point/edit.js +29 -21
  180. package/src/actions/marks/point/index.js +1 -0
  181. package/src/actions/marks/point/jitter.js +3 -0
  182. package/src/actions/marks/point/materialize.js +154 -16
  183. package/src/actions/marks/point/packing.js +68 -0
  184. package/src/actions/marks/rect/actions.js +33 -12
  185. package/src/actions/marks/references.js +658 -0
  186. package/src/actions/marks/remove.js +87 -4
  187. package/src/actions/marks/rule/actions.js +77 -9
  188. package/src/actions/marks/rule/appearance.js +26 -0
  189. package/src/actions/marks/shared.js +13 -13
  190. package/src/actions/marks/text/actions.js +296 -13
  191. package/src/actions/marks/text/index.js +4 -0
  192. package/src/actions/marks/text/layout.js +156 -8
  193. package/src/actions/marks/text/removal.js +173 -0
  194. package/src/actions/marks/text/selection.js +73 -0
  195. package/src/actions/marks/tick/actions.js +22 -5
  196. package/src/actions/primitives/createGraphics.js +3 -1
  197. package/src/actions/primitives/editGraphics.js +26 -0
  198. package/src/actions/primitives/semantic.js +3 -2
  199. package/src/actions/primitives/semanticAction.js +120 -10
  200. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  201. package/src/actions/primitives/semanticValidation/index.js +9 -1
  202. package/src/actions/primitives/semanticValidation/layer.js +23 -1
  203. package/src/actions/primitives/semanticValidation/scale.js +38 -8
  204. package/src/actions/regression/components.js +26 -11
  205. package/src/actions/regression/create.js +11 -3
  206. package/src/actions/regression/edit.js +40 -13
  207. package/src/actions/regression/resolve.js +1 -1
  208. package/src/actions/resources/index.js +7 -0
  209. package/src/actions/resources/remove.js +172 -0
  210. package/src/actions/scales/channels.js +212 -0
  211. package/src/actions/scales/consumers/common.js +21 -54
  212. package/src/actions/scales/consumers/families.js +28 -14
  213. package/src/actions/scales/consumers/index.js +42 -4
  214. package/src/actions/scales/consumers/seriesLayout.js +57 -12
  215. package/src/actions/scales/create.js +58 -26
  216. package/src/actions/scales/definitions.js +109 -39
  217. package/src/actions/scales/edit.js +101 -34
  218. package/src/actions/scales/editPolicy.js +95 -35
  219. package/src/actions/scales/index.js +6 -0
  220. package/src/actions/scales/materialize.js +14 -49
  221. package/src/actions/scales/offset.js +48 -0
  222. package/src/actions/scales/parallel.js +71 -0
  223. package/src/actions/scales/patch.js +1 -1
  224. package/src/actions/scales/preview.js +140 -0
  225. package/src/actions/scales/quantitativeColor.js +9 -5
  226. package/src/actions/selection/actions.js +154 -30
  227. package/src/actions/selection/index.js +3 -0
  228. package/src/actions/theme/actions.js +98 -0
  229. package/src/actions/theme/composition.js +238 -0
  230. package/src/actions/theme/index.js +16 -0
  231. package/src/actions/theme/reconcile.js +1130 -0
  232. package/src/actions/theme/state.js +160 -0
  233. package/src/actions/titles/actions.js +11 -10
  234. package/src/actions/titles/resolve.js +1 -40
  235. package/src/actions/violinPlots/create.js +90 -43
  236. package/src/actions/violinPlots/edit.js +192 -0
  237. package/src/actions/violinPlots/index.js +2 -0
  238. package/src/core/ChartProgram.js +7 -0
  239. package/src/core/action.js +32 -2
  240. package/src/core/compositionState.js +141 -8
  241. package/src/core/programState.js +20 -0
  242. package/src/core/resourceReferences.js +332 -0
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/grammar/aggregate.js +66 -23
  245. package/src/grammar/arcs.js +58 -1
  246. package/src/grammar/areaEndpoints.js +46 -0
  247. package/src/grammar/areaSeries.js +87 -41
  248. package/src/grammar/bars/aggregate.js +37 -13
  249. package/src/grammar/bars/geometry.js +44 -0
  250. package/src/grammar/bars/policy.js +9 -7
  251. package/src/grammar/bin.js +226 -0
  252. package/src/grammar/categoryOrder.js +34 -0
  253. package/src/grammar/complete.js +279 -0
  254. package/src/grammar/computed.js +351 -0
  255. package/src/grammar/coordinates.js +37 -0
  256. package/src/grammar/density.js +239 -37
  257. package/src/grammar/displayLabels.js +57 -0
  258. package/src/grammar/ecdf.js +198 -0
  259. package/src/grammar/facets/dependencies.js +133 -1
  260. package/src/grammar/facets/guides.js +3 -0
  261. package/src/grammar/facets/index.js +123 -52
  262. package/src/grammar/facets/scales.js +15 -18
  263. package/src/grammar/fold.js +123 -0
  264. package/src/grammar/horizon.js +6 -4
  265. package/src/grammar/impute.js +340 -0
  266. package/src/grammar/interval.js +37 -20
  267. package/src/grammar/lineSeries.js +20 -63
  268. package/src/grammar/markFilter.js +22 -4
  269. package/src/grammar/markLabelSelection.js +57 -0
  270. package/src/grammar/markLabels.js +110 -0
  271. package/src/grammar/normalize.js +366 -0
  272. package/src/grammar/parallelAxisTitles.js +16 -0
  273. package/src/grammar/pathSeries.js +109 -0
  274. package/src/grammar/pointPacking.js +183 -0
  275. package/src/grammar/pointShapes.js +13 -4
  276. package/src/grammar/polar.js +90 -8
  277. package/src/grammar/positionDatum.js +12 -0
  278. package/src/grammar/rects.js +9 -1
  279. package/src/grammar/regression/derive.js +4 -0
  280. package/src/grammar/regression/index.js +8 -2
  281. package/src/grammar/regression/models.js +22 -7
  282. package/src/grammar/regression/parameters.js +63 -13
  283. package/src/grammar/rotation.js +29 -0
  284. package/src/grammar/roundedRect.js +180 -0
  285. package/src/grammar/rules.js +8 -21
  286. package/src/grammar/scales/color.js +23 -1
  287. package/src/grammar/scales/colorConsumers.js +57 -0
  288. package/src/grammar/scales/continuous.js +2 -2
  289. package/src/grammar/scales/definition.js +42 -5
  290. package/src/grammar/scales/discretized.js +11 -5
  291. package/src/grammar/scales/fields.js +38 -8
  292. package/src/grammar/scales/index.js +1 -0
  293. package/src/grammar/scales/mapping.js +2 -0
  294. package/src/grammar/scales/ordinal.js +12 -3
  295. package/src/grammar/scales/policies.js +2 -2
  296. package/src/grammar/scales/radial.js +55 -0
  297. package/src/grammar/scales/size.js +456 -0
  298. package/src/grammar/scales/types.js +1 -0
  299. package/src/grammar/schemas/concreteGraphic.js +14 -2
  300. package/src/grammar/schemas/graphic.js +15 -0
  301. package/src/grammar/schemas/graphicBounds.js +179 -30
  302. package/src/grammar/schemas/semanticPath.js +23 -4
  303. package/src/grammar/stack.js +170 -0
  304. package/src/grammar/statisticalReference.js +103 -0
  305. package/src/grammar/statistics/confidenceInterval.js +101 -0
  306. package/src/grammar/strokeStyle.js +77 -0
  307. package/src/grammar/summary.js +245 -0
  308. package/src/grammar/text.js +22 -16
  309. package/src/grammar/timeUnit.js +234 -13
  310. package/src/grammar/transformTopology.js +10 -0
  311. package/src/grammar/transforms.js +418 -16
  312. package/src/grammar/valueFormat.js +112 -0
  313. package/src/grammar/weightedStatistics.js +374 -0
  314. package/src/grammar/window.js +232 -21
  315. package/src/layout/aspect.js +80 -0
  316. package/src/layout/facets.js +100 -12
  317. package/src/layout/guideCollisions.js +12 -0
  318. package/src/layout/labels.js +343 -0
  319. package/src/layout/legend.js +2 -10
  320. package/src/layout/legendItems.js +102 -0
  321. package/src/layout/legendLane.js +100 -11
  322. package/src/materialization/bars/aggregate.js +56 -147
  323. package/src/materialization/bars/histogram.js +104 -20
  324. package/src/materialization/bars/ranged.js +19 -3
  325. package/src/materialization/bars/resolve.js +7 -4
  326. package/src/materialization/categorySlotOffset.js +40 -0
  327. package/src/materialization/composition.js +3 -1
  328. package/src/materialization/compositionSnapshot.js +1 -1
  329. package/src/materialization/coordinateBounds.js +204 -0
  330. package/src/materialization/dependencies.js +93 -16
  331. package/src/materialization/encodings.js +1 -1
  332. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  333. package/src/materialization/facetGuides/placement.js +8 -2
  334. package/src/materialization/facetGuides/preparation.js +12 -7
  335. package/src/materialization/facetHeaders.js +346 -0
  336. package/src/materialization/facets.js +23 -66
  337. package/src/materialization/guides/layout.js +57 -0
  338. package/src/materialization/guides/resources.js +28 -2
  339. package/src/materialization/legends.js +11 -2
  340. package/src/materialization/marks/capabilities.js +20 -5
  341. package/src/materialization/marks/grouping.js +52 -0
  342. package/src/materialization/marks/index.js +68 -27
  343. package/src/materialization/marks/policies.js +4 -1
  344. package/src/materialization/rect.js +55 -42
  345. package/src/materialization/rectConfig.js +5 -1
  346. package/src/materialization/rowEncoding.js +13 -13
  347. package/src/materialization/scaleGuideDependencies.js +8 -5
  348. package/src/materialization/scales/map.js +6 -3
  349. package/src/materialization/scales/policies/arc.js +42 -0
  350. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  351. package/src/materialization/scales/policies/offset.js +50 -8
  352. package/src/materialization/scales/policies/series.js +5 -1
  353. package/src/materialization/scales/resolve.js +65 -17
  354. package/src/materialization/selection/compatibility.js +11 -0
  355. package/src/materialization/selection/items/arc.js +28 -4
  356. package/src/materialization/selection/items/bar.js +42 -71
  357. package/src/materialization/selection/items/common.js +27 -6
  358. package/src/materialization/selection/items/path.js +3 -13
  359. package/src/materialization/selection/items/rect.js +35 -8
  360. package/src/materialization/selection/path.js +1 -1
  361. package/src/materialization/selection/point.js +3 -1
  362. package/src/materialization/text.js +324 -14
  363. package/src/mcp/adapter.js +2 -2
  364. package/src/renderers/canvas/circle.js +2 -3
  365. package/src/renderers/canvas/line.js +2 -3
  366. package/src/renderers/canvas/native.js +4 -4
  367. package/src/renderers/canvas/path.js +2 -3
  368. package/src/renderers/canvas/rect.js +2 -3
  369. package/src/renderers/canvas/stroke.js +11 -0
  370. package/src/renderers/svg.js +19 -0
  371. package/src/selectors/datasets.js +44 -0
  372. package/src/selectors/layers.js +7 -2
  373. package/src/selectors/scales.js +6 -0
  374. package/src/theme/defaults.js +140 -0
  375. package/types/basic.d.ts +17 -2
  376. package/types/index.d.ts +211 -0
  377. package/types/program.d.ts +2182 -204
  378. package/src/materialization/bars/grouped.js +0 -144
@@ -1,102 +1,62 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { validateKeys } from "../../../core/validation.js";
3
- import { legendGraphicIds, legendResourcePolicy } from
4
- "../../../materialization/guides/resources.js";
5
- import { LEGEND_CHANNELS } from "../../../core/vocabulary.js";
6
- import { resolveLegendTarget } from "./target.js";
3
+ import {
4
+ removeLegendKinds,
5
+ resolveCategoricalLegendRevision,
6
+ createCategoricalLegendFromConfig
7
+ } from "./lifecycle.js";
8
+ import { resolveLegendTarget, validateLegendChannels } from "./target.js";
9
+ import { planLegendBlockTransitions } from "./transition.js";
10
+
11
+ export { removeLegendKinds } from "./lifecycle.js";
7
12
 
8
13
  const OPTIONS = Object.freeze(["target", "channels"]);
9
14
 
10
15
  function legendKindChannels(kind, config) {
11
- if (["series", "color"].includes(kind)) return config.channels;
16
+ if (["series", "color", "stroke"].includes(kind)) return config.channels;
12
17
  return {
13
18
  size: ["size"],
14
19
  gradient: ["color"],
15
20
  interval: ["color"],
21
+ strokeGradient: ["stroke"],
22
+ strokeInterval: ["stroke"],
16
23
  opacity: ["opacity"],
17
24
  strokeWidth: ["strokeWidth"]
18
25
  }[kind];
19
26
  }
20
27
 
21
- function validateRequestedChannels(channels) {
22
- if (!Array.isArray(channels)) {
23
- throw new TypeError("removeLegend channels must be an array.");
24
- }
25
- if (channels.length === 0) {
26
- throw new Error("removeLegend channels must select at least one channel.");
27
- }
28
- const seen = new Set();
29
- for (const channel of channels) {
30
- if (!LEGEND_CHANNELS.includes(channel)) {
31
- throw new Error(`Unsupported legend channel "${channel}".`);
32
- }
33
- if (seen.has(channel)) {
34
- throw new Error(`removeLegend channels contains duplicate "${channel}".`);
35
- }
36
- seen.add(channel);
37
- }
38
- }
39
-
40
- function resolveRequestedKinds(program, target, channels) {
41
- validateRequestedChannels(channels);
28
+ function resolveRequestedRemoval(program, target, channels) {
29
+ validateLegendChannels(channels, "removeLegend");
42
30
  const requested = new Set(channels);
43
31
  const matched = new Set();
44
32
  const kinds = [];
33
+ let partial;
45
34
  for (const [kind, config] of Object.entries(program.guideConfigs.legend ?? {})) {
46
35
  if (config?.target !== target) continue;
47
36
  const owned = legendKindChannels(kind, config);
48
37
  const overlap = owned.filter(channel => requested.has(channel));
49
- if (overlap.length > 0 && overlap.length !== owned.length) {
50
- throw new Error(
51
- `Legend channels ${owned.map(channel => `"${channel}"`).join(", ")} ` +
52
- "belong to one combined block and must be removed together."
53
- );
54
- }
55
- if (overlap.length === owned.length) {
56
- kinds.push(kind);
57
- for (const channel of owned) matched.add(channel);
38
+ if (overlap.length === 0) continue;
39
+ kinds.push(kind);
40
+ for (const channel of overlap) matched.add(channel);
41
+ if (overlap.length !== owned.length) {
42
+ partial = { kind, config, channels: owned.filter(channel => !requested.has(channel)) };
58
43
  }
59
44
  }
60
45
  const missing = channels.filter(channel => !matched.has(channel));
61
46
  if (missing.length > 0) {
62
- throw new Error(
63
- `Legend target "${target}" has no complete block for channel "${missing[0]}".`
64
- );
65
- }
66
- return kinds;
67
- }
68
-
69
- export function removeLegendKinds(program, kinds) {
70
- const removed = new Set(kinds);
71
- const retainedSemanticKinds = new Set(
72
- Object.entries(program.guideConfigs.legend ?? {})
73
- .filter(([kind]) => !removed.has(kind))
74
- .map(([kind]) => legendResourcePolicy(kind).semanticKind)
75
- );
76
- const semanticKinds = new Set(
77
- kinds.map(kind => legendResourcePolicy(kind).semanticKind)
78
- );
79
- let next = program;
80
- for (const kind of semanticKinds) {
81
- if (
82
- !retainedSemanticKinds.has(kind) &&
83
- next.semanticSpec.guides.legend?.[kind] !== undefined
84
- ) {
85
- next = next.editSemantic({
86
- property: `guide.legend.${kind}`,
87
- remove: true
88
- });
89
- }
47
+ throw new Error(`Legend target "${target}" has no complete block for channel "${missing[0]}".`);
90
48
  }
91
- for (const id of new Set(kinds.flatMap(legendGraphicIds))) {
92
- if (next.graphicSpec.objects[id] !== undefined) {
93
- next = next.editGraphics({ target: id, remove: true });
94
- }
95
- }
96
- for (const kind of kinds) {
97
- next = next._withoutMaterializationConfig(["guides", "legend", kind]);
98
- }
99
- return next;
49
+ // Remove departing sibling configs from the immutable layout preflight view.
50
+ const view = kinds.reduce((next, kind) =>
51
+ next._withoutMaterializationConfig(["guides", "legend", kind]), program);
52
+ const resolved = partial === undefined ? undefined
53
+ : resolveCategoricalLegendRevision(view, partial.kind, partial.config, partial.channels);
54
+ const revision = resolved === undefined ? undefined : planLegendBlockTransitions(
55
+ program,
56
+ target,
57
+ [{ ...resolved, kind: resolved.config.kind }]
58
+ )[0];
59
+ return { kinds, revision };
100
60
  }
101
61
 
102
62
  export function removeOwnedColorLegends(program, target) {
@@ -108,17 +68,20 @@ export function removeOwnedColorLegends(program, target) {
108
68
  }
109
69
 
110
70
  export const removeLegend = action(
111
- { op: "removeLegend", description: "Remove every legend block owned by one mark." },
71
+ { op: "removeLegend", description: "Remove selected legend content or every block owned by one mark." },
112
72
  function (args = {}) {
113
73
  validateKeys(args, OPTIONS, "removeLegend");
114
74
  const target = resolveLegendTarget(this, args.target, "removeLegend");
115
75
  const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
116
76
  .filter(([, config]) => config?.target === target)
117
77
  .map(([kind]) => kind);
118
- const kinds = args.channels === undefined
119
- ? targetKinds
120
- : resolveRequestedKinds(this, target, args.channels);
78
+ const { kinds, revision } = args.channels === undefined
79
+ ? { kinds: targetKinds }
80
+ : resolveRequestedRemoval(this, target, args.channels);
121
81
  let next = removeLegendKinds(this, kinds);
82
+ if (revision !== undefined) {
83
+ next = createCategoricalLegendFromConfig(next, revision.config, revision.order);
84
+ }
122
85
  const retainedKinds = Object.keys(next.guideConfigs.legend ?? {});
123
86
  if (args.channels === undefined) {
124
87
  return retainedKinds.length === 0
@@ -126,9 +89,11 @@ export const removeLegend = action(
126
89
  : next.rematerializeLegend();
127
90
  }
128
91
  const remainingTargetKinds = targetKinds.filter(kind => !kinds.includes(kind));
129
- const removedCategorical = kinds.some(kind => ["series", "color"].includes(kind));
92
+ const removedCategorical = kinds.some(kind =>
93
+ ["series", "color", "stroke"].includes(kind)
94
+ );
130
95
  if (
131
- removedCategorical &&
96
+ removedCategorical && revision === undefined &&
132
97
  remainingTargetKinds.includes("size") &&
133
98
  next.guideConfigs.legend.size.inheritAppearance === true
134
99
  ) {
@@ -0,0 +1,140 @@
1
+ import { sampleNumericRange } from "../../../grammar/numeric.js";
2
+ import {
3
+ validateGeneratedItemLimit
4
+ } from "../../../core/validation.js";
5
+
6
+ const DEFAULT_COUNT = 5;
7
+ const MAX_EXACT_VALUES = 100;
8
+
9
+ function validateCount(value, label) {
10
+ if (!Number.isInteger(value) || value < 2) {
11
+ throw new RangeError(`${label} count must be an integer of at least 2.`);
12
+ }
13
+ validateGeneratedItemLimit(value, `${label} count`);
14
+ return value;
15
+ }
16
+
17
+ function normalizeExactValues(value, label) {
18
+ if (!Array.isArray(value)) {
19
+ throw new TypeError(`${label} values must be an array or "auto".`);
20
+ }
21
+ if (value.length === 0 || value.length > MAX_EXACT_VALUES) {
22
+ throw new RangeError(
23
+ `${label} values must contain between 1 and ${MAX_EXACT_VALUES} values.`
24
+ );
25
+ }
26
+ const values = [...value];
27
+ for (let index = 0; index < values.length; index += 1) {
28
+ if (!Number.isFinite(values[index])) {
29
+ throw new TypeError(`${label} values must contain only finite numbers.`);
30
+ }
31
+ if (index > 0 && !(values[index] > values[index - 1])) {
32
+ throw new RangeError(
33
+ `${label} values must be unique and strictly increasing.`
34
+ );
35
+ }
36
+ }
37
+ return Object.freeze(values);
38
+ }
39
+
40
+ export function readLegendSampling(config, defaultCount = DEFAULT_COUNT) {
41
+ if (config?.sampling?.mode === "values") {
42
+ return {
43
+ mode: "values",
44
+ values: config.sampling.values,
45
+ count: config.sampling.count ?? config.count ?? defaultCount
46
+ };
47
+ }
48
+ return {
49
+ mode: "auto",
50
+ count: config?.sampling?.count ?? config?.count ?? defaultCount
51
+ };
52
+ }
53
+
54
+ export function normalizeLegendSampling(
55
+ patch,
56
+ { previous, operation = "create", label = "Legend" } = {}
57
+ ) {
58
+ const prior = readLegendSampling(previous);
59
+ const hasValues = Object.hasOwn(patch, "values");
60
+ const hasCount = Object.hasOwn(patch, "count");
61
+ const requestedValues = patch.values;
62
+
63
+ if (hasValues && requestedValues !== "auto" && hasCount) {
64
+ throw new Error(`${label} cannot specify both count and exact values.`);
65
+ }
66
+ if (requestedValues === "auto" && operation === "create") {
67
+ throw new Error(`${label} creation uses omission for automatic values.`);
68
+ }
69
+ if (!hasValues && !hasCount) {
70
+ return Object.freeze({
71
+ ...prior,
72
+ ...(prior.values === undefined
73
+ ? {}
74
+ : { values: Object.freeze([...prior.values]) })
75
+ });
76
+ }
77
+ if (!hasValues) {
78
+ if (prior.mode === "values") {
79
+ throw new Error(
80
+ `${label} count cannot replace exact values; set values to "auto" first.`
81
+ );
82
+ }
83
+ return Object.freeze({ mode: "auto", count: validateCount(patch.count, label) });
84
+ }
85
+ if (requestedValues === "auto") {
86
+ return Object.freeze({
87
+ mode: "auto",
88
+ count: hasCount ? validateCount(patch.count, label) : validateCount(prior.count, label)
89
+ });
90
+ }
91
+ return Object.freeze({
92
+ mode: "values",
93
+ values: normalizeExactValues(requestedValues, label),
94
+ count: validateCount(prior.count, label)
95
+ });
96
+ }
97
+
98
+ export function validateSamplingAgainstScale(sampling, scale, label = "Legend") {
99
+ if (sampling.mode !== "values") return sampling;
100
+ const domain = scale?.domain;
101
+ if (!Array.isArray(domain) || domain.length < 2 || !domain.every(Number.isFinite)) {
102
+ throw new Error(`${label} exact values require a finite quantitative scale domain.`);
103
+ }
104
+ const lower = Math.min(...domain);
105
+ const upper = Math.max(...domain);
106
+ for (const value of sampling.values) {
107
+ if (scale.type === "log" && value <= 0) {
108
+ throw new RangeError(`${label} exact values must be positive for a log scale.`);
109
+ }
110
+ if (value < lower || value > upper) {
111
+ throw new RangeError(
112
+ `${label} exact value ${value} is outside the scale domain [${lower}, ${upper}].`
113
+ );
114
+ }
115
+ }
116
+ return sampling;
117
+ }
118
+
119
+ export function resolveLegendSampleValues(config, scale, label = "Legend") {
120
+ const sampling = validateSamplingAgainstScale(
121
+ readLegendSampling(config),
122
+ scale,
123
+ label
124
+ );
125
+ return sampling.mode === "values"
126
+ ? [...sampling.values]
127
+ : sampleNumericRange(
128
+ scale.domain[0],
129
+ scale.domain[1],
130
+ sampling.count,
131
+ `${label} domain`
132
+ );
133
+ }
134
+
135
+ export function hasExactLegendSamplingForScale(program, scaleId) {
136
+ return ["size", "opacity", "strokeWidth"].some(kind => {
137
+ const config = program.guideConfigs.legend?.[kind];
138
+ return config?.scale === scaleId && readLegendSampling(config).mode === "values";
139
+ });
140
+ }