ggaction 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +10 -3
  11. package/src/actions/basic.js +4 -2
  12. package/src/actions/boxPlots/components.js +14 -4
  13. package/src/actions/boxPlots/edit.js +31 -5
  14. package/src/actions/boxPlots/materialize.js +13 -4
  15. package/src/actions/boxPlots/options.js +48 -16
  16. package/src/actions/canvas/actions.js +3 -1
  17. package/src/actions/canvas/fitting.js +211 -0
  18. package/src/actions/canvas/index.js +2 -0
  19. package/src/actions/categoryOrder/index.js +18 -6
  20. package/src/actions/charts/area.js +51 -0
  21. package/src/actions/charts/bar.js +13 -6
  22. package/src/actions/charts/beeswarm.js +70 -0
  23. package/src/actions/charts/density.js +48 -0
  24. package/src/actions/charts/ecdf.js +186 -0
  25. package/src/actions/charts/endpoints.js +473 -0
  26. package/src/actions/charts/heatmap.js +4 -3
  27. package/src/actions/charts/histogram.js +5 -4
  28. package/src/actions/charts/horizon.js +73 -0
  29. package/src/actions/charts/index.js +37 -0
  30. package/src/actions/charts/interval-regression.js +172 -0
  31. package/src/actions/charts/line.js +8 -6
  32. package/src/actions/charts/parallel.js +8 -2
  33. package/src/actions/charts/pie.js +52 -0
  34. package/src/actions/charts/polar.js +134 -0
  35. package/src/actions/charts/radar.js +305 -0
  36. package/src/actions/charts/radial.js +50 -0
  37. package/src/actions/charts/raincloud.js +422 -0
  38. package/src/actions/charts/rug-strip.js +283 -0
  39. package/src/actions/charts/scatter.js +11 -3
  40. package/src/actions/charts/shared.js +69 -10
  41. package/src/actions/composition/actions.js +129 -3
  42. package/src/actions/coordinates/actions.js +1 -1
  43. package/src/actions/coordinates/edit.js +76 -0
  44. package/src/actions/data/bin.js +29 -0
  45. package/src/actions/data/bin2d.js +57 -185
  46. package/src/actions/data/complete.js +26 -0
  47. package/src/actions/data/computed.js +24 -0
  48. package/src/actions/data/create.js +2 -2
  49. package/src/actions/data/density.js +12 -20
  50. package/src/actions/data/derived.js +111 -15
  51. package/src/actions/data/ecdf.js +38 -0
  52. package/src/actions/data/edit.js +539 -0
  53. package/src/actions/data/filter.js +226 -30
  54. package/src/actions/data/fold.js +24 -0
  55. package/src/actions/data/impute.js +26 -0
  56. package/src/actions/data/index.js +38 -0
  57. package/src/actions/data/interval.js +2 -0
  58. package/src/actions/data/intervalEdit.js +86 -4
  59. package/src/actions/data/normalize.js +27 -0
  60. package/src/actions/data/regression.js +19 -21
  61. package/src/actions/data/revisionBindings.js +145 -0
  62. package/src/actions/data/shared.js +20 -4
  63. package/src/actions/data/stack.js +26 -0
  64. package/src/actions/data/summary.js +26 -0
  65. package/src/actions/data/timeUnit.js +4 -1
  66. package/src/actions/data/window.js +1 -1
  67. package/src/actions/encodings/appearance.js +32 -13
  68. package/src/actions/encodings/barWidth.js +8 -0
  69. package/src/actions/encodings/basic.js +2 -0
  70. package/src/actions/encodings/channels.js +415 -0
  71. package/src/actions/encodings/color/continuous.js +19 -59
  72. package/src/actions/encodings/color/index.js +30 -23
  73. package/src/actions/encodings/color/layout.js +3 -97
  74. package/src/actions/encodings/color/policy.js +5 -8
  75. package/src/actions/encodings/density/resolve.js +37 -5
  76. package/src/actions/encodings/density.js +124 -34
  77. package/src/actions/encodings/histogram.js +3 -2
  78. package/src/actions/encodings/horizon/resolve.js +8 -4
  79. package/src/actions/encodings/horizon.js +18 -8
  80. package/src/actions/encodings/index.js +6 -0
  81. package/src/actions/encodings/offset.js +30 -29
  82. package/src/actions/encodings/parallel.js +2 -8
  83. package/src/actions/encodings/position/apply.js +63 -11
  84. package/src/actions/encodings/position/index.js +52 -6
  85. package/src/actions/encodings/position/policies/arc.js +14 -0
  86. package/src/actions/encodings/position/policies/area.js +4 -42
  87. package/src/actions/encodings/position/policies/bar.js +57 -13
  88. package/src/actions/encodings/position/resolve.js +88 -26
  89. package/src/actions/encodings/ranged.js +123 -97
  90. package/src/actions/encodings/remove.js +44 -34
  91. package/src/actions/encodings/ruleAppearance.js +24 -32
  92. package/src/actions/encodings/seriesLayout.js +105 -0
  93. package/src/actions/encodings/shared.js +107 -37
  94. package/src/actions/encodings/stroke.js +226 -0
  95. package/src/actions/encodings/strokeDash.js +9 -6
  96. package/src/actions/encodings/temporal.js +7 -0
  97. package/src/actions/encodings/text.js +30 -15
  98. package/src/actions/errorBands/create.js +41 -8
  99. package/src/actions/errorBands/edit.js +360 -25
  100. package/src/actions/errorBars/create.js +56 -17
  101. package/src/actions/errorBars/edit.js +372 -21
  102. package/src/actions/facets/actions.js +619 -41
  103. package/src/actions/facets/derive.js +139 -25
  104. package/src/actions/gradientPlots/components.js +43 -13
  105. package/src/actions/gradientPlots/edit.js +14 -4
  106. package/src/actions/gradientPlots/materialize.js +8 -8
  107. package/src/actions/gradientPlots/options.js +10 -2
  108. package/src/actions/guides/applicability.js +28 -19
  109. package/src/actions/guides/axes/axes.js +4 -3
  110. package/src/actions/guides/axes/axis.js +18 -18
  111. package/src/actions/guides/axes/components.js +43 -0
  112. package/src/actions/guides/axes/edit.js +16 -51
  113. package/src/actions/guides/axes/labels.js +184 -28
  114. package/src/actions/guides/axes/lines.js +13 -7
  115. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  116. package/src/actions/guides/axes/parallel/policy.js +137 -0
  117. package/src/actions/guides/axes/parallel/resolve.js +50 -25
  118. package/src/actions/guides/axes/parallel.js +93 -217
  119. package/src/actions/guides/axes/policy.js +21 -67
  120. package/src/actions/guides/axes/tickGroups.js +15 -8
  121. package/src/actions/guides/axes/ticks.js +11 -6
  122. package/src/actions/guides/axes/titles.js +32 -11
  123. package/src/actions/guides/basic.js +2 -0
  124. package/src/actions/guides/facade.js +106 -0
  125. package/src/actions/guides/facadeAxes.js +200 -0
  126. package/src/actions/guides/facadeLegend.js +63 -0
  127. package/src/actions/guides/grids/grid.js +37 -33
  128. package/src/actions/guides/grids/resolve.js +17 -4
  129. package/src/actions/guides/guides.js +54 -31
  130. package/src/actions/guides/legends/blocks.js +426 -0
  131. package/src/actions/guides/legends/categorical/actions.js +309 -279
  132. package/src/actions/guides/legends/categorical/components.js +21 -17
  133. package/src/actions/guides/legends/categorical/index.js +37 -33
  134. package/src/actions/guides/legends/categorical/layout.js +123 -354
  135. package/src/actions/guides/legends/categorical/options.js +32 -16
  136. package/src/actions/guides/legends/categorical/recipes.js +114 -9
  137. package/src/actions/guides/legends/categorical/resolve.js +30 -15
  138. package/src/actions/guides/legends/categorical/symbols.js +87 -33
  139. package/src/actions/guides/legends/continuous/common.js +99 -26
  140. package/src/actions/guides/legends/continuous/gradient.js +120 -89
  141. package/src/actions/guides/legends/continuous/index.js +8 -2
  142. package/src/actions/guides/legends/continuous/interval.js +79 -95
  143. package/src/actions/guides/legends/continuous/opacity.js +113 -89
  144. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  145. package/src/actions/guides/legends/creation.js +40 -0
  146. package/src/actions/guides/legends/edit.js +321 -90
  147. package/src/actions/guides/legends/focused.js +2 -2
  148. package/src/actions/guides/legends/index.js +2 -0
  149. package/src/actions/guides/legends/lane.js +149 -102
  150. package/src/actions/guides/legends/lifecycle.js +143 -0
  151. package/src/actions/guides/legends/remove.js +43 -78
  152. package/src/actions/guides/legends/sampling.js +140 -0
  153. package/src/actions/guides/legends/size.js +213 -116
  154. package/src/actions/guides/legends/strokeWidth.js +121 -73
  155. package/src/actions/guides/legends/target.js +123 -0
  156. package/src/actions/guides/legends/transition.js +192 -0
  157. package/src/actions/guides/polar/axes/facade.js +81 -66
  158. package/src/actions/guides/polar/axes/labels.js +28 -8
  159. package/src/actions/guides/polar/axes/lines.js +12 -5
  160. package/src/actions/guides/polar/axes/shared.js +22 -0
  161. package/src/actions/guides/polar/axes/ticks.js +6 -5
  162. package/src/actions/guides/polar/axes/titles.js +6 -5
  163. package/src/actions/guides/polar/grids.js +8 -8
  164. package/src/actions/guides/polar/resolve.js +12 -8
  165. package/src/actions/guides/reuse.js +44 -0
  166. package/src/actions/guides/tickValues.js +19 -21
  167. package/src/actions/index.js +15 -2
  168. package/src/actions/intervals/resolve.js +35 -15
  169. package/src/actions/marks/arc/actions.js +78 -20
  170. package/src/actions/marks/area/actions.js +51 -12
  171. package/src/actions/marks/area/materialize.js +22 -3
  172. package/src/actions/marks/bar/create.js +11 -2
  173. package/src/actions/marks/bar/edit.js +20 -3
  174. package/src/actions/marks/bar/materialize.js +33 -9
  175. package/src/actions/marks/index.js +14 -0
  176. package/src/actions/marks/line/actions.js +71 -113
  177. package/src/actions/marks/line/materialize.js +57 -56
  178. package/src/actions/marks/point/create.js +8 -2
  179. package/src/actions/marks/point/edit.js +29 -21
  180. package/src/actions/marks/point/index.js +1 -0
  181. package/src/actions/marks/point/jitter.js +3 -0
  182. package/src/actions/marks/point/materialize.js +154 -16
  183. package/src/actions/marks/point/packing.js +68 -0
  184. package/src/actions/marks/rect/actions.js +33 -12
  185. package/src/actions/marks/references.js +658 -0
  186. package/src/actions/marks/remove.js +87 -4
  187. package/src/actions/marks/rule/actions.js +77 -9
  188. package/src/actions/marks/rule/appearance.js +26 -0
  189. package/src/actions/marks/shared.js +13 -13
  190. package/src/actions/marks/text/actions.js +296 -13
  191. package/src/actions/marks/text/index.js +4 -0
  192. package/src/actions/marks/text/layout.js +156 -8
  193. package/src/actions/marks/text/removal.js +173 -0
  194. package/src/actions/marks/text/selection.js +73 -0
  195. package/src/actions/marks/tick/actions.js +22 -5
  196. package/src/actions/primitives/createGraphics.js +3 -1
  197. package/src/actions/primitives/editGraphics.js +26 -0
  198. package/src/actions/primitives/semantic.js +3 -2
  199. package/src/actions/primitives/semanticAction.js +120 -10
  200. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  201. package/src/actions/primitives/semanticValidation/index.js +9 -1
  202. package/src/actions/primitives/semanticValidation/layer.js +23 -1
  203. package/src/actions/primitives/semanticValidation/scale.js +38 -8
  204. package/src/actions/regression/components.js +26 -11
  205. package/src/actions/regression/create.js +11 -3
  206. package/src/actions/regression/edit.js +40 -13
  207. package/src/actions/regression/resolve.js +1 -1
  208. package/src/actions/resources/index.js +7 -0
  209. package/src/actions/resources/remove.js +172 -0
  210. package/src/actions/scales/channels.js +212 -0
  211. package/src/actions/scales/consumers/common.js +21 -54
  212. package/src/actions/scales/consumers/families.js +28 -14
  213. package/src/actions/scales/consumers/index.js +42 -4
  214. package/src/actions/scales/consumers/seriesLayout.js +57 -12
  215. package/src/actions/scales/create.js +58 -26
  216. package/src/actions/scales/definitions.js +109 -39
  217. package/src/actions/scales/edit.js +101 -34
  218. package/src/actions/scales/editPolicy.js +95 -35
  219. package/src/actions/scales/index.js +6 -0
  220. package/src/actions/scales/materialize.js +14 -49
  221. package/src/actions/scales/offset.js +48 -0
  222. package/src/actions/scales/parallel.js +71 -0
  223. package/src/actions/scales/patch.js +1 -1
  224. package/src/actions/scales/preview.js +140 -0
  225. package/src/actions/scales/quantitativeColor.js +9 -5
  226. package/src/actions/selection/actions.js +154 -30
  227. package/src/actions/selection/index.js +3 -0
  228. package/src/actions/theme/actions.js +98 -0
  229. package/src/actions/theme/composition.js +238 -0
  230. package/src/actions/theme/index.js +16 -0
  231. package/src/actions/theme/reconcile.js +1130 -0
  232. package/src/actions/theme/state.js +160 -0
  233. package/src/actions/titles/actions.js +11 -10
  234. package/src/actions/titles/resolve.js +1 -40
  235. package/src/actions/violinPlots/create.js +90 -43
  236. package/src/actions/violinPlots/edit.js +192 -0
  237. package/src/actions/violinPlots/index.js +2 -0
  238. package/src/core/ChartProgram.js +7 -0
  239. package/src/core/action.js +32 -2
  240. package/src/core/compositionState.js +141 -8
  241. package/src/core/programState.js +20 -0
  242. package/src/core/resourceReferences.js +332 -0
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/grammar/aggregate.js +66 -23
  245. package/src/grammar/arcs.js +58 -1
  246. package/src/grammar/areaEndpoints.js +46 -0
  247. package/src/grammar/areaSeries.js +87 -41
  248. package/src/grammar/bars/aggregate.js +37 -13
  249. package/src/grammar/bars/geometry.js +44 -0
  250. package/src/grammar/bars/policy.js +9 -7
  251. package/src/grammar/bin.js +226 -0
  252. package/src/grammar/categoryOrder.js +34 -0
  253. package/src/grammar/complete.js +279 -0
  254. package/src/grammar/computed.js +351 -0
  255. package/src/grammar/coordinates.js +37 -0
  256. package/src/grammar/density.js +239 -37
  257. package/src/grammar/displayLabels.js +57 -0
  258. package/src/grammar/ecdf.js +198 -0
  259. package/src/grammar/facets/dependencies.js +133 -1
  260. package/src/grammar/facets/guides.js +3 -0
  261. package/src/grammar/facets/index.js +123 -52
  262. package/src/grammar/facets/scales.js +15 -18
  263. package/src/grammar/fold.js +123 -0
  264. package/src/grammar/horizon.js +6 -4
  265. package/src/grammar/impute.js +340 -0
  266. package/src/grammar/interval.js +37 -20
  267. package/src/grammar/lineSeries.js +20 -63
  268. package/src/grammar/markFilter.js +22 -4
  269. package/src/grammar/markLabelSelection.js +57 -0
  270. package/src/grammar/markLabels.js +110 -0
  271. package/src/grammar/normalize.js +366 -0
  272. package/src/grammar/parallelAxisTitles.js +16 -0
  273. package/src/grammar/pathSeries.js +109 -0
  274. package/src/grammar/pointPacking.js +183 -0
  275. package/src/grammar/pointShapes.js +13 -4
  276. package/src/grammar/polar.js +90 -8
  277. package/src/grammar/positionDatum.js +12 -0
  278. package/src/grammar/rects.js +9 -1
  279. package/src/grammar/regression/derive.js +4 -0
  280. package/src/grammar/regression/index.js +8 -2
  281. package/src/grammar/regression/models.js +22 -7
  282. package/src/grammar/regression/parameters.js +63 -13
  283. package/src/grammar/rotation.js +29 -0
  284. package/src/grammar/roundedRect.js +180 -0
  285. package/src/grammar/rules.js +8 -21
  286. package/src/grammar/scales/color.js +23 -1
  287. package/src/grammar/scales/colorConsumers.js +57 -0
  288. package/src/grammar/scales/continuous.js +2 -2
  289. package/src/grammar/scales/definition.js +42 -5
  290. package/src/grammar/scales/discretized.js +11 -5
  291. package/src/grammar/scales/fields.js +38 -8
  292. package/src/grammar/scales/index.js +1 -0
  293. package/src/grammar/scales/mapping.js +2 -0
  294. package/src/grammar/scales/ordinal.js +12 -3
  295. package/src/grammar/scales/policies.js +2 -2
  296. package/src/grammar/scales/radial.js +55 -0
  297. package/src/grammar/scales/size.js +456 -0
  298. package/src/grammar/scales/types.js +1 -0
  299. package/src/grammar/schemas/concreteGraphic.js +14 -2
  300. package/src/grammar/schemas/graphic.js +15 -0
  301. package/src/grammar/schemas/graphicBounds.js +179 -30
  302. package/src/grammar/schemas/semanticPath.js +23 -4
  303. package/src/grammar/stack.js +170 -0
  304. package/src/grammar/statisticalReference.js +103 -0
  305. package/src/grammar/statistics/confidenceInterval.js +101 -0
  306. package/src/grammar/strokeStyle.js +77 -0
  307. package/src/grammar/summary.js +245 -0
  308. package/src/grammar/text.js +22 -16
  309. package/src/grammar/timeUnit.js +234 -13
  310. package/src/grammar/transformTopology.js +10 -0
  311. package/src/grammar/transforms.js +418 -16
  312. package/src/grammar/valueFormat.js +112 -0
  313. package/src/grammar/weightedStatistics.js +374 -0
  314. package/src/grammar/window.js +232 -21
  315. package/src/layout/aspect.js +80 -0
  316. package/src/layout/facets.js +100 -12
  317. package/src/layout/guideCollisions.js +12 -0
  318. package/src/layout/labels.js +343 -0
  319. package/src/layout/legend.js +2 -10
  320. package/src/layout/legendItems.js +102 -0
  321. package/src/layout/legendLane.js +100 -11
  322. package/src/materialization/bars/aggregate.js +56 -147
  323. package/src/materialization/bars/histogram.js +104 -20
  324. package/src/materialization/bars/ranged.js +19 -3
  325. package/src/materialization/bars/resolve.js +7 -4
  326. package/src/materialization/categorySlotOffset.js +40 -0
  327. package/src/materialization/composition.js +3 -1
  328. package/src/materialization/compositionSnapshot.js +1 -1
  329. package/src/materialization/coordinateBounds.js +204 -0
  330. package/src/materialization/dependencies.js +93 -16
  331. package/src/materialization/encodings.js +1 -1
  332. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  333. package/src/materialization/facetGuides/placement.js +8 -2
  334. package/src/materialization/facetGuides/preparation.js +12 -7
  335. package/src/materialization/facetHeaders.js +346 -0
  336. package/src/materialization/facets.js +23 -66
  337. package/src/materialization/guides/layout.js +57 -0
  338. package/src/materialization/guides/resources.js +28 -2
  339. package/src/materialization/legends.js +11 -2
  340. package/src/materialization/marks/capabilities.js +20 -5
  341. package/src/materialization/marks/grouping.js +52 -0
  342. package/src/materialization/marks/index.js +68 -27
  343. package/src/materialization/marks/policies.js +4 -1
  344. package/src/materialization/rect.js +55 -42
  345. package/src/materialization/rectConfig.js +5 -1
  346. package/src/materialization/rowEncoding.js +13 -13
  347. package/src/materialization/scaleGuideDependencies.js +8 -5
  348. package/src/materialization/scales/map.js +6 -3
  349. package/src/materialization/scales/policies/arc.js +42 -0
  350. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  351. package/src/materialization/scales/policies/offset.js +50 -8
  352. package/src/materialization/scales/policies/series.js +5 -1
  353. package/src/materialization/scales/resolve.js +65 -17
  354. package/src/materialization/selection/compatibility.js +11 -0
  355. package/src/materialization/selection/items/arc.js +28 -4
  356. package/src/materialization/selection/items/bar.js +42 -71
  357. package/src/materialization/selection/items/common.js +27 -6
  358. package/src/materialization/selection/items/path.js +3 -13
  359. package/src/materialization/selection/items/rect.js +35 -8
  360. package/src/materialization/selection/path.js +1 -1
  361. package/src/materialization/selection/point.js +3 -1
  362. package/src/materialization/text.js +324 -14
  363. package/src/mcp/adapter.js +2 -2
  364. package/src/renderers/canvas/circle.js +2 -3
  365. package/src/renderers/canvas/line.js +2 -3
  366. package/src/renderers/canvas/native.js +4 -4
  367. package/src/renderers/canvas/path.js +2 -3
  368. package/src/renderers/canvas/rect.js +2 -3
  369. package/src/renderers/canvas/stroke.js +11 -0
  370. package/src/renderers/svg.js +19 -0
  371. package/src/selectors/datasets.js +44 -0
  372. package/src/selectors/layers.js +7 -2
  373. package/src/selectors/scales.js +6 -0
  374. package/src/theme/defaults.js +140 -0
  375. package/types/basic.d.ts +17 -2
  376. package/types/index.d.ts +211 -0
  377. package/types/program.d.ts +2182 -204
  378. package/src/materialization/bars/grouped.js +0 -144
