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
@@ -10,19 +10,34 @@ import {
10
10
  } from "../../core/validation.js";
11
11
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
12
12
  import { normalizeStrokeDashPattern } from "../../grammar/scales/index.js";
13
+ import {
14
+ requestedStrokeDetails,
15
+ STROKE_STYLE_PROPERTIES
16
+ } from "../../grammar/strokeStyle.js";
13
17
  import { findLayer } from "../../selectors/layers.js";
18
+ import { findSemanticScale } from "../../selectors/scales.js";
14
19
  import { removeOwnedMark } from "../marks/remove.js";
15
20
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
21
+ import { resolveIntervalComposite } from "../intervals/resolve.js";
22
+ import {
23
+ resolveDistributionScalePlan,
24
+ setCartesianPosition,
25
+ updateDistributionPositions
26
+ } from "../distributions/revision.js";
16
27
  import {
17
28
  applyIntervalRevision,
29
+ collectIntervalConsumers,
30
+ findIntervalTransform,
18
31
  ownOptions,
19
32
  planIntervalEdit,
33
+ planIntervalRoleData,
20
34
  releaseIntervalRevision,
21
35
  resolveIntervalOwner
22
36
  } from "../data/intervalEdit.js";
23
37
 
24
38
  export const ERROR_BAND_BOUNDARY_OPTIONS = Object.freeze([
25
- "stroke", "strokeWidth", "strokeDash", "opacity", "curve"
39
+ "stroke", "strokeWidth", "strokeDash", "opacity", "curve",
40
+ ...STROKE_STYLE_PROPERTIES
26
41
  ]);
27
42
 
28
43
  export function resolveBoundaryAppearance(value, { defaults, operation }) {
@@ -39,12 +54,37 @@ export function resolveBoundaryAppearance(value, { defaults, operation }) {
39
54
  validateNonNegativeFinite(strokeWidth, `${operation} strokeWidth`);
40
55
  const resolvedStrokeDash = normalizeStrokeDashPattern(strokeDash);
41
56
  validateUnitInterval(opacity, `${operation} opacity`);
42
- return { stroke, strokeWidth, strokeDash: resolvedStrokeDash, opacity, curve };
57
+ return {
58
+ stroke,
59
+ strokeWidth,
60
+ strokeDash: resolvedStrokeDash,
61
+ opacity,
62
+ curve,
63
+ ...requestedStrokeDetails({ ...defaults, ...value }, operation)
64
+ };
43
65
  }
44
66
 
45
67
  const EDIT_OPTIONS = Object.freeze([
46
- "target", "fill", "opacity", "curve", "statistics", "boundaries"
68
+ "target", "data", "x", "y", "groupBy", "fill", "opacity", "curve",
69
+ "statistics", "boundaries", ...STROKE_STYLE_PROPERTIES
47
70
  ]);
71
+ const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "method", "level"]);
72
+ const EDIT_POLICY = Object.freeze({
73
+ operation: "editErrorBand",
74
+ resourceLabel: "error-band",
75
+ defaultId: "errorBand",
76
+ ownerLabel: "Error-band id",
77
+ positionTypes: Object.freeze(["quantitative", "temporal"]),
78
+ defaultPositionType: "quantitative",
79
+ defaultIntervalChannel: "y",
80
+ scaleDefaults: fieldType => fieldType === "temporal"
81
+ ? { nice: true }
82
+ : { nice: true, zero: false },
83
+ intervalScaleDefaults: Object.freeze({ nice: true, zero: false }),
84
+ allowExplicitGrouping: true,
85
+ ambiguousMessage:
86
+ "editErrorBand cannot infer the interval axis when both positions are quantitative; provide an interval option."
87
+ });
48
88
  const BOUNDARY_EDIT_OPTIONS = Object.freeze([
49
89
  "target", "boundary", ...ERROR_BAND_BOUNDARY_OPTIONS
50
90
  ]);
@@ -76,6 +116,223 @@ function resolveOwner(program, requested) {
76
116
  });
77
117
  }
78
118
 
