ggaction 0.0.17 → 0.0.18

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 (263) hide show
  1. package/CHANGELOG.md +46 -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/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/edit.js +1 -1
  35. package/src/actions/data/regression.js +2 -1
  36. package/src/actions/data/shared.js +3 -2
  37. package/src/actions/data/stack.js +3 -1
  38. package/src/actions/encodings/appearance.js +37 -29
  39. package/src/actions/encodings/barWidth.js +36 -11
  40. package/src/actions/encodings/channels.js +0 -1
  41. package/src/actions/encodings/color/continuous.js +1 -1
  42. package/src/actions/encodings/color/index.js +1 -1
  43. package/src/actions/encodings/color/policy.js +10 -3
  44. package/src/actions/encodings/density/resolve.js +1 -6
  45. package/src/actions/encodings/position/policies/arc.js +12 -10
  46. package/src/actions/encodings/position/policies/area.js +3 -3
  47. package/src/actions/encodings/position/policies/bar.js +15 -6
  48. package/src/actions/encodings/position/policies/line.js +17 -6
  49. package/src/actions/encodings/position/resolve.js +3 -2
  50. package/src/actions/encodings/ranged.js +17 -0
  51. package/src/actions/encodings/remove.js +3 -2
  52. package/src/actions/encodings/seriesLayout.js +1 -0
  53. package/src/actions/encodings/text.js +6 -3
  54. package/src/actions/errorBands/create.js +2 -2
  55. package/src/actions/errorBands/edit.js +2 -1
  56. package/src/actions/facets/actions.js +158 -240
  57. package/src/actions/gradientPlots/resolve.js +2 -17
  58. package/src/actions/guides/applicability.js +1 -1
  59. package/src/actions/guides/axes/axis.js +16 -4
  60. package/src/actions/guides/axes/edit.js +4 -4
  61. package/src/actions/guides/axes/labels.js +120 -46
  62. package/src/actions/guides/axes/lines.js +6 -5
  63. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  64. package/src/actions/guides/axes/parallel/policy.js +8 -7
  65. package/src/actions/guides/axes/remove.js +3 -2
  66. package/src/actions/guides/axes/tickGroups.js +5 -2
  67. package/src/actions/guides/axes/ticks.js +6 -3
  68. package/src/actions/guides/axes/titles.js +71 -26
  69. package/src/actions/guides/basic.js +2 -0
  70. package/src/actions/guides/facadeLegend.js +10 -2
  71. package/src/actions/guides/grids/grid.js +7 -1
  72. package/src/actions/guides/grids/resolve.js +59 -12
  73. package/src/actions/guides/legends/blocks.js +3 -1
  74. package/src/actions/guides/legends/categorical/actions.js +70 -17
  75. package/src/actions/guides/legends/categorical/components.js +12 -8
  76. package/src/actions/guides/legends/categorical/index.js +6 -0
  77. package/src/actions/guides/legends/categorical/layout.js +37 -8
  78. package/src/actions/guides/legends/categorical/options.js +31 -20
  79. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  80. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  81. package/src/actions/guides/legends/categorical/validation.js +1 -6
  82. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  83. package/src/actions/guides/legends/continuous/common.js +1 -447
  84. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  85. package/src/actions/guides/legends/continuous/interval.js +4 -4
  86. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  87. package/src/actions/guides/legends/edit.js +1 -604
  88. package/src/actions/guides/legends/focused.js +2 -2
  89. package/src/actions/guides/legends/group.js +1 -0
  90. package/src/actions/guides/legends/lane.js +1 -516
  91. package/src/actions/guides/legends/remove.js +3 -0
  92. package/src/actions/guides/legends/sampling.js +1 -1
  93. package/src/actions/guides/legends/size.js +51 -43
  94. package/src/actions/guides/legends/strokeWidth.js +11 -29
  95. package/src/actions/guides/legends/target.js +3 -3
  96. package/src/actions/guides/legends/transition.js +21 -7
  97. package/src/actions/guides/polar/axes/labels.js +4 -2
  98. package/src/actions/guides/polar/axes/titles.js +7 -5
  99. package/src/actions/guides/polar/resolve.js +6 -14
  100. package/src/actions/index.js +16 -16
  101. package/src/actions/marks/arc/actions.js +3 -10
  102. package/src/actions/marks/area/actions.js +8 -5
  103. package/src/actions/marks/area/materialize.js +8 -9
  104. package/src/actions/marks/bar/create.js +10 -4
  105. package/src/actions/marks/bar/edit.js +8 -4
  106. package/src/actions/marks/bar/materialize.js +9 -6
  107. package/src/actions/marks/line/actions.js +11 -4
  108. package/src/actions/marks/line/materialize.js +9 -5
  109. package/src/actions/marks/point/edit.js +1 -1
  110. package/src/actions/marks/rect/actions.js +3 -3
  111. package/src/actions/marks/remove.js +2 -0
  112. package/src/actions/marks/rule/actions.js +7 -4
  113. package/src/actions/marks/text/actions.js +17 -5
  114. package/src/actions/marks/text/layout.js +3 -0
  115. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  116. package/src/actions/regression/create.js +1 -1
  117. package/src/actions/regression/edit.js +3 -3
  118. package/src/actions/regression/resolve.js +2 -6
  119. package/src/actions/scales/channels.js +9 -4
  120. package/src/actions/scales/consumers/families.js +16 -5
  121. package/src/actions/scales/create.js +8 -3
  122. package/src/actions/scales/definitions.js +1 -425
  123. package/src/actions/scales/edit.js +2 -2
  124. package/src/actions/scales/editPolicy.js +9 -3
  125. package/src/actions/scales/materialize.js +1 -1
  126. package/src/actions/scales/preview.js +21 -1
  127. package/src/actions/scales/quantitativeColor.js +3 -3
  128. package/src/actions/selection/actions.js +1 -13
  129. package/src/actions/theme/reconcile.js +81 -27
  130. package/src/actions/theme/state.js +12 -0
  131. package/src/actions/titles/actions.js +5 -18
  132. package/src/actions/titles/resolve.js +41 -19
  133. package/src/actions/violinPlots/edit.js +3 -20
  134. package/src/core/compositionState.js +4 -1
  135. package/src/core/font.js +9 -2
  136. package/src/core/optionRequirements.js +1 -0
  137. package/src/core/resourceReferences.js +3 -0
  138. package/src/core/textMetricProfile.js +3 -2
  139. package/src/core/textMetrics.js +41 -4
  140. package/src/core/validation.js +26 -0
  141. package/src/core/vocabulary.js +4 -0
  142. package/src/grammar/aggregate.js +4 -18
  143. package/src/grammar/arcs.js +1 -386
  144. package/src/grammar/areaPaint.js +10 -0
  145. package/src/grammar/areaSeries.js +18 -10
  146. package/src/grammar/bars/geometry.js +30 -3
  147. package/src/grammar/bars/policy.js +12 -1
  148. package/src/grammar/bin.js +2 -12
  149. package/src/grammar/bin2d.js +1 -6
  150. package/src/grammar/boxPlot.js +56 -9
  151. package/src/grammar/categoryOrder.js +1 -6
  152. package/src/grammar/complete.js +15 -19
  153. package/src/grammar/computed.js +92 -25
  154. package/src/grammar/curveCommands.js +29 -16
  155. package/src/grammar/datasetSchema.js +3 -7
  156. package/src/grammar/displayLabels.js +28 -0
  157. package/src/grammar/ecdf.js +2 -9
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/guides.js +5 -0
  160. package/src/grammar/facets/index.js +2 -5
  161. package/src/grammar/fold.js +2 -12
  162. package/src/grammar/gradientProfile.js +1 -6
  163. package/src/grammar/impute.js +21 -48
  164. package/src/grammar/interval.js +2 -9
  165. package/src/grammar/legendGradient.js +18 -0
  166. package/src/grammar/legendGroups.js +1 -0
  167. package/src/grammar/legendTitleStyle.js +22 -0
  168. package/src/grammar/lineSeries.js +32 -17
  169. package/src/grammar/markLabels.js +5 -2
  170. package/src/grammar/normalize.js +4 -54
  171. package/src/grammar/numeric.js +1 -290
  172. package/src/grammar/parallelCoordinates.js +1 -6
  173. package/src/grammar/parseTime.js +41 -0
  174. package/src/grammar/pathCommands.js +11 -4
  175. package/src/grammar/polar.js +1 -184
  176. package/src/grammar/positionCompatibility.js +3 -3
  177. package/src/grammar/regression/derive.js +7 -1
  178. package/src/grammar/regression/models.js +83 -55
  179. package/src/grammar/regression/parameters.js +14 -3
  180. package/src/grammar/roundedRect.js +42 -54
  181. package/src/grammar/scales/appearance.js +1 -206
  182. package/src/grammar/scales/color.js +1 -382
  183. package/src/grammar/scales/colorConsumers.js +5 -2
  184. package/src/grammar/scales/definition.js +1 -197
  185. package/src/grammar/scales/discretized.js +1 -195
  186. package/src/grammar/scales/size.js +1 -456
  187. package/src/grammar/scales/temporal.js +2 -13
  188. package/src/grammar/scales/transformed.js +1 -383
  189. package/src/grammar/scales/types.js +1 -1
  190. package/src/grammar/scales/validation.js +1 -66
  191. package/src/grammar/schemas/concreteGraphic.js +11 -0
  192. package/src/grammar/schemas/graphic.js +3 -0
  193. package/src/grammar/schemas/semanticPath.js +2 -1
  194. package/src/grammar/stack.js +2 -10
  195. package/src/grammar/statisticalReference.js +1 -6
  196. package/src/grammar/summary.js +3 -26
  197. package/src/grammar/text.js +19 -0
  198. package/src/grammar/timeUnit.js +16 -7
  199. package/src/grammar/transformKeys.js +47 -0
  200. package/src/grammar/transforms.js +4 -1
  201. package/src/grammar/valueFormat.js +17 -5
  202. package/src/grammar/weightedStatistics.js +1 -6
  203. package/src/grammar/window.js +4 -58
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -110
  206. package/src/layout/facets.js +1 -244
  207. package/src/layout/labels.js +1 -0
  208. package/src/layout/legend.js +13 -0
  209. package/src/layout/legendGroup.js +1 -0
  210. package/src/layout/legendItems.js +59 -15
  211. package/src/layout/legendLane.js +1 -441
  212. package/src/layout/legendText.js +1 -0
  213. package/src/layout/title.js +35 -13
  214. package/src/materialization/bars/aggregate.js +11 -5
  215. package/src/materialization/bars/histogram.js +4 -1
  216. package/src/materialization/bars/ranged.js +19 -6
  217. package/src/materialization/bars/resolve.js +22 -1
  218. package/src/materialization/coordinateBounds.js +8 -0
  219. package/src/materialization/dependencies.js +4 -44
  220. package/src/materialization/facetGuides/layout.js +1 -0
  221. package/src/materialization/facetGuides/placement.js +19 -8
  222. package/src/materialization/facetGuides/preparation.js +15 -7
  223. package/src/materialization/facetHeaders.js +1 -347
  224. package/src/materialization/facets.js +1 -185
  225. package/src/materialization/graphicHierarchy.js +1 -1
  226. package/src/materialization/guides/layout.js +1 -57
  227. package/src/materialization/guides/resources.js +1 -123
  228. package/src/materialization/layout.js +53 -0
  229. package/src/materialization/legendGroups.js +1 -0
  230. package/src/materialization/legendText.js +1 -0
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/marks/capabilities.js +5 -6
  233. package/src/materialization/rect.js +1 -1
  234. package/src/materialization/scaleGuideDependencies.js +1 -1
  235. package/src/materialization/scales/map.js +3 -0
  236. package/src/materialization/scales/policies/arc.js +8 -7
  237. package/src/materialization/scales/policies/bar.js +19 -7
  238. package/src/materialization/scales/policies/series.js +2 -1
  239. package/src/materialization/scales/resolve.js +5 -3
  240. package/src/materialization/selection/items/arc.js +1 -1
  241. package/src/materialization/selection/items/bar.js +7 -6
  242. package/src/materialization/selection/items/path.js +7 -6
  243. package/src/materialization/selection/items/rect.js +2 -2
  244. package/src/materialization/text.js +13 -4
  245. package/src/materialization/textLines.js +15 -0
  246. package/src/persistence/codec.js +37 -6
  247. package/src/persistence/validation.js +47 -0
  248. package/src/persistence.js +18 -5
  249. package/src/renderers/canvas/circle.js +6 -3
  250. package/src/renderers/canvas/fill.js +7 -1
  251. package/src/renderers/canvas/index.js +2 -1
  252. package/src/renderers/canvas/line.js +1 -0
  253. package/src/renderers/canvas/path.js +1 -1
  254. package/src/renderers/canvas/rect.js +11 -8
  255. package/src/renderers/canvas/stroke.js +2 -1
  256. package/src/renderers/canvas/text.js +5 -3
  257. package/src/renderers/svg.js +4 -1
  258. package/src/selectors/layers.js +19 -0
  259. package/src/theme/defaults.js +8 -2
  260. package/src/version.js +1 -1
  261. package/types/basic.d.ts +1 -0
  262. package/types/index.d.ts +7 -0
  263. package/types/program.d.ts +347 -85