@@ -1,25 +1,57 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import {
3
- validateGeneratedItemLimit,
3
+ validateNonEmptyString,
4
4
  validateKeys
5
5
  } from "../../../core/validation.js";
6
- import { mapLinearValues } from "../../../grammar/scales/index.js";
7
- import { resolveGraphicBounds } from "../../../layout/canvas.js";
6
+ import {
7
+ formatDiscretizedIntervals,
8
+ isDiscreteSizeScaleType,
9
+ isSizeScaleType,
10
+ mapSizeValues
11
+ } from "../../../grammar/scales/index.js";
12
+ import { resolveLegendItemLayout } from "../../../layout/legendItems.js";
8
13
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
9
14
  "../../../theme/defaults.js";
10
- import { resolveLayout as resolveCategoricalLayout } from
11
- "./categorical/layout.js";
12
15
  import { findLayer } from "../../../selectors/layers.js";
13
- import { findCanvasGraphic, resolveLegendGraphicPlacement } from
16
+ import { resolveLegendGraphicPlacement } from
14
17
  "../../../materialization/graphicHierarchy.js";
15
18
  import {
19
+ assertLegendBoundsInsideCanvas,
20
+ editLegendBackground,
21
+ normalizeItemLegendLayout,
22
+ normalizeLegendBorder,
23
+ normalizeLegendTextOptions,
24
+ normalizeLegendTitleOptions,
25
+ resolveContinuousBounds,
26
+ resolveLegendBackgroundFromBounds,
16
27
  formatContinuousValues,
17
- sampleContinuousValues,
18
28
  selectLegendLayer,
19
29
  styleContinuousText
20
30
  } from "./continuous/common.js";
