ggaction 0.0.13 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +41 -12
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/knowledge/task-packet.schema.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +39 -7
  9. package/src/BasicChartProgram.js +3 -1
  10. package/src/ChartProgram.js +2 -0
  11. package/src/accessibility.js +157 -0
  12. package/src/actions/basic.js +2 -2
  13. package/src/actions/boxPlots/components.js +20 -12
  14. package/src/actions/boxPlots/create.js +4 -5
  15. package/src/actions/boxPlots/edit.js +34 -9
  16. package/src/actions/boxPlots/materialize.js +12 -4
  17. package/src/actions/boxPlots/options.js +48 -16
  18. package/src/actions/canvas/actions.js +3 -3
  19. package/src/actions/canvas/fitting.js +1 -1
  20. package/src/actions/categoryOrder/index.js +21 -12
  21. package/src/actions/charts/area.js +6 -2
  22. package/src/actions/charts/bar.js +3 -2
  23. package/src/actions/charts/beeswarm.js +1 -1
  24. package/src/actions/charts/density.js +7 -3
  25. package/src/actions/charts/ecdf.js +6 -3
  26. package/src/actions/charts/endpoints.js +29 -7
  27. package/src/actions/charts/heatmap.js +3 -2
  28. package/src/actions/charts/histogram.js +5 -4
  29. package/src/actions/charts/horizon.js +4 -2
  30. package/src/actions/charts/interval-regression.js +7 -4
  31. package/src/actions/charts/line.js +4 -2
  32. package/src/actions/charts/parallel.js +9 -3
  33. package/src/actions/charts/pie.js +11 -3
  34. package/src/actions/charts/polar.js +17 -6
  35. package/src/actions/charts/radar.js +9 -3
  36. package/src/actions/charts/radial.js +12 -4
  37. package/src/actions/charts/raincloud.js +2 -2
  38. package/src/actions/charts/rug-strip.js +8 -4
  39. package/src/actions/charts/scatter.js +4 -2
  40. package/src/actions/composition/actions.js +29 -24
  41. package/src/actions/coordinates/actions.js +2 -2
  42. package/src/actions/coordinates/edit.js +76 -0
  43. package/src/actions/data/basicBin2d.js +4 -5
  44. package/src/actions/data/bin.js +3 -3
  45. package/src/actions/data/bin2d.js +63 -193
  46. package/src/actions/data/bin2dMaterialize.js +4 -5
  47. package/src/actions/data/box.js +4 -4
  48. package/src/actions/data/complete.js +26 -0
  49. package/src/actions/data/computed.js +4 -4
  50. package/src/actions/data/create.js +7 -14
  51. package/src/actions/data/density.js +15 -23
  52. package/src/actions/data/derived.js +57 -31
  53. package/src/actions/data/ecdf.js +2 -2
  54. package/src/actions/data/edit.js +551 -0
  55. package/src/actions/data/filter.js +28 -22
  56. package/src/actions/data/fold.js +2 -2
  57. package/src/actions/data/gradientProfile.js +2 -2
  58. package/src/actions/data/horizon.js +2 -2
  59. package/src/actions/data/impute.js +26 -0
  60. package/src/actions/data/index.js +62 -47
  61. package/src/actions/data/interval.js +2 -2
  62. package/src/actions/data/intervalEdit.js +26 -18
  63. package/src/actions/data/normalize.js +27 -0
  64. package/src/actions/data/regression.js +21 -27
  65. package/src/actions/data/revise.js +48 -0
  66. package/src/actions/data/revisionBindings.js +125 -0
  67. package/src/actions/data/shared.js +20 -4
  68. package/src/actions/data/stack.js +2 -2
  69. package/src/actions/data/summary.js +3 -3
  70. package/src/actions/data/timeUnit.js +6 -3
  71. package/src/actions/data/window.js +3 -3
  72. package/src/actions/encodings/angle.js +1 -1
  73. package/src/actions/encodings/appearance.js +4 -4
  74. package/src/actions/encodings/barWidth.js +1 -1
  75. package/src/actions/encodings/channels.js +415 -0
  76. package/src/actions/encodings/color/index.js +3 -2
  77. package/src/actions/encodings/density.js +7 -4
  78. package/src/actions/encodings/histogram.js +4 -3
  79. package/src/actions/encodings/horizon.js +6 -8
  80. package/src/actions/encodings/index.js +4 -0
  81. package/src/actions/encodings/offset.js +22 -15
  82. package/src/actions/encodings/parallel.js +4 -5
  83. package/src/actions/encodings/pathOrder.js +6 -11
  84. package/src/actions/encodings/position/apply.js +53 -7
  85. package/src/actions/encodings/position/index.js +14 -6
  86. package/src/actions/encodings/position/policies/bar.js +9 -0
  87. package/src/actions/encodings/position/resolve.js +24 -7
  88. package/src/actions/encodings/ranged.js +5 -5
  89. package/src/actions/encodings/remove.js +13 -9
  90. package/src/actions/encodings/ruleAppearance.js +2 -24
  91. package/src/actions/encodings/seriesLayout.js +2 -1
  92. package/src/actions/encodings/shared.js +82 -14
  93. package/src/actions/encodings/stroke.js +226 -0
  94. package/src/actions/encodings/strokeDash.js +2 -2
  95. package/src/actions/encodings/text.js +1 -1
  96. package/src/actions/errorBands/create.js +34 -20
  97. package/src/actions/errorBands/edit.js +34 -137
  98. package/src/actions/errorBars/create.js +43 -20
  99. package/src/actions/errorBars/edit.js +29 -125
  100. package/src/actions/facets/actions.js +267 -73
  101. package/src/actions/facets/derive.js +99 -28
  102. package/src/actions/facets/guides.js +4 -5
  103. package/src/actions/facets/replay.js +4 -5
  104. package/src/actions/gradientPlots/components.js +15 -8
  105. package/src/actions/gradientPlots/create.js +4 -5
  106. package/src/actions/gradientPlots/edit.js +18 -9
  107. package/src/actions/gradientPlots/materialize.js +5 -3
  108. package/src/actions/gradientPlots/options.js +10 -2
  109. package/src/actions/gradientPlots/rebind.js +4 -5
  110. package/src/actions/guides/applicability.js +8 -2
  111. package/src/actions/guides/axes/axes.js +23 -4
  112. package/src/actions/guides/axes/axis.js +2 -2
  113. package/src/actions/guides/axes/edit.js +2 -2
  114. package/src/actions/guides/axes/labels.js +67 -21
  115. package/src/actions/guides/axes/lines.js +15 -9
  116. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  117. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  118. package/src/actions/guides/axes/parallel.js +5 -5
  119. package/src/actions/guides/axes/remove.js +4 -4
  120. package/src/actions/guides/axes/tickGroups.js +6 -5
  121. package/src/actions/guides/axes/ticks.js +19 -6
  122. package/src/actions/guides/axes/titles.js +21 -13
  123. package/src/actions/guides/grids/grid.js +18 -29
  124. package/src/actions/guides/grids/resolve.js +7 -2
  125. package/src/actions/guides/guides.js +20 -4
  126. package/src/actions/guides/legends/blocks.js +426 -0
  127. package/src/actions/guides/legends/categorical/actions.js +138 -52
  128. package/src/actions/guides/legends/categorical/components.js +55 -45
  129. package/src/actions/guides/legends/categorical/index.js +37 -33
  130. package/src/actions/guides/legends/categorical/layout.js +98 -16
  131. package/src/actions/guides/legends/categorical/options.js +2 -2
  132. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  133. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  134. package/src/actions/guides/legends/categorical/symbols.js +104 -52
  135. package/src/actions/guides/legends/continuous/common.js +76 -20
  136. package/src/actions/guides/legends/continuous/gradient.js +95 -158
  137. package/src/actions/guides/legends/continuous/index.js +2 -0
  138. package/src/actions/guides/legends/continuous/interval.js +21 -16
  139. package/src/actions/guides/legends/continuous/opacity.js +37 -83
  140. package/src/actions/guides/legends/continuous/stroke.js +225 -0
  141. package/src/actions/guides/legends/creation.js +14 -1
  142. package/src/actions/guides/legends/edit.js +159 -60
  143. package/src/actions/guides/legends/focused.js +5 -5
  144. package/src/actions/guides/legends/index.js +2 -0
  145. package/src/actions/guides/legends/lane.js +59 -24
  146. package/src/actions/guides/legends/lifecycle.js +19 -8
  147. package/src/actions/guides/legends/remove.js +17 -8
  148. package/src/actions/guides/legends/sampling.js +140 -0
  149. package/src/actions/guides/legends/size.js +100 -108
  150. package/src/actions/guides/legends/strokeWidth.js +38 -92
  151. package/src/actions/guides/legends/target.js +112 -1
  152. package/src/actions/guides/legends/transition.js +165 -16
  153. package/src/actions/guides/polar/axes/facade.js +8 -8
  154. package/src/actions/guides/polar/axes/labels.js +29 -10
  155. package/src/actions/guides/polar/axes/lines.js +19 -19
  156. package/src/actions/guides/polar/axes/shared.js +16 -0
  157. package/src/actions/guides/polar/axes/ticks.js +10 -16
  158. package/src/actions/guides/polar/axes/titles.js +6 -6
  159. package/src/actions/guides/polar/grids.js +12 -14
  160. package/src/actions/guides/polar/resolve.js +12 -8
  161. package/src/actions/index.js +16 -3
  162. package/src/actions/intervals/revision.js +126 -0
  163. package/src/actions/marks/arc/actions.js +80 -22
  164. package/src/actions/marks/area/actions.js +38 -13
  165. package/src/actions/marks/area/materialize.js +17 -1
  166. package/src/actions/marks/bar/create.js +12 -3
  167. package/src/actions/marks/bar/edit.js +21 -4
  168. package/src/actions/marks/bar/materialize.js +34 -40
  169. package/src/actions/marks/index.js +10 -1
  170. package/src/actions/marks/line/actions.js +40 -17
  171. package/src/actions/marks/line/materialize.js +29 -10
  172. package/src/actions/marks/point/create.js +9 -3
  173. package/src/actions/marks/point/edit.js +19 -7
  174. package/src/actions/marks/point/jitter.js +6 -8
  175. package/src/actions/marks/point/materialize.js +48 -22
  176. package/src/actions/marks/point/packing.js +6 -8
  177. package/src/actions/marks/rect/actions.js +36 -15
  178. package/src/actions/marks/references.js +602 -33
  179. package/src/actions/marks/remove.js +67 -41
  180. package/src/actions/marks/rule/actions.js +51 -14
  181. package/src/actions/marks/shared.js +1 -1
  182. package/src/actions/marks/text/actions.js +79 -14
  183. package/src/actions/marks/text/index.js +4 -0
  184. package/src/actions/marks/text/layout.js +157 -17
  185. package/src/actions/marks/text/removal.js +173 -0
  186. package/src/actions/marks/text/selection.js +72 -0
  187. package/src/actions/marks/tick/actions.js +25 -8
  188. package/src/actions/primitives/createGraphics.js +5 -4
  189. package/src/actions/primitives/editGraphics.js +31 -4
  190. package/src/actions/primitives/graphicProperties.js +6 -0
  191. package/src/actions/primitives/semantic.js +11 -3
  192. package/src/actions/primitives/semanticAction.js +90 -14
  193. package/src/actions/primitives/semanticValidation/dataset.js +2 -4
  194. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  195. package/src/actions/primitives/semanticValidation/index.js +9 -1
  196. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  197. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  198. package/src/actions/regression/components.js +36 -25
  199. package/src/actions/regression/create.js +13 -6
  200. package/src/actions/regression/edit.js +24 -11
  201. package/src/actions/resources/index.js +7 -0
  202. package/src/actions/resources/remove.js +173 -0
  203. package/src/actions/scales/channels.js +212 -0
  204. package/src/actions/scales/consumers/common.js +1 -1
  205. package/src/actions/scales/consumers/families.js +3 -2
  206. package/src/actions/scales/consumers/index.js +19 -2
  207. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  208. package/src/actions/scales/create.js +55 -23
  209. package/src/actions/scales/definitions.js +94 -40
  210. package/src/actions/scales/edit.js +67 -12
  211. package/src/actions/scales/editPolicy.js +81 -11
  212. package/src/actions/scales/index.js +6 -0
  213. package/src/actions/scales/materialize.js +5 -4
  214. package/src/actions/scales/offset.js +48 -0
  215. package/src/actions/scales/parallel.js +71 -0
  216. package/src/actions/scales/preview.js +111 -10
  217. package/src/actions/scales/quantitativeColor.js +4 -5
  218. package/src/actions/selection/actions.js +179 -66
  219. package/src/actions/selection/index.js +3 -0
  220. package/src/actions/textMetrics/index.js +56 -0
  221. package/src/actions/theme/actions.js +70 -15
  222. package/src/actions/theme/composition.js +238 -0
  223. package/src/actions/theme/index.js +9 -2
  224. package/src/actions/theme/reconcile.js +313 -121
  225. package/src/actions/theme/state.js +160 -0
  226. package/src/actions/titles/actions.js +8 -8
  227. package/src/actions/titles/resolve.js +3 -3
  228. package/src/actions/violinPlots/create.js +6 -3
  229. package/src/actions/violinPlots/edit.js +2 -1
  230. package/src/core/action.js +117 -35
  231. package/src/core/compositionState.js +6 -1
  232. package/src/core/diagnostics.js +16 -0
  233. package/src/core/extensionRegistry.js +27 -0
  234. package/src/core/font.js +18 -0
  235. package/src/core/identifiers.js +5 -4
  236. package/src/core/immutable.js +10 -3
  237. package/src/core/optionRequirements.js +8 -0
  238. package/src/core/programState.js +21 -1
  239. package/src/core/resourceReferences.js +341 -0
  240. package/src/core/textMetricProfile.js +28 -0
  241. package/src/core/textMetrics.js +24 -3
  242. package/src/core/validation.js +22 -10
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/diagnostics.js +1 -0
  245. package/src/grammar/bars/aggregate.js +4 -1
  246. package/src/grammar/bars/geometry.js +44 -0
  247. package/src/grammar/bin.js +54 -9
  248. package/src/grammar/complete.js +279 -0
  249. package/src/grammar/computed.js +265 -64
  250. package/src/grammar/coordinates.js +37 -0
  251. package/src/grammar/density.js +239 -37
  252. package/src/grammar/displayLabels.js +57 -0
  253. package/src/grammar/facets/dependencies.js +133 -1
  254. package/src/grammar/facets/guides.js +3 -0
  255. package/src/grammar/facets/index.js +7 -47
  256. package/src/grammar/facets/scales.js +15 -18
  257. package/src/grammar/impute.js +341 -0
  258. package/src/grammar/markLabelSelection.js +57 -0
  259. package/src/grammar/markLabels.js +18 -1
  260. package/src/grammar/normalize.js +366 -0
  261. package/src/grammar/pathSeries.js +26 -8
  262. package/src/grammar/pointShapes.js +13 -4
  263. package/src/grammar/polar.js +90 -8
  264. package/src/grammar/regression/index.js +1 -0
  265. package/src/grammar/regression/parameters.js +23 -0
  266. package/src/grammar/roundedRect.js +180 -0
  267. package/src/grammar/rules.js +1 -0
  268. package/src/grammar/scales/colorConsumers.js +20 -1
  269. package/src/grammar/scales/continuous.js +2 -2
  270. package/src/grammar/scales/definition.js +27 -4
  271. package/src/grammar/scales/index.js +1 -0
  272. package/src/grammar/scales/ordinal.js +12 -3
  273. package/src/grammar/scales/policies.js +2 -2
  274. package/src/grammar/scales/size.js +456 -0
  275. package/src/grammar/scales/types.js +8 -0
  276. package/src/grammar/schemas/concreteGraphic.js +14 -2
  277. package/src/grammar/schemas/graphic.js +15 -0
  278. package/src/grammar/schemas/graphicBounds.js +190 -41
  279. package/src/grammar/schemas/semanticPath.js +7 -2
  280. package/src/grammar/statisticalReference.js +103 -0
  281. package/src/grammar/strokeStyle.js +77 -0
  282. package/src/grammar/summary.js +78 -16
  283. package/src/grammar/timeUnit.js +216 -5
  284. package/src/grammar/transformTopology.js +4 -0
  285. package/src/grammar/transforms.js +394 -28
  286. package/src/grammar/weightedStatistics.js +374 -0
  287. package/src/grammar/window.js +221 -16
  288. package/src/layout/aspect.js +80 -0
  289. package/src/layout/facets.js +59 -5
  290. package/src/layout/labels.js +351 -7
  291. package/src/layout/legend.js +4 -4
  292. package/src/layout/legendItems.js +6 -6
  293. package/src/layout/text.js +10 -9
  294. package/src/layout/title.js +9 -9
  295. package/src/materialization/bars/aggregate.js +25 -2
  296. package/src/materialization/bars/histogram.js +84 -24
  297. package/src/materialization/bars/ranged.js +19 -3
  298. package/src/materialization/bars/resolve.js +7 -4
  299. package/src/materialization/composition.js +3 -1
  300. package/src/materialization/coordinateBounds.js +204 -0
  301. package/src/materialization/dependencies.js +36 -0
  302. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  303. package/src/materialization/facetGuides/placement.js +9 -3
  304. package/src/materialization/facetGuides/preparation.js +13 -8
  305. package/src/materialization/facetHeaders.js +347 -0
  306. package/src/materialization/facets.js +15 -65
  307. package/src/materialization/guides/layout.js +3 -3
  308. package/src/materialization/guides/resources.js +26 -0
  309. package/src/materialization/legends.js +1 -1
  310. package/src/materialization/marks/index.js +11 -5
  311. package/src/materialization/marks/policies.js +2 -1
  312. package/src/materialization/rect.js +25 -12
  313. package/src/materialization/rectConfig.js +5 -1
  314. package/src/materialization/scaleGuideDependencies.js +10 -0
  315. package/src/materialization/scales/map.js +5 -3
  316. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  317. package/src/materialization/scales/policies/offset.js +50 -8
  318. package/src/materialization/scales/policies/series.js +4 -1
  319. package/src/materialization/scales/resolve.js +22 -6
  320. package/src/materialization/selection/items/arc.js +28 -4
  321. package/src/materialization/selection/items/bar.js +30 -3
  322. package/src/materialization/selection/items/common.js +20 -5
  323. package/src/materialization/selection/items/rect.js +28 -4
  324. package/src/materialization/selection/path.js +1 -1
  325. package/src/materialization/selection/point.js +3 -1
  326. package/src/materialization/text.js +265 -14
  327. package/src/materialization/typography.js +31 -0
  328. package/src/mcp/cli.js +5 -3
  329. package/src/persistence/codec.js +65 -0
  330. package/src/persistence/validation.js +185 -0
  331. package/src/persistence.js +74 -0
  332. package/src/renderers/canvas/circle.js +2 -3
  333. package/src/renderers/canvas/index.js +5 -4
  334. package/src/renderers/canvas/line.js +2 -3
  335. package/src/renderers/canvas/native.js +4 -4
  336. package/src/renderers/canvas/path.js +2 -3
  337. package/src/renderers/canvas/rect.js +2 -3
  338. package/src/renderers/canvas/stroke.js +11 -0
  339. package/src/renderers/nativeBackend.js +10 -0
  340. package/src/renderers/options.js +13 -0
  341. package/src/renderers/pdf.js +25 -54
  342. package/src/renderers/png.js +22 -17
  343. package/src/renderers/svg.js +29 -14
  344. package/src/renderers/text.js +1 -11
  345. package/src/selectors/coordinates.js +3 -1
  346. package/src/selectors/datasets.js +38 -5
  347. package/src/selectors/layers.js +10 -4
  348. package/src/selectors/markOwners.js +46 -0
  349. package/src/selectors/scales.js +6 -4
  350. package/src/theme/defaults.js +103 -1
  351. package/src/version.js +2 -0
  352. package/types/accessibility.d.ts +37 -0
  353. package/types/basic.d.ts +10 -2
  354. package/types/diagnostics.d.ts +15 -0
  355. package/types/index.d.ts +90 -0
  356. package/types/pdf.d.ts +9 -0
  357. package/types/persistence.d.ts +7 -0
  358. package/types/png.d.ts +9 -0
  359. package/types/program.d.ts +944 -118
