ggaction 0.0.13 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +41 -12
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/knowledge/task-packet.schema.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +39 -7
  9. package/src/BasicChartProgram.js +3 -1
  10. package/src/ChartProgram.js +2 -0
  11. package/src/accessibility.js +157 -0
  12. package/src/actions/basic.js +2 -2
  13. package/src/actions/boxPlots/components.js +20 -12
  14. package/src/actions/boxPlots/create.js +4 -5
  15. package/src/actions/boxPlots/edit.js +34 -9
  16. package/src/actions/boxPlots/materialize.js +12 -4
  17. package/src/actions/boxPlots/options.js +48 -16
  18. package/src/actions/canvas/actions.js +3 -3
  19. package/src/actions/canvas/fitting.js +1 -1
  20. package/src/actions/categoryOrder/index.js +21 -12
  21. package/src/actions/charts/area.js +6 -2
  22. package/src/actions/charts/bar.js +3 -2
  23. package/src/actions/charts/beeswarm.js +1 -1
  24. package/src/actions/charts/density.js +7 -3
  25. package/src/actions/charts/ecdf.js +6 -3
  26. package/src/actions/charts/endpoints.js +29 -7
  27. package/src/actions/charts/heatmap.js +3 -2
  28. package/src/actions/charts/histogram.js +5 -4
  29. package/src/actions/charts/horizon.js +4 -2
  30. package/src/actions/charts/interval-regression.js +7 -4
  31. package/src/actions/charts/line.js +4 -2
  32. package/src/actions/charts/parallel.js +9 -3
  33. package/src/actions/charts/pie.js +11 -3
  34. package/src/actions/charts/polar.js +17 -6
  35. package/src/actions/charts/radar.js +9 -3
  36. package/src/actions/charts/radial.js +12 -4
  37. package/src/actions/charts/raincloud.js +2 -2
  38. package/src/actions/charts/rug-strip.js +8 -4
  39. package/src/actions/charts/scatter.js +4 -2
  40. package/src/actions/composition/actions.js +29 -24
  41. package/src/actions/coordinates/actions.js +2 -2
  42. package/src/actions/coordinates/edit.js +76 -0
  43. package/src/actions/data/basicBin2d.js +4 -5
  44. package/src/actions/data/bin.js +3 -3
  45. package/src/actions/data/bin2d.js +63 -193
  46. package/src/actions/data/bin2dMaterialize.js +4 -5
  47. package/src/actions/data/box.js +4 -4
  48. package/src/actions/data/complete.js +26 -0
  49. package/src/actions/data/computed.js +4 -4
  50. package/src/actions/data/create.js +7 -14
  51. package/src/actions/data/density.js +15 -23
  52. package/src/actions/data/derived.js +57 -31
  53. package/src/actions/data/ecdf.js +2 -2
  54. package/src/actions/data/edit.js +551 -0
  55. package/src/actions/data/filter.js +28 -22
  56. package/src/actions/data/fold.js +2 -2
  57. package/src/actions/data/gradientProfile.js +2 -2
  58. package/src/actions/data/horizon.js +2 -2
  59. package/src/actions/data/impute.js +26 -0
  60. package/src/actions/data/index.js +62 -47
  61. package/src/actions/data/interval.js +2 -2
  62. package/src/actions/data/intervalEdit.js +26 -18
  63. package/src/actions/data/normalize.js +27 -0
  64. package/src/actions/data/regression.js +21 -27
  65. package/src/actions/data/revise.js +48 -0
  66. package/src/actions/data/revisionBindings.js +125 -0
  67. package/src/actions/data/shared.js +20 -4
  68. package/src/actions/data/stack.js +2 -2
  69. package/src/actions/data/summary.js +3 -3
  70. package/src/actions/data/timeUnit.js +6 -3
  71. package/src/actions/data/window.js +3 -3
  72. package/src/actions/encodings/angle.js +1 -1
  73. package/src/actions/encodings/appearance.js +4 -4
  74. package/src/actions/encodings/barWidth.js +1 -1
  75. package/src/actions/encodings/channels.js +415 -0
  76. package/src/actions/encodings/color/index.js +3 -2
  77. package/src/actions/encodings/density.js +7 -4
  78. package/src/actions/encodings/histogram.js +4 -3
  79. package/src/actions/encodings/horizon.js +6 -8
  80. package/src/actions/encodings/index.js +4 -0
  81. package/src/actions/encodings/offset.js +22 -15
  82. package/src/actions/encodings/parallel.js +4 -5
  83. package/src/actions/encodings/pathOrder.js +6 -11
  84. package/src/actions/encodings/position/apply.js +53 -7
  85. package/src/actions/encodings/position/index.js +14 -6
  86. package/src/actions/encodings/position/policies/bar.js +9 -0
  87. package/src/actions/encodings/position/resolve.js +24 -7
  88. package/src/actions/encodings/ranged.js +5 -5
  89. package/src/actions/encodings/remove.js +13 -9
  90. package/src/actions/encodings/ruleAppearance.js +2 -24
  91. package/src/actions/encodings/seriesLayout.js +2 -1
  92. package/src/actions/encodings/shared.js +82 -14
  93. package/src/actions/encodings/stroke.js +226 -0
  94. package/src/actions/encodings/strokeDash.js +2 -2
  95. package/src/actions/encodings/text.js +1 -1
  96. package/src/actions/errorBands/create.js +34 -20
  97. package/src/actions/errorBands/edit.js +34 -137
  98. package/src/actions/errorBars/create.js +43 -20
  99. package/src/actions/errorBars/edit.js +29 -125
  100. package/src/actions/facets/actions.js +267 -73
  101. package/src/actions/facets/derive.js +99 -28
  102. package/src/actions/facets/guides.js +4 -5
  103. package/src/actions/facets/replay.js +4 -5
  104. package/src/actions/gradientPlots/components.js +15 -8
  105. package/src/actions/gradientPlots/create.js +4 -5
  106. package/src/actions/gradientPlots/edit.js +18 -9
  107. package/src/actions/gradientPlots/materialize.js +5 -3
  108. package/src/actions/gradientPlots/options.js +10 -2
  109. package/src/actions/gradientPlots/rebind.js +4 -5
  110. package/src/actions/guides/applicability.js +8 -2
  111. package/src/actions/guides/axes/axes.js +23 -4
  112. package/src/actions/guides/axes/axis.js +2 -2
  113. package/src/actions/guides/axes/edit.js +2 -2
  114. package/src/actions/guides/axes/labels.js +67 -21
  115. package/src/actions/guides/axes/lines.js +15 -9
  116. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  117. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  118. package/src/actions/guides/axes/parallel.js +5 -5
  119. package/src/actions/guides/axes/remove.js +4 -4
  120. package/src/actions/guides/axes/tickGroups.js +6 -5
  121. package/src/actions/guides/axes/ticks.js +19 -6
  122. package/src/actions/guides/axes/titles.js +21 -13
  123. package/src/actions/guides/grids/grid.js +18 -29
  124. package/src/actions/guides/grids/resolve.js +7 -2
  125. package/src/actions/guides/guides.js +20 -4
  126. package/src/actions/guides/legends/blocks.js +426 -0
  127. package/src/actions/guides/legends/categorical/actions.js +138 -52
  128. package/src/actions/guides/legends/categorical/components.js +55 -45
  129. package/src/actions/guides/legends/categorical/index.js +37 -33
  130. package/src/actions/guides/legends/categorical/layout.js +98 -16
  131. package/src/actions/guides/legends/categorical/options.js +2 -2
  132. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  133. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  134. package/src/actions/guides/legends/categorical/symbols.js +104 -52
  135. package/src/actions/guides/legends/continuous/common.js +76 -20
  136. package/src/actions/guides/legends/continuous/gradient.js +95 -158
  137. package/src/actions/guides/legends/continuous/index.js +2 -0
  138. package/src/actions/guides/legends/continuous/interval.js +21 -16
  139. package/src/actions/guides/legends/continuous/opacity.js +37 -83
  140. package/src/actions/guides/legends/continuous/stroke.js +225 -0
  141. package/src/actions/guides/legends/creation.js +14 -1
  142. package/src/actions/guides/legends/edit.js +159 -60
  143. package/src/actions/guides/legends/focused.js +5 -5
  144. package/src/actions/guides/legends/index.js +2 -0
  145. package/src/actions/guides/legends/lane.js +59 -24
  146. package/src/actions/guides/legends/lifecycle.js +19 -8
  147. package/src/actions/guides/legends/remove.js +17 -8
  148. package/src/actions/guides/legends/sampling.js +140 -0
  149. package/src/actions/guides/legends/size.js +100 -108
  150. package/src/actions/guides/legends/strokeWidth.js +38 -92
  151. package/src/actions/guides/legends/target.js +112 -1
  152. package/src/actions/guides/legends/transition.js +165 -16
  153. package/src/actions/guides/polar/axes/facade.js +8 -8
  154. package/src/actions/guides/polar/axes/labels.js +29 -10
  155. package/src/actions/guides/polar/axes/lines.js +19 -19
  156. package/src/actions/guides/polar/axes/shared.js +16 -0
  157. package/src/actions/guides/polar/axes/ticks.js +10 -16
  158. package/src/actions/guides/polar/axes/titles.js +6 -6
  159. package/src/actions/guides/polar/grids.js +12 -14
  160. package/src/actions/guides/polar/resolve.js +12 -8
  161. package/src/actions/index.js +16 -3
  162. package/src/actions/intervals/revision.js +126 -0
  163. package/src/actions/marks/arc/actions.js +80 -22
  164. package/src/actions/marks/area/actions.js +38 -13
  165. package/src/actions/marks/area/materialize.js +17 -1
  166. package/src/actions/marks/bar/create.js +12 -3
  167. package/src/actions/marks/bar/edit.js +21 -4
  168. package/src/actions/marks/bar/materialize.js +34 -40
  169. package/src/actions/marks/index.js +10 -1
  170. package/src/actions/marks/line/actions.js +40 -17
  171. package/src/actions/marks/line/materialize.js +29 -10
  172. package/src/actions/marks/point/create.js +9 -3
  173. package/src/actions/marks/point/edit.js +19 -7
  174. package/src/actions/marks/point/jitter.js +6 -8
  175. package/src/actions/marks/point/materialize.js +48 -22
  176. package/src/actions/marks/point/packing.js +6 -8
  177. package/src/actions/marks/rect/actions.js +36 -15
  178. package/src/actions/marks/references.js +602 -33
  179. package/src/actions/marks/remove.js +67 -41
  180. package/src/actions/marks/rule/actions.js +51 -14
  181. package/src/actions/marks/shared.js +1 -1
  182. package/src/actions/marks/text/actions.js +79 -14
  183. package/src/actions/marks/text/index.js +4 -0
  184. package/src/actions/marks/text/layout.js +157 -17
  185. package/src/actions/marks/text/removal.js +173 -0
  186. package/src/actions/marks/text/selection.js +72 -0
  187. package/src/actions/marks/tick/actions.js +25 -8
  188. package/src/actions/primitives/createGraphics.js +5 -4
  189. package/src/actions/primitives/editGraphics.js +31 -4
  190. package/src/actions/primitives/graphicProperties.js +6 -0
  191. package/src/actions/primitives/semantic.js +11 -3
  192. package/src/actions/primitives/semanticAction.js +90 -14
  193. package/src/actions/primitives/semanticValidation/dataset.js +2 -4
  194. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  195. package/src/actions/primitives/semanticValidation/index.js +9 -1
  196. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  197. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  198. package/src/actions/regression/components.js +36 -25
  199. package/src/actions/regression/create.js +13 -6
  200. package/src/actions/regression/edit.js +24 -11
  201. package/src/actions/resources/index.js +7 -0
  202. package/src/actions/resources/remove.js +173 -0
  203. package/src/actions/scales/channels.js +212 -0
  204. package/src/actions/scales/consumers/common.js +1 -1
  205. package/src/actions/scales/consumers/families.js +3 -2
  206. package/src/actions/scales/consumers/index.js +19 -2
  207. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  208. package/src/actions/scales/create.js +55 -23
  209. package/src/actions/scales/definitions.js +94 -40
  210. package/src/actions/scales/edit.js +67 -12
  211. package/src/actions/scales/editPolicy.js +81 -11
  212. package/src/actions/scales/index.js +6 -0
  213. package/src/actions/scales/materialize.js +5 -4
  214. package/src/actions/scales/offset.js +48 -0
  215. package/src/actions/scales/parallel.js +71 -0
  216. package/src/actions/scales/preview.js +111 -10
  217. package/src/actions/scales/quantitativeColor.js +4 -5
  218. package/src/actions/selection/actions.js +179 -66
  219. package/src/actions/selection/index.js +3 -0
  220. package/src/actions/textMetrics/index.js +56 -0
  221. package/src/actions/theme/actions.js +70 -15
  222. package/src/actions/theme/composition.js +238 -0
  223. package/src/actions/theme/index.js +9 -2
  224. package/src/actions/theme/reconcile.js +313 -121
  225. package/src/actions/theme/state.js +160 -0
  226. package/src/actions/titles/actions.js +8 -8
  227. package/src/actions/titles/resolve.js +3 -3
  228. package/src/actions/violinPlots/create.js +6 -3
  229. package/src/actions/violinPlots/edit.js +2 -1
  230. package/src/core/action.js +117 -35
  231. package/src/core/compositionState.js +6 -1
  232. package/src/core/diagnostics.js +16 -0
  233. package/src/core/extensionRegistry.js +27 -0
  234. package/src/core/font.js +18 -0
  235. package/src/core/identifiers.js +5 -4
  236. package/src/core/immutable.js +10 -3
  237. package/src/core/optionRequirements.js +8 -0
  238. package/src/core/programState.js +21 -1
  239. package/src/core/resourceReferences.js +341 -0
  240. package/src/core/textMetricProfile.js +28 -0
  241. package/src/core/textMetrics.js +24 -3
  242. package/src/core/validation.js +22 -10
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/diagnostics.js +1 -0
  245. package/src/grammar/bars/aggregate.js +4 -1
  246. package/src/grammar/bars/geometry.js +44 -0
  247. package/src/grammar/bin.js +54 -9
  248. package/src/grammar/complete.js +279 -0
  249. package/src/grammar/computed.js +265 -64
  250. package/src/grammar/coordinates.js +37 -0
  251. package/src/grammar/density.js +239 -37
  252. package/src/grammar/displayLabels.js +57 -0
  253. package/src/grammar/facets/dependencies.js +133 -1
  254. package/src/grammar/facets/guides.js +3 -0
  255. package/src/grammar/facets/index.js +7 -47
  256. package/src/grammar/facets/scales.js +15 -18
  257. package/src/grammar/impute.js +341 -0
  258. package/src/grammar/markLabelSelection.js +57 -0
  259. package/src/grammar/markLabels.js +18 -1
  260. package/src/grammar/normalize.js +366 -0
  261. package/src/grammar/pathSeries.js +26 -8
  262. package/src/grammar/pointShapes.js +13 -4
  263. package/src/grammar/polar.js +90 -8
  264. package/src/grammar/regression/index.js +1 -0
  265. package/src/grammar/regression/parameters.js +23 -0
  266. package/src/grammar/roundedRect.js +180 -0
  267. package/src/grammar/rules.js +1 -0
  268. package/src/grammar/scales/colorConsumers.js +20 -1
  269. package/src/grammar/scales/continuous.js +2 -2
  270. package/src/grammar/scales/definition.js +27 -4
  271. package/src/grammar/scales/index.js +1 -0
  272. package/src/grammar/scales/ordinal.js +12 -3
  273. package/src/grammar/scales/policies.js +2 -2
  274. package/src/grammar/scales/size.js +456 -0
  275. package/src/grammar/scales/types.js +8 -0
  276. package/src/grammar/schemas/concreteGraphic.js +14 -2
  277. package/src/grammar/schemas/graphic.js +15 -0
  278. package/src/grammar/schemas/graphicBounds.js +190 -41
  279. package/src/grammar/schemas/semanticPath.js +7 -2
  280. package/src/grammar/statisticalReference.js +103 -0
  281. package/src/grammar/strokeStyle.js +77 -0
  282. package/src/grammar/summary.js +78 -16
  283. package/src/grammar/timeUnit.js +216 -5
  284. package/src/grammar/transformTopology.js +4 -0
  285. package/src/grammar/transforms.js +394 -28
  286. package/src/grammar/weightedStatistics.js +374 -0
  287. package/src/grammar/window.js +221 -16
  288. package/src/layout/aspect.js +80 -0
  289. package/src/layout/facets.js +59 -5
  290. package/src/layout/labels.js +351 -7
  291. package/src/layout/legend.js +4 -4
  292. package/src/layout/legendItems.js +6 -6
  293. package/src/layout/text.js +10 -9
  294. package/src/layout/title.js +9 -9
  295. package/src/materialization/bars/aggregate.js +25 -2
  296. package/src/materialization/bars/histogram.js +84 -24
  297. package/src/materialization/bars/ranged.js +19 -3
  298. package/src/materialization/bars/resolve.js +7 -4
  299. package/src/materialization/composition.js +3 -1
  300. package/src/materialization/coordinateBounds.js +204 -0
  301. package/src/materialization/dependencies.js +36 -0
  302. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  303. package/src/materialization/facetGuides/placement.js +9 -3
  304. package/src/materialization/facetGuides/preparation.js +13 -8
  305. package/src/materialization/facetHeaders.js +347 -0
  306. package/src/materialization/facets.js +15 -65
  307. package/src/materialization/guides/layout.js +3 -3
  308. package/src/materialization/guides/resources.js +26 -0
  309. package/src/materialization/legends.js +1 -1
  310. package/src/materialization/marks/index.js +11 -5
  311. package/src/materialization/marks/policies.js +2 -1
  312. package/src/materialization/rect.js +25 -12
  313. package/src/materialization/rectConfig.js +5 -1
  314. package/src/materialization/scaleGuideDependencies.js +10 -0
  315. package/src/materialization/scales/map.js +5 -3
  316. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  317. package/src/materialization/scales/policies/offset.js +50 -8
  318. package/src/materialization/scales/policies/series.js +4 -1
  319. package/src/materialization/scales/resolve.js +22 -6
  320. package/src/materialization/selection/items/arc.js +28 -4
  321. package/src/materialization/selection/items/bar.js +30 -3
  322. package/src/materialization/selection/items/common.js +20 -5
  323. package/src/materialization/selection/items/rect.js +28 -4
  324. package/src/materialization/selection/path.js +1 -1
  325. package/src/materialization/selection/point.js +3 -1
  326. package/src/materialization/text.js +265 -14
  327. package/src/materialization/typography.js +31 -0
  328. package/src/mcp/cli.js +5 -3
  329. package/src/persistence/codec.js +65 -0
  330. package/src/persistence/validation.js +185 -0
  331. package/src/persistence.js +74 -0
  332. package/src/renderers/canvas/circle.js +2 -3
  333. package/src/renderers/canvas/index.js +5 -4
  334. package/src/renderers/canvas/line.js +2 -3
  335. package/src/renderers/canvas/native.js +4 -4
  336. package/src/renderers/canvas/path.js +2 -3
  337. package/src/renderers/canvas/rect.js +2 -3
  338. package/src/renderers/canvas/stroke.js +11 -0
  339. package/src/renderers/nativeBackend.js +10 -0
  340. package/src/renderers/options.js +13 -0
  341. package/src/renderers/pdf.js +25 -54
  342. package/src/renderers/png.js +22 -17
  343. package/src/renderers/svg.js +29 -14
  344. package/src/renderers/text.js +1 -11
  345. package/src/selectors/coordinates.js +3 -1
  346. package/src/selectors/datasets.js +38 -5
  347. package/src/selectors/layers.js +10 -4
  348. package/src/selectors/markOwners.js +46 -0
  349. package/src/selectors/scales.js +6 -4
  350. package/src/theme/defaults.js +103 -1
  351. package/src/version.js +2 -0
  352. package/types/accessibility.d.ts +37 -0
  353. package/types/basic.d.ts +10 -2
  354. package/types/diagnostics.d.ts +15 -0
  355. package/types/index.d.ts +90 -0
  356. package/types/pdf.d.ts +9 -0
  357. package/types/persistence.d.ts +7 -0
  358. package/types/png.d.ts +9 -0
  359. package/types/program.d.ts +944 -118
