ggaction 0.0.9 → 0.0.11

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 (222) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +5 -4
  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/extension-authoring.md +248 -0
  7. package/knowledge/intent-taxonomy.json +1 -183
  8. package/knowledge/intent-taxonomy.schema.json +1 -0
  9. package/knowledge/mcp-resources.json +1 -95
  10. package/knowledge/mcp-resources.schema.json +1 -0
  11. package/knowledge/task-packet.schema.json +1 -159
  12. package/knowledge/task-resolver.js +1 -1230
  13. package/package.json +19 -2
  14. package/src/actions/boxPlots/edit.js +45 -298
  15. package/src/actions/boxPlots/options.js +46 -77
  16. package/src/actions/boxPlots/resolve.js +1 -1
  17. package/src/actions/charts/bar.js +3 -0
  18. package/src/actions/charts/heatmap.js +73 -83
  19. package/src/actions/charts/index.js +1 -1
  20. package/src/actions/charts/parallel.js +35 -1
  21. package/src/actions/charts/shared.js +20 -0
  22. package/src/actions/composition/actions.js +46 -49
  23. package/src/actions/data/bin2d.js +105 -2
  24. package/src/actions/data/box.js +17 -17
  25. package/src/actions/data/density.js +42 -78
  26. package/src/actions/data/gradientProfile.js +22 -52
  27. package/src/actions/data/horizon.js +16 -41
  28. package/src/actions/data/index.js +10 -10
  29. package/src/actions/data/interval.js +16 -38
  30. package/src/actions/data/intervalEdit.js +55 -0
  31. package/src/actions/data/regression.js +7 -17
  32. package/src/actions/data/shared.js +47 -0
  33. package/src/actions/data/timeUnit.js +14 -38
  34. package/src/actions/data/window.js +14 -38
  35. package/src/actions/distributions/revision.js +154 -34
  36. package/src/actions/encodings/angle.js +9 -10
  37. package/src/actions/encodings/appearance.js +17 -51
  38. package/src/actions/encodings/density/resolve.js +3 -1
  39. package/src/actions/encodings/density.js +1 -15
  40. package/src/actions/encodings/horizon/resolve.js +13 -3
  41. package/src/actions/encodings/horizon.js +8 -7
  42. package/src/actions/encodings/index.js +9 -9
  43. package/src/actions/encodings/offset.js +8 -24
  44. package/src/actions/encodings/parallel.js +9 -10
  45. package/src/actions/encodings/pathOrder.js +6 -13
  46. package/src/actions/encodings/position/policies/index.js +4 -4
  47. package/src/actions/encodings/position/policies/line.js +47 -17
  48. package/src/actions/encodings/position/resolve.js +24 -5
  49. package/src/actions/encodings/ranged.js +18 -23
  50. package/src/actions/encodings/remove.js +15 -25
  51. package/src/actions/encodings/ruleAppearance.js +9 -24
  52. package/src/actions/encodings/shared.js +37 -0
  53. package/src/actions/encodings/strokeDash.js +8 -23
  54. package/src/actions/encodings/text.js +9 -10
  55. package/src/actions/errorBands/create.js +105 -39
  56. package/src/actions/errorBands/edit.js +89 -93
  57. package/src/actions/errorBands/index.js +1 -2
  58. package/src/actions/errorBars/create.js +78 -27
  59. package/src/actions/errorBars/edit.js +81 -48
  60. package/src/actions/errorBars/index.js +1 -2
  61. package/src/actions/facets/actions.js +45 -69
  62. package/src/actions/facets/derive.js +38 -53
  63. package/src/actions/facets/replay.js +0 -1
  64. package/src/actions/gradientPlots/components.js +59 -48
  65. package/src/actions/gradientPlots/edit.js +35 -216
  66. package/src/actions/gradientPlots/index.js +11 -10
  67. package/src/actions/gradientPlots/materialize.js +21 -35
  68. package/src/actions/gradientPlots/options.js +5 -7
  69. package/src/actions/gradientPlots/paint.js +9 -6
  70. package/src/actions/gradientPlots/resolve.js +1 -1
  71. package/src/actions/guides/axes/index.js +3 -3
  72. package/src/actions/guides/axes/labels.js +68 -26
  73. package/src/actions/guides/axes/lines.js +22 -2
  74. package/src/actions/guides/axes/policy.js +28 -8
  75. package/src/actions/guides/axes/ticks.js +27 -9
  76. package/src/actions/guides/axes/titles.js +113 -65
  77. package/src/actions/guides/grids/resolve.js +14 -0
  78. package/src/actions/guides/guides.js +2 -4
  79. package/src/actions/guides/index.js +2 -2
  80. package/src/actions/guides/legends/categorical/actions.js +44 -68
  81. package/src/actions/guides/legends/categorical/components.js +6 -1
  82. package/src/actions/guides/legends/categorical/index.js +2 -2
  83. package/src/actions/guides/legends/categorical/layout.js +148 -175
  84. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  85. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  86. package/src/actions/guides/legends/continuous/common.js +117 -66
  87. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  88. package/src/actions/guides/legends/continuous/interval.js +97 -76
  89. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  90. package/src/actions/guides/legends/edit.js +14 -1
  91. package/src/actions/guides/legends/index.js +1 -1
  92. package/src/actions/guides/legends/lane.js +2 -1
  93. package/src/actions/guides/legends/remove.js +8 -0
  94. package/src/actions/guides/legends/size.js +36 -43
  95. package/src/actions/guides/legends/strokeWidth.js +20 -26
  96. package/src/actions/guides/polar/axes/labels.js +17 -1
  97. package/src/actions/guides/polar/axes/shared.js +7 -1
  98. package/src/actions/guides/polar/axes/titles.js +19 -2
  99. package/src/actions/guides/polar/grids.js +4 -0
  100. package/src/actions/guides/polar/resolve.js +6 -0
  101. package/src/actions/index.js +13 -13
  102. package/src/actions/intervals/resolve.js +87 -117
  103. package/src/actions/marks/arc/actions.js +31 -36
  104. package/src/actions/marks/area/actions.js +23 -31
  105. package/src/actions/marks/index.js +6 -6
  106. package/src/actions/marks/line/actions.js +33 -52
  107. package/src/actions/marks/line/materialize.js +7 -5
  108. package/src/actions/marks/point/materialize.js +5 -3
  109. package/src/actions/marks/remove.js +21 -0
  110. package/src/actions/marks/rule/actions.js +53 -53
  111. package/src/actions/marks/shared.js +8 -4
  112. package/src/actions/marks/text/actions.js +14 -6
  113. package/src/actions/marks/text/layout.js +21 -45
  114. package/src/actions/marks/tick/actions.js +22 -21
  115. package/src/actions/primitives/createGraphics.js +4 -0
  116. package/src/actions/primitives/editGraphics.js +4 -0
  117. package/src/actions/primitives/index.js +3 -3
  118. package/src/actions/primitives/semantic.js +1 -1
  119. package/src/actions/primitives/semanticValidation/index.js +2 -2
  120. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  121. package/src/actions/regression/edit.js +3 -43
  122. package/src/actions/scales/consumers/families.js +24 -4
  123. package/src/actions/scales/consumers/index.js +1 -1
  124. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  125. package/src/actions/scales/definitions.js +100 -103
  126. package/src/actions/scales/materialize.js +5 -0
  127. package/src/actions/selection/actions.js +79 -121
  128. package/src/actions/titles/actions.js +48 -94
  129. package/src/actions/titles/resolve.js +14 -27
  130. package/src/actions/violinPlots/create.js +37 -44
  131. package/src/core/action.js +20 -5
  132. package/src/core/extensionRegistry.js +104 -0
  133. package/src/core/textMetrics.js +74 -37
  134. package/src/core/validation.js +18 -0
  135. package/src/extension.js +8 -1
  136. package/src/grammar/aggregate.js +48 -28
  137. package/src/grammar/arcs.js +58 -11
  138. package/src/grammar/areaSeries.js +14 -16
  139. package/src/grammar/bin2d.js +37 -23
  140. package/src/grammar/boxPlot.js +14 -13
  141. package/src/grammar/categoricalDensity.js +14 -14
  142. package/src/grammar/categoryOrder.js +8 -14
  143. package/src/grammar/curveCommands.js +87 -20
  144. package/src/grammar/density.js +162 -141
  145. package/src/grammar/facets/index.js +14 -0
  146. package/src/grammar/facets/scales.js +7 -4
  147. package/src/grammar/gradientProfile.js +12 -15
  148. package/src/grammar/histogram.js +145 -83
  149. package/src/grammar/horizon.js +60 -23
  150. package/src/grammar/interval.js +14 -13
  151. package/src/grammar/lineSeries.js +91 -61
  152. package/src/grammar/numeric.js +290 -0
  153. package/src/grammar/paint.js +3 -4
  154. package/src/grammar/palettes.js +21 -32
  155. package/src/grammar/pathCommands.js +6 -0
  156. package/src/grammar/polarPaths.js +5 -5
  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/scales/color.js +35 -26
  161. package/src/grammar/scales/continuous.js +71 -24
  162. package/src/grammar/scales/definition.js +1 -1
  163. package/src/grammar/scales/discretized.js +42 -12
  164. package/src/grammar/scales/fields.js +21 -4
  165. package/src/grammar/scales/ordinal.js +117 -61
  166. package/src/grammar/scales/temporal.js +40 -11
  167. package/src/grammar/scales/transformed.js +199 -52
  168. package/src/grammar/schemas/graphicBounds.js +270 -79
  169. package/src/grammar/seriesLayout.js +50 -18
  170. package/src/grammar/ticks.js +123 -53
  171. package/src/grammar/timeUnit.js +18 -8
  172. package/src/grammar/transforms.js +4 -1
  173. package/src/grammar/window.js +70 -57
  174. package/src/index.js +1 -1
  175. package/src/layout/canvas.js +4 -8
  176. package/src/layout/composition.js +39 -40
  177. package/src/layout/facets.js +10 -14
  178. package/src/layout/labels.js +91 -15
  179. package/src/layout/legend.js +22 -20
  180. package/src/layout/legendLane.js +54 -40
  181. package/src/layout/title.js +7 -7
  182. package/src/materialization/bars/aggregate.js +14 -40
  183. package/src/materialization/bars/grouped.js +7 -18
  184. package/src/materialization/bars/histogram.js +2 -19
  185. package/src/materialization/bars/ranged.js +20 -15
  186. package/src/materialization/bars/resolve.js +21 -0
  187. package/src/materialization/facetGuides/legacyCategorical.js +2 -1
  188. package/src/materialization/facetGuides/placement.js +34 -19
  189. package/src/materialization/facetGuides/preparation.js +9 -5
  190. package/src/materialization/facets.js +39 -27
  191. package/src/materialization/guides/resources.js +2 -1
  192. package/src/materialization/marks/capabilities.js +5 -2
  193. package/src/materialization/marks/index.js +24 -4
  194. package/src/materialization/scales/policies/bar.js +61 -59
  195. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  196. package/src/materialization/scales/policies/series.js +13 -7
  197. package/src/materialization/scales/resolve.js +4 -5
  198. package/src/materialization/selection/items/arc.js +16 -2
  199. package/src/materialization/selection/items/bar.js +4 -2
  200. package/src/materialization/selection/items/common.js +21 -1
  201. package/src/materialization/selection/items/path.js +19 -2
  202. package/src/materialization/selection/items/point.js +4 -16
  203. package/src/materialization/selection/items/tick.js +13 -21
  204. package/src/materialization/selection/point.js +11 -4
  205. package/src/materialization/selection/styles.js +19 -27
  206. package/src/materialization/text.js +4 -1
  207. package/src/mcp/adapter.js +8 -3
  208. package/src/renderers/canvas/fill.js +2 -2
  209. package/src/renderers/canvas/index.js +35 -2
  210. package/src/renderers/canvas/native.js +160 -0
  211. package/src/renderers/pdf.js +14 -0
  212. package/src/renderers/svg.js +37 -4
  213. package/types/extension.d.ts +19 -0
  214. package/types/index.d.ts +3 -0
  215. package/types/program.d.ts +509 -131
  216. package/types/svg.d.ts +1 -0
  217. package/src/actions/errorBands/components.js +0 -81
  218. package/src/actions/errorBands/options.js +0 -39
  219. package/src/actions/errorBands/resolve.js +0 -28
  220. package/src/actions/errorBars/components.js +0 -62
  221. package/src/actions/errorBars/options.js +0 -58
  222. 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"
