ggaction 0.0.10 → 0.0.12

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 (224) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +5 -3
  3. package/knowledge/action-card.schema.json +1 -0
  4. package/knowledge/action-cards.json +1 -10941
  5. package/knowledge/action-cards.schema.json +1 -0
  6. package/knowledge/intent-taxonomy.json +1 -183
  7. package/knowledge/intent-taxonomy.schema.json +1 -0
  8. package/knowledge/mcp-resources.json +1 -95
  9. package/knowledge/mcp-resources.schema.json +1 -0
  10. package/knowledge/task-packet.schema.json +1 -159
  11. package/knowledge/task-resolver.js +1 -1230
  12. package/package.json +18 -2
  13. package/src/actions/boxPlots/edit.js +45 -298
  14. package/src/actions/boxPlots/options.js +46 -77
  15. package/src/actions/boxPlots/resolve.js +1 -1
  16. package/src/actions/charts/bar.js +3 -0
  17. package/src/actions/charts/heatmap.js +73 -83
  18. package/src/actions/charts/index.js +1 -1
  19. package/src/actions/charts/parallel.js +35 -1
  20. package/src/actions/charts/shared.js +20 -0
  21. package/src/actions/composition/actions.js +46 -49
  22. package/src/actions/data/bin2d.js +105 -2
  23. package/src/actions/data/box.js +17 -17
  24. package/src/actions/data/density.js +42 -78
  25. package/src/actions/data/gradientProfile.js +22 -52
  26. package/src/actions/data/horizon.js +16 -41
  27. package/src/actions/data/index.js +10 -10
  28. package/src/actions/data/interval.js +16 -38
  29. package/src/actions/data/intervalEdit.js +55 -0
  30. package/src/actions/data/regression.js +7 -17
  31. package/src/actions/data/shared.js +47 -0
  32. package/src/actions/data/timeUnit.js +14 -38
  33. package/src/actions/data/window.js +14 -38
  34. package/src/actions/distributions/revision.js +154 -34
  35. package/src/actions/encodings/angle.js +9 -10
  36. package/src/actions/encodings/appearance.js +17 -51
  37. package/src/actions/encodings/density/resolve.js +3 -1
  38. package/src/actions/encodings/density.js +1 -15
  39. package/src/actions/encodings/horizon/resolve.js +13 -3
  40. package/src/actions/encodings/horizon.js +8 -7
  41. package/src/actions/encodings/index.js +9 -9
  42. package/src/actions/encodings/offset.js +41 -45
  43. package/src/actions/encodings/parallel.js +9 -10
  44. package/src/actions/encodings/pathOrder.js +6 -13
  45. package/src/actions/encodings/position/apply.js +30 -0
  46. package/src/actions/encodings/position/policies/arc.js +23 -2
  47. package/src/actions/encodings/position/policies/index.js +4 -4
  48. package/src/actions/encodings/position/policies/line.js +47 -17
  49. package/src/actions/encodings/position/resolve.js +36 -7
  50. package/src/actions/encodings/ranged.js +18 -23
  51. package/src/actions/encodings/remove.js +15 -25
  52. package/src/actions/encodings/ruleAppearance.js +9 -24
  53. package/src/actions/encodings/shared.js +37 -0
  54. package/src/actions/encodings/strokeDash.js +8 -23
  55. package/src/actions/encodings/text.js +9 -10
  56. package/src/actions/errorBands/create.js +105 -39
  57. package/src/actions/errorBands/edit.js +89 -93
  58. package/src/actions/errorBands/index.js +1 -2
  59. package/src/actions/errorBars/create.js +212 -30
  60. package/src/actions/errorBars/edit.js +91 -48
  61. package/src/actions/errorBars/index.js +1 -2
  62. package/src/actions/facets/actions.js +45 -69
  63. package/src/actions/facets/derive.js +38 -53
  64. package/src/actions/facets/replay.js +0 -1
  65. package/src/actions/gradientPlots/components.js +59 -48
  66. package/src/actions/gradientPlots/edit.js +35 -216
  67. package/src/actions/gradientPlots/index.js +11 -10
  68. package/src/actions/gradientPlots/materialize.js +21 -35
  69. package/src/actions/gradientPlots/options.js +5 -7
  70. package/src/actions/gradientPlots/paint.js +9 -6
  71. package/src/actions/gradientPlots/resolve.js +1 -1
  72. package/src/actions/guides/axes/index.js +3 -3
  73. package/src/actions/guides/axes/labels.js +68 -26
  74. package/src/actions/guides/axes/lines.js +22 -2
  75. package/src/actions/guides/axes/policy.js +28 -8
  76. package/src/actions/guides/axes/ticks.js +27 -9
  77. package/src/actions/guides/axes/titles.js +113 -65
  78. package/src/actions/guides/grids/resolve.js +14 -0
  79. package/src/actions/guides/guides.js +2 -4
  80. package/src/actions/guides/index.js +2 -2
  81. package/src/actions/guides/legends/categorical/actions.js +44 -68
  82. package/src/actions/guides/legends/categorical/components.js +6 -1
  83. package/src/actions/guides/legends/categorical/index.js +2 -2
  84. package/src/actions/guides/legends/categorical/layout.js +148 -175
  85. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  86. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  87. package/src/actions/guides/legends/continuous/common.js +117 -66
  88. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  89. package/src/actions/guides/legends/continuous/interval.js +97 -76
  90. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  91. package/src/actions/guides/legends/edit.js +14 -1
  92. package/src/actions/guides/legends/index.js +1 -1
  93. package/src/actions/guides/legends/lane.js +2 -1
  94. package/src/actions/guides/legends/remove.js +8 -0
  95. package/src/actions/guides/legends/size.js +36 -43
  96. package/src/actions/guides/legends/strokeWidth.js +20 -26
  97. package/src/actions/guides/polar/axes/labels.js +17 -1
  98. package/src/actions/guides/polar/axes/shared.js +7 -1
  99. package/src/actions/guides/polar/axes/titles.js +19 -2
  100. package/src/actions/guides/polar/grids.js +4 -0
  101. package/src/actions/guides/polar/resolve.js +6 -0
  102. package/src/actions/index.js +13 -13
  103. package/src/actions/intervals/resolve.js +87 -117
  104. package/src/actions/marks/arc/actions.js +31 -36
  105. package/src/actions/marks/area/actions.js +23 -31
  106. package/src/actions/marks/index.js +6 -6
  107. package/src/actions/marks/line/actions.js +33 -52
  108. package/src/actions/marks/line/materialize.js +7 -5
  109. package/src/actions/marks/point/materialize.js +38 -16
  110. package/src/actions/marks/remove.js +21 -0
  111. package/src/actions/marks/rule/actions.js +79 -54
  112. package/src/actions/marks/shared.js +8 -4
  113. package/src/actions/marks/text/actions.js +14 -6
  114. package/src/actions/marks/text/layout.js +21 -45
  115. package/src/actions/marks/tick/actions.js +22 -21
  116. package/src/actions/primitives/createGraphics.js +4 -0
  117. package/src/actions/primitives/editGraphics.js +4 -0
  118. package/src/actions/primitives/index.js +3 -3
  119. package/src/actions/primitives/semantic.js +1 -1
  120. package/src/actions/primitives/semanticValidation/index.js +2 -2
  121. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  122. package/src/actions/regression/edit.js +3 -43
  123. package/src/actions/scales/consumers/families.js +24 -4
  124. package/src/actions/scales/consumers/index.js +1 -1
  125. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  126. package/src/actions/scales/definitions.js +100 -103
  127. package/src/actions/scales/materialize.js +5 -0
  128. package/src/actions/selection/actions.js +79 -121
  129. package/src/actions/titles/actions.js +48 -94
  130. package/src/actions/titles/resolve.js +14 -27
  131. package/src/actions/violinPlots/create.js +37 -44
  132. package/src/core/textMetrics.js +74 -37
  133. package/src/core/validation.js +18 -0
  134. package/src/core/vocabulary.js +2 -2
  135. package/src/grammar/aggregate.js +48 -28
  136. package/src/grammar/arcs.js +170 -32
  137. package/src/grammar/areaSeries.js +14 -16
  138. package/src/grammar/bin2d.js +37 -23
  139. package/src/grammar/boxPlot.js +14 -13
  140. package/src/grammar/categoricalDensity.js +14 -14
  141. package/src/grammar/categoryOrder.js +8 -14
  142. package/src/grammar/curveCommands.js +87 -20
  143. package/src/grammar/density.js +162 -141
  144. package/src/grammar/facets/index.js +14 -0
  145. package/src/grammar/facets/scales.js +7 -4
  146. package/src/grammar/gradientProfile.js +12 -15
  147. package/src/grammar/histogram.js +145 -83
  148. package/src/grammar/horizon.js +60 -23
  149. package/src/grammar/interval.js +14 -13
  150. package/src/grammar/lineSeries.js +91 -61
  151. package/src/grammar/numeric.js +290 -0
  152. package/src/grammar/paint.js +3 -4
  153. package/src/grammar/palettes.js +21 -32
  154. package/src/grammar/pathCommands.js +6 -0
  155. package/src/grammar/polarPaths.js +5 -5
  156. package/src/grammar/positionCompatibility.js +1 -1
  157. package/src/grammar/regression/derive.js +48 -9
  158. package/src/grammar/regression/models.js +248 -69
  159. package/src/grammar/regression/parameters.js +8 -0
  160. package/src/grammar/rules.js +2 -0
  161. package/src/grammar/scales/color.js +35 -26
  162. package/src/grammar/scales/continuous.js +71 -24
  163. package/src/grammar/scales/definition.js +1 -1
  164. package/src/grammar/scales/discretized.js +42 -12
  165. package/src/grammar/scales/fields.js +21 -4
  166. package/src/grammar/scales/ordinal.js +139 -60
  167. package/src/grammar/scales/temporal.js +40 -11
  168. package/src/grammar/scales/transformed.js +199 -52
  169. package/src/grammar/schemas/graphicBounds.js +270 -79
  170. package/src/grammar/seriesLayout.js +50 -18
  171. package/src/grammar/ticks.js +123 -53
  172. package/src/grammar/timeUnit.js +18 -8
  173. package/src/grammar/transforms.js +4 -1
  174. package/src/grammar/window.js +70 -57
  175. package/src/index.js +1 -1
  176. package/src/layout/canvas.js +4 -8
  177. package/src/layout/composition.js +39 -40
  178. package/src/layout/facets.js +55 -30
  179. package/src/layout/labels.js +91 -15
  180. package/src/layout/legend.js +22 -20
  181. package/src/layout/legendLane.js +54 -40
  182. package/src/layout/title.js +7 -7
  183. package/src/materialization/bars/aggregate.js +14 -40
  184. package/src/materialization/bars/grouped.js +7 -18
  185. package/src/materialization/bars/histogram.js +2 -19
  186. package/src/materialization/bars/ranged.js +20 -15
  187. package/src/materialization/bars/resolve.js +21 -0
  188. package/src/materialization/facetGuides/legacyCategorical.js +9 -2
  189. package/src/materialization/facetGuides/placement.js +49 -19
  190. package/src/materialization/facetGuides/preparation.js +35 -6
  191. package/src/materialization/facets.js +42 -28
  192. package/src/materialization/guides/resources.js +2 -1
  193. package/src/materialization/marks/capabilities.js +15 -4
  194. package/src/materialization/marks/index.js +29 -6
  195. package/src/materialization/rowEncoding.js +57 -1
  196. package/src/materialization/scales/policies/bar.js +61 -102
  197. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  198. package/src/materialization/scales/policies/offset.js +54 -0
  199. package/src/materialization/scales/policies/series.js +13 -7
  200. package/src/materialization/scales/resolve.js +6 -9
  201. package/src/materialization/selection/items/arc.js +16 -2
  202. package/src/materialization/selection/items/bar.js +4 -2
  203. package/src/materialization/selection/items/common.js +21 -1
  204. package/src/materialization/selection/items/path.js +19 -2
  205. package/src/materialization/selection/items/point.js +4 -16
  206. package/src/materialization/selection/items/tick.js +13 -21
  207. package/src/materialization/selection/point.js +11 -4
  208. package/src/materialization/selection/styles.js +19 -27
  209. package/src/materialization/text.js +4 -1
  210. package/src/mcp/adapter.js +8 -3
  211. package/src/renderers/canvas/fill.js +2 -2
  212. package/src/renderers/canvas/index.js +35 -2
  213. package/src/renderers/canvas/native.js +160 -0
  214. package/src/renderers/pdf.js +14 -0
  215. package/src/renderers/svg.js +37 -4
  216. package/types/index.d.ts +3 -0
  217. package/types/program.d.ts +523 -131
  218. package/types/svg.d.ts +1 -0
  219. package/src/actions/errorBands/components.js +0 -81
  220. package/src/actions/errorBands/options.js +0 -39
  221. package/src/actions/errorBands/resolve.js +0 -28
  222. package/src/actions/errorBars/components.js +0 -62
  223. package/src/actions/errorBars/options.js +0 -58
  224. package/src/actions/errorBars/resolve.js +0 -25