31
+ import { legendResourcePolicies } from "../../../materialization/guides/resources.js";
32
+ import {
33
+ normalizeLegendSampling,
34
+ readLegendSampling,
35
+ resolveLegendSampleValues
36
+ } from "./sampling.js";
37
+ import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
21
38
 
22
- const SIZE_OPTIONS = Object.freeze(["target", "count"]);
39
+ const SIZE_OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
40
+ "direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
41
+
42
+ export const SIZE_LEGEND_LABELS = Object.freeze({
43
+ offset: 12,
44
+ color: DEFAULT_COLORS.text,
45
+ fontSize: 12,
46
+ fontFamily: DEFAULT_FONT_FAMILY,
47
+ fontWeight: "normal"
48
+ });
49
+ export const SIZE_LEGEND_TITLE_STYLE = Object.freeze({
50
+ color: DEFAULT_COLORS.strongText,
51
+ fontSize: 13,
52
+ fontFamily: DEFAULT_FONT_FAMILY,
53
+ fontWeight: 600
54
+ });
23
55
 
24
56
  export function isSizeLegendPoint(layer) {
25
57
  return layer?.mark?.type === "point" &&
@@ -38,25 +70,80 @@ export function resolveSizeLegendPoint(program, requested) {
38
70
  return layer;
39
71
  }
40
72
 
41
- function requireScale(program, id, type) {
73
+ function requireScale(program, id) {
42
74
  const scale = program.resolvedScales[id];
43
- if (scale?.type !== type) {
44
- throw new Error(`Legend requires resolved ${type} scale "${id}".`);
75
+ if (!isSizeScaleType(scale?.type)) {
76
+ throw new Error(`Legend requires resolved size scale "${id}".`);
45
77
  }
46
78
  return scale;
47
79
  }
48
80
 
49
- function bounds(program) {
50
- const value = resolveGraphicBounds(program);
51
- const canvas = findCanvasGraphic(program);
52
- if (
53
- value === undefined ||
54
- ![value.x, value.y, value.width, value.height].every(Number.isFinite) ||
55
- !Number.isFinite(canvas?.properties.width)
56
- ) {
57
- throw new Error("Legend layout requires Canvas bounds and width.");
81
+ export function resolveSizeLegendLayout(program, config) {
82
+ const scale = requireScale(program, config.scale);
83
+ const categorical = [
84
+ program.guideConfigs.legend?.series,
85
+ program.guideConfigs.legend?.color,
86
+ program.guideConfigs.legend?.stroke
87
+ ]
88
+ .find(candidate => candidate?.target === config.target);
89
+ const inherit = config.inheritAppearance === true && categorical !== undefined;
90
+ const inheritedLabels = inherit ? { ...categorical.labels, offset: config.labels.offset } : config.labels;
91
+ const inheritedTitleStyle = inherit ? categorical.titleStyle : config.titleStyle;
92
+ const effective = resolveEffectiveLegendBlockConfig(program, "size", {
93
+ ...config,
94
+ labels: inheritedLabels,
95
+ titleStyle: inheritedTitleStyle
96
+ });
97
+ const labels = effective.labels;
98
+ const titleStyle = effective.titleStyle;
99
+ const position = categorical?.position ?? config.position;
100
+ if (categorical?.layout === "legacy-bottom") {
101
+ throw new Error('Combined size legends require layout "edge".');
102
+ }
103
+ const horizontal = categorical !== undefined && ["top", "bottom"].includes(position);
104
+ const geometry = horizontal ? {
105
+ position, align: categorical.align, direction: categorical.direction,
106
+ columns: categorical.columns, titlePosition: categorical.titlePosition,
107
+ offset: categorical.offset, itemGap: effective.blockGap ?? categorical.itemGap
108
+ } : { position };
109
+ const discrete = isDiscreteSizeScaleType(scale.type);
110
+ if (discrete && readLegendSampling(config).mode === "values") {
111
+ throw new Error("Discrete size legends do not support exact values.");
58
112
  }
59
- return value;
113
+ const values = discrete
114
+ ? undefined
115
+ : resolveLegendSampleValues(effective, scale, "Size legend");
116
+ const areas = discrete
117
+ ? scale.range
118
+ : mapSizeValues(values, scale);
119
+ const radii = areas.map(area => Math.sqrt(area / Math.PI));
120
+ const radius = Math.max(...radii);
121
+ const width = Math.max(32, radius * 2);
122
+ const { plot, canvas } = resolveContinuousBounds(program);
123
+ const text = discrete
124
+ ? formatDiscretizedIntervals(scale.thresholds, config.labels.format)
125
+ : formatContinuousValues(
126
+ values,
127
+ scale.domain,
128
+ "quantitative",
129
+ config.labels.format
130
+ );
131
+ const symbolStroke = effective.blockSymbol?.strokeWidth ?? 0;
132
+ const symbolExtent = symbolStroke / 2;
133
+ const sampleWidth = width + symbolStroke;
134
+ const layout = resolveLegendItemLayout(plot, { ...effective, ...geometry, labels, titleStyle }, text, {
135
+ width: sampleWidth, height: radius * 2 + symbolStroke,
136
+ itemBounds: radii.map(r => ({
137
+ left: sampleWidth / 2 - r - symbolExtent,
138
+ right: sampleWidth / 2 + r + symbolExtent,
139
+ top: -r - symbolExtent,
140
+ bottom: r + symbolExtent
141
+ }))
142
+ });
143
+ assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Size legend layout", { ...effective, ...geometry });
144
+ const background = resolveLegendBackgroundFromBounds(layout.bounds, effective.border, canvas, "Size legend", { ...effective, ...geometry });
145
+ return { ...layout, symbolX: layout.symbolX.map(x => x + sampleWidth / 2), radii, text,
146
+ labels, titleStyle, background, config: effective };
60
147
  }
61
148
 
62
149
  export const rematerializeSizeLegend = action(
@@ -73,7 +160,7 @@ export const rematerializeSizeLegend = action(
73
160
  if (encoding?.scale === undefined) {
74
161
  throw new Error("Size legend target requires a size encoding.");
75
162
  }
76
- const scale = requireScale(this, encoding.scale, "linear");
163
+ const scale = requireScale(this, encoding.scale);
77
164
  const title = config.inferredTitle === true ? encoding.field : config.title;
78
165
  const currentConfig = {
79
166
  ...config,
@@ -81,127 +168,137 @@ export const rematerializeSizeLegend = action(
81
168
  title,
82
169
  domain: scale.domain
83
170
  };
84
- const plot = bounds(this);
85
- const categorical = this.guideConfigs.legend?.series ??
86
- this.guideConfigs.legend?.color;
87
- const leftLayout = categorical?.position === "left"
88
- ? resolveCategoricalLayout(this, categorical).size
89
- : undefined;
90
- const originX = leftLayout?.titleX ?? plot.x + plot.width + 30;
91
- const seriesCount = categorical?.domain.length ?? 0;
92
- const titleY = leftLayout?.titleY ?? plot.y + 56 + seriesCount * 34 + 22;
93
- const values = sampleContinuousValues(scale.domain, currentConfig.count);
94
- const areas = mapLinearValues(values, scale.domain, scale.range, {
95
- clamp: scale.clamp ?? false
96
- });
97
- const itemY = leftLayout?.itemY ??
98
- values.map((_, index) => titleY + 34 + index * 40);
99
- const symbolX = leftLayout?.symbolX ?? values.map(() => originX + 16);
100
- const labelX = leftLayout?.labelX ?? values.map(() => originX + 44);
171
+ const layout = resolveSizeLegendLayout(this, currentConfig);
172
+ const effective = layout.config;
173
+ const { itemY, symbolX, labelX, radii, labels, titleStyle } = layout;
101
174
  let next = this
102
175
  .editSemantic({ property: "guide.legend.size.scale", value: encoding.scale })
103
176
  .editSemantic({ property: "guide.legend.size.title", value: title })
104
177
  ._withLegendConfig("size", currentConfig)
105
- .editGraphics({ target: "sizeLegendSymbols", property: "length", value: values.length })
178
+ .editGraphics({ target: "sizeLegendSymbols", property: "length", value: radii.length })
106
179
  .editGraphics({ target: "sizeLegendSymbols", property: "x", value: symbolX })
107
180
  .editGraphics({ target: "sizeLegendSymbols", property: "y", value: itemY })
108
181
  .editGraphics({
109
182
  target: "sizeLegendSymbols",
110
183
  property: "radius",
111
- value: areas.map(area => Math.sqrt(area / Math.PI))
184
+ value: radii
112
185
  })
113
186
  .editGraphics({
114
187
  target: "sizeLegendSymbols",
115
188
  property: "fill",
116
- value: DEFAULT_COLORS.sizeSymbol
189
+ value: effective.blockSymbol?.fill ?? DEFAULT_COLORS.sizeSymbol
117
190
  })
118
- .editGraphics({ target: "sizeLegendSymbols", property: "opacity", value: 0.7 })
119
- .editGraphics({ target: "sizeLegendLabels", property: "length", value: values.length })
191
+ .editGraphics({ target: "sizeLegendSymbols", property: "opacity",
192
+ value: effective.blockSymbol?.opacity ?? 0.7 })
193
+ .editGraphics({ target: "sizeLegendLabels", property: "length", value: radii.length })
120
194
  .editGraphics({ target: "sizeLegendLabels", property: "x", value: labelX })
121
195
  .editGraphics({ target: "sizeLegendLabels", property: "y", value: itemY })
122
196
  .editGraphics({
123
197
  target: "sizeLegendLabels",
124
198
  property: "text",
125
- value: formatContinuousValues(values, scale.domain, "quantitative")
126
- })
127
- .editGraphics({ target: "sizeLegendTitle", property: "x", value: originX })
128
- .editGraphics({ target: "sizeLegendTitle", property: "y", value: titleY })
129
- .editGraphics({ target: "sizeLegendTitle", property: "text", value: title });
130
- next = styleContinuousText(
131
- next,
132
- "sizeLegendLabels",
133
- currentConfig.inheritAppearance === true
134
- ? categorical.labels
135
- : {
136
- color: DEFAULT_COLORS.text,
137
- fontSize: 12,
138
- fontFamily: DEFAULT_FONT_FAMILY,
139
- fontWeight: "normal"
140
- }
141
- );
142
- return styleContinuousText(
143
- next,
144
- "sizeLegendTitle",
145
- currentConfig.inheritAppearance === true
146
- ? categorical.titleStyle
147
- : {
148
- color: DEFAULT_COLORS.strongText,
149
- fontSize: 13,
150
- fontFamily: DEFAULT_FONT_FAMILY,
151
- fontWeight: 600
152
- }
153
- );
199
+ value: layout.text
200
+ });
201
+ next = editLegendBackground(next, "sizeLegendBackground", layout.background, currentConfig.border);
202
+ if (effective.blockSymbol?.stroke !== undefined) {
203
+ next = next.editGraphics({ target: "sizeLegendSymbols", property: "stroke",
204
+ value: effective.blockSymbol.stroke });
205
+ }
206
+ if (effective.blockSymbol?.strokeWidth !== undefined) {
207
+ next = next.editGraphics({ target: "sizeLegendSymbols", property: "strokeWidth",
208
+ value: effective.blockSymbol.strokeWidth });
209
+ }
210
+ next = styleContinuousText(next, "sizeLegendLabels", labels);
211
+ if (effective.titleVisible === false) return next;
212
+ next = next
213
+ .editGraphics({ target: "sizeLegendTitle", property: "x", value: layout.title.x })
214
+ .editGraphics({ target: "sizeLegendTitle", property: "y", value: layout.title.y })
215
+ .editGraphics({ target: "sizeLegendTitle", property: "text", value: effective.title });
216
+ return styleContinuousText(next, "sizeLegendTitle", titleStyle, { align: layout.title.align });
154
217
  }
155
218
  );
156
219
 
220
+
221
+ export function resolveSizeLegendConfig(program, args = {}) {
222
+ validateKeys(args, [...SIZE_OPTIONS, "inheritAppearance"], "createSizeLegend");
223
+ if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
224
+ const layer = resolveSizeLegendPoint(program, args.target);
225
+ const encoding = layer.encoding?.size;
226
+ if (encoding?.scale === undefined) {
227
+ throw new Error(`Point mark "${layer.id}" requires a size encoding.`);
228
+ }
229
+ const scale = requireScale(program, encoding.scale);
230
+ const discrete = isDiscreteSizeScaleType(scale.type);
231
+ if (discrete && (Object.hasOwn(args, "count") || Object.hasOwn(args, "values"))) {
232
+ throw new Error("Discrete size legends do not support count or exact values.");
233
+ }
234
+ const sampling = discrete ? undefined : normalizeLegendSampling(args, {
235
+ operation: "create",
236
+ label: "Size legend"
237
+ });
238
+ const count = discrete ? scale.range.length : undefined;
239
+ return {
240
+ target: layer.id,
241
+ scale: encoding.scale,
242
+ ...normalizeItemLegendLayout({ ...args, itemGap: args.itemGap ?? 40 }),
243
+ title: args.title ?? encoding.field,
244
+ inferredTitle: args.title === undefined,
245
+ domain: scale.domain,
246
+ ...(discrete ? { count } : { sampling }),
247
+ inheritAppearance: args.inheritAppearance === true,
248
+ labels: normalizeLegendTextOptions(args.labels, "createLegend.labels", SIZE_LEGEND_LABELS),
249
+ titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", SIZE_LEGEND_TITLE_STYLE),
250
+ border: normalizeLegendBorder(args.border),
251
+ titleVisible: true
252
+ };
253
+ }
254
+
255
+ export function createSizeLegendFromConfig(program, config) {
256
+ const { radii } = resolveSizeLegendLayout(program, config);
257
+ const count = radii.length;
258
+ const following = new Set(legendResourcePolicies().filter(policy =>
259
+ policy.kind !== "size" && policy.family !== "categorical" &&
260
+ program.guideConfigs.legend?.[policy.kind] !== undefined
261
+ ).flatMap(policy => policy.graphicIds));
262
+ const before = program.graphicSpec.objects.canvas?.children?.find(id => following.has(id));
263
+ const placement = resolveLegendGraphicPlacement(program, before === undefined ? {} : { before });
264
+ let next = program
265
+ .editSemantic({ property: "guide.legend.size.scale", value: config.scale })
266
+ .editSemantic({ property: "guide.legend.size.title", value: config.title })
267
+ ._withLegendConfig("size", config);
268
+ if (config.border !== false) {
269
+ next = next.createGraphics({ id: "sizeLegendBackground", type: "rect",
270
+ ...placement });
271
+ }
272
+ next = next.createGraphics({
273
+ id: "sizeLegendSymbols",
274
+ type: "circle",
275
+ length: count,
276
+ ...placement
277
+ })
278
+ .createGraphics({
279
+ id: "sizeLegendLabels",
280
+ type: "text",
281
+ length: count,
282
+ ...placement
283
+ });
284
+ if (config.titleVisible !== false) {
285
+ next = next.createGraphics({
286
+ id: "sizeLegendTitle",
287
+ type: "text",
288
+ ...placement
289
+ });
290
+ }
291
+ return next.rematerializeSizeLegend();
292
+ }
293
+
157
294
  export const createSizeLegend = action(
158
295
  {
159
296
  op: "createSizeLegend",
160
- description: "Create a quantitative equal-area point-size legend."
297
+ description: "Create an equal-area point-size legend."
161
298
  },
162
299
  function (args = {}) {
163
- validateKeys(args, [...SIZE_OPTIONS, "inheritAppearance"], "createSizeLegend");
164
- const layer = resolveSizeLegendPoint(this, args.target);
165
- const encoding = layer.encoding?.size;
166
- if (encoding?.scale === undefined) {
167
- throw new Error(`Point mark "${layer.id}" requires a size encoding.`);
168
- }
169
- const scale = requireScale(this, encoding.scale, "linear");
170
- const count = args.count ?? 5;
171
- if (!Number.isInteger(count) || count < 2) {
172
- throw new RangeError("Size legend count must be an integer of at least 2.");
173
- }
174
- validateGeneratedItemLimit(count, "Size legend count");
175
- return this
176
- .editSemantic({ property: "guide.legend.size.scale", value: encoding.scale })
177
- .editSemantic({ property: "guide.legend.size.title", value: encoding.field })
178
- ._withLegendConfig("size", {
179
- target: layer.id,
180
- scale: encoding.scale,
181
- title: encoding.field,
182
- inferredTitle: true,
183
- domain: scale.domain,
184
- count,
185
- inheritAppearance: args.inheritAppearance === true
186
- })
187
- .createGraphics({
188
- id: "sizeLegendSymbols",
189
- type: "circle",
190
- length: count,
191
- ...resolveLegendGraphicPlacement(this)
192
- })
193
- .createGraphics({
194
- id: "sizeLegendLabels",
195
- type: "text",
196
- length: count,
197
- ...resolveLegendGraphicPlacement(this)
198
- })
199
- .createGraphics({
200
- id: "sizeLegendTitle",
201
- type: "text",
202
- ...resolveLegendGraphicPlacement(this)
203
- })
204
- .rematerializeSizeLegend();
300
+ const config = resolveSizeLegendConfig(this, args);
301
+ return createSizeLegendFromConfig(this, config);
205
302
  }
206
303
  );
207
304
 
@@ -1,23 +1,36 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import {
3
- validateGeneratedItemLimit,
3
+ validateNonEmptyString,
4
4
  validateKeys
5
5
  } from "../../../core/validation.js";
6
6
  import { mapContinuousScaleValues } from "../../../grammar/scales/index.js";
7
- import { resolveGraphicBounds } from "../../../layout/canvas.js";
7
+ import { resolveLegendItemLayout } from "../../../layout/legendItems.js";
8
8
  import { resolveLegendGraphicPlacement } from
9
9
  "../../../materialization/graphicHierarchy.js";
10
10
  import { findLayer } from "../../../selectors/layers.js";
11
11
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
12
12
  "../../../theme/defaults.js";
13
13
  import {
14
+ assertLegendBoundsInsideCanvas,
15
+ editLegendBackground,
16
+ normalizeItemLegendLayout,
17
+ normalizeLegendBorder,
18
+ normalizeLegendTextOptions,
19
+ normalizeLegendTitleOptions,
20
+ resolveContinuousBounds,
21
+ resolveLegendBackgroundFromBounds,
14
22
  formatContinuousValues,
15
- sampleContinuousValues,
16
23
  selectLegendLayer,
17
24
  styleContinuousText
18
25
  } from "./continuous/common.js";
26
+ import {
27
+ normalizeLegendSampling,
28
+ resolveLegendSampleValues
29
+ } from "./sampling.js";
30
+ import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
19
31
 
20
- const OPTIONS = Object.freeze(["target", "count"]);
32
+ const OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
33
+ "direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
21
34
 
22
35
  export const STROKE_WIDTH_LEGEND_LABELS = Object.freeze({
23
36
  offset: 12,
@@ -63,7 +76,25 @@ function requireScale(program, id) {
63
76
  return scale;
64
77
  }
65
78
 
66
- export const rematerializeStrokeWidthLegend = action(
79
+ export function resolveStrokeWidthLegendLayout(program, config) {
80
+ const effective = resolveEffectiveLegendBlockConfig(program, "strokeWidth", config);
81
+ const scale = requireScale(program, config.scale);
82
+ const { plot, canvas } = resolveContinuousBounds(program);
83
+ const values = resolveLegendSampleValues(effective, scale, "Stroke-width legend");
84
+ const widths = mapContinuousScaleValues(values, scale);
85
+ const labels = formatContinuousValues(
86
+ values,
87
+ scale.domain,
88
+ "quantitative",
89
+ effective.labels.format
90
+ );
91
+ const layout = resolveLegendItemLayout(plot, effective, labels, { width: 32, height: 0, strokeWidth: widths });
92
+ assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Stroke-width legend layout", effective);
93
+ const background = resolveLegendBackgroundFromBounds(layout.bounds, effective.border, canvas, "Stroke-width legend", effective);
94
+ return { ...layout, widths, labels, background, config: effective };
95
+ }
96
+
97
+ export const rematerializeStrokeWidthLegend = /* @__PURE__ */ action(
67
98
  {
68
99
  op: "rematerializeStrokeWidthLegend",
69
100
  description: "Rematerialize a quantitative stroke-width legend."
@@ -86,102 +117,119 @@ export const rematerializeStrokeWidthLegend = action(
86
117
  throw new Error("Stroke-width legend target requires a strokeWidth encoding.");
87
118
  }
88
119
  const scale = requireScale(this, encoding.scale);
89
- const plot = resolveGraphicBounds(this);
90
- if (plot === undefined) {
91
- throw new Error("Stroke-width legend requires resolved plot bounds.");
92
- }
93
- const values = sampleContinuousValues(scale.domain, config.count);
94
- const widths = mapContinuousScaleValues(values, scale);
95
- const originX = plot.x + plot.width + 30;
96
- const titleY = plot.y + 28;
97
- const y = values.map((_, index) => titleY + 34 + index * 32);
98
120
  const title = config.inferredTitle === true ? encoding.field : config.title;
121
+ const currentConfig = { ...config, scale: encoding.scale, title };
122
+ const layout = resolveStrokeWidthLegendLayout(this, currentConfig);
123
+ const effective = layout.config;
124
+ const { itemY: y, widths } = layout;
99
125
  let next = this
100
126
  .editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
101
127
  .editSemantic({ property: "guide.legend.strokeWidth.title", value: title })
102
128
  ._withLegendConfig("strokeWidth", {
103
- ...config,
104
- scale: encoding.scale,
105
- title,
129
+ ...currentConfig,
106
130
  domain: scale.domain
107
131
  })
108
- .editGraphics({ target: "strokeWidthLegendSymbols", property: "length", value: values.length })
109
- .editGraphics({ target: "strokeWidthLegendSymbols", property: "x1", value: values.map(() => originX) })
110
- .editGraphics({ target: "strokeWidthLegendSymbols", property: "x2", value: values.map(() => originX + 32) })
132
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "length", value: widths.length })
133
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "x1", value: layout.symbolX })
134
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "x2", value: layout.symbolX.map(x => x + 32) })
111
135
  .editGraphics({ target: "strokeWidthLegendSymbols", property: "y1", value: y })
112
136
  .editGraphics({ target: "strokeWidthLegendSymbols", property: "y2", value: y })
113
- .editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke", value: DEFAULT_COLORS.mark })
137
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke",
138
+ value: effective.blockSymbol?.stroke ?? DEFAULT_COLORS.mark })
114
139
  .editGraphics({ target: "strokeWidthLegendSymbols", property: "strokeWidth", value: widths })
115
- .editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: values.length })
140
+ .editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: widths.length })
116
141
  .editGraphics({
117
142
  target: "strokeWidthLegendLabels",
118
143
  property: "x",
119
- value: values.map(() => originX + 32 + config.labels.offset)
144
+ value: layout.labelX
120
145
  })
