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
@@ -1,4 +1,4 @@
1
- import { action } from "../../../../core/action.js";
1
+ import { action, closedAction } from "../../../../core/action.js";
2
2
  import { isPlainObject } from "../../../../core/immutable.js";
3
3
  import { validateKeys } from "../../../../core/validation.js";
4
4
  import { mapScaleConsumerValues } from "../../../../materialization/scales/map.js";
@@ -7,6 +7,7 @@ import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
7
7
  import {
8
8
  assertLegendBoundsInsideCanvas,
9
9
  editLegendBackground,
10
+ editGraphicProperties,
10
11
  formatContinuousValues,
11
12
  normalizeContinuousLegend,
12
13
  requireResolvedLegendScale,
@@ -20,24 +21,26 @@ import {
20
21
  } from "./common.js";
21
22
  import { resolveLegendGraphicPlacement } from
22
23
  "../../../../materialization/graphicHierarchy.js";
24
+ import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
23
25
 
24
26
  export const DEFAULT_GRADIENT_SIZE = Object.freeze({ length: 120, thickness: 12 });
25
27
 
26
28
  const GRADIENT_OPTIONS = Object.freeze(["length", "thickness"]);
27
29
 
28
- function resolveGradientLayout(program, config, scale) {
30
+ export function resolveGradientLayout(program, config, scale, label = "Gradient legend") {
29
31
  const { plot, canvas } = resolveContinuousBounds(program);
30
32
  const vertical = ["right", "left"].includes(config.position);
31
33
  const length = config.gradient.length;
32
34
  const thickness = config.gradient.thickness;
35
+ const titleGap = config.blockGap ?? 12;
33
36
  let x;
34
37
  let y;
35
38
  if (config.position === "right") {
36
39
  x = plot.x + plot.width + config.offset;
37
- y = plot.y + 46;
40
+ y = plot.y + 46 + titleGap - 12;
38
41
  } else if (config.position === "left") {
39
42
  x = plot.x - config.offset - thickness;
40
- y = plot.y + 46;
43
+ y = plot.y + 46 + titleGap - 12;
41
44
  } else {
42
45
  x = config.align === "left" ? plot.x
43
46
  : config.align === "right" ? plot.x + plot.width - length
@@ -46,13 +49,13 @@ function resolveGradientLayout(program, config, scale) {
46
49
  ? plot.y - config.offset - thickness - config.labels.offset -
47
50
  config.labels.fontSize
48
51
  : plot.y + plot.height + config.offset +
49
- (config.titleVisible === false ? 0 : config.titleStyle.fontSize + 12);
52
+ (config.titleVisible === false ? 0 : config.titleStyle.fontSize + titleGap);
50
53
  }
51
54
  const title = vertical
52
55
  ? { x, y: plot.y + 20, align: "left" }
53
56
  : {
54
57
  x: x + length / 2,
55
- y: y - 12 - config.titleStyle.fontSize / 2,
58
+ y: y - titleGap - config.titleStyle.fontSize / 2,
56
59
  align: "center"
57
60
  };
58
61
  const values = [...sampleContinuousValues(scale.domain, config.count)];
@@ -109,25 +112,25 @@ function resolveGradientLayout(program, config, scale) {
109
112
  bottom: Math.max(tick.y1, tick.y2) + 0.5
110
113
  }));
111
114
  const labelBounds = labels.map((label, index) =>
112
- resolveLegendTextBounds(label, texts[index], config.labels)
115
+ resolveLegendTextBounds(label, texts[index], config.labels, program.materializationConfigs.textMetrics)
113
116
  );
114
117
  const titleBounds = config.titleVisible === false ? undefined : resolveLegendTextBounds(
115
118
  title,
116
119
  config.title,
117
120
  config.titleStyle
118
- );
121
+ , program.materializationConfigs.textMetrics);
119
122
  const occupiedBounds = [stripBounds, ...tickBounds, ...labelBounds,
120
123
  ...(config.titleVisible === false ? [] : [titleBounds])];
121
124
  assertLegendBoundsInsideCanvas(
122
125
  occupiedBounds,
123
126
  canvas,
124
- "Gradient legend layout", config
127
+ `${label} layout`, config
125
128
  );
126
129
  const background = resolveLegendBackgroundFromBounds(
127
130
  occupiedBounds,
128
131
  config.border,
129
132
  canvas,
130
- "Gradient legend", config
133
+ label, config
131
134
  );
132
135
  return {
133
136
  vertical, x, y, length, thickness, values, texts, labels, ticks, title,
@@ -135,11 +138,11 @@ function resolveGradientLayout(program, config, scale) {
135
138
  };
136
139
  }
137
140
 
138
- function resolveGradientConfig(program, config) {
139
- const layer = resolveContinuousColorLayer(program, config.target);
140
- const encoding = layer.encoding.color;
141
+ export function resolveGradientConfig(program, config, channel = "color") {
142
+ const layer = resolveContinuousColorLayer(program, config.target, channel);
143
+ const encoding = layer.encoding[channel];
141
144
  if (!["quantitative", "temporal"].includes(encoding.fieldType)) {
142
- throw new Error("Gradient legend requires quantitative or temporal color.");
145
+ throw new Error(`${channel === "color" ? "Gradient" : "Stroke gradient"} legend requires quantitative or temporal ${channel}.`);
143
146
  }
144
147
  const scale = requireResolvedLegendScale(
145
148
  program,
@@ -161,152 +164,84 @@ function resolveGradientConfig(program, config) {
161
164
  };
162
165
  }
163
166
 
164
- export const rematerializeGradientLegend = action(
167
+ // Both channels use the same positions and width policy. Their paint and
168
+ // semantic/graphic namespaces remain explicit channel-owned choices.
169
+ export function materializeGradientLegend(program, resolved, appearance) {
170
+ const stroke = appearance !== undefined;
171
+ const channel = stroke ? "stroke" : "color";
172
+ const kind = stroke ? "strokeGradient" : "gradient";
173
+ const prefix = `${channel}Gradient`;
174
+ const { scale, encoding, config: currentConfig } = resolved;
175
+ const config = resolveEffectiveLegendBlockConfig(program, kind, currentConfig);
176
+ const layout = resolveGradientLayout(program, config, scale,
177
+ stroke ? "Stroke gradient legend" : "Gradient legend");
178
+ const stripCount = 60;
179
+ const stripSize = layout.length / stripCount;
180
+ const strips = Array.from({ length: stripCount }, (_, index) => {
181
+ const fraction = (index + 0.5) / stripCount;
182
+ const position = layout.vertical ? 1 - fraction : fraction;
183
+ const samplingScale = scale.midpoint === undefined ? { ...scale, domain: [0, 1] } : scale;
184
+ const value = scale.midpoint === undefined ? position : interpolateNumber(...scale.domain, position);
185
+ const [color] = mapScaleConsumerValues([value], samplingScale, channel);
186
+ return {
187
+ x: layout.x + (layout.vertical ? 0 : index * stripSize),
188
+ y: layout.y + (layout.vertical ? index * stripSize : 0),
189
+ width: layout.vertical ? layout.thickness : stripSize,
190
+ height: layout.vertical ? stripSize : layout.thickness,
191
+ fill: stroke ? appearance.fill : color,
192
+ stroke: color,
193
+ strokeWidth: stroke ? appearance.strokeWidth : 0
194
+ };
195
+ });
196
+ let next = program
197
+ .editSemantic({ property: `guide.legend.${channel}.scale`, value: encoding.scale })
198
+ .editSemantic({ property: `guide.legend.${channel}.title`, value: config.title })
199
+ ._withLegendConfig(kind, currentConfig);
200
+ // Preserve each channel's established primitive trace order.
201
+ if (!stroke) next = next.editGraphics({ target: `${prefix}Strips`, property: "length", value: strips.length });
202
+ next = editLegendBackground(next, `${prefix}Background`, layout.background, config.border);
203
+ if (stroke) next = next.editGraphics({ target: `${prefix}Strips`, property: "length", value: strips.length });
204
+ for (const property of ["x", "y", "width", "height", "fill", "stroke", "strokeWidth"]) {
205
+ next = next.editGraphics({ target: `${prefix}Strips`, property, value: strips.map(strip => strip[property]) });
206
+ }
207
+ next = next.editGraphics({ target: `${prefix}Ticks`, property: "length", value: layout.ticks.length });
208
+ for (const property of ["x1", "y1", "x2", "y2"]) {
209
+ next = next.editGraphics({ target: `${prefix}Ticks`, property, value: layout.ticks.map(tick => tick[property]) });
210
+ }
211
+ next = editGraphicProperties(next, `${prefix}Ticks`, { stroke: DEFAULT_COLORS.mutedText, strokeWidth: 1 });
212
+ next = editGraphicProperties(next, `${prefix}Labels`, {
213
+ length: layout.labels.length, x: layout.labels.map(label => label.x),
214
+ y: layout.labels.map(label => label.y), text: layout.texts
215
+ });
216
+ next = styleContinuousText(next, `${prefix}Labels`, config.labels, { align: layout.labels[0].align });
217
+ if (config.titleVisible === false) return next;
218
+ next = editGraphicProperties(next, `${prefix}Title`, { x: layout.title.x, y: layout.title.y, text: config.title });
219
+ return styleContinuousText(next, `${prefix}Title`, config.titleStyle, { align: layout.title.align });
220
+ }
221
+
222
+ export const rematerializeGradientLegend = /* @__PURE__ */ closedAction(
165
223
  {
166
224
  op: "rematerializeGradientLegend",
167
225
  description: "Rematerialize a continuous color gradient legend."
168
- },
226
+ }, [],
169
227
  function (args = {}) {
170
- validateKeys(args, [], "rematerializeGradientLegend");
171
228
  const stored = this.guideConfigs.legend?.gradient;
172
229
  if (stored === undefined) {
173
230
  throw new Error("Gradient legend requires stored configuration.");
174
231
  }
175
- const { scale, encoding, config } = resolveGradientConfig(this, stored);
176
- const layout = resolveGradientLayout(this, config, scale);
177
- const stripCount = 60;
178
- const stripSize = layout.length / stripCount;
179
- const strips = Array.from({ length: stripCount }, (_, index) => {
180
- const fraction = (index + 0.5) / stripCount;
181
- const position = layout.vertical ? 1 - fraction : fraction;
182
- // Keep legacy uniform samples exact without a value/domain round trip.
183
- const samplingScale = scale.midpoint === undefined ? { ...scale, domain: [0, 1] } : scale;
184
- const value = scale.midpoint === undefined ? position : interpolateNumber(...scale.domain, position);
185
- const [color] = mapScaleConsumerValues([value], samplingScale, "color");
186
- return {
187
- x: layout.x + (layout.vertical ? 0 : index * stripSize),
188
- y: layout.y + (layout.vertical ? index * stripSize : 0),
189
- width: layout.vertical ? layout.thickness : stripSize,
190
- height: layout.vertical ? stripSize : layout.thickness,
191
- fill: color,
192
- stroke: color,
193
- strokeWidth: 0
194
- };
195
- });
196
- let next = this
197
- .editSemantic({
198
- property: "guide.legend.color.scale",
199
- value: encoding.scale
200
- })
201
- .editSemantic({
202
- property: "guide.legend.color.title",
203
- value: config.title
204
- })
205
- ._withLegendConfig("gradient", config)
206
- .editGraphics({
207
- target: "colorGradientStrips",
208
- property: "length",
209
- value: strips.length
210
- });
211
- next = editLegendBackground(
212
- next,
213
- "colorGradientBackground",
214
- layout.background,
215
- config.border
216
- );
217
- for (const property of [
218
- "x", "y", "width", "height", "fill", "stroke", "strokeWidth"
219
- ]) {
220
- next = next.editGraphics({
221
- target: "colorGradientStrips",
222
- property,
223
- value: strips.map(strip => strip[property])
224
- });
225
- }
226
- next = next.editGraphics({
227
- target: "colorGradientTicks",
228
- property: "length",
229
- value: layout.ticks.length
230
- });
231
- for (const property of ["x1", "y1", "x2", "y2"]) {
232
- next = next.editGraphics({
233
- target: "colorGradientTicks",
234
- property,
235
- value: layout.ticks.map(tick => tick[property])
236
- });
237
- }
238
- next = next
239
- .editGraphics({
240
- target: "colorGradientTicks",
241
- property: "stroke",
242
- value: DEFAULT_COLORS.mutedText
243
- })
244
- .editGraphics({
245
- target: "colorGradientTicks",
246
- property: "strokeWidth",
247
- value: 1
248
- })
249
- .editGraphics({
250
- target: "colorGradientLabels",
251
- property: "length",
252
- value: layout.labels.length
253
- })
254
- .editGraphics({
255
- target: "colorGradientLabels",
256
- property: "x",
257
- value: layout.labels.map(label => label.x)
258
- })
259
- .editGraphics({
260
- target: "colorGradientLabels",
261
- property: "y",
262
- value: layout.labels.map(label => label.y)
263
- })
264
- .editGraphics({
265
- target: "colorGradientLabels",
266
- property: "text",
267
- value: layout.texts
268
- });
269
- next = styleContinuousText(
270
- next,
271
- "colorGradientLabels",
272
- config.labels,
273
- { align: layout.labels[0].align }
274
- );
275
- if (config.titleVisible === false) return next;
276
- next = next
277
- .editGraphics({
278
- target: "colorGradientTitle",
279
- property: "x",
280
- value: layout.title.x
281
- })
282
- .editGraphics({
283
- target: "colorGradientTitle",
284
- property: "y",
285
- value: layout.title.y
286
- })
287
- .editGraphics({
288
- target: "colorGradientTitle",
289
- property: "text",
290
- value: config.title
291
- });
292
- return styleContinuousText(
293
- next,
294
- "colorGradientTitle",
295
- config.titleStyle,
296
- { align: layout.title.align }
297
- );
232
+ return materializeGradientLegend(this, resolveGradientConfig(this, stored));
298
233
  }
299
234
  );
300
235
 
301
236
 
302
- export function resolveGradientLegendCreation(program, args = {}) {
237
+ export function resolveGradientLegendCreation(program, args = {}, channel = "color") {
303
238
  const config = normalizeContinuousLegend(args, "gradient");
304
239
  if (args.channels !== undefined && (
305
240
  !Array.isArray(args.channels) ||
306
241
  args.channels.length !== 1 ||
307
- args.channels[0] !== "color"
242
+ args.channels[0] !== channel
308
243
  )) {
309
- throw new Error('Gradient legend requires channels: ["color"].');
244
+ throw new Error(`Gradient legend requires channels: ["${channel}"].`);
310
245
  }
311
246
  if (args.gradient !== undefined && !isPlainObject(args.gradient)) {
312
247
  throw new TypeError("createLegend.gradient must be a plain object.");
@@ -323,62 +258,64 @@ export function resolveGradientLegendCreation(program, args = {}) {
323
258
  config.titleVisible = true;
324
259
  validatePositive(config.gradient.length, "Gradient length");
325
260
  validatePositive(config.gradient.thickness, "Gradient thickness");
326
- const resolved = resolveGradientConfig(program, config);
261
+ const resolved = resolveGradientConfig(program, config, channel);
327
262
  return resolved;
328
263
  }
329
264
 
330
- export function createGradientLegendFromConfig(program, config) {
331
- const resolved = resolveGradientConfig(program, config);
332
- resolveGradientLayout(program, resolved.config, resolved.scale);
265
+ export function createGradientLegendFromConfig(program, config, channel = "color") {
266
+ const prefix = `${channel}Gradient`;
267
+ const kind = channel === "color" ? "gradient" : "strokeGradient";
268
+ const resolved = resolveGradientConfig(program, config, channel);
269
+ resolveGradientLayout(program, resolved.config, resolved.scale, channel === "color" ? "Gradient legend" : "Stroke gradient legend");
333
270
  let next = program
334
271
  .editSemantic({
335
- property: "guide.legend.color.scale",
272
+ property: `guide.legend.${channel}.scale`,
336
273
  value: resolved.encoding.scale
337
274
  })
338
275
  .editSemantic({
339
- property: "guide.legend.color.title",
276
+ property: `guide.legend.${channel}.title`,
340
277
  value: resolved.config.title
341
278
  })
342
- ._withLegendConfig("gradient", resolved.config);
279
+ ._withLegendConfig(kind, resolved.config);
343
280
  if (resolved.config.border !== false) {
344
281
  next = next.createGraphics({
345
- id: "colorGradientBackground",
282
+ id: `${prefix}Background`,
346
283
  type: "rect",
347
284
  ...resolveLegendGraphicPlacement(next)
348
285
  });
349
286
  }
350
287
  next = next
351
288
  .createGraphics({
352
- id: "colorGradientStrips",
289
+ id: `${prefix}Strips`,
353
290
  type: "rect",
354
291
  length: 0,
355
292
  ...resolveLegendGraphicPlacement(next, resolved.config.border === false
356
293
  ? {}
357
- : { after: "colorGradientBackground" })
294
+ : { after: `${prefix}Background` })
358
295
  })
359
296
  .createGraphics({
360
- id: "colorGradientTicks",
297
+ id: `${prefix}Ticks`,
361
298
  type: "line",
362
299
  length: 0,
363
300
  ...resolveLegendGraphicPlacement(next)
364
301
  })
365
302
  .createGraphics({
366
- id: "colorGradientLabels",
303
+ id: `${prefix}Labels`,
367
304
  type: "text",
368
305
  length: 0,
369
306
  ...resolveLegendGraphicPlacement(next)
370
307
  });
371
308
  if (resolved.config.titleVisible !== false) {
372
309
  next = next.createGraphics({
373
- id: "colorGradientTitle",
310
+ id: `${prefix}Title`,
374
311
  type: "text",
375
312
  ...resolveLegendGraphicPlacement(next)
376
313
  });
377
314
  }
378
- return next.rematerializeGradientLegend();
315
+ return next[channel === "color" ? "rematerializeGradientLegend" : "rematerializeStrokeGradientLegend"]();
379
316
  }
380
317
 
381
- export const createGradientLegend = action(
318
+ export const createGradientLegend = /* @__PURE__ */ action(
382
319
  {
383
320
  op: "createGradientLegend",
384
321
  description: "Create a continuous color gradient legend."
@@ -5,6 +5,7 @@ import {
5
5
  removeOpacityLegend
6
6
  } from "./opacity.js";
7
7
  import { createIntervalLegend, rematerializeIntervalLegend } from "./interval.js";
8
+ import { registerStrokeColorLegendActions } from "./stroke.js";
8
9
 
9
10
  export { createGradientLegend, rematerializeGradientLegend } from "./gradient.js";
10
11
  export {
@@ -20,6 +21,7 @@ export function registerContinuousLegendActions(ProgramClass) {
20
21
  ProgramClass.prototype.rematerializeOpacityLegend = rematerializeOpacityLegend;
21
22
  ProgramClass.prototype.removeOpacityLegend = removeOpacityLegend;
22
23
  registerIntervalLegendActions(ProgramClass);
24
+ registerStrokeColorLegendActions(ProgramClass);
23
25
  }
24
26
 
25
27
  export function registerGradientLegendActions(ProgramClass) {
@@ -1,5 +1,5 @@
1
1
  import { resolveLegendItemLayout } from "../../../../layout/legendItems.js";
2
- import { action } from "../../../../core/action.js";
2
+ import { action, closedAction } from "../../../../core/action.js";
3
3
  import { isPlainObject } from "../../../../core/immutable.js";
4
4
  import {
5
5
  validateKeys,
@@ -26,6 +26,7 @@ import {
26
26
  } from "./common.js";
27
27
  import { resolveLegendGraphicPlacement } from
28
28
  "../../../../materialization/graphicHierarchy.js";
29
+ import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
29
30
 
30
31
  const OPTIONS = [
31
32
  "target", "channels", "position", "align", "offset", "title",
@@ -85,15 +86,16 @@ export function normalizeIntervalLegend(args) {
85
86
  };
86
87
  }
87
88
 
88
- export function resolveIntervalConfig(program, stored) {
89
- const layer = resolveContinuousColorLayer(program, stored.target);
90
- const encoding = layer.encoding.color;
89
+ export function resolveIntervalConfig(program, stored, channel = "color") {
90
+ const label = channel === "color" ? "Interval legend" : "Stroke interval legend";
91
+ const layer = resolveContinuousColorLayer(program, stored.target, channel);
92
+ const encoding = layer.encoding[channel];
91
93
  if (encoding.fieldType !== "quantitative") {
92
- throw new Error("Interval legend requires quantitative color.");
94
+ throw new Error(`${label} requires quantitative ${channel}.`);
93
95
  }
94
96
  const scale = program.resolvedScales[encoding.scale];
95
97
  if (!["quantize", "quantile", "threshold"].includes(scale?.type)) {
96
- throw new Error(`Interval legend requires a resolved discretized scale "${encoding.scale}".`);
98
+ throw new Error(`${label} requires a resolved discretized scale "${encoding.scale}".`);
97
99
  }
98
100
  return { encoding, scale, config: {
99
101
  ...stored,
@@ -103,42 +105,42 @@ export function resolveIntervalConfig(program, stored) {
103
105
  } };
104
106
  }
105
107
 
106
- export function resolveIntervalLayout(program, config, scale) {
108
+ export function resolveIntervalLayout(program, config, scale, label = "Interval legend") {
107
109
  const { plot, canvas } = resolveContinuousBounds(program);
108
110
  const labels = formatDiscretizedIntervals(scale.thresholds, config.labels.format);
109
- const layout = resolveLegendItemLayout(plot, config, labels, config.symbol);
111
+ const layout = resolveLegendItemLayout(plot, config, labels, config.symbol, undefined, program.materializationConfigs.textMetrics);
110
112
  const occupiedBounds = layout.bounds;
111
113
  assertLegendBoundsInsideCanvas(
112
114
  occupiedBounds,
113
115
  canvas,
114
- "Interval legend layout", config
116
+ `${label} layout`, config
115
117
  );
116
118
  const background = resolveLegendBackgroundFromBounds(
117
119
  occupiedBounds,
118
120
  config.border,
119
121
  canvas,
120
- "Interval legend", config
122
+ label, config
121
123
  );
122
124
  return { labels, ...layout, background };
123
125
  }
124
126
 
125
- export const rematerializeIntervalLegend = /* @__PURE__ */ action(
127
+ export const rematerializeIntervalLegend = /* @__PURE__ */ closedAction(
126
128
  {
127
129
  op: "rematerializeIntervalLegend",
128
130
  description: "Rematerialize a discretized color interval legend."
129
- },
131
+ }, [],
130
132
  function (args = {}) {
131
- validateKeys(args, [], "rematerializeIntervalLegend");
132
133
  const stored = this.guideConfigs.legend?.interval;
133
134
  if (stored === undefined) {
134
135
  throw new Error("Interval legend requires stored configuration.");
135
136
  }
136
- const { encoding, scale, config } = resolveIntervalConfig(this, stored);
137
+ const { encoding, scale, config: currentConfig } = resolveIntervalConfig(this, stored);
138
+ const config = resolveEffectiveLegendBlockConfig(this, "interval", currentConfig);
137
139
  const layout = resolveIntervalLayout(this, config, scale);
138
140
  let next = editGraphicProperties(this
139
141
  .editSemantic({ property: "guide.legend.color.scale", value: encoding.scale })
140
142
  .editSemantic({ property: "guide.legend.color.title", value: config.title })
141
- ._withLegendConfig("interval", config), "colorLegendSymbols", {
143
+ ._withLegendConfig("interval", currentConfig), "colorLegendSymbols", {
142
144
  length: scale.range.length,
143
145
  x: layout.symbolX,
144
146
  y: layout.itemY.map(value => value - config.symbol.height / 2),
@@ -146,7 +148,10 @@ export const rematerializeIntervalLegend = /* @__PURE__ */ action(
146
148
  height: config.symbol.height,
147
149
  fill: scale.range,
148
150
  stroke: config.symbol.stroke,
149
- strokeWidth: config.symbol.strokeWidth
151
+ strokeWidth: config.symbol.strokeWidth,
152
+ ...(config.blockSymbol?.opacity === undefined
153
+ ? {}
154
+ : { opacity: config.blockSymbol.opacity })
150
155
  });
151
156
  next = editGraphicProperties(next, "colorLegendLabels", {
152
157
  length: layout.labels.length,