@@ -76,20 +76,11 @@ function createOffsetEncoding(channel) {
76
76
  channel
77
77
  );
78
78
 
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, {
79
+ let next = setEncodingProperties(this, target, channel, {
80
+ field: args.field,
81
+ fieldType,
82
+ scale: scale.id
83
+ })._withMarkConfig(target, {
93
84
  ...this.markConfigs[target],
94
85
  [channel]: padding
95
86
  });
@@ -101,14 +92,7 @@ function createOffsetEncoding(channel) {
101
92
  .rematerializeScale({ id: scale.id })
102
93
  .editGraphics({ target, property: "length", value: 0 });
103
94
  }
104
- return applyMaterializationPlan(
105
- next,
106
- planEncodingRematerialization(next, {
107
- target,
108
- channel,
109
- scale: scale.id
110
- })
111
- );
95
+ return rematerializeEncoding(next, target, channel, scale.id);
112
96
  }
113
97
  );
114
98
  }
@@ -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
  );
@@ -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,
@@ -33,6 +34,12 @@ const POSITION_ENCODING_OPTIONS = Object.freeze([
33
34
  "aggregate", "bin", "stack", "weight"
34
35
  ]);
35
36
 
37
+ function inferRuleDatumFieldType(datum, operation) {
38
+ if (Number.isFinite(datum)) return "quantitative";
39
+ if (isNominalValue(datum)) return "nominal";
40
+ throw new Error(`${operation} datum requires an explicit fieldType.`);
41
+ }
42
+
36
43
  function validateCoordinateFamily(layer, channel, operation) {
37
44
  const family = getPositionChannelDefinition(channel).family;
38
45
  const incompatible = positionChannelsForFamily(
@@ -96,7 +103,7 @@ export function resolvePositionEncoding(program, channel, args, operation) {
96
103
  if (hasField === hasDatum) {
97
104
  throw new Error(`${operation} requires exactly one of field or datum for a rule mark.`);
98
105
  }
99
- if (args.fieldType === undefined) {
106
+ if (hasField && args.fieldType === undefined) {
100
107
  throw new Error(`${operation} requires fieldType for a rule mark.`);
101
108
  }
102
109
  } else if (hasDatum) {
@@ -104,10 +111,12 @@ export function resolvePositionEncoding(program, channel, args, operation) {
104
111
  }
105
112
  const previous = layer.encoding?.[channel];
106
113
  const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
107
- layer.mark.type === "arc" && channel === "theta" &&
108
- ["count", "sum"].includes(args.aggregate)
109
- ? "nominal"
110
- : "quantitative"
114
+ layer.mark.type === "rule" && hasDatum
115
+ ? inferRuleDatumFieldType(args.datum, operation)
116
+ : layer.mark.type === "arc" && channel === "theta" &&
117
+ ["count", "sum"].includes(args.aggregate)
118
+ ? "nominal"
119
+ : "quantitative"
111
120
  );
112
121
  const fieldType = requestedFieldType === "nominal"
113
122
  ? "nominal"
@@ -169,6 +178,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
169
178
  : fieldType === "temporal"
170
179
  ? { nice: true }
171
180
  : { discreteType: "band" }
181
+ : layer.mark.type === "line" && channel === "x" && policy.bin !== undefined
182
+ ? { nice: true, zero: false }
172
183
  : ["ordinal", "nominal"].includes(fieldType)
173
184
  ? {
174
185
  discreteType: ["arc", "rect"].includes(layer.mark.type)
@@ -177,6 +188,14 @@ export function resolvePositionEncoding(program, channel, args, operation) {
177
188
  }
178
189
  : {}
179
190
  );
191
+ if (
192
+ layer.mark.type === "bar" &&
193
+ program.markConfigs[target]?.boxPlot === undefined &&
194
+ scale.type === "log" &&
195
+ policy.bin === undefined
196
+ ) {
197
+ throw new Error('Bar scale type "log" does not support zero baselines.');
198
+ }
180
199
  if (
181
200
  layer.mark.type === "arc" &&
182
201
  channel === "theta" &&
@@ -11,11 +11,13 @@ import {
11
11
  import { normalizeRuleDatum } from "../../grammar/rules.js";
12
12
  import {
13
13
  canMaterializeArea,
14
- canMaterializeLine
14
+ canMaterializeLine,
15
+ getPositionEncodingMaterializationSteps
15
16
  } from "../../materialization/marks/index.js";
16
17
  import { findLayer } from "../../selectors/layers.js";
17
18
  import {
18
19
  resolveTarget,
20
+ setEncodingProperties,
19
21
  validateLineSeriesCompatibility,
20
22
  validateOptions
21
23
  } from "./shared.js";
@@ -138,28 +140,21 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
138
140
  });
139
141
  }
140
142
  }
141
- next = next
142
- .editSemantic({
143
- property: `layer[${target}].encoding.${channel}.${hasField ? "field" : "datum"}`,
144
- value: hasField ? args.field : args.datum
145
- })
146
- .editSemantic({
147
- property: `layer[${target}].encoding.${channel}.fieldType`,
148
- value: fieldType
149
- })
150
- .editSemantic({
151
- property: `layer[${target}].encoding.${channel}.scale`,
152
- value: primary.scale
153
- })
154
- .rematerializeScale({ id: primary.scale });
155
- if (rule) return next.rematerializeRuleMark({ id: target });
156
- return layer.mark.type === "bar"
157
- ? next.rematerializeBarMark({ id: target })
158
- : layer.mark.type === "rect"
159
- ? next.rematerializeRectMark({ id: target })
160
- : canMaterializeArea(next, findLayer(next, target))
161
- ? next.rematerializeAreaMark({ id: target })
162
- : next;
143
+ next = setEncodingProperties(next, target, channel, {
144
+ [hasField ? "field" : "datum"]: hasField ? args.field : args.datum,
145
+ fieldType,
146
+ scale: primary.scale
147
+ });
148
+
149
+ const updated = findLayer(next, target);
150
+ for (const step of getPositionEncodingMaterializationSteps(
151
+ next,
152
+ updated,
153
+ primary.scale
154
+ )) {
155
+ next = next[step.op](step.args);
156
+ }
157
+ return next;
163
158
  }
