ggaction 0.0.13 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +41 -12
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/knowledge/task-packet.schema.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +39 -7
  9. package/src/BasicChartProgram.js +3 -1
  10. package/src/ChartProgram.js +2 -0
  11. package/src/accessibility.js +157 -0
  12. package/src/actions/basic.js +2 -2
  13. package/src/actions/boxPlots/components.js +20 -12
  14. package/src/actions/boxPlots/create.js +4 -5
  15. package/src/actions/boxPlots/edit.js +34 -9
  16. package/src/actions/boxPlots/materialize.js +12 -4
  17. package/src/actions/boxPlots/options.js +48 -16
  18. package/src/actions/canvas/actions.js +3 -3
  19. package/src/actions/canvas/fitting.js +1 -1
  20. package/src/actions/categoryOrder/index.js +21 -12
  21. package/src/actions/charts/area.js +6 -2
  22. package/src/actions/charts/bar.js +3 -2
  23. package/src/actions/charts/beeswarm.js +1 -1
  24. package/src/actions/charts/density.js +7 -3
  25. package/src/actions/charts/ecdf.js +6 -3
  26. package/src/actions/charts/endpoints.js +29 -7
  27. package/src/actions/charts/heatmap.js +3 -2
  28. package/src/actions/charts/histogram.js +5 -4
  29. package/src/actions/charts/horizon.js +4 -2
  30. package/src/actions/charts/interval-regression.js +7 -4
  31. package/src/actions/charts/line.js +4 -2
  32. package/src/actions/charts/parallel.js +9 -3
  33. package/src/actions/charts/pie.js +11 -3
  34. package/src/actions/charts/polar.js +17 -6
  35. package/src/actions/charts/radar.js +9 -3
  36. package/src/actions/charts/radial.js +12 -4
  37. package/src/actions/charts/raincloud.js +2 -2
  38. package/src/actions/charts/rug-strip.js +8 -4
  39. package/src/actions/charts/scatter.js +4 -2
  40. package/src/actions/composition/actions.js +29 -24
  41. package/src/actions/coordinates/actions.js +2 -2
  42. package/src/actions/coordinates/edit.js +76 -0
  43. package/src/actions/data/basicBin2d.js +4 -5
  44. package/src/actions/data/bin.js +3 -3
  45. package/src/actions/data/bin2d.js +63 -193
  46. package/src/actions/data/bin2dMaterialize.js +4 -5
  47. package/src/actions/data/box.js +4 -4
  48. package/src/actions/data/complete.js +26 -0
  49. package/src/actions/data/computed.js +4 -4
  50. package/src/actions/data/create.js +7 -14
  51. package/src/actions/data/density.js +15 -23
  52. package/src/actions/data/derived.js +57 -31
  53. package/src/actions/data/ecdf.js +2 -2
  54. package/src/actions/data/edit.js +551 -0
  55. package/src/actions/data/filter.js +28 -22
  56. package/src/actions/data/fold.js +2 -2
  57. package/src/actions/data/gradientProfile.js +2 -2
  58. package/src/actions/data/horizon.js +2 -2
  59. package/src/actions/data/impute.js +26 -0
  60. package/src/actions/data/index.js +62 -47
  61. package/src/actions/data/interval.js +2 -2
  62. package/src/actions/data/intervalEdit.js +26 -18
  63. package/src/actions/data/normalize.js +27 -0
  64. package/src/actions/data/regression.js +21 -27
  65. package/src/actions/data/revise.js +48 -0
  66. package/src/actions/data/revisionBindings.js +125 -0
  67. package/src/actions/data/shared.js +20 -4
  68. package/src/actions/data/stack.js +2 -2
  69. package/src/actions/data/summary.js +3 -3
  70. package/src/actions/data/timeUnit.js +6 -3
  71. package/src/actions/data/window.js +3 -3
  72. package/src/actions/encodings/angle.js +1 -1
  73. package/src/actions/encodings/appearance.js +4 -4
  74. package/src/actions/encodings/barWidth.js +1 -1
  75. package/src/actions/encodings/channels.js +415 -0
  76. package/src/actions/encodings/color/index.js +3 -2
  77. package/src/actions/encodings/density.js +7 -4
  78. package/src/actions/encodings/histogram.js +4 -3
  79. package/src/actions/encodings/horizon.js +6 -8
  80. package/src/actions/encodings/index.js +4 -0
  81. package/src/actions/encodings/offset.js +22 -15
  82. package/src/actions/encodings/parallel.js +4 -5
  83. package/src/actions/encodings/pathOrder.js +6 -11
  84. package/src/actions/encodings/position/apply.js +53 -7
  85. package/src/actions/encodings/position/index.js +14 -6
  86. package/src/actions/encodings/position/policies/bar.js +9 -0
  87. package/src/actions/encodings/position/resolve.js +24 -7
  88. package/src/actions/encodings/ranged.js +5 -5
  89. package/src/actions/encodings/remove.js +13 -9
  90. package/src/actions/encodings/ruleAppearance.js +2 -24
  91. package/src/actions/encodings/seriesLayout.js +2 -1
  92. package/src/actions/encodings/shared.js +82 -14
  93. package/src/actions/encodings/stroke.js +226 -0
  94. package/src/actions/encodings/strokeDash.js +2 -2
  95. package/src/actions/encodings/text.js +1 -1
  96. package/src/actions/errorBands/create.js +34 -20
  97. package/src/actions/errorBands/edit.js +34 -137
  98. package/src/actions/errorBars/create.js +43 -20
  99. package/src/actions/errorBars/edit.js +29 -125
  100. package/src/actions/facets/actions.js +267 -73
  101. package/src/actions/facets/derive.js +99 -28
  102. package/src/actions/facets/guides.js +4 -5
  103. package/src/actions/facets/replay.js +4 -5
  104. package/src/actions/gradientPlots/components.js +15 -8
  105. package/src/actions/gradientPlots/create.js +4 -5
  106. package/src/actions/gradientPlots/edit.js +18 -9
  107. package/src/actions/gradientPlots/materialize.js +5 -3
  108. package/src/actions/gradientPlots/options.js +10 -2
  109. package/src/actions/gradientPlots/rebind.js +4 -5
  110. package/src/actions/guides/applicability.js +8 -2
  111. package/src/actions/guides/axes/axes.js +23 -4
  112. package/src/actions/guides/axes/axis.js +2 -2
  113. package/src/actions/guides/axes/edit.js +2 -2
  114. package/src/actions/guides/axes/labels.js +67 -21
  115. package/src/actions/guides/axes/lines.js +15 -9
  116. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  117. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  118. package/src/actions/guides/axes/parallel.js +5 -5
  119. package/src/actions/guides/axes/remove.js +4 -4
  120. package/src/actions/guides/axes/tickGroups.js +6 -5
  121. package/src/actions/guides/axes/ticks.js +19 -6
  122. package/src/actions/guides/axes/titles.js +21 -13
  123. package/src/actions/guides/grids/grid.js +18 -29
  124. package/src/actions/guides/grids/resolve.js +7 -2
  125. package/src/actions/guides/guides.js +20 -4
  126. package/src/actions/guides/legends/blocks.js +426 -0
  127. package/src/actions/guides/legends/categorical/actions.js +138 -52
  128. package/src/actions/guides/legends/categorical/components.js +55 -45
  129. package/src/actions/guides/legends/categorical/index.js +37 -33
  130. package/src/actions/guides/legends/categorical/layout.js +98 -16
  131. package/src/actions/guides/legends/categorical/options.js +2 -2
  132. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  133. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  134. package/src/actions/guides/legends/categorical/symbols.js +104 -52
  135. package/src/actions/guides/legends/continuous/common.js +76 -20
  136. package/src/actions/guides/legends/continuous/gradient.js +95 -158
  137. package/src/actions/guides/legends/continuous/index.js +2 -0
  138. package/src/actions/guides/legends/continuous/interval.js +21 -16
  139. package/src/actions/guides/legends/continuous/opacity.js +37 -83
  140. package/src/actions/guides/legends/continuous/stroke.js +225 -0
  141. package/src/actions/guides/legends/creation.js +14 -1
  142. package/src/actions/guides/legends/edit.js +159 -60
  143. package/src/actions/guides/legends/focused.js +5 -5
  144. package/src/actions/guides/legends/index.js +2 -0
  145. package/src/actions/guides/legends/lane.js +59 -24
  146. package/src/actions/guides/legends/lifecycle.js +19 -8
  147. package/src/actions/guides/legends/remove.js +17 -8
  148. package/src/actions/guides/legends/sampling.js +140 -0
  149. package/src/actions/guides/legends/size.js +100 -108
  150. package/src/actions/guides/legends/strokeWidth.js +38 -92
  151. package/src/actions/guides/legends/target.js +112 -1
  152. package/src/actions/guides/legends/transition.js +165 -16
  153. package/src/actions/guides/polar/axes/facade.js +8 -8
  154. package/src/actions/guides/polar/axes/labels.js +29 -10
  155. package/src/actions/guides/polar/axes/lines.js +19 -19
  156. package/src/actions/guides/polar/axes/shared.js +16 -0
  157. package/src/actions/guides/polar/axes/ticks.js +10 -16
  158. package/src/actions/guides/polar/axes/titles.js +6 -6
  159. package/src/actions/guides/polar/grids.js +12 -14
  160. package/src/actions/guides/polar/resolve.js +12 -8
  161. package/src/actions/index.js +16 -3
  162. package/src/actions/intervals/revision.js +126 -0
  163. package/src/actions/marks/arc/actions.js +80 -22
  164. package/src/actions/marks/area/actions.js +38 -13
  165. package/src/actions/marks/area/materialize.js +17 -1
  166. package/src/actions/marks/bar/create.js +12 -3
  167. package/src/actions/marks/bar/edit.js +21 -4
  168. package/src/actions/marks/bar/materialize.js +34 -40
  169. package/src/actions/marks/index.js +10 -1
  170. package/src/actions/marks/line/actions.js +40 -17
  171. package/src/actions/marks/line/materialize.js +29 -10
  172. package/src/actions/marks/point/create.js +9 -3
  173. package/src/actions/marks/point/edit.js +19 -7
  174. package/src/actions/marks/point/jitter.js +6 -8
  175. package/src/actions/marks/point/materialize.js +48 -22
  176. package/src/actions/marks/point/packing.js +6 -8
  177. package/src/actions/marks/rect/actions.js +36 -15
  178. package/src/actions/marks/references.js +602 -33
  179. package/src/actions/marks/remove.js +67 -41
  180. package/src/actions/marks/rule/actions.js +51 -14
  181. package/src/actions/marks/shared.js +1 -1
  182. package/src/actions/marks/text/actions.js +79 -14
  183. package/src/actions/marks/text/index.js +4 -0
  184. package/src/actions/marks/text/layout.js +157 -17
  185. package/src/actions/marks/text/removal.js +173 -0
  186. package/src/actions/marks/text/selection.js +72 -0
  187. package/src/actions/marks/tick/actions.js +25 -8
  188. package/src/actions/primitives/createGraphics.js +5 -4
  189. package/src/actions/primitives/editGraphics.js +31 -4
  190. package/src/actions/primitives/graphicProperties.js +6 -0
  191. package/src/actions/primitives/semantic.js +11 -3
  192. package/src/actions/primitives/semanticAction.js +90 -14
  193. package/src/actions/primitives/semanticValidation/dataset.js +2 -4
  194. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  195. package/src/actions/primitives/semanticValidation/index.js +9 -1
  196. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  197. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  198. package/src/actions/regression/components.js +36 -25
  199. package/src/actions/regression/create.js +13 -6
  200. package/src/actions/regression/edit.js +24 -11
  201. package/src/actions/resources/index.js +7 -0
  202. package/src/actions/resources/remove.js +173 -0
  203. package/src/actions/scales/channels.js +212 -0
  204. package/src/actions/scales/consumers/common.js +1 -1
  205. package/src/actions/scales/consumers/families.js +3 -2
  206. package/src/actions/scales/consumers/index.js +19 -2
  207. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  208. package/src/actions/scales/create.js +55 -23
  209. package/src/actions/scales/definitions.js +94 -40
  210. package/src/actions/scales/edit.js +67 -12
  211. package/src/actions/scales/editPolicy.js +81 -11
  212. package/src/actions/scales/index.js +6 -0
  213. package/src/actions/scales/materialize.js +5 -4
  214. package/src/actions/scales/offset.js +48 -0
  215. package/src/actions/scales/parallel.js +71 -0
  216. package/src/actions/scales/preview.js +111 -10
  217. package/src/actions/scales/quantitativeColor.js +4 -5
  218. package/src/actions/selection/actions.js +179 -66
  219. package/src/actions/selection/index.js +3 -0
  220. package/src/actions/textMetrics/index.js +56 -0
  221. package/src/actions/theme/actions.js +70 -15
  222. package/src/actions/theme/composition.js +238 -0
  223. package/src/actions/theme/index.js +9 -2
  224. package/src/actions/theme/reconcile.js +313 -121
  225. package/src/actions/theme/state.js +160 -0
  226. package/src/actions/titles/actions.js +8 -8
  227. package/src/actions/titles/resolve.js +3 -3
  228. package/src/actions/violinPlots/create.js +6 -3
  229. package/src/actions/violinPlots/edit.js +2 -1
  230. package/src/core/action.js +117 -35
  231. package/src/core/compositionState.js +6 -1
  232. package/src/core/diagnostics.js +16 -0
  233. package/src/core/extensionRegistry.js +27 -0
  234. package/src/core/font.js +18 -0
  235. package/src/core/identifiers.js +5 -4
  236. package/src/core/immutable.js +10 -3
  237. package/src/core/optionRequirements.js +8 -0
  238. package/src/core/programState.js +21 -1
  239. package/src/core/resourceReferences.js +341 -0
  240. package/src/core/textMetricProfile.js +28 -0
  241. package/src/core/textMetrics.js +24 -3
  242. package/src/core/validation.js +22 -10
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/diagnostics.js +1 -0
  245. package/src/grammar/bars/aggregate.js +4 -1
  246. package/src/grammar/bars/geometry.js +44 -0
  247. package/src/grammar/bin.js +54 -9
  248. package/src/grammar/complete.js +279 -0
  249. package/src/grammar/computed.js +265 -64
  250. package/src/grammar/coordinates.js +37 -0
  251. package/src/grammar/density.js +239 -37
  252. package/src/grammar/displayLabels.js +57 -0
  253. package/src/grammar/facets/dependencies.js +133 -1
  254. package/src/grammar/facets/guides.js +3 -0
  255. package/src/grammar/facets/index.js +7 -47
  256. package/src/grammar/facets/scales.js +15 -18
  257. package/src/grammar/impute.js +341 -0
  258. package/src/grammar/markLabelSelection.js +57 -0
  259. package/src/grammar/markLabels.js +18 -1
  260. package/src/grammar/normalize.js +366 -0
  261. package/src/grammar/pathSeries.js +26 -8
  262. package/src/grammar/pointShapes.js +13 -4
  263. package/src/grammar/polar.js +90 -8
  264. package/src/grammar/regression/index.js +1 -0
  265. package/src/grammar/regression/parameters.js +23 -0
  266. package/src/grammar/roundedRect.js +180 -0
  267. package/src/grammar/rules.js +1 -0
  268. package/src/grammar/scales/colorConsumers.js +20 -1
  269. package/src/grammar/scales/continuous.js +2 -2
  270. package/src/grammar/scales/definition.js +27 -4
  271. package/src/grammar/scales/index.js +1 -0
  272. package/src/grammar/scales/ordinal.js +12 -3
  273. package/src/grammar/scales/policies.js +2 -2
  274. package/src/grammar/scales/size.js +456 -0
  275. package/src/grammar/scales/types.js +8 -0
  276. package/src/grammar/schemas/concreteGraphic.js +14 -2
  277. package/src/grammar/schemas/graphic.js +15 -0
  278. package/src/grammar/schemas/graphicBounds.js +190 -41
  279. package/src/grammar/schemas/semanticPath.js +7 -2
  280. package/src/grammar/statisticalReference.js +103 -0
  281. package/src/grammar/strokeStyle.js +77 -0
  282. package/src/grammar/summary.js +78 -16
  283. package/src/grammar/timeUnit.js +216 -5
  284. package/src/grammar/transformTopology.js +4 -0
  285. package/src/grammar/transforms.js +394 -28
  286. package/src/grammar/weightedStatistics.js +374 -0
  287. package/src/grammar/window.js +221 -16
  288. package/src/layout/aspect.js +80 -0
  289. package/src/layout/facets.js +59 -5
  290. package/src/layout/labels.js +351 -7
  291. package/src/layout/legend.js +4 -4
  292. package/src/layout/legendItems.js +6 -6
  293. package/src/layout/text.js +10 -9
  294. package/src/layout/title.js +9 -9
  295. package/src/materialization/bars/aggregate.js +25 -2
  296. package/src/materialization/bars/histogram.js +84 -24
  297. package/src/materialization/bars/ranged.js +19 -3
  298. package/src/materialization/bars/resolve.js +7 -4
  299. package/src/materialization/composition.js +3 -1
  300. package/src/materialization/coordinateBounds.js +204 -0
  301. package/src/materialization/dependencies.js +36 -0
  302. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  303. package/src/materialization/facetGuides/placement.js +9 -3
  304. package/src/materialization/facetGuides/preparation.js +13 -8
  305. package/src/materialization/facetHeaders.js +347 -0
  306. package/src/materialization/facets.js +15 -65
  307. package/src/materialization/guides/layout.js +3 -3
  308. package/src/materialization/guides/resources.js +26 -0
  309. package/src/materialization/legends.js +1 -1
  310. package/src/materialization/marks/index.js +11 -5
  311. package/src/materialization/marks/policies.js +2 -1
  312. package/src/materialization/rect.js +25 -12
  313. package/src/materialization/rectConfig.js +5 -1
  314. package/src/materialization/scaleGuideDependencies.js +10 -0
  315. package/src/materialization/scales/map.js +5 -3
  316. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  317. package/src/materialization/scales/policies/offset.js +50 -8
  318. package/src/materialization/scales/policies/series.js +4 -1
  319. package/src/materialization/scales/resolve.js +22 -6
  320. package/src/materialization/selection/items/arc.js +28 -4
  321. package/src/materialization/selection/items/bar.js +30 -3
  322. package/src/materialization/selection/items/common.js +20 -5
  323. package/src/materialization/selection/items/rect.js +28 -4
  324. package/src/materialization/selection/path.js +1 -1
  325. package/src/materialization/selection/point.js +3 -1
  326. package/src/materialization/text.js +265 -14
  327. package/src/materialization/typography.js +31 -0
  328. package/src/mcp/cli.js +5 -3
  329. package/src/persistence/codec.js +65 -0
  330. package/src/persistence/validation.js +185 -0
  331. package/src/persistence.js +74 -0
  332. package/src/renderers/canvas/circle.js +2 -3
  333. package/src/renderers/canvas/index.js +5 -4
  334. package/src/renderers/canvas/line.js +2 -3
  335. package/src/renderers/canvas/native.js +4 -4
  336. package/src/renderers/canvas/path.js +2 -3
  337. package/src/renderers/canvas/rect.js +2 -3
  338. package/src/renderers/canvas/stroke.js +11 -0
  339. package/src/renderers/nativeBackend.js +10 -0
  340. package/src/renderers/options.js +13 -0
  341. package/src/renderers/pdf.js +25 -54
  342. package/src/renderers/png.js +22 -17
  343. package/src/renderers/svg.js +29 -14
  344. package/src/renderers/text.js +1 -11
  345. package/src/selectors/coordinates.js +3 -1
  346. package/src/selectors/datasets.js +38 -5
  347. package/src/selectors/layers.js +10 -4
  348. package/src/selectors/markOwners.js +46 -0
  349. package/src/selectors/scales.js +6 -4
  350. package/src/theme/defaults.js +103 -1
  351. package/src/version.js +2 -0
  352. package/types/accessibility.d.ts +37 -0
  353. package/types/basic.d.ts +10 -2
  354. package/types/diagnostics.d.ts +15 -0
  355. package/types/index.d.ts +90 -0
  356. package/types/pdf.d.ts +9 -0
  357. package/types/persistence.d.ts +7 -0
  358. package/types/png.d.ts +9 -0
  359. package/types/program.d.ts +944 -118