119
+ function currentRoleArgs(program, owner, current) {
120
+ const transform = findIntervalTransform(program, current.data);
121
+ const intervalChannel = current.orientation === "vertical" ? "y" : "x";
122
+ const positionChannel = intervalChannel === "x" ? "y" : "x";
123
+ const position = {
124
+ field: current.position.field,
125
+ fieldType: current.position.fieldType,
126
+ ...(current.position.temporalUnit === undefined
127
+ ? {}
128
+ : { temporalUnit: current.position.temporalUnit }),
129
+ scale: { id: current.positionScale }
130
+ };
131
+ const interval = transform === undefined
132
+ ? {
133
+ center: current.centerField ?? owner.encoding[intervalChannel].title,
134
+ lower: current.lowerField,
135
+ upper: current.upperField,
136
+ scale: { id: current.intervalScale }
137
+ }
138
+ : {
139
+ field: transform.field,
140
+ center: transform.center,
141
+ extent: transform.extent,
142
+ ...(transform.method === undefined ? {} : { method: transform.method }),
143
+ ...(transform.level === undefined ? {} : { level: transform.level }),
144
+ scale: { id: current.intervalScale }
145
+ };
146
+ return {
147
+ source: current.source ?? transform?.source ?? current.data,
148
+ x: positionChannel === "x" ? position : interval,
149
+ y: positionChannel === "y" ? position : interval,
150
+ groupBy: current.groupBy
151
+ };
152
+ }
153
+
154
+ function channelArgs(requested, fallback) {
155
+ if (requested === undefined) return fallback;
156
+ return requested;
157
+ }
158
+
159
+ function resolveRoleCandidate(program, owner, current, args) {
160
+ const previous = currentRoleArgs(program, owner, current);
161
+ const full = {
162
+ data: Object.hasOwn(args, "data") ? args.data : previous.source,
163
+ x: channelArgs(args.x, previous.x),
164
+ y: channelArgs(args.y, previous.y),
165
+ ...(Object.hasOwn(args, "groupBy")
166
+ ? { groupBy: args.groupBy }
167
+ : previous.groupBy === undefined ? {} : { groupBy: previous.groupBy }),
168
+ coordinate: current.coordinate
169
+ };
170
+ const policy = {
171
+ ...EDIT_POLICY,
172
+ existingId: owner.id,
173
+ coordinate: current.coordinate,
174
+ generatedFields: findIntervalTransform(program, current.data)?.as
175
+ };
176
+ let resolved = resolveIntervalComposite(program, full, policy);
177
+ if (resolved.groupField === resolved.position.field) {
178
+ throw new Error(
179
+ "editErrorBand groupBy must differ from the independent position field."
180
+ );
181
+ }
182
+ if (Object.hasOwn(args, "statistics")) {
183
+ if (!isPlainObject(args.statistics)) {
184
+ throw new TypeError("editErrorBand statistics must be a plain object.");
185
+ }
186
+ validateKeys(args.statistics, STATISTICS_OPTIONS, "editErrorBand statistics");
187
+ if (!STATISTICS_OPTIONS.some(key => Object.hasOwn(args.statistics, key))) {
188
+ throw new Error(
189
+ "editErrorBand statistics requires center, extent, method, or level."
190
+ );
191
+ }
192
+ full[resolved.interval.channel] = {
193
+ ...full[resolved.interval.channel],
194
+ ...args.statistics
195
+ };
196
+ resolved = resolveIntervalComposite(program, full, policy);
197
+ }
198
+ const positionRole = {
199
+ field: resolved.position.field,
200
+ fieldType: resolved.position.fieldType,
201
+ scale: resolved.position.scale
202
+ };
203
+ const intervalTitle = resolved.interval.mode === "statistical"
204
+ ? resolved.interval.field
205
+ : resolved.interval.title;
206
+ const intervalRole = {
207
+ field: intervalTitle,
208
+ fieldType: "quantitative",
209
+ scale: resolved.interval.scale
210
+ };
211
+ const x = resolved.position.channel === "x" ? positionRole : intervalRole;
212
+ const y = resolved.position.channel === "y" ? positionRole : intervalRole;
213
+ const plan = (channel, role) => {
214
+ const fallback = role === intervalRole
215
+ ? current.intervalScale
216
+ : current.positionScale;
217
+ const stored = findSemanticScale(program, fallback);
218
+ const compatible = role.fieldType === "temporal"
219
+ ? stored?.type === "time"
220
+ : ["linear", "log", "pow", "sqrt", "symlog"].includes(stored?.type);
221
+ const requested = role.scale.type !== undefined || compatible
222
+ ? role.scale
223
+ : { ...role.scale, type: role.fieldType === "temporal" ? "time" : "linear" };
224
+ return resolveDistributionScalePlan(program, {
225
+ channel,
226
+ fieldType: role.fieldType,
227
+ requested,
228
+ fallback,
229
+ defaults: role === intervalRole
230
+ ? EDIT_POLICY.intervalScaleDefaults
231
+ : EDIT_POLICY.scaleDefaults(role.fieldType)
232
+ });
233
+ };
234
+ const xScale = plan("x", x);
235
+ const yScale = plan("y", y);
236
+ return {
237
+ ...resolved,
238
+ x: { ...x, scale: xScale.id },
239
+ y: { ...y, scale: yScale.id },
240
+ xScale,
241
+ yScale,
242
+ category: resolved.position.field,
243
+ categoryType: resolved.position.fieldType,
244
+ measure: intervalTitle,
245
+ previous: {
246
+ x: { field: previous.x.center ?? previous.x.field,
247
+ fieldType: owner.encoding.x.fieldType, scale: owner.encoding.x.scale },
248
+ y: { field: previous.y.center ?? previous.y.field,
249
+ fieldType: owner.encoding.y.fieldType, scale: owner.encoding.y.scale }
250
+ }
251
+ };
252
+ }
253
+
254
+ function updateGrouping(program, ids, previous, nextField) {
255
+ let next = program;
256
+ for (const id of ids) {
257
+ const layer = findLayer(next, id);
258
+ if (layer === undefined) continue;
259
+ if (nextField === undefined) {
260
+ if (layer.encoding?.group !== undefined) {
261
+ next = next.editSemantic({
262
+ property: `layer[${id}].encoding.group`, remove: true
263
+ });
264
+ }
265
+ } else if (layer.encoding?.group === undefined) {
266
+ next = next.encodeGroup({ target: id, field: nextField, fieldType: "nominal" });
267
+ } else if (layer.encoding.group.field !== nextField) {
268
+ next = next.editSemantic({
269
+ property: `layer[${id}].encoding.group.field`, value: nextField
270
+ });
271
+ }
272
+ }
273
+ return next;
274
+ }
275
+
276
+ function updateErrorBandRoles(program, owner, current, candidate, dataId, boundaryIds) {
277
+ const owned = [owner.id, ...boundaryIds];
278
+ let next = updateDistributionPositions(program, owner, current, candidate, {
279
+ owned,
280
+ lower: candidate.fields.lower,
281
+ upper: candidate.fields.upper,
282
+ title: candidate.measure,
283
+ update(value, { category, measure, categoryChannel, measureChannel }) {
284
+ let updated = value;
285
+ for (const [id, field] of [
286
+ [current.lowerBoundaryId, candidate.fields.lower],
287
+ [current.upperBoundaryId, candidate.fields.upper]
288
+ ]) {
289
+ if (findLayer(updated, id) === undefined) continue;
290
+ updated = setCartesianPosition(updated, id, categoryChannel, {
291
+ field: candidate.position.field,
292
+ fieldType: candidate.position.fieldType,
293
+ scale: category.scale
294
+ });
295
+ updated = setCartesianPosition(updated, id, measureChannel, {
296
+ field,
297
+ fieldType: "quantitative",
298
+ scale: measure.scale
299
+ });
300
+ }
301
+ return updated;
302
+ }
303
+ });
304
+ next = updateGrouping(next, owned, current.groupBy, candidate.groupField);
305
+ const revisedConfig = {
306
+ ...current,
307
+ source: candidate.source,
308
+ data: dataId,
309
+ intervalMode: candidate.interval.mode,
310
+ orientation: candidate.orientation,
311
+ position: candidate.position,
312
+ positionScale: candidate.position.channel === "x"
313
+ ? candidate.xScale.id : candidate.yScale.id,
314
+ intervalScale: candidate.interval.channel === "x"
315
+ ? candidate.xScale.id : candidate.yScale.id,
316
+ groupBy: candidate.groupField,
317
+ transformGroupBy: candidate.groupBy,
318
+ lowerField: candidate.fields.lower,
319
+ upperField: candidate.fields.upper
320
+ };
321
+ delete revisedConfig.intervalField;
322
+ delete revisedConfig.centerField;
323
+ if (candidate.interval.mode === "statistical") {
324
+ revisedConfig.intervalField = candidate.interval.field;
325
+ } else {
326
+ revisedConfig.centerField = candidate.fields.center;
327
+ }
328
+ next = next._withMarkConfig(owner.id, {
329
+ ...next.markConfigs[owner.id],
330
+ errorBand: revisedConfig
331
+ }).rematerializeAreaMark({ id: owner.id });
332
+ for (const id of boundaryIds) next = next.rematerializeLineMark({ id });
333
+ return next;
334
+ }
335
+
79
336
  function currentBoundaryAppearance(program, id) {
80
337
  const config = program.markConfigs[id];
81
338
  if (findLayer(program, id)?.mark?.type !== "line" || config === undefined) {
@@ -86,7 +343,8 @@ function currentBoundaryAppearance(program, id) {
86
343
  strokeWidth: config.strokeWidth,
87
344
  strokeDash: config.strokeDash,
88
345
  opacity: config.opacity,
89
- curve: config.curve ?? "linear"
346
+ curve: config.curve ?? "linear",
347
+ ...requestedStrokeDetails(config, "Error-band boundary")
90
348
  };
91
349
  }
92
350
 
@@ -152,7 +410,8 @@ export const rematerializeErrorBandBoundary = action(
152
410
  strokeWidth: args.strokeWidth,
153
411
  strokeDash: args.strokeDash,
154
412
  opacity: args.opacity,
155
- curve: args.curve
413
+ curve: args.curve,
414
+ ...requestedStrokeDetails(args, "Error-band boundary")
156
415
  })