@@ -1,12 +1,7 @@
1
+ export { requireStringValue as requireDensityField } from "../../../core/validation.js";
1
2
  import { isPlainObject } from "../../../core/immutable.js";
2
3
  import { findSemanticScale } from "../../../selectors/scales.js";
3
4
 
4
- export function requireDensityField(value, label) {
5
- if (typeof value !== "string" || value.length === 0) {
6
- throw new TypeError(`${label} must be a non-empty string.`);
7
- }
8
- return value;
9
- }
10
5
 
11
6
  export function resolveDensityScaleOptions(value, defaults, label) {
12
7
  if (value !== undefined && !isPlainObject(value)) {
@@ -11,11 +11,6 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
11
11
  throw new Error(`Arc ${channel} encoding does not support stack.`);
12
12
  }
13
13
  if (channel === "radius") {
14
- if (layer.encoding?.theta?.fieldType === "quantitative") {
15
- throw new Error(
16
- "Arc radius encoding cannot be combined with quantitative theta."
17
- );
18
- }
19
14
  if (args.mapping !== undefined) {
20
15
  validateRadialMapping(args.mapping);
21
16
  if (fieldType !== "quantitative" || !["count", "sum"].includes(args.aggregate)) {
@@ -30,7 +25,16 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
30
25
  return { ...emptyPositionPolicy(), aggregate: args.aggregate };
31
26
  }
32
27
  if (args.aggregate !== undefined) {
33
- throw new Error("Arc radius encoding does not support aggregate.");
28
+ if (!["count", "sum"].includes(args.aggregate)) throw new Error("Arc radius aggregate supports count or sum.");
29
+ const theta = layer.encoding?.theta;
30
+ if (theta !== undefined && !["count", "sum"].includes(theta.aggregate)) {
31
+ throw new Error("Grouped arc radius requires aggregated categorical theta.");
32
+ }
33
+ if (args.aggregate === "count" && Object.hasOwn(args, "field")) {
34
+ throw new Error("Arc radius count does not accept a field.");
35
+ }
36
+ if (args.weight !== undefined) throw new Error("Arc radius encoding does not support weight.");
37
+ return { ...emptyPositionPolicy(), aggregate: args.aggregate };
34
38
  }
35
39
  if (args.weight !== undefined) {
36
40
  throw new Error("Arc radius encoding does not support weight.");
@@ -38,10 +42,8 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
38
42
  return emptyPositionPolicy();
39
43
  }
40
44
  if (fieldType === "quantitative") {
41
- if (layer.encoding?.radius !== undefined) {
42
- throw new Error(
43
- "Quantitative arc theta cannot be combined with radius encoding."
44
- );
45
+ if (layer.encoding?.radius?.aggregate !== undefined) {
46
+ throw new Error("Grouped arc radius requires aggregated categorical theta.");
45
47
  }
46
48
  if (args.aggregate !== undefined) {
47
49
  throw new Error("Quantitative arc theta does not support aggregate.");
@@ -26,16 +26,16 @@ export function resolveAreaPositionPolicy({
26
26
  const density = dataset.transform?.find(transform => transform.type === "density");
27
27
  if (["nominal", "ordinal"].includes(fieldType)) {
28
28
  if (
29
- density?.placement?.type !== "category" ||
29
+ density !== undefined && (density.placement?.type !== "category" ||
30
30
  density.placement.channel !== channel ||
31
- density.placement.categoryField !== field
31
+ density.placement.categoryField !== field)
32
32
  ) {
33
33
  throw new Error(
34
34
  "Categorical area position requires a matching category density placement."
35
35
  );
36
36
  }
37
37
  if (args.aggregate !== undefined || args.bin !== undefined || args.stack !== undefined) {
38
- throw new Error("Categorical density position does not support aggregate, bin, or stack.");
38
+ throw new Error("Categorical area position does not support aggregate, bin, or stack.");
39
39
  }
40
40
  return emptyPositionPolicy();
41
41
  }
@@ -26,7 +26,15 @@ function resolveBarChannelPolicy({
26
26
  let stack;
27
27
  const xEncoding = layer.encoding?.x;
28
28
  const opposite = layer.encoding?.[channel === "x" ? "y" : "x"];
29
- const pendingBoxRange = program.markConfigs[layer.id]?.boxPlot !== undefined;
29
+ const pendingRange = program.markConfigs[layer.id]?.boxPlot !== undefined ||
30
+ layer.encoding?.[`${channel}2`] !== undefined;
31
+
32
+ if (layer.mark?.orientation !== undefined) {
33
+ if (fieldType !== "quantitative" || ["bin", "aggregate", "stack", "weight"].some(key => args[key] !== undefined)) {
34
+ throw new Error("Oriented numeric bars require raw quantitative positions without bin, aggregate, stack, or weight.");
35
+ }
36
+ return { bin, aggregate, stack };
37
+ }
30
38
 
31
39
  if (["nominal", "ordinal", "temporal"].includes(fieldType)) {
32
40
  if (args.aggregate !== undefined || args.bin !== undefined || args.stack !== undefined) {
@@ -73,11 +81,11 @@ function resolveBarChannelPolicy({
73
81
  }
74
82
  aggregate = args.aggregate ?? (
75
83
  ["nominal", "ordinal", "temporal"].includes(opposite?.fieldType) &&
76
- !pendingBoxRange
84
+ !pendingRange
77
85
  ? "mean"
78
86
  : undefined
79
87
  );
80
- if (pendingBoxRange && args.aggregate === undefined) {
88
+ if (pendingRange && args.aggregate === undefined) {
81
89
  return { bin, aggregate, stack };
82
90
  }
83
91
  stack = Object.hasOwn(args, "stack") ? args.stack : opposite === undefined ? undefined : null;
@@ -106,7 +114,8 @@ export function resolveBarPositionPolicy(context) {
106
114
  const policy = resolveBarChannelPolicy(context);
107
115
  const oppositeChannel = channel === "x" ? "y" : "x";
108
116
  const opposite = layer.encoding?.[oppositeChannel];
109
- const pendingBoxRange = program.markConfigs[layer.id]?.boxPlot !== undefined;
117
+ const pendingRange = program.markConfigs[layer.id]?.boxPlot !== undefined ||
118
+ layer.encoding?.[`${channel}2`] !== undefined;
110
119
  const candidate = {
111
120
  ...layer,
112
121
  encoding: {
@@ -118,7 +127,7 @@ export function resolveBarPositionPolicy(context) {
118
127
  // Absence of an aggregate/stack is sufficient to represent an unresolved role.
119
128
  let companion;
120
129
  if (
121
- !pendingBoxRange && resolveBarGrain(layer) === undefined &&
130
+ !pendingRange && resolveBarGrain(layer) === undefined &&
122
131
  opposite?.fieldType === "quantitative" && opposite.bin === undefined
123
132
  ) {
124
133
  const resolved = resolveBarChannelPolicy({
@@ -138,7 +147,7 @@ export function resolveBarPositionPolicy(context) {
138
147
  }
139
148
  }
140
149
  const orientation = resolveBarOrientation(candidate);
141
- if (opposite !== undefined && orientation === undefined && !pendingBoxRange) {
150
+ if (opposite !== undefined && orientation === undefined && !pendingRange) {
142
151
  throw new Error(
143
152
  `Bar ${channel} encoding requires a quantitative field opposite a categorical position.`
144
153
  );
@@ -68,6 +68,13 @@ export function resolveLinePositionPolicy({
68
68
  stack: undefined
69
69
  };
70
70
  }
71
+ if (["nominal", "ordinal"].includes(fieldType)) {
72
+ if (layer.encoding?.y !== undefined && layer.encoding.y.aggregate === undefined &&
73
+ layer.encoding.y.fieldType !== "quantitative") {
74
+ throw new Error("Categorical line x requires a quantitative value or aggregate y encoding.");
75
+ }
76
+ return emptyPositionPolicy();
77
+ }
71
78
  const directPair =
72
79
  layer.encoding?.y?.aggregate === undefined &&
73
80
  ["quantitative", "temporal"].includes(layer.encoding?.y?.fieldType) &&
@@ -82,7 +89,7 @@ export function resolveLinePositionPolicy({
82
89
  )
83
90
  ) {
84
91
  throw new Error(
85
- "Line x encoding requires a temporal field or a compatible derived quantitative field."
92
+ "Line x encoding requires a categorical or temporal field, or a compatible quantitative field."
86
93
  );
87
94
  }
88
95
  return emptyPositionPolicy();
@@ -93,15 +100,19 @@ export function resolveLinePositionPolicy({
93
100
  if (layer.encoding?.x?.bin !== undefined && args.aggregate === undefined) {
94
101
  throw new Error("Binned line x encoding requires an aggregate y encoding.");
95
102
  }
96
- const quantitativePair =
103
+ if (["nominal", "ordinal"].includes(layer.encoding?.x?.fieldType) &&
104
+ args.aggregate === undefined && fieldType !== "quantitative") {
105
+ throw new Error("Categorical line x requires a quantitative value or aggregate y encoding.");
106
+ }
107
+ const continuousPair =
97
108
  fieldType === "quantitative" &&
98
- layer.encoding?.x?.fieldType === "quantitative" &&
109
+ ["quantitative", "temporal", "nominal", "ordinal"].includes(layer.encoding?.x?.fieldType) &&
99
110
  layer.encoding.x.bin === undefined;
100
111
  const prospectiveDirect =
101
112
  args.aggregate === undefined &&
102
113
  (fieldType === "temporal" ||
103
114
  (fieldType === "quantitative" &&
104
- (layer.encoding?.x === undefined || quantitativePair)));
115
+ (layer.encoding?.x === undefined || continuousPair)));
105
116
  if (interval || (windowOutput && args.aggregate === undefined) || prospectiveDirect) {
106
117
  if (!["quantitative", "temporal"].includes(fieldType)) {
107
118
  throw new Error(
@@ -125,12 +136,12 @@ export function resolveLinePositionPolicy({
125
136
 
126
137
  if (layer.encoding?.x !== undefined) {
127
138
  const compatibleAggregateX =
128
- layer.encoding.x.fieldType === "temporal" ||
139
+ ["temporal", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) ||
129
140
  (layer.encoding.x.fieldType === "quantitative" &&
130
141
  layer.encoding.x.bin !== undefined);
131
142
  if (!compatibleAggregateX) {
132
143
  throw new Error(
133
- "Aggregate line y encoding requires a temporal or binned quantitative x encoding."
144
+ "Aggregate line y encoding requires a temporal, categorical, or binned quantitative x encoding."
134
145
  );
135
146
  }
136
147
  }
@@ -142,7 +142,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
142
142
  ? undefined
143
143
  : args.mapping ?? findSemanticScale(program, previous?.scale)?.radialMapping
144
144
  : undefined;
145
- const countRadius = mapping !== undefined && (args.aggregate ?? previous?.aggregate) === "count";
145
+ const countRadius = channel === "radius" && layer.mark.type === "arc" &&
146
+ !clearRadialMapping && (args.aggregate ?? previous?.aggregate) === "count";
146
147
  const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
147
148
  ["rule", "rect", "text", "point", "tick"].includes(layer.mark.type) && hasDatum
148
149
  ? inferDatumFieldType(args.datum, operation)
@@ -179,7 +180,7 @@ export function resolvePositionEncoding(program, channel, args, operation) {
179
180
  } else readScaleField(dataset.values, field, fieldType, { temporalUnit });
180
181
  }
181
182
  const effectiveArgs = { ...args };
182
- if (clearRadialMapping) delete effectiveArgs.mapping;
183
+ if (clearRadialMapping && args.aggregate === undefined) delete effectiveArgs.mapping;
183
184
  else if (mapping !== undefined) effectiveArgs.mapping = mapping;
184
185
  const directQuantitativeArcTheta =
185
186
  layer.mark.type === "arc" &&
@@ -127,6 +127,9 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
127
127
 
128
128
  let next = program;
129
129
  if (layer.mark.type === "bar") {
130
+ if (layer.mark.orientation !== undefined && primaryChannel !== (layer.mark.orientation === "vertical" ? "y" : "x")) {
131
+ throw new Error("Numeric bar endpoints must use the measure channel.");
132
+ }
130
133
  if (layer.layout?.mode !== undefined && layer.layout.mode !== "overlay") throw new Error("Ranged bars support only overlay layout; clear series layout before assigning endpoints.");
131
134
  for (const property of ["aggregate", "stack", "bin"]) {
132
135
  if (layer.encoding?.[primaryChannel]?.[property] !== undefined) {
@@ -210,6 +213,20 @@ function rangeAction(channel) {
210
213
  const { layer, dataset } = resolveTarget(this, args.target, ["area", "bar", "rect", "rule"], "ranged mark");
211
214
  let next = this;
212
215
  let lower = { field: args.lower }, upper = { field: args.upper };
216
+ if (layer.mark.type === "bar") {
217
+ if (layer.mark.orientation !== undefined && channel !== (layer.mark.orientation === "vertical" ? "y" : "x")) {
218
+ throw new Error("Numeric bar endpoints must use the measure channel.");
219
+ }
220
+ common.target = layer.id;
221
+ const resolved = resolvePositionEncoding(this, channel, {
222
+ ...common, ...lower,
223
+ ...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
224
+ ...(args.scale === undefined ? {} : { scale: args.scale })
225
+ }, op);
226
+ next = setEncodingProperties(next, layer.id, `${channel}2`, {
227
+ ...upper, fieldType: common.fieldType, scale: resolved.scale.id
228
+ });
229
+ }
213
230
  if (layer.mark.type === "area") {
214
231
  common.target = layer.id;
215
232
  const prepared = prepareAreaRange(this, layer, dataset, channel, args, common);
@@ -26,6 +26,7 @@ import { findDataset } from "../../selectors/datasets.js";
26
26
  import { assertPathGroupCompatible, validatePathGroupAppearance } from "../../materialization/marks/grouping.js";
27
27
  import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
28
28
  import { planLegendBlockTransitions } from "../guides/legends/transition.js";
29
+ import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
29
30
 
30
31
  const OPTIONS = Object.freeze(["target", "channel"]);
31
32
  const REMOVABLE_CHANNELS = Object.freeze([
@@ -208,7 +209,7 @@ export const removeEncoding = /* @__PURE__ */ closedAction(
208
209
  op: "removeEncoding",
209
210
  description: "Remove one semantic encoding and its owned materialized state."
210
211
  }, OPTIONS,
211
- function (args = {}) {
212
+ withGuideLayoutValidation(function (args = {}) {
212
213
  if (!REMOVABLE_CHANNELS.includes(args.channel)) {
213
214
  throw new Error(
214
215
  `Unsupported removable encoding channel "${args.channel}".`
@@ -259,7 +260,7 @@ export const removeEncoding = /* @__PURE__ */ closedAction(
259
260
  next = cleanupLegends(next, layer.id, channels);
260
261
  next = cleanupPositionGuides(next, removedPositions);
261
262
  return applyDetachedScaleRematerialization(rematerializeTarget(next, layer.id), [layer]);
262
- }
263
+ })
263
264
  );
264
265
 
265
266
  export function registerEncodingRemovalAction(ProgramClass) {
@@ -48,6 +48,7 @@ export const layoutSeries = /* @__PURE__ */ action({ op: "layoutSeries", descrip
48
48
  if (grain === undefined || encoding.x?.scale === undefined || encoding.y?.scale === undefined) throw new Error("Series layout requires a complete bar.");
49
49
  if (mode === "center") throw new Error("Centered bars are not supported.");
50
50
  if (grain === BAR_GRAINS.ranged && mode !== "overlay") throw new Error("Ranged bars support only overlay layout.");
51
+ if (grain === BAR_GRAINS.centered && mode !== "overlay") throw new Error("Numeric-center bars support only overlay layout.");
51
52
  if (grain === BAR_GRAINS.aggregate) deriveBarAggregates(dataset.values, updated);
52
53
  if (mode === "group" && grain === BAR_GRAINS.aggregate) {
53
54
  if (encoding.group === undefined) throw new Error("Grouped bars require a group encoding.");
@@ -1,4 +1,5 @@
1
1
  import { action } from "../../core/action.js";
2
+ import { schemaField } from "../../grammar/datasetSchema.js";
2
3
  import { formatTextValue, validateTextFormat } from "../../grammar/text.js";
3
4
  import { normalizeMarkLabelContent, resolveMarkLabelValues } from "../../grammar/markLabels.js";
4
5
  import { canMaterializeText } from "../../materialization/marks/index.js";
@@ -14,11 +15,13 @@ import { applyRequestedItemMissingPolicy } from
14
15
 
15
16
  const OPTIONS = Object.freeze(["target", "field", "value", "content", "normalizeBy", "format"]);
16
17
 
17
- function validateTextField(rows, field, format) {
18
+ function validateTextField(dataset, field, format) {
19
+ const rows = dataset.values;
18
20
  if (typeof field !== "string" || field.length === 0) {
19
21
  throw new TypeError("encodeText field must be a non-empty string.");
20
22
  }
21
- let found = false;
23
+ let found = rows.length === 0 && dataset.schema !== undefined &&
24
+ schemaField(dataset.schema, field) !== undefined;
22
25
  for (const row of rows) {
23
26
  if (!Object.hasOwn(row, field)) continue;
24
27
  found = true;
@@ -57,7 +60,7 @@ export const encodeText = /* @__PURE__ */ action(
57
60
  const content = hasContent
58
61
  ? normalizeMarkLabelContent(findLayer(this, layer.source), args)
59
62
  : undefined;
60
- if (hasField) validateTextField(dataset.values, args.field, format);
63
+ if (hasField) validateTextField(dataset, args.field, format);
61
64
  else if (hasValue && formatTextValue(args.value, format) === undefined) {
62
65
  throw new Error("encodeText value must produce non-empty text.");
63
66
  }
@@ -1,7 +1,7 @@
1
+ import { validateFillPaint } from "../../grammar/paint.js";
1
2
  import { editGraphicProperties } from "../primitives/graphicProperties.js";
2
3
  import { action, closedAction } from "../../core/action.js";
3
4
  import { isPlainObject } from "../../core/immutable.js";
4
- import { validateNonEmptyString } from "../../core/validation.js";
5
5
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
6
6
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
7
7
  import {
@@ -176,7 +176,7 @@ export const createErrorBand = /* @__PURE__ */ closedAction(
176
176
  const resolved = resolveErrorBand(this, args);
177
177
  const curve = validateCurveInterpolation(args.curve ?? "linear");
178
178
  const boundaries = resolveBoundaries(args.boundaries, curve);
179
- if (args.fill !== undefined) validateNonEmptyString(args.fill, "Error-band fill");
179
+ if (args.fill !== undefined) validateFillPaint(args.fill, "Error-band fill");
180
180
  let next = createResolvedIntervalData(this, resolved);
181
181
  next = next.createAreaMark({
182
182
  id: resolved.id,
@@ -1,3 +1,4 @@
1
+ import { validateFillPaint } from "../../grammar/paint.js";
1
2
  import { currentIntervalRoleArgs, resolveIntervalRoleScales, applyIntervalStatistics } from "../intervals/revision.js";
2
3
  import { closedAction } from "../../core/action.js";
3
4
  import { isPlainObject } from "../../core/immutable.js";
@@ -333,7 +334,7 @@ export const editErrorBand = /* @__PURE__ */ closedAction(
333
334
  if (owner.encoding?.color !== undefined) {
334
335
  throw new Error("editErrorBand fill conflicts with a color encoding; remove the color encoding first.");
335
336
  }
336
- errorBand.fill = validateNonEmptyString(args.fill, "Error-band fill");
337
+ errorBand.fill = validateFillPaint(args.fill, "Error-band fill");
337
338
  config.fill = errorBand.fill;
338
339
  }
339
340
  }