@@ -1,18 +1,24 @@
1
- import { action } from "../../../core/action.js";
1
+ import { action, closedAction } from "../../../core/action.js";
2
2
  import { validateUserId } from "../../../core/identifiers.js";
3
3
  import { validateOptionObject } from "../../../core/validation.js";
4
4
  import { DEFAULT_TEXT_MARK } from "../../../grammar/text.js";
5
+ import { isSourceOwnedText } from "../../../grammar/text.js";
5
6
  import { findGraphicParent } from "../../../grammar/schemas/graphicTree.js";
6
7
  import { validateConcreteGraphicValue } from
7
8
  "../../../grammar/schemas/concreteGraphic.js";
8
9
  import {
10
+ markLabelPlacementLeaderId,
9
11
  normalizeLabelLayoutGeometry,
10
12
  resolveLabelLayout,
11
13
  resolveLabelLeader
12
14
  } from "../../../layout/labels.js";
15
+ import { resolveTextBounds } from "../../../core/textMetrics.js";
13
16
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
14
17
  import { canMaterializeText } from "../../../materialization/marks/index.js";
15
- import { resolveTextGraphicEntries } from "../../../materialization/text.js";
18
+ import {
19
+ resolveTextGraphicEntries,
20
+ validateSourceMarkLabelPlacement
21
+ } from "../../../materialization/text.js";
16
22
  import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
