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
@@ -5,6 +5,15 @@ import {
5
5
  positive,
6
6
  validateKeys
7
7
  } from "./validation.js";
8
+ import {
9
+ requestedRectStyleDetails,
10
+ RECT_STYLE_PROPERTIES
11
+ } from "../../../../grammar/roundedRect.js";
12
+ import {
13
+ requestedStrokeDetails,
14
+ STROKE_STYLE_PROPERTIES
15
+ } from "../../../../grammar/strokeStyle.js";
16
+ import { findLayerMatching } from "../../../../selectors/layers.js";
8
17
 
9
18
  const LAYER_OPTIONS = Object.freeze({
10
19
  line: Object.freeze(["type", "length", "lineWidth"]),
@@ -24,6 +33,102 @@ const LAYER_OPTIONS = Object.freeze({
24
33
  "strokeWidth"
25
34
  ])
26
35
  });
36
+ const INTERNAL_LAYER_OPTIONS = Object.freeze({
37
+ line: Object.freeze([...LAYER_OPTIONS.line, ...STROKE_STYLE_PROPERTIES]),
38
+ point: Object.freeze([...LAYER_OPTIONS.point, ...STROKE_STYLE_PROPERTIES]),
39
+ swatch: Object.freeze([...LAYER_OPTIONS.swatch, ...RECT_STYLE_PROPERTIES])
40
+ });
41
+
42
+ export function resolveLegendSymbol(program, layer, channels, requested, kind) {
43
+ if (requested !== undefined && requested !== "auto") return requested;
44
+ const config = program.markConfigs[layer.id] ?? {};
45
+ const bar = config.barAppearance ?? {};
46
+ const styleOwner = layer.mark.type === "bar" ? bar : config;
47
+ const strokeDetails = requestedStrokeDetails(styleOwner, "Legend source");
48
+ const rectDetails = ["bar", "rect"].includes(layer.mark.type)
49
+ ? requestedRectStyleDetails(styleOwner, "Legend source")
50
+ : strokeDetails;
51
+ if (channels?.length === 1 && channels[0] === "stroke") {
52
+ const strokeWidth = bar.strokeWidth ?? config.strokeWidth ?? ({
53
+ point: 1,
54
+ line: 2,
55
+ area: 1,
56
+ bar: 0.5,
57
+ rect: 1,
58
+ arc: 1,
59
+ rule: 2,
60
+ tick: 2
61
+ }[layer.mark.type] ?? 1);
62
+ if (["line", "rule", "tick"].includes(layer.mark.type)) {
63
+ return { layers: [{
64
+ type: "line",
65
+ length: 32,
66
+ lineWidth: strokeWidth,
67
+ ...strokeDetails
68
+ }] };
69
+ }
70
+ if (layer.mark.type === "point") {
71
+ return { layers: [{
72
+ type: "point",
73
+ size: 5,
74
+ fill: config.fill ?? "#4c78a8",
75
+ stroke: "white",
76
+ strokeWidth,
77
+ ...strokeDetails
78
+ }] };
79
+ }
80
+ return { layers: [{
81
+ type: "swatch",
82
+ width: 14,
83
+ height: 12,
84
+ stroke: "white",
85
+ strokeWidth,
86
+ ...rectDetails
87
+ }] };
88
+ }
89
+ if (layer.mark?.type === "point" && channels?.includes("shape")) {
90
+ const color = channels.includes("color") ? layer.encoding?.color : undefined;
91
+ const matchingLine = color?.scale === undefined
92
+ ? undefined
93
+ : findLayerMatching(program, candidate =>
94
+ candidate.mark?.type === "line" &&
95
+ candidate.encoding?.color?.field === color.field &&
96
+ candidate.encoding?.color?.scale === color.scale
97
+ );
98
+ const lineDetails = matchingLine === undefined
99
+ ? {}
100
+ : requestedStrokeDetails(
101
+ program.markConfigs[matchingLine.id] ?? {},
102
+ "Legend line source"
103
+ );
104
+ return { layers: [
105
+ ...(matchingLine !== undefined ? [{
106
+ type: "line",
107
+ length: 32,
108
+ lineWidth: 3,
109
+ ...lineDetails
110
+ }] : []),
111
+ {
112
+ type: "point",
113
+ size: Math.sqrt(64 / Math.PI),
114
+ stroke: "white",
115
+ strokeWidth: 0,
116
+ ...strokeDetails
117
+ }
118
+ ] };
119
+ }
120
+ if (Object.keys(rectDetails).length === 0) return undefined;
121
+ return kind === "series"
122
+ ? { layers: [{ type: "line", length: 32, lineWidth: 2, ...strokeDetails }] }
123
+ : { layers: [{
124
+ type: "swatch",
125
+ width: 14,
126
+ height: 12,
127
+ stroke: "white",
128
+ strokeWidth: 0.5,
129
+ ...rectDetails
130
+ }] };
131
+ }
27
132
 