121
146
  .editGraphics({ target: "strokeWidthLegendLabels", property: "y", value: y })
122
147
  .editGraphics({
123
148
  target: "strokeWidthLegendLabels",
124
149
  property: "text",
125
- value: formatContinuousValues(values, scale.domain, "quantitative")
150
+ value: layout.labels
126
151
  });
127
- next = styleContinuousText(next, "strokeWidthLegendLabels", config.labels);
128
- if (config.titleVisible === false) return next;
152
+ next = editLegendBackground(next, "strokeWidthLegendBackground", layout.background, config.border);
153
+ if (effective.blockSymbol?.opacity !== undefined) {
154
+ next = next.editGraphics({ target: "strokeWidthLegendSymbols", property: "opacity",
155
+ value: effective.blockSymbol.opacity });
156
+ }
157
+ next = styleContinuousText(next, "strokeWidthLegendLabels", effective.labels);
158
+ if (effective.titleVisible === false) return next;
129
159
  next = next
130
- .editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: originX })
131
- .editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: titleY })
132
- .editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
133
- return styleContinuousText(next, "strokeWidthLegendTitle", config.titleStyle);
160
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: layout.title.x })
161
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: layout.title.y })
162
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: effective.title });
163
+ return styleContinuousText(next, "strokeWidthLegendTitle", effective.titleStyle, { align: layout.title.align });
134
164
  }
