ggaction 0.0.12 → 0.0.13

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 (281) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +2 -2
  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 +4 -2
  11. package/src/actions/basic.js +2 -0
  12. package/src/actions/boxPlots/materialize.js +2 -1
  13. package/src/actions/canvas/actions.js +3 -1
  14. package/src/actions/canvas/fitting.js +211 -0
  15. package/src/actions/canvas/index.js +2 -0
  16. package/src/actions/categoryOrder/index.js +3 -2
  17. package/src/actions/charts/area.js +47 -0
  18. package/src/actions/charts/bar.js +11 -5
  19. package/src/actions/charts/beeswarm.js +70 -0
  20. package/src/actions/charts/density.js +44 -0
  21. package/src/actions/charts/ecdf.js +183 -0
  22. package/src/actions/charts/endpoints.js +451 -0
  23. package/src/actions/charts/heatmap.js +2 -2
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +71 -0
  26. package/src/actions/charts/index.js +37 -0
  27. package/src/actions/charts/interval-regression.js +169 -0
  28. package/src/actions/charts/line.js +5 -5
  29. package/src/actions/charts/parallel.js +1 -1
  30. package/src/actions/charts/pie.js +44 -0
  31. package/src/actions/charts/polar.js +123 -0
  32. package/src/actions/charts/radar.js +299 -0
  33. package/src/actions/charts/radial.js +42 -0
  34. package/src/actions/charts/raincloud.js +422 -0
  35. package/src/actions/charts/rug-strip.js +279 -0
  36. package/src/actions/charts/scatter.js +9 -3
  37. package/src/actions/charts/shared.js +69 -10
  38. package/src/actions/composition/actions.js +118 -1
  39. package/src/actions/data/bin.js +29 -0
  40. package/src/actions/data/computed.js +24 -0
  41. package/src/actions/data/derived.js +66 -0
  42. package/src/actions/data/ecdf.js +38 -0
  43. package/src/actions/data/filter.js +212 -28
  44. package/src/actions/data/fold.js +24 -0
  45. package/src/actions/data/index.js +24 -0
  46. package/src/actions/data/interval.js +2 -0
  47. package/src/actions/data/intervalEdit.js +86 -4
  48. package/src/actions/data/regression.js +5 -2
  49. package/src/actions/data/stack.js +26 -0
  50. package/src/actions/data/summary.js +26 -0
  51. package/src/actions/data/timeUnit.js +1 -1
  52. package/src/actions/encodings/appearance.js +32 -13
  53. package/src/actions/encodings/barWidth.js +8 -0
  54. package/src/actions/encodings/basic.js +2 -0
  55. package/src/actions/encodings/color/continuous.js +19 -59
  56. package/src/actions/encodings/color/index.js +28 -22
  57. package/src/actions/encodings/color/layout.js +3 -97
  58. package/src/actions/encodings/color/policy.js +5 -8
  59. package/src/actions/encodings/density/resolve.js +37 -5
  60. package/src/actions/encodings/density.js +120 -33
  61. package/src/actions/encodings/horizon/resolve.js +8 -4
  62. package/src/actions/encodings/horizon.js +18 -8
  63. package/src/actions/encodings/index.js +2 -0
  64. package/src/actions/encodings/offset.js +10 -16
  65. package/src/actions/encodings/parallel.js +2 -8
  66. package/src/actions/encodings/position/apply.js +10 -4
  67. package/src/actions/encodings/position/index.js +43 -5
  68. package/src/actions/encodings/position/policies/arc.js +14 -0
  69. package/src/actions/encodings/position/policies/area.js +4 -42
  70. package/src/actions/encodings/position/policies/bar.js +48 -13
  71. package/src/actions/encodings/position/resolve.js +67 -22
  72. package/src/actions/encodings/ranged.js +123 -97
  73. package/src/actions/encodings/remove.js +37 -32
  74. package/src/actions/encodings/ruleAppearance.js +23 -9
  75. package/src/actions/encodings/seriesLayout.js +104 -0
  76. package/src/actions/encodings/shared.js +25 -23
  77. package/src/actions/encodings/strokeDash.js +9 -6
  78. package/src/actions/encodings/temporal.js +7 -0
  79. package/src/actions/encodings/text.js +30 -15
  80. package/src/actions/errorBands/create.js +18 -5
  81. package/src/actions/errorBands/edit.js +341 -21
  82. package/src/actions/errorBars/create.js +21 -10
  83. package/src/actions/errorBars/edit.js +355 -19
  84. package/src/actions/facets/actions.js +410 -12
  85. package/src/actions/facets/derive.js +57 -8
  86. package/src/actions/gradientPlots/components.js +33 -11
  87. package/src/actions/gradientPlots/materialize.js +5 -7
  88. package/src/actions/guides/applicability.js +20 -17
  89. package/src/actions/guides/axes/axes.js +4 -3
  90. package/src/actions/guides/axes/axis.js +18 -18
  91. package/src/actions/guides/axes/components.js +43 -0
  92. package/src/actions/guides/axes/edit.js +16 -51
  93. package/src/actions/guides/axes/labels.js +129 -19
  94. package/src/actions/guides/axes/lines.js +7 -5
  95. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  96. package/src/actions/guides/axes/parallel/policy.js +137 -0
  97. package/src/actions/guides/axes/parallel/resolve.js +44 -23
  98. package/src/actions/guides/axes/parallel.js +93 -217
  99. package/src/actions/guides/axes/policy.js +21 -67
  100. package/src/actions/guides/axes/tickGroups.js +14 -8
  101. package/src/actions/guides/axes/ticks.js +5 -4
  102. package/src/actions/guides/axes/titles.js +21 -8
  103. package/src/actions/guides/basic.js +2 -0
  104. package/src/actions/guides/facade.js +106 -0
  105. package/src/actions/guides/facadeAxes.js +200 -0
  106. package/src/actions/guides/facadeLegend.js +63 -0
  107. package/src/actions/guides/grids/grid.js +37 -33
  108. package/src/actions/guides/grids/resolve.js +10 -2
  109. package/src/actions/guides/guides.js +35 -28
  110. package/src/actions/guides/legends/categorical/actions.js +193 -249
  111. package/src/actions/guides/legends/categorical/components.js +4 -5
  112. package/src/actions/guides/legends/categorical/layout.js +36 -349
  113. package/src/actions/guides/legends/categorical/options.js +30 -14
  114. package/src/actions/guides/legends/categorical/recipes.js +14 -0
  115. package/src/actions/guides/legends/categorical/resolve.js +21 -10
  116. package/src/actions/guides/legends/categorical/symbols.js +2 -3
  117. package/src/actions/guides/legends/continuous/common.js +69 -14
  118. package/src/actions/guides/legends/continuous/gradient.js +111 -83
  119. package/src/actions/guides/legends/continuous/index.js +6 -2
  120. package/src/actions/guides/legends/continuous/interval.js +71 -92
  121. package/src/actions/guides/legends/continuous/opacity.js +105 -84
  122. package/src/actions/guides/legends/creation.js +27 -0
  123. package/src/actions/guides/legends/edit.js +198 -66
  124. package/src/actions/guides/legends/focused.js +2 -2
  125. package/src/actions/guides/legends/lane.js +98 -86
  126. package/src/actions/guides/legends/lifecycle.js +132 -0
  127. package/src/actions/guides/legends/remove.js +31 -76
  128. package/src/actions/guides/legends/size.js +149 -105
  129. package/src/actions/guides/legends/strokeWidth.js +106 -64
  130. package/src/actions/guides/legends/target.js +12 -0
  131. package/src/actions/guides/legends/transition.js +43 -0
  132. package/src/actions/guides/polar/axes/facade.js +77 -62
  133. package/src/actions/guides/polar/axes/labels.js +5 -4
  134. package/src/actions/guides/polar/axes/lines.js +2 -1
  135. package/src/actions/guides/polar/axes/shared.js +6 -0
  136. package/src/actions/guides/polar/axes/ticks.js +5 -4
  137. package/src/actions/guides/polar/axes/titles.js +5 -4
  138. package/src/actions/guides/polar/grids.js +7 -7
  139. package/src/actions/guides/reuse.js +44 -0
  140. package/src/actions/guides/tickValues.js +19 -21
  141. package/src/actions/index.js +2 -0
  142. package/src/actions/intervals/resolve.js +35 -15
  143. package/src/actions/marks/arc/actions.js +1 -1
  144. package/src/actions/marks/area/actions.js +18 -4
  145. package/src/actions/marks/area/materialize.js +5 -2
  146. package/src/actions/marks/bar/materialize.js +11 -7
  147. package/src/actions/marks/index.js +5 -0
  148. package/src/actions/marks/line/actions.js +43 -108
  149. package/src/actions/marks/line/materialize.js +34 -52
  150. package/src/actions/marks/point/edit.js +11 -15
  151. package/src/actions/marks/point/index.js +1 -0
  152. package/src/actions/marks/point/jitter.js +3 -0
  153. package/src/actions/marks/point/materialize.js +106 -2
  154. package/src/actions/marks/point/packing.js +68 -0
  155. package/src/actions/marks/references.js +87 -0
  156. package/src/actions/marks/remove.js +17 -4
  157. package/src/actions/marks/rule/actions.js +34 -3
  158. package/src/actions/marks/rule/appearance.js +26 -0
  159. package/src/actions/marks/shared.js +12 -12
  160. package/src/actions/marks/text/actions.js +224 -6
  161. package/src/actions/marks/text/layout.js +2 -2
  162. package/src/actions/primitives/createGraphics.js +3 -1
  163. package/src/actions/primitives/semantic.js +1 -1
  164. package/src/actions/primitives/semanticAction.js +39 -2
  165. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  166. package/src/actions/primitives/semanticValidation/layer.js +18 -1
  167. package/src/actions/primitives/semanticValidation/scale.js +10 -1
  168. package/src/actions/regression/create.js +1 -1
  169. package/src/actions/regression/edit.js +19 -6
  170. package/src/actions/regression/resolve.js +1 -1
  171. package/src/actions/scales/consumers/common.js +20 -53
  172. package/src/actions/scales/consumers/families.js +27 -14
  173. package/src/actions/scales/consumers/index.js +23 -2
  174. package/src/actions/scales/consumers/seriesLayout.js +33 -2
  175. package/src/actions/scales/create.js +4 -4
  176. package/src/actions/scales/definitions.js +19 -3
  177. package/src/actions/scales/edit.js +46 -34
  178. package/src/actions/scales/editPolicy.js +21 -31
  179. package/src/actions/scales/materialize.js +10 -46
  180. package/src/actions/scales/patch.js +1 -1
  181. package/src/actions/scales/preview.js +39 -0
  182. package/src/actions/scales/quantitativeColor.js +9 -5
  183. package/src/actions/theme/actions.js +43 -0
  184. package/src/actions/theme/index.js +9 -0
  185. package/src/actions/theme/reconcile.js +882 -0
  186. package/src/actions/titles/actions.js +11 -10
  187. package/src/actions/titles/resolve.js +1 -40
  188. package/src/actions/violinPlots/create.js +86 -42
  189. package/src/actions/violinPlots/edit.js +191 -0
  190. package/src/actions/violinPlots/index.js +2 -0
  191. package/src/core/ChartProgram.js +7 -0
  192. package/src/core/action.js +22 -2
  193. package/src/core/compositionState.js +136 -8
  194. package/src/grammar/aggregate.js +66 -23
  195. package/src/grammar/arcs.js +58 -1
  196. package/src/grammar/areaEndpoints.js +46 -0
  197. package/src/grammar/areaSeries.js +87 -41
  198. package/src/grammar/bars/aggregate.js +33 -12
  199. package/src/grammar/bars/policy.js +9 -7
  200. package/src/grammar/bin.js +181 -0
  201. package/src/grammar/categoryOrder.js +34 -0
  202. package/src/grammar/computed.js +150 -0
  203. package/src/grammar/ecdf.js +198 -0
  204. package/src/grammar/facets/index.js +117 -6
  205. package/src/grammar/fold.js +123 -0
  206. package/src/grammar/horizon.js +6 -4
  207. package/src/grammar/interval.js +37 -20
  208. package/src/grammar/lineSeries.js +20 -63
  209. package/src/grammar/markFilter.js +22 -4
  210. package/src/grammar/markLabels.js +93 -0
  211. package/src/grammar/parallelAxisTitles.js +16 -0
  212. package/src/grammar/pathSeries.js +91 -0
  213. package/src/grammar/pointPacking.js +183 -0
  214. package/src/grammar/positionDatum.js +12 -0
  215. package/src/grammar/rects.js +9 -1
  216. package/src/grammar/regression/derive.js +4 -0
  217. package/src/grammar/regression/index.js +7 -2
  218. package/src/grammar/regression/models.js +22 -7
  219. package/src/grammar/regression/parameters.js +40 -13
  220. package/src/grammar/rotation.js +29 -0
  221. package/src/grammar/rules.js +7 -21
  222. package/src/grammar/scales/color.js +23 -1
  223. package/src/grammar/scales/colorConsumers.js +38 -0
  224. package/src/grammar/scales/definition.js +15 -1
  225. package/src/grammar/scales/discretized.js +11 -5
  226. package/src/grammar/scales/fields.js +38 -8
  227. package/src/grammar/scales/mapping.js +2 -0
  228. package/src/grammar/scales/radial.js +55 -0
  229. package/src/grammar/scales/types.js +1 -0
  230. package/src/grammar/schemas/semanticPath.js +17 -3
  231. package/src/grammar/stack.js +170 -0
  232. package/src/grammar/statistics/confidenceInterval.js +101 -0
  233. package/src/grammar/summary.js +183 -0
  234. package/src/grammar/text.js +22 -16
  235. package/src/grammar/timeUnit.js +21 -11
  236. package/src/grammar/transformTopology.js +6 -0
  237. package/src/grammar/transforms.js +36 -0
  238. package/src/grammar/valueFormat.js +112 -0
  239. package/src/grammar/window.js +13 -7
  240. package/src/layout/facets.js +41 -7
  241. package/src/layout/guideCollisions.js +12 -0
  242. package/src/layout/legend.js +2 -10
  243. package/src/layout/legendItems.js +102 -0
  244. package/src/layout/legendLane.js +100 -11
  245. package/src/materialization/bars/aggregate.js +38 -152
  246. package/src/materialization/bars/histogram.js +24 -0
  247. package/src/materialization/categorySlotOffset.js +40 -0
  248. package/src/materialization/compositionSnapshot.js +1 -1
  249. package/src/materialization/dependencies.js +57 -16
  250. package/src/materialization/encodings.js +1 -1
  251. package/src/materialization/facets.js +8 -1
  252. package/src/materialization/guides/layout.js +57 -0
  253. package/src/materialization/guides/resources.js +2 -2
  254. package/src/materialization/legends.js +10 -1
  255. package/src/materialization/marks/capabilities.js +20 -5
  256. package/src/materialization/marks/grouping.js +52 -0
  257. package/src/materialization/marks/index.js +58 -23
  258. package/src/materialization/marks/policies.js +2 -0
  259. package/src/materialization/rect.js +33 -33
  260. package/src/materialization/rowEncoding.js +13 -13
  261. package/src/materialization/scaleGuideDependencies.js +8 -5
  262. package/src/materialization/scales/map.js +1 -0
  263. package/src/materialization/scales/policies/arc.js +42 -0
  264. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  265. package/src/materialization/scales/policies/series.js +2 -1
  266. package/src/materialization/scales/resolve.js +46 -14
  267. package/src/materialization/selection/compatibility.js +11 -0
  268. package/src/materialization/selection/items/bar.js +12 -68
  269. package/src/materialization/selection/items/common.js +8 -2
  270. package/src/materialization/selection/items/path.js +3 -13
  271. package/src/materialization/selection/items/rect.js +7 -4
  272. package/src/materialization/text.js +64 -5
  273. package/src/mcp/adapter.js +2 -2
  274. package/src/selectors/datasets.js +13 -0
  275. package/src/selectors/layers.js +3 -2
  276. package/src/selectors/scales.js +6 -0
  277. package/src/theme/defaults.js +39 -0
  278. package/types/basic.d.ts +11 -2
  279. package/types/index.d.ts +126 -0
  280. package/types/program.d.ts +1320 -135
  281. package/src/materialization/bars/grouped.js +0 -144