164
159
 
165
160
  const encodeX2 = action(
@@ -16,6 +16,7 @@ import {
16
16
  } from "../../materialization/marks/index.js";
17
17
  import { findLayer } from "../../selectors/layers.js";
18
18
  import { removeLegendKinds } from "../guides/legends/remove.js";
19
+ import { clearMarkGraphic } from "./shared.js";
19
20
 
20
21
  const OPTIONS = Object.freeze(["target", "channel"]);
21
22
  const REMOVABLE_CHANNELS = Object.freeze([
@@ -35,6 +36,10 @@ const SPECIALIZED_LEGEND_KIND = Object.freeze({
35
36
  opacity: Object.freeze(["opacity"]),
36
37
  strokeWidth: Object.freeze(["strokeWidth"])
37
38
  });
39
+ const LEGEND_OPTION_KEYS = Object.freeze([
40
+ "position", "align", "direction", "columns", "offset", "titlePosition",
41
+ "title", "symbol", "labels", "titleStyle", "itemGap", "border"
42
+ ]);
38
43
 
39
44
  function resolveTarget(program, requested, channel) {
40
45
  const candidates = program.semanticSpec.layers.filter(
@@ -178,22 +183,15 @@ function reconcileCategoricalLegend(program, target, channels) {
178
183
  return removeLegendKinds(program, [kind]);
179
184
  }
180
185
  const next = removeLegendKinds(program, [kind]);
181
- return next.createLegend({
182
- target,
183
- channels: remaining,
184
- position: config.position,
185
- align: config.align,
186
- direction: config.direction,
187
- ...(config.columns === undefined ? {} : { columns: config.columns }),
188
- offset: config.offset,
189
- titlePosition: config.titlePosition,
190
- ...(config.inferredTitle ? {} : { title: config.title }),
191
- symbol: config.symbol,
192
- labels: config.labels,
193
- titleStyle: config.titleStyle,
194
- itemGap: config.itemGap,
195
- border: config.border
196
- });
186
+ const options = { target, channels: remaining };
187
+ for (const property of LEGEND_OPTION_KEYS) {
188
+ if (
189
+ (property === "columns" && config[property] === undefined) ||
190
+ (property === "title" && config.inferredTitle)
191
+ ) continue;
192
+ options[property] = config[property];
193
+ }
194
+ return next.createLegend(options);
197
195
  }
198
196
 
199
197
  function cleanupLegends(program, target, channels) {
@@ -204,18 +202,10 @@ function cleanupLegends(program, target, channels) {
204
202
  return kinds.length === 0 ? next : removeLegendKinds(next, kinds);
205
203
  }
206
204
 
207
- function clearGraphic(program, target) {
208
- const graphic = program.graphicSpec.objects[target];
209
- if (graphic === undefined) return program;
210
- return graphic.type === "collection"
211
- ? program.editGraphics({ target, property: "items", value: [] })
212
- : program.editGraphics({ target, property: "length", value: 0 });
213
- }
214
-
215
205
  function rematerializeTarget(program, target) {
216
206
  const layer = findLayer(program, target);
217
207
  const step = getMarkMaterializationStep(program, layer);
218
- const baseline = clearGraphic(program, target);
208
+ const baseline = clearMarkGraphic(program, target);
219
209
  let next = step === undefined
220
210
  ? baseline
221
211
  : baseline[step.op](step.args);
@@ -7,12 +7,12 @@ import { readQuantitativeField } from "../../grammar/scales/index.js";
7
7
  import { resolveStrokeWidthScaleDefinition } from "../scales/definitions.js";
8
8
  import {
9
9
  applyEncodingScale,
10
+ rematerializeEncoding,
10
11
  resolveReassignmentScaleOptions,
11
12
  resolveTarget,
13
+ setEncodingProperties,
12
14
  validateOptions
13
15
  } from "./shared.js";
14
- import { applyMaterializationPlan } from "../../materialization/dependencies.js";
15
- import { planEncodingRematerialization } from "../../materialization/encodings.js";
16
16
  import { findLayer } from "../../selectors/layers.js";
17
17
 
18
18
  const STROKE_OPTIONS = Object.freeze(["target", "value"]);
@@ -91,31 +91,16 @@ const encodeStrokeWidth = action(
91
91
  const scale = resolveStrokeWidthScaleDefinition(this, requestedScale);
92
92
  const { strokeWidth, ...config } = this.markConfigs[id] ?? {};
93
93
  void strokeWidth;
94
- let next = this
95
- ._withMarkConfig(id, config)
96
- .editSemantic({
97
- property: `layer[${id}].encoding.strokeWidth.field`,
98
- value: args.field
99
- })
100
- .editSemantic({
101
- property: `layer[${id}].encoding.strokeWidth.fieldType`,
102
- value: fieldType
103
- })
104
- .editSemantic({
105
- property: `layer[${id}].encoding.strokeWidth.scale`,
106
- value: scale.id
107
- });
94
+ let next = setEncodingProperties(
95
+ this._withMarkConfig(id, config),
96
+ id,
97
+ "strokeWidth",
98
+ { field: args.field, fieldType, scale: scale.id }
99
+ );
108
100
  next = applyEncodingScale(next, scale, requestedScale, {
109
101
  reassignment: previous?.scale === scale.id
110
102
  });
111
- return applyMaterializationPlan(
112
- next,
113
- planEncodingRematerialization(next, {
114
- target: id,
115
- channel: "strokeWidth",
116
- scale: scale.id
117
- })
118
- );
103
+ return rematerializeEncoding(next, id, "strokeWidth", scale.id);
119
104
  }
120
105
  );
121
106