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
@@ -4,6 +4,7 @@ import { registerSizeLegendActions } from "./size.js";
4
4
  import { registerStrokeWidthLegendActions } from "./strokeWidth.js";
5
5
  import { registerFocusedLegendActions } from "./focused.js";
6
6
  import { removeLegend } from "./remove.js";
7
+ import { editLegendBlock } from "./blocks.js";
7
8
 
8
9
  export function registerLegendActions(ProgramClass) {
9
10
  registerCategoricalLegendActions(ProgramClass);
@@ -11,5 +12,6 @@ export function registerLegendActions(ProgramClass) {
11
12
  registerSizeLegendActions(ProgramClass);
12
13
  registerStrokeWidthLegendActions(ProgramClass);
13
14
  registerFocusedLegendActions(ProgramClass);
15
+ ProgramClass.prototype.editLegendBlock = editLegendBlock;
14
16
  ProgramClass.prototype.removeLegend = removeLegend;
15
17
  }
@@ -1,3 +1,4 @@
1
+ import { editLegendBackground, resolveLegendBackgroundFromBounds } from "./continuous/common.js";
1
2
  import { action } from "../../../core/action.js";
2
3
  import { noOptions } from "../../../core/validation.js";
3
4
  import {
@@ -8,6 +9,9 @@ import { finiteMidpoint } from "../../../grammar/numeric.js";
8
9
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
9
10
  import {
10
11
  resolveHorizontalLegendLane,
12
+ resolveHorizontalLegendGroup,
13
+ resolveSingleHorizontalLegendPlacement,
14
+ isHorizontalEdgeLegend,
11
15
  resolveSideLegendLane
12
16
  } from "../../../layout/legendLane.js";
13
17
  import { findCanvasGraphic } from
@@ -18,31 +22,31 @@ import { legendResourcePolicy } from
18
22
  const FAMILY_ORDER = Object.freeze({
19
23
  series: 0,
20
24
  color: 0,
25
+ stroke: 0,
21
26
  gradient: 0,
22
27
  interval: 0,
28
+ strokeGradient: 0,
29
+ strokeInterval: 0,
23
30
  size: 1,
24
31
  opacity: 2,
25
32
  strokeWidth: 3
26
33
  });
27
34
 
28
35
  function categoricalFor(program, target) {
29
- return ["series", "color"]
36
+ return ["series", "color", "stroke"]
30
37
  .map(kind => program.guideConfigs.legend?.[kind])
31
38
  .find(config => config?.target === target);
32
39
  }
33
40
 
34
41
  function legendPosition(program, kind, config) {
35
42
  if (kind === "size") {
36
- return categoricalFor(program, config.target)?.position ?? "right";
43
+ return categoricalFor(program, config.target)?.position ?? config.position;
37
44
  }
38
- if (kind === "strokeWidth" || kind === "interval") return "right";
39
45
  return config.position;
40
46
  }
41
47
 
42
- function requestedOffset(kind, config) {
43
- return ["size", "strokeWidth"].includes(kind)
44
- ? 30
45
- : config.offset;
48
+ function requestedOffset(config) {
49
+ return config.offset;
46
50
  }
47
51
 
48
52
  function existingIds(program, kind) {
@@ -62,6 +66,41 @@ function componentIds(program, kind) {
62
66
  return { titleId, labelId, backgroundId, symbolIds };
63
67
  }
64
68
 
69
+ function legendGraphicBounds(program, id) {
70
+ let bounds = resolveConcreteGraphicBounds(program.graphicSpec, id);
71
+ const graphic = program.graphicSpec.objects[id];
72
+ if (!id.includes("LegendSymbol") || graphic?.type !== "line") return bounds;
73
+ const properties = graphic.items?.map(item => item.properties) ??
74
+ [graphic.properties];
75
+ for (const item of properties) {
76
+ if (
77
+ Object.hasOwn(item, "lineCap") ||
78
+ typeof item.stroke !== "string" ||
79
+ !(item.strokeWidth > 0)
80
+ ) continue;
81
+ const extent = item.strokeWidth / 2;
82
+ bounds = {
83
+ left: Math.min(bounds.left, Math.min(item.x1, item.x2) - extent),
84
+ right: Math.max(bounds.right, Math.max(item.x1, item.x2) + extent),
85
+ top: Math.min(bounds.top, Math.min(item.y1, item.y2) - extent),
86
+ bottom: Math.max(bounds.bottom, Math.max(item.y1, item.y2) + extent)
87
+ };
88
+ }
89
+ return bounds;
90
+ }
91
+
92
+ function unionLegendBounds(program, ids) {
93
+ return ids.map(id => legendGraphicBounds(program, id))
94
+ .reduce((result, bounds) => result === undefined
95
+ ? bounds
96
+ : {
97
+ left: Math.min(result.left, bounds.left),
98
+ right: Math.max(result.right, bounds.right),
99
+ top: Math.min(result.top, bounds.top),
100
+ bottom: Math.max(result.bottom, bounds.bottom)
101
+ }, undefined);
102
+ }
103
+
65
104
  function textAnchor(program, id) {
66
105
  const graphic = program.graphicSpec.objects[id];
67
106
  const properties = graphic.items?.[0]?.properties ?? graphic.properties;
@@ -78,18 +117,12 @@ function blockDescriptor(program, kind, config) {
78
117
  components.labelId,
79
118
  ...(components.titleId === undefined ? [] : [components.titleId])
80
119
  ];
81
- const bounds = unionConcreteGraphicBounds(program.graphicSpec, foregroundIds);
82
- const symbolAnchorIds = kind === "gradient"
83
- ? ["colorGradientStrips"]
120
+ const bounds = unionLegendBounds(program, foregroundIds);
121
+ const symbolAnchorIds = ["gradient", "strokeGradient"].includes(kind)
122
+ ? [kind === "gradient" ? "colorGradientStrips" : "strokeGradientStrips"]
84
123
  : components.symbolIds;
85
- const symbolAnchor = unionConcreteGraphicBounds(
86
- program.graphicSpec,
87
- symbolAnchorIds
88
- );
89
- const symbolBounds = unionConcreteGraphicBounds(
90
- program.graphicSpec,
91
- components.symbolIds
92
- );
124
+ const symbolAnchor = unionLegendBounds(program, symbolAnchorIds);
125
+ const symbolBounds = unionLegendBounds(program, components.symbolIds);
93
126
  const labels = resolveConcreteGraphicBounds(
94
127
  program.graphicSpec,
95
128
  components.labelId
@@ -117,7 +150,8 @@ function blockDescriptor(program, kind, config) {
117
150
  id: kind,
118
151
  kind,
119
152
  target: config.target,
120
- offset: requestedOffset(kind, config),
153
+ border: borderFor(kind, config),
154
+ offset: requestedOffset(config),
121
155
  bounds,
122
156
  title,
123
157
  symbol: {
@@ -130,19 +164,20 @@ function blockDescriptor(program, kind, config) {
130
164
  x: textAnchor(program, components.labelId),
131
165
  width: labels.right - labels.left
132
166
  },
133
- occupiedBounds: components.backgroundId === undefined
134
- ? bounds
135
- : resolveConcreteGraphicBounds(
136
- program.graphicSpec,
137
- components.backgroundId
138
- ),
167
+ occupiedBounds: unionLegendBounds(program, [
168
+ ...foregroundIds,
169
+ ...(components.backgroundId === undefined ? [] : [components.backgroundId])
170
+ ]),
139
171
  ...components,
140
172
  foregroundIds
141
173
  };
142
174
  }
143
175
 
144
176
  function borderFor(kind, config) {
145
- return ["series", "color", "gradient", "opacity"].includes(kind)
177
+ return [
178
+ "series", "color", "stroke", "gradient", "strokeGradient", "opacity",
179
+ "interval", "strokeInterval", "strokeWidth", "size"
180
+ ].includes(kind)
146
181
  ? config.border
147
182
  : false;
148
183
  }
@@ -154,12 +189,13 @@ function groupBlocks(blocks, configs) {
154
189
  const config = configs[block.kind];
155
190
  const next = blocks[index + 1];
156
191
  if (
157
- ["series", "color"].includes(block.kind) &&
192
+ ["series", "color", "stroke"].includes(block.kind) &&
158
193
  next?.kind === "size" && next.target === block.target
159
194
  ) {
160
195
  groups.push({
161
196
  id: `${block.kind}+size`,
162
- blocks: [block, next],
197
+ blocks: [block, next.backgroundId === undefined ? next : { ...next,
198
+ bounds: next.occupiedBounds, inset: next.border.padding + next.border.lineWidth / 2 }],
163
199
  border: borderFor(block.kind, config),
164
200
  backgroundId: block.backgroundId
165
201
  });
@@ -193,13 +229,15 @@ export function hasMultiSideLegendLane(program) {
193
229
  return ["right", "left"].some(side => edgeKinds(program, side).length > 1);
194
230
  }
195
231
 
196
- export function hasMultiHorizontalLegendLane(program) {
197
- return ["top", "bottom"].some(edge => edgeKinds(program, edge).length > 1);
232
+ export function hasHorizontalLegendLane(program) {
233
+ return ["top", "bottom"].some(edge => edgeKinds(program, edge).some(
234
+ ([, config]) => isHorizontalEdgeLegend({ ...config, position: edge })
235
+ ));
198
236
  }
199
237
 
200
- export function hasMultiLegendLane(program) {
238
+ export function hasLegendLane(program) {
201
239
  return hasMultiSideLegendLane(program) ||
202
- hasMultiHorizontalLegendLane(program);
240
+ hasHorizontalLegendLane(program);
203
241
  }
204
242
 
205
243
  function translateCommands(commands, dx, dy) {
@@ -269,35 +307,20 @@ function translateGraphic(program, id, dx, dy) {
269
307
  return next;
270
308
  }
271
309
 
272
- function yAxisBounds(program) {
273
- const ids = ["yAxisLine", "yAxisTicks", "yAxisLabels", "yAxisTitle"]
274
- .filter(id => program.graphicSpec.objects[id] !== undefined);
275
- return ids.length === 0
276
- ? undefined
277
- : unionConcreteGraphicBounds(program.graphicSpec, ids);
278
- }
279
-
280
- function horizontalCollisionBounds(program, edge) {
281
- const ids = edge === "top"
282
- ? ["chartTitle", "chartSubtitle"]
283
- : ["xAxisLine", "xAxisTicks", "xAxisLabels", "xAxisTitle"];
284
- return ids
285
- .filter(id => program.graphicSpec.objects[id] !== undefined)
286
- .map(id => resolveConcreteGraphicBounds(program.graphicSpec, id))
287
- .filter(bounds => bounds !== undefined);
288
- }
289
-
290
310
  function horizontalGroups(program, groups) {
291
311
  return groups.map(group => {
292
312
  const representative = group.blocks[0];
293
313
  const config = program.guideConfigs.legend?.[representative.kind];
294
- const titleId = representative.titleId;
314
+ const atomic = group.blocks.length > 1;
315
+ const titleId = atomic ? undefined : representative.titleId;
295
316
  const contentIds = group.blocks.flatMap(block => block.foregroundIds)
296
317
  .filter(id => id !== titleId);
297
- const content = unionConcreteGraphicBounds(program.graphicSpec, contentIds);
298
- const foreground = unionConcreteGraphicBounds(
299
- program.graphicSpec,
300
- group.blocks.flatMap(block => block.foregroundIds)
318
+ if (atomic) contentIds.push(...group.blocks.slice(1).flatMap(block =>
319
+ block.backgroundId === undefined ? [] : [block.backgroundId]));
320
+ const content = unionLegendBounds(program, contentIds);
321
+ const foreground = unionLegendBounds(
322
+ program,
323
+ [...contentIds, ...(titleId === undefined ? [] : [titleId])]
301
324
  );
302
325
  if (content === undefined || foreground === undefined) {
303
326
  throw new Error(`Legend lane could not measure ${group.id} content.`);
@@ -308,11 +331,12 @@ function horizontalGroups(program, groups) {
308
331
  : border.padding + border.lineWidth / 2;
309
332
  return {
310
333
  id: group.id,
334
+ atomic,
311
335
  titleId,
312
336
  contentIds,
313
- title: representative.title,
337
+ title: atomic ? undefined : representative.title,
314
338
  inline: config?.titlePosition === "left",
315
- element: representative.symbol.bounds,
339
+ element: atomic ? foreground : representative.symbol.bounds,
316
340
  content,
317
341
  horizontal: { left: foreground.left, right: foreground.right,
318
342
  top: foreground.top, bottom: foreground.bottom },
@@ -323,6 +347,33 @@ function horizontalGroups(program, groups) {
323
347
  });
324
348
  }
325
349
 
350
+ function applyHorizontalPlan(program, groups, plan) {
351
+ let next = program;
352
+ const byId = new Map(groups.map(group => [group.id, group]));
353
+ for (const placement of plan.placements) {
354
+ const group = byId.get(placement.id);
355
+ if (group.titleId !== undefined) {
356
+ next = translateGraphic(next, group.titleId, placement.dx, placement.titleDy);
357
+ }
358
+ for (const id of group.contentIds) {
359
+ next = translateGraphic(next, id, placement.dx, placement.contentDy);
360
+ }
361
+ if (placement.background !== undefined) {
362
+ for (const property of ["x", "y", "width", "height"]) {
363
+ next = next.editGraphics({ target: placement.background.id, property,
364
+ value: placement.background[property] });
365
+ }
366
+ }
367
+ }
368
+ if (plan.background !== undefined) {
369
+ for (const property of ["x", "y", "width", "height"]) {
370
+ next = next.editGraphics({ target: plan.background.id, property,
371
+ value: plan.background[property] });
372
+ }
373
+ }
374
+ return next;
375
+ }
376
+
326
377
  export const rematerializeSideLegendLane = action(
327
378
  {
328
379
  op: "rematerializeSideLegendLane",
@@ -346,8 +397,7 @@ export const rematerializeSideLegendLane = action(
346
397
  side,
347
398
  plot,
348
399
  canvas,
349
- groups: groupBlocks(blocks, configs),
350
- axisBounds: yAxisBounds(this)
400
+ groups: groupBlocks(blocks, configs)
351
401
  });
352
402
  return [{ plan, blocks }];
353
403
  });
@@ -387,6 +437,12 @@ export const rematerializeSideLegendLane = action(
387
437
  value: "left"
388
438
  });
389
439
  }
440
+ for (const block of blocks) {
441
+ if (block.backgroundId === undefined || plan.backgrounds.some(item => item.id === block.backgroundId)) continue;
442
+ const bounds = unionLegendBounds(next, block.foregroundIds);
443
+ const background = resolveLegendBackgroundFromBounds([bounds], block.border, canvas, "Legend");
444
+ next = editLegendBackground(next, block.backgroundId, background, block.border);
445
+ }
390
446
  for (const background of plan.backgrounds) {
391
447
  for (const property of ["x", "y", "width", "height"]) {
392
448
  next = next.editGraphics({
@@ -414,55 +470,46 @@ export const rematerializeHorizontalLegendLane = action(
414
470
  throw new Error("Legend lane requires resolved Canvas and plot bounds.");
415
471
  }
416
472
  const configs = this.guideConfigs.legend ?? {};
417
- const plans = ["top", "bottom"].flatMap(edge => {
418
- const entries = edgeKinds(this, edge);
419
- if (entries.length < 2) return [];
420
- const blocks = entries.map(([kind, config]) =>
421
- blockDescriptor(this, kind, config)
473
+ let next = this;
474
+ for (const edge of ["top", "bottom"]) {
475
+ const entries = edgeKinds(next, edge).filter(
476
+ ([, config]) => isHorizontalEdgeLegend({ ...config, position: edge })
422
477
  );
423
- const groups = groupBlocks(blocks, configs);
424
- if (groups.length < 2) return [];
425
- const horizontal = horizontalGroups(this, groups);
478
+ if (entries.length === 0) continue;
479
+ if (entries.length === 1) {
480
+ const [kind, config] = entries[0];
481
+ const ids = existingIds(next, kind);
482
+ const bounds = unionConcreteGraphicBounds(next.graphicSpec, ids);
483
+ const { dx, dy } = resolveSingleHorizontalLegendPlacement({
484
+ plot, canvas, config, bounds
485
+ });
486
+ for (const id of ids) next = translateGraphic(next, id, dx, dy);
487
+ continue;
488
+ }
489
+ let groups = groupBlocks(entries.map(([kind, config]) =>
490
+ blockDescriptor(next, kind, config)), configs);
491
+ for (const group of groups.filter(group => group.blocks.length > 1)) {
492
+ const config = configs[group.blocks[0].kind];
493
+ const children = horizontalGroups(next, group.blocks.map((block, index) => ({
494
+ id: block.id, blocks: [block], border: index === 0 ? false : block.border,
495
+ backgroundId: index === 0 ? undefined : block.backgroundId
496
+ }))).map(child => ({ ...child, element: child.content }));
497
+ const plan = resolveHorizontalLegendGroup({ edge, plot, canvas, groups: children,
498
+ align: config.align, offset: config.offset, border: group.border,
499
+ backgroundId: group.backgroundId });
500
+ next = applyHorizontalPlan(next, children, plan);
501
+ }
502
+ if (groups.length < 2) continue;
503
+ groups = groupBlocks(entries.map(([kind, config]) =>
504
+ blockDescriptor(next, kind, config)), configs);
505
+ const horizontal = horizontalGroups(next, groups);
426
506
  const plan = resolveHorizontalLegendLane({
427
507
  edge,
428
508
  plot,
429
509
  canvas,
430
- groups: horizontal,
431
- collisionBounds: horizontalCollisionBounds(this, edge)
510
+ groups: horizontal
432
511
  });
433
- return [{ plan, groups: horizontal }];
434
- });
435
- let next = this;
436
- for (const { plan, groups } of plans) {
437
- const byId = new Map(groups.map(group => [group.id, group]));
438
- for (const placement of plan.placements) {
439
- const group = byId.get(placement.id);
440
- if (group.titleId !== undefined) {
441
- next = translateGraphic(
442
- next,
443
- group.titleId,
444
- placement.dx,
445
- placement.titleDy
446
- );
447
- }
448
- for (const id of group.contentIds) {
449
- next = translateGraphic(
450
- next,
451
- id,
452
- placement.dx,
453
- placement.contentDy
454
- );
455
- }
456
- if (placement.background !== undefined) {
457
- for (const property of ["x", "y", "width", "height"]) {
458
- next = next.editGraphics({
459
- target: placement.background.id,
460
- property,
461
- value: placement.background[property]
462
- });
463
- }
464
- }
465
- }
512
+ next = applyHorizontalPlan(next, horizontal, plan);
466
513
  }
467
514
  return next;
468
515
  }
@@ -0,0 +1,143 @@
1
+ import {
2
+ legendGraphicIds, legendResourcePolicy, legendResourcePolicies
3
+ } from "../../../materialization/guides/resources.js";
4
+ import { findLayer } from "../../../selectors/layers.js";
5
+ import { resolveDefinition } from "./categorical/resolve.js";
6
+ import { normalizeRecipe, resolveLegendSymbol } from "./categorical/recipes.js";
7
+ import { resolveLayout, symbolGraphic } from "./categorical/layout.js";
8
+ import { resolveLegendGraphicPlacement } from "../../../materialization/graphicHierarchy.js";
9
+
10
+ export function removeLegendKinds(program, kinds) {
11
+ const removed = new Set(kinds);
12
+ const retainedSemanticKinds = new Set(
13
+ Object.entries(program.guideConfigs.legend ?? {})
14
+ .filter(([kind]) => !removed.has(kind))
15
+ .map(([kind]) => legendResourcePolicy(kind).semanticKind)
16
+ );
17
+ const semanticKinds = new Set(
18
+ kinds.map(kind => legendResourcePolicy(kind).semanticKind)
19
+ );
20
+ let next = program;
21
+ for (const kind of semanticKinds) {
22
+ if (
23
+ !retainedSemanticKinds.has(kind) &&
24
+ next.semanticSpec.guides.legend?.[kind] !== undefined
25
+ ) {
26
+ next = next.editSemantic({
27
+ property: `guide.legend.${kind}`,
28
+ remove: true
29
+ });
30
+ }
31
+ }
32
+ for (const id of new Set(kinds.flatMap(legendGraphicIds))) {
33
+ if (next.graphicSpec.objects[id] !== undefined) {
34
+ next = next.editGraphics({ target: id, remove: true });
35
+ }
36
+ }
37
+ for (const kind of kinds) {
38
+ next = next._withoutMaterializationConfig(["guides", "legend", kind]);
39
+ }
40
+ return next;
41
+ }
42
+
43
+ export function resolveCategoricalLegendRevision(program, kind, previous, channels, {
44
+ order = program.semanticSpec.guides.legend?.[kind]?.order, validateLayout = true
45
+ } = {}) {
46
+ const layer = findLayer(program, previous.target);
47
+ const definition = resolveDefinition(program, layer, channels,
48
+ previous.inferredTitle ? undefined : previous.title, order);
49
+ const config = {
50
+ ...previous,
51
+ ...definition,
52
+ symbol: normalizeRecipe(resolveLegendSymbol(
53
+ program,
54
+ layer,
55
+ channels,
56
+ previous.inferredSymbol ? undefined : previous.symbol,
57
+ definition.kind
58
+ ), definition.kind, { internal: previous.inferredSymbol })
59
+ };
60
+ if (validateLayout) resolveLayout(program, config);
61
+ return { kind, config, order };
62
+ }
63
+
64
+ export function createCategoricalLegendFromConfig(program, config, order) {
65
+ const { kind } = config;
66
+ let next = program;
67
+ if (kind === "series") {
68
+ next = next
69
+ .editSemantic({
70
+ property: "guide.legend.series.channels",
71
+ value: config.channels
72
+ })
73
+ .editSemantic({
74
+ property: "guide.legend.series.scales",
75
+ value: config.scales
76
+ })
77
+ .editSemantic({
78
+ property: "guide.legend.series.title",
79
+ value: config.title
80
+ });
81
+ } else {
82
+ next = next
83
+ .editSemantic({
84
+ property: `guide.legend.${kind}.scale`,
85
+ value: config.scales[0]
86
+ })
87
+ .editSemantic({
88
+ property: `guide.legend.${kind}.title`,
89
+ value: config.title
90
+ });
91
+ }
92
+ if (order !== undefined && order !== "scale") {
93
+ next = next.editSemantic({
94
+ property: `guide.legend.${kind}.order`,
95
+ value: order
96
+ });
97
+ }
98
+ next = next._withLegendConfig(kind, config);
99
+ if (config.border !== false) {
100
+ next = next.createLegendBackground({ kind });
101
+ }
102
+ next = next
103
+ .createLegendSymbols({ kind })
104
+ .createLegendLabels({ kind });
105
+ return config.titleVisible === false
106
+ ? next
107
+ : next.createLegendTitle({ kind });
108
+ }
109
+
110
+ // Categorical content precedes its sampled companions in both creation and revision.
111
+ export function resolveCategoricalLegendPlacement(program) {
112
+ const companions = new Set(legendResourcePolicies()
113
+ .filter(policy => policy.family !== "categorical" && program.guideConfigs.legend?.[policy.kind] !== undefined)
114
+ .flatMap(policy => legendGraphicIds(policy.kind)));
115
+ const before = program.graphicSpec.objects.canvas?.children?.find(id => companions.has(id));
116
+ return resolveLegendGraphicPlacement(program, before === undefined ? {} : { before });
117
+ }
118
+
119
+ export function categoricalSymbolIds(config) {
120
+ return new Set(config.symbol.layers.map(layer => symbolGraphic(config, layer.type)));
121
+ }
122
+
123
+ export function reconcileCategoricalSymbols(program, previous, config) {
124
+ const oldSymbols = categoricalSymbolIds(previous);
125
+ const newSymbols = categoricalSymbolIds(config);
126
+ const sameOrder = oldSymbols.size === newSymbols.size &&
127
+ [...oldSymbols].every((id, index) => id === [...newSymbols][index]);
128
+ let next = program;
129
+ if (!sameOrder) {
130
+ for (const id of oldSymbols) {
131
+ if (next.graphicSpec.objects[id] !== undefined) {
132
+ next = next.editGraphics({ target: id, remove: true });
133
+ }
134
+ }
135
+ }
136
+ for (const layer of config.symbol.layers) {
137
+ if (next.graphicSpec.objects[symbolGraphic(config, layer.type)] === undefined) {
138
+ const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[layer.type];
139
+ next = next[`createLegendSymbol${suffix}`]({ kind: config.kind });
140
+ }
141
+ }
142
+ return next;
143
+ }