@@ -22,15 +22,18 @@ import { legendResourcePolicy } from
22
22
  const FAMILY_ORDER = Object.freeze({
23
23
  series: 0,
24
24
  color: 0,
25
+ stroke: 0,
25
26
  gradient: 0,
26
27
  interval: 0,
28
+ strokeGradient: 0,
29
+ strokeInterval: 0,
27
30
  size: 1,
28
31
  opacity: 2,
29
32
  strokeWidth: 3
30
33
  });
31
34
 
32
35
  function categoricalFor(program, target) {
33
- return ["series", "color"]
36
+ return ["series", "color", "stroke"]
34
37
  .map(kind => program.guideConfigs.legend?.[kind])
35
38
  .find(config => config?.target === target);
36
39
  }
@@ -63,6 +66,41 @@ function componentIds(program, kind) {
63
66
  return { titleId, labelId, backgroundId, symbolIds };
64
67
  }
65
68
 
69
+ function legendGraphicBounds(program, id) {
70
+ let bounds = resolveConcreteGraphicBounds(program.graphicSpec, id, program.materializationConfigs.textMetrics);
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
+
66
104
  function textAnchor(program, id) {
67
105
  const graphic = program.graphicSpec.objects[id];
68
106
  const properties = graphic.items?.[0]?.properties ?? graphic.properties;
@@ -79,22 +117,16 @@ function blockDescriptor(program, kind, config) {
79
117
  components.labelId,
80
118
  ...(components.titleId === undefined ? [] : [components.titleId])
81
119
  ];
82
- const bounds = unionConcreteGraphicBounds(program.graphicSpec, foregroundIds);
83
- const symbolAnchorIds = kind === "gradient"
84
- ? ["colorGradientStrips"]
120
+ const bounds = unionLegendBounds(program, foregroundIds);
121
+ const symbolAnchorIds = ["gradient", "strokeGradient"].includes(kind)
122
+ ? [kind === "gradient" ? "colorGradientStrips" : "strokeGradientStrips"]
85
123
  : components.symbolIds;
86
- const symbolAnchor = unionConcreteGraphicBounds(
87
- program.graphicSpec,
88
- symbolAnchorIds
89
- );
90
- const symbolBounds = unionConcreteGraphicBounds(
91
- program.graphicSpec,
92
- components.symbolIds
93
- );
124
+ const symbolAnchor = unionLegendBounds(program, symbolAnchorIds);
125
+ const symbolBounds = unionLegendBounds(program, components.symbolIds);
94
126
  const labels = resolveConcreteGraphicBounds(
95
127
  program.graphicSpec,
96
128
  components.labelId
97
- );
129
+ , program.materializationConfigs.textMetrics);
98
130
  const title = components.titleId === undefined
99
131
  ? undefined
100
132
  : (() => {
@@ -102,7 +134,7 @@ function blockDescriptor(program, kind, config) {
102
134
  const titleBounds = resolveConcreteGraphicBounds(
103
135
  program.graphicSpec,
104
136
  components.titleId
105
- );
137
+ , program.materializationConfigs.textMetrics);
106
138
  return {
107
139
  x: graphic.properties.x,
108
140
  y: graphic.properties.y,
@@ -132,7 +164,7 @@ function blockDescriptor(program, kind, config) {
132
164
  x: textAnchor(program, components.labelId),
133
165
  width: labels.right - labels.left
134
166
  },
135
- occupiedBounds: unionConcreteGraphicBounds(program.graphicSpec, [
167
+ occupiedBounds: unionLegendBounds(program, [
136
168
  ...foregroundIds,
137
169
  ...(components.backgroundId === undefined ? [] : [components.backgroundId])
138
170
  ]),
@@ -142,7 +174,10 @@ function blockDescriptor(program, kind, config) {
142
174
  }
143
175
 
144
176
  function borderFor(kind, config) {
145
- return ["series", "color", "gradient", "opacity", "interval", "strokeWidth", "size"].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,7 +189,7 @@ 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({
@@ -282,9 +317,9 @@ function horizontalGroups(program, groups) {
282
317
  .filter(id => id !== titleId);
283
318
  if (atomic) contentIds.push(...group.blocks.slice(1).flatMap(block =>
284
319
  block.backgroundId === undefined ? [] : [block.backgroundId]));
285
- const content = unionConcreteGraphicBounds(program.graphicSpec, contentIds);
286
- const foreground = unionConcreteGraphicBounds(
287
- program.graphicSpec,
320
+ const content = unionLegendBounds(program, contentIds);
321
+ const foreground = unionLegendBounds(
322
+ program,
288
323
  [...contentIds, ...(titleId === undefined ? [] : [titleId])]
289
324
  );
290
325
  if (content === undefined || foreground === undefined) {
@@ -339,7 +374,7 @@ function applyHorizontalPlan(program, groups, plan) {
339
374
  return next;
340
375
  }
341
376
 
342
- export const rematerializeSideLegendLane = action(
377
+ export const rematerializeSideLegendLane = /* @__PURE__ */ action(
343
378
  {
344
379
  op: "rematerializeSideLegendLane",
345
380
  description: "Align and stack every right or left legend block."
@@ -404,7 +439,7 @@ export const rematerializeSideLegendLane = action(
404
439
  }
405
440
  for (const block of blocks) {
406
441
  if (block.backgroundId === undefined || plan.backgrounds.some(item => item.id === block.backgroundId)) continue;
407
- const bounds = unionConcreteGraphicBounds(next.graphicSpec, block.foregroundIds);
442
+ const bounds = unionLegendBounds(next, block.foregroundIds);
408
443
  const background = resolveLegendBackgroundFromBounds([bounds], block.border, canvas, "Legend");
409
444
  next = editLegendBackground(next, block.backgroundId, background, block.border);
410
445
  }
@@ -422,7 +457,7 @@ export const rematerializeSideLegendLane = action(
422
457
  }
423
458
  );
424
459
 
425
- export const rematerializeHorizontalLegendLane = action(
460
+ export const rematerializeHorizontalLegendLane = /* @__PURE__ */ action(
426
461
  {
427
462
  op: "rematerializeHorizontalLegendLane",
428
463
  description: "Pack and align every top or bottom legend block."
@@ -444,7 +479,7 @@ export const rematerializeHorizontalLegendLane = action(
444
479
  if (entries.length === 1) {
445
480
  const [kind, config] = entries[0];
446
481
  const ids = existingIds(next, kind);
447
- const bounds = unionConcreteGraphicBounds(next.graphicSpec, ids);
482
+ const bounds = unionConcreteGraphicBounds(next.graphicSpec, ids, next.materializationConfigs.textMetrics);
448
483
  const { dx, dy } = resolveSingleHorizontalLegendPlacement({
449
484
  plot, canvas, config, bounds
450
485
  });
@@ -49,8 +49,13 @@ export function resolveCategoricalLegendRevision(program, kind, previous, channe
49
49
  const config = {
50
50
  ...previous,
51
51
  ...definition,
52
- symbol: normalizeRecipe(resolveLegendSymbol(program, layer, channels,
53
- previous.inferredSymbol ? undefined : previous.symbol), definition.kind)
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 })
54
59
  };
55
60
  if (validateLayout) resolveLayout(program, config);
56
61
  return { kind, config, order };
@@ -76,11 +81,11 @@ export function createCategoricalLegendFromConfig(program, config, order) {
76
81
  } else {
77
82
  next = next
78
83
  .editSemantic({
79
- property: "guide.legend.color.scale",
84
+ property: `guide.legend.${kind}.scale`,
80
85
  value: config.scales[0]
81
86
  })
82
87
  .editSemantic({
83
- property: "guide.legend.color.title",
88
+ property: `guide.legend.${kind}.title`,
84
89
  value: config.title
85
90
  });
86
91
  }
@@ -91,9 +96,15 @@ export function createCategoricalLegendFromConfig(program, config, order) {
91
96
  });
92
97
  }
93
98
  next = next._withLegendConfig(kind, config);
94
- if (config.border !== false) next = next.createLegendBackground();
95
- next = next.createLegendSymbols().createLegendLabels();
96
- return config.titleVisible === false ? next : next.createLegendTitle();
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 });
97
108
  }
98
109
 
99
110
  // Categorical content precedes its sampled companions in both creation and revision.
@@ -125,7 +136,7 @@ export function reconcileCategoricalSymbols(program, previous, config) {
125
136
  for (const layer of config.symbol.layers) {
126
137
  if (next.graphicSpec.objects[symbolGraphic(config, layer.type)] === undefined) {
127
138
  const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[layer.type];
128
- next = next[`createLegendSymbol${suffix}`]();
139
+ next = next[`createLegendSymbol${suffix}`]({ kind: config.kind });
129
140
  }
130
141
  }
131
142
  return next;
@@ -1,22 +1,25 @@
1
- import { action } from "../../../core/action.js";
2
- import { validateKeys } from "../../../core/validation.js";
1
+ import { closedAction } from "../../../core/action.js";
2
+
3
3
  import {
4
4
  removeLegendKinds,
5
5
  resolveCategoricalLegendRevision,
6
6
  createCategoricalLegendFromConfig
7
7
  } from "./lifecycle.js";
8
8
  import { resolveLegendTarget, validateLegendChannels } from "./target.js";
9
+ import { planLegendBlockTransitions } from "./transition.js";
9
10
 
10
11
  export { removeLegendKinds } from "./lifecycle.js";
11
12
 
12
13
  const OPTIONS = Object.freeze(["target", "channels"]);
13
14
 
14
15
  function legendKindChannels(kind, config) {
15
- if (["series", "color"].includes(kind)) return config.channels;
16
+ if (["series", "color", "stroke"].includes(kind)) return config.channels;
16
17
  return {
17
18
  size: ["size"],
18
19
  gradient: ["color"],
19
20
  interval: ["color"],
21
+ strokeGradient: ["stroke"],
22
+ strokeInterval: ["stroke"],
20
23
  opacity: ["opacity"],
21
24
  strokeWidth: ["strokeWidth"]
22
25
  }[kind];
@@ -46,8 +49,13 @@ function resolveRequestedRemoval(program, target, channels) {
46
49
  // Remove departing sibling configs from the immutable layout preflight view.
47
50
  const view = kinds.reduce((next, kind) =>
48
51
  next._withoutMaterializationConfig(["guides", "legend", kind]), program);
49
- const revision = partial === undefined ? undefined
52
+ const resolved = partial === undefined ? undefined
50
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];
51
59
  return { kinds, revision };
52
60
  }
53
61
 
@@ -59,10 +67,9 @@ export function removeOwnedColorLegends(program, target) {
59
67
  return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
60
68
  }
61
69
 
62
- export const removeLegend = action(
63
- { op: "removeLegend", description: "Remove selected legend content or every block owned by one mark." },
70
+ export const removeLegend = /* @__PURE__ */ closedAction(
71
+ { op: "removeLegend", description: "Remove selected legend content or every block owned by one mark." }, OPTIONS,
64
72
  function (args = {}) {
65
- validateKeys(args, OPTIONS, "removeLegend");
66
73
  const target = resolveLegendTarget(this, args.target, "removeLegend");
67
74
  const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
68
75
  .filter(([, config]) => config?.target === target)
@@ -81,7 +88,9 @@ export const removeLegend = action(
81
88
  : next.rematerializeLegend();
82
89
  }
83
90
  const remainingTargetKinds = targetKinds.filter(kind => !kinds.includes(kind));
84
- const removedCategorical = kinds.some(kind => ["series", "color"].includes(kind));
91
+ const removedCategorical = kinds.some(kind =>
92
+ ["series", "color", "stroke"].includes(kind)
93
+ );
85
94
  if (
86
95
  removedCategorical && revision === undefined &&
87
96
  remainingTargetKinds.includes("size") &&
@@ -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
+ }