157
416
  .editGraphics({
158
417
  target: id,
@@ -166,19 +425,30 @@ export const rematerializeErrorBandBoundary = action(
166
425
  export const editErrorBand = action(
167
426
  {
168
427
  op: "editErrorBand",
169
- description: "Edit one error-band body appearance."
428
+ description: "Revise one error band's roles, statistics, body, and boundaries."
170
429
  },
171
430
  function (args = {}) {
172
431
  validateOptionObject(args, EDIT_OPTIONS, "editErrorBand");
173
- if (!["fill", "opacity", "curve", "statistics", "boundaries"]
432
+ if (!["data", "x", "y", "groupBy", "fill", "opacity", "curve",
433
+ "statistics", "boundaries", ...STROKE_STYLE_PROPERTIES]
174
434
  .some(key => Object.hasOwn(args, key))) {
175
435
  throw new Error("editErrorBand requires at least one change.");
176
436
  }
177
437
  const owner = resolveOwner(this, args.target);
178
438
  const config = { ...this.markConfigs[owner.id] };
439
+ Object.assign(config, requestedStrokeDetails(args, "editErrorBand"));
179
440
  const errorBand = { ...config.errorBand };
180
441
  if (Object.hasOwn(args, "fill")) {
181
- errorBand.fill = validateNonEmptyString(args.fill, "Error-band fill");
442
+ if (args.fill === false) {
443
+ delete errorBand.fill;
444
+ config.fill = DEFAULT_COLORS.mark;
445
+ } else {
446
+ if (owner.encoding?.color !== undefined) {
447
+ throw new Error("editErrorBand fill conflicts with a color encoding; remove the color encoding first.");
448
+ }
449
+ errorBand.fill = validateNonEmptyString(args.fill, "Error-band fill");
450
+ config.fill = errorBand.fill;
451
+ }
182
452
  }
183
453
  if (Object.hasOwn(args, "opacity")) {
184
454
  config.opacity = validateUnitInterval(args.opacity, "Error-band opacity");
@@ -186,16 +456,30 @@ export const editErrorBand = action(
186
456
  if (Object.hasOwn(args, "curve")) {
187
457
  config.curve = validateCurveInterpolation(args.curve);
188
458
  }
189
- const interval = Object.hasOwn(args, "statistics")
459
+ const roleRequested = ["data", "x", "y", "groupBy"].some(
460
+ key => Object.hasOwn(args, key)
461
+ );
462
+ const candidate = roleRequested
463
+ ? resolveRoleCandidate(this, owner, errorBand, args)
464
+ : undefined;
465
+ const existingBoundaryIds = errorBandBoundaries(errorBand)
466
+ .map(([id]) => id)
467
+ .filter(id => findLayer(this, id) !== undefined);
468
+ const consumers = collectIntervalConsumers(
469
+ this, [owner.id, ...existingBoundaryIds]
470
+ );
471
+ const interval = roleRequested
472
+ ? planIntervalRoleData(this, {
473
+ owner: owner.id,
474
+ currentData: errorBand.data,
475
+ candidate,
476
+ consumers
477
+ })
478
+ : Object.hasOwn(args, "statistics")
190
479
  ? planIntervalEdit(this, {
191
480
  owner: owner.id,
192
481
  data: errorBand.data,
193
- consumers: [
194
- owner.id,
195
- ...errorBandBoundaries(errorBand)
196
- .map(([id]) => id)
197
- .filter(id => findLayer(this, id) !== undefined)
198
- ],
482
+ consumers,
199
483
  statistics: args.statistics,
200
484
  operation: "editErrorBand"
201
485
  })
@@ -210,17 +494,68 @@ export const editErrorBand = action(
210
494
  );
211
495
  }
212
496
 
213
- let next = applyIntervalRevision(this, interval);
214
- if (interval.changed) errorBand.data = interval.revision.id;
215
- next = next
216
- ._withMarkConfig(owner.id, { ...config, errorBand })
217
- .rematerializeAreaMark({ id: owner.id });
497
+ let base = this;
498
+ if (roleRequested && owner.encoding?.color?.field !== undefined) {
499
+ const colorField = owner.encoding.color.field;
500
+ const followsGroup = colorField === this.markConfigs[owner.id].errorBand.groupBy;
501
+ if (followsGroup && candidate.groupField !== undefined) {
502
+ const referenced = Object.entries(
503
+ this.materializationConfigs.selections ?? {}
504
+ ).find(([, selection]) =>
505
+ selection.target === owner.id &&
506
+ ["group", "color"].includes(selection.selector?.channel)
507
+ );
508
+ if (referenced !== undefined && candidate.groupField !== colorField) {
509
+ throw new Error(
510
+ `editErrorBand cannot change groupBy while selection "${referenced[0]}" ` +
511
+ `references ${referenced[1].selector.channel}.`
512
+ );
513
+ }
514
+ base = base.editSemantic({
515
+ property: `layer[${owner.id}].encoding.color.field`,
516
+ value: candidate.groupField
517
+ });
518
+ } else if (
519
+ candidate.interval.mode === "statistical" &&
520
+ !candidate.groupBy.includes(colorField)
521
+ ) {
522
+ throw new Error(
523
+ `editErrorBand roles would remove color field "${colorField}" from the interval data.`
524
+ );
525
+ }
526
+ }
527
+ if (roleRequested && candidate.groupField !== undefined) {
528
+ for (const id of [owner.id, ...existingBoundaryIds]) {
529
+ const layer = findLayer(base, id);
530
+ if (
531
+ layer?.encoding?.group !== undefined &&
532
+ layer.encoding.group.field !== candidate.groupField
533
+ ) {
534
+ base = base.editSemantic({
535
+ property: `layer[${id}].encoding.group.field`,
536
+ value: candidate.groupField
537
+ });
538
+ }
539
+ }
540
+ }
541
+ let next = applyIntervalRevision(base, interval);
542
+ if (interval.changed) {
543
+ errorBand.data = interval.dataId ?? interval.revision.id;
544
+ }
545
+ next = next._withMarkConfig(owner.id, { ...config, errorBand });
546
+ next = roleRequested
547
+ ? updateErrorBandRoles(
548
+ next, owner, this.markConfigs[owner.id].errorBand,
549
+ candidate, interval.dataId, existingBoundaryIds
550
+ )
551
+ : next.rematerializeAreaMark({ id: owner.id });
552
+ const revisedErrorBand = next.markConfigs[owner.id].errorBand;
218
553
  if (Object.hasOwn(args, "boundaries")) {
219
554
  if (args.boundaries === false) {
220
- next = removeBoundary(next, errorBand.lowerBoundaryId);
221
- next = removeBoundary(next, errorBand.upperBoundaryId);
555
+ next = removeBoundary(next, revisedErrorBand.lowerBoundaryId);
556
+ next = removeBoundary(next, revisedErrorBand.upperBoundaryId);
222
557
  } else if (Object.keys(args.boundaries).length === 0) {
223
- for (const [id, bound] of errorBandBoundaries(errorBand)) {
558
+ for (const [id, bound] of errorBandBoundaries(revisedErrorBand)) {
224
559
  next = findLayer(next, id) === undefined
225
560
  ? createBoundary(
226
561
  next,
@@ -239,8 +574,8 @@ export const editErrorBand = action(
239
574
  ...args.boundaries
240
575
  });
241
576
  }
242
- } else if (interval.changed) {
243
- for (const [id] of errorBandBoundaries(errorBand)) {
577
+ } else if (interval.changed && !roleRequested) {
578
+ for (const [id] of errorBandBoundaries(revisedErrorBand)) {
244
579
  if (findLayer(next, id) !== undefined) {
245
580
  next = next.rematerializeLineMark({ id });
246
581
  }
@@ -5,12 +5,14 @@ import {
5
5
  } from "../../core/validation.js";
6
6
  import { findLayer } from "../../selectors/layers.js";
7
7
  import { findDataset } from "../../selectors/datasets.js";
8
+ import { findSemanticScale } from "../../selectors/scales.js";
8
9
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
9
10
  import {
10
11
  readNominalField,
11
12
  validateCategoricalFieldType
12
13
  } from "../../grammar/scales/index.js";
13
14
  import { normalizeOffsetPadding } from "../../grammar/bars/geometry.js";
15
+ import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
14
16
  import { createResolvedIntervalData, ownOptions } from
15
17
  "../data/intervalEdit.js";
16
18
  import { resolveIntervalComposite } from "../intervals/resolve.js";
@@ -31,14 +33,15 @@ const OPTIONS = Object.freeze([
31
33
  "stroke",
32
34
  "strokeWidth",
33
35
  "strokeDash",
34
- "opacity"
36
+ "opacity",
37
+ ...STROKE_STYLE_PROPERTIES
35
38
  ]);
36
39
 
37
40
  const OFFSET_OPTIONS = Object.freeze([
38
41
  "field", "fieldType", "scale", "paddingInner", "paddingOuter"
39
42
  ]);
40
43
 
41
- const ERROR_BAR_POLICY = Object.freeze({
44
+ export const ERROR_BAR_POLICY = Object.freeze({
42
45
  operation: "createErrorBar",
43
46
  resourceLabel: "error-bar",
44
47
  defaultId: "errorBar",
@@ -55,9 +58,9 @@ const ERROR_BAR_POLICY = Object.freeze({
55
58
  "createErrorBar requires one quantitative interval axis and one compatible position axis; use interval options to disambiguate two quantitative channels."
56
59
  });
57
60
 
58
- function resolveErrorBar(program, args) {
59
- const resolved = resolveIntervalComposite(program, args, ERROR_BAR_POLICY);
60
- const offset = resolveErrorBarOffset(program, args, resolved);
61
+ export function resolveErrorBar(program, args, policy = ERROR_BAR_POLICY) {
62
+ const resolved = resolveIntervalComposite(program, args, policy);
63
+ const offset = resolveErrorBarOffset(program, args, resolved, policy.operation);
61
64
  const groupBy = resolved.interval.mode === "statistical" && offset !== undefined
62
65
  ? [...new Set([...resolved.groupBy, offset.field])]
63
66
  : resolved.groupBy;
@@ -70,27 +73,27 @@ function resolveErrorBar(program, args) {
70
73
  };
71
74
  }
72
75
 
73
- function resolveErrorBarOffset(program, args, resolved) {
76
+ function resolveErrorBarOffset(program, args, resolved, operation) {
74
77
  const channel = `${resolved.position.channel}Offset`;
75
78
  const incompatible = channel === "xOffset" ? "yOffset" : "xOffset";
76
79
  if (args[incompatible] !== undefined) {
77
80
  throw new Error(
78
- `createErrorBar ${incompatible} does not match the ${resolved.orientation} interval orientation.`
81
+ `${operation} ${incompatible} does not match the ${resolved.orientation} interval orientation.`
79
82
  );
80
83
  }
81
84
  const explicit = args[channel] === undefined
82
85
  ? undefined
83
- : validateOptionObject(args[channel], OFFSET_OPTIONS, `createErrorBar ${channel}`);
86
+ : validateOptionObject(args[channel], OFFSET_OPTIONS, `${operation} ${channel}`);
84
87
  const inferred = resolved.sourceLayer?.encoding?.[channel];
85
88
  if (explicit === undefined && inferred === undefined) return undefined;
86
89
  if (!["nominal", "ordinal"].includes(resolved.position.fieldType)) {
87
90
  throw new Error(
88
- `createErrorBar ${channel} requires a categorical ${resolved.position.channel} position.`
91
+ `${operation} ${channel} requires a categorical ${resolved.position.channel} position.`
89
92
  );
90
93
  }
91
94
  const field = explicit?.field ?? inferred?.field;
92
95
  if (typeof field !== "string" || field.length === 0) {
93
- throw new TypeError(`createErrorBar ${channel} field must be a non-empty string.`);
96
+ throw new TypeError(`${operation} ${channel} field must be a non-empty string.`);
94
97
  }
95
98
  const fieldType = validateCategoricalFieldType(
96
99
  explicit?.fieldType ?? inferred?.fieldType ?? "nominal"
@@ -101,7 +104,15 @@ function resolveErrorBarOffset(program, args, resolved) {
101
104
  explicit ?? {},
102
105
  resolved.sourceLayer === undefined
103
106
  ? undefined
104
- : program.markConfigs[resolved.sourceLayer.id]?.[channel],
107
+ : (() => {
108
+ const semantic = inferred?.scale === undefined
109
+ ? undefined
110
+ : findSemanticScale(program, inferred.scale);
111
+ return semantic !== undefined && ["paddingInner", "paddingOuter"]
112
+ .some(property => Object.hasOwn(semantic, property))
113
+ ? semantic
114
+ : program.markConfigs[resolved.sourceLayer.id]?.[channel];
115
+ })(),
105
116
  channel
106
117
  );
107
118
  return {
@@ -118,7 +129,8 @@ function resolveErrorBarOffset(program, args, resolved) {
118
129
 
119
130
  function resolveAppearance(args) {
120
131
  return resolveErrorBarAppearance(ownOptions(args, [
121
- "caps", "capSize", "stroke", "strokeWidth", "strokeDash", "opacity"
132
+ "caps", "capSize", "stroke", "strokeWidth", "strokeDash", "opacity",
133
+ ...STROKE_STYLE_PROPERTIES
122
134
  ]), {
123
135
  defaults: {
124
136
  caps: true,
@@ -140,10 +152,11 @@ export const createErrorBarCap = action(
140
152
  function (args = {}) {
141
153
  validateKeys(args, [
142
154
  "id", "data", "orientation", "positionField", "positionFieldType",
143
- "intervalField", "coordinate", "positionScale", "intervalScale",
155
+ "intervalField", "coordinate", "positionScale", "intervalScale", "positionTemporalUnit",
144
156
  "offsetChannel", "offsetField", "offsetFieldType", "offsetScale",
145
157
  "offsetPaddingInner", "offsetPaddingOuter",
146
- "capSize", "stroke", "strokeWidth", "strokeDash", "opacity"
158
+ "capSize", "stroke", "strokeWidth", "strokeDash", "opacity",
159
+ ...STROKE_STYLE_PROPERTIES
147
160
  ], "createErrorBarCap");
148
161
  if (!["vertical", "horizontal"].includes(args.orientation)) {
149
162
  throw new Error(`Unsupported error-bar orientation "${args.orientation}".`);
@@ -161,11 +174,18 @@ export const createErrorBarCap = action(
161
174
  const positionAction = vertical ? "encodeX" : "encodeY";
162
175
  const intervalAction = vertical ? "encodeY" : "encodeX";
163
176
  let next = this
164
- .createRuleMark({ id: args.id, data: args.data })
177
+ .createRuleMark({
178
+ id: args.id,
179
+ data: args.data,
180
+ ...Object.fromEntries(STROKE_STYLE_PROPERTIES.flatMap(property =>
181
+ Object.hasOwn(args, property) ? [[property, args[property]]] : []
182
+ ))
183
+ })
165
184
  [positionAction]({
166
185
  target: args.id,
167
186
  field: args.positionField,
168
187
  fieldType: args.positionFieldType,
188
+ ...(args.positionTemporalUnit === undefined ? {} : { temporalUnit: args.positionTemporalUnit }),
169
189
  coordinate: args.coordinate,
170
190
  scale: { id: args.positionScale }
171
191
  })
@@ -207,6 +227,7 @@ function positionArgs(resolved) {
207
227
  target: resolved.id,
208
228
  field: resolved.position.field,
209
229
  fieldType: resolved.position.fieldType,
230
+ ...(resolved.position.temporalUnit === undefined ? {} : { temporalUnit: resolved.position.temporalUnit }),
210
231
  coordinate: resolved.coordinate,
211
232
  scale: resolved.position.scale
212
233
  };
@@ -245,7 +266,13 @@ export const createErrorBar = action(
245
266
  const resolved = resolveErrorBar(this, args);
246
267
  const appearance = resolveAppearance(args);
247
268
  let next = createResolvedIntervalData(this, resolved);
248
- next = next.createRuleMark({ id: resolved.id, data: resolved.dataId });
269
+ next = next.createRuleMark({
270
+ id: resolved.id,
271
+ data: resolved.dataId,
272
+ ...Object.fromEntries(STROKE_STYLE_PROPERTIES.flatMap(property =>
273
+ Object.hasOwn(appearance, property) ? [[property, appearance[property]]] : []
274
+ ))
275
+ });
249
276
  const positionAction = resolved.position.channel === "x" ? "encodeX" : "encodeY";
250
277
  const intervalAction = resolved.interval.channel === "x" ? "encodeX" : "encodeY";
251
278
  const secondaryAction = resolved.interval.channel === "x" ? "encodeX2" : "encodeY2";
@@ -282,6 +309,7 @@ export const createErrorBar = action(
282
309
  orientation: resolved.orientation,
283
310
  positionField: resolved.position.field,
284
311
  positionFieldType: resolved.position.fieldType,
312
+ ...(resolved.position.temporalUnit === undefined ? {} : { positionTemporalUnit: resolved.position.temporalUnit }),
285
313
  intervalField: field,
286
314
  coordinate: resolved.coordinate,
287
315
  positionScale: intervalLayer.encoding[resolved.position.channel].scale,
@@ -300,17 +328,28 @@ export const createErrorBar = action(
300
328
  stroke: appearance.stroke,
301
329
  strokeWidth: appearance.strokeWidth,
302
330
  strokeDash: appearance.strokeDash,
303
- opacity: appearance.opacity
331
+ opacity: appearance.opacity,
332
+ ...Object.fromEntries(STROKE_STYLE_PROPERTIES.flatMap(property =>
333
+ Object.hasOwn(appearance, property) ? [[property, appearance[property]]] : []
334
+ ))
304
335
  });
305
336
  }
306
337
  }
307
338
  return next._withMarkConfig(resolved.id, {
308
339
  ...next.markConfigs[resolved.id],
309
340
  errorBar: {
341
+ source: resolved.source,
342
+ intervalMode: resolved.interval.mode,
343
+ ...(resolved.interval.mode === "statistical"
344
+ ? { intervalField: resolved.interval.field }
345
+ : { centerField: resolved.fields.center }),
346
+ groupField: resolved.groupField,
347
+ groupBy: resolved.groupBy,
310
348
  data: resolved.dataId,
311
349
  orientation: resolved.orientation,
312
350
  positionField: resolved.position.field,
313
351
  positionFieldType: resolved.position.fieldType,
352
+ ...(resolved.position.temporalUnit === undefined ? {} : { positionTemporalUnit: resolved.position.temporalUnit }),
314
353
  lowerField: resolved.fields.lower,
315
354
  upperField: resolved.fields.upper,
316
355
  coordinate: resolved.coordinate,