28
133
  function defaultRecipe(kind) {
29
134
  return kind === "series"
@@ -39,20 +144,20 @@ function defaultRecipe(kind) {
39
144
  };
40
145
  }
41
146
 
42
- function normalizeLayer(layer) {
147
+ function normalizeLayer(layer, internal) {
43
148
  if (!isPlainObject(layer) || !Object.hasOwn(LAYER_OPTIONS, layer.type)) {
44
149
  throw new Error("Legend symbol layer type must be line, point, or swatch.");
45
150
  }
46
151
  validateKeys(
47
152
  layer,
48
- LAYER_OPTIONS[layer.type],
153
+ internal ? INTERNAL_LAYER_OPTIONS[layer.type] : LAYER_OPTIONS[layer.type],
49
154
  `createLegend.symbol.${layer.type}`
50
155
  );
51
156
  if (layer.type === "line") {
52
157
  const normalized = { length: 32, lineWidth: 2, ...layer };
53
158
  positive(normalized.length, "Legend line symbol length");
54
159
  nonNegative(normalized.lineWidth, "Legend line symbol lineWidth");
55
- return normalized;
160
+ return { ...normalized, ...requestedStrokeDetails(normalized, "Legend line symbol") };
56
161
  }
57
162
  if (layer.type === "point") {
58
163
  const normalized = {
@@ -71,7 +176,7 @@ function normalizeLayer(layer) {
71
176
  if (normalized.fill !== undefined) {
72
177
  nonEmptyString(normalized.fill, "Legend point symbol fill");
73
178
  }
74
- return normalized;
179
+ return { ...normalized, ...requestedStrokeDetails(normalized, "Legend point symbol") };
75
180
  }
76
181
  const normalized = {
77
182
  width: 14,
@@ -84,10 +189,10 @@ function normalizeLayer(layer) {
84
189
  positive(normalized.height, "Legend swatch height");
85
190
  nonEmptyString(normalized.stroke, "Legend swatch stroke");
86
191
  nonNegative(normalized.strokeWidth, "Legend swatch strokeWidth");
87
- return normalized;
192
+ return { ...normalized, ...requestedRectStyleDetails(normalized, "Legend swatch symbol") };
88
193
  }
89
194
 
90
- export function normalizeRecipe(symbol, kind) {
195
+ export function normalizeRecipe(symbol, kind, { internal = false } = {}) {
91
196
  if (symbol === undefined || symbol === "auto") return defaultRecipe(kind);
92
197
  if (!isPlainObject(symbol)) {
93
198
  throw new TypeError('createLegend.symbol must be "auto" or a plain object.');
@@ -104,17 +209,17 @@ export function normalizeRecipe(symbol, kind) {
104
209
  "Legend symbol recipe supports at most one layer per type."
105
210
  );
106
211
  }
107
- layers = symbol.layers.map(normalizeLayer);
212
+ layers = symbol.layers.map(layer => normalizeLayer(layer, internal));
108
213
  } else if (kind === "series") {
109
214
  validateKeys(symbol, ["length", "lineWidth"], "createLegend.symbol");
110
- layers = [normalizeLayer({ type: "line", ...symbol })];
215
+ layers = [normalizeLayer({ type: "line", ...symbol }, internal)];
111
216
  } else {
112
217
  validateKeys(
113
218
  symbol,
114
219
  ["width", "height", "stroke", "strokeWidth"],
115
220
  "createLegend.symbol"
116
221
  );
117
- layers = [normalizeLayer({ type: "swatch", ...symbol })];
222
+ layers = [normalizeLayer({ type: "swatch", ...symbol }, internal)];
118
223
  }
119
224
 
120
225
  const types = layers.map(layer => layer.type);
@@ -1,3 +1,6 @@
1
+ import { requireDataset } from "../../../../selectors/datasets.js";
2
+ import { readNominalField } from "../../../../grammar/scales/fields.js";
3
+ import { resolveLegendOrderDomain } from "../../../../grammar/categoryOrder.js";
1
4
  import { validateUserId } from "../../../../core/identifiers.js";
2
5
  import {
3
6
  sameOrderedValues,
@@ -14,8 +17,11 @@ function isCategoricalTarget(layer) {
14
17
  channel => layer.encoding?.[channel]?.scale !== undefined
15
18
  );
16
19
  }
17
- return ["bar", "area", "arc", "rect"].includes(layer?.mark?.type) &&
18
- layer.encoding?.color?.scale !== undefined;
20
+ return ["bar", "area", "arc", "rect", "rule", "tick"].includes(
21
+ layer?.mark?.type
22
+ ) && ["color", "stroke"].some(
23
+ channel => layer.encoding?.[channel]?.scale !== undefined
24
+ );
19
25
  }
20
26
 
21
27
  export function resolveTarget(program, requested) {
@@ -41,7 +47,8 @@ export function resolveTarget(program, requested) {
41
47
 
42
48
  export const sameValues = sameOrderedValues;
43
49
 
44
- export function resolveLegendKind(layer, requestedChannels) {
50
+ function resolveLegendKind(layer, requestedChannels) {
51
+ if (sameValues(requestedChannels, ["stroke"])) return "stroke";
45
52
  if (["bar", "area", "arc", "rect"].includes(layer.mark.type)) return "color";
46
53
  if (
47
54
  layer.mark.type === "point" &&
@@ -74,13 +81,11 @@ function resolveOrdinalScales(program, scaleIds) {
74
81
  return { scales, domain: scales[0].domain };
75
82
  }
76
83
 
77
- export function resolveDefinition(program, layer, requestedChannels, requestedTitle) {
78
- const kind = resolveLegendKind(layer, requestedChannels);
79
- const channels = requestedChannels ?? (kind === "color"
80
- ? ["color"]
81
- : CHANNELS.filter(
82
- channel => layer.encoding?.[channel]?.scale !== undefined
83
- ));
84
+ export function resolveDefinition(program, layer, requestedChannels, requestedTitle, order) {
85
+ const channels = requestedChannels ?? CHANNELS.filter(
86
+ channel => layer.encoding?.[channel]?.scale !== undefined
87
+ );
88
+ const kind = resolveLegendKind(layer, channels);
84
89
  if (
85
90
  !Array.isArray(channels) ||
86
91
  channels.length === 0 ||
@@ -91,8 +96,8 @@ export function resolveDefinition(program, layer, requestedChannels, requestedTi
91
96
  "Legend channels must be a non-empty unique color/strokeDash/shape array."
92
97
  );
93
98
  }
94
- if (kind === "color" && !sameValues(channels, ["color"])) {
95
- throw new Error("Color legends currently support only the color channel.");
99
+ if (["color", "stroke"].includes(kind) && !sameValues(channels, [kind])) {
100
+ throw new Error(`${kind} legends currently support only the ${kind} channel.`);
96
101
  }
97
102
 
98
103
  const encodings = channels.map(channel => {
@@ -109,13 +114,22 @@ export function resolveDefinition(program, layer, requestedChannels, requestedTi
109
114
  const scales = encodings.map(item => item.encoding.scale);
110
115
  const resolved = resolveOrdinalScales(program, scales);
111
116
  const field = [...fields][0];
117
+ let linked;
118
+ if (order?.channel !== undefined) {
119
+ const position = layer.encoding?.[order.channel];
120
+ if (!["nominal", "ordinal"].includes(position?.fieldType) || position.field !== field) {
121
+ throw new Error("Linked legend order requires the same categorical field on its target.");
122
+ }
123
+ linked = program.resolvedScales[position.scale]?.domain;
124
+ }
112
125
  return {
113
126
  kind,
114
127
  channels,
115
128
  scales,
116
129
  field,
117
130
  title: nonEmptyString(requestedTitle ?? field, "Legend title"),
118
- domain: resolved.domain
131
+ domain: resolveLegendOrderDomain(resolved.domain, order, linked,
132
+ order?.values === undefined ? undefined : readNominalField(requireDataset(program, layer.data).values, field))
119
133
  };
120
134
  }
121
135
 
@@ -128,11 +142,12 @@ export function resolveCurrentDefinition(program, config) {
128
142
  if (guide === undefined) {
129
143
  throw new Error("Legend rematerialization requires semantic guide state.");
130
144
  }
131
- const channels = config.kind === "series" ? guide.channels : ["color"];
145
+ const channels = config.kind === "series" ? guide.channels : [config.kind];
132
146
  return resolveDefinition(
133
147
  program,
134
148
  layer,
135
149
  channels,
136
- config.inferredTitle === true ? undefined : guide.title
150
+ config.inferredTitle === true ? undefined : guide.title,
151
+ guide.order
137
152
  );
138
153
  }
@@ -9,9 +9,20 @@ import {
9
9
  symbolGraphic,
10
10
  symbolWidth
11
11
  } from "./layout.js";
12
- import { resolveLegendGraphicPlacement } from
13
- "../../../../materialization/graphicHierarchy.js";
12
+ import { resolveCategoricalLegendPlacement } from "../lifecycle.js";
14
13
  import { createPointShapeGraphic } from "../../../../grammar/pointShapes.js";
14
+ import {
15
+ materializeRectItem,
16
+ requestedRectStyleDetails
17
+ } from "../../../../grammar/roundedRect.js";
18
+ import { requestedStrokeDetails } from "../../../../grammar/strokeStyle.js";
19
+ import { replaceGraphicItems } from "../../../primitives/editGraphics.js";
20
+
21
+ function applyLayerOpacity(program, id, opacity) {
22
+ return opacity === undefined
23
+ ? program
24
+ : program.editGraphics({ target: id, property: "opacity", value: opacity });
25
+ }
15
26
 
16
27
  function makeEditSymbol(type) {
17
28
  const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[type];
@@ -20,20 +31,23 @@ function makeEditSymbol(type) {
20
31
  { op, description: `Rematerialize categorical legend ${type} symbols.` },
21
32
  function (args = {}) {
22
33
  noOptions(args, op);
23
- const { config } = activeConfig(this);
34
+ const { config } = activeConfig(this, args.kind);
24
35
  const layer = layerFor(config, type);
25
36
  const id = symbolGraphic(config, type);
26
37
  const dynamicPoint = type === "point" && config.channels.includes("shape");
27
- const expected = dynamicPoint
38
+ const styledSwatch = type === "swatch" && Object.hasOwn(layer, "cornerRadius");
39
+ const expected = dynamicPoint || styledSwatch
28
40
  ? "collection"
29
41
  : { line: "line", point: "circle", swatch: "rect" }[type];
30
- if (this.graphicSpec.objects[id]?.type !== expected) {
42
+ const actual = this.graphicSpec.objects[id]?.type;
43
+ const compatibleStyledSwatch = styledSwatch && ["rect", "collection"].includes(actual);
44
+ if (actual !== expected && !compatibleStyledSwatch) {
31
45
  throw new Error(`${op} requires existing ${type} symbols.`);
32
46
  }
33
47
  const layout = resolveLayout(this, config);
34
48
  const appearance = resolveAppearance(this, config);
35
49
  let next = this;
36
- if (!dynamicPoint) {
50
+ if (!dynamicPoint && !styledSwatch) {
37
51
  next = next.editGraphics({
38
52
  target: id,
39
53
  property: "length",
@@ -44,7 +58,7 @@ function makeEditSymbol(type) {
44
58
  const x1 = layout.symbolX.map(
45
59
  value => value + (symbolWidth(config) - layer.length) / 2
46
60
  );
47
- return next
61
+ next = next
48
62
  .editGraphics({ target: id, property: "x1", value: x1 })
49
63
  .editGraphics({ target: id, property: "y1", value: layout.itemY })
50
64
  .editGraphics({
@@ -53,9 +67,21 @@ function makeEditSymbol(type) {
53
67
  value: x1.map(value => value + layer.length)
54
68
  })
55
69
  .editGraphics({ target: id, property: "y2", value: layout.itemY })
56
- .editGraphics({ target: id, property: "stroke", value: appearance.colors })
70
+ .editGraphics({
71
+ target: id,
72
+ property: "stroke",
73
+ value: layer.stroke ?? (config.channels.includes("stroke")
74
+ ? appearance.strokes
75
+ : appearance.colors)
76
+ })
57
77
  .editGraphics({ target: id, property: "strokeWidth", value: layer.lineWidth })
58
78
  .editGraphics({ target: id, property: "strokeDash", value: appearance.dashes });
79
+ for (const [property, value] of Object.entries(
80
+ requestedStrokeDetails(layer, "Legend line symbol")
81
+ )) {
82
+ next = next.editGraphics({ target: id, property, value });
83
+ }
84
+ return applyLayerOpacity(next, id, layer.opacity);
59
85
  }
60
86
  if (type === "point") {
61
87
  const x = layout.symbolX.map(value => value + symbolWidth(config) / 2);
@@ -68,8 +94,12 @@ function makeEditSymbol(type) {
68
94
  y: layout.itemY[index],
69
95
  area: Math.PI * layer.size ** 2,
70
96
  fill,
71
- stroke: layer.stroke,
72
- strokeWidth: layer.strokeWidth
97
+ stroke: config.channels.includes("stroke")
98
+ ? appearance.strokes[index]
99
+ : layer.stroke,
100
+ strokeWidth: layer.strokeWidth,
101
+ opacity: layer.opacity,
102
+ ...requestedStrokeDetails(layer, "Legend point symbol")
73
103
  });
74
104
  });
75
105
  return next.editGraphics({
@@ -78,7 +108,7 @@ function makeEditSymbol(type) {
78
108
  value: items
79
109
  });
80
110
  }
81
- return next
111
+ next = next
82
112
  .editGraphics({ target: id, property: "x", value: x })
83
113
  .editGraphics({ target: id, property: "y", value: layout.itemY })
84
114
  .editGraphics({ target: id, property: "radius", value: layer.size })
@@ -87,24 +117,47 @@ function makeEditSymbol(type) {
87
117
  property: "fill",
88
118
  value: layer.fill ?? appearance.colors
89
119
  })
90
- .editGraphics({ target: id, property: "stroke", value: layer.stroke })
120
+ .editGraphics({
121
+ target: id,
122
+ property: "stroke",
123
+ value: config.channels.includes("stroke")
124
+ ? appearance.strokes
125
+ : layer.stroke
126
+ })
91
127
  .editGraphics({ target: id, property: "strokeWidth", value: layer.strokeWidth });
128
+ for (const [property, value] of Object.entries(
129
+ requestedStrokeDetails(layer, "Legend point symbol")
130
+ )) {
131
+ next = next.editGraphics({ target: id, property, value });
132
+ }
133
+ return applyLayerOpacity(next, id, layer.opacity);
92
134
  }
93
135
  const x = layout.symbolX.map(
94
136
  value => value + (symbolWidth(config) - layer.width) / 2
95
137
  );
96
- return next
97
- .editGraphics({ target: id, property: "x", value: x })
98
- .editGraphics({
99
- target: id,
100
- property: "y",
101
- value: layout.itemY.map(value => value - layer.height / 2)
102
- })
103
- .editGraphics({ target: id, property: "width", value: layer.width })
104
- .editGraphics({ target: id, property: "height", value: layer.height })
105
- .editGraphics({ target: id, property: "fill", value: appearance.colors })
106
- .editGraphics({ target: id, property: "stroke", value: layer.stroke })
107
- .editGraphics({ target: id, property: "strokeWidth", value: layer.strokeWidth });
138
+ const details = requestedRectStyleDetails(layer, "Legend swatch symbol");
139
+ const fills = layer.fill === undefined
140
+ ? appearance.colors
141
+ : config.domain.map(() => layer.fill);
142
+ const strokes = config.channels.includes("stroke")
143
+ ? appearance.strokes
144
+ : config.domain.map(() => layer.stroke);
145
+ return replaceGraphicItems(
146
+ next,
147
+ id,
148
+ "rect",
149
+ config.domain.map((_, index) => materializeRectItem({
150
+ x: x[index],
151
+ y: layout.itemY[index] - layer.height / 2,
152
+ width: layer.width,
153
+ height: layer.height,
154
+ fill: fills[index],
155
+ stroke: strokes[index],
156
+ strokeWidth: layer.strokeWidth,
157
+ ...(layer.opacity === undefined ? {} : { opacity: layer.opacity }),
158
+ ...requestedStrokeDetails(details, "Legend swatch symbol")
159
+ }, details.cornerRadius ?? 0))
160
+ );
108
161
  }
109
162
  );
110
163
  }
@@ -116,20 +169,21 @@ function makeCreateSymbol(type, edit) {
116
169
  { op, description: `Create categorical legend ${type} symbols.` },
117
170
  function (args = {}) {
118
171
  noOptions(args, op);
119
- const { config } = activeConfig(this);
120
- layerFor(config, type);
172
+ const { config } = activeConfig(this, args.kind);
173
+ const layer = layerFor(config, type);
121
174
  const id = symbolGraphic(config, type);
122
175
  if (this.graphicSpec.objects[id] !== undefined) {
123
176
  throw new Error(`${op} requires missing ${type} symbols.`);
124
177
  }
125
- const graphicType = type === "point" && config.channels.includes("shape")
178
+ const graphicType = type === "point" && config.channels.includes("shape") ||
179
+ type === "swatch" && (layer.cornerRadius ?? 0) > 0
126
180
  ? "collection"
127
181
  : { line: "line", point: "circle", swatch: "rect" }[type];
128
182
  return this
129
183
  .createGraphics({
130
184
  id,
131
185
  type: graphicType,
132
- ...resolveLegendGraphicPlacement(this),
186
+ ...resolveCategoricalLegendPlacement(this),
133
187
  ...(this.graphicSpec.objects[graphic(config, "Labels")] === undefined
134
188
  ? {}
135
189
  : { before: graphic(config, "Labels") }),
@@ -137,7 +191,7 @@ function makeCreateSymbol(type, edit) {
137
191
  ? {}
138
192
  : { length: config.domain.length })
139
193
  })
140
- [edit]();
194
+ [edit]({ kind: config.kind });
141
195
  }
142
196
  );
143
197
  }
@@ -162,7 +216,7 @@ export const createLegendSymbols = action(
162
216
  { op: "createLegendSymbols", description: "Create layered legend symbols." },
163
217
  function (args = {}) {
164
218
  noOptions(args, "createLegendSymbols");
165
- const { config } = activeConfig(this);
219
+ const { config } = activeConfig(this, args.kind);
166
220
  let next = this;
167
221
  for (const layer of config.symbol.layers) {
168
222
  const operation = {
@@ -170,7 +224,7 @@ export const createLegendSymbols = action(
170
224
  point: "createLegendSymbolPoints",
171
225
  swatch: "createLegendSymbolSwatches"
172
226
  }[layer.type];
173
- next = next[operation]();
227
+ next = next[operation]({ kind: config.kind });
174
228
  }
175
229
  return next;
176
230
  }
@@ -180,7 +234,7 @@ export const rematerializeLegendSymbols = action(
180
234
  { op: "rematerializeLegendSymbols", description: "Rematerialize layered legend symbols." },
181
235
  function (args = {}) {
182
236
  noOptions(args, "rematerializeLegendSymbols");
183
- const { config } = activeConfig(this);
237
+ const { config } = activeConfig(this, args.kind);
184
238
  let next = this;
185
239
  for (const layer of config.symbol.layers) {
186
240
  const operation = {
@@ -188,7 +242,7 @@ export const rematerializeLegendSymbols = action(
188
242
  point: "rematerializeLegendSymbolPoints",
189
243
  swatch: "rematerializeLegendSymbolSwatches"
190
244
  }[layer.type];
191
- next = next[operation]();
245
+ next = next[operation]({ kind: config.kind });
192
246
  }
193
247
  return next;
194
248
  }