ggaction 0.0.11 → 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 (289) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +3 -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 +33 -27
  65. package/src/actions/encodings/parallel.js +2 -8
  66. package/src/actions/encodings/position/apply.js +40 -4
  67. package/src/actions/encodings/position/index.js +43 -5
  68. package/src/actions/encodings/position/policies/arc.js +37 -2
  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 +79 -24
  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 +153 -11
  83. package/src/actions/errorBars/edit.js +365 -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 +138 -14
  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 +60 -4
  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/core/vocabulary.js +2 -2
  195. package/src/grammar/aggregate.js +66 -23
  196. package/src/grammar/arcs.js +213 -65
  197. package/src/grammar/areaEndpoints.js +46 -0
  198. package/src/grammar/areaSeries.js +87 -41
  199. package/src/grammar/bars/aggregate.js +33 -12
  200. package/src/grammar/bars/policy.js +9 -7
  201. package/src/grammar/bin.js +181 -0
  202. package/src/grammar/categoryOrder.js +34 -0
  203. package/src/grammar/computed.js +150 -0
  204. package/src/grammar/ecdf.js +198 -0
  205. package/src/grammar/facets/index.js +117 -6
  206. package/src/grammar/fold.js +123 -0
  207. package/src/grammar/horizon.js +6 -4
  208. package/src/grammar/interval.js +37 -20
  209. package/src/grammar/lineSeries.js +20 -63
  210. package/src/grammar/markFilter.js +22 -4
  211. package/src/grammar/markLabels.js +93 -0
  212. package/src/grammar/parallelAxisTitles.js +16 -0
  213. package/src/grammar/pathSeries.js +91 -0
  214. package/src/grammar/pointPacking.js +183 -0
  215. package/src/grammar/positionCompatibility.js +1 -1
  216. package/src/grammar/positionDatum.js +12 -0
  217. package/src/grammar/rects.js +9 -1
  218. package/src/grammar/regression/derive.js +4 -0
  219. package/src/grammar/regression/index.js +7 -2
  220. package/src/grammar/regression/models.js +22 -7
  221. package/src/grammar/regression/parameters.js +40 -13
  222. package/src/grammar/rotation.js +29 -0
  223. package/src/grammar/rules.js +9 -21
  224. package/src/grammar/scales/color.js +23 -1
  225. package/src/grammar/scales/colorConsumers.js +38 -0
  226. package/src/grammar/scales/definition.js +15 -1
  227. package/src/grammar/scales/discretized.js +11 -5
  228. package/src/grammar/scales/fields.js +38 -8
  229. package/src/grammar/scales/mapping.js +2 -0
  230. package/src/grammar/scales/ordinal.js +23 -0
  231. package/src/grammar/scales/radial.js +55 -0
  232. package/src/grammar/scales/types.js +1 -0
  233. package/src/grammar/schemas/semanticPath.js +17 -3
  234. package/src/grammar/stack.js +170 -0
  235. package/src/grammar/statistics/confidenceInterval.js +101 -0
  236. package/src/grammar/summary.js +183 -0
  237. package/src/grammar/text.js +22 -16
  238. package/src/grammar/timeUnit.js +21 -11
  239. package/src/grammar/transformTopology.js +6 -0
  240. package/src/grammar/transforms.js +36 -0
  241. package/src/grammar/valueFormat.js +112 -0
  242. package/src/grammar/window.js +13 -7
  243. package/src/layout/facets.js +86 -23
  244. package/src/layout/guideCollisions.js +12 -0
  245. package/src/layout/legend.js +2 -10
  246. package/src/layout/legendItems.js +102 -0
  247. package/src/layout/legendLane.js +100 -11
  248. package/src/materialization/bars/aggregate.js +38 -152
  249. package/src/materialization/bars/histogram.js +24 -0
  250. package/src/materialization/categorySlotOffset.js +40 -0
  251. package/src/materialization/compositionSnapshot.js +1 -1
  252. package/src/materialization/dependencies.js +57 -16
  253. package/src/materialization/encodings.js +1 -1
  254. package/src/materialization/facetGuides/legacyCategorical.js +7 -1
  255. package/src/materialization/facetGuides/placement.js +16 -1
  256. package/src/materialization/facetGuides/preparation.js +26 -1
  257. package/src/materialization/facets.js +11 -2
  258. package/src/materialization/guides/layout.js +57 -0
  259. package/src/materialization/guides/resources.js +2 -2
  260. package/src/materialization/legends.js +10 -1
  261. package/src/materialization/marks/capabilities.js +30 -7
  262. package/src/materialization/marks/grouping.js +52 -0
  263. package/src/materialization/marks/index.js +63 -25
  264. package/src/materialization/marks/policies.js +2 -0
  265. package/src/materialization/rect.js +33 -33
  266. package/src/materialization/rowEncoding.js +70 -14
  267. package/src/materialization/scaleGuideDependencies.js +8 -5
  268. package/src/materialization/scales/map.js +1 -0
  269. package/src/materialization/scales/policies/arc.js +42 -0
  270. package/src/materialization/scales/policies/bar.js +0 -43
  271. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  272. package/src/materialization/scales/policies/offset.js +54 -0
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +48 -18
  275. package/src/materialization/selection/compatibility.js +11 -0
  276. package/src/materialization/selection/items/bar.js +12 -68
  277. package/src/materialization/selection/items/common.js +8 -2
  278. package/src/materialization/selection/items/path.js +3 -13
  279. package/src/materialization/selection/items/rect.js +7 -4
  280. package/src/materialization/text.js +64 -5
  281. package/src/mcp/adapter.js +2 -2
  282. package/src/selectors/datasets.js +13 -0
  283. package/src/selectors/layers.js +3 -2
  284. package/src/selectors/scales.js +6 -0
  285. package/src/theme/defaults.js +39 -0
  286. package/types/basic.d.ts +11 -2
  287. package/types/index.d.ts +126 -0
  288. package/types/program.d.ts +1327 -124
  289. 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,10 +288,21 @@ 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,
