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,5 @@
1
- import { action } from "../../../../core/action.js";
1
+ import { editGraphicProperties } from "../../../primitives/graphicProperties.js";
2
+ import { action, closedAction } from "../../../../core/action.js";
2
3
  import { isPlainObject } from "../../../../core/immutable.js";
3
4
  import { validateKeys } from "../../../../core/validation.js";
4
5
  import { mapLinearValues } from "../../../../grammar/scales/index.js";
@@ -14,13 +15,14 @@ import {
14
15
  resolveContinuousLegendLayer,
15
16
  resolveLegendBackgroundFromBounds,
16
17
  resolveLegendTextBounds,
17
- sampleContinuousValues,
18
18
  styleContinuousText,
19
19
  validateNonNegative,
20
20
  validatePositive
21
21
  } from "./common.js";
22
22
  import { resolveLegendGraphicPlacement } from
23
23
  "../../../../materialization/graphicHierarchy.js";
24
+ import { resolveLegendSampleValues } from "../sampling.js";
25
+ import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
24
26
 
25
27
  const SYMBOL_OPTIONS = Object.freeze([
26
28
  "type", "radius", "fill", "stroke", "strokeWidth"
@@ -85,7 +87,7 @@ function resolveOpacityConfig(program, config) {
85
87
  function resolveOpacityLayout(program, config, scale) {
86
88
  const { plot, canvas } = resolveContinuousBounds(program);
87
89
  const vertical = ["right", "left"].includes(config.position);
88
- const values = sampleContinuousValues(scale.domain, config.count);
90
+ const values = resolveLegendSampleValues(config, scale, "Opacity legend");
89
91
  const texts = formatContinuousValues(
90
92
  values,
91
93
  scale.domain,
@@ -93,7 +95,7 @@ function resolveOpacityLayout(program, config, scale) {
93
95
  config.labels.format
94
96
  );
95
97
  const symbolExtent = config.symbol.radius + (config.symbol.strokeWidth ?? 0) / 2;
96
- const labelWidths = texts.map(text => measureTextWidth(text, config.labels));
98
+ const labelWidths = texts.map(text => measureTextWidth(text, config.labels, program.materializationConfigs.textMetrics));
97
99
  let symbols;
98
100
  let labels;
99
101
  let title;
@@ -123,7 +125,7 @@ function resolveOpacityLayout(program, config, scale) {
123
125
  align: config.position === "right" ? "left" : "right"
124
126
  };
125
127
  } else if (config.titlePosition === "left") {
126
- const titleWidth = config.titleVisible === false ? 0 : measureTextWidth(config.title, config.titleStyle);
128
+ const titleWidth = config.titleVisible === false ? 0 : measureTextWidth(config.title, config.titleStyle, program.materializationConfigs.textMetrics);
127
129
  const titlePrefix = config.titleVisible === false ? 0 : titleWidth + 20;
128
130
  const samplesWidth = labelWidths.reduce(
129
131
  (sum, width) => sum + symbolExtent * 2 + config.labels.offset + width,
@@ -166,8 +168,9 @@ function resolveOpacityLayout(program, config, scale) {
166
168
  config.labels.offset - symbolExtent
167
169
  : plot.y + plot.height + config.offset +
168
170
  (config.titleVisible === false ? 0 : config.titleStyle.fontSize + 12) + symbolExtent;
171
+ const samplePitch = values.length === 1 ? 0 : width / (values.length - 1);
169
172
  symbols = values.map((_, index) => ({
170
- x: startX + index * width / (values.length - 1),
173
+ x: startX + index * samplePitch,
171
174
  y
172
175
  }));
173
176
  labels = symbols.map(symbol => ({
@@ -185,9 +188,9 @@ function resolveOpacityLayout(program, config, scale) {
185
188
  title,
186
189
  config.title,
187
190
  config.titleStyle
188
- );
191
+ , program.materializationConfigs.textMetrics);
189
192
  const labelBounds = labels.map((label, index) =>
190
- resolveLegendTextBounds(label, texts[index], config.labels)
193
+ resolveLegendTextBounds(label, texts[index], config.labels, program.materializationConfigs.textMetrics)
191
194
  );
192
195
  const symbolBounds = symbols.map(symbol => ({
193
196
  left: symbol.x - symbolExtent,
@@ -210,23 +213,23 @@ function resolveOpacityLayout(program, config, scale) {
210
213
  return { values, texts, symbols, labels, title, background };
211
214
  }
212
215
 
213
- export const rematerializeOpacityLegend = /* @__PURE__ */ action(
216
+ export const rematerializeOpacityLegend = /* @__PURE__ */ closedAction(
214
217
  {
215
218
  op: "rematerializeOpacityLegend",
216
219
  description: "Rematerialize a field-opacity sample legend."
217
- },
220
+ }, [],
218
221
  function (args = {}) {
219
- validateKeys(args, [], "rematerializeOpacityLegend");
220
222
  const stored = this.guideConfigs.legend?.opacity;
221
223
  if (stored === undefined) {
222
224
  throw new Error("Opacity legend requires stored configuration.");
223
225
  }
224
- const { encoding, scale, config } = resolveOpacityConfig(this, stored);
226
+ const { encoding, scale, config: currentConfig } = resolveOpacityConfig(this, stored);
227
+ const config = resolveEffectiveLegendBlockConfig(this, "opacity", currentConfig);
225
228
  const layout = resolveOpacityLayout(this, config, scale);
226
229
  const opacities = mapLinearValues(layout.values, scale.domain, scale.range, {
227
230
  clamp: scale.clamp ?? false
228
231
  });
229
- let next = this
232
+ let next = editGraphicProperties(editGraphicProperties(editGraphicProperties(this
230
233
  .editSemantic({
231
234
  property: "guide.legend.opacity.scale",
232
235
  value: encoding.scale
@@ -235,57 +238,20 @@ export const rematerializeOpacityLegend = /* @__PURE__ */ action(
235
238
  property: "guide.legend.opacity.title",
236
239
  value: config.title
237
240
  })
238
- ._withLegendConfig("opacity", config)
239
- .editGraphics({
240
- target: "opacityLegendSymbols",
241
- property: "length",
242
- value: layout.values.length
243
- })
244
- .editGraphics({
245
- target: "opacityLegendSymbols",
246
- property: "x",
247
- value: layout.symbols.map(symbol => symbol.x)
248
- })
249
- .editGraphics({
250
- target: "opacityLegendSymbols",
251
- property: "y",
252
- value: layout.symbols.map(symbol => symbol.y)
253
- })
254
- .editGraphics({
255
- target: "opacityLegendSymbols",
256
- property: "radius",
257
- value: config.symbol.radius
258
- })
259
- .editGraphics({
260
- target: "opacityLegendSymbols",
261
- property: "fill",
262
- value: config.symbol.fill
263
- })
264
- .editGraphics({
265
- target: "opacityLegendSymbols",
266
- property: "opacity",
267
- value: opacities
268
- })
269
- .editGraphics({
270
- target: "opacityLegendLabels",
271
- property: "length",
272
- value: layout.values.length
273
- })
274
- .editGraphics({
275
- target: "opacityLegendLabels",
276
- property: "x",
277
- value: layout.labels.map(label => label.x)
278
- })
279
- .editGraphics({
280
- target: "opacityLegendLabels",
281
- property: "y",
282
- value: layout.labels.map(label => label.y)
283
- })
284
- .editGraphics({
285
- target: "opacityLegendLabels",
286
- property: "text",
287
- value: layout.texts
288
- });
241
+ ._withLegendConfig("opacity", currentConfig), "opacityLegendSymbols", {
242
+ length: layout.values.length,
243
+ x: layout.symbols.map(symbol => symbol.x),
244
+ y: layout.symbols.map(symbol => symbol.y)
245
+ }), "opacityLegendSymbols", {
246
+ radius: config.symbol.radius,
247
+ fill: config.symbol.fill,
248
+ opacity: opacities
249
+ }), "opacityLegendLabels", {
250
+ length: layout.values.length,
251
+ x: layout.labels.map(label => label.x),
252
+ y: layout.labels.map(label => label.y),
253
+ text: layout.texts
254
+ });
289
255
  next = editLegendBackground(
290
256
  next,
291
257
  "opacityLegendBackground",
@@ -313,22 +279,11 @@ export const rematerializeOpacityLegend = /* @__PURE__ */ action(
313
279
  { align: layout.labels[0].align }
314
280
  );
315
281
  if (config.titleVisible === false) return next;
316
- next = next
317
- .editGraphics({
318
- target: "opacityLegendTitle",
319
- property: "x",
320
- value: layout.title.x
321
- })
322
- .editGraphics({
323
- target: "opacityLegendTitle",
324
- property: "y",
325
- value: layout.title.y
326
- })
327
- .editGraphics({
328
- target: "opacityLegendTitle",
329
- property: "text",
330
- value: config.title
331
- });
282
+ next = editGraphicProperties(next, "opacityLegendTitle", {
283
+ x: layout.title.x,
284
+ y: layout.title.y,
285
+ text: config.title
286
+ });
332
287
  return styleContinuousText(
333
288
  next,
334
289
  "opacityLegendTitle",
@@ -416,13 +371,12 @@ export const createOpacityLegend = /* @__PURE__ */ action(
416
371
  }
417
372
  );
418
373
 
419
- export const removeOpacityLegend = /* @__PURE__ */ action(
374
+ export const removeOpacityLegend = /* @__PURE__ */ closedAction(
420
375
  {
421
376
  op: "removeOpacityLegend",
422
377
  description: "Remove a field-opacity legend after switching to constant opacity."
423
- },
378
+ }, [],
424
379
  function (args = {}) {
425
- validateKeys(args, [], "removeOpacityLegend");
426
380
  if (this.guideConfigs.legend?.opacity === undefined) return this;
427
381
  const targets = [
428
382
  "opacityLegendBackground",
@@ -0,0 +1,225 @@
1
+ import { action, closedAction } from "../../../../core/action.js";
2
+
3
+ import { resolveLegendGraphicPlacement } from
4
+ "../../../../materialization/graphicHierarchy.js";
5
+ import { findLayer } from "../../../../selectors/layers.js";
6
+ import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
7
+ import {
8
+ editGraphicProperties,
9
+ editLegendBackground,
10
+ resolveContinuousColorLayer,
11
+ styleContinuousText,
12
+ validateNonNegative,
13
+ } from "./common.js";
14
+ import { materializeGradientLegend, resolveGradientConfig, resolveGradientLegendCreation, createGradientLegendFromConfig } from "./gradient.js";
15
+ import { normalizeIntervalLegend, resolveIntervalLayout, resolveIntervalConfig } from "./interval.js";
16
+ import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
17
+
18
+ function requireChannels(args, operation) {
19
+ if (
20
+ !Array.isArray(args.channels) ||
21
+ args.channels.length !== 1 ||
22
+ args.channels[0] !== "stroke"
23
+ ) {
24
+ throw new Error(`${operation} requires channels: ["stroke"].`);
25
+ }
26
+ }
27
+
28
+ function strokeWidth(program, target) {
29
+ const config = program.markConfigs[target] ?? {};
30
+ return config.barAppearance?.strokeWidth ?? config.strokeWidth ?? ({
31
+ point: 1,
32
+ line: 2,
33
+ area: 1,
34
+ bar: 0.5,
35
+ rect: 1,
36
+ arc: 1,
37
+ rule: 2,
38
+ tick: 2
39
+ }[findLayer(program, target)?.mark?.type] ?? 1);
40
+ }
41
+
42
+ function sampleFill(program, target) {
43
+ const graphic = program.graphicSpec.objects[target];
44
+ const properties = graphic?.items?.[0]?.properties ?? graphic?.properties;
45
+ return properties?.fill ?? DEFAULT_COLORS.mark;
46
+ }
47
+
48
+ export function resolveStrokeGradientLegendCreation(program, args = {}) {
49
+ requireChannels(args, "Stroke gradient legend");
50
+ return resolveGradientLegendCreation(program, args, "stroke");
51
+ }
52
+
53
+ export const rematerializeStrokeGradientLegend = /* @__PURE__ */ closedAction(
54
+ {
55
+ op: "rematerializeStrokeGradientLegend",
56
+ description: "Rematerialize a continuous stroke gradient legend."
57
+ }, [],
58
+ function (args = {}) {
59
+ const stored = this.guideConfigs.legend?.strokeGradient;
60
+ if (stored === undefined) {
61
+ throw new Error("Stroke gradient legend requires stored configuration.");
62
+ }
63
+ const resolved = resolveGradientConfig(this, stored, "stroke");
64
+ return materializeGradientLegend(this, resolved, {
65
+ fill: sampleFill(this, resolved.config.target),
66
+ strokeWidth: strokeWidth(this, resolved.config.target)
67
+ });
68
+ }
69
+ );
70
+
71
+ export function createStrokeGradientLegendFromConfig(program, config) {
72
+ return createGradientLegendFromConfig(program, config, "stroke");
73
+ }
74
+
75
+ export const createStrokeGradientLegend = /* @__PURE__ */ action(
76
+ {
77
+ op: "createStrokeGradientLegend",
78
+ description: "Create a continuous stroke gradient legend."
79
+ },
80
+ function (args = {}) {
81
+ const resolved = resolveStrokeGradientLegendCreation(this, args);
82
+ if (this.guideConfigs.legend?.strokeGradient !== undefined) {
83
+ throw new Error("createStrokeGradientLegend requires a missing stroke legend.");
84
+ }
85
+ return createStrokeGradientLegendFromConfig(this, resolved.config);
86
+ }
87
+ );
88
+
89
+ export function resolveStrokeIntervalConfig(program, stored) {
90
+ const resolved = resolveIntervalConfig(program, stored, "stroke");
91
+ if (stored.inferredStrokeWidth !== true) return resolved;
92
+ return { ...resolved, config: { ...resolved.config,
93
+ symbol: { ...stored.symbol, strokeWidth: strokeWidth(program, resolved.config.target) }
94
+ } };
95
+ }
96
+
97
+ export function resolveStrokeIntervalLayout(program, config, scale) {
98
+ return resolveIntervalLayout(program, config, scale, "Stroke interval legend");
99
+ }
100
+
101
+ export function resolveStrokeIntervalLegendCreation(program, args = {}) {
102
+ requireChannels(args, "Stroke interval legend");
103
+ const config = normalizeIntervalLegend({ ...args, channels: undefined });
104
+ const layer = resolveContinuousColorLayer(program, config.target, "stroke");
105
+ config.symbol = {
106
+ ...config.symbol,
107
+ ...(args.symbol?.strokeWidth === undefined
108
+ ? { strokeWidth: strokeWidth(program, layer.id) }
109
+ : {})
110
+ };
111
+ config.inferredStrokeWidth = args.symbol?.strokeWidth === undefined;
112
+ validateNonNegative(config.symbol.strokeWidth, "Legend symbol strokeWidth");
113
+ return resolveStrokeIntervalConfig(program, config);
114
+ }
115
+
116
+ export const rematerializeStrokeIntervalLegend = /* @__PURE__ */ closedAction(
117
+ {
118
+ op: "rematerializeStrokeIntervalLegend",
119
+ description: "Rematerialize a discretized stroke interval legend."
120
+ }, [],
121
+ function (args = {}) {
122
+ const stored = this.guideConfigs.legend?.strokeInterval;
123
+ if (stored === undefined) {
124
+ throw new Error("Stroke interval legend requires stored configuration.");
125
+ }
126
+ const { encoding, scale, config: currentConfig } = resolveStrokeIntervalConfig(this, stored);
127
+ const config = resolveEffectiveLegendBlockConfig(this, "strokeInterval", currentConfig);
128
+ const layout = resolveStrokeIntervalLayout(this, config, scale);
129
+ const fill = config.blockSymbol?.fill ?? sampleFill(this, config.target);
130
+ let next = this
131
+ .editSemantic({ property: "guide.legend.stroke.scale", value: encoding.scale })
132
+ .editSemantic({ property: "guide.legend.stroke.title", value: config.title })
133
+ ._withLegendConfig("strokeInterval", currentConfig);
134
+ next = editGraphicProperties(next, "strokeIntervalSymbols", {
135
+ length: scale.range.length,
136
+ x: layout.symbolX,
137
+ y: layout.itemY.map(value => value - config.symbol.height / 2),
138
+ width: config.symbol.width,
139
+ height: config.symbol.height,
140
+ fill,
141
+ stroke: scale.range,
142
+ strokeWidth: config.symbol.strokeWidth,
143
+ ...(config.blockSymbol?.opacity === undefined
144
+ ? {}
145
+ : { opacity: config.blockSymbol.opacity })
146
+ });
147
+ next = editGraphicProperties(next, "strokeIntervalLabels", {
148
+ length: layout.labels.length,
149
+ x: layout.labelX,
150
+ y: layout.itemY,
151
+ text: layout.labels
152
+ });
153
+ next = editLegendBackground(
154
+ next,
155
+ "strokeIntervalBackground",
156
+ layout.background,
157
+ config.border
158
+ );
159
+ next = styleContinuousText(next, "strokeIntervalLabels", config.labels);
160
+ if (config.titleVisible === false) return next;
161
+ next = editGraphicProperties(next, "strokeIntervalTitle", {
162
+ x: layout.title.x,
163
+ y: layout.title.y,
164
+ text: config.title
165
+ });
166
+ return styleContinuousText(next, "strokeIntervalTitle", config.titleStyle, {
167
+ align: layout.title.align
168
+ });
169
+ }
170
+ );
171
+
172
+ export function createStrokeIntervalLegendFromConfig(program, config) {
173
+ const resolved = resolveStrokeIntervalConfig(program, config);
174
+ resolveStrokeIntervalLayout(program, resolved.config, resolved.scale);
175
+ let next = program
176
+ .editSemantic({
177
+ property: "guide.legend.stroke.scale",
178
+ value: resolved.encoding.scale
179
+ })
180
+ .editSemantic({
181
+ property: "guide.legend.stroke.title",
182
+ value: resolved.config.title
183
+ })
184
+ ._withLegendConfig("strokeInterval", resolved.config);
185
+ const placement = resolveLegendGraphicPlacement(next);
186
+ if (resolved.config.border !== false) {
187
+ next = next.createGraphics({ id: "strokeIntervalBackground", type: "rect",
188
+ ...placement });
189
+ }
190
+ next = next
191
+ .createGraphics({ id: "strokeIntervalSymbols", type: "rect", length: 0,
192
+ ...resolveLegendGraphicPlacement(next, resolved.config.border === false
193
+ ? {}
194
+ : { after: "strokeIntervalBackground" }) })
195
+ .createGraphics({ id: "strokeIntervalLabels", type: "text", length: 0,
196
+ ...placement });
197
+ if (resolved.config.titleVisible !== false) {
198
+ next = next.createGraphics({ id: "strokeIntervalTitle", type: "text",
199
+ ...placement });
200
+ }
201
+ return next.rematerializeStrokeIntervalLegend();
202
+ }
203
+
204
+ export const createStrokeIntervalLegend = /* @__PURE__ */ closedAction(
205
+ {
206
+ op: "createStrokeIntervalLegend",
207
+ description: "Create a discretized stroke interval legend."
208
+ }, undefined,
209
+ function (args = {}) {
210
+ const resolved = resolveStrokeIntervalLegendCreation(this, args);
211
+ if (this.guideConfigs.legend?.strokeInterval !== undefined) {
212
+ throw new Error("createStrokeIntervalLegend requires a missing stroke legend.");
213
+ }
214
+ return createStrokeIntervalLegendFromConfig(this, resolved.config);
215
+ }
216
+ );
217
+
218
+ export function registerStrokeColorLegendActions(ProgramClass) {
219
+ Object.assign(ProgramClass.prototype, {
220
+ createStrokeGradientLegend,
221
+ rematerializeStrokeGradientLegend,
222
+ createStrokeIntervalLegend,
223
+ rematerializeStrokeIntervalLegend
224
+ });
225
+ }
@@ -4,6 +4,10 @@ import { resolveOpacityLegendCreation } from "./continuous/opacity.js";
4
4
  import { normalizeIntervalLegend, resolveIntervalConfig } from "./continuous/interval.js";
5
5
  import { resolveSizeLegendConfig } from "./size.js";
6
6
  import { resolveStrokeWidthLegendConfig } from "./strokeWidth.js";
7
+ import {
8
+ resolveStrokeGradientLegendCreation,
9
+ resolveStrokeIntervalLegendCreation
10
+ } from "./continuous/stroke.js";
7
11
 
8
12
  export function resolveLegendStepConfig(program, step) {
9
13
  switch (step.op) {
@@ -21,7 +25,16 @@ export function resolveLegendStepConfig(program, step) {
21
25
  return { kind: "size", config: resolveSizeLegendConfig(program, step.args) };
22
26
  case "createStrokeWidthLegend":
23
27
  return { kind: "strokeWidth", config: resolveStrokeWidthLegendConfig(program, step.args) };
28
+ case "createStrokeGradientLegend":
29
+ return {
30
+ kind: "strokeGradient",
31
+ config: resolveStrokeGradientLegendCreation(program, step.args).config
32
+ };
33
+ case "createStrokeIntervalLegend":
34
+ return {
35
+ kind: "strokeInterval",
36
+ config: resolveStrokeIntervalLegendCreation(program, step.args).config
37
+ };
24
38
  default: throw new Error(`Unsupported legend creation owner "${step.op}".`);
25
39
  }
26
40
  }
27
-