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
@@ -0,0 +1,525 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import { isPlainObject } from "../../../../core/immutable.js";
3
+ import { validateKeys, validateOptionObject } from
4
+ "../../../../core/validation.js";
5
+ import { formatDiscretizedIntervals } from
6
+ "../../../../grammar/scales/index.js";
7
+ import { inverseLerp, interpolateNumber } from
8
+ "../../../../grammar/numeric.js";
9
+ import { resolveLegendItemLayout } from "../../../../layout/legendItems.js";
10
+ import { resolveLegendGraphicPlacement } from
11
+ "../../../../materialization/graphicHierarchy.js";
12
+ import { mapScaleConsumerValues } from
13
+ "../../../../materialization/scales/map.js";
14
+ import { findLayer } from "../../../../selectors/layers.js";
15
+ import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
16
+ import {
17
+ assertLegendBoundsInsideCanvas,
18
+ editGraphicProperties,
19
+ editLegendBackground,
20
+ formatContinuousValues,
21
+ normalizeContinuousLegend,
22
+ requireResolvedLegendScale,
23
+ resolveContinuousBounds,
24
+ resolveContinuousColorLayer,
25
+ resolveLegendBackgroundFromBounds,
26
+ resolveLegendTextBounds,
27
+ sampleContinuousValues,
28
+ styleContinuousText,
29
+ validateNonNegative,
30
+ validatePositive
31
+ } from "./common.js";
32
+ import { DEFAULT_GRADIENT_SIZE } from "./gradient.js";
33
+ import { normalizeIntervalLegend } from "./interval.js";
34
+ import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
35
+
36
+ const GRADIENT_OPTIONS = Object.freeze(["length", "thickness"]);
37
+
38
+ function requireChannels(args, operation) {
39
+ if (
40
+ !Array.isArray(args.channels) ||
41
+ args.channels.length !== 1 ||
42
+ args.channels[0] !== "stroke"
43
+ ) {
44
+ throw new Error(`${operation} requires channels: ["stroke"].`);
45
+ }
46
+ }
47
+
48
+ function strokeWidth(program, target) {
49
+ const config = program.markConfigs[target] ?? {};
50
+ return config.barAppearance?.strokeWidth ?? config.strokeWidth ?? ({
51
+ point: 1,
52
+ line: 2,
53
+ area: 1,
54
+ bar: 0.5,
55
+ rect: 1,
56
+ arc: 1,
57
+ rule: 2,
58
+ tick: 2
59
+ }[findLayer(program, target)?.mark?.type] ?? 1);
60
+ }
61
+
62
+ function sampleFill(program, target) {
63
+ const graphic = program.graphicSpec.objects[target];
64
+ const properties = graphic?.items?.[0]?.properties ?? graphic?.properties;
65
+ return properties?.fill ?? DEFAULT_COLORS.mark;
66
+ }
67
+
68
+ function resolveGradientConfig(program, stored) {
69
+ const layer = resolveContinuousColorLayer(program, stored.target, "stroke");
70
+ const encoding = layer.encoding.stroke;
71
+ if (!["quantitative", "temporal"].includes(encoding.fieldType)) {
72
+ throw new Error("Stroke gradient legend requires quantitative or temporal stroke.");
73
+ }
74
+ const scale = requireResolvedLegendScale(program, encoding.scale, "sequential");
75
+ return {
76
+ encoding,
77
+ scale,
78
+ config: {
79
+ ...stored,
80
+ target: layer.id,
81
+ scale: encoding.scale,
82
+ fieldType: encoding.fieldType,
83
+ title: stored.inferredTitle ? encoding.field : stored.title,
84
+ domain: scale.domain
85
+ }
86
+ };
87
+ }
88
+
89
+ function resolveGradientLayout(program, config, scale) {
90
+ const { plot, canvas } = resolveContinuousBounds(program);
91
+ const vertical = ["right", "left"].includes(config.position);
92
+ const { length, thickness } = config.gradient;
93
+ const titleGap = config.blockGap ?? 12;
94
+ let x;
95
+ let y;
96
+ if (config.position === "right") {
97
+ x = plot.x + plot.width + config.offset;
98
+ y = plot.y + 46 + titleGap - 12;
99
+ } else if (config.position === "left") {
100
+ x = plot.x - config.offset - thickness;
101
+ y = plot.y + 46 + titleGap - 12;
102
+ } else {
103
+ x = config.align === "left" ? plot.x
104
+ : config.align === "right" ? plot.x + plot.width - length
105
+ : plot.x + (plot.width - length) / 2;
106
+ y = config.position === "top"
107
+ ? plot.y - config.offset - thickness - config.labels.offset -
108
+ config.labels.fontSize
109
+ : plot.y + plot.height + config.offset +
110
+ (config.titleVisible === false ? 0 : config.titleStyle.fontSize + titleGap);
111
+ }
112
+ const title = vertical
113
+ ? { x, y: plot.y + 20, align: "left" }
114
+ : { x: x + length / 2, y: y - titleGap - config.titleStyle.fontSize / 2,
115
+ align: "center" };
116
+ const values = [...sampleContinuousValues(scale.domain, config.count)];
117
+ if (scale.midpoint !== undefined && !values.includes(scale.midpoint)) {
118
+ values.push(scale.midpoint);
119
+ values.sort((a, b) => scale.domain[1] > scale.domain[0] ? a - b : b - a);
120
+ }
121
+ const texts = formatContinuousValues(
122
+ values,
123
+ scale.domain,
124
+ config.fieldType,
125
+ config.labels.format
126
+ );
127
+ const fractions = values.map((value, index) => scale.midpoint === undefined
128
+ ? index / (values.length - 1)
129
+ : inverseLerp(value, ...scale.domain));
130
+ const labels = vertical
131
+ ? fractions.map(fraction => ({
132
+ x: config.position === "right"
133
+ ? x + thickness + config.labels.offset
134
+ : x - config.labels.offset,
135
+ y: y + length * (1 - fraction),
136
+ align: config.position === "right" ? "left" : "right"
137
+ }))
138
+ : fractions.map(fraction => ({
139
+ x: x + length * fraction,
140
+ y: y + thickness + config.labels.offset + config.labels.fontSize / 2,
141
+ align: "center"
142
+ }));
143
+ const ticks = vertical
144
+ ? labels.map(label => ({
145
+ x1: config.position === "right" ? x + thickness : x,
146
+ y1: label.y,
147
+ x2: config.position === "right" ? x + thickness + 6 : x - 6,
148
+ y2: label.y
149
+ }))
150
+ : labels.map(label => ({
151
+ x1: label.x,
152
+ y1: y + thickness,
153
+ x2: label.x,
154
+ y2: y + thickness + 6
155
+ }));
156
+ const occupied = [
157
+ { left: x, right: x + (vertical ? thickness : length), top: y,
158
+ bottom: y + (vertical ? length : thickness) },
159
+ ...ticks.map(tick => ({
160
+ left: Math.min(tick.x1, tick.x2) - 0.5,
161
+ right: Math.max(tick.x1, tick.x2) + 0.5,
162
+ top: Math.min(tick.y1, tick.y2) - 0.5,
163
+ bottom: Math.max(tick.y1, tick.y2) + 0.5
164
+ })),
165
+ ...labels.map((label, index) =>
166
+ resolveLegendTextBounds(label, texts[index], config.labels)),
167
+ ...(config.titleVisible === false ? [] : [
168
+ resolveLegendTextBounds(title, config.title, config.titleStyle)
169
+ ])
170
+ ];
171
+ assertLegendBoundsInsideCanvas(occupied, canvas, "Stroke gradient legend layout", config);
172
+ return {
173
+ vertical,
174
+ x,
175
+ y,
176
+ length,
177
+ thickness,
178
+ values,
179
+ texts,
180
+ labels,
181
+ ticks,
182
+ title,
183
+ background: resolveLegendBackgroundFromBounds(
184
+ occupied,
185
+ config.border,
186
+ canvas,
187
+ "Stroke gradient legend",
188
+ config
189
+ )
190
+ };
191
+ }
192
+
193
+ export function resolveStrokeGradientLegendCreation(program, args = {}) {
194
+ requireChannels(args, "Stroke gradient legend");
195
+ const config = normalizeContinuousLegend(args, "gradient");
196
+ if (args.gradient !== undefined && !isPlainObject(args.gradient)) {
197
+ throw new TypeError("createLegend.gradient must be a plain object.");
198
+ }
199
+ validateKeys(args.gradient ?? {}, GRADIENT_OPTIONS, "createLegend.gradient");
200
+ config.gradient = {
201
+ length: args.gradient?.length ?? DEFAULT_GRADIENT_SIZE.length,
202
+ thickness: args.gradient?.thickness ?? DEFAULT_GRADIENT_SIZE.thickness
203
+ };
204
+ config.titleVisible = true;
205
+ validatePositive(config.gradient.length, "Gradient length");
206
+ validatePositive(config.gradient.thickness, "Gradient thickness");
207
+ return resolveGradientConfig(program, config);
208
+ }
209
+
210
+ export const rematerializeStrokeGradientLegend = action(
211
+ {
212
+ op: "rematerializeStrokeGradientLegend",
213
+ description: "Rematerialize a continuous stroke gradient legend."
214
+ },
215
+ function (args = {}) {
216
+ validateKeys(args, [], "rematerializeStrokeGradientLegend");
217
+ const stored = this.guideConfigs.legend?.strokeGradient;
218
+ if (stored === undefined) {
219
+ throw new Error("Stroke gradient legend requires stored configuration.");
220
+ }
221
+ const { encoding, scale, config: currentConfig } = resolveGradientConfig(this, stored);
222
+ const config = resolveEffectiveLegendBlockConfig(this, "strokeGradient", currentConfig);
223
+ const layout = resolveGradientLayout(this, config, scale);
224
+ const fill = sampleFill(this, config.target);
225
+ const width = strokeWidth(this, config.target);
226
+ const stripCount = 60;
227
+ const stripSize = layout.length / stripCount;
228
+ const strips = Array.from({ length: stripCount }, (_, index) => {
229
+ const fraction = (index + 0.5) / stripCount;
230
+ const position = layout.vertical ? 1 - fraction : fraction;
231
+ const samplingScale = scale.midpoint === undefined
232
+ ? { ...scale, domain: [0, 1] }
233
+ : scale;
234
+ const value = scale.midpoint === undefined
235
+ ? position
236
+ : interpolateNumber(...scale.domain, position);
237
+ const [color] = mapScaleConsumerValues([value], samplingScale, "stroke");
238
+ return {
239
+ x: layout.x + (layout.vertical ? 0 : index * stripSize),
240
+ y: layout.y + (layout.vertical ? index * stripSize : 0),
241
+ width: layout.vertical ? layout.thickness : stripSize,
242
+ height: layout.vertical ? stripSize : layout.thickness,
243
+ fill,
244
+ stroke: color,
245
+ strokeWidth: width
246
+ };
247
+ });
248
+ let next = this
249
+ .editSemantic({ property: "guide.legend.stroke.scale", value: encoding.scale })
250
+ .editSemantic({ property: "guide.legend.stroke.title", value: config.title })
251
+ ._withLegendConfig("strokeGradient", currentConfig);
252
+ next = editLegendBackground(
253
+ next,
254
+ "strokeGradientBackground",
255
+ layout.background,
256
+ config.border
257
+ );
258
+ next = editGraphicProperties(next, "strokeGradientStrips", {
259
+ length: strips.length,
260
+ x: strips.map(strip => strip.x),
261
+ y: strips.map(strip => strip.y),
262
+ width: strips.map(strip => strip.width),
263
+ height: strips.map(strip => strip.height),
264
+ fill: strips.map(strip => strip.fill),
265
+ stroke: strips.map(strip => strip.stroke),
266
+ strokeWidth: strips.map(strip => strip.strokeWidth)
267
+ });
268
+ next = editGraphicProperties(next, "strokeGradientTicks", {
269
+ length: layout.ticks.length,
270
+ x1: layout.ticks.map(tick => tick.x1),
271
+ y1: layout.ticks.map(tick => tick.y1),
272
+ x2: layout.ticks.map(tick => tick.x2),
273
+ y2: layout.ticks.map(tick => tick.y2),
274
+ stroke: DEFAULT_COLORS.mutedText,
275
+ strokeWidth: 1
276
+ });
277
+ next = editGraphicProperties(next, "strokeGradientLabels", {
278
+ length: layout.labels.length,
279
+ x: layout.labels.map(label => label.x),
280
+ y: layout.labels.map(label => label.y),
281
+ text: layout.texts
282
+ });
283
+ next = styleContinuousText(next, "strokeGradientLabels", config.labels, {
284
+ align: layout.labels[0].align
285
+ });
286
+ if (config.titleVisible === false) return next;
287
+ next = editGraphicProperties(next, "strokeGradientTitle", {
288
+ x: layout.title.x,
289
+ y: layout.title.y,
290
+ text: config.title
291
+ });
292
+ return styleContinuousText(next, "strokeGradientTitle", config.titleStyle, {
293
+ align: layout.title.align
294
+ });
295
+ }
296
+ );
297
+
298
+ export function createStrokeGradientLegendFromConfig(program, config) {
299
+ const resolved = resolveGradientConfig(program, config);
300
+ resolveGradientLayout(program, resolved.config, resolved.scale);
301
+ let next = program
302
+ .editSemantic({
303
+ property: "guide.legend.stroke.scale",
304
+ value: resolved.encoding.scale
305
+ })
306
+ .editSemantic({
307
+ property: "guide.legend.stroke.title",
308
+ value: resolved.config.title
309
+ })
310
+ ._withLegendConfig("strokeGradient", resolved.config);
311
+ const placement = resolveLegendGraphicPlacement(next);
312
+ if (resolved.config.border !== false) {
313
+ next = next.createGraphics({
314
+ id: "strokeGradientBackground",
315
+ type: "rect",
316
+ ...placement
317
+ });
318
+ }
319
+ next = next
320
+ .createGraphics({
321
+ id: "strokeGradientStrips",
322
+ type: "rect",
323
+ length: 0,
324
+ ...resolveLegendGraphicPlacement(next, resolved.config.border === false
325
+ ? {}
326
+ : { after: "strokeGradientBackground" })
327
+ })
328
+ .createGraphics({ id: "strokeGradientTicks", type: "line", length: 0,
329
+ ...resolveLegendGraphicPlacement(next) })
330
+ .createGraphics({ id: "strokeGradientLabels", type: "text", length: 0,
331
+ ...resolveLegendGraphicPlacement(next) });
332
+ if (resolved.config.titleVisible !== false) {
333
+ next = next.createGraphics({ id: "strokeGradientTitle", type: "text",
334
+ ...resolveLegendGraphicPlacement(next) });
335
+ }
336
+ return next.rematerializeStrokeGradientLegend();
337
+ }
338
+
339
+ export const createStrokeGradientLegend = action(
340
+ {
341
+ op: "createStrokeGradientLegend",
342
+ description: "Create a continuous stroke gradient legend."
343
+ },
344
+ function (args = {}) {
345
+ const resolved = resolveStrokeGradientLegendCreation(this, args);
346
+ if (this.guideConfigs.legend?.strokeGradient !== undefined) {
347
+ throw new Error("createStrokeGradientLegend requires a missing stroke legend.");
348
+ }
349
+ return createStrokeGradientLegendFromConfig(this, resolved.config);
350
+ }
351
+ );
352
+
353
+ export function resolveStrokeIntervalConfig(program, stored) {
354
+ const layer = resolveContinuousColorLayer(program, stored.target, "stroke");
355
+ const encoding = layer.encoding.stroke;
356
+ if (encoding.fieldType !== "quantitative") {
357
+ throw new Error("Stroke interval legend requires quantitative stroke.");
358
+ }
359
+ const scale = program.resolvedScales[encoding.scale];
360
+ if (!["quantize", "quantile", "threshold"].includes(scale?.type)) {
361
+ throw new Error(
362
+ `Stroke interval legend requires a resolved discretized scale "${encoding.scale}".`
363
+ );
364
+ }
365
+ return { encoding, scale, config: {
366
+ ...stored,
367
+ target: layer.id,
368
+ scale: encoding.scale,
369
+ title: stored.inferredTitle ? encoding.field : stored.title,
370
+ ...(stored.inferredStrokeWidth === true ? {
371
+ symbol: { ...stored.symbol, strokeWidth: strokeWidth(program, layer.id) }
372
+ } : {})
373
+ } };
374
+ }
375
+
376
+ export function resolveStrokeIntervalLayout(program, config, scale) {
377
+ const { plot, canvas } = resolveContinuousBounds(program);
378
+ const labels = formatDiscretizedIntervals(scale.thresholds, config.labels.format);
379
+ const layout = resolveLegendItemLayout(plot, config, labels, config.symbol);
380
+ assertLegendBoundsInsideCanvas(
381
+ layout.bounds,
382
+ canvas,
383
+ "Stroke interval legend layout",
384
+ config
385
+ );
386
+ return {
387
+ labels,
388
+ ...layout,
389
+ background: resolveLegendBackgroundFromBounds(
390
+ layout.bounds,
391
+ config.border,
392
+ canvas,
393
+ "Stroke interval legend",
394
+ config
395
+ )
396
+ };
397
+ }
398
+
399
+ export function resolveStrokeIntervalLegendCreation(program, args = {}) {
400
+ requireChannels(args, "Stroke interval legend");
401
+ const config = normalizeIntervalLegend({ ...args, channels: undefined });
402
+ const layer = resolveContinuousColorLayer(program, config.target, "stroke");
403
+ config.symbol = {
404
+ ...config.symbol,
405
+ ...(args.symbol?.strokeWidth === undefined
406
+ ? { strokeWidth: strokeWidth(program, layer.id) }
407
+ : {})
408
+ };
409
+ config.inferredStrokeWidth = args.symbol?.strokeWidth === undefined;
410
+ validateNonNegative(config.symbol.strokeWidth, "Legend symbol strokeWidth");
411
+ return resolveStrokeIntervalConfig(program, config);
412
+ }
413
+
414
+ export const rematerializeStrokeIntervalLegend = action(
415
+ {
416
+ op: "rematerializeStrokeIntervalLegend",
417
+ description: "Rematerialize a discretized stroke interval legend."
418
+ },
419
+ function (args = {}) {
420
+ validateKeys(args, [], "rematerializeStrokeIntervalLegend");
421
+ const stored = this.guideConfigs.legend?.strokeInterval;
422
+ if (stored === undefined) {
423
+ throw new Error("Stroke interval legend requires stored configuration.");
424
+ }
425
+ const { encoding, scale, config: currentConfig } = resolveStrokeIntervalConfig(this, stored);
426
+ const config = resolveEffectiveLegendBlockConfig(this, "strokeInterval", currentConfig);
427
+ const layout = resolveStrokeIntervalLayout(this, config, scale);
428
+ const fill = config.blockSymbol?.fill ?? sampleFill(this, config.target);
429
+ let next = this
430
+ .editSemantic({ property: "guide.legend.stroke.scale", value: encoding.scale })
431
+ .editSemantic({ property: "guide.legend.stroke.title", value: config.title })
432
+ ._withLegendConfig("strokeInterval", currentConfig);
433
+ next = editGraphicProperties(next, "strokeIntervalSymbols", {
434
+ length: scale.range.length,
435
+ x: layout.symbolX,
436
+ y: layout.itemY.map(value => value - config.symbol.height / 2),
437
+ width: config.symbol.width,
438
+ height: config.symbol.height,
439
+ fill,
440
+ stroke: scale.range,
441
+ strokeWidth: config.symbol.strokeWidth,
442
+ ...(config.blockSymbol?.opacity === undefined
443
+ ? {}
444
+ : { opacity: config.blockSymbol.opacity })
445
+ });
446
+ next = editGraphicProperties(next, "strokeIntervalLabels", {
447
+ length: layout.labels.length,
448
+ x: layout.labelX,
449
+ y: layout.itemY,
450
+ text: layout.labels
451
+ });
452
+ next = editLegendBackground(
453
+ next,
454
+ "strokeIntervalBackground",
455
+ layout.background,
456
+ config.border
457
+ );
458
+ next = styleContinuousText(next, "strokeIntervalLabels", config.labels);
459
+ if (config.titleVisible === false) return next;
460
+ next = editGraphicProperties(next, "strokeIntervalTitle", {
461
+ x: layout.title.x,
462
+ y: layout.title.y,
463
+ text: config.title
464
+ });
465
+ return styleContinuousText(next, "strokeIntervalTitle", config.titleStyle, {
466
+ align: layout.title.align
467
+ });
468
+ }
469
+ );
470
+
471
+ export function createStrokeIntervalLegendFromConfig(program, config) {
472
+ const resolved = resolveStrokeIntervalConfig(program, config);
473
+ resolveStrokeIntervalLayout(program, resolved.config, resolved.scale);
474
+ let next = program
475
+ .editSemantic({
476
+ property: "guide.legend.stroke.scale",
477
+ value: resolved.encoding.scale
478
+ })
479
+ .editSemantic({
480
+ property: "guide.legend.stroke.title",
481
+ value: resolved.config.title
482
+ })
483
+ ._withLegendConfig("strokeInterval", resolved.config);
484
+ const placement = resolveLegendGraphicPlacement(next);
485
+ if (resolved.config.border !== false) {
486
+ next = next.createGraphics({ id: "strokeIntervalBackground", type: "rect",
487
+ ...placement });
488
+ }
489
+ next = next
490
+ .createGraphics({ id: "strokeIntervalSymbols", type: "rect", length: 0,
491
+ ...resolveLegendGraphicPlacement(next, resolved.config.border === false
492
+ ? {}
493
+ : { after: "strokeIntervalBackground" }) })
494
+ .createGraphics({ id: "strokeIntervalLabels", type: "text", length: 0,
495
+ ...placement });
496
+ if (resolved.config.titleVisible !== false) {
497
+ next = next.createGraphics({ id: "strokeIntervalTitle", type: "text",
498
+ ...placement });
499
+ }
500
+ return next.rematerializeStrokeIntervalLegend();
501
+ }
502
+
503
+ export const createStrokeIntervalLegend = action(
504
+ {
505
+ op: "createStrokeIntervalLegend",
506
+ description: "Create a discretized stroke interval legend."
507
+ },
508
+ function (args = {}) {
509
+ validateOptionObject(args, undefined, "createStrokeIntervalLegend");
510
+ const resolved = resolveStrokeIntervalLegendCreation(this, args);
511
+ if (this.guideConfigs.legend?.strokeInterval !== undefined) {
512
+ throw new Error("createStrokeIntervalLegend requires a missing stroke legend.");
513
+ }
514
+ return createStrokeIntervalLegendFromConfig(this, resolved.config);
515
+ }
516
+ );
517
+
518
+ export function registerStrokeColorLegendActions(ProgramClass) {
519
+ Object.assign(ProgramClass.prototype, {
520
+ createStrokeGradientLegend,
521
+ rematerializeStrokeGradientLegend,
522
+ createStrokeIntervalLegend,
523
+ rematerializeStrokeIntervalLegend
524
+ });
525
+ }
@@ -0,0 +1,40 @@
1
+ import { resolveCategoricalLegendConfig } from "./categorical/actions.js";
2
+ import { resolveGradientLegendCreation } from "./continuous/gradient.js";
3
+ import { resolveOpacityLegendCreation } from "./continuous/opacity.js";
4
+ import { normalizeIntervalLegend, resolveIntervalConfig } from "./continuous/interval.js";
5
+ import { resolveSizeLegendConfig } from "./size.js";
6
+ import { resolveStrokeWidthLegendConfig } from "./strokeWidth.js";
7
+ import {
8
+ resolveStrokeGradientLegendCreation,
9
+ resolveStrokeIntervalLegendCreation
10
+ } from "./continuous/stroke.js";
11
+
12
+ export function resolveLegendStepConfig(program, step) {
13
+ switch (step.op) {
14
+ case "createCategoricalLegend": {
15
+ const config = resolveCategoricalLegendConfig(program, step.args);
16
+ return { kind: config.kind, config };
17
+ }
18
+ case "createGradientLegend":
19
+ return { kind: "gradient", config: resolveGradientLegendCreation(program, step.args).config };
20
+ case "createOpacityLegend":
21
+ return { kind: "opacity", config: resolveOpacityLegendCreation(program, step.args).config };
22
+ case "createIntervalLegend":
23
+ return { kind: "interval", config: resolveIntervalConfig(program, normalizeIntervalLegend(step.args)).config };
24
+ case "createSizeLegend":
25
+ return { kind: "size", config: resolveSizeLegendConfig(program, step.args) };
26
+ case "createStrokeWidthLegend":
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
+ };
38
+ default: throw new Error(`Unsupported legend creation owner "${step.op}".`);
39
+ }
40
+ }