ggaction 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +20 -11
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +10 -3
  11. package/src/actions/basic.js +4 -2
  12. package/src/actions/boxPlots/components.js +14 -4
  13. package/src/actions/boxPlots/edit.js +31 -5
  14. package/src/actions/boxPlots/materialize.js +13 -4
  15. package/src/actions/boxPlots/options.js +48 -16
  16. package/src/actions/canvas/actions.js +3 -1
  17. package/src/actions/canvas/fitting.js +211 -0
  18. package/src/actions/canvas/index.js +2 -0
  19. package/src/actions/categoryOrder/index.js +18 -6
  20. package/src/actions/charts/area.js +51 -0
  21. package/src/actions/charts/bar.js +13 -6
  22. package/src/actions/charts/beeswarm.js +70 -0
  23. package/src/actions/charts/density.js +48 -0
  24. package/src/actions/charts/ecdf.js +186 -0
  25. package/src/actions/charts/endpoints.js +473 -0
  26. package/src/actions/charts/heatmap.js +4 -3
  27. package/src/actions/charts/histogram.js +5 -4
  28. package/src/actions/charts/horizon.js +73 -0
  29. package/src/actions/charts/index.js +37 -0
  30. package/src/actions/charts/interval-regression.js +172 -0
  31. package/src/actions/charts/line.js +8 -6
  32. package/src/actions/charts/parallel.js +8 -2
  33. package/src/actions/charts/pie.js +52 -0
  34. package/src/actions/charts/polar.js +134 -0
  35. package/src/actions/charts/radar.js +305 -0
  36. package/src/actions/charts/radial.js +50 -0
  37. package/src/actions/charts/raincloud.js +422 -0
  38. package/src/actions/charts/rug-strip.js +283 -0
  39. package/src/actions/charts/scatter.js +11 -3
  40. package/src/actions/charts/shared.js +69 -10
  41. package/src/actions/composition/actions.js +129 -3
  42. package/src/actions/coordinates/actions.js +1 -1
  43. package/src/actions/coordinates/edit.js +76 -0
  44. package/src/actions/data/bin.js +29 -0
  45. package/src/actions/data/bin2d.js +57 -185
  46. package/src/actions/data/complete.js +26 -0
  47. package/src/actions/data/computed.js +24 -0
  48. package/src/actions/data/create.js +2 -2
  49. package/src/actions/data/density.js +12 -20
  50. package/src/actions/data/derived.js +111 -15
  51. package/src/actions/data/ecdf.js +38 -0
  52. package/src/actions/data/edit.js +539 -0
  53. package/src/actions/data/filter.js +226 -30
  54. package/src/actions/data/fold.js +24 -0
  55. package/src/actions/data/impute.js +26 -0
  56. package/src/actions/data/index.js +38 -0
  57. package/src/actions/data/interval.js +2 -0
  58. package/src/actions/data/intervalEdit.js +86 -4
  59. package/src/actions/data/normalize.js +27 -0
  60. package/src/actions/data/regression.js +19 -21
  61. package/src/actions/data/revisionBindings.js +145 -0
  62. package/src/actions/data/shared.js +20 -4
  63. package/src/actions/data/stack.js +26 -0
  64. package/src/actions/data/summary.js +26 -0
  65. package/src/actions/data/timeUnit.js +4 -1
  66. package/src/actions/data/window.js +1 -1
  67. package/src/actions/encodings/appearance.js +32 -13
  68. package/src/actions/encodings/barWidth.js +8 -0
  69. package/src/actions/encodings/basic.js +2 -0
  70. package/src/actions/encodings/channels.js +415 -0
  71. package/src/actions/encodings/color/continuous.js +19 -59
  72. package/src/actions/encodings/color/index.js +30 -23
  73. package/src/actions/encodings/color/layout.js +3 -97
  74. package/src/actions/encodings/color/policy.js +5 -8
  75. package/src/actions/encodings/density/resolve.js +37 -5
  76. package/src/actions/encodings/density.js +124 -34
  77. package/src/actions/encodings/histogram.js +3 -2
  78. package/src/actions/encodings/horizon/resolve.js +8 -4
  79. package/src/actions/encodings/horizon.js +18 -8
  80. package/src/actions/encodings/index.js +6 -0
  81. package/src/actions/encodings/offset.js +30 -29
  82. package/src/actions/encodings/parallel.js +2 -8
  83. package/src/actions/encodings/position/apply.js +63 -11
  84. package/src/actions/encodings/position/index.js +52 -6
  85. package/src/actions/encodings/position/policies/arc.js +14 -0
  86. package/src/actions/encodings/position/policies/area.js +4 -42
  87. package/src/actions/encodings/position/policies/bar.js +57 -13
  88. package/src/actions/encodings/position/resolve.js +88 -26
  89. package/src/actions/encodings/ranged.js +123 -97
  90. package/src/actions/encodings/remove.js +44 -34
  91. package/src/actions/encodings/ruleAppearance.js +24 -32
  92. package/src/actions/encodings/seriesLayout.js +105 -0
  93. package/src/actions/encodings/shared.js +107 -37
  94. package/src/actions/encodings/stroke.js +226 -0
  95. package/src/actions/encodings/strokeDash.js +9 -6
  96. package/src/actions/encodings/temporal.js +7 -0
  97. package/src/actions/encodings/text.js +30 -15
  98. package/src/actions/errorBands/create.js +41 -8
  99. package/src/actions/errorBands/edit.js +360 -25
  100. package/src/actions/errorBars/create.js +56 -17
  101. package/src/actions/errorBars/edit.js +372 -21
  102. package/src/actions/facets/actions.js +619 -41
  103. package/src/actions/facets/derive.js +139 -25
  104. package/src/actions/gradientPlots/components.js +43 -13
  105. package/src/actions/gradientPlots/edit.js +14 -4
  106. package/src/actions/gradientPlots/materialize.js +8 -8
  107. package/src/actions/gradientPlots/options.js +10 -2
  108. package/src/actions/guides/applicability.js +28 -19
  109. package/src/actions/guides/axes/axes.js +4 -3
  110. package/src/actions/guides/axes/axis.js +18 -18
  111. package/src/actions/guides/axes/components.js +43 -0
  112. package/src/actions/guides/axes/edit.js +16 -51
  113. package/src/actions/guides/axes/labels.js +184 -28
  114. package/src/actions/guides/axes/lines.js +13 -7
  115. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  116. package/src/actions/guides/axes/parallel/policy.js +137 -0
  117. package/src/actions/guides/axes/parallel/resolve.js +50 -25
  118. package/src/actions/guides/axes/parallel.js +93 -217
  119. package/src/actions/guides/axes/policy.js +21 -67
  120. package/src/actions/guides/axes/tickGroups.js +15 -8
  121. package/src/actions/guides/axes/ticks.js +11 -6
  122. package/src/actions/guides/axes/titles.js +32 -11
  123. package/src/actions/guides/basic.js +2 -0
  124. package/src/actions/guides/facade.js +106 -0
  125. package/src/actions/guides/facadeAxes.js +200 -0
  126. package/src/actions/guides/facadeLegend.js +63 -0
  127. package/src/actions/guides/grids/grid.js +37 -33
  128. package/src/actions/guides/grids/resolve.js +17 -4
  129. package/src/actions/guides/guides.js +54 -31
  130. package/src/actions/guides/legends/blocks.js +426 -0
  131. package/src/actions/guides/legends/categorical/actions.js +309 -279
  132. package/src/actions/guides/legends/categorical/components.js +21 -17
  133. package/src/actions/guides/legends/categorical/index.js +37 -33
  134. package/src/actions/guides/legends/categorical/layout.js +123 -354
  135. package/src/actions/guides/legends/categorical/options.js +32 -16
  136. package/src/actions/guides/legends/categorical/recipes.js +114 -9
  137. package/src/actions/guides/legends/categorical/resolve.js +30 -15
  138. package/src/actions/guides/legends/categorical/symbols.js +87 -33
  139. package/src/actions/guides/legends/continuous/common.js +99 -26
  140. package/src/actions/guides/legends/continuous/gradient.js +120 -89
  141. package/src/actions/guides/legends/continuous/index.js +8 -2
  142. package/src/actions/guides/legends/continuous/interval.js +79 -95
  143. package/src/actions/guides/legends/continuous/opacity.js +113 -89
  144. package/src/actions/guides/legends/continuous/stroke.js +525 -0
  145. package/src/actions/guides/legends/creation.js +40 -0
  146. package/src/actions/guides/legends/edit.js +321 -90
  147. package/src/actions/guides/legends/focused.js +2 -2
  148. package/src/actions/guides/legends/index.js +2 -0
  149. package/src/actions/guides/legends/lane.js +149 -102
  150. package/src/actions/guides/legends/lifecycle.js +143 -0
  151. package/src/actions/guides/legends/remove.js +43 -78
  152. package/src/actions/guides/legends/sampling.js +140 -0
  153. package/src/actions/guides/legends/size.js +213 -116
  154. package/src/actions/guides/legends/strokeWidth.js +121 -73
  155. package/src/actions/guides/legends/target.js +123 -0
  156. package/src/actions/guides/legends/transition.js +192 -0
  157. package/src/actions/guides/polar/axes/facade.js +81 -66
  158. package/src/actions/guides/polar/axes/labels.js +28 -8
  159. package/src/actions/guides/polar/axes/lines.js +12 -5
  160. package/src/actions/guides/polar/axes/shared.js +22 -0
  161. package/src/actions/guides/polar/axes/ticks.js +6 -5
  162. package/src/actions/guides/polar/axes/titles.js +6 -5
  163. package/src/actions/guides/polar/grids.js +8 -8
  164. package/src/actions/guides/polar/resolve.js +12 -8
  165. package/src/actions/guides/reuse.js +44 -0
  166. package/src/actions/guides/tickValues.js +19 -21
  167. package/src/actions/index.js +15 -2
  168. package/src/actions/intervals/resolve.js +35 -15
  169. package/src/actions/marks/arc/actions.js +78 -20
  170. package/src/actions/marks/area/actions.js +51 -12
  171. package/src/actions/marks/area/materialize.js +22 -3
  172. package/src/actions/marks/bar/create.js +11 -2
  173. package/src/actions/marks/bar/edit.js +20 -3
  174. package/src/actions/marks/bar/materialize.js +33 -9
  175. package/src/actions/marks/index.js +14 -0
  176. package/src/actions/marks/line/actions.js +71 -113
  177. package/src/actions/marks/line/materialize.js +57 -56
  178. package/src/actions/marks/point/create.js +8 -2
  179. package/src/actions/marks/point/edit.js +29 -21
  180. package/src/actions/marks/point/index.js +1 -0
  181. package/src/actions/marks/point/jitter.js +3 -0
  182. package/src/actions/marks/point/materialize.js +154 -16
  183. package/src/actions/marks/point/packing.js +68 -0
  184. package/src/actions/marks/rect/actions.js +33 -12
  185. package/src/actions/marks/references.js +658 -0
  186. package/src/actions/marks/remove.js +87 -4
  187. package/src/actions/marks/rule/actions.js +77 -9
  188. package/src/actions/marks/rule/appearance.js +26 -0
  189. package/src/actions/marks/shared.js +13 -13
  190. package/src/actions/marks/text/actions.js +296 -13
  191. package/src/actions/marks/text/index.js +4 -0
  192. package/src/actions/marks/text/layout.js +156 -8
  193. package/src/actions/marks/text/removal.js +173 -0
  194. package/src/actions/marks/text/selection.js +73 -0
  195. package/src/actions/marks/tick/actions.js +22 -5
  196. package/src/actions/primitives/createGraphics.js +3 -1
  197. package/src/actions/primitives/editGraphics.js +26 -0
  198. package/src/actions/primitives/semantic.js +3 -2
  199. package/src/actions/primitives/semanticAction.js +120 -10
  200. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  201. package/src/actions/primitives/semanticValidation/index.js +9 -1
  202. package/src/actions/primitives/semanticValidation/layer.js +23 -1
  203. package/src/actions/primitives/semanticValidation/scale.js +38 -8
  204. package/src/actions/regression/components.js +26 -11
  205. package/src/actions/regression/create.js +11 -3
  206. package/src/actions/regression/edit.js +40 -13
  207. package/src/actions/regression/resolve.js +1 -1
  208. package/src/actions/resources/index.js +7 -0
  209. package/src/actions/resources/remove.js +172 -0
  210. package/src/actions/scales/channels.js +212 -0
  211. package/src/actions/scales/consumers/common.js +21 -54
  212. package/src/actions/scales/consumers/families.js +28 -14
  213. package/src/actions/scales/consumers/index.js +42 -4
  214. package/src/actions/scales/consumers/seriesLayout.js +57 -12
  215. package/src/actions/scales/create.js +58 -26
  216. package/src/actions/scales/definitions.js +109 -39
  217. package/src/actions/scales/edit.js +101 -34
  218. package/src/actions/scales/editPolicy.js +95 -35
  219. package/src/actions/scales/index.js +6 -0
  220. package/src/actions/scales/materialize.js +14 -49
  221. package/src/actions/scales/offset.js +48 -0
  222. package/src/actions/scales/parallel.js +71 -0
  223. package/src/actions/scales/patch.js +1 -1
  224. package/src/actions/scales/preview.js +140 -0
  225. package/src/actions/scales/quantitativeColor.js +9 -5
  226. package/src/actions/selection/actions.js +154 -30
  227. package/src/actions/selection/index.js +3 -0
  228. package/src/actions/theme/actions.js +98 -0
  229. package/src/actions/theme/composition.js +238 -0
  230. package/src/actions/theme/index.js +16 -0
  231. package/src/actions/theme/reconcile.js +1130 -0
  232. package/src/actions/theme/state.js +160 -0
  233. package/src/actions/titles/actions.js +11 -10
  234. package/src/actions/titles/resolve.js +1 -40
  235. package/src/actions/violinPlots/create.js +90 -43
  236. package/src/actions/violinPlots/edit.js +192 -0
  237. package/src/actions/violinPlots/index.js +2 -0
  238. package/src/core/ChartProgram.js +7 -0
  239. package/src/core/action.js +32 -2
  240. package/src/core/compositionState.js +141 -8
  241. package/src/core/programState.js +20 -0
  242. package/src/core/resourceReferences.js +332 -0
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/grammar/aggregate.js +66 -23
  245. package/src/grammar/arcs.js +58 -1
  246. package/src/grammar/areaEndpoints.js +46 -0
  247. package/src/grammar/areaSeries.js +87 -41
  248. package/src/grammar/bars/aggregate.js +37 -13
  249. package/src/grammar/bars/geometry.js +44 -0
  250. package/src/grammar/bars/policy.js +9 -7
  251. package/src/grammar/bin.js +226 -0
  252. package/src/grammar/categoryOrder.js +34 -0
  253. package/src/grammar/complete.js +279 -0
  254. package/src/grammar/computed.js +351 -0
  255. package/src/grammar/coordinates.js +37 -0
  256. package/src/grammar/density.js +239 -37
  257. package/src/grammar/displayLabels.js +57 -0
  258. package/src/grammar/ecdf.js +198 -0
  259. package/src/grammar/facets/dependencies.js +133 -1
  260. package/src/grammar/facets/guides.js +3 -0
  261. package/src/grammar/facets/index.js +123 -52
  262. package/src/grammar/facets/scales.js +15 -18
  263. package/src/grammar/fold.js +123 -0
  264. package/src/grammar/horizon.js +6 -4
  265. package/src/grammar/impute.js +340 -0
  266. package/src/grammar/interval.js +37 -20
  267. package/src/grammar/lineSeries.js +20 -63
  268. package/src/grammar/markFilter.js +22 -4
  269. package/src/grammar/markLabelSelection.js +57 -0
  270. package/src/grammar/markLabels.js +110 -0
  271. package/src/grammar/normalize.js +366 -0
  272. package/src/grammar/parallelAxisTitles.js +16 -0
  273. package/src/grammar/pathSeries.js +109 -0
  274. package/src/grammar/pointPacking.js +183 -0
  275. package/src/grammar/pointShapes.js +13 -4
  276. package/src/grammar/polar.js +90 -8
  277. package/src/grammar/positionDatum.js +12 -0
  278. package/src/grammar/rects.js +9 -1
  279. package/src/grammar/regression/derive.js +4 -0
  280. package/src/grammar/regression/index.js +8 -2
  281. package/src/grammar/regression/models.js +22 -7
  282. package/src/grammar/regression/parameters.js +63 -13
  283. package/src/grammar/rotation.js +29 -0
  284. package/src/grammar/roundedRect.js +180 -0
  285. package/src/grammar/rules.js +8 -21
  286. package/src/grammar/scales/color.js +23 -1
  287. package/src/grammar/scales/colorConsumers.js +57 -0
  288. package/src/grammar/scales/continuous.js +2 -2
  289. package/src/grammar/scales/definition.js +42 -5
  290. package/src/grammar/scales/discretized.js +11 -5
  291. package/src/grammar/scales/fields.js +38 -8
  292. package/src/grammar/scales/index.js +1 -0
  293. package/src/grammar/scales/mapping.js +2 -0
  294. package/src/grammar/scales/ordinal.js +12 -3
  295. package/src/grammar/scales/policies.js +2 -2
  296. package/src/grammar/scales/radial.js +55 -0
  297. package/src/grammar/scales/size.js +456 -0
  298. package/src/grammar/scales/types.js +1 -0
  299. package/src/grammar/schemas/concreteGraphic.js +14 -2
  300. package/src/grammar/schemas/graphic.js +15 -0
  301. package/src/grammar/schemas/graphicBounds.js +179 -30
  302. package/src/grammar/schemas/semanticPath.js +23 -4
  303. package/src/grammar/stack.js +170 -0
  304. package/src/grammar/statisticalReference.js +103 -0
  305. package/src/grammar/statistics/confidenceInterval.js +101 -0
  306. package/src/grammar/strokeStyle.js +77 -0
  307. package/src/grammar/summary.js +245 -0
  308. package/src/grammar/text.js +22 -16
  309. package/src/grammar/timeUnit.js +234 -13
  310. package/src/grammar/transformTopology.js +10 -0
  311. package/src/grammar/transforms.js +418 -16
  312. package/src/grammar/valueFormat.js +112 -0
  313. package/src/grammar/weightedStatistics.js +374 -0
  314. package/src/grammar/window.js +232 -21
  315. package/src/layout/aspect.js +80 -0
  316. package/src/layout/facets.js +100 -12
  317. package/src/layout/guideCollisions.js +12 -0
  318. package/src/layout/labels.js +343 -0
  319. package/src/layout/legend.js +2 -10
  320. package/src/layout/legendItems.js +102 -0
  321. package/src/layout/legendLane.js +100 -11
  322. package/src/materialization/bars/aggregate.js +56 -147
  323. package/src/materialization/bars/histogram.js +104 -20
  324. package/src/materialization/bars/ranged.js +19 -3
  325. package/src/materialization/bars/resolve.js +7 -4
  326. package/src/materialization/categorySlotOffset.js +40 -0
  327. package/src/materialization/composition.js +3 -1
  328. package/src/materialization/compositionSnapshot.js +1 -1
  329. package/src/materialization/coordinateBounds.js +204 -0
  330. package/src/materialization/dependencies.js +93 -16
  331. package/src/materialization/encodings.js +1 -1
  332. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  333. package/src/materialization/facetGuides/placement.js +8 -2
  334. package/src/materialization/facetGuides/preparation.js +12 -7
  335. package/src/materialization/facetHeaders.js +346 -0
  336. package/src/materialization/facets.js +23 -66
  337. package/src/materialization/guides/layout.js +57 -0
  338. package/src/materialization/guides/resources.js +28 -2
  339. package/src/materialization/legends.js +11 -2
  340. package/src/materialization/marks/capabilities.js +20 -5
  341. package/src/materialization/marks/grouping.js +52 -0
  342. package/src/materialization/marks/index.js +68 -27
  343. package/src/materialization/marks/policies.js +4 -1
  344. package/src/materialization/rect.js +55 -42
  345. package/src/materialization/rectConfig.js +5 -1
  346. package/src/materialization/rowEncoding.js +13 -13
  347. package/src/materialization/scaleGuideDependencies.js +8 -5
  348. package/src/materialization/scales/map.js +6 -3
  349. package/src/materialization/scales/policies/arc.js +42 -0
  350. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  351. package/src/materialization/scales/policies/offset.js +50 -8
  352. package/src/materialization/scales/policies/series.js +5 -1
  353. package/src/materialization/scales/resolve.js +65 -17
  354. package/src/materialization/selection/compatibility.js +11 -0
  355. package/src/materialization/selection/items/arc.js +28 -4
  356. package/src/materialization/selection/items/bar.js +42 -71
  357. package/src/materialization/selection/items/common.js +27 -6
  358. package/src/materialization/selection/items/path.js +3 -13
  359. package/src/materialization/selection/items/rect.js +35 -8
  360. package/src/materialization/selection/path.js +1 -1
  361. package/src/materialization/selection/point.js +3 -1
  362. package/src/materialization/text.js +324 -14
  363. package/src/mcp/adapter.js +2 -2
  364. package/src/renderers/canvas/circle.js +2 -3
  365. package/src/renderers/canvas/line.js +2 -3
  366. package/src/renderers/canvas/native.js +4 -4
  367. package/src/renderers/canvas/path.js +2 -3
  368. package/src/renderers/canvas/rect.js +2 -3
  369. package/src/renderers/canvas/stroke.js +11 -0
  370. package/src/renderers/svg.js +19 -0
  371. package/src/selectors/datasets.js +44 -0
  372. package/src/selectors/layers.js +7 -2
  373. package/src/selectors/scales.js +6 -0
  374. package/src/theme/defaults.js +140 -0
  375. package/types/basic.d.ts +17 -2
  376. package/types/index.d.ts +211 -0
  377. package/types/program.d.ts +2182 -204
  378. package/src/materialization/bars/grouped.js +0 -144