96
295
  intervalScale: config.intervalScale,
296
+ ...(config.offset === undefined
297
+ ? {}
298
+ : {
299
+ offsetChannel: config.offset.channel,
300
+ offsetField: config.offset.field,
301
+ offsetFieldType: config.offset.fieldType,
302
+ offsetScale: config.offset.scale,
303
+ offsetPaddingInner: config.offset.paddingInner,
304
+ offsetPaddingOuter: config.offset.paddingOuter
305
+ }),
97
306
  capSize: config.capSize,
98
307
  stroke: config.stroke,
99
308
  strokeWidth: config.strokeWidth,
@@ -119,6 +328,123 @@ function rematerializeRuleAppearance(program, id, config, fixedSpan) {
119
328
  .rematerializeRuleMark({ id });
120
329
  }
121
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
+
122
448
  export const rematerializeErrorBar = action(
123
449
  {
124
450
  op: "rematerializeErrorBar",
@@ -156,7 +482,7 @@ export const rematerializeErrorBar = action(
156
482
  export const editErrorBar = action(
157
483
  {
158
484
  op: "editErrorBar",
159
- description: "Edit one error bar and its owned cap appearance."
485
+ description: "Revise one error bar's roles, statistics, and owned caps."
160
486
  },
161
487
  function (args = {}) {
162
488
  validateKeys(args, ERROR_BAR_EDIT_OPTIONS, "editErrorBar");
@@ -172,29 +498,49 @@ export const editErrorBar = action(
172
498
  operation: "editErrorBar"
173
499
  }
174
500
  );
175
- 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")
176
519
  ? planIntervalEdit(this, {
177
520
  owner: owner.id,
178
521
  data: current.data,
179
- consumers: [
180
- owner.id,
181
- ...errorBarCaps(current)
182
- .map(([id]) => id)
183
- .filter(id => findLayer(this, id) !== undefined)
184
- ],
522
+ consumers,
185
523
  statistics: args.statistics,
186
524
  operation: "editErrorBar"
187
525
  })
188
526
  : { changed: false };
189
527
 
190
- const next = applyIntervalRevision(this, interval);
191
- return releaseIntervalRevision(next._withMarkConfig(owner.id, {
528
+ let next = applyIntervalRevision(this, interval);
529
+ next = next._withMarkConfig(owner.id, {
192
530
  ...next.markConfigs[owner.id],
193
531
  errorBar: {
194
532
  ...current,
195
533
  ...appearance,
196
- ...(interval.changed ? { data: interval.revision.id } : {})
534
+ ...(interval.changed
535
+ ? { data: interval.dataId ?? interval.revision.id }
536
+ : {})
197
537
  }
198
- }).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);
199
545
  }
200
546
  );