135
165
  );
136
166
 
137
- export const createStrokeWidthLegend = action(
167
+
168
+ export function resolveStrokeWidthLegendConfig(program, args = {}) {
169
+ validateKeys(args, OPTIONS, "createStrokeWidthLegend");
170
+ if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
171
+ const layer = resolveLayer(program, args.target);
172
+ const encoding = layer.encoding.strokeWidth;
173
+ requireScale(program, encoding.scale);
174
+ const sampling = normalizeLegendSampling(args, {
175
+ operation: "create",
176
+ label: "Stroke-width legend"
177
+ });
178
+ return {
179
+ target: layer.id,
180
+ scale: encoding.scale,
181
+ ...normalizeItemLegendLayout({ ...args, itemGap: args.itemGap ?? 32 }),
182
+ title: args.title ?? encoding.field,
183
+ inferredTitle: args.title === undefined,
184
+ sampling,
185
+ labels: normalizeLegendTextOptions(args.labels, "createLegend.labels", STROKE_WIDTH_LEGEND_LABELS),
186
+ titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", STROKE_WIDTH_LEGEND_TITLE_STYLE),
187
+ border: normalizeLegendBorder(args.border),
188
+ titleVisible: true
189
+ };
190
+ }
191
+
192
+ export function createStrokeWidthLegendFromConfig(program, config) {
193
+ const layout = resolveStrokeWidthLegendLayout(program, config);
194
+ const count = layout.widths.length;
195
+ let next = program
196
+ .editSemantic({ property: "guide.legend.strokeWidth.scale", value: config.scale })
197
+ .editSemantic({ property: "guide.legend.strokeWidth.title", value: config.title })
198
+ ._withLegendConfig("strokeWidth", config);
199
+ if (config.border !== false) {
200
+ next = next.createGraphics({ id: "strokeWidthLegendBackground", type: "rect",
201
+ ...resolveLegendGraphicPlacement(program) });
202
+ }
203
+ next = next.createGraphics({
204
+ id: "strokeWidthLegendSymbols",
205
+ type: "line",
206
+ length: count,
207
+ ...resolveLegendGraphicPlacement(program)
208
+ })
209
+ .createGraphics({
210
+ id: "strokeWidthLegendLabels",
211
+ type: "text",
212
+ length: count,
213
+ ...resolveLegendGraphicPlacement(program)
214
+ });
215
+ if (config.titleVisible !== false) {
216
+ next = next.createGraphics({
217
+ id: "strokeWidthLegendTitle",
218
+ type: "text",
219
+ ...resolveLegendGraphicPlacement(program)
220
+ });
221
+ }
222
+ return next.rematerializeStrokeWidthLegend();
223
+ }
224
+
225
+ export const createStrokeWidthLegend = /* @__PURE__ */ action(
138
226
  {
139
227
  op: "createStrokeWidthLegend",
140
228
  description: "Create a quantitative stroke-width legend."
141
229
  },
142
230
  function (args = {}) {
143
- validateKeys(args, OPTIONS, "createStrokeWidthLegend");
144
- const layer = resolveLayer(this, args.target);
145
- const encoding = layer.encoding.strokeWidth;
146
- requireScale(this, encoding.scale);
147
- const count = args.count ?? 5;
148
- if (!Number.isInteger(count) || count < 2) {
149
- throw new RangeError(
150
- "Stroke-width legend count must be an integer of at least 2."
151
- );
152
- }
153
- validateGeneratedItemLimit(count, "Stroke-width legend count");
154
- return this
155
- .editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
156
- .editSemantic({ property: "guide.legend.strokeWidth.title", value: encoding.field })
157
- ._withLegendConfig("strokeWidth", {
158
- target: layer.id,
159
- scale: encoding.scale,
160
- title: encoding.field,
161
- inferredTitle: true,
162
- count,
163
- labels: { ...STROKE_WIDTH_LEGEND_LABELS },
164
- titleStyle: { ...STROKE_WIDTH_LEGEND_TITLE_STYLE },
165
- titleVisible: true
166
- })
167
- .createGraphics({
168
- id: "strokeWidthLegendSymbols",
169
- type: "line",
170
- length: count,
171
- ...resolveLegendGraphicPlacement(this)
172
- })
173
- .createGraphics({
174
- id: "strokeWidthLegendLabels",
175
- type: "text",
176
- length: count,
177
- ...resolveLegendGraphicPlacement(this)
178
- })
179
- .createGraphics({
180
- id: "strokeWidthLegendTitle",
181
- type: "text",
182
- ...resolveLegendGraphicPlacement(this)
183
- })
184
- .rematerializeStrokeWidthLegend();
231
+ const config = resolveStrokeWidthLegendConfig(this, args);
232
+ return createStrokeWidthLegendFromConfig(this, config);
185
233
  }
186
234
  );
187
235