@@ -1,14 +1,29 @@
1
+ import { TEXT_LABEL_LAYOUT_OPTIONS } from "./layout.js";
1
2
  import { action } from "../../../core/action.js";
2
3
  import { validateUserId } from "../../../core/identifiers.js";
3
4
  import {
4
5
  DEFAULT_TEXT_MARK,
6
+ isSourceOwnedText,
5
7
  normalizeTextMarkConfig
6
8
  } from "../../../grammar/text.js";
9
+ import { findCoordinate } from "../../../selectors/coordinates.js";
7
10
  import {
8
11
  canMaterializeArc,
9
- canMaterializeText
12
+ canMaterializeRect,
13
+ canMaterializeText,
14
+ isTextSource
10
15
  } from "../../../materialization/marks/index.js";
11
- import { resolveTextGraphicItems } from "../../../materialization/text.js";
16
+ import {
17
+ resolveTextGraphicItems,
18
+ validateSourceMarkLabelPlacement
19
+ } from "../../../materialization/text.js";
20
+ import { markLabelPlacementLeaderId } from "../../../layout/labels.js";
21
+ import { resolveMarkSelection } from
22
+ "../../../materialization/selection/state.js";
23
+ import { rematerializeLabelsBeforeHighlights } from
24
+ "../../selection/actions.js";
25
+ import { normalizeLabelSelectionCreate } from
26
+ "../../../grammar/markLabelSelection.js";
12
27
  import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