@@ -1,4 +1,5 @@
1
1
  import { action } from "../../core/action.js";
2
+ import { isPlainObject } from "../../core/immutable.js";
2
3
  import { validateUserId } from "../../core/identifiers.js";
3
4
  import {
4
5
  validateKeys,
@@ -13,24 +14,50 @@ import {
13
14
  validateRuleStrokeWidth
14
15
  } from "../../grammar/ruleAppearance.js";
15
16
  import { findLayer } from "../../selectors/layers.js";
17
+ import { findSemanticScale } from "../../selectors/scales.js";
18
+ import { resolveIntervalComposite } from "../intervals/resolve.js";
19
+ import {
20
+ resolveDistributionScalePlan,
21
+ setCartesianPosition,
22
+ updateDistributionPositions
23
+ } from "../distributions/revision.js";
16
24
  import {
17
25
  applyIntervalRevision,
26
+ collectIntervalConsumers,
27
+ findIntervalTransform,
18
28
  ownOptions,
19
29
  planIntervalEdit,
30
+ planIntervalRoleData,
20
31
  releaseIntervalRevision,
21
32
  resolveIntervalOwner
22
33
  } from "../data/intervalEdit.js";
23
34
 
24
35
  export const ERROR_BAR_EDIT_OPTIONS = Object.freeze([
25
- "target", "caps", "capSize", "stroke", "strokeWidth",
26
- "strokeDash", "opacity", "statistics"
36
+ "target", "data", "x", "y", "xOffset", "yOffset", "groupBy",
37
+ "caps", "capSize", "stroke", "strokeWidth", "strokeDash", "opacity",
38
+ "statistics"
27
39
  ]);
28
40
 
29
- export const ERROR_BAR_APPEARANCE_OPTIONS = Object.freeze(
30
- ERROR_BAR_EDIT_OPTIONS.filter(option =>
31
- option !== "target" && option !== "statistics"
32
- )
33
- );
41
+ export const ERROR_BAR_APPEARANCE_OPTIONS = Object.freeze([
42
+ "caps", "capSize", "stroke", "strokeWidth", "strokeDash", "opacity"
43
+ ]);
44
+ const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "method", "level"]);
45
+ const EDIT_POLICY = Object.freeze({
46
+ operation: "editErrorBar",
47
+ resourceLabel: "error-bar",
48
+ defaultId: "errorBar",
49
+ ownerLabel: "Error-bar id",
50
+ positionTypes: Object.freeze([
51
+ "quantitative", "nominal", "ordinal", "temporal"
52
+ ]),
53
+ defaultPositionType: "nominal",
54
+ defaultIntervalChannel: "y",
55
+ scaleDefaults: () => ({}),
56
+ intervalScaleDefaults: Object.freeze({ nice: true, zero: false }),
57
+ allowExplicitGrouping: false,
58
+ ambiguousMessage:
59
+ "editErrorBar requires one quantitative interval axis and one compatible position axis; use interval options to disambiguate two quantitative channels."
60
+ });
34
61
 
