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,5 +1,12 @@
1
1
  import { action } from "../../../../core/action.js";
2
- import { polarGuideNames, resolvePolarGuideResources } from "../resolve.js";
2
+ import { resolvePolarGuideResources } from "../resolve.js";
3
+ import {
4
+ AXIS_COMPONENTS,
5
+ assertRemovableAxisComponent,
6
+ hasAxisComponent,
7
+ removeAxisComponent,
8
+ validateEnabledAxisComponents
9
+ } from "../../axes/components.js";
3
10
  import {
4
11
  LABEL_EDIT_OPTIONS,
5
12
  LINE_EDIT_OPTIONS,
@@ -22,12 +29,15 @@ const AXIS_EDIT_OPTIONS = Object.freeze([
22
29
  "angle", "line", "ticks", "labels", "ticksAndLabels", "title"
23
30
  ]);
24
31
 
25
- function validateAxisArgs(kind, args, operation) {
26
- validateObject(args, AXIS_OPTIONS, operation);
27
- if (Object.hasOwn(args, "line")) {
32
+ export function validatePolarAxisArgs(kind, args, operation) {
33
+ validateObject(args, kind === "theta"
34
+ ? AXIS_OPTIONS.filter(option => option !== "angle")
35
+ : AXIS_OPTIONS, operation);
36
+ validateEnabledAxisComponents(args, operation);
37
+ if (Object.hasOwn(args, "line") && args.line !== false) {
28
38
  validateObject(args.line, LINE_EDIT_OPTIONS, `${operation}.line`);
29
39
  }
30
- if (Object.hasOwn(args, "ticksAndLabels")) {
40
+ if (Object.hasOwn(args, "ticksAndLabels") && args.ticksAndLabels !== false) {
31
41
  validateObject(
32
42
  args.ticksAndLabels,
33
43
  TICK_GROUP_OPTIONS,
@@ -68,7 +78,7 @@ function makeCreateAxis(kind) {
68
78
  op: operation,
69
79
  description: `Create the complete Polar ${kind} axis.`
70
80
  }, function (args = {}) {
71
- validateAxisArgs(kind, args, operation);
81
+ validatePolarAxisArgs(kind, args, operation);
72
82
  const resources = resolvePolarGuideResources(this, kind, args, operation);
73
83
  const angle = resolveAngle(this, kind, args);
74
84
  const shared = {
@@ -82,21 +92,16 @@ function makeCreateAxis(kind) {
82
92
  ...(Object.hasOwn(group, "values") ? { values: group.values } : {})
83
93
  };
84
94
  let next = this;
85
- if (kind === "radius") {
86
- next = next._withGuideConfig("radius", "layout", { angle });
95
+ if (args.line !== false) {
96
+ next = next[`create${prefix(kind)}AxisLine`]({ ...shared, ...(args.line ?? {}) });
87
97
  }
88
- next = next
89
- [`create${prefix(kind)}AxisLine`]({ ...shared, ...(args.line ?? {}) })
90
- [`create${prefix(kind)}AxisTicks`]({
91
- ...shared,
92
- ...mode,
93
- ...(group.ticks ?? {})
94
- })
95
- [`create${prefix(kind)}AxisLabels`]({
96
- ...shared,
97
- ...mode,
98
- ...(group.labels ?? {})
98
+ if (args.ticksAndLabels !== false) {
99
+ next = next[`create${prefix(kind)}AxisTicks`]({
100
+ ...shared, ...mode, ...(group.ticks ?? {})
101
+ })[`create${prefix(kind)}AxisLabels`]({
102
+ ...shared, ...mode, ...(group.labels ?? {})
99
103
  });
104
+ }
100
105
  return args.title === false
101
106
  ? next
102
107
  : next[`create${prefix(kind)}AxisTitle`]({
@@ -118,29 +123,28 @@ function validateAxisEditArgs(kind, args, operation) {
118
123
  throw new Error(`${operation} requires at least one axis change.`);
119
124
  }
120
125
  if (Object.hasOwn(args, "angle")) validateAngle(args.angle);
121
- if (Object.hasOwn(args, "line")) {
126
+ if (Object.hasOwn(args, "line") && args.line !== false) {
122
127
  validateObject(args.line, LINE_EDIT_OPTIONS, `${operation}.line`);
123
128
  }
124
- if (Object.hasOwn(args, "ticks")) {
129
+ if (Object.hasOwn(args, "ticks") && args.ticks !== false) {
125
130
  validateObject(args.ticks, TICK_EDIT_OPTIONS, `${operation}.ticks`);
126
131
  validateModeOptions(args.ticks, `${operation}.ticks`);
127
132
  }
128
- if (Object.hasOwn(args, "labels")) {
133
+ if (Object.hasOwn(args, "labels") && args.labels !== false) {
129
134
  validateObject(args.labels, LABEL_EDIT_OPTIONS, `${operation}.labels`);
130
135
  validateModeOptions(args.labels, `${operation}.labels`);
131
136
  }
132
- if (Object.hasOwn(args, "ticksAndLabels")) {
137
+ if (args.ticksAndLabels !== undefined &&
138
+ (args.ticks !== undefined || args.labels !== undefined)) {
139
+ throw new Error(`${operation} cannot combine ticksAndLabels with ticks or labels.`);
140
+ }
141
+ if (Object.hasOwn(args, "ticksAndLabels") && args.ticksAndLabels !== false) {
133
142
  validateObject(
134
143
  args.ticksAndLabels,
135
144
  TICK_GROUP_OPTIONS,
136
145
  `${operation}.ticksAndLabels`
137
146
  );
138
147
  validateModeOptions(args.ticksAndLabels, `${operation}.ticksAndLabels`);
139
- if (args.ticks !== undefined || args.labels !== undefined) {
140
- throw new Error(
141
- `${operation} cannot combine ticksAndLabels with ticks or labels.`
142
- );
143
- }
144
148
  if (args.ticksAndLabels.ticks !== undefined) {
145
149
  validateObject(
146
150
  args.ticksAndLabels.ticks,
@@ -156,7 +160,7 @@ function validateAxisEditArgs(kind, args, operation) {
156
160
  );
157
161
  }
158
162
  }
159
- if (Object.hasOwn(args, "title")) {
163
+ if (Object.hasOwn(args, "title") && args.title !== false) {
160
164
  validateObject(
161
165
  args.title,
162
166
  kind === "theta"
@@ -167,6 +171,41 @@ function validateAxisEditArgs(kind, args, operation) {
167
171
  }
168
172
  }
169
173
 
174
+ function applyAxisEdit(program, kind, args) {
175
+ const angleChanged = Object.hasOwn(args, "angle");
176
+ let next = angleChanged
177
+ ? program._withGuideConfig("radius", "layout", { angle: args.angle })
178
+ : program;
179
+ const edit = (component, options) => {
180
+ if (options === false) {
181
+ next = removeAxisComponent(next, kind, component);
182
+ } else if (options !== undefined || angleChanged && hasAxisComponent(next, kind, component)) {
183
+ next = next[`edit${prefix(kind)}Axis${component[0].toUpperCase() + component.slice(1)}`](options ?? {});
184
+ }
185
+ };
186
+ edit("line", args.line);
187
+ if (args.ticksAndLabels === false) {
188
+ edit("ticks", false);
189
+ edit("labels", false);
190
+ } else if (args.ticksAndLabels !== undefined) {
191
+ const group = args.ticksAndLabels;
192
+ const mode = {
193
+ ...(Object.hasOwn(group, "count") ? { count: group.count } : {}),
194
+ ...(Object.hasOwn(group, "values") ? { values: group.values } : {})
195
+ };
196
+ edit("ticks", { ...mode, ...(group.ticks ?? {}) });
197
+ edit("labels", { ...mode, ...(group.labels ?? {}) });
198
+ } else {
199
+ edit("ticks", args.ticks);
200
+ edit("labels", args.labels);
201
+ }
202
+ edit("title", args.title);
203
+ if (!AXIS_COMPONENTS.some(component => hasAxisComponent(next, kind, component))) {
204
+ next = next[`remove${prefix(kind)}Axis`]();
205
+ }
206
+ return next;
207
+ }
208
+
170
209
  function makeEditAxis(kind) {
171
210
  const operation = `edit${prefix(kind)}Axis`;
172
211
  return action({
@@ -174,42 +213,18 @@ function makeEditAxis(kind) {
174
213
  description: `Edit selected Polar ${kind}-axis components.`
175
214
  }, function (args = {}) {
176
215
  validateAxisEditArgs(kind, args, operation);
177
- const names = polarGuideNames(kind);
178
- const angleChanged = Object.hasOwn(args, "angle");
179
- let next = angleChanged
180
- ? this._withGuideConfig("radius", "layout", { angle: args.angle })
181
- : this;
182
- const has = component =>
183
- next.graphicSpec.objects[names[component]] !== undefined;
184
- if (args.line !== undefined || (angleChanged && has("line"))) {
185
- next = next[`edit${prefix(kind)}AxisLine`](args.line ?? {});
216
+ if (!AXIS_COMPONENTS.some(component => hasAxisComponent(this, kind, component))) {
217
+ throw new Error(`${operation} requires an existing ${kind}-axis component.`);
186
218
  }
187
- if (args.ticksAndLabels !== undefined) {
188
- const group = args.ticksAndLabels;
189
- const mode = {
190
- ...(Object.hasOwn(group, "count") ? { count: group.count } : {}),
191
- ...(Object.hasOwn(group, "values") ? { values: group.values } : {})
192
- };
193
- next = next[`edit${prefix(kind)}AxisTicks`]({
194
- ...mode,
195
- ...(group.ticks ?? {})
196
- });
197
- next = next[`edit${prefix(kind)}AxisLabels`]({
198
- ...mode,
199
- ...(group.labels ?? {})
200
- });
201
- } else {
202
- if (args.ticks !== undefined || (angleChanged && has("ticks"))) {
203
- next = next[`edit${prefix(kind)}AxisTicks`](args.ticks ?? {});
219
+ for (const component of AXIS_COMPONENTS) {
220
+ if (args[component] === false ||
221
+ args.ticksAndLabels === false && ["ticks", "labels"].includes(component)) {
222
+ assertRemovableAxisComponent(this, kind, component, operation);
204
223
  }
205
- if (args.labels !== undefined || (angleChanged && has("labels"))) {
206
- next = next[`edit${prefix(kind)}AxisLabels`](args.labels ?? {});
207
- }
208
- }
209
- if (args.title !== undefined || (angleChanged && has("title"))) {
210
- next = next[`edit${prefix(kind)}AxisTitle`](args.title ?? {});
211
224
  }
212
- return next;
225
+ // Validate the whole proposal on an immutable branch before returning changes.
226
+ applyAxisEdit(this, kind, args);
227
+ return applyAxisEdit(this, kind, args);
213
228
  });
214
229
  }
215
230
 
@@ -26,11 +26,12 @@ import {
26
26
  prefix,
27
27
  resolveAngle,
28
28
  validateModeOptions,
29
+ validateComponentCreateArgs,
29
30
  validateObject,
30
31
  withAxisSemantics
31
32
  } from "./shared.js";
32
33
 
33
- function labelGeometry(program, kind, config) {
34
+ function labelGeometry(program, kind, config, angle = resolveAngle(program, kind, {})) {
34
35
  const frame = resolvePolarFrameForProgram(program);
35
36
  const mapped = mapPolarGuideValues(program, config);
36
37
  const text = formatPolarGuideValues(program, config, mapped.values);
@@ -45,7 +46,7 @@ function labelGeometry(program, kind, config) {
45
46
  })
46
47
  : resolveRadialAxisLabels({
47
48
  frame,
48
- angle: resolveAngle(program, kind, {}),
49
+ angle,
49
50
  radii: mapped.positions,
50
51
  offset: config.offset
51
52
  }))
@@ -171,7 +172,7 @@ function makeCreateLabels(kind) {
171
172
  op: operation.create,
172
173
  description: `Create the Polar ${kind}-axis labels.`
173
174
  }, function (args = {}) {
174
- validateObject(args, LABEL_CREATE_OPTIONS, operation.create);
175
+ validateComponentCreateArgs(kind, args, LABEL_CREATE_OPTIONS, operation.create);
175
176
  validateModeOptions(args, operation.create);
176
177
  const names = polarGuideNames(kind);
177
178
  if (this.graphicSpec.objects[names.labels] !== undefined) {
@@ -180,7 +181,7 @@ function makeCreateLabels(kind) {
180
181
  const resources = componentResources(this, kind, args, operation.create);
181
182
  const angle = resolveAngle(this, kind, args);
182
183
  const config = resolveLabelConfig(this, kind, args, resources);
183
- labelGeometry(this, kind, config);
184
+ labelGeometry(this, kind, config, angle);
184
185
  let next = withAxisSemantics(this, kind, resources);
185
186
  if (kind === "radius" &&
186
187
  next.guideConfigs.axis?.radius?.layout === undefined) {
@@ -18,6 +18,7 @@ import {
18
18
  operations,
19
19
  prefix,
20
20
  resolveAngle,
21
+ validateComponentCreateArgs,
21
22
  validateObject,
22
23
  withAxisSemantics
23
24
  } from "./shared.js";
@@ -76,7 +77,7 @@ function makeCreateLine(kind) {
76
77
  op: operation.create,
77
78
  description: `Create the Polar ${kind}-axis baseline.`
78
79
  }, function (args = {}) {
79
- validateObject(args, LINE_CREATE_OPTIONS, operation.create);
80
+ validateComponentCreateArgs(kind, args, LINE_CREATE_OPTIONS, operation.create);
80
81
  const names = polarGuideNames(kind);
81
82
  if (this.graphicSpec.objects[names.line] !== undefined) {
82
83
  throw new Error(`${operation.create} requires a missing axis line.`);
@@ -48,6 +48,12 @@ export function validateObject(args, supported, operation) {
48
48
  validateOptionObject(args, supported, operation);
49
49
  }
50
50
 
51
+ export function validateComponentCreateArgs(kind, args, supported, operation) {
52
+ validateObject(args, kind === "theta"
53
+ ? supported.filter(option => !["angle", "position"].includes(option))
54
+ : supported, operation);
55
+ }
56
+
51
57
  export function validateModeOptions(args, operation) {
52
58
  if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
53
59
  throw new Error(`${operation} cannot use count and values together.`);
@@ -23,11 +23,12 @@ import {
23
23
  TICK_CREATE_OPTIONS,
24
24
  TICK_EDIT_OPTIONS,
25
25
  validateModeOptions,
26
+ validateComponentCreateArgs,
26
27
  validateObject,
27
28
  withAxisSemantics
28
29
  } from "./shared.js";
29
30
 
30
- function tickGeometry(program, kind, config) {
31
+ function tickGeometry(program, kind, config, angle = resolveAngle(program, kind, {})) {
31
32
  const frame = resolvePolarFrameForProgram(program);
32
33
  const mapped = mapPolarGuideValues(program, config);
33
34
  return {
@@ -40,7 +41,7 @@ function tickGeometry(program, kind, config) {
40
41
  })
41
42
  : resolveRadialAxisTicks({
42
43
  frame,
43
- angle: resolveAngle(program, kind, {}),
44
+ angle,
44
45
  radii: mapped.positions,
45
46
  length: config.length
46
47
  }))
@@ -131,7 +132,7 @@ function makeCreateTicks(kind) {
131
132
  op: operation.create,
132
133
  description: `Create the Polar ${kind}-axis ticks.`
133
134
  }, function (args = {}) {
134
- validateObject(args, TICK_CREATE_OPTIONS, operation.create);
135
+ validateComponentCreateArgs(kind, args, TICK_CREATE_OPTIONS, operation.create);
135
136
  validateModeOptions(args, operation.create);
136
137
  const names = polarGuideNames(kind);
137
138
  if (this.graphicSpec.objects[names.ticks] !== undefined) {
@@ -140,7 +141,7 @@ function makeCreateTicks(kind) {
140
141
  const resources = componentResources(this, kind, args, operation.create);
141
142
  const angle = resolveAngle(this, kind, args);
142
143
  const config = resolveTickConfig(this, kind, args, resources);
143
- tickGeometry(this, kind, config);
144
+ tickGeometry(this, kind, config, angle);
144
145
  let next = withAxisSemantics(this, kind, resources);
145
146
  if (kind === "radius" &&
146
147
  next.guideConfigs.axis?.radius?.layout === undefined) {
@@ -24,17 +24,18 @@ import {
24
24
  resolveAngle,
25
25
  TITLE_CREATE_OPTIONS,
26
26
  TITLE_EDIT_OPTIONS,
27
+ validateComponentCreateArgs,
27
28
  validateObject,
28
29
  withAxisSemantics
29
30
  } from "./shared.js";
30
31
 
31
- function titleGeometry(program, kind, config) {
32
+ function titleGeometry(program, kind, config, angle = resolveAngle(program, kind, {})) {
32
33
  const frame = resolvePolarFrameForProgram(program);
33
34
  return kind === "theta"
34
35
  ? resolveThetaAxisTitle({ frame, offset: config.offset })
35
36
  : resolveRadialAxisTitle({
36
37
  frame,
37
- angle: resolveAngle(program, kind, {}),
38
+ angle,
38
39
  offset: config.offset,
39
40
  position: config.position
40
41
  });
@@ -142,7 +143,7 @@ function makeCreateTitle(kind) {
142
143
  op: operation.create,
143
144
  description: `Create the Polar ${kind}-axis title.`
144
145
  }, function (args = {}) {
145
- validateObject(args, TITLE_CREATE_OPTIONS, operation.create);
146
+ validateComponentCreateArgs(kind, args, TITLE_CREATE_OPTIONS, operation.create);
146
147
  const names = polarGuideNames(kind);
147
148
  if (this.graphicSpec.objects[names.title] !== undefined) {
148
149
  throw new Error(`${operation.create} requires a missing axis title.`);
@@ -154,7 +155,7 @@ function makeCreateTitle(kind) {
154
155
  args.text ?? inferAxisTitleText(this, names.channel, resources.scale),
155
156
  "Polar axis title text"
156
157
  );
157
- const geometry = titleGeometry(this, kind, config);
158
+ const geometry = titleGeometry(this, kind, config, angle);
158
159
  validateTitleGeometry(this, kind, config, geometry, text);
159
160
  let next = withAxisSemantics(this, kind, resources);
160
161
  if (kind === "radius" &&
@@ -28,7 +28,7 @@ const EDIT_OPTIONS = Object.freeze([
28
28
  "count", "values", "color", "lineWidth", "strokeDash"
29
29
  ]);
30
30
 
31
- function operationNames(kind) {
31
+ export function polarGridOperations(kind) {
32
32
  const prefix = kind === "theta" ? "Theta" : "Radial";
33
33
  return {
34
34
  create: `create${prefix}Grid`,
@@ -46,7 +46,7 @@ function validateStrokeDash(value) {
46
46
  }
47
47
  }
48
48
 
49
- function validateOptions(args, operation, create) {
49
+ export function validatePolarGridOptions(args, operation, create) {
50
50
  validateOptionObject(
51
51
  args,
52
52
  create ? CREATE_OPTIONS : EDIT_OPTIONS,
@@ -154,7 +154,7 @@ function editConcrete(program, kind, config, geometry) {
154
154
  }
155
155
 
156
156
  function makeRematerialize(kind) {
157
- const operations = operationNames(kind);
157
+ const operations = polarGridOperations(kind);
158
158
  return action({
159
159
  op: operations.rematerialize,
160
160
  description: `Recompute concrete Polar ${kind} grid geometry.`
@@ -181,12 +181,12 @@ function makeRematerialize(kind) {
181
181
  }
182
182
 
183
183
  function makeCreate(kind) {
184
- const operations = operationNames(kind);
184
+ const operations = polarGridOperations(kind);
185
185
  return action({
186
186
  op: operations.create,
187
187
  description: `Create a semantic and concrete Polar ${kind} grid.`
188
188
  }, function (args = {}) {
189
- validateOptions(args, operations.create, true);
189
+ validatePolarGridOptions(args, operations.create, true);
190
190
  const graphic = polarGuideNames(kind).grid;
191
191
  if (this.semanticSpec.guides.grid?.[kind] !== undefined ||
192
192
  this.graphicSpec.objects[graphic] !== undefined) {
@@ -222,12 +222,12 @@ function makeCreate(kind) {
222
222
  }
223
223
 
224
224
  function makeEdit(kind) {
225
- const operations = operationNames(kind);
225
+ const operations = polarGridOperations(kind);
226
226
  return action({
227
227
  op: operations.edit,
228
228
  description: `Edit the existing Polar ${kind} grid.`
229
229
  }, function (args = {}) {
230
- validateOptions(args, operations.edit, false);
230
+ validatePolarGridOptions(args, operations.edit, false);
231
231
  const previous = this.guideConfigs.grid?.[kind];
232
232
  if (previous === undefined) {
233
233
  throw new Error(`${operations.edit} requires an existing grid.`);
@@ -0,0 +1,44 @@
1
+ import { isPlainObject } from "../../core/immutable.js";
2
+
3
+ export function sameGuideValue(left, right) {
4
+ if (left === right) return true;
5
+ if (Array.isArray(left) && Array.isArray(right)) {
6
+ return left.length === right.length && left.every((value, i) => sameGuideValue(value, right[i]));
7
+ }
8
+ if (isPlainObject(left) && isPlainObject(right)) {
9
+ const keys = Object.keys(left);
10
+ return keys.length === Object.keys(right).length && keys.every(key =>
11
+ Object.hasOwn(right, key) && sameGuideValue(left[key], right[key])
12
+ );
13
+ }
14
+ return false;
15
+ }
16
+
17
+ export function guideConflict(label) {
18
+ throw new Error(`Facade guide conflict: ${label}. Use the existing guide editor or disable this facade's guide branch.`);
19
+ }
20
+
21
+ // Only explicit choices constrain existing appearance. Omitted defaults never
22
+ // overwrite an earlier author's title, placement, or style.
23
+ export function assertGuideOptions(requested, stored, label) {
24
+ for (const [key, value] of Object.entries(requested)) {
25
+ if (Array.isArray(value) && Array.isArray(stored?.[key]) && value.length === stored[key].length) {
26
+ assertGuideOptions(Object.fromEntries(value.entries()), Object.fromEntries(stored[key].entries()), `${label}.${key}`);
27
+ } else if (isPlainObject(value) && isPlainObject(stored?.[key])) {
28
+ assertGuideOptions(value, stored[key], `${label}.${key}`);
29
+ } else if (!sameGuideValue(value, stored?.[key])) {
30
+ guideConflict(`${label}.${key} differs from the existing guide`);
31
+ }
32
+ }
33
+ }
34
+
35
+ export function resolveStoredGuideCoordinate(program, guide, channel) {
36
+ if (guide.coordinate !== undefined) return guide.coordinate;
37
+ const ids = [...new Set(program.semanticSpec.layers.filter(layer =>
38
+ layer.encoding?.[channel]?.scale === guide.scale
39
+ ).map(layer => layer.coordinate).filter(id => id !== undefined))];
40
+ if (ids.length !== 1) {
41
+ guideConflict(`legacy ${channel} guide has no unique compatible coordinate`);
42
+ }
43
+ return ids[0];
44
+ }
@@ -19,29 +19,27 @@ export function inferHistogramBoundaries(program, channel, scaleId) {
19
19
  layer.encoding.x.bin !== undefined
20
20
  );
21
21
  if (consumers.length === 0) return undefined;
22
- if (consumers.length > 1) {
23
- throw new Error(
24
- `Guide values cannot infer shared histogram bins for scale "${scaleId}".`
25
- );
26
- }
27
-
28
- const [layer] = consumers;
29
- const encoding = layer.encoding.x;
30
- const dataset = findDataset(program, layer.data);
31
22
  const scale = findSemanticScale(program, scaleId);
32
- if (dataset === undefined || scale === undefined) {
33
- throw new Error(
34
- `Guide values require histogram data and scale "${scaleId}".`
35
- );
23
+ const boundaries = consumers.map(layer => {
24
+ const encoding = layer.encoding.x;
25
+ const dataset = findDataset(program, layer.data);
26
+ if (dataset === undefined || scale === undefined) {
27
+ throw new Error(`Guide values require histogram data and scale "${scaleId}".`);
28
+ }
29
+ return resolveHistogramBins({
30
+ values: readQuantitativeField(dataset.values, encoding.field),
31
+ bin: encoding.bin,
32
+ domain: scale.domain,
33
+ nice: scale.nice ?? true,
34
+ zero: scale.zero ?? false
35
+ }).boundaries;
36
+ });
37
+ const [unique] = boundaries;
38
+ if (boundaries.some(values => values.length !== unique.length ||
39
+ values.some((value, index) => value !== unique[index]))) {
40
+ throw new Error(`Guide values cannot infer shared histogram bins for scale "${scaleId}".`);
36
41
  }
37
-
38
- return resolveHistogramBins({
39
- values: readQuantitativeField(dataset.values, encoding.field),
40
- bin: encoding.bin,
41
- domain: scale.domain,
42
- nice: scale.nice ?? true,
43
- zero: scale.zero ?? false
44
- }).boundaries;
42
+ return unique;
45
43
  }
46
44
 
47
45
  export function valuesFromTickConfig(program, config) {
@@ -18,8 +18,10 @@ import { registerRegressionActions } from "./regression/index.js";
18
18
  import { registerViolinPlotActions } from "./violinPlots/index.js";
19
19
  import { registerErrorBandActions } from "./errorBands/index.js";
20
20
  import { registerChartActions } from "./charts/index.js";
21
+ import { registerThemeActions } from "./theme/index.js";
21
22
 
22
23
  export function registerActions(ProgramClass) {
24
+ registerThemeActions(ProgramClass);
23
25
  registerPrimitiveActions(ProgramClass);
24
26
  registerCompositionActions(ProgramClass);
25
27
  registerFacetActions(ProgramClass);
@@ -7,15 +7,15 @@ import {
7
7
  validateNonEmptyString as requireField,
8
8
  validateOptionObject
9
9
  } from "../../core/validation.js";
10
- import { readQuantitativeField } from "../../grammar/scales/index.js";
10
+ import { readQuantitativeField, resolveTemporalUnit } from "../../grammar/scales/index.js";
11
11
  import { findDataset } from "../../selectors/datasets.js";
12
12
  import { hasLayer, resolveEligibleLayer } from "../../selectors/layers.js";
13
13
  import { findSemanticScale } from "../../selectors/scales.js";
14
14
 
15
15
  const CHANNEL_OPTIONS = [
16
- "field", "fieldType", "scale", "center", "extent", "level", "lower", "upper"
16
+ "field", "fieldType", "scale", "center", "extent", "method", "level", "lower", "upper", "temporalUnit"
17
17
  ];
18
- const INTERVAL_PARAMETER_KEYS = CHANNEL_OPTIONS.slice(3);
18
+ const INTERVAL_PARAMETER_KEYS = ["center", "extent", "method", "level", "lower", "upper"];
19
19
  const POSITION_CHANNELS = ["x", "y"];
20
20
  const FIELD_TYPES = [
21
21
  "quantitative", "temporal", "ordinal", "nominal"
@@ -71,7 +71,7 @@ function resolveIntervalChannel(channels, sourceLayer, {
71
71
  }) {
72
72
  for (const hints of [
73
73
  ["lower", "upper"],
74
- ["center", "extent", "level"]
74
+ ["center", "extent", "method", "level"]
75
75
  ]) {
76
76
  const hinted = POSITION_CHANNELS.filter(channel =>
77
77
  hasAny(channels[channel], hints)
@@ -123,8 +123,10 @@ function resolvePosition(program, channel, explicit, inferred, {
123
123
  `${operation} ${channel} position requires ${positionTypes.join(", ")} field type.`
124
124
  );
125
125
  }
126
+ const temporalUnit = resolveTemporalUnit({ ...explicit, field: explicit?.field ?? inferred?.field }, fieldType, inferred);
126
127
  return {
127
128
  channel,
129
+ ...(temporalUnit === undefined ? {} : { temporalUnit }),
128
130
  field: requireField(
129
131
  explicit?.field ?? inferred?.field,
130
132
  `${operation} ${channel} field`
@@ -143,6 +145,9 @@ function resolveInterval(program, channel, explicit, inferred, dataset, {
143
145
  operation,
144
146
  intervalScaleDefaults
145
147
  }) {
148
+ if (Object.hasOwn(explicit ?? {}, "temporalUnit")) {
149
+ throw new Error(`${operation} ${channel} interval does not accept temporalUnit.`);
150
+ }
146
151
  if (explicit?.fieldType !== undefined) {
147
152
  throw new Error(`${operation} ${channel} interval does not accept fieldType.`);
148
153
  }
@@ -167,10 +172,11 @@ function resolveInterval(program, channel, explicit, inferred, dataset, {
167
172
  if (
168
173
  explicit.field !== undefined ||
169
174
  explicit.extent !== undefined ||
175
+ explicit.method !== undefined ||
170
176
  explicit.level !== undefined
171
177
  ) {
172
178
  throw new Error(
173
- `Explicit ${operation} ${channel} interval cannot combine field, extent, or level.`
179
+ `Explicit ${operation} ${channel} interval cannot combine field, extent, method, or level.`
174
180
  );
175
181
  }
176
182
  const fields = {
@@ -187,15 +193,22 @@ function resolveInterval(program, channel, explicit, inferred, dataset, {
187
193
  }
188
194
  return { channel, mode: "explicit", fields, scale, title: fields.center };
189
195
  }
196
+ const field = requireField(
197
+ explicit?.field ?? inferred?.field,
198
+ `${operation} ${channel} field`
199
+ );
200
+ if (!dataset.values.some(row => Number.isFinite(row?.[field]))) {
201
+ throw new TypeError(
202
+ `Field "${field}" must contain at least one finite number.`
203
+ );
204
+ }
190
205
  return {
191
206
  channel,
192
207
  mode: "statistical",
193
- field: requireField(
194
- explicit?.field ?? inferred?.field,
195
- `${operation} ${channel} field`
196
- ),
208
+ field,
197
209
  center: explicit?.center,
198
210
  extent: explicit?.extent,
211
+ method: explicit?.method,
199
212
  level: explicit?.level,
200
213
  scale
201
214
  };
@@ -205,11 +218,16 @@ function resolveGrouping(args, sourceLayer, independentField, mode, {
205
218
  operation,
206
219
  allowExplicitGrouping
207
220
  }) {
208
- if (mode === "explicit" && !allowExplicitGrouping && args.groupBy !== undefined) {
221
+ if (
222
+ mode === "explicit" &&
223
+ !allowExplicitGrouping &&
224
+ args.groupBy !== undefined &&
225
+ args.groupBy !== false
226
+ ) {
209
227
  throw new Error(`Explicit ${operation} intervals do not accept groupBy.`);
210
228
  }
211
229
  const inferred = sourceLayer?.encoding?.group?.field;
212
- const groupField = args.groupBy ?? inferred;
230
+ const groupField = args.groupBy === false ? undefined : args.groupBy ?? inferred;
213
231
  const normalizedGroup = groupField === undefined
214
232
  ? undefined
215
233
  : requireField(groupField, `${operation} groupBy`);
@@ -234,7 +252,9 @@ export function resolveIntervalComposite(program, args, policy) {
234
252
  : requireObject(args.y, `${operation} y`)
235
253
  };
236
254
  let sourceLayer;
237
- if (!(args.target === undefined && args.x !== undefined && args.y !== undefined)) {
255
+ if (policy.existingId === undefined && !(
256
+ args.target === undefined && args.x !== undefined && args.y !== undefined
257
+ )) {
238
258
  const target = args.target === undefined
239
259
  ? undefined
240
260
  : validateUserId(args.target, `${operation} source layer id`);
@@ -281,7 +301,7 @@ export function resolveIntervalComposite(program, args, policy) {
281
301
  policy
282
302
  );
283
303
  const defaultId = policy.defaultId;
284
- const id = resolveOptionalUserId(args.id, {
304
+ const id = policy.existingId ?? resolveOptionalUserId(args.id, {
285
305
  defaultId,
286
306
  label: policy.ownerLabel,
287
307
  operation,
@@ -289,7 +309,7 @@ export function resolveIntervalComposite(program, args, policy) {
289
309
  program.graphicSpec.objects[defaultId] !== undefined ||
290
310
  findDataset(program, `${defaultId}IntervalData`) !== undefined
291
311
  });
292
- const generatedFields = {
312
+ const generatedFields = policy.generatedFields ?? {
293
313
  center: `__${id}_center`,
294
314
  lower: `__${id}_lower`,
295
315
  upper: `__${id}_upper`
@@ -309,7 +329,7 @@ export function resolveIntervalComposite(program, args, policy) {
309
329
  source: dataset.id,
310
330
  dataId: interval.mode === "statistical" ? `${id}IntervalData` : dataset.id,
311
331
  coordinate: validateUserId(
312
- args.coordinate ?? sourceLayer?.coordinate ?? "main",
332
+ args.coordinate ?? policy.coordinate ?? sourceLayer?.coordinate ?? "main",
313
333
  `${resourceLabel} coordinate id`
314
334
  ),
315
335
  orientation: intervalChannel === "y" ? "vertical" : "horizontal",