13
28
  import {
14
29
  applyLayeredMarkInheritance,
@@ -18,6 +33,10 @@ import {
18
33
  resolveMarkId,
19
34
  validateMarkOptions
20
35
  } from "../shared.js";
36
+ import {
37
+ resolveFacadeData,
38
+ resolveFacadeId
39
+ } from "../../charts/shared.js";
21
40
  import { resolveMarkGraphicPlacement } from
22
41
  "../../../materialization/graphicHierarchy.js";
23
42
 
@@ -25,10 +44,9 @@ const STYLE_OPTIONS = Object.freeze([
25
44
  "fill", "opacity", "fontSize", "fontFamily", "fontWeight",
26
45
  "align", "baseline", "rotation", "dx", "dy"
27
46
  ]);
28
- const CREATE_OPTIONS = Object.freeze(["id", "data", "text", ...STYLE_OPTIONS]);
47
+ const CREATE_OPTIONS = Object.freeze(["id", "data", "source", "text", ...STYLE_OPTIONS]);
29
48
  const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
30
49
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
31
- const SOURCE_TYPES = new Set(["point", "bar", "rule", "rect", "arc"]);
32
50
 
33
51
  function sourceMatchesData(program, layer, requestedData) {
34
52
  if (requestedData === undefined || layer?.data === requestedData) return true;
@@ -37,16 +55,34 @@ function sourceMatchesData(program, layer, requestedData) {
37
55
 
38
56
  function eligibleSource(program, layer, requestedData) {
39
57
  if (
40
- !SOURCE_TYPES.has(layer?.mark?.type) ||
41
- layer.data === undefined ||
58
+ !isTextSource(layer) ||
42
59
  !sourceMatchesData(program, layer, requestedData)
43
60
  ) return false;
44
61
  if (layer.mark.type === "arc") return canMaterializeArc(program, layer);
62
+ if (
63
+ layer.mark.type === "point" &&
64
+ findCoordinate(program, layer.coordinate)?.type === "polar"
65
+ ) {
66
+ return layer.encoding?.theta?.scale !== undefined &&
67
+ layer.encoding?.radius?.scale !== undefined;
68
+ }
69
+ if (layer.mark.type === "rect" && (canMaterializeRect(program, layer) ||
70
+ program.markConfigs[layer.id]?.gradientPlot?.materialized === true)) return true;
45
71
  const encodings = resolveCompatibleEncodings(program, layer, "text");
46
72
  return encodings.x !== undefined && encodings.y !== undefined;
47
73
  }
48
74
 
49
75
  function resolveTextInheritance(program, args) {
76
+ if (Object.hasOwn(args, "source")) {
77
+ if (Object.hasOwn(args, "data")) {
78
+ throw new Error("createTextMark source and data are mutually exclusive.");
79
+ }
80
+ return textInheritance(program, resolveEligibleLayer(program, {
81
+ target: validateUserId(args.source, "Text source id"),
82
+ predicate: isTextSource,
83
+ label: "text source"
84
+ }));
85
+ }
50
86
  if (Object.hasOwn(args, "data")) return undefined;
51
87
  const requestedData = program.context.currentData;
52
88
  const candidates = program.semanticSpec.layers.filter(layer =>
@@ -60,15 +96,21 @@ function resolveTextInheritance(program, args) {
60
96
  : undefined;
61
97
  if (source === undefined && candidates.length > 1) {
62
98
  throw new Error(
63
- "Text source inference is ambiguous; provide data and encode its position explicitly."
99
+ "Text source inference is ambiguous; provide source, or data and encode its position explicitly."
64
100
  );
65
101
  }
66
102
  if (source === undefined) return undefined;
103
+ return textInheritance(program, source);
104
+ }
105
+
106
+ function textInheritance(program, source) {
67
107
  return {
68
108
  source: source.id,
69
109
  data: source.data,
70
110
  coordinate: source.coordinate,
71
- encoding: source.mark.type === "arc"
111
+ encoding: source.mark.type === "arc" ||
112
+ (source.mark.type === "point" &&
113
+ findCoordinate(program, source.coordinate)?.type === "polar")
72
114
  ? {}
73
115
  : resolveCompatibleEncodings(program, source, "text")
74
116
  };
@@ -136,6 +178,240 @@ const createTextMark = action(
136
178
  }
137
179
  );
138
180
 
181
+ const LABEL_OPTIONS = Object.freeze([
182
+ "id", "source", "field", "value", "content", "normalizeBy", "format", "layout",
183
+ "select", "selection", "placement",
184
+ ...STYLE_OPTIONS
185
+ ]);
186
+
187
+ const ANNOTATION_OPTIONS = Object.freeze([
188
+ "id", "text", "format", "x", "y", "space", "source", "data", "coordinate", "layout",
189
+ ...STYLE_OPTIONS
190
+ ]);
191
+
192
+ function annotationLayout(value) {
193
+ const layout = value === undefined || value === false ? undefined : value;
194
+ if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
195
+ throw new Error("createAnnotation layout target is owned by the created text layer.");
196
+ }
197
+ if (layout !== undefined) {
198
+ validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createAnnotation layout");
199
+ }
200
+ return layout;
201
+ }
202
+
203
+ function resolveAnnotationBinding(program, args) {
204
+ const hasX = Object.hasOwn(args, "x");
205
+ const hasY = Object.hasOwn(args, "y");
206
+ if (!hasX && !hasY) {
207
+ if (["space", "data", "coordinate"].some(option => Object.hasOwn(args, option))) {
208
+ throw new Error("createAnnotation mark anchor accepts source but not space, data, or coordinate.");
209
+ }
210
+ return { mode: "mark" };
211
+ }
212
+ if (!hasX || !hasY) {
213
+ throw new Error("createAnnotation coordinate anchor requires both x and y.");
214
+ }
215
+ const space = args.space ?? "data";
216
+ if (space !== "data" && space !== "plot") {
217
+ throw new Error("createAnnotation space must be data or plot.");
218
+ }
219
+ if (space === "plot") {
220
+ if (Object.hasOwn(args, "source")) {
221
+ throw new Error("createAnnotation plot anchor does not accept source.");
222
+ }
223
+ if (![args.x, args.y].every(value => Number.isFinite(value) && value >= 0 && value <= 1)) {
224
+ throw new Error("createAnnotation plot coordinates must be finite numbers in [0, 1].");
225
+ }
226
+ return {
227
+ mode: "plot",
228
+ data: resolveFacadeData(program, args.data, "createAnnotation"),
229
+ coordinate: args.coordinate
230
+ };
231
+ }
232
+ if (Object.hasOwn(args, "data") || Object.hasOwn(args, "coordinate")) {
233
+ throw new Error("createAnnotation data anchor uses source data and coordinate.");
234
+ }
235
+ const source = resolveEligibleLayer(program, {
236
+ target: args.source === undefined
237
+ ? undefined
238
+ : validateUserId(args.source, "createAnnotation source"),
239
+ predicate: layer =>
240
+ !isSourceOwnedText(layer) &&
241
+ layer.data !== undefined &&
242
+ layer.encoding?.x?.scale !== undefined &&
243
+ layer.encoding?.y?.scale !== undefined &&
244
+ findCoordinate(program, layer.coordinate)?.type === "cartesian",
245
+ label: "createAnnotation Cartesian layer",
246
+ targetOption: "source"
247
+ });
248
+ return {
249
+ mode: "data",
250
+ data: source.data,
251
+ coordinate: source.coordinate,
252
+ x: source.encoding.x,
253
+ y: source.encoding.y
254
+ };
255
+ }
256
+
257
+ const createAnnotation = action(
258
+ {
259
+ op: "createAnnotation",
260
+ description: "Create text at a final mark, data, or plot anchor."
261
+ },
262
+ function (args = {}) {
263
+ validateMarkOptions(args, ANNOTATION_OPTIONS, "createAnnotation");
264
+ if (!Object.hasOwn(args, "text")) {
265
+ throw new Error("createAnnotation requires text.");
266
+ }
267
+ const id = resolveFacadeId(this, args.id, {
268
+ defaultId: "annotation",
269
+ operation: "createAnnotation"
270
+ });
271
+ const binding = resolveAnnotationBinding(this, args);
272
+ const layout = annotationLayout(args.layout);
273
+ const style = Object.fromEntries(STYLE_OPTIONS
274
+ .filter(option => Object.hasOwn(args, option))
275
+ .map(option => [option, args[option]]));
276
+ const text = {
277
+ value: args.text,
278
+ ...(args.format === undefined ? {} : { format: args.format })
279
+ };
280
+ const apply = program => {
281
+ if (binding.mode === "mark") {
282
+ return program.createMarkLabels({
283
+ id,
284
+ ...(args.source === undefined ? {} : { source: args.source }),
285
+ ...style,
286
+ ...text,
287
+ ...(layout === undefined ? {} : { layout })
288
+ });
289
+ }
290
+ const xScale = binding.mode === "plot" ? `${id}-x` : binding.x.scale;
291
+ const yScale = binding.mode === "plot" ? `${id}-y` : binding.y.scale;
292
+ let next = binding.mode === "plot"
293
+ ? program
294
+ .createScale({ id: xScale, type: "linear", domain: [0, 1] })
295
+ .createScale({ id: yScale, type: "linear", domain: [0, 1] })
296
+ : program;
297
+ next = next
298
+ .createTextMark({ id, data: binding.data, ...style })
299
+ .encodeText({ target: id, ...text })
300
+ .encodeX({
301
+ target: id,
302
+ datum: args.x,
303
+ fieldType: binding.mode === "plot" ? "quantitative" : binding.x.fieldType,
304
+ scale: { id: xScale },
305
+ ...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
306
+ ...(binding.mode === "data" && binding.x.temporalUnit !== undefined
307
+ ? { temporalUnit: binding.x.temporalUnit }
308
+ : {})
309
+ })
310
+ .encodeY({
311
+ target: id,
312
+ datum: args.y,
313
+ fieldType: binding.mode === "plot" ? "quantitative" : binding.y.fieldType,
314
+ scale: { id: yScale },
315
+ ...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
316
+ ...(binding.mode === "data" && binding.y.temporalUnit !== undefined
317
+ ? { temporalUnit: binding.y.temporalUnit }
318
+ : {})
319
+ });
320
+ return layout === undefined
321
+ ? next
322
+ : next.layoutLabels({ ...layout, target: id });
323
+ };
324
+ // Validate the full lower action chain before committing any child effect.
325
+ apply(this);
326
+ return apply(this);
327
+ }
328
+ );
329
+
330
+ const createMarkLabels = action(
331
+ {
332
+ op: "createMarkLabels",
333
+ description: "Create final-item text labels attached to an existing mark."
334
+ },
335
+ function (args = {}) {
336
+ validateMarkOptions(args, LABEL_OPTIONS, "createMarkLabels");
337
+ const inherited = resolveTextInheritance(this, args);
338
+ if (inherited === undefined) {
339
+ throw new Error("createMarkLabels requires an eligible source mark; provide source explicitly.");
340
+ }
341
+ const id = validateUserId(args.id === undefined ? `${inherited.source}-labels` : args.id, "Text mark id");
342
+ assertMarkAvailable(this, id);
343
+ const selection = normalizeLabelSelectionCreate(args);
344
+ const placement = args.placement === undefined
345
+ ? undefined
346
+ : validateSourceMarkLabelPlacement(this, inherited.source, args.placement);
347
+ if (
348
+ placement?.leader !== undefined && placement.leader !== false &&
349
+ this.graphicSpec.objects[markLabelPlacementLeaderId(id)] !== undefined
350
+ ) {
351
+ throw new Error(
352
+ `Mark label placement leader graphic "${markLabelPlacementLeaderId(id)}" already exists.`
353
+ );
354
+ }
355
+ if (selection.kind === "inline") {
356
+ resolveMarkSelection(this, inherited.source, selection.selector);
357
+ } else if (selection.kind === "named") {
358
+ const definition = this.materializationConfigs.selections?.[selection.id];
359
+ if (definition === undefined) {
360
+ throw new Error(`Unknown label selection "${selection.id}".`);
361
+ }
362
+ if (definition.target !== inherited.source) {
363
+ throw new Error(
364
+ `Label selection "${selection.id}" must target source mark "${inherited.source}".`
365
+ );
366
+ }
367
+ resolveMarkSelection(this, inherited.source, definition.selector);
368
+ }
369
+ const style = Object.fromEntries(STYLE_OPTIONS
370
+ .filter(option => Object.hasOwn(args, option))
371
+ .map(option => [option, args[option]]));
372
+ normalizeTextMarkConfig(style);
373
+ const encoding = Object.fromEntries(
374
+ ["field", "value", "content", "normalizeBy", "format"]
375
+ .filter(option => Object.hasOwn(args, option))
376
+ .map(option => [option, args[option]])
377
+ );
378
+ if (!["field", "value", "content"].some(option => Object.hasOwn(args, option))) {
379
+ encoding.content = "value";
380
+ }
381
+ const layout = args.layout === undefined || args.layout === false ? undefined : args.layout;
382
+ if (layout !== undefined && Object.hasOwn(layout ?? {}, "target")) {
383
+ throw new Error("createMarkLabels layout target is owned by the created label layer.");
384
+ }
385
+ const apply = program => {
386
+ let next = program.createTextMark({
387
+ id, source: inherited.source, align: "center", baseline: "middle", ...style
388
+ });
389
+ next = next._withMarkConfig(id, {
390
+ ...next.markConfigs[id],
391
+ labelAuthoring: {
392
+ selection,
393
+ ...(placement === undefined ? {} : { placement })
394
+ }
395
+ });
396
+ next = next.encodeText({ target: id, ...encoding });
397
+ next = rematerializeLabelsBeforeHighlights(
398
+ next,
399
+ inherited.source,
400
+ [id],
401
+ { skipWithoutHighlights: true }
402
+ );
403
+ return layout === undefined ? next : next.layoutLabels({ ...layout, target: id });
404
+ };
405
+ if (layout !== undefined) {
406
+ // Validate object shape before spreading it into the child action's options.
407
+ validateMarkOptions(layout, TEXT_LABEL_LAYOUT_OPTIONS, "createMarkLabels layout");
408
+ }
409
+ // Validate all child effects on a discarded immutable branch, as scale edits do.
410
+ apply(this);
411
+ return apply(this);
412
+ }
413
+ );
414
+
139
415
  const rematerializeTextMark = action(
140
416
  {
141
417
  op: "rematerializeTextMark",
@@ -160,9 +436,11 @@ const rematerializeTextMark = action(
160
436
  : this.editGraphics({ target: id, property: "length", value: 0 });
161
437
  return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
162
438
  ? next.materializeLabelLayout({ id, rematerializeBase: false })
163
- : next;
439
+ : layer.source === undefined
440
+ ? next
441
+ : next.materializeMarkLabelPlacement({ id });
164
442
  }
165
- const next = this.editGraphics({
443
+ let next = this.editGraphics({
166
444
  target: id,
167
445
  property: "items",
168
446
  value: resolveTextGraphicItems(
@@ -171,9 +449,12 @@ const rematerializeTextMark = action(
171
449
  this.markConfigs[id] ?? DEFAULT_TEXT_MARK
172
450
  )
173
451
  });
174
- return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
175
- ? next.materializeLabelLayout({ id, rematerializeBase: false })
176
- : next;
452
+ if (replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined) {
453
+ return next.materializeLabelLayout({ id, rematerializeBase: false });
454
+ }
455
+ return layer.source === undefined
456
+ ? next
457
+ : next.materializeMarkLabelPlacement({ id });
177
458
  }
178
459
  );
179
460
 
@@ -209,6 +490,8 @@ const editTextMark = action(
209
490
  export function registerTextMarkActions(ProgramClass) {
210
491
  Object.assign(ProgramClass.prototype, {
211
492
  createTextMark,
493
+ createMarkLabels,
494
+ createAnnotation,
212
495
  editTextMark,
213
496
  rematerializeTextMark
214
497
  });
@@ -1,7 +1,11 @@
1
1
  import { registerTextMarkActions as registerTextCoreActions } from "./actions.js";
2
2
  import { registerTextLabelLayoutActions } from "./layout.js";
3
+ import { registerTextLabelRemovalActions } from "./removal.js";
4
+ import { registerTextLabelSelectionActions } from "./selection.js";
3
5
 
4
6
  export function registerTextMarkActions(ProgramClass) {
5
7
  registerTextCoreActions(ProgramClass);
6
8
  registerTextLabelLayoutActions(ProgramClass);
9
+ registerTextLabelRemovalActions(ProgramClass);
10
+ registerTextLabelSelectionActions(ProgramClass);
7
11
  }
@@ -2,25 +2,33 @@ import { action } 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
 
19
- const LAYOUT_OPTIONS = Object.freeze([
25
+ export const TEXT_LABEL_LAYOUT_OPTIONS = Object.freeze([
20
26
  "target", "axis", "padding", "maxDisplacement", "bounds", "leader"
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,6 +198,75 @@ function materializeLeaders(program, layer, config, resolved) {
190
198
  return { program: next, count: leaders.length };
191
199
  }
192
200
 
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)
225
+ };
226
+ });
227
+ }
228
+
229
+ const materializeMarkLabelPlacement = action(
230
+ {
231
+ op: "materializeMarkLabelPlacement",
232
+ description: "Reconcile semantic attached-label placement leaders."
233
+ },
234
+ function (args = {}) {
235
+ validateOptionObject(
236
+ args,
237
+ MATERIALIZE_PLACEMENT_OPTIONS,
238
+ "materializeMarkLabelPlacement"
239
+ );
240
+ const id = validateUserId(args.id, "Attached label id");
241
+ const layer = findLayer(this, id);
242
+ if (!isSourceOwnedText(layer)) {
243
+ throw new Error(`Unknown attached label target "${id}".`);
244
+ }
245
+ const leaderId = markLabelPlacementLeaderId(id);
246
+ const placement = this.markConfigs[id]?.labelAuthoring?.placement;
247
+ if (placement?.leader === undefined || placement.leader === false) {
248
+ return this;
249
+ }
250
+ const layout = this.materializationConfigs.labelLayouts?.[id];
251
+ if (layout !== undefined && layout.leader !== false) {
252
+ throw new Error(
253
+ "Semantic label placement leader conflicts with the active collision-layout leader."
254
+ );
255
+ }
256
+ if (!canMaterializeText(this, layer)) {
257
+ return removeLeaderGraphic(this, leaderId);
258
+ }
259
+ return materializeLeaders(this, layer, {
260
+ leaderId,
261
+ leader: {
262
+ ...placement.leader,
263
+ strokeDash: [],
264
+ opacity: 1
265
+ }
266
+ }, placementLeaderItems(this, layer)).program;
267
+ }
268
+ );
269
+
193
270
  const materializeLabelLayout = action(
194
271
  {
195
272
  op: "materializeLabelLayout",
@@ -222,9 +299,9 @@ const materializeLabelLayout = action(
222
299
  x: resolved.items.map(item => item.x),
223
300
  y: resolved.items.map(item => item.y)
224
301
  });
225
- const leaders = materializeLeaders(next, layer, config, resolved);
302
+ const leaders = materializeLeaders(next, layer, config, resolved.items);
226
303
  next = leaders.program;
227
- return next._withMaterializationConfig(["labelLayouts", id], {
304
+ next = next._withMaterializationConfig(["labelLayouts", id], {
228
305
  axis: config.axis,
229
306
  padding: config.padding,
230
307
  maxDisplacement: config.maxDisplacement,
@@ -243,6 +320,9 @@ const materializeLabelLayout = action(
243
320
  warnings: resolved.warnings
244
321
  }
245
322
  });
323
+ return layer.source === undefined
324
+ ? next
325
+ : next.materializeMarkLabelPlacement({ id });
246
326
  }
247
327
  );
248
328
 
@@ -252,9 +332,15 @@ const layoutLabels = action(
252
332
  description: "Assign deterministic collision-aware layout to a text mark."
253
333
  },
254
334
  function (args = {}) {
255
- validateOptionObject(args, LAYOUT_OPTIONS, "layoutLabels");
335
+ validateOptionObject(args, TEXT_LABEL_LAYOUT_OPTIONS, "layoutLabels");
256
336
  const layer = requireCompleteText(this, args.target, "layoutLabels");
257
337
  const policy = normalizePolicy(args);
338
+ const placement = this.markConfigs[layer.id]?.labelAuthoring?.placement;
339
+ if (policy.leader !== false && placement?.leader !== false && placement?.leader !== undefined) {
340
+ throw new Error(
341
+ "Collision-layout leader conflicts with the active semantic label placement leader."
342
+ );
343
+ }
258
344
  const generatedId = leaderId(layer.id);
259
345
  const existingConfig = this.materializationConfigs.labelLayouts?.[layer.id];
260
346
  if (
@@ -272,6 +358,66 @@ const layoutLabels = action(
272
358
  }
273
359
  );
274
360
 
361
+ const editMarkLabelPlacement = action(
362
+ {
363
+ op: "editMarkLabelPlacement",
364
+ description: "Replace or reset semantic placement for one attached label layer."
365
+ },
366
+ function (args = {}) {
367
+ validateOptionObject(args, EDIT_PLACEMENT_OPTIONS, "editMarkLabelPlacement", {
368
+ allowEmpty: false,
369
+ emptyError: Error
370
+ });
371
+ if (!Object.hasOwn(args, "target") || !Object.hasOwn(args, "placement")) {
372
+ throw new Error("editMarkLabelPlacement requires target and placement.");
373
+ }
374
+ const id = validateUserId(args.target, "Attached label id");
375
+ const layer = findLayer(this, id);
376
+ if (!isSourceOwnedText(layer)) {
377
+ throw new Error(`Unknown attached label target "${id}".`);
378
+ }
379
+ let placement;
380
+ if (args.placement !== "auto") {
381
+ placement = validateSourceMarkLabelPlacement(this, layer.source, args.placement);
382
+ if (
383
+ placement.leader !== false &&
384
+ this.materializationConfigs.labelLayouts?.[id]?.leader !== false &&
385
+ this.materializationConfigs.labelLayouts?.[id]?.leader !== undefined
386
+ ) {
387
+ throw new Error(
388
+ "Semantic label placement leader conflicts with the active collision-layout leader."
389
+ );
390
+ }
391
+ const generatedId = markLabelPlacementLeaderId(id);
392
+ const previous = this.markConfigs[id]?.labelAuthoring?.placement;
393
+ if (
394
+ placement.leader !== false &&
395
+ this.graphicSpec.objects[generatedId] !== undefined &&
396
+ (previous?.leader === undefined || previous.leader === false)
397
+ ) {
398
+ throw new Error(`Mark label placement leader graphic "${generatedId}" already exists.`);
399
+ }
400
+ }
401
+ const config = this.markConfigs[id] ?? DEFAULT_TEXT_MARK;
402
+ const previousPlacement = config.labelAuthoring?.placement;
403
+ let base = this;
404
+ if (
405
+ previousPlacement?.leader !== undefined &&
406
+ previousPlacement.leader !== false &&
407
+ (placement === undefined || placement.leader === false)
408
+ ) {
409
+ base = removeLeaderGraphic(base, markLabelPlacementLeaderId(id));
410
+ }
411
+ const labelAuthoring = { ...config.labelAuthoring };
412
+ if (placement === undefined) delete labelAuthoring.placement;
413
+ else labelAuthoring.placement = placement;
414
+ const next = base._withMarkConfig(id, { ...config, labelAuthoring });
415
+ return canMaterializeText(next, layer)
416
+ ? next.rematerializeTextMark({ id })
417
+ : next.materializeMarkLabelPlacement({ id });
418
+ }
419
+ );
420
+
275
421
  const removeLabelLayout = action(
276
422
  {
277
423
  op: "removeLabelLayout",
@@ -292,6 +438,8 @@ export function registerTextLabelLayoutActions(ProgramClass) {
292
438
  Object.assign(ProgramClass.prototype, {
293
439
  layoutLabels,
294
440
  removeLabelLayout,
295
- materializeLabelLayout
441
+ materializeLabelLayout,
442
+ editMarkLabelPlacement,
443
+ materializeMarkLabelPlacement
296
444
  });
297
445
  }