35
62
  export function resolveErrorBarAppearance(args, { defaults, operation }) {
36
63
  validateKeys(args, ERROR_BAR_EDIT_OPTIONS, operation);
@@ -75,6 +102,177 @@ function resolveOwner(program, requested) {
75
102
  });
76
103
  }
77
104
 
105
+ function currentRoleArgs(program, owner, current) {
106
+ const transform = findIntervalTransform(program, current.data);
107
+ const intervalChannel = current.orientation === "vertical" ? "y" : "x";
108
+ const positionChannel = intervalChannel === "x" ? "y" : "x";
109
+ const position = {
110
+ field: current.positionField,
111
+ fieldType: current.positionFieldType,
112
+ ...(current.positionTemporalUnit === undefined
113
+ ? {}
114
+ : { temporalUnit: current.positionTemporalUnit }),
115
+ scale: { id: current.positionScale }
116
+ };
117
+ const interval = transform === undefined
118
+ ? {
119
+ center: current.centerField ?? owner.encoding[intervalChannel].title,
120
+ lower: current.lowerField,
121
+ upper: current.upperField,
122
+ scale: { id: current.intervalScale }
123
+ }
124
+ : {
125
+ field: transform.field,
126
+ center: transform.center,
127
+ extent: transform.extent,
128
+ ...(transform.method === undefined ? {} : { method: transform.method }),
129
+ ...(transform.level === undefined ? {} : { level: transform.level }),
130
+ scale: { id: current.intervalScale }
131
+ };
132
+ return {
133
+ source: current.source ?? transform?.source ?? current.data,
134
+ x: positionChannel === "x" ? position : interval,
135
+ y: positionChannel === "y" ? position : interval,
136
+ groupBy: current.groupField ?? transform?.groupBy?.find(
137
+ field => field !== current.positionField
138
+ )
139
+ };
140
+ }
141
+
142
+ function resolveRoleCandidate(program, owner, current, args) {
143
+ const previous = currentRoleArgs(program, owner, current);
144
+ const full = {
145
+ data: Object.hasOwn(args, "data") ? args.data : previous.source,
146
+ x: Object.hasOwn(args, "x") ? args.x : previous.x,
147
+ y: Object.hasOwn(args, "y") ? args.y : previous.y,
148
+ ...(Object.hasOwn(args, "groupBy")
149
+ ? { groupBy: args.groupBy }
150
+ : previous.groupBy === undefined ? {} : { groupBy: previous.groupBy }),
151
+ coordinate: current.coordinate
152
+ };
153
+ const policy = {
154
+ ...EDIT_POLICY,
155
+ existingId: owner.id,
156
+ coordinate: current.coordinate,
157
+ generatedFields: findIntervalTransform(program, current.data)?.as
158
+ };
159
+ let resolved = resolveIntervalComposite(program, full, policy);
160
+ if (Object.hasOwn(args, "statistics")) {
161
+ if (!isPlainObject(args.statistics)) {
162
+ throw new TypeError("editErrorBar statistics must be a plain object.");
163
+ }
164
+ validateKeys(args.statistics, STATISTICS_OPTIONS, "editErrorBar statistics");
165
+ if (!STATISTICS_OPTIONS.some(key => Object.hasOwn(args.statistics, key))) {
166
+ throw new Error(
167
+ "editErrorBar statistics requires center, extent, method, or level."
168
+ );
169
+ }
170
+ full[resolved.interval.channel] = {
171
+ ...full[resolved.interval.channel],
172
+ ...args.statistics
173
+ };
174
+ resolved = resolveIntervalComposite(program, full, policy);
175
+ }
176
+ const positionRole = {
177
+ field: resolved.position.field,
178
+ fieldType: resolved.position.fieldType,
179
+ scale: resolved.position.scale
180
+ };
181
+ const intervalTitle = resolved.interval.mode === "statistical"
182
+ ? resolved.interval.field
183
+ : resolved.interval.title;
184
+ const intervalRole = {
185
+ field: intervalTitle,
186
+ fieldType: "quantitative",
187
+ scale: resolved.interval.scale
188
+ };
189
+ const x = resolved.position.channel === "x" ? positionRole : intervalRole;
190
+ const y = resolved.position.channel === "y" ? positionRole : intervalRole;
191
+ const plan = (channel, role) => {
192
+ const fallback = role === intervalRole
193
+ ? current.intervalScale
194
+ : current.positionScale;
195
+ const stored = findSemanticScale(program, fallback);
196
+ const compatible = role.fieldType === "temporal"
197
+ ? stored?.type === "time"
198
+ : ["nominal", "ordinal"].includes(role.fieldType)
199
+ ? ["band", "point"].includes(stored?.type)
200
+ : ["linear", "log", "pow", "sqrt", "symlog"].includes(stored?.type);
201
+ const defaultType = role.fieldType === "temporal"
202
+ ? "time"
203
+ : ["nominal", "ordinal"].includes(role.fieldType) ? "band" : "linear";
204
+ const requested = role.scale.type !== undefined || compatible
205
+ ? role.scale
206
+ : { ...role.scale, type: defaultType };
207
+ return resolveDistributionScalePlan(program, {
208
+ channel,
209
+ fieldType: role.fieldType,
210
+ requested,
211
+ fallback,
212
+ defaults: role === intervalRole
213
+ ? EDIT_POLICY.intervalScaleDefaults
214
+ : EDIT_POLICY.scaleDefaults(role.fieldType)
215
+ });
216
+ };
217
+ const xScale = plan("x", x);
218
+ const yScale = plan("y", y);
219
+ const expectedOffset = `${resolved.position.channel}Offset`;
220
+ const incompatibleOffset = expectedOffset === "xOffset" ? "yOffset" : "xOffset";
221
+ if (Object.hasOwn(args, incompatibleOffset)) {
222
+ throw new Error(
223
+ `editErrorBar ${incompatibleOffset} does not match the ${resolved.orientation} interval orientation.`
224
+ );
225
+ }
226
+ const offset = Object.hasOwn(args, expectedOffset)
227
+ ? args[expectedOffset] === false ? undefined : args[expectedOffset]
228
+ : current.offset === undefined ? undefined : {
229
+ field: current.offset.field,
230
+ fieldType: current.offset.fieldType,
231
+ scale: { id: current.offset.scale },
232
+ paddingInner: current.offset.paddingInner,
233
+ paddingOuter: current.offset.paddingOuter
234
+ };
235
+ const normalizedOffset = offset === undefined ? undefined : {
236
+ channel: expectedOffset,
237
+ field: offset.field,
238
+ fieldType: offset.fieldType ?? "nominal",
239
+ scale: offset.scale ?? { id: current.offset?.scale ?? expectedOffset },
240
+ paddingInner: offset.paddingInner ?? current.offset?.paddingInner ?? 0,
241
+ paddingOuter: offset.paddingOuter ?? current.offset?.paddingOuter ?? 0
242
+ };
243
+ if (normalizedOffset !== undefined) {
244
+ if (typeof normalizedOffset.field !== "string" || normalizedOffset.field.length === 0) {
245
+ throw new TypeError(`editErrorBar ${expectedOffset} field must be a non-empty string.`);
246
+ }
247
+ if (!["nominal", "ordinal"].includes(normalizedOffset.fieldType)) {
248
+ throw new Error(`editErrorBar ${expectedOffset} requires a categorical field.`);
249
+ }
250
+ if (resolved.interval.mode === "statistical") {
251
+ resolved = {
252
+ ...resolved,
253
+ groupBy: [...new Set([...resolved.groupBy, normalizedOffset.field])]
254
+ };
255
+ }
256
+ }
257
+ return {
258
+ ...resolved,
259
+ offset: normalizedOffset,
260
+ x: { ...x, scale: xScale.id },
261
+ y: { ...y, scale: yScale.id },
262
+ xScale,
263
+ yScale,
264
+ category: resolved.position.field,
265
+ categoryType: resolved.position.fieldType,
266
+ measure: intervalTitle,
267
+ previous: {
268
+ x: { field: previous.x.center ?? previous.x.field,
269
+ fieldType: owner.encoding.x.fieldType, scale: owner.encoding.x.scale },
270
+ y: { field: previous.y.center ?? previous.y.field,
271
+ fieldType: owner.encoding.y.fieldType, scale: owner.encoding.y.scale }
272
+ }
273
+ };
274
+ }
275
+
78
276
  function removeCap(program, id) {
79
277
  if (findLayer(program, id) === undefined) return program;
80
278
  return program
@@ -90,6 +288,7 @@ function capArgs(config, id, intervalField) {
90
288
  orientation: config.orientation,
91
289
  positionField: config.positionField,
92
290
  positionFieldType: config.positionFieldType,
291
+ ...(config.positionTemporalUnit === undefined ? {} : { positionTemporalUnit: config.positionTemporalUnit }),
93
292
  intervalField,
94
293
  coordinate: config.coordinate,
95
294
  positionScale: config.positionScale,
@@ -129,6 +328,123 @@ function rematerializeRuleAppearance(program, id, config, fixedSpan) {
129
328
  .rematerializeRuleMark({ id });
130
329
  }
131
330
 
331
+ function updateErrorBarOffsets(program, ids, current, candidate) {
332
+ let next = program;
333
+ const oldChannel = current.offset?.channel;
334
+ const newChannel = candidate.offset?.channel;
335
+ for (const id of ids) {
336
+ const layer = findLayer(next, id);
337
+ if (
338
+ layer !== undefined && oldChannel !== undefined &&
339
+ (newChannel !== oldChannel || candidate.offset === undefined) &&
340
+ layer.encoding?.[oldChannel] !== undefined
341
+ ) {
342
+ next = next.editSemantic({
343
+ property: `layer[${id}].encoding.${oldChannel}`, remove: true
344
+ });
345
+ }
346
+ }
347
+ if (candidate.offset === undefined) return next;
348
+ const actionName = newChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset";
349
+ for (const id of ids) {
350
+ if (findLayer(next, id) === undefined) continue;
351
+ next = next[actionName]({
352
+ target: id,
353
+ field: candidate.offset.field,
354
+ fieldType: candidate.offset.fieldType,
355
+ scale: candidate.offset.scale,
356
+ paddingInner: candidate.offset.paddingInner,
357
+ paddingOuter: candidate.offset.paddingOuter
358
+ });
359
+ }
360
+ return next;
361
+ }
362
+
363
+ function updateErrorBarRoles(program, owner, current, candidate, dataId, capIds) {
364
+ const owned = [owner.id, ...capIds];
365
+ let next = updateErrorBarOffsets(
366
+ program, owned, current, { ...candidate, offset: undefined }
367
+ );
368
+ next = updateDistributionPositions(next, owner, current, candidate, {
369
+ owned,
370
+ lower: candidate.fields.lower,
371
+ upper: candidate.fields.upper,
372
+ title: candidate.measure,
373
+ update(value, { category, measure, categoryChannel, measureChannel }) {
374
+ let updated = value;
375
+ for (const [id, field] of [
376
+ [current.lowerCapId, candidate.fields.lower],
377
+ [current.upperCapId, candidate.fields.upper]
378
+ ]) {
379
+ if (findLayer(updated, id) === undefined) continue;
380
+ updated = setCartesianPosition(updated, id, categoryChannel, {
381
+ field: candidate.position.field,
382
+ fieldType: candidate.position.fieldType,
383
+ scale: category.scale
384
+ });
385
+ updated = setCartesianPosition(updated, id, measureChannel, {
386
+ field,
387
+ fieldType: "quantitative",
388
+ scale: measure.scale
389
+ })._withMarkConfig(id, {
390
+ ...updated.markConfigs[id],
391
+ fixedSpan: {
392
+ ...updated.markConfigs[id].fixedSpan,
393
+ orientation: candidate.orientation === "vertical"
394
+ ? "horizontal" : "vertical"
395
+ }
396
+ });
397
+ }
398
+ return updated;
399
+ }
400
+ });
401
+ next = updateErrorBarOffsets(next, owned, current, candidate);
402
+ const positionScale = candidate.position.channel === "x"
403
+ ? candidate.xScale.id : candidate.yScale.id;
404
+ const intervalScale = candidate.interval.channel === "x"
405
+ ? candidate.xScale.id : candidate.yScale.id;
406
+ const revisedConfig = {
407
+ ...next.markConfigs[owner.id].errorBar,
408
+ source: candidate.source,
409
+ data: dataId,
410
+ intervalMode: candidate.interval.mode,
411
+ groupField: candidate.groupField,
412
+ groupBy: candidate.groupBy,
413
+ orientation: candidate.orientation,
414
+ positionField: candidate.position.field,
415
+ positionFieldType: candidate.position.fieldType,
416
+ lowerField: candidate.fields.lower,
417
+ upperField: candidate.fields.upper,
418
+ positionScale,
419
+ intervalScale
420
+ };
421
+ delete revisedConfig.intervalField;
422
+ delete revisedConfig.centerField;
423
+ delete revisedConfig.positionTemporalUnit;
424
+ delete revisedConfig.offset;
425
+ if (candidate.interval.mode === "statistical") {
426
+ revisedConfig.intervalField = candidate.interval.field;
427
+ } else {
428
+ revisedConfig.centerField = candidate.fields.center;
429
+ }
430
+ if (candidate.position.temporalUnit !== undefined) {
431
+ revisedConfig.positionTemporalUnit = candidate.position.temporalUnit;
432
+ }
433
+ if (candidate.offset !== undefined) {
434
+ revisedConfig.offset = {
435
+ ...candidate.offset,
436
+ scale: typeof candidate.offset.scale === "string"
437
+ ? candidate.offset.scale
438
+ : candidate.offset.scale.id
439
+ };
440
+ }
441
+ next = next._withMarkConfig(owner.id, {
442
+ ...next.markConfigs[owner.id],
443
+ errorBar: revisedConfig
444
+ });
445
+ return next.rematerializeErrorBar({ id: owner.id });
446
+ }
447
+
132
448
  export const rematerializeErrorBar = action(
133
449
  {
134
450
  op: "rematerializeErrorBar",
@@ -166,7 +482,7 @@ export const rematerializeErrorBar = action(
166
482
  export const editErrorBar = action(
167
483
  {
168
484
  op: "editErrorBar",
169
- description: "Edit one error bar and its owned cap appearance."
485
+ description: "Revise one error bar's roles, statistics, and owned caps."
170
486
  },
171
487
  function (args = {}) {
172
488
  validateKeys(args, ERROR_BAR_EDIT_OPTIONS, "editErrorBar");
@@ -182,29 +498,49 @@ export const editErrorBar = action(
182
498
  operation: "editErrorBar"
183
499
  }
184
500
  );
185
- const interval = Object.hasOwn(args, "statistics")
501
+ const roleRequested = [
502
+ "data", "x", "y", "xOffset", "yOffset", "groupBy"
503
+ ].some(key => Object.hasOwn(args, key));
504
+ const candidate = roleRequested
505
+ ? resolveRoleCandidate(this, owner, current, args)
506
+ : undefined;
507
+ const capIds = errorBarCaps(current)
508
+ .map(([id]) => id)
509
+ .filter(id => findLayer(this, id) !== undefined);
510
+ const consumers = collectIntervalConsumers(this, [owner.id, ...capIds]);
511
+ const interval = roleRequested
512
+ ? planIntervalRoleData(this, {
513
+ owner: owner.id,
514
+ currentData: current.data,
515
+ candidate,
516
+ consumers
517
+ })
518
+ : Object.hasOwn(args, "statistics")
186
519
  ? planIntervalEdit(this, {
187
520
  owner: owner.id,
188
521
  data: current.data,
189
- consumers: [
190
- owner.id,
191
- ...errorBarCaps(current)
192
- .map(([id]) => id)
193
- .filter(id => findLayer(this, id) !== undefined)
194
- ],
522
+ consumers,
195
523
  statistics: args.statistics,
196
524
  operation: "editErrorBar"
197
525
  })
198
526
  : { changed: false };
199
527
 
200
- const next = applyIntervalRevision(this, interval);
201
- return releaseIntervalRevision(next._withMarkConfig(owner.id, {
528
+ let next = applyIntervalRevision(this, interval);
529
+ next = next._withMarkConfig(owner.id, {
202
530
  ...next.markConfigs[owner.id],
203
531
  errorBar: {
204
532
  ...current,
205
533
  ...appearance,
206
- ...(interval.changed ? { data: interval.revision.id } : {})
534
+ ...(interval.changed
535
+ ? { data: interval.dataId ?? interval.revision.id }
536
+ : {})
207
537
  }
208
- }).rematerializeErrorBar({ id: owner.id }), interval);
538
+ });
539
+ next = roleRequested
540
+ ? updateErrorBarRoles(
541
+ next, owner, current, candidate, interval.dataId, capIds
542
+ )
543
+ : next.rematerializeErrorBar({ id: owner.id });
544
+ return releaseIntervalRevision(next, interval);
209
545
  }
210
546
  );