@@ -3,10 +3,12 @@ import {
3
3
  readScaleField
4
4
  } from "../grammar/scales/index.js";
5
5
  import { normalizePositionDatum } from "../grammar/positionDatum.js";
6
- import { resolveGraphicBounds } from "../layout/canvas.js";
6
+ import { resolveCoordinateBounds } from "./coordinateBounds.js";
7
7
  import { RECT_MODES, resolveRectMode, rectUsesFields } from "../grammar/rects.js";
8
8
  import { DEFAULT_RECT_MARK } from "./rectConfig.js";
9
9
  import { mapScaleConsumerValues } from "./scales/map.js";
10
+ import { materializeRectItem } from "../grammar/roundedRect.js";
11
+ import { requestedStrokeDetails } from "../grammar/strokeStyle.js";
10
12
 
11
13
  function optionalValues(rows, encoding, length, channel) {
12
14
  if (Object.hasOwn(encoding, "datum")) {
@@ -22,7 +24,11 @@ function requiredChannels(layer, mode) {
22
24
  const position = mode === RECT_MODES.xSpan ? ["x", "x2"]
23
25
  : mode === RECT_MODES.ySpan ? ["y", "y2"]
24
26
  : mode === RECT_MODES.ranged ? ["x", "x2", "y", "y2"] : ["x", "y"];
25
- return [...position, ...(layer.encoding?.color ? ["color"] : [])];
27
+ return [
28
+ ...position,
29
+ ...(layer.encoding?.color ? ["color"] : []),
30
+ ...(layer.encoding?.stroke ? ["stroke"] : [])
31
+ ];
26
32
  }
27
33
 
28
34
  export function resolveRectConsumerValues(layer, dataset, channel) {
@@ -64,19 +70,22 @@ function mappedEncoding(program, layer, dataset, channel) {
64
70
  };
65
71
  }
66
72
 
67
- function appearance(config, fill) {
73
+ function appearance(config, fill, stroke) {
68
74
  return {
69
75
  fill,
70
76
  opacity: config.opacity,
71
- stroke: config.stroke === false ? "transparent" : config.stroke,
72
- strokeWidth: config.stroke === false ? 0 : config.strokeWidth
77
+ stroke: stroke ?? (config.stroke === false ? "transparent" : config.stroke),
78
+ strokeWidth: stroke === undefined && config.stroke === false
79
+ ? 0
80
+ : config.strokeWidth ?? 1,
81
+ ...requestedStrokeDetails(config, "Rect mark")
73
82
  };
74
83
  }
75
84
 
76
85
  export function resolveRectRows(program, layer, dataset) {
77
86
  const mode = resolveRectMode(layer);
78
87
  if (mode === undefined) return [];
79
- const bounds = resolveGraphicBounds(program);
88
+ const bounds = resolveCoordinateBounds(program, layer.coordinate);
80
89
  const x = layer.encoding.x === undefined ? undefined : mappedEncoding(program, layer, dataset, "x");
81
90
  const y = layer.encoding.y === undefined ? undefined : mappedEncoding(program, layer, dataset, "y");
82
91
  const x2 = layer.encoding.x2 === undefined ? undefined : mappedEncoding(program, layer, dataset, "x2");
@@ -84,6 +93,9 @@ export function resolveRectRows(program, layer, dataset) {
84
93
  const color = layer.encoding?.color === undefined
85
94
  ? undefined
86
95
  : mappedEncoding(program, layer, dataset, "color").values;
96
+ const stroke = layer.encoding?.stroke === undefined
97
+ ? undefined
98
+ : mappedEncoding(program, layer, dataset, "stroke").values;
87
99
  const config = program.markConfigs[layer.id] ?? DEFAULT_RECT_MARK;
88
100
 
89
101
  const rows = rectUsesFields(layer) ? dataset.values : [{}];
@@ -92,7 +104,8 @@ export function resolveRectRows(program, layer, dataset) {
92
104
  if (
93
105
  (x !== undefined && !Number.isFinite(x.values[index])) ||
94
106
  (y !== undefined && !Number.isFinite(y.values[index])) ||
95
- typeof fill !== "string"
107
+ typeof fill !== "string" ||
108
+ (stroke !== undefined && typeof stroke[index] !== "string")
96
109
  ) return [];
97
110
 
98
111
  let geometry;
@@ -125,14 +138,14 @@ export function resolveRectRows(program, layer, dataset) {
125
138
  return [{
126
139
  row,
127
140
  sourceIndex: index,
128
- properties: { ...geometry, ...appearance(config, fill) }
141
+ properties: { ...geometry, ...appearance(config, fill, stroke?.[index]) }
129
142
  }];
130
143
  });
131
144
  }
132
145
 
133
146
  export function resolveRectGraphicItems(program, layer, dataset) {
134
- return resolveRectRows(program, layer, dataset).map(item => ({
135
- type: "rect",
136
- properties: item.properties
137
- }));
147
+ const radius = program.markConfigs[layer.id]?.cornerRadius ?? 0;
148
+ return resolveRectRows(program, layer, dataset).map(item =>
149
+ materializeRectItem(item.properties, radius)
150
+ );
138
151
  }
@@ -4,6 +4,7 @@ import {
4
4
  validateUnitInterval
5
5
  } from "../core/validation.js";
6
6
  import { DEFAULT_COLORS } from "../theme/defaults.js";
7
+ import { requestedRectStyleDetails } from "../grammar/roundedRect.js";
7
8
 
8
9
  export const DEFAULT_RECT_MARK = Object.freeze({
9
10
  fill: DEFAULT_COLORS.mark,
@@ -13,7 +14,10 @@ export const DEFAULT_RECT_MARK = Object.freeze({
13
14
  });
14
15
 
15
16
  export function normalizeRectMarkConfig(patch, previous = DEFAULT_RECT_MARK) {
16
- const result = { ...previous };
17
+ const result = {
18
+ ...previous,
19
+ ...requestedRectStyleDetails(patch, "Rect")
20
+ };
17
21
  if (Object.hasOwn(patch, "fill")) {
18
22
  result.fill = validateNonEmptyString(patch.fill, "Rect fill");
19
23
  }
@@ -18,6 +18,16 @@ const GRIDS = Object.freeze([
18
18
  Object.freeze({ role: "radial", op: "rematerializeRadialGrid" })
19
19
  ]);
20
20
 
21
+ export function planAxisTickGridRematerialization(program, channel) {
22
+ const ticks = program.guideConfigs.axis?.[channel]?.ticks;
23
+ const role = channel === "x" ? "vertical" : "horizontal";
24
+ const config = program.guideConfigs.grid?.[role];
25
+ return buildMaterializationPlan({ guides:
26
+ config?.inferredValues === true && config.scale === ticks?.scale
27
+ ? GRIDS.filter(grid => grid.role === role).map(grid => ({ op: grid.op })) : []
28
+ });
29
+ }
30
+
21
31
  function usesPositionalScale(program, id) {
22
32
  return program.semanticSpec.layers.some(layer =>
23
33
  !isSourceOwnedText(layer) && (
@@ -4,15 +4,17 @@ import {
4
4
  isOrdinalScaleType,
5
5
  mapContinuousScaleValues,
6
6
  mapDiscretizedColors,
7
+ mapSizeValues,
7
8
  mapOrdinalValues,
8
9
  mapSequentialColors
9
10
  } from "../../grammar/scales/index.js";
10
11
 
11
12
  export function mapScaleConsumerValues(values, resolvedScale, channel) {
12
- if (channel === "color" && isDiscretizedColorScaleType(resolvedScale.type)) {
13
+ if (channel === "size") return mapSizeValues(values, resolvedScale);
14
+ if (["color", "stroke"].includes(channel) && isDiscretizedColorScaleType(resolvedScale.type)) {
13
15
  return mapDiscretizedColors(values, resolvedScale);
14
16
  }
15
- if (channel === "color" && isContinuousColorScaleType(resolvedScale.type)) {
17
+ if (["color", "stroke"].includes(channel) && isContinuousColorScaleType(resolvedScale.type)) {
16
18
  return mapSequentialColors(
17
19
  values,
18
20
  resolvedScale.domain,
@@ -28,7 +30,7 @@ export function mapScaleConsumerValues(values, resolvedScale, channel) {
28
30
  );
29
31
  }
30
32
  if (
31
- ["color", "strokeDash", "shape"].includes(channel) &&
33
+ ["color", "stroke", "strokeDash", "shape"].includes(channel) &&
32
34
  isOrdinalScaleType(resolvedScale.type)
33
35
  ) {
34
36
  return mapOrdinalValues(values, resolvedScale.domain, resolvedScale.range, {
@@ -13,8 +13,8 @@ export function resolveBinnedPositionDomain({
13
13
  consumer.encoding.bin !== undefined
14
14
  );
15
15
  if (binnedPositions.length === 0) return undefined;
16
- const independentConsumers = valuesByConsumer.filter(({ consumer }) =>
17
- !isSourceOwnedText(consumer.layer)
16
+ const independentConsumers = valuesByConsumer.filter(({ consumer, scalePolicy }) =>
17
+ scalePolicy !== false && !isSourceOwnedText(consumer.layer)
18
18
  );
19
19
  if (channel !== "x" || binnedPositions.length !== independentConsumers.length) {
20
20
  throw new Error(
@@ -1,4 +1,4 @@
1
- import { normalizeOffsetPadding } from "../../../grammar/bars/geometry.js";
1
+ import { normalizeOffsetScalePolicy } from "../../../grammar/bars/geometry.js";
2
2
 
3
3
  function resolveParentSlot(consumer, resolvedScales, parentChannel) {
4
4
  if (parentChannel === "x" && consumer.layer.encoding?.x?.bin !== undefined) {
@@ -16,6 +16,7 @@ function resolveParentSlot(consumer, resolvedScales, parentChannel) {
16
16
  }
17
17
 
18
18
  export function resolveOffsetScalePolicy({
19
+ scale,
19
20
  consumers,
20
21
  resolvedScales,
21
22
  markConfigs,
@@ -34,21 +35,62 @@ export function resolveOffsetScalePolicy({
34
35
  `${channel} scale "${id}" requires one shared resolved ${parentChannel} categorical slot.`
35
36
  );
36
37
  }
37
- const paddings = consumers.map(consumer => normalizeOffsetPadding(
38
+ return {
39
+ parentBandwidth: slots[0],
40
+ ...resolveRequestedOffsetPolicy({
41
+ scale,
42
+ consumers,
43
+ markConfigs,
44
+ id,
45
+ channel
46
+ })
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Resolve the requested offset policy independently from its parent slot.
52
+ * Semantic scale fields are canonical. Consistent legacy mark-owned padding is
53
+ * retained as a fallback until the next successful scale/encoding edit migrates
54
+ * it into the semantic scale.
55
+ */
56
+ export function resolveRequestedOffsetPolicy({
57
+ scale,
58
+ consumers = [],
59
+ markConfigs = {},
60
+ id = scale?.id ?? channel,
61
+ channel = "xOffset"
62
+ }) {
63
+ const semanticPatch = scale === undefined
64
+ ? {}
65
+ : Object.fromEntries(
66
+ ["padding", "paddingInner", "paddingOuter", "align"]
67
+ .filter(property => Object.hasOwn(scale, property))
68
+ .map(property => [property, scale[property]])
69
+ );
70
+ if (
71
+ Object.hasOwn(semanticPatch, "padding") ||
72
+ ["paddingInner", "paddingOuter", "align"].every(
73
+ property => Object.hasOwn(semanticPatch, property)
74
+ )
75
+ ) {
76
+ return normalizeOffsetScalePolicy(semanticPatch, undefined, channel);
77
+ }
78
+ const legacyPolicies = consumers.map(consumer => normalizeOffsetScalePolicy(
38
79
  markConfigs[consumer.layer.id]?.[channel],
39
80
  undefined,
40
81
  channel
41
82
  ));
42
83
  const signatures = new Set(
43
- paddings.map(padding => JSON.stringify(padding))
84
+ legacyPolicies.map(policy => JSON.stringify(policy))
44
85
  );
45
- if (signatures.size !== 1) {
86
+ if (signatures.size > 1) {
46
87
  throw new Error(
47
88
  `${channel} scale "${id}" requires one shared padding policy.`
48
89
  );
49
90
  }
50
- return {
51
- parentBandwidth: slots[0],
52
- ...paddings[0]
53
- };
91
+ const legacy = legacyPolicies[0] ?? normalizeOffsetScalePolicy(
92
+ {}, undefined, channel
93
+ );
94
+ if (scale === undefined) return legacy;
95
+ return normalizeOffsetScalePolicy(semanticPatch, legacy, channel);
54
96
  }
@@ -22,7 +22,10 @@ export function resolveSeriesLayoutDomain({
22
22
  (_, index) => seriesLayouts[index] !== undefined
23
23
  );
24
24
  const directConsumers = valuesByConsumer.filter(
25
- ({ consumer }, index) => seriesLayouts[index] === undefined && !isSourceOwnedText(consumer.layer)
25
+ ({ consumer, scalePolicy }, index) =>
26
+ scalePolicy !== false &&
27
+ seriesLayouts[index] === undefined &&
28
+ !isSourceOwnedText(consumer.layer)
26
29
  );
27
30
  const compatibleDirect = directConsumers.every(({ consumer }) =>
28
31
  layoutConsumers.every(({ consumer: layoutConsumer }) => {
@@ -17,6 +17,7 @@ import {
17
17
  resolveScaleRange,
18
18
  resolveSequentialColorStops,
19
19
  resolveShapeRange,
20
+ resolveSizeScale,
20
21
  resolveSizeRange,
21
22
  resolveStrokeDashRange,
22
23
  resolveStrokeWidthRange,
@@ -96,7 +97,8 @@ function resolveRange({
96
97
  isSequentialColor,
97
98
  isDiscretizedColor,
98
99
  isOrdinalOffset,
99
- discretizedScale
100
+ discretizedScale,
101
+ polarFrame
100
102
  }) {
101
103
  if (channel === "color") {
102
104
  if (isDiscretizedColor) return discretizedScale.range;
@@ -114,7 +116,7 @@ function resolveRange({
114
116
  scale,
115
117
  channel,
116
118
  domain,
117
- range: resolveScaleRange(scale.range, channel, bounds),
119
+ range: resolveScaleRange(scale.range, channel, bounds, polarFrame),
118
120
  markConfigs
119
121
  });
120
122
  }
@@ -190,14 +192,16 @@ export function resolveScaleMaterialization(options) {
190
192
  const {
191
193
  id,
192
194
  scale,
193
- channel,
195
+ channel: requestedChannel,
194
196
  consumers,
195
197
  valuesByConsumer,
196
198
  bounds,
197
199
  resolvedScales,
198
200
  markConfigs,
199
- thetaScales
201
+ thetaScales,
202
+ polarFrame
200
203
  } = options;
204
+ const channel = requestedChannel === "stroke" ? "color" : requestedChannel;
201
205
  const allValues = valuesByConsumer
202
206
  .flatMap(item => item.values)
203
207
  .filter(value => value !== undefined);
@@ -218,6 +222,9 @@ export function resolveScaleMaterialization(options) {
218
222
  }))
219
223
  });
220
224
  }
225
+ if (channel === "size") {
226
+ return resolveSizeScale({ ...scale, values: allValues });
227
+ }
221
228
  const isSequentialColor = channel === "color" &&
222
229
  isContinuousColorScaleType(scale.type);
223
230
  const isDiscretizedColor = channel === "color" &&
@@ -234,7 +241,14 @@ export function resolveScaleMaterialization(options) {
234
241
  isDiscretePositionScaleType(scale.type);
235
242
 
236
243
  if (isSequentialColor || isDiscretizedColor) {
237
- for (const consumer of consumers) validateContinuousColorConsumer(consumer.layer, consumer.encoding, scale);
244
+ for (const consumer of consumers) {
245
+ validateContinuousColorConsumer(
246
+ consumer.layer,
247
+ consumer.encoding,
248
+ scale,
249
+ { channel: consumer.channel }
250
+ );
251
+ }
238
252
  }
239
253
  let discretizedScale;
240
254
  if (isDiscretizedColor) {
@@ -290,7 +304,8 @@ export function resolveScaleMaterialization(options) {
290
304
  isSequentialColor,
291
305
  isDiscretizedColor,
292
306
  isOrdinalOffset,
293
- discretizedScale
307
+ discretizedScale,
308
+ polarFrame
294
309
  });
295
310
  validateSequentialMidpoint(scale.midpoint, scale.type, domain);
296
311
  if (scale.midpoint !== undefined && (channel !== "color" || consumers.some(
@@ -313,6 +328,7 @@ export function resolveScaleMaterialization(options) {
313
328
  values: allValues,
314
329
  range: scale.range,
315
330
  ...resolveOffsetScalePolicy({
331
+ scale,
316
332
  consumers,
317
333
  resolvedScales,
318
334
  markConfigs,
@@ -1,7 +1,7 @@
1
1
  import { deriveArcSectors } from "../../../grammar/arcs.js";
2
2
  import { resolvePolarPoint } from "../../../grammar/polarPaths.js";
3
- import { resolvePolarFrame } from "../../../grammar/polar.js";
4
- import { resolveGraphicBounds } from "../../../layout/canvas.js";
3
+ import { readScaleField } from "../../../grammar/scales/index.js";
4
+ import { resolveCoordinatePolarFrame } from "../../coordinateBounds.js";
5
5
  import {
6
6
  concreteProperties,
7
7
  finalizeItems,
@@ -11,7 +11,7 @@ import {
11
11
  export function resolveArcItems(program, layer, dataset) {
12
12
  const thetaScale = program.resolvedScales[layer.encoding?.theta?.scale];
13
13
  const radiusScale = program.resolvedScales[layer.encoding?.radius?.scale];
14
- const frame = resolvePolarFrame(resolveGraphicBounds(program));
14
+ const frame = resolveCoordinatePolarFrame(program, layer.coordinate);
15
15
  const derived = deriveArcSectors(dataset.values, layer, {
16
16
  thetaScale,
17
17
  ...(radiusScale === undefined ? {} : { radiusScale }),
@@ -19,14 +19,25 @@ export function resolveArcItems(program, layer, dataset) {
19
19
  innerRadiusRatio: program.markConfigs[layer.id]?.innerRadius ?? 0
20
20
  });
21
21
  const graphic = program.graphicSpec.objects[layer.id];
22
+ const stroke = layer.encoding?.stroke;
22
23
  const definitions = derived.sectors.map((sector, index) => {
23
24
  const members = sector.sourceIndices.map(index => dataset.values[index]);
25
+ const strokeValues = stroke === undefined ? undefined : readScaleField(
26
+ members,
27
+ stroke.field,
28
+ stroke.fieldType,
29
+ { temporalUnit: stroke.temporalUnit }
30
+ );
31
+ if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
32
+ throw new Error("Arc stroke requires one value within each sector.");
33
+ }
24
34
  return {
25
35
  fields: uniqueFields(members),
26
36
  channels: {
27
37
  theta: sector.theta,
28
38
  ...(sector.radius === undefined ? {} : { radius: sector.radius }),
29
- ...(sector.color === undefined ? {} : { color: sector.color })
39
+ ...(sector.color === undefined ? {} : { color: sector.color }),
40
+ ...(strokeValues === undefined ? {} : { stroke: strokeValues[0] })
30
41
  },
31
42
  properties: {
32
43
  ...concreteProperties(graphic.items[index]?.properties),
@@ -36,6 +47,19 @@ export function resolveArcItems(program, layer, dataset) {
36
47
  (sector.innerRadius + sector.outerRadius) / 2
37
48
  )
38
49
  },
50
+ geometry: {
51
+ kind: "arc",
52
+ centerX: frame.centerX,
53
+ centerY: frame.centerY,
54
+ startTheta: sector.startTheta +
55
+ Math.sign(sector.endTheta - sector.startTheta) *
56
+ (program.markConfigs[layer.id]?.padAngle ?? 0) / 2,
57
+ endTheta: sector.endTheta -
58
+ Math.sign(sector.endTheta - sector.startTheta) *
59
+ (program.markConfigs[layer.id]?.padAngle ?? 0) / 2,
60
+ innerRadius: sector.innerRadius,
61
+ outerRadius: sector.outerRadius
62
+ },
39
63
  members
40
64
  };
41
65
  });
@@ -10,6 +10,7 @@ import {
10
10
  import { numericExtent } from "../../../grammar/numeric.js";
11
11
  import {
12
12
  readNominalField,
13
+ readScaleField,
13
14
  readTemporalField
14
15
  } from "../../../grammar/scales/index.js";
15
16
  import { findSemanticScale } from "../../../selectors/scales.js";
@@ -52,9 +53,19 @@ function aggregateCellDefinitions(program, layer, dataset) {
52
53
  const offsetChannel = resolveBarOffsetChannel(layer);
53
54
  const offsetScale = program.resolvedScales[layer.encoding?.[offsetChannel]?.scale];
54
55
  const layout = resolveBarColorLayout(layer);
56
+ const stroke = layer.encoding?.stroke;
55
57
 
56
58
  function definition(cell, start, end) {
57
59
  const members = aggregateMembers(dataset.values, layer, cell, channels);
60
+ const strokeValues = stroke === undefined ? undefined : readScaleField(
61
+ members,
62
+ stroke.field,
63
+ stroke.fieldType,
64
+ { temporalUnit: stroke.temporalUnit }
65
+ );
66
+ if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
67
+ throw new Error("Bar stroke requires one value within each aggregate cell.");
68
+ }
58
69
  return {
59
70
  fields: uniqueFields(members),
60
71
  channels: {
@@ -62,6 +73,7 @@ function aggregateCellDefinitions(program, layer, dataset) {
62
73
  [channels.measure]: start,
63
74
  [`${channels.measure}2`]: end,
64
75
  ...(cell.color === undefined ? {} : { color: cell.color }),
76
+ ...(strokeValues === undefined ? {} : { stroke: strokeValues[0] }),
65
77
  ...(layer.encoding?.[offsetChannel] === undefined
66
78
  ? {}
67
79
  : { [offsetChannel]: cell.series ?? cell.color })
@@ -88,8 +100,18 @@ function histogramDefinitions(program, layer, dataset) {
88
100
  resolvedScales: program.resolvedScales
89
101
  });
90
102
  const colorScale = program.resolvedScales[colorEncoding?.scale];
103
+ const stroke = layer.encoding?.stroke;
91
104
  return segments.map(segment => {
92
105
  const colorValue = segment.colorValue ?? colorScale?.domain[segment.category];
106
+ const strokeValues = stroke === undefined ? undefined : readScaleField(
107
+ segment.members,
108
+ stroke.field,
109
+ stroke.fieldType,
110
+ { temporalUnit: stroke.temporalUnit }
111
+ );
112
+ if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
113
+ throw new Error("Histogram stroke requires one value within each bin/series cell.");
114
+ }
93
115
  return {
94
116
  fields: uniqueFields(segment.members),
95
117
  channels: {
@@ -97,7 +119,8 @@ function histogramDefinitions(program, layer, dataset) {
97
119
  x2: segment.end,
98
120
  y: segment.stackStart,
99
121
  y2: segment.stackEnd,
100
- ...(colorValue === undefined ? {} : { color: colorValue })
122
+ ...(colorValue === undefined ? {} : { color: colorValue }),
123
+ ...(strokeValues === undefined ? {} : { stroke: strokeValues[0] })
101
124
  },
102
125
  members: segment.members
103
126
  };
@@ -188,14 +211,18 @@ export function resolveBarItems(program, layer, dataset, selectionGrain) {
188
211
  if (definitions === undefined) {
189
212
  throw new Error(`Bar mark "${layer.id}" is incomplete for selection.`);
190
213
  }
214
+ const graphicTypes = Object.hasOwn(
215
+ program.markConfigs[layer.id]?.barAppearance ?? {},
216
+ "cornerRadius"
217
+ ) ? ["rect", "path"] : "rect";
191
218
  if (selectionGrain === "stack") {
192
219
  return finalizeItems(
193
220
  program,
194
221
  layer,
195
222
  "stack",
196
223
  barStackDefinitions(layer, grain, definitions),
197
- "rect"
224
+ graphicTypes
198
225
  );
199
226
  }
200
- return finalizeItems(program, layer, grain, definitions, "rect");
227
+ return finalizeItems(program, layer, grain, definitions, graphicTypes);
201
228
  }
@@ -6,6 +6,7 @@ import {
6
6
  } from "../../../grammar/markSelection.js";
7
7
  import { unionConcreteGraphicBounds } from
8
8
  "../../../grammar/schemas/graphicBounds.js";
9
+ import { readRectItemGeometry } from "../../../grammar/roundedRect.js";
9
10
 
10
11
  export function itemKey(layer, grain, index) {
11
12
  return `${layer.id}/${grain}/${index}`;
@@ -54,14 +55,15 @@ export function uniqueFields(rows) {
54
55
 
55
56
  function requireResolvedGraphic(program, layer, type) {
56
57
  const graphic = program.graphicSpec.objects[layer.id];
58
+ const types = Array.isArray(type) ? type : [type];
57
59
  const compatibleCollection = graphic?.type === "collection" &&
58
- graphic.items?.every(child => child.type === type);
60
+ graphic.items?.every(child => types.includes(child.type));
59
61
  if (
60
- (graphic?.type !== type && !compatibleCollection) ||
62
+ (!types.includes(graphic?.type) && !compatibleCollection) ||
61
63
  !Array.isArray(graphic.items)
62
64
  ) {
63
65
  throw new Error(
64
- `Mark "${layer.id}" requires a materialized ${type} collection for selection.`
66
+ `Mark "${layer.id}" requires a materialized ${types.join("/")} collection for selection.`
65
67
  );
66
68
  }
67
69
  return graphic;
@@ -78,6 +80,13 @@ export function concreteProperties(properties) {
78
80
  );
79
81
  }
80
82
 
83
+ function selectableGraphicProperties(item) {
84
+ return {
85
+ ...concreteProperties(item?.properties),
86
+ ...(readRectItemGeometry(item) ?? {})
87
+ };
88
+ }
89
+
81
90
  function sharedConcreteProperties(items) {
82
91
  if (items.length === 0) return {};
83
92
  const shared = concreteProperties(items[0].properties);
@@ -93,7 +102,7 @@ function collectionBounds(program, items) {
93
102
  const bounds = unionConcreteGraphicBounds(
94
103
  program.graphicSpec,
95
104
  items.map(item => item.id)
96
- );
105
+ , program.materializationConfigs.textMetrics);
97
106
  if (bounds === undefined) return {};
98
107
  const { left, top, right, bottom } = bounds;
99
108
  return { x: left, y: top, width: right - left, height: bottom - top };
@@ -118,7 +127,10 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
118
127
  const items = indices.map(childIndex => graphic.items[childIndex]);
119
128
  const properties = definition.properties ?? (
120
129
  items.length === 1
121
- ? concreteProperties(items[0].properties)
130
+ ? selectableGraphicProperties({
131
+ type: items[0].type ?? graphic.type,
132
+ properties: items[0].properties
133
+ })
122
134
  : {
123
135
  ...sharedConcreteProperties(items),
124
136
  ...collectionBounds(program, items)
@@ -131,6 +143,9 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
131
143
  fields: cloneAndFreeze(definition.fields),
132
144
  channels: cloneAndFreeze(definition.channels),
133
145
  properties: cloneAndFreeze(properties),
146
+ ...(definition.geometry === undefined
147
+ ? {}
148
+ : { geometry: cloneAndFreeze(definition.geometry) }),
134
149
  members: Object.freeze([...definition.members]),
135
150
  graphicIds: Object.freeze(items.map((child, childOffset) =>
136
151
  child.id ?? graphicId(layer, indices[childOffset])
@@ -1,4 +1,5 @@
1
1
  import { rectUsesFields } from "../../../grammar/rects.js";
2
+ import { readRectItemGeometry } from "../../../grammar/roundedRect.js";
2
3
  import { resolveRectRows } from "../../rect.js";
3
4
  import {
4
5
  channelMapFromRow,
@@ -9,6 +10,17 @@ import {
9
10
  uniqueFields
10
11
  } from "./common.js";
11
12
 
13
+ function rectSelectionProperties(graphic, index) {
14
+ const item = graphic.items[index];
15
+ return {
16
+ ...concreteProperties(item?.properties),
17
+ ...(readRectItemGeometry({
18
+ type: item?.type ?? graphic.type,
19
+ properties: item?.properties
20
+ }) ?? {})
21
+ };
22
+ }
23
+
12
24
  function resolveGradientPlotItems(program, layer, dataset) {
13
25
  const graphic = program.graphicSpec.objects[layer.id];
14
26
  return finalizeItems(
@@ -19,7 +31,7 @@ function resolveGradientPlotItems(program, layer, dataset) {
19
31
  key: itemKey(layer, "rect", index),
20
32
  fields: ownFields(row),
21
33
  channels: channelMapFromRow(row, layer),
22
- properties: concreteProperties(graphic.items[index]?.properties),
34
+ properties: rectSelectionProperties(graphic, index),
23
35
  members: [row]
24
36
  })),
25
37
  "rect"
@@ -28,7 +40,13 @@ function resolveGradientPlotItems(program, layer, dataset) {
28
40
 
29
41
  export function resolveRectItems(program, layer, dataset) {
30
42
  const graphic = program.graphicSpec.objects[layer.id];
31
- if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
43
+ const rounded = Object.hasOwn(program.markConfigs[layer.id] ?? {}, "cornerRadius");
44
+ const compatibleCollection = rounded && graphic?.type === "collection" &&
45
+ graphic.items?.every(item => ["rect", "path"].includes(item.type));
46
+ if (
47
+ (graphic?.type !== "rect" && !compatibleCollection) ||
48
+ !Array.isArray(graphic.items)
49
+ ) {
32
50
  throw new Error(`Rect mark "${layer.id}" is incomplete for selection.`);
33
51
  }
34
52
  if (program.markConfigs[layer.id]?.gradientPlot?.materialized === true) {
@@ -40,8 +58,14 @@ export function resolveRectItems(program, layer, dataset) {
40
58
  key: itemKey(layer, "rect", item.sourceIndex),
41
59
  fields: hasField ? ownFields(item.row) : uniqueFields(dataset.values),
42
60
  channels: channelMapFromRow(item.row, layer),
43
- properties: concreteProperties(graphic.items[graphicIndex]?.properties),
61
+ properties: rectSelectionProperties(graphic, graphicIndex),
44
62
  members: hasField ? [item.row] : dataset.values
45
63
  }));
46
- return finalizeItems(program, layer, "rect", definitions, "rect");
64
+ return finalizeItems(
65
+ program,
66
+ layer,
67
+ "rect",
68
+ definitions,
69
+ rounded ? ["rect", "path"] : "rect"
70
+ );
47
71
  }
@@ -10,7 +10,7 @@ function translateCommand(command, offset) {
10
10
  }
11
11
 
12
12
  export function transformPathHighlightProperties(properties, style) {
13
- const { offset, ...appearance } = style;
13
+ const { offset = { x: 0, y: 0 }, ...appearance } = style;
14
14
  return {
15
15
  ...properties,
16
16
  ...appearance,
@@ -1,5 +1,6 @@
1
1
  import { createPointShapeGraphic } from "../../grammar/pointShapes.js";
2
2
  import { finiteMidpoint } from "../../grammar/numeric.js";
3
+ import { requestedStrokeDetails } from "../../grammar/strokeStyle.js";
3
4
 
4
5
  function pointCenterAndArea(child) {
5
6
  const properties = child.properties;
@@ -75,7 +76,8 @@ export function transformPointHighlightChild(child, style) {
75
76
  ? (child.properties.opacity === undefined ? {} : { opacity: child.properties.opacity })
76
77
  : { opacity: style.opacity }),
77
78
  ...(style.stroke === undefined ? {} : { stroke: style.stroke }),
78
- ...(style.strokeWidth === undefined ? {} : { strokeWidth: style.strokeWidth })
79
+ ...(style.strokeWidth === undefined ? {} : { strokeWidth: style.strokeWidth }),
80
+ ...requestedStrokeDetails(child.properties, "Point highlight source")
79
81
  };
80
82
  if (style.shape !== undefined) {
81
83
  return createPointShapeGraphic({