@@ -15,7 +15,7 @@ import { planDerivedDataRevision } from
15
15
  import { validateOptions } from "./shared.js";
16
16
  import { normalizeDensityPlacement } from "../../grammar/density.js";
17
17
  import { scaleEditPatch } from "../scales/patch.js";
18
- import { removeLegendKinds } from "../guides/legends/remove.js";
18
+ import { removeOwnedColorLegends } from "../guides/legends/remove.js";
19
19
  import {
20
20
  requireDensityField,
21
21
  resolveDensityCategoryScaleOptions,
@@ -215,19 +215,6 @@ function assertGroupingSelectionCompatibility(program, layer, changesGroup) {
215
215
  }
216
216
  }
217
217
 
218
- function removeOwnedColorLegends(program, target) {
219
- const kinds = Object.entries(program.guideConfigs.legend ?? {})
220
- .filter(([kind, config]) =>
221
- config?.target === target &&
222
- (
223
- config.channels?.includes("color") ||
224
- ["gradient", "interval"].includes(kind)
225
- )
226
- )
227
- .map(([kind]) => kind);
228
- return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
229
- }
230
-
231
218
  const editDensity = action(
232
219
  {
233
220
  op: "editDensity",
@@ -454,7 +441,6 @@ const editDensity = action(
454
441
  }
455
442
  return next;
456
443
  };
457
- applyEdit(this);
458
444
  return applyEdit(this);
459
445
  }