17
23
  import { editMarkGraphic } from "../shared.js";
18
24
 
@@ -21,6 +27,8 @@ export const TEXT_LABEL_LAYOUT_OPTIONS = Object.freeze([
21
27
  ]);
22
28
  const REMOVE_OPTIONS = Object.freeze(["target"]);
23
29
  const MATERIALIZE_OPTIONS = Object.freeze(["id", "rematerializeBase"]);
30
+ const MATERIALIZE_PLACEMENT_OPTIONS = Object.freeze(["id"]);
31
+ const EDIT_PLACEMENT_OPTIONS = Object.freeze(["target", "placement"]);
24
32
  const LEADER_OPTIONS = Object.freeze([
25
33
  "stroke", "strokeWidth", "strokeDash", "opacity"
26
34
  ]);
@@ -166,10 +174,10 @@ function removeLeaderGraphic(program, id) {
166
174
  : program.editGraphics({ target: id, remove: true });
167
175
  }
168
176
 
169
- function materializeLeaders(program, layer, config, resolved) {
177
+ function materializeLeaders(program, layer, config, items) {
170
178
  let next = removeLeaderGraphic(program, config.leaderId);
171
179
  if (config.leader === false) return { program: next, count: 0 };
172
- const leaders = resolved.items.map(resolveLabelLeader).filter(Boolean);
180
+ const leaders = items.map(resolveLabelLeader).filter(Boolean);
173
181
  if (leaders.length === 0) return { program: next, count: 0 };
174
182
  next = next.createGraphics({
175
183
  id: config.leaderId,
@@ -190,13 +198,76 @@ function materializeLeaders(program, layer, config, resolved) {
190
198
  return { program: next, count: leaders.length };
191
199
  }
192
200
 
193
- const materializeLabelLayout = action(
201
+ function placementLeaderItems(program, layer) {
202
+ const entries = resolveTextGraphicEntries(
203
+ program,
204
+ layer,
205
+ program.markConfigs[layer.id] ?? DEFAULT_TEXT_MARK
206
+ );
207
+ const graphic = program.graphicSpec.objects[layer.id];
208
+ if (graphic?.type !== "text" || !Array.isArray(graphic.items)) {
209
+ throw new Error(`Text mark "${layer.id}" requires text collection graphics.`);
210
+ }
211
+ if (entries.length !== graphic.items.length) {
212
+ throw new Error(`Text mark "${layer.id}" placement materialization is inconsistent.`);
213
+ }
214
+ return entries.map((entry, index) => {
215
+ const properties = graphic.items[index].properties;
216
+ return {
217
+ id: graphic.items[index].id,
218
+ x: properties.x,
219
+ y: properties.y,
220
+ sourceX: entry.anchor.x,
221
+ sourceY: entry.anchor.y,
222
+ dx: properties.x - entry.anchor.x,
223
+ dy: properties.y - entry.anchor.y,
224
+ bounds: resolveTextBounds(properties, program.materializationConfigs.textMetrics)
225
+ };
226
+ });
227
+ }
228
+
229
+ const materializeMarkLabelPlacement = /* @__PURE__ */ closedAction(
230
+ {
231
+ op: "materializeMarkLabelPlacement",
232
+ description: "Reconcile semantic attached-label placement leaders."
233
+ }, MATERIALIZE_PLACEMENT_OPTIONS,
234
+ function (args = {}) {
235
+ const id = validateUserId(args.id, "Attached label id");
236
+ const layer = findLayer(this, id);
237
+ if (!isSourceOwnedText(layer)) {
238
+ throw new Error(`Unknown attached label target "${id}".`);
239
+ }
240
+ const leaderId = markLabelPlacementLeaderId(id);
241
+ const placement = this.markConfigs[id]?.labelAuthoring?.placement;
242
+ if (placement?.leader === undefined || placement.leader === false) {
243
+ return this;
244
+ }
245
+ const layout = this.materializationConfigs.labelLayouts?.[id];
246
+ if (layout !== undefined && layout.leader !== false) {
247
+ throw new Error(
248
+ "Semantic label placement leader conflicts with the active collision-layout leader."
249
+ );
250
+ }
251
+ if (!canMaterializeText(this, layer)) {
252
+ return removeLeaderGraphic(this, leaderId);
253
+ }
254
+ return materializeLeaders(this, layer, {
255
+ leaderId,
256
+ leader: {
257
+ ...placement.leader,
258
+ strokeDash: [],
259
+ opacity: 1
260
+ }
261
+ }, placementLeaderItems(this, layer)).program;
262
+ }
263
+ );
264
+
265
+ const materializeLabelLayout = /* @__PURE__ */ closedAction(
194
266
  {
195
267
  op: "materializeLabelLayout",
196
268
  description: "Resolve concrete label positions and optional leader lines."
197
- },
269
+ }, MATERIALIZE_OPTIONS,
198
270
  function (args = {}) {
199
- validateOptionObject(args, MATERIALIZE_OPTIONS, "materializeLabelLayout");
200
271
  const id = validateUserId(args.id, "Text mark id");
201
272
  const rematerializeBase = args.rematerializeBase ?? true;
202
273
  if (typeof rematerializeBase !== "boolean") {
@@ -217,14 +288,14 @@ const materializeLabelLayout = action(
217
288
  axis: config.axis,
218
289
  padding: config.padding,
219
290
  maxDisplacement: config.maxDisplacement
220
- });
291
+ }, next.materializationConfigs.textMetrics);
221
292
  next = editMarkGraphic(next, id, {
222
293
  x: resolved.items.map(item => item.x),
223
294
  y: resolved.items.map(item => item.y)
224
295
  });
225
- const leaders = materializeLeaders(next, layer, config, resolved);
296
+ const leaders = materializeLeaders(next, layer, config, resolved.items);
226
297
  next = leaders.program;
227
- return next._withMaterializationConfig(["labelLayouts", id], {
298
+ next = next._withMaterializationConfig(["labelLayouts", id], {
228
299
  axis: config.axis,
229
300
  padding: config.padding,
230
301
  maxDisplacement: config.maxDisplacement,
@@ -243,18 +314,26 @@ const materializeLabelLayout = action(
243
314
  warnings: resolved.warnings
244
315
  }
245
316
  });
317
+ return layer.source === undefined
318
+ ? next
319
+ : next.materializeMarkLabelPlacement({ id });
246
320
  }
247
321
  );
248
322
 
249
- const layoutLabels = action(
323
+ const layoutLabels = /* @__PURE__ */ closedAction(
250
324
  {
251
325
  op: "layoutLabels",
252
326
  description: "Assign deterministic collision-aware layout to a text mark."
253
- },
327
+ }, TEXT_LABEL_LAYOUT_OPTIONS,
254
328
  function (args = {}) {
255
- validateOptionObject(args, TEXT_LABEL_LAYOUT_OPTIONS, "layoutLabels");
256
329
  const layer = requireCompleteText(this, args.target, "layoutLabels");
257
330
  const policy = normalizePolicy(args);
331
+ const placement = this.markConfigs[layer.id]?.labelAuthoring?.placement;
332
+ if (policy.leader !== false && placement?.leader !== false && placement?.leader !== undefined) {
333
+ throw new Error(
334
+ "Collision-layout leader conflicts with the active semantic label placement leader."
335
+ );
336
+ }
258
337
  const generatedId = leaderId(layer.id);
259
338
  const existingConfig = this.materializationConfigs.labelLayouts?.[layer.id];
260
339
  if (
@@ -272,13 +351,72 @@ const layoutLabels = action(
272
351
  }
273
352
  );
274
353
 
275
- const removeLabelLayout = action(
354
+ const editMarkLabelPlacement = /* @__PURE__ */ action(
355
+ {
356
+ op: "editMarkLabelPlacement",
357
+ description: "Replace or reset semantic placement for one attached label layer."
358
+ },
359
+ function (args = {}) {
360
+ validateOptionObject(args, EDIT_PLACEMENT_OPTIONS, "editMarkLabelPlacement", {
361
+ allowEmpty: false,
362
+ emptyError: Error
363
+ });
364
+ if (!Object.hasOwn(args, "target") || !Object.hasOwn(args, "placement")) {
365
+ throw new Error("editMarkLabelPlacement requires target and placement.");
366
+ }
367
+ const id = validateUserId(args.target, "Attached label id");
368
+ const layer = findLayer(this, id);
369
+ if (!isSourceOwnedText(layer)) {
370
+ throw new Error(`Unknown attached label target "${id}".`);
371
+ }
372
+ let placement;
373
+ if (args.placement !== "auto") {
374
+ placement = validateSourceMarkLabelPlacement(this, layer.source, args.placement);
375
+ if (
376
+ placement.leader !== false &&
377
+ this.materializationConfigs.labelLayouts?.[id]?.leader !== false &&
378
+ this.materializationConfigs.labelLayouts?.[id]?.leader !== undefined
379
+ ) {
380
+ throw new Error(
381
+ "Semantic label placement leader conflicts with the active collision-layout leader."
382
+ );
383
+ }
384
+ const generatedId = markLabelPlacementLeaderId(id);
385
+ const previous = this.markConfigs[id]?.labelAuthoring?.placement;
386
+ if (
387
+ placement.leader !== false &&
388
+ this.graphicSpec.objects[generatedId] !== undefined &&
389
+ (previous?.leader === undefined || previous.leader === false)
390
+ ) {
391
+ throw new Error(`Mark label placement leader graphic "${generatedId}" already exists.`);
392
+ }
393
+ }
394
+ const config = this.markConfigs[id] ?? DEFAULT_TEXT_MARK;
395
+ const previousPlacement = config.labelAuthoring?.placement;
396
+ let base = this;
397
+ if (
398
+ previousPlacement?.leader !== undefined &&
399
+ previousPlacement.leader !== false &&
400
+ (placement === undefined || placement.leader === false)
401
+ ) {
402
+ base = removeLeaderGraphic(base, markLabelPlacementLeaderId(id));
403
+ }
404
+ const labelAuthoring = { ...config.labelAuthoring };
405
+ if (placement === undefined) delete labelAuthoring.placement;
406
+ else labelAuthoring.placement = placement;
407
+ const next = base._withMarkConfig(id, { ...config, labelAuthoring });
408
+ return canMaterializeText(next, layer)
409
+ ? next.rematerializeTextMark({ id })
410
+ : next.materializeMarkLabelPlacement({ id });
411
+ }
412
+ );
413
+
414
+ const removeLabelLayout = /* @__PURE__ */ closedAction(
276
415
  {
277
416
  op: "removeLabelLayout",
278
417
  description: "Remove label layout and restore semantic base text positions."
279
- },
418
+ }, REMOVE_OPTIONS,
280
419
  function (args = {}) {
281
- validateOptionObject(args, REMOVE_OPTIONS, "removeLabelLayout");
282
420
  const layer = requireConfiguredText(this, args.target);
283
421
  const config = this.materializationConfigs.labelLayouts[layer.id];
284
422
  return removeLeaderGraphic(
@@ -292,6 +430,8 @@ export function registerTextLabelLayoutActions(ProgramClass) {
292
430
  Object.assign(ProgramClass.prototype, {
293
431
  layoutLabels,
294
432
  removeLabelLayout,
295
- materializeLabelLayout
433
+ materializeLabelLayout,
434
+ editMarkLabelPlacement,
435
+ materializeMarkLabelPlacement
296
436
  });
297
437
  }
@@ -0,0 +1,173 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import { validateOptionObject } from "../../../core/validation.js";
4
+ import { isSourceOwnedText } from "../../../grammar/text.js";
5
+ import { isTextSource } from "../../../materialization/marks/index.js";
6
+ import { findLayer } from "../../../selectors/layers.js";
7
+ import { markLabelPlacementLeaderId } from "../../../layout/labels.js";
8
+
9
+ const REMOVE_MARK_LABELS_OPTIONS = Object.freeze(["target", "source"]);
10
+
11
+ function compareIds(left, right) {
12
+ return left < right ? -1 : left > right ? 1 : 0;
13
+ }
14
+
15
+ function attachedLabels(program, source) {
16
+ return program.semanticSpec.layers
17
+ .filter(layer => isSourceOwnedText(layer) && layer.source === source)
18
+ .sort((left, right) => compareIds(left.id, right.id));
19
+ }
20
+
21
+ function resolveAttachedLabelTargets(program, args) {
22
+ const hasTarget = Object.hasOwn(args, "target");
23
+ const hasSource = Object.hasOwn(args, "source");
24
+ if (hasTarget === hasSource) {
25
+ throw new Error("removeMarkLabels requires exactly one of target or source.");
26
+ }
27
+ if (hasTarget) {
28
+ const target = validateUserId(args.target, "Attached label id");
29
+ const layer = findLayer(program, target);
30
+ if (!isSourceOwnedText(layer) || !isTextSource(findLayer(program, layer.source))) {
31
+ throw new Error(`Unknown attached label target "${target}".`);
32
+ }
33
+ return [layer];
34
+ }
35
+ const source = validateUserId(args.source, "Label source id");
36
+ const layer = findLayer(program, source);
37
+ if (!isTextSource(layer)) {
38
+ throw new Error(`Unknown label source "${source}".`);
39
+ }
40
+ return attachedLabels(program, source);
41
+ }
42
+
43
+ function collectAttachedLabelRemoval(program, labels) {
44
+ const labelIds = labels.map(layer => layer.id);
45
+ const targets = new Set(labelIds);
46
+ const sourceByLabel = new Map(labels.map(layer => [layer.id, layer.source]));
47
+ const leaderIds = labelIds.flatMap(id => {
48
+ const ids = [];
49
+ const layoutLeader = program.materializationConfigs.labelLayouts?.[id]?.leaderId;
50
+ if (layoutLeader !== undefined) ids.push(layoutLeader);
51
+ const placement = program.markConfigs[id]?.labelAuthoring?.placement;
52
+ if (placement?.leader !== undefined && placement.leader !== false) {
53
+ ids.push(markLabelPlacementLeaderId(id));
54
+ }
55
+ return ids;
56
+ });
57
+ const selectionIds = Object.entries(
58
+ program.materializationConfigs.selections ?? {}
59
+ ).filter(([, config]) => targets.has(config.target)).map(([id]) => id).sort();
60
+ const selected = new Set(selectionIds);
61
+ const highlightIds = Object.entries(
62
+ program.materializationConfigs.highlights ?? {}
63
+ ).filter(([, config]) =>
64
+ targets.has(config.target) || selected.has(config.selection)
65
+ ).map(([id]) => id).sort();
66
+ return Object.freeze({
67
+ sourceByLabel,
68
+ labelIds: Object.freeze(labelIds),
69
+ leaderIds: Object.freeze([...new Set(leaderIds)].sort()),
70
+ selectionIds: Object.freeze(selectionIds),
71
+ highlightIds: Object.freeze(highlightIds)
72
+ });
73
+ }
74
+
75
+ function externalAttachedLabelReferences(program, plan) {
76
+ const labels = new Set(plan.labelIds);
77
+ const leaders = new Set(plan.leaderIds);
78
+ const selections = new Set(plan.selectionIds);
79
+ const references = [];
80
+ for (const layer of program.semanticSpec.layers) {
81
+ if (!labels.has(layer.id) && labels.has(layer.source)) {
82
+ references.push(`layer[${layer.id}].source`);
83
+ }
84
+ }
85
+ for (const [kind, config] of Object.entries(program.guideConfigs.legend ?? {})) {
86
+ if (labels.has(config?.target)) references.push(`guide.legend.${kind}.target`);
87
+ }
88
+ for (const [id, config] of Object.entries(
89
+ program.materializationConfigs.labelLayouts ?? {}
90
+ )) {
91
+ if (!labels.has(id) && leaders.has(config?.leaderId)) {
92
+ references.push(`materialization.labelLayouts[${id}].leaderId`);
93
+ }
94
+ }
95
+ for (const [id, config] of Object.entries(program.markConfigs)) {
96
+ if (labels.has(id)) continue;
97
+ const selection = config?.labelAuthoring?.selection;
98
+ if (selection?.kind === "named" && selections.has(selection.id)) {
99
+ references.push(`markConfig[${id}].labelAuthoring.selection`);
100
+ }
101
+ }
102
+ return references.sort();
103
+ }
104
+
105
+ function removeLabelState(program, plan) {
106
+ let next = program;
107
+ for (const id of plan.highlightIds) {
108
+ next = next._withoutMaterializationConfig(["highlights", id]);
109
+ }
110
+ for (const id of plan.selectionIds) {
111
+ next = next._withoutMaterializationConfig(["selections", id]);
112
+ }
113
+ for (const id of plan.labelIds) {
114
+ next = next
115
+ ._withoutMaterializationConfig(["labelLayouts", id])
116
+ ._withoutMaterializationConfig(["marks", id])
117
+ ._withoutMaterializationConfig(["jitters", id])
118
+ ._withoutMaterializationConfig(["pointPacking", id]);
119
+ }
120
+ for (const id of plan.leaderIds) {
121
+ if (next.graphicSpec.objects[id] !== undefined) {
122
+ next = next.editGraphics({ target: id, remove: true });
123
+ }
124
+ }
125
+ for (const id of plan.labelIds) {
126
+ if (next.graphicSpec.objects[id] !== undefined) {
127
+ next = next.editGraphics({ target: id, remove: true });
128
+ }
129
+ if (findLayer(next, id) !== undefined) {
130
+ next = next.editSemantic({ property: `layer[${id}]`, remove: true });
131
+ }
132
+ }
133
+ const currentLabel = plan.sourceByLabel.get(program.context.currentMark);
134
+ const context = {
135
+ ...(currentLabel === undefined ? {} : { currentMark: currentLabel }),
136
+ ...(plan.selectionIds.includes(program.context.currentSelection)
137
+ ? { currentSelection: undefined }
138
+ : {})
139
+ };
140
+ return Object.keys(context).length === 0 ? next : next._withContext(context);
141
+ }
142
+
143
+ export const removeMarkLabels = /* @__PURE__ */ action(
144
+ {
145
+ op: "removeMarkLabels",
146
+ description: "Remove attached label layers while preserving their source mark."
147
+ },
148
+ function (args = {}) {
149
+ validateOptionObject(
150
+ args,
151
+ REMOVE_MARK_LABELS_OPTIONS,
152
+ "removeMarkLabels",
153
+ {
154
+ allowEmpty: false,
155
+ emptyError: Error,
156
+ emptyMessage: "removeMarkLabels requires exactly one of target or source."
157
+ }
158
+ );
159
+ const labels = resolveAttachedLabelTargets(this, args);
160
+ const plan = collectAttachedLabelRemoval(this, labels);
161
+ const external = externalAttachedLabelReferences(this, plan);
162
+ if (external.length > 0) {
163
+ throw new Error(
164
+ `removeMarkLabels cannot remove externally referenced labels: ${external.join(", ")}.`
165
+ );
166
+ }
167
+ return removeLabelState(this, plan);
168
+ }
169
+ );
170
+
171
+ export function registerTextLabelRemovalActions(ProgramClass) {
172
+ ProgramClass.prototype.removeMarkLabels = removeMarkLabels;
173
+ }
@@ -0,0 +1,72 @@
1
+ import { closedAction } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+
4
+ import { normalizeLabelSelectionEdit } from
5
+ "../../../grammar/markLabelSelection.js";
6
+ import { isSourceOwnedText } from "../../../grammar/text.js";
7
+ import { isTextSource } from "../../../materialization/marks/index.js";
8
+ import { resolveMarkSelection } from
9
+ "../../../materialization/selection/state.js";
10
+ import { findLayer } from "../../../selectors/layers.js";
11
+ import { rematerializeLabelsBeforeHighlights } from
12
+ "../../selection/actions.js";
13
+
14
+ const OPTIONS = Object.freeze(["target", "select", "selection", "all"]);
15
+
16
+ function requireAttachedLabel(program, target) {
17
+ const id = validateUserId(target, "Attached label id");
18
+ const layer = findLayer(program, id);
19
+ if (!isSourceOwnedText(layer) || !isTextSource(findLayer(program, layer.source))) {
20
+ throw new Error(`Unknown attached label target "${id}".`);
21
+ }
22
+ return layer;
23
+ }
24
+
25
+ function validateSelectionSource(program, source, requested) {
26
+ if (requested.kind === "all") return;
27
+ if (requested.kind === "inline") {
28
+ resolveMarkSelection(program, source, requested.selector);
29
+ return;
30
+ }
31
+ const definition = program.materializationConfigs.selections?.[requested.id];
32
+ if (definition === undefined) {
33
+ throw new Error(`Unknown label selection "${requested.id}".`);
34
+ }
35
+ if (definition.target !== source) {
36
+ throw new Error(
37
+ `Label selection "${requested.id}" must target source mark "${source}".`
38
+ );
39
+ }
40
+ resolveMarkSelection(program, source, definition.selector);
41
+ }
42
+
43
+ const editMarkLabelSelection = /* @__PURE__ */ closedAction(
44
+ {
45
+ op: "editMarkLabelSelection",
46
+ description: "Replace the final-item membership requested by an attached label."
47
+ }, OPTIONS,
48
+ function (args = {}) {
49
+ if (!Object.hasOwn(args, "target")) {
50
+ throw new Error("editMarkLabelSelection requires target.");
51
+ }
52
+ const layer = requireAttachedLabel(this, args.target);
53
+ const selection = normalizeLabelSelectionEdit(args);
54
+ validateSelectionSource(this, layer.source, selection);
55
+ const next = this._withMarkConfig(layer.id, {
56
+ ...this.markConfigs[layer.id],
57
+ labelAuthoring: {
58
+ ...this.markConfigs[layer.id]?.labelAuthoring,
59
+ selection
60
+ }
61
+ });
62
+ return rematerializeLabelsBeforeHighlights(
63
+ next,
64
+ layer.source,
65
+ [layer.id]
66
+ );
67
+ }
68
+ );
69
+
70
+ export function registerTextLabelSelectionActions(ProgramClass) {
71
+ ProgramClass.prototype.editMarkLabelSelection = editMarkLabelSelection;
72
+ }
@@ -17,7 +17,12 @@ import { resolveRowEncodingValues } from
17
17
  import { findDataset } from "../../../selectors/datasets.js";
18
18
  import { findLayer } from "../../../selectors/layers.js";
19
19
  import { DEFAULT_COLORS } from "../../../theme/defaults.js";
20
+ import {
21
+ requestedStrokeDetails,
22
+ STROKE_STYLE_PROPERTIES
23
+ } from "../../../grammar/strokeStyle.js";
20
24
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
25
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
21
26
  import {
22
27
  applyLayeredMarkInheritance,
23
28
  assertMarkAvailable,
@@ -30,7 +35,7 @@ import {
30
35
  } from "../shared.js";
31
36
 
32
37
  const APPEARANCE_OPTIONS = Object.freeze([
33
- "length", "stroke", "strokeWidth", "opacity"
38
+ "length", "stroke", "strokeWidth", "opacity", ...STROKE_STYLE_PROPERTIES
34
39
  ]);
35
40
  const CREATE_OPTIONS = Object.freeze(["id", "data", ...APPEARANCE_OPTIONS]);
36
41
  const EDIT_OPTIONS = Object.freeze(["target", ...APPEARANCE_OPTIONS]);
@@ -66,6 +71,7 @@ function resolveTick(program, requested, operation) {
66
71
 
67
72
  function validateTickConfig(args, current = DEFAULT_TICK_CONFIG) {
68
73
  return {
74
+ ...current,
69
75
  length: Object.hasOwn(args, "length")
70
76
  ? validatePositiveFinite(args.length, "Tick length")
71
77
  : current.length,
@@ -77,17 +83,19 @@ function validateTickConfig(args, current = DEFAULT_TICK_CONFIG) {
77
83
  : current.strokeWidth,
78
84
  opacity: Object.hasOwn(args, "opacity")
79
85
  ? validateUnitInterval(args.opacity, "Tick opacity")
80
- : current.opacity
86
+ : current.opacity,
87
+ ...requestedStrokeDetails(args, "Tick")
81
88
  };
82
89
  }
83
90
 
84
- export const createTickMark = action(
91
+ export const createTickMark = /* @__PURE__ */ action(
85
92
  {
86
93
  op: "createTickMark",
87
94
  description: "Create a centered fixed-length Tick mark."
88
95
  },
89
96
  function (args = {}) {
90
97
  validateMarkOptions(args, CREATE_OPTIONS, "createTickMark");
98
+ requestedStrokeDetails(args, "createTickMark");
91
99
  const id = resolveMarkId(this, args.id, {
92
100
  defaultId: "tick",
93
101
  label: "Tick mark id",
@@ -125,30 +133,37 @@ export const createTickMark = action(
125
133
  }
126
134
  );
127
135
 
128
- export const editTickMark = action(
136
+ export const editTickMark = /* @__PURE__ */ action(
129
137
  {
130
138
  op: "editTickMark",
131
139
  description: "Edit Tick length and constant line appearance."
132
140
  },
133
141
  function (args = {}) {
134
142
  validateMarkOptions(args, EDIT_OPTIONS, "editTickMark");
143
+ requestedStrokeDetails(args, "editTickMark");
135
144
  if (!APPEARANCE_OPTIONS.some(property => Object.hasOwn(args, property))) {
136
145
  throw new Error(
137
146
  "editTickMark requires length, stroke, strokeWidth, or opacity."
138
147
  );
139
148
  }
140
149
  const layer = resolveTick(this, args.target, "editTickMark");
150
+ if (Object.hasOwn(args, "stroke") && layer.encoding?.stroke !== undefined) {
151
+ throw new Error(
152
+ "editTickMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
153
+ );
154
+ }
141
155
  const config = validateTickConfig(args, {
142
156
  ...DEFAULT_TICK_CONFIG,
143
157
  ...this.markConfigs[layer.id]
144
158
  });
145
- return this
159
+ const materialized = this
146
160
  ._withMarkConfig(layer.id, config)
147
161
  .rematerializeTickMark({ id: layer.id });
162
+ return rematerializeExistingLegend(materialized);
148
163
  }
149
164
  );
150
165
 
151
- export const rematerializeTickMark = action(
166
+ export const rematerializeTickMark = /* @__PURE__ */ action(
152
167
  {
153
168
  op: "rematerializeTickMark",
154
169
  description: "Recompute concrete centered Tick endpoints and appearance."
@@ -190,6 +205,7 @@ export const rematerializeTickMark = action(
190
205
 
191
206
  const x = resolveRowEncodingValues(this, layer, dataset, "x");
192
207
  const y = resolveRowEncodingValues(this, layer, dataset, "y");
208
+ const stroke = resolveRowEncodingValues(this, layer, dataset, "stroke");
193
209
  const angles = resolveDirectionValues(dataset.values, layer.encoding?.angle);
194
210
  const config = validateTickConfig({}, {
195
211
  ...DEFAULT_TICK_CONFIG,
@@ -210,9 +226,10 @@ export const rematerializeTickMark = action(
210
226
  y1: segments.map(item => item.y1),
211
227
  x2: segments.map(item => item.x2),
212
228
  y2: segments.map(item => item.y2),
213
- stroke: config.stroke,
229
+ stroke: stroke ?? config.stroke,
214
230
  strokeWidth: config.strokeWidth,
215
- opacity: config.opacity
231
+ opacity: config.opacity,
232
+ ...requestedStrokeDetails(config, "Tick mark")
216
233
  });
217
234
  }
218
235
  );
@@ -1,4 +1,4 @@
1
- import { action } from "../../core/action.js";
1
+ import { closedAction } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
3
  import { freezeOwned } from "../../core/immutable.js";
4
4
  import { validateGeneratedItemLimit } from "../../core/validation.js";
@@ -100,13 +100,14 @@ function insertSibling(siblings, id, { before, after }) {
100
100
  return freezeOwned(next);
101
101
  }
102
102
 
103
- const createGraphics = action(
103
+ const createGraphics = /* @__PURE__ */ closedAction(
104
104
  {
105
105
  op: "createGraphics",
106
106
  description: "Create and optionally attach a concrete graphic.",
107
107
  scope: "any"
108
- },
109
- function ({ id, type, length, parent, before, after } = {}) {
108
+ }, ["id", "type", "length", "parent", "before", "after"],
109
+ function (args = {}) {
110
+ const { id, type, length, parent, before, after } = args;
110
111
  if (typeof id !== "string" || !GRAPHIC_ID_PATTERN.test(id)) {
111
112
  throw new TypeError(
112
113
  "createGraphics requires an id containing only letters, numbers, _ or -."