460
446
  );
@@ -167,7 +167,11 @@ export function resolveHorizonPaletteColors(transform) {
167
167
  for (const sign of ["negative", "positive"]) {
168
168
  const palette = transform.palette[sign];
169
169
  const dense = palette.count === undefined && transform.bands > 1
170
- ? resolvePalette({ ...palette, count: transform.bands * 2 }, transform.bands * 2)
170
+ ? resolvePalette(
171
+ { ...palette, count: transform.bands * 2 },
172
+ transform.bands * 2,
173
+ transform.bands * 2
174
+ )
171
175
  : resolvePalette(palette, transform.bands);
172
176
  const colors = palette.count === undefined && transform.bands > 1
173
177
  ? Array.from({ length: transform.bands }, (_, index) =>
@@ -181,11 +185,17 @@ export function resolveHorizonPaletteColors(transform) {
181
185
  return { domain, range };
182
186
  }
183
187
 
184
- export function resolveFoldedHorizonScaleOptions(target, requested) {
188
+ export function resolveFoldedHorizonScaleOptions(target, requested, existing = {}) {
185
189
  if (requested !== undefined && !isPlainObject(requested)) {
186
190
  throw new TypeError("Horizon y scale must be a plain object.");
187
191
  }
188
- const options = requested ?? {};
192
+ const patch = requested ?? {};
193
+ validateOptionObject(
194
+ patch,
195
+ ["id", "type", "domain", "range", "clamp", "reverse"],
196
+ "Horizon y scale"
197
+ );
198
+ const options = { ...existing, ...patch };
189
199
  if (
190
200
  options.domain !== undefined &&
191
201
  (!Array.isArray(options.domain) ||
@@ -5,6 +5,7 @@ import {
5
5
  deriveHorizon,
6
6
  validateHorizonTransform
7
7
  } from "../../grammar/horizon.js";
8
+ import { numericExtent } from "../../grammar/numeric.js";
8
9
  import {
9
10
  normalizeTemporalValue,
10
11
  readQuantitativeField
@@ -217,7 +218,7 @@ const editHorizon = action(
217
218
  id: currentX.id,
218
219
  ...(emptyXDomain === undefined
219
220
  ? {}
220
- : { domain: [Math.min(...emptyXDomain), Math.max(...emptyXDomain)] }),
221
+ : { domain: numericExtent(emptyXDomain) }),
221
222
  ...(xTypeChanged && requestedXScale.type === undefined
222
223
  ? { type: x.fieldType === "temporal" ? "time" : "linear" }
223
224
  : {})
@@ -233,11 +234,11 @@ const editHorizon = action(
233
234
  if (requestedYScale.id !== undefined && requestedYScale.id !== currentY.id) {
234
235
  throw new Error("editHorizon y scale cannot change its id.");
235
236
  }
236
- const nextY = resolveFoldedHorizonScaleOptions(layer.id, {
237
- ...currentY,
238
- ...requestedYScale,
239
- id: currentY.id
240
- });
237
+ const nextY = resolveFoldedHorizonScaleOptions(
238
+ layer.id,
239
+ { ...requestedYScale, id: currentY.id },
240
+ currentY
241
+ );
241
242
  next = applyScaleDefinition(next, currentY, nextY);
242
243
 
243
244
  const colors = resolveHorizonPaletteColors(candidate);
@@ -314,7 +315,7 @@ const encodeHorizon = action(
314
315
  : {})
315
316
  };
316
317
  if (Array.isArray(xScale.domain) && xScale.domain.length > 2) {
317
- xScale.domain = [Math.min(...xScale.domain), Math.max(...xScale.domain)];
318
+ xScale.domain = numericExtent(xScale.domain);
318
319
  }
319
320
  const yScale = resolveFoldedHorizonScaleOptions(layer.id, y.scale);
320
321
  const colors = resolveHorizonPaletteColors(transform);
@@ -1,18 +1,18 @@
1
- import { registerAppearanceEncodingAction } from "./appearance.js";
2
- import { registerAngleEncodingAction } from "./angle.js";
3
- import { registerBarWidthEncodingAction } from "./barWidth.js";
4
- import { registerColorEncodingAction } from "./color/index.js";
5
1
  import { registerDensityEncodingAction } from "./density.js";
6
- import { registerHistogramEncodingAction } from "./histogram.js";
7
- import { registerHorizonEncodingAction } from "./horizon.js";
8
- import { registerOffsetEncodingAction } from "./offset.js";
9
2
  import { registerPathOrderEncodingActions } from "./pathOrder.js";
3
+ import { registerHorizonEncodingAction } from "./horizon.js";
4
+ import { registerHistogramEncodingAction } from "./histogram.js";
10
5
  import { registerPositionEncodingActions } from "./position/index.js";
11
6
  import { registerRangedEncodingActions } from "./ranged.js";
7
+ import { registerAngleEncodingAction } from "./angle.js";
8
+ import { registerOffsetEncodingAction } from "./offset.js";
9
+ import { registerParallelEncodingAction } from "./parallel.js";
10
+ import { registerColorEncodingAction } from "./color/index.js";
11
+ import { registerTextEncodingAction } from "./text.js";
12
+ import { registerAppearanceEncodingAction } from "./appearance.js";
12
13
  import { registerStrokeDashEncodingActions } from "./strokeDash.js";
13
14
  import { registerRuleAppearanceEncodingActions } from "./ruleAppearance.js";
14
- import { registerTextEncodingAction } from "./text.js";
15
- import { registerParallelEncodingAction } from "./parallel.js";
15
+ import { registerBarWidthEncodingAction } from "./barWidth.js";
16
16
  import { registerEncodingRemovalAction } from "./remove.js";
17
17
 
18
18
  export function registerEncodingActions(ProgramClass) {
@@ -6,8 +6,10 @@ import {
6
6
  import { resolveOffsetScaleDefinition } from "../scales/definitions.js";
7
7
  import {
8
8
  applyEncodingScale,
9
+ rematerializeEncoding,
9
10
  resolveReassignmentScaleOptions,
10
11
  resolveTarget,
12
+ setEncodingProperties,
11
13
  validateOptions
12
14
  } from "./shared.js";
13
15
  import {
@@ -16,8 +18,6 @@ import {
16
18
  resolveBarGrain
17
19
  } from "../../grammar/bars/policy.js";
18
20
  import { normalizeOffsetPadding } from "../../grammar/bars/geometry.js";
19
- import { applyMaterializationPlan } from "../../materialization/dependencies.js";
20
- import { planEncodingRematerialization } from "../../materialization/encodings.js";
21
21
 
22
22
  const ENCODING_OPTIONS = Object.freeze([
23
23
  "field", "target", "fieldType", "scale", "paddingInner", "paddingOuter"
@@ -36,27 +36,39 @@ function createOffsetEncoding(channel) {
36
36
  const { id: target, dataset, layer } = resolveTarget(
37
37
  this,
38
38
  args.target,
39
- ["bar"],
40
- "bar mark"
39
+ ["bar", "point", "rule"],
40
+ "offset-compatible mark"
41
41
  );
42
42
 
43
- const channels = resolveBarChannels(layer);
44
- if (
45
- resolveBarGrain(layer) === undefined ||
46
- channels?.category !== parentChannel
47
- ) {
48
- throw new Error(
49
- `${operation} requires a complete bar with a ${parentChannel} category encoding.`
50
- );
51
- }
52
- if (
53
- layer.encoding?.color !== undefined &&
54
- (resolveBarColorLayout(layer) !== "group" ||
55
- layer.encoding.color.field !== args.field)
56
- ) {
57
- throw new Error(
58
- `${operation} field must match a grouped bar color field.`
59
- );
43
+ if (layer.mark.type === "bar") {
44
+ const channels = resolveBarChannels(layer);
45
+ if (
46
+ resolveBarGrain(layer) === undefined ||
47
+ channels?.category !== parentChannel
48
+ ) {
49
+ throw new Error(
50
+ `${operation} requires a complete bar with a ${parentChannel} category encoding.`
51
+ );
52
+ }
53
+ if (
54
+ layer.encoding?.color !== undefined &&
55
+ (resolveBarColorLayout(layer) !== "group" ||
56
+ layer.encoding.color.field !== args.field)
57
+ ) {
58
+ throw new Error(
59
+ `${operation} field must match a grouped bar color field.`
60
+ );
61
+ }
62
+ } else {
63
+ const parent = layer.encoding?.[parentChannel];
64
+ if (
65
+ parent === undefined ||
66
+ !["nominal", "ordinal"].includes(parent.fieldType)
67
+ ) {
68
+ throw new Error(
69
+ `${operation} requires a ${parentChannel} category encoding (categorical position).`
70
+ );
71
+ }
60
72
  }
61
73
 
62
74
  readNominalField(dataset.values, args.field);
@@ -67,7 +79,7 @@ function createOffsetEncoding(channel) {
67
79
  const scale = resolveOffsetScaleDefinition(this, requestedScale, channel);
68
80
  if (Object.hasOwn(scale, "unknown")) {
69
81
  throw new Error(
70
- `${channel} scale unknown is not supported for grouped bars.`
82
+ `${channel} scale unknown is not supported for offset positions.`
71
83
  );
72
84
  }
73
85
  const padding = normalizeOffsetPadding(
@@ -76,39 +88,23 @@ function createOffsetEncoding(channel) {
76
88
  channel
77
89
  );
78
90
 
79
- let next = this
80
- .editSemantic({
81
- property: `layer[${target}].encoding.${channel}.field`,
82
- value: args.field
83
- })
84
- .editSemantic({
85
- property: `layer[${target}].encoding.${channel}.fieldType`,
86
- value: fieldType
87
- })
88
- .editSemantic({
89
- property: `layer[${target}].encoding.${channel}.scale`,
90
- value: scale.id
91
- })
92
- ._withMarkConfig(target, {
91
+ let next = setEncodingProperties(this, target, channel, {
92
+ field: args.field,
93
+ fieldType,
94
+ scale: scale.id
95
+ })._withMarkConfig(target, {
93
96
  ...this.markConfigs[target],
94
97
  [channel]: padding
95
98
  });
96
99
  next = applyEncodingScale(next, scale, requestedScale, {
97
100
  reassignment: layer.encoding?.[channel]?.scale === scale.id
98
101
  });
99
- if (layer.encoding?.color === undefined) {
102
+ if (layer.mark.type === "bar" && layer.encoding?.color === undefined) {
100
103
  return next
101
104
  .rematerializeScale({ id: scale.id })
102
105
  .editGraphics({ target, property: "length", value: 0 });
103
106
  }
104
- return applyMaterializationPlan(
105
- next,
106
- planEncodingRematerialization(next, {
107
- target,
108
- channel,
109
- scale: scale.id
110
- })
111
- );
107
+ return rematerializeEncoding(next, target, channel, scale.id);
112
108
  }
113
109
  );
114
110
  }
@@ -7,7 +7,11 @@ import {
7
7
  validateParallelRows
8
8
  } from "../../grammar/parallelCoordinates.js";
9
9
  import { resolvePositionScaleDefinition } from "../scales/definitions.js";
10
- import { applyEncodingScale, resolveTarget } from "./shared.js";
10
+ import {
11
+ applyEncodingScale,
12
+ resolveTarget,
13
+ setEncodingProperties
14
+ } from "./shared.js";
11
15
  import { resolveParallelCoordinate } from "../coordinates/parallel.js";
12
16
 
13
17
  const OPTIONS = Object.freeze([
@@ -76,15 +80,10 @@ export const encodeParallelCoordinates = action(
76
80
  value: coordinate.id
77
81
  });
78
82
  }
79
- next = next
80
- .editSemantic({
81
- property: `layer[${target}].encoding.parallel.dimensions`,
82
- value: storedDimensions
83
- })
84
- .editSemantic({
85
- property: `layer[${target}].encoding.parallel.missing`,
86
- value: missing
87
- });
83
+ next = setEncodingProperties(next, target, "parallel", {
84
+ dimensions: storedDimensions,
85
+ missing
86
+ });
88
87
  if (key === undefined && layer.encoding?.parallel?.key !== undefined) {
89
88
  next = next.editSemantic({
90
89
  property: `layer[${target}].encoding.parallel.key`,
@@ -15,6 +15,7 @@ import {
15
15
  canMaterializeLine
16
16
  } from "../../materialization/marks/index.js";
17
17
  import { findLayer } from "../../selectors/layers.js";
18
+ import { setEncodingProperties } from "./shared.js";
18
19
 
19
20
  const ENCODE_OPTIONS = Object.freeze([
20
21
  "target", "field", "fieldType", "order"
@@ -92,19 +93,11 @@ const encodePathOrder = action(
92
93
  const dataset = assertPathOrderCompatible(this, layer, "encodePathOrder");
93
94
  readQuantitativeField(dataset.values, field);
94
95
 
95
- const next = this
96
- .editSemantic({
97
- property: `layer[${layer.id}].encoding.pathOrder.field`,
98
- value: field
99
- })
100
- .editSemantic({
101
- property: `layer[${layer.id}].encoding.pathOrder.fieldType`,
102
- value: fieldType
103
- })
104
- .editSemantic({
105
- property: `layer[${layer.id}].encoding.pathOrder.order`,
106
- value: order
107
- });
96
+ const next = setEncodingProperties(this, layer.id, "pathOrder", {
97
+ field,
98
+ fieldType,
99
+ order
100
+ });
108
101
  return rematerializePath(next, findLayer(next, layer.id));
109
102
  }
110
103
  );
@@ -80,6 +80,28 @@ function applyArcThetaWeight(program, target, channel, previous, aggregate, weig
80
80
  });
81
81
  }
82
82
 
83
+ function removeStaleArcThetaAggregate(
84
+ program,
85
+ target,
86
+ channel,
87
+ previous,
88
+ fieldType,
89
+ aggregate
90
+ ) {
91
+ if (
92
+ channel !== "theta" ||
93
+ fieldType !== "quantitative" ||
94
+ aggregate !== undefined ||
95
+ previous?.aggregate === undefined
96
+ ) {
97
+ return program;
98
+ }
99
+ return program.editSemantic({
100
+ property: `layer[${target}].encoding.theta.aggregate`,
101
+ remove: true
102
+ });
103
+ }
104
+
83
105
  export function applyPositionSemantics(program, {
84
106
  target,
85
107
  channel,
@@ -124,6 +146,14 @@ export function applyPositionSemantics(program, {
124
146
  value
125
147
  });
126
148
  }
149
+ next = removeStaleArcThetaAggregate(
150
+ next,
151
+ target,
152
+ channel,
153
+ previous,
154
+ fieldType,
155
+ aggregate
156
+ );
127
157
  return applyArcThetaWeight(
128
158
  next,
129
159
  target,
@@ -2,7 +2,7 @@ import { validateAggregate } from "../../../../grammar/aggregate.js";
2
2
  import { validateNonEmptyString } from "../../../../core/validation.js";
3
3
  import { emptyPositionPolicy } from "./common.js";
4
4
 
5
- export function resolveArcPositionPolicy({ channel, args, fieldType }) {
5
+ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
6
6
  if (args.bin !== undefined) {
7
7
  throw new Error(`Arc ${channel} encoding does not support bin.`);
8
8
  }
@@ -10,6 +10,11 @@ export function resolveArcPositionPolicy({ channel, args, fieldType }) {
10
10
  throw new Error(`Arc ${channel} encoding does not support stack.`);
11
11
  }
12
12
  if (channel === "radius") {
13
+ if (layer.encoding?.theta?.fieldType === "quantitative") {
14
+ throw new Error(
15
+ "Arc radius encoding cannot be combined with quantitative theta."
16
+ );
17
+ }
13
18
  if (args.aggregate !== undefined) {
14
19
  throw new Error("Arc radius encoding does not support aggregate.");
15
20
  }
@@ -18,8 +23,24 @@ export function resolveArcPositionPolicy({ channel, args, fieldType }) {
18
23
  }
19
24
  return emptyPositionPolicy();
20
25
  }
26
+ if (fieldType === "quantitative") {
27
+ if (layer.encoding?.radius !== undefined) {
28
+ throw new Error(
29
+ "Quantitative arc theta cannot be combined with radius encoding."
30
+ );
31
+ }
32
+ if (args.aggregate !== undefined) {
33
+ throw new Error("Quantitative arc theta does not support aggregate.");
34
+ }
35
+ if (args.weight !== undefined) {
36
+ throw new Error("Quantitative arc theta does not support weight.");
37
+ }
38
+ return emptyPositionPolicy();
39
+ }
21
40
  if (!["ordinal", "nominal"].includes(fieldType)) {
22
- throw new Error("Arc theta encoding requires an ordinal or nominal field.");
41
+ throw new Error(
42
+ "Arc theta encoding requires a quantitative, ordinal, or nominal field."
43
+ );
23
44
  }
24
45
  if (args.aggregate === undefined) {
25
46
  if (args.weight !== undefined) {
@@ -1,15 +1,15 @@
1
- import { validatePositionFieldCompatibility } from
2
- "../../../../grammar/positionCompatibility.js";
1
+ import { resolveTickPositionPolicy } from "./tick.js";
3
2
  import {
4
3
  isCategoricalDensityPosition,
5
4
  resolveAreaPositionPolicy
6
5
  } from "./area.js";
7
- import { resolveArcPositionPolicy } from "./arc.js";
8
6
  import { resolveBarPositionPolicy } from "./bar.js";
9
7
  import { resolveLinePositionPolicy } from "./line.js";
10
8
  import { resolvePointPositionPolicy } from "./point.js";
11
9
  import { resolveRulePositionPolicy } from "./rule.js";
12
- import { resolveTickPositionPolicy } from "./tick.js";
10
+ import { resolveArcPositionPolicy } from "./arc.js";
11
+ import { validatePositionFieldCompatibility } from
12
+ "../../../../grammar/positionCompatibility.js";
13
13
 
14
14
  const POSITION_POLICIES = Object.freeze({
15
15
  area: resolveAreaPositionPolicy,
@@ -2,17 +2,20 @@ import {
2
2
  validateAggregate,
3
3
  validateAggregateFieldType
4
4
  } from "../../../../grammar/aggregate.js";
5
- import { emptyPositionPolicy } from "./common.js";
5
+ import { emptyPositionPolicy, resolveBin } from "./common.js";
6
6
 
7
- function rejectSharedOptions(args, channel) {
8
- if (args.bin !== undefined) {
9
- throw new Error(`Line ${channel} encoding does not support bin.`);
10
- }
7
+ function rejectStack(args, channel) {
11
8
  if (args.stack !== undefined) {
12
9
  throw new Error(`Line ${channel} encoding does not support stack.`);
13
10
  }
14
11
  }
15
12
 
13
+ function rejectBin(args, channel) {
14
+ if (args.bin !== undefined) {
15
+ throw new Error(`Line ${channel} encoding does not support bin.`);
16
+ }
17
+ }
18
+
16
19
  export function resolveLinePositionPolicy({
17
20
  program,
18
21
  layer,
@@ -27,7 +30,8 @@ export function resolveLinePositionPolicy({
27
30
  if (config.curve !== undefined && config.curve !== "linear") {
28
31
  throw new Error("Polar line position currently requires curve \"linear\".");
29
32
  }
30
- rejectSharedOptions(args, channel);
33
+ rejectBin(args, channel);
34
+ rejectStack(args, channel);
31
35
  if (args.aggregate !== undefined) {
32
36
  throw new Error(`Line ${channel} encoding does not support aggregate.`);
33
37
  }
@@ -44,6 +48,26 @@ export function resolveLinePositionPolicy({
44
48
  );
45
49
 
46
50
  if (channel === "x") {
51
+ rejectStack(args, "x");
52
+ if (args.aggregate !== undefined) {
53
+ throw new Error("Line x encoding does not support aggregate.");
54
+ }
55
+ if (args.bin !== undefined) {
56
+ if (fieldType !== "quantitative") {
57
+ throw new Error("Binned line x encoding requires a quantitative field.");
58
+ }
59
+ if (
60
+ layer.encoding?.y !== undefined &&
61
+ layer.encoding.y.aggregate === undefined
62
+ ) {
63
+ throw new Error("Binned line x encoding requires an aggregate y encoding.");
64
+ }
65
+ return {
66
+ bin: resolveBin(args.bin),
67
+ aggregate: undefined,
68
+ stack: undefined
69
+ };
70
+ }
47
71
  const directPair =
48
72
  layer.encoding?.y?.aggregate === undefined &&
49
73
  ["quantitative", "temporal"].includes(layer.encoding?.y?.fieldType) &&
@@ -61,17 +85,18 @@ export function resolveLinePositionPolicy({
61
85
  "Line x encoding requires a temporal field or a compatible derived quantitative field."
62
86
  );
63
87
  }
64
- if (args.aggregate !== undefined) {
65
- throw new Error("Line x encoding does not support aggregate.");
66
- }
67
- rejectSharedOptions(args, "x");
68
88
  return emptyPositionPolicy();
69
89
  }
70
90
 
71
- rejectSharedOptions(args, "y");
91
+ rejectBin(args, "y");
92
+ rejectStack(args, "y");
93
+ if (layer.encoding?.x?.bin !== undefined && args.aggregate === undefined) {
94
+ throw new Error("Binned line x encoding requires an aggregate y encoding.");
95
+ }
72
96
  const quantitativePair =
73
97
  fieldType === "quantitative" &&
74
- layer.encoding?.x?.fieldType === "quantitative";
98
+ layer.encoding?.x?.fieldType === "quantitative" &&
99
+ layer.encoding.x.bin === undefined;
75
100
  const prospectiveDirect =
76
101
  args.aggregate === undefined &&
77
102
  (fieldType === "temporal" ||
@@ -98,11 +123,16 @@ export function resolveLinePositionPolicy({
98
123
  return emptyPositionPolicy();
99
124
  }
100
125
 
101
- if (
102
- layer.encoding?.x !== undefined &&
103
- layer.encoding.x.fieldType !== "temporal"
104
- ) {
105
- throw new Error("Aggregate line y encoding requires a temporal x encoding.");
126
+ if (layer.encoding?.x !== undefined) {
127
+ const compatibleAggregateX =
128
+ layer.encoding.x.fieldType === "temporal" ||
129
+ (layer.encoding.x.fieldType === "quantitative" &&
130
+ layer.encoding.x.bin !== undefined);
131
+ if (!compatibleAggregateX) {
132
+ throw new Error(
133
+ "Aggregate line y encoding requires a temporal or binned quantitative x encoding."
134
+ );
135
+ }
106
136
  }
107
137
 
108
138
  const aggregate = validateAggregate(args.aggregate);
@@ -1,6 +1,7 @@
1
1
  import { validateUserId } from "../../../core/identifiers.js";
2
2
  import { getPositionCoordinateDefaults } from "../../../grammar/coordinates.js";
3
3
  import {
4
+ isNominalValue,
4
5
  readNominalField,
5
6
  readQuantitativeField,
6
7
  readScaleField,
@@ -21,7 +22,10 @@ import {
21
22
  validateAggregateFieldValues,
22
23
  } from "../../../grammar/aggregate.js";
23
24
  import { normalizeRuleDatum } from "../../../grammar/rules.js";
24
- import { readArcThetaWeights } from "../../../grammar/arcs.js";
25
+ import {
26
+ readArcThetaValues,
27
+ readArcThetaWeights
28
+ } from "../../../grammar/arcs.js";
25
29
  import { resolveMarkPositionPolicy } from "./policies/index.js";
26
30
  import {
27
31
  getPositionChannelDefinition,
@@ -33,6 +37,12 @@ const POSITION_ENCODING_OPTIONS = Object.freeze([
33
37
  "aggregate", "bin", "stack", "weight"
34
38
  ]);
35
39
 
40
+ function inferRuleDatumFieldType(datum, operation) {
41
+ if (Number.isFinite(datum)) return "quantitative";
42
+ if (isNominalValue(datum)) return "nominal";
43
+ throw new Error(`${operation} datum requires an explicit fieldType.`);
44
+ }
45
+
36
46
  function validateCoordinateFamily(layer, channel, operation) {
37
47
  const family = getPositionChannelDefinition(channel).family;
38
48
  const incompatible = positionChannelsForFamily(
@@ -96,7 +106,7 @@ export function resolvePositionEncoding(program, channel, args, operation) {
96
106
  if (hasField === hasDatum) {
97
107
  throw new Error(`${operation} requires exactly one of field or datum for a rule mark.`);
98
108
  }
99
- if (args.fieldType === undefined) {
109
+ if (hasField && args.fieldType === undefined) {
100
110
  throw new Error(`${operation} requires fieldType for a rule mark.`);
101
111
  }
102
112
  } else if (hasDatum) {
@@ -104,10 +114,12 @@ export function resolvePositionEncoding(program, channel, args, operation) {
104
114
  }
105
115
  const previous = layer.encoding?.[channel];
106
116
  const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
107
- layer.mark.type === "arc" && channel === "theta" &&
108
- ["count", "sum"].includes(args.aggregate)
109
- ? "nominal"
110
- : "quantitative"
117
+ layer.mark.type === "rule" && hasDatum
118
+ ? inferRuleDatumFieldType(args.datum, operation)
119
+ : layer.mark.type === "arc" && channel === "theta" &&
120
+ ["count", "sum"].includes(args.aggregate)
121
+ ? "nominal"
122
+ : "quantitative"
111
123
  );
112
124
  const fieldType = requestedFieldType === "nominal"
113
125
  ? "nominal"
@@ -119,9 +131,14 @@ export function resolvePositionEncoding(program, channel, args, operation) {
119
131
  : args.field;
120
132
  const datum = args.datum;
121
133
  const effectiveArgs = { ...args };
134
+ const directQuantitativeArcTheta =
135
+ layer.mark.type === "arc" &&
136
+ channel === "theta" &&
137
+ fieldType === "quantitative";
122
138
  for (const property of ["aggregate", "bin", "stack"]) {
123
139
  if (!Object.hasOwn(effectiveArgs, property) && previous !== undefined &&
124
- Object.hasOwn(previous, property)) {
140
+ Object.hasOwn(previous, property) &&
141
+ !(directQuantitativeArcTheta && property === "aggregate")) {
125
142
  effectiveArgs[property] = previous[property];
126
143
  }
127
144
  }
@@ -169,6 +186,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
169
186
  : fieldType === "temporal"
170
187
  ? { nice: true }
171
188
  : { discreteType: "band" }
189
+ : layer.mark.type === "line" && channel === "x" && policy.bin !== undefined
190
+ ? { nice: true, zero: false }
172
191
  : ["ordinal", "nominal"].includes(fieldType)
173
192
  ? {
174
193
  discreteType: ["arc", "rect"].includes(layer.mark.type)
@@ -177,6 +196,14 @@ export function resolvePositionEncoding(program, channel, args, operation) {
177
196
  }
178
197
  : {}
179
198
  );
199
+ if (
200
+ layer.mark.type === "bar" &&
201
+ program.markConfigs[target]?.boxPlot === undefined &&
202
+ scale.type === "log" &&
203
+ policy.bin === undefined
204
+ ) {
205
+ throw new Error('Bar scale type "log" does not support zero baselines.');
206
+ }
180
207
  if (
181
208
  layer.mark.type === "arc" &&
182
209
  channel === "theta" &&
@@ -201,6 +228,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
201
228
  policy.aggregate === "sum"
202
229
  ) {
203
230
  readArcThetaWeights(dataset.values, policy.weight, layer.id);
231
+ } else if (directQuantitativeArcTheta) {
232
+ readArcThetaValues(dataset.values, field, layer.id);
204
233
  } else if (program.markConfigs[target]?.boxPlot !== undefined) {
205
234
  for (const [index, row] of dataset.values.entries()) {
206
235
  const value = row[field];