ggaction 0.0.18 → 0.0.19

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 (155) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +6 -2
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +1 -1
  7. package/src/actions/canvas/fitting.js +6 -2
  8. package/src/actions/charts/heatmap.js +4 -3
  9. package/src/actions/data/derived.js +11 -1
  10. package/src/actions/data/edit.js +2 -0
  11. package/src/actions/data/index.js +4 -0
  12. package/src/actions/data/regression.js +24 -16
  13. package/src/actions/data/regressionStatistics.js +73 -0
  14. package/src/actions/data/shared.js +5 -2
  15. package/src/actions/distributions/revision.js +24 -18
  16. package/src/actions/encodings/remove.js +11 -6
  17. package/src/actions/encodings/shared.js +26 -24
  18. package/src/actions/facets/actions.js +18 -7
  19. package/src/actions/facets/guides.js +2 -1
  20. package/src/actions/guides/axes/axes.js +30 -11
  21. package/src/actions/guides/axes/axis.js +58 -8
  22. package/src/actions/guides/axes/components.js +13 -13
  23. package/src/actions/guides/axes/edit.js +36 -24
  24. package/src/actions/guides/axes/labels.js +37 -27
  25. package/src/actions/guides/axes/lines.js +22 -20
  26. package/src/actions/guides/axes/remove.js +10 -6
  27. package/src/actions/guides/axes/tickGroups.js +8 -7
  28. package/src/actions/guides/axes/ticks.js +31 -22
  29. package/src/actions/guides/axes/titles.js +53 -60
  30. package/src/actions/guides/facadeAxes.js +33 -24
  31. package/src/actions/guides/facadeLegend.js +3 -3
  32. package/src/actions/guides/grids/grid.js +1 -1
  33. package/src/actions/guides/grids/resolve.js +48 -51
  34. package/src/actions/guides/legends/blocks.js +43 -19
  35. package/src/actions/guides/legends/categorical/actions.js +30 -7
  36. package/src/actions/guides/legends/categorical/components.js +3 -10
  37. package/src/actions/guides/legends/categorical/index.js +2 -0
  38. package/src/actions/guides/legends/categorical/layout.js +9 -13
  39. package/src/actions/guides/legends/categorical/options.js +8 -2
  40. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  41. package/src/actions/guides/legends/categorical/resolve.js +9 -2
  42. package/src/actions/guides/legends/categorical/symbols.js +22 -8
  43. package/src/actions/guides/legends/colorSize.js +110 -0
  44. package/src/actions/guides/legends/combine.js +37 -0
  45. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -1
  46. package/src/actions/guides/legends/continuous/common.js +1 -1
  47. package/src/actions/guides/legends/continuous/gradient.js +27 -13
  48. package/src/actions/guides/legends/continuous/opacity.js +16 -11
  49. package/src/actions/guides/legends/creation.js +2 -0
  50. package/src/actions/guides/legends/edit.js +1 -1
  51. package/src/actions/guides/legends/focused.js +6 -5
  52. package/src/actions/guides/legends/lane.js +1 -1
  53. package/src/actions/guides/legends/lifecycle.js +2 -1
  54. package/src/actions/guides/legends/remove.js +6 -3
  55. package/src/actions/guides/legends/size.js +16 -6
  56. package/src/actions/guides/legends/strokeWidth.js +2 -0
  57. package/src/actions/guides/legends/target.js +2 -79
  58. package/src/actions/guides/legends/transition.js +24 -5
  59. package/src/actions/guides/tickValues.js +8 -5
  60. package/src/actions/marks/bar/edit.js +23 -12
  61. package/src/actions/marks/bar/materialize.js +6 -1
  62. package/src/actions/marks/point/materialize.js +5 -1
  63. package/src/actions/marks/remove.js +10 -10
  64. package/src/actions/primitives/createGraphics.js +3 -2
  65. package/src/actions/primitives/editGraphics.js +15 -11
  66. package/src/actions/primitives/graphicProperties.js +1 -6
  67. package/src/actions/primitives/semanticAction.js +27 -1
  68. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  69. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  70. package/src/actions/regression/edit.js +15 -3
  71. package/src/actions/scales/channels.js +2 -2
  72. package/src/actions/scales/create.js +3 -3
  73. package/src/actions/scales/definitions.js +1 -1
  74. package/src/actions/scales/edit.js +1 -1
  75. package/src/actions/scales/editPolicy.js +3 -0
  76. package/src/actions/scales/patch.js +1 -1
  77. package/src/actions/scales/quantitativeColor.js +8 -3
  78. package/src/actions/theme/reconcile.js +50 -23
  79. package/src/core/compositionState.js +2 -2
  80. package/src/core/graphicProperties.js +1 -0
  81. package/src/core/programState.js +4 -0
  82. package/src/core/resourceReferences.js +2 -2
  83. package/src/core/validation.js +9 -0
  84. package/src/core/vocabulary.js +3 -0
  85. package/src/grammar/axisTextPlacement.js +1 -0
  86. package/src/grammar/colorSizeLegend.js +39 -0
  87. package/src/grammar/computed.js +23 -1
  88. package/src/grammar/datasetSchema.js +10 -2
  89. package/src/grammar/displayLabels.js +8 -0
  90. package/src/grammar/facets/guides.js +54 -31
  91. package/src/grammar/facets/legendIntent.js +65 -0
  92. package/src/grammar/guideDash.js +1 -0
  93. package/src/grammar/legendBlocks.js +1 -0
  94. package/src/grammar/legendBorder.js +9 -0
  95. package/src/grammar/legendClip.js +19 -0
  96. package/src/grammar/legendCombinations.js +45 -0
  97. package/src/grammar/legendGradient.js +9 -0
  98. package/src/grammar/legendGroups.js +1 -1
  99. package/src/grammar/legendOpacity.js +6 -0
  100. package/src/grammar/pointShapes.js +16 -2
  101. package/src/grammar/regression/derive.js +9 -1
  102. package/src/grammar/regression/models.js +60 -0
  103. package/src/grammar/regression/parameters.js +9 -1
  104. package/src/grammar/regression/statistics.js +64 -0
  105. package/src/grammar/scales/color.js +1 -1
  106. package/src/grammar/scales/colorConsumers.js +1 -1
  107. package/src/grammar/scales/definition.js +1 -1
  108. package/src/grammar/scales/types.js +1 -1
  109. package/src/grammar/schemas/concreteGraphic.js +3 -0
  110. package/src/grammar/schemas/graphic.js +3 -0
  111. package/src/grammar/schemas/graphicBounds.js +31 -7
  112. package/src/grammar/schemas/semanticPath.js +17 -2
  113. package/src/grammar/summary.js +1 -1
  114. package/src/grammar/timeUnit.js +6 -4
  115. package/src/grammar/transformTopology.js +1 -0
  116. package/src/grammar/transforms.js +11 -1
  117. package/src/inspectionDescriptors.js +1 -1
  118. package/src/layout/guideCollisions.js +9 -1
  119. package/src/layout/legendGroup.js +1 -1
  120. package/src/materialization/compositionSnapshot.js +2 -7
  121. package/src/materialization/coordinateBounds.js +17 -4
  122. package/src/materialization/facetGuides/edit.js +30 -0
  123. package/src/materialization/facetGuides/layout.js +1 -1
  124. package/src/materialization/facetGuides/owner.js +125 -0
  125. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  126. package/src/materialization/facetGuides/placement.js +52 -6
  127. package/src/materialization/facetGuides/preparation.js +7 -0
  128. package/src/materialization/facets.js +1 -1
  129. package/src/materialization/graphicHierarchy.js +5 -1
  130. package/src/materialization/graphicTranslation.js +73 -0
  131. package/src/materialization/guides/layout.js +1 -1
  132. package/src/materialization/guides/resources.js +1 -1
  133. package/src/materialization/legendBorder.js +28 -0
  134. package/src/materialization/legendClip.js +76 -0
  135. package/src/materialization/legendGroups.js +1 -1
  136. package/src/materialization/legends.js +1 -1
  137. package/src/materialization/scaleGuideDependencies.js +13 -14
  138. package/src/materialization/scales/map.js +3 -0
  139. package/src/materialization/scales/resolve.js +2 -0
  140. package/src/materialization/typography.js +10 -2
  141. package/src/persistence/validation.js +174 -1
  142. package/src/persistence.js +1 -1
  143. package/src/renderers/canvas/index.js +20 -10
  144. package/src/renderers/canvas/isolated.js +166 -0
  145. package/src/renderers/canvas/limits.js +3 -0
  146. package/src/renderers/canvas/rect.js +1 -1
  147. package/src/renderers/pdf.js +2 -2
  148. package/src/renderers/png.js +2 -2
  149. package/src/renderers/svg.js +10 -3
  150. package/src/selectors/axes.js +1 -0
  151. package/src/selectors/graphicCapacity.js +38 -0
  152. package/src/version.js +1 -1
  153. package/types/basic.d.ts +2 -0
  154. package/types/index.d.ts +5 -0
  155. package/types/program.d.ts +206 -61
@@ -18,20 +18,20 @@ import { validateFillPaint } from "../../../grammar/paint.js";
18
18
  import { validateItemMissing } from "../../../grammar/itemMissing.js";
19
19
 
20
20
  const EDIT_OPTIONS = Object.freeze([
21
- "target", "missing", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
21
+ "target", "missing", "blend", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
22
22
  ...STROKE_STYLE_PROPERTIES
23
23
  ]);
24
24
 
25
25
  export const editBarMark = /* @__PURE__ */ action(
26
26
  {
27
27
  op: "editBarMark",
28
- description: "Edit whole-bar fill, opacity, and outline appearance."
28
+ description: "Edit whole-bar fill, opacity, outline, and compositing appearance."
29
29
  },
30
30
  function (args = {}) {
31
31
  validateMarkOptions(args, EDIT_OPTIONS, "editBarMark");
32
32
  const styleDetails = requestedRectStyleDetails(args, "editBarMark");
33
33
  const changes = [
34
- "missing", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
34
+ "missing", "blend", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
35
35
  ...STROKE_STYLE_PROPERTIES
36
36
  ];
37
37
  if (!changes.some(key => Object.hasOwn(args, key))) {
@@ -66,13 +66,12 @@ export const editBarMark = /* @__PURE__ */ action(
66
66
  );
67
67
  }
68
68
 
69
- const semantic = Object.hasOwn(args, "missing")
70
- ? this.editSemantic({
71
- property: `layer[${layer.id}].mark.missing`,
72
- value: validateItemMissing(args.missing, "Bar missing")
73
- })
74
- : this;
75
- const config = { ...semantic.markConfigs[layer.id] };
69
+ if (Object.hasOwn(args, "blend") && !["normal", "difference"].includes(args.blend)) {
70
+ throw new Error("Bar blend must be normal or difference.");
71
+ }
72
+ const missing = Object.hasOwn(args, "missing")
73
+ ? validateItemMissing(args.missing, "Bar missing") : undefined;
74
+ const config = { ...this.markConfigs[layer.id] };
76
75
  const appearance = { ...config.barAppearance };
77
76
  Object.assign(appearance, styleDetails);
78
77
  if (Object.hasOwn(args, "fill")) {
@@ -106,10 +105,22 @@ export const editBarMark = /* @__PURE__ */ action(
106
105
  );
107
106
  }
108
107
 
109
- const next = semantic._withMarkConfig(layer.id, {
108
+ let semantic = missing === undefined ? this : this.editSemantic({
109
+ property: `layer[${layer.id}].mark.missing`, value: missing
110
+ });
111
+ if (Object.hasOwn(args, "blend")) {
112
+ if (args.blend === "difference") {
113
+ semantic = semantic.editSemantic({property: `layer[${layer.id}].mark.blend`, value: "difference"});
114
+ } else if (layer.mark.blend !== undefined) {
115
+ semantic = semantic.editSemantic({property: `layer[${layer.id}].mark.blend`, remove: true});
116
+ }
117
+ }
118
+ const editsAppearance = changes.some(key => key !== "missing" && key !== "blend" &&
119
+ Object.hasOwn(args, key));
120
+ const next = editsAppearance ? semantic._withMarkConfig(layer.id, {
110
121
  ...config,
111
122
  barAppearance: appearance
112
- });
123
+ }) : semantic;
113
124
  const materialized = canMaterializeBar(next, layer)
114
125
  ? next.rematerializeBarMark({ id: layer.id })
115
126
  : next;
@@ -1,3 +1,4 @@
1
+ import { findLayer } from "../../../selectors/layers.js";
1
2
  import { editGraphicProperties } from "../../primitives/graphicProperties.js";
2
3
  import { action } from "../../../core/action.js";
3
4
  import { validateUserId } from "../../../core/identifiers.js";
@@ -20,9 +21,13 @@ const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
20
21
 
21
22
  function editRectangles(program, id, rectangles) {
22
23
  const appearance = program.markConfigs[id]?.barAppearance ?? {};
24
+ const blend = findLayer(program, id)?.mark?.blend;
25
+ if (blend !== undefined) rectangles = rectangles.map(rect => ({...rect, blend}));
23
26
  const hasShapeStyle = RECT_STYLE_PROPERTIES
24
27
  .some(property => Object.hasOwn(appearance, property));
25
- if (hasShapeStyle) {
28
+ const previousBlend = program.graphicSpec.objects[id]?.items?.some(item =>
29
+ item.properties?.blend !== undefined);
30
+ if (hasShapeStyle || blend !== undefined || previousBlend) {
26
31
  const radius = appearance;
27
32
  return replaceGraphicItems(
28
33
  program,
@@ -1,3 +1,4 @@
1
+ import { validateGraphicItemLimit } from "../../../core/validation.js";
1
2
  import { editGraphicProperties } from "../../primitives/graphicProperties.js";
2
3
  import { action } from "../../../core/action.js";
3
4
  import { validateUserId } from "../../../core/identifiers.js";
@@ -29,6 +30,8 @@ import {
29
30
  } from "../../../theme/defaults.js";
30
31
  import { findDataset } from "../../../selectors/datasets.js";
31
32
  import { findLayer } from "../../../selectors/layers.js";
33
+ import { resolveGraphicItemCapacity } from "../../../selectors/graphicCapacity.js";
34
+ import { getMarkGraphicTypes } from "../../../core/vocabulary.js";
32
35
  import { canMaterializePoint } from "../../../materialization/marks/capabilities.js";
33
36
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
34
37
  import {
@@ -335,13 +338,14 @@ export const rematerializePointMark = /* @__PURE__ */ action(
335
338
  if (layer?.mark?.type !== "point") {
336
339
  throw new Error(`Unknown point mark "${id}".`);
337
340
  }
338
- if (!["circle", "rect", "path", "collection"].includes(graphic?.type)) {
341
+ if (!getMarkGraphicTypes("point").includes(graphic?.type)) {
339
342
  throw new Error(`Point mark "${id}" requires point graphics.`);
340
343
  }
341
344
  const sourceDataset = findDataset(resolved, layer.data);
342
345
  if (sourceDataset === undefined) {
343
346
  throw new Error(`Point mark "${id}" requires an existing dataset.`);
344
347
  }
348
+ validateGraphicItemLimit(sourceDataset.values.length, resolveGraphicItemCapacity(resolved, { id, label: "Point mark item count" }));
345
349
  const dataset = applyItemMissingPolicy(layer, sourceDataset);
346
350
 
347
351
  if (graphic.items.length === 0 && !canMaterializePoint(resolved, layer)) {
@@ -1,3 +1,4 @@
1
+ import { cartesianAxisOwners, axisTargetArgs } from "../../selectors/axes.js";
1
2
  import { ownedChildren, ownership } from "../../selectors/markOwners.js";
2
3
  import { isSourceOwnedText } from "../../grammar/text.js";
3
4
  import { closedAction } from "../../core/action.js";
@@ -183,16 +184,15 @@ function cleanupPositionGuides(program, scales) {
183
184
  let next = program;
184
185
  for (const channel of POSITION_CHANNELS) {
185
186
  const definition = getPositionChannelDefinition(channel);
186
- const axis = next.semanticSpec.guides.axis?.[channel];
187
- if (
188
- axis !== undefined &&
189
- scales[channel].has(axis.scale) &&
190
- !hasScaleConsumer(next, channel, axis.scale)
191
- ) {
192
- next = next[REMOVE_AXIS[channel]]({
193
- scale: axis.scale,
194
- ...(axis.coordinate === undefined ? {} : { coordinate: axis.coordinate })
195
- });
187
+ const owners = ["x", "y"].includes(channel)
188
+ ? cartesianAxisOwners(next).filter(owner => owner.channel === channel)
189
+ : [{ id: channel, channel, semantic: next.semanticSpec.guides.axis?.[channel] }];
190
+ for (const owner of owners) {
191
+ const axis = owner.semantic;
192
+ if (axis !== undefined && scales[channel].has(axis.scale) && !hasScaleConsumer(next, channel, axis.scale)) {
193
+ next = next[REMOVE_AXIS[channel]]({ ...axisTargetArgs(owner.id, channel), scale: axis.scale,
194
+ ...(axis.coordinate === undefined ? {} : { coordinate: axis.coordinate }) });
195
+ }
196
196
  }
197
197
  const grid = next.semanticSpec.guides.grid?.[definition.gridDirection];
198
198
  if (
@@ -1,7 +1,8 @@
1
+ import { validateGraphicItemLimit } from "../../core/validation.js";
1
2
  import { closedAction } from "../../core/action.js";
2
3
  import { validateUserId } from "../../core/identifiers.js";
3
4
  import { freezeOwned } from "../../core/immutable.js";
4
- import { validateGeneratedItemLimit } from "../../core/validation.js";
5
+ import { resolveGraphicItemCapacity } from "../../selectors/graphicCapacity.js";
5
6
  import {
6
7
  isDrawableGraphicType,
7
8
  isGraphicContainerType,
@@ -118,7 +119,7 @@ const createGraphics = /* @__PURE__ */ closedAction(
118
119
  throw new TypeError("createGraphics length must be a non-negative integer.");
119
120
  }
120
121
  if (length !== undefined) {
121
- validateGeneratedItemLimit(length, "createGraphics length");
122
+ validateGraphicItemLimit(length, resolveGraphicItemCapacity(this, { id, label: "createGraphics length", type: validatedType }));
122
123
  }
123
124
  if (validatedType === "collection" && length !== undefined) {
124
125
  throw new Error("Heterogeneous collections use editGraphics items instead of length.");
@@ -1,6 +1,7 @@
1
+ import { validateGraphicItemLimit } from "../../core/validation.js";
1
2
  import { closedAction } from "../../core/action.js";
2
3
  import { cloneAndFreeze, freezeOwned, isPlainObject } from "../../core/immutable.js";
3
- import { validateGeneratedItemLimit } from "../../core/validation.js";
4
+ import { resolveGraphicItemCapacity } from "../../selectors/graphicCapacity.js";
4
5
  import {
5
6
  isPrimitiveDrawableGraphicType,
6
7
  isStructuralGraphicType,
@@ -43,14 +44,14 @@ function validateCollectionItem(item, ownerId, index) {
43
44
  });
44
45
  }
45
46
 
46
- function replaceCollectionItems(value, id) {
47
+ function replaceCollectionItems(value, id, program) {
47
48
  if (!Array.isArray(value)) throw new TypeError("collection.items must be an array.");
48
- validateGeneratedItemLimit(value.length, "collection.items length");
49
+ validateGraphicItemLimit(value.length, resolveGraphicItemCapacity(program, { id, label: "collection.items length" }));
49
50
  return freezeOwned(value.map((item, index) => validateCollectionItem(item, id, index)));
50
51
  }
51
52
 
52
- function replaceDrawableItems(value, id, type) {
53
- const items = replaceCollectionItems(value, id);
53
+ function replaceDrawableItems(value, id, type, program) {
54
+ const items = replaceCollectionItems(value, id, program);
54
55
  if (!items.every(item => item.type === type)) {
55
56
  return freezeOwned({ type: "collection", items });
56
57
  }
@@ -102,15 +103,15 @@ function editGeneratedItem(owner, itemIndex, property, value) {
102
103
  return freezeOwned({ ...owner, items: freezeOwned(items) });
103
104
  }
104
105
 
105
- function editItems(owner, property, value, id) {
106
+ function editItems(owner, property, value, id, program) {
106
107
  if (owner.type === "collection" && property === "items") {
107
- return freezeOwned({ ...owner, items: replaceCollectionItems(value, id) });
108
+ return freezeOwned({ ...owner, items: replaceCollectionItems(value, id, program) });
108
109
  }
109
110
  if (property === "length") {
110
111
  if (!Number.isInteger(value) || value < 0) {
111
112
  throw new TypeError("Graphic collection length must be a non-negative integer.");
112
113
  }
113
- validateGeneratedItemLimit(value, "Graphic collection length");
114
+ validateGraphicItemLimit(value, resolveGraphicItemCapacity(program, { id, label: "Graphic collection length" }));
114
115
  const items = owner.items.slice(0, value);
115
116
  for (let index = items.length; index < value; index += 1) {
116
117
  if (owner.type === "collection") {
@@ -189,6 +190,9 @@ const editGraphics = /* @__PURE__ */ closedAction(
189
190
 
190
191
  const ownerId = found.kind === "item" ? found.ownerId : found.id;
191
192
  const owner = found.kind === "item" ? found.owner : found.object;
193
+ if (owner.items !== undefined) validateGraphicItemLimit(owner.items.length, resolveGraphicItemCapacity(this, {
194
+ id: ownerId, label: "Graphic collection length"
195
+ }));
192
196
  const replacesDrawableItems =
193
197
  found.kind === "object" && property === "items" &&
194
198
  owner.items !== undefined && owner.type !== "collection";
@@ -199,7 +203,7 @@ const editGraphics = /* @__PURE__ */ closedAction(
199
203
  : owner.type, property);
200
204
  let updated;
201
205
  if (replacesDrawableItems) {
202
- updated = replaceDrawableItems(value, ownerId, owner.type);
206
+ updated = replaceDrawableItems(value, ownerId, owner.type, this);
203
207
  } else if (found.kind === "item") {
204
208
  if (validatedProperty === "length" || validatedProperty === "items") {
205
209
  throw new Error(`${validatedProperty} can only target an owning graphic.`);
@@ -212,12 +216,12 @@ const editGraphics = /* @__PURE__ */ closedAction(
212
216
  properties: setGraphicProperty(owner.properties, validatedProperty, value)
213
217
  });
214
218
  } else if (owner.items !== undefined) {
215
- updated = editItems(owner, validatedProperty, value, ownerId);
219
+ updated = editItems(owner, validatedProperty, value, ownerId, this);
216
220
  } else if (validatedProperty === "length") {
217
221
  if (!Number.isInteger(value) || value < 0) {
218
222
  throw new TypeError("Graphic collection length must be a non-negative integer.");
219
223
  }
220
- validateGeneratedItemLimit(value, "Graphic collection length");
224
+ validateGraphicItemLimit(value, resolveGraphicItemCapacity(this, { id: ownerId, label: "Graphic collection length" }));
221
225
  updated = freezeOwned({
222
226
  type: owner.type,
223
227
  items: freezeOwned(Array.from({ length: value }, (_, index) => freezeOwned({
@@ -1,6 +1 @@
1
- export function editGraphicProperties(program, target, properties) {
2
- for (const [property, value] of Object.entries(properties)) {
3
- program = program.editGraphics({ target, property, value });
4
- }
5
- return program;
6
- }
1
+ export { editGraphicProperties } from "../../core/graphicProperties.js";
@@ -57,7 +57,7 @@ function updateEntity(spec, parsed, value) {
57
57
  source.schema,
58
58
  value[0],
59
59
  undefined,
60
- { sourceId: source.id, ownerId: parsed.id }
60
+ { sourceId: source.id, ownerId: parsed.id, sourceTransform: source.transform?.[0] }
61
61
  ));
62
62
  }
63
63
  }
@@ -72,6 +72,7 @@ function updateEntity(spec, parsed, value) {
72
72
  const schema = source?.schema !== undefined && current.transform?.length === 1
73
73
  ? deriveTransformSchema(source.schema, current.transform[0], value, {
74
74
  sourceId: source.id,
75
+ sourceTransform: source.transform?.[0],
75
76
  ownerId: parsed.id
76
77
  })
77
78
  : inferDatasetSchema(value);
@@ -225,6 +226,31 @@ export function withoutPreviewLayerEncodings(program, { id, channels }) {
225
226
  return program._clone({ semanticSpec });
226
227
  }
227
228
 
229
+ // Composition materialization may retain this native two-scale guide meaning.
230
+ // Use the existing semantic primitives on a private unit adapter; this does not
231
+ // relax the public composition guard or accept arbitrary semantic paths.
232
+ export function synchronizeFacetColorSizeMeaning(program, meaning) {
233
+ if (program.compositionSpec?.type !== "facet") {
234
+ throw new Error("Color-size parent meaning requires a facet composition.");
235
+ }
236
+ if (meaning !== undefined && (!isPlainObject(meaning) ||
237
+ Object.keys(meaning).some(key => !["colorScale", "sizeScale", "title"].includes(key)))) {
238
+ throw new Error("Color-size parent meaning requires its native scale bindings and title.");
239
+ }
240
+ let unit = new program.constructor({ semanticSpec: program.semanticSpec, graphicSpec: program.graphicSpec,
241
+ resolvedScales: program.resolvedScales, materializationConfigs: program.materializationConfigs,
242
+ context: program.context, trace: program.trace, actionStack: program.actionStack,
243
+ actionSequence: program._actionSequence });
244
+ if (unit.semanticSpec.guides.legend?.colorSize !== undefined) {
245
+ unit = unit.editSemantic({ property: "guide.legend.colorSize", remove: true });
246
+ }
247
+ if (meaning !== undefined) for (const [property, value] of Object.entries(meaning)) {
248
+ unit = unit.editSemantic({ property: `guide.legend.colorSize.${property}`, value });
249
+ }
250
+ return program._clone({ semanticSpec: unit.semanticSpec, context: unit.context,
251
+ trace: unit.trace, actionStack: unit.actionStack, actionSequence: unit._actionSequence });
252
+ }
253
+
228
254
  export function createSemanticPrimitiveAction(validateSemanticValue, planNamedRemoval) {
229
255
  return closedAction(
230
256
  {
@@ -1,3 +1,4 @@
1
+ import {isSizeShapeChannels} from "../../../grammar/legendCombinations.js";
1
2
  import { normalizeLegendOrder } from "../../../grammar/categoryOrder.js";
2
3
  import { validateUserId } from "../../../core/identifiers.js";
3
4
  import { CATEGORICAL_LEGEND_CHANNELS } from "../../../core/vocabulary.js";
@@ -16,7 +17,7 @@ function validateLegend(property, value) {
16
17
  throw new Error(`Legend ${property} must not contain duplicates.`);
17
18
  }
18
19
  if (property === "channels") {
19
- if (!value.every(channel => CATEGORICAL_LEGEND_CHANNELS.includes(channel))) {
20
+ if (!isSizeShapeChannels(value) && !value.every(channel => CATEGORICAL_LEGEND_CHANNELS.includes(channel))) {
20
21
  throw new Error("Legend channels support only color, strokeDash, and shape.");
21
22
  }
22
23
  return;
@@ -26,6 +27,11 @@ function validateLegend(property, value) {
26
27
 
27
28
  export function validateGuideSemanticValue(_program, parsed, value) {
28
29
  const property = parsed.path.at(-1);
30
+ if (parsed.id === "legend.colorSize") {
31
+ if(property === "title") validateNonEmptySemanticString(value,"Legend title");
32
+ else validateUserId(value,"Legend scale id");
33
+ return;
34
+ }
29
35
  if (parsed.id === "axis.parallel" && property === "titles") {
30
36
  validateParallelAxisTitles(value);
31
37
  return;
@@ -44,6 +50,10 @@ export function validateGuideSemanticValue(_program, parsed, value) {
44
50
  throw new TypeError("Parallel axis scales must contain at least two ids.");
45
51
  }
46
52
  value.forEach(id => validateUserId(id, "Parallel axis scale id"));
53
+ } else if (parsed.id.startsWith("axis.") && property === "channel") {
54
+ if (!["x", "y"].includes(value)) throw new Error("Named Cartesian axis channel requires x or y.");
55
+ } else if (parsed.id.startsWith("axis.") && property === "position") {
56
+ if (!["top", "bottom", "left", "right"].includes(value)) throw new Error("Named Cartesian axis position requires a physical side.");
47
57
  } else if (property === "title") {
48
58
  validateNonEmptySemanticString(value, "Axis title");
49
59
  } else {
@@ -70,6 +70,10 @@ export function validateLayerSemanticValue(
70
70
  !["vertical", "horizontal"].includes(value))) {
71
71
  throw new Error("Bar orientation must be vertical or horizontal.");
72
72
  }
73
+ if (property === "mark.blend" &&
74
+ (findLayer(program, parsed.id)?.mark?.type !== "bar" || value !== "difference")) {
75
+ throw new Error("Bar blend must be difference; remove the optional intent for normal painting.");
76
+ }
73
77
  if (property === "derivedBindings.regression") {
74
78
  if (!value || typeof value !== "object" || Array.isArray(value) ||
75
79
  Object.keys(value).length !== 2 || value.mode !== "follow" ||
@@ -19,6 +19,7 @@ import { findLayer } from "../../selectors/layers.js";
19
19
  import { removeOwnedMark } from "../marks/remove.js";
20
20
  import { removeOwnedColorLegends } from "../guides/legends/remove.js";
21
21
  import { requireRegressionField } from "./resolve.js";
22
+ import { buildSourceRevisionPlan, applyRevisionPlan } from "../data/edit.js";
22
23
 
23
24
  const OPTIONS = Object.freeze([
24
25
  "target", "data", "x", "y", "groupBy", "method", "degree", "span", "robustIterations",
@@ -128,7 +129,7 @@ function resolveParameters(previous, args) {
128
129
  raw[key] = previous[key];
129
130
  }
130
131
  }
131
- } else if (method !== "loess" && previous.method !== "loess" && args.interval !== false) {
132
+ } else if (!["loess", "exp"].includes(method) && !["loess", "exp"].includes(previous.method) && args.interval !== false) {
132
133
  for (const key of ["confidenceMethod", "level", "interval"]) {
133
134
  if (
134
135
  !Object.hasOwn(raw, key) &&
@@ -295,6 +296,11 @@ export const editRegression = /* @__PURE__ */ closedAction(
295
296
  const changesParameters = JSON.stringify(parameters) !==
296
297
  JSON.stringify(current.parameters);
297
298
  const changesStatistics = changesProvenance || changesParameters;
299
+ const hasStatistics = this.semanticSpec.datasets.some(dataset =>
300
+ dataset.source === current.dataId && dataset.transform?.[0]?.type === "regressionStatistics");
301
+ if (hasStatistics && parameters.method !== "linear") {
302
+ throw new Error("Remove dependent regression statistics before changing the linear fit method.");
303
+ }
298
304
  const bandPatch = Object.hasOwn(args, "band")
299
305
  ? validateBandPatch(args.band)
300
306
  : undefined;
@@ -302,14 +308,16 @@ export const editRegression = /* @__PURE__ */ closedAction(
302
308
  ? validateLinePatch(args.line)
303
309
  : undefined;
304
310
  if ((parameters.method === "loess" || parameters.interval === false) && bandPatch !== undefined && bandPatch !== false) {
305
- throw new Error("LOESS regression does not support a band object.");
311
+ throw new Error(parameters.method === "loess"
312
+ ? "LOESS regression does not support a band object."
313
+ : "Regression interval false cannot include a band object.");
306
314
  }
307
315
 
308
316
  const hadBand = current.bandId !== undefined;
309
317
  const wantsBand = parameters.method !== "loess" && parameters.interval !== false &&
310
318
  (bandPatch === false
311
319
  ? false
312
- : hadBand || bandPatch !== undefined || current.parameters.method === "loess");
320
+ : hadBand || bandPatch !== undefined || ["loess", "exp"].includes(current.parameters.method));
313
321
  const revision = changesStatistics
314
322
  ? planDerivedDataRevision(this, {
315
323
  owner: owner.id,
@@ -321,6 +329,8 @@ export const editRegression = /* @__PURE__ */ closedAction(
321
329
  ]
322
330
  })
323
331
  : undefined;
332
+ const statisticsRevision = changesStatistics && hasStatistics
333
+ ? buildSourceRevisionPlan(this, current.dataId, revision.id) : undefined;
324
334
 
325
335
  const applyEdit = program => {
326
336
  let dataId = current.dataId;
@@ -412,6 +422,8 @@ export const editRegression = /* @__PURE__ */ closedAction(
412
422
  }
413
423
  });
414
424
  if (changesStatistics) {
425
+ if (statisticsRevision !== undefined) next = applyRevisionPlan(next, undefined,
426
+ statisticsRevision, { retain: new Set([current.dataId]) });
415
427
  next = next.releaseDerivedData(revision.release);
416
428
  }
417
429
  const binding = findLayer(next, owner.id)?.derivedBindings?.regression;
@@ -42,7 +42,7 @@ const DEFINITIONS = Object.freeze({
42
42
  channel: "color",
43
43
  options: Object.freeze([
44
44
  "type", "domain", "range", "emptyDomain", "clamp", "reverse", "palette",
45
- "interpolate", "midpoint", "base", "constant", "unknown"
45
+ "interpolate", "whitePoint", "gamma", "midpoint", "base", "exponent", "constant", "unknown"
46
46
  ])
47
47
  }),
48
48
  editStrokeScale: Object.freeze({
@@ -50,7 +50,7 @@ const DEFINITIONS = Object.freeze({
50
50
  targetOnly: true,
51
51
  options: Object.freeze([
52
52
  "type", "domain", "range", "emptyDomain", "clamp", "reverse", "palette",
53
- "interpolate", "midpoint", "base", "constant", "unknown"
53
+ "interpolate", "whitePoint", "gamma", "midpoint", "base", "exponent", "constant", "unknown"
54
54
  ])
55
55
  }),
56
56
  editSizeScale: Object.freeze({
@@ -38,7 +38,7 @@ const CREATE_SCALE_OPTIONS = Object.freeze([
38
38
  "padding",
39
39
  "align",
40
40
  "palette",
41
- "interpolate", "midpoint",
41
+ "interpolate", "whitePoint", "gamma", "midpoint",
42
42
  "radialMapping", "unknown"
43
43
  ]);
44
44
 
@@ -88,7 +88,7 @@ export const createScale = /* @__PURE__ */ action(
88
88
  const id = validateUserId(args.id, "Scale id");
89
89
  const type = validateScaleType(args.type ?? "linear");
90
90
  const colorType = isColorScaleType(type) && (
91
- !["log", "sqrt", "symlog"].includes(type) || args.palette !== undefined ||
91
+ !["log", "pow", "sqrt", "symlog"].includes(type) || args.palette !== undefined ||
92
92
  args.interpolate !== undefined || (Array.isArray(args.range) && typeof args.range[0] === "string") ||
93
93
  args.range?.palette !== undefined
94
94
  );
@@ -171,7 +171,7 @@ export const createScale = /* @__PURE__ */ action(
171
171
 
172
172
  for (const property of [
173
173
  "emptyDomain", "nice", "zero", "clamp", "reverse", "base", "exponent", "constant",
174
- "paddingInner", "paddingOuter", "padding", "align", "interpolate", "midpoint",
174
+ "paddingInner", "paddingOuter", "padding", "align", "interpolate", "whitePoint", "gamma", "midpoint",
175
175
  "radialMapping", "unknown"
176
176
  ]) {
177
177
  if (definition[property] === undefined) continue;
@@ -1 +1 @@
1
- import{prepareScaleEdit as M}from"./editPolicy.js";import{validateUserId as m}from"../../core/identifiers.js";import{isPlainObject as W}from"../../core/immutable.js";import{validateKeys as u}from"../../core/validation.js";import{validateColorRange as U,isContinuousColorScaleType as w,normalizeTransformParameters as B,validateTransformedDomain as F,validateContinuousColorInterpolation as K,validateSequentialMidpoint as Q,validateLinearScaleType as z,validateOpacityRange as H,validateOrdinalDomain as E,validateOrdinalScaleType as N,validateScaleDomain as h,validateScaleRange as _,validateShapeRange as V,validateStrokeDashRange as Y,validateStrokeWidthRange as Z,validateSequentialColorRange as G,validateDiscretizedColorDomain as J,validateDiscretizedColorRange as X,validateTimeScaleType as ee,normalizeScaleDefinition as k,SCALE_ROLES as P,validateScalePropertyForType as A,validateScaleTypeForRole as b,isDiscretePositionScaleType as te,normalizeSizeScaleDefinition as ae,withScaleUnknown as O}from"../../grammar/scales/index.js";import{validateRadialRange as ne,validateThetaRange as ie}from"../../grammar/polar.js";import{findSemanticScale as p}from"../../selectors/scales.js";import{normalizeOffsetScalePolicy as re}from"../../grammar/bars/geometry.js";import{resolveRequestedOffsetPolicy as oe}from"../../materialization/scales/policies/offset.js";import{findScaleConsumers as ce}from"./consumers/index.js";const T=["id","type","domain","range","emptyDomain"],j=[...T,"unknown"],S=["clamp","reverse"],v=["nice","zero",...S],q=["base","exponent","constant"],de=[...T,...v,...q,"paddingInner","paddingOuter","padding","align","unknown"],C=[...T,"palette","unknown"],L=[...C,"interpolate","midpoint","base","constant",...S],le=[...T,...v,"unknown"],se=[...T,...v,...q],ue=[...j,"clamp","reverse","base","exponent"];function y(i){if(!W(i))throw new TypeError("Encoding scale must be a plain object.")}function D(i,e,t){for(const a of e){if(i[a]!==void 0&&typeof i[a]!="boolean")throw new TypeError(`Scale ${a} must be a boolean.`);t!==void 0&&i[a]!==void 0&&A(t,a)}}function R(i,e,t,a){for(const n of a){const r=e[n]??t?.[n];r!==void 0&&(i[n]=r)}return i}function x(i){if(i.palette!==void 0&&i.range!==void 0)throw new Error("Color scale cannot specify both palette and range.")}function Ce(i,e,t,a,n={}){y(a),u(a,de,"scale");const r=m(a.id??e,"Scale id"),o=p(i,r),c=["ordinal","nominal"].includes(t),f=t==="temporal"?"time":c?n.discreteType??"point":"linear",d=a.type??o?.type??f;if(t==="temporal")ee(d);else if(c){if(!te(d))throw new Error(`Scale type "${d}" is not valid for discrete position.`)}else e==="theta"?z(d):b(d,P.quantitativePosition);const l={id:r,...k({type:d,previous:o,patch:{...a,...n.radialMapping===void 0?{}:{radialMapping:n.radialMapping}},defaults:{...o===void 0&&n.nice!==void 0?{nice:n.nice}:{},...o===void 0&&n.zero!==void 0&&(d!=="log"||n.zero)?{zero:n.zero}:{}},retainCoreOnTypeChange:!0,retainCompatibleOnTypeChange:!0,validateDomain:(g,s)=>c?E(s):h(s),validateRange:(g,s)=>e==="theta"?ie(s):e==="radius"?ne(s):_(s)})};return O(l,{...o,...a},e)}function De(i,e,t="color"){y(e),u(e,C,"scale"),x(e);const a=m(e.id??t,"Scale id"),n=p(i,a),r=n?.type==="ordinal"?n:void 0,o=e.palette===void 0?e.range:{palette:e.palette};return O({id:a,type:N(e.type??r?.type??"ordinal"),domain:E(e.domain??r?.domain??"auto"),range:U(o??r?.range??"auto")},{...r,...e},t)}function fe(i,e){if(i==="auto")return i;if(!Array.isArray(i)||i.length!==2)throw new TypeError("Continuous color domain must contain two values or auto.");const t=i.map(a=>e==="quantitative"?a:typeof a=="string"?Date.parse(a):a);if(!t.every(Number.isFinite)||t[0]===t[1])throw new TypeError("Continuous color domain requires two distinct valid values.");return t}function me(i,e,t,a="color"){y(t),u(t,L,"scale"),x(t);const n=m(t.id??a,"Scale id"),r=p(i,n),o=w(r?.type)?r:void 0,c=t.type??o?.type??"sequential";if(!w(c))throw new Error(`Unsupported continuous color scale type "${c}".`);D(t,S);const f=t.palette===void 0?t.range:{palette:t.palette},d={id:n,type:c,domain:fe(t.domain??o?.domain??"auto",e),range:G(f??o?.range??{palette:"viridis"}),interpolate:K(t.interpolate??o?.interpolate??"rgb")};if(c!=="sequential"&&e!=="quantitative")throw new Error(`Scale type "${c}" requires quantitative color.`);const l=Object.fromEntries(["base","constant"].flatMap(s=>{const I=t[s]??o?.[s];return I===void 0?[]:[[s,I]]}));if(c!=="sequential"){const s=B(c,l);c==="log"&&(d.base=s.base),c==="symlog"&&(d.constant=s.constant),d.domain!=="auto"&&F(c,d.domain,l)}else if(Object.keys(l).length)throw new Error("Sequential color does not support transform parameters.");const g=Q(Object.hasOwn(t,"midpoint")?t.midpoint:o?.midpoint,c,d.domain);if(g!==void 0){if(e!=="quantitative")throw new Error("Scale midpoint requires quantitative color consumers.");d.midpoint=g}return O(R(d,t,o,S),{...o,...t},a)}function Re(i,e,t,a="color"){y(t);const n=p(i,t.id??a),r=n!==void 0&&(w(n.type)||["quantize","quantile","threshold"].includes(n.type)),o=t.type??(r?n.type:"sequential");if(r&&n.type!==o){if(u(t,w(o)?L:[...C,...S],"scale"),e!=="quantitative")throw new Error("Color scale type transition requires quantitative color.");return{id:n.id,...M(i,n,a,[],t)}}if(w(o))return me(i,e,t,a);if(u(t,[...C,...S],"scale"),e!=="quantitative")throw new Error(`Scale type "${o}" requires quantitative color.`);b(o,P.discretizedColor),x(t),D(t,S,o);const c=m(t.id??a,"Scale id"),f=n?.type===o?n:void 0,d=t.palette===void 0?t.range:{palette:t.palette},l={id:c,type:o,domain:J(o,t.domain??f?.domain??"auto"),range:X(d??f?.range??{palette:"viridis"})};return O(R(l,t,f,S),{...n,...t},a)}function $(i,e,t,a){y(e),u(e,j,"scale");const n=m(e.id??t,"Scale id"),r=p(i,n);return O({id:n,type:N(e.type??r?.type??"ordinal"),domain:E(e.domain??r?.domain??"auto"),range:a(e.range??r?.range??"auto")},{...r,...e},t)}function he(i,e){return $(i,e,"strokeDash",Y)}function Pe(i,e,t,a="quantitative"){if(y(t),e==="size"){u(t,ue,"scale");const n=m(t.id??e,"Scale id"),r=p(i,n),o=["nominal","ordinal"].includes(a),c=ae({previous:r,patch:{...t,type:t.type??r?.type??(o?"ordinal":"linear")}});if(c.type==="ordinal"!==o)throw new Error("Size scale type must match its field type.");return{id:n,...c}}return $(i,t,e,V)}function be(i,e){y(e),u(e,le,"scale");const t=m(e.id??"opacity","Scale id"),a=p(i,t),n=z(e.type??a?.type??"linear");D(e,v,n);const r={id:t,type:n,domain:h(e.domain??a?.domain??"auto"),range:H(e.range??a?.range??"auto")};return O(R(r,e,a,v),{...a,...e},"opacity")}function xe(i,e){y(e),u(e,se,"scale");const t=m(e.id??"strokeWidth","Scale id"),a=p(i,t),n=e.type??a?.type??"linear";b(n,P.quantitativePosition),D(e,v,n);for(const c of q)e[c]!==void 0&&A(n,c);const r=h(e.domain??a?.domain??"auto");if(r!=="auto"&&r.some(c=>c<0))throw new RangeError("StrokeWidth scale domain cannot contain negative values.");const o={id:t,type:n,domain:r,range:Z(e.range??a?.range??"auto")};return R(o,e,a,[...v,...q])}function Ie(i,e,t="xOffset"){y(e),u(e,["id","type","domain","range","reverse","padding","paddingInner","paddingOuter","align"],"scale");const a=m(e.id??t,"Scale id"),n=p(i,a),r=e.type??n?.type??"ordinal";if(r!=="ordinal")throw new Error(`Scale type "${r}" is not valid for ${t}.`);if(Object.hasOwn(e,"range")&&e.range!=="auto")throw new Error(`${t} scale range is derived from its parent categorical slot.`);const o=ce(i,a).filter(l=>l.channel===t),c=oe({scale:n,consumers:o,markConfigs:i.markConfigs,id:a,channel:t}),f=re(e,c,t),d=Object.fromEntries(Object.entries(e).filter(([l])=>!["id","padding"].includes(l)));return{id:a,...k({type:r,previous:n,patch:{...d,...f},allowOrdinalBandParameters:!0,validateDomain:(l,g)=>E(g),validateRange:(l,g)=>_(g)})}}export{Pe as resolveAppearanceScaleDefinition,De as resolveColorScaleDefinition,Ie as resolveOffsetScaleDefinition,be as resolveOpacityScaleDefinition,Ce as resolvePositionScaleDefinition,Re as resolveQuantitativeColorScaleDefinition,me as resolveSequentialColorScaleDefinition,he as resolveStrokeDashScaleDefinition,xe as resolveStrokeWidthScaleDefinition};
1
+ import{prepareScaleEdit as U}from"./editPolicy.js";import{validateUserId as p}from"../../core/identifiers.js";import{isPlainObject as B}from"../../core/immutable.js";import{validateKeys as f}from"../../core/validation.js";import{validateColorRange as F,isContinuousColorScaleType as E,normalizeTransformParameters as K,validateTransformedDomain as Q,validateContinuousColorInterpolation as H,normalizeColorInterpolationOptions as V,validateSequentialMidpoint as Y,validateLinearScaleType as _,validateOpacityRange as Z,validateOrdinalDomain as h,validateOrdinalScaleType as j,validateScaleDomain as P,validateScaleRange as k,validateShapeRange as G,validateStrokeDashRange as J,validateStrokeWidthRange as X,validateSequentialColorRange as ee,validateDiscretizedColorDomain as te,validateDiscretizedColorRange as ne,validateTimeScaleType as ae,normalizeScaleDefinition as A,SCALE_ROLES as R,validateScalePropertyForType as L,validateScaleTypeForRole as I,isDiscretePositionScaleType as ie,normalizeSizeScaleDefinition as re,withScaleUnknown as w}from"../../grammar/scales/index.js";import{validateRadialRange as oe,validateThetaRange as ce}from"../../grammar/polar.js";import{findSemanticScale as g}from"../../selectors/scales.js";import{normalizeOffsetScalePolicy as le}from"../../grammar/bars/geometry.js";import{resolveRequestedOffsetPolicy as de}from"../../materialization/scales/policies/offset.js";import{findScaleConsumers as se}from"./consumers/index.js";const C=["id","type","domain","range","emptyDomain"],M=[...C,"unknown"],S=["clamp","reverse"],O=["nice","zero",...S],q=["base","exponent","constant"],ue=[...C,...O,...q,"paddingInner","paddingOuter","padding","align","unknown"],D=[...C,"palette","unknown"],$=[...D,"interpolate","whitePoint","gamma","midpoint","base","exponent","constant",...S],fe=[...C,...O,"unknown"],me=[...C,...O,...q],pe=[...M,"clamp","reverse","base","exponent"];function y(i){if(!B(i))throw new TypeError("Encoding scale must be a plain object.")}function b(i,t,e){for(const n of t){if(i[n]!==void 0&&typeof i[n]!="boolean")throw new TypeError(`Scale ${n} must be a boolean.`);e!==void 0&&i[n]!==void 0&&L(e,n)}}function x(i,t,e,n){for(const a of n){const o=t[a]??e?.[a];o!==void 0&&(i[a]=o)}return i}function z(i){if(i.palette!==void 0&&i.range!==void 0)throw new Error("Color scale cannot specify both palette and range.")}function be(i,t,e,n,a={}){y(n),f(n,ue,"scale");const o=p(n.id??t,"Scale id"),r=g(i,o),c=["ordinal","nominal"].includes(e),m=e==="temporal"?"time":c?a.discreteType??"point":"linear",l=n.type??r?.type??m;if(e==="temporal")ae(l);else if(c){if(!ie(l))throw new Error(`Scale type "${l}" is not valid for discrete position.`)}else t==="theta"?_(l):I(l,R.quantitativePosition);const s={id:o,...A({type:l,previous:r,patch:{...n,...a.radialMapping===void 0?{}:{radialMapping:a.radialMapping}},defaults:{...r===void 0&&a.nice!==void 0?{nice:a.nice}:{},...r===void 0&&a.zero!==void 0&&(l!=="log"||a.zero)?{zero:a.zero}:{}},retainCoreOnTypeChange:!0,retainCompatibleOnTypeChange:!0,validateDomain:(v,u)=>c?h(u):P(u),validateRange:(v,u)=>t==="theta"?ce(u):t==="radius"?oe(u):k(u)})};return w(s,{...r,...n},t)}function xe(i,t,e="color"){y(t),f(t,D,"scale"),z(t);const n=p(t.id??e,"Scale id"),a=g(i,n),o=a?.type==="ordinal"?a:void 0,r=t.palette===void 0?t.range:{palette:t.palette};return w({id:n,type:j(t.type??o?.type??"ordinal"),domain:h(t.domain??o?.domain??"auto"),range:F(r??o?.range??"auto")},{...o,...t},e)}function ge(i,t){if(i==="auto")return i;if(!Array.isArray(i)||i.length!==2)throw new TypeError("Continuous color domain must contain two values or auto.");const e=i.map(n=>t==="quantitative"?n:typeof n=="string"?Date.parse(n):n);if(!e.every(Number.isFinite)||e[0]===e[1])throw new TypeError("Continuous color domain requires two distinct valid values.");return e}function ye(i,t,e,n="color"){y(e),f(e,$,"scale"),z(e);const a=p(e.id??n,"Scale id"),o=g(i,a),r=E(o?.type)?o:void 0,c=e.type??r?.type??"sequential";if(!E(c))throw new Error(`Unsupported continuous color scale type "${c}".`);b(e,S);const m=e.palette===void 0?e.range:{palette:e.palette},l={id:a,type:c,domain:ge(e.domain??r?.domain??"auto",t),range:ee(m??r?.range??{palette:"viridis"}),interpolate:H(e.interpolate??r?.interpolate??"rgb")},s=Object.hasOwn(e,"interpolate")&&e.interpolate!==r?.interpolate,v=Object.fromEntries(["whitePoint","gamma"].flatMap(d=>{const T=Object.hasOwn(e,d)?e[d]:s?void 0:r?.[d];return T===void 0?[]:[[d,T]]}));if(Object.assign(l,V(l.interpolate,v)),c!=="sequential"&&t!=="quantitative")throw new Error(`Scale type "${c}" requires quantitative color.`);const u=Object.fromEntries(["base","exponent","constant"].flatMap(d=>{const T=e[d]??(c===r?.type?r?.[d]:void 0);return T===void 0?[]:[[d,T]]}));if(c!=="sequential"){const d=K(c,u);c==="log"&&(l.base=d.base),c==="pow"&&(l.exponent=d.exponent),c==="symlog"&&(l.constant=d.constant),l.domain!=="auto"&&Q(c,l.domain,u)}else if(Object.keys(u).length)throw new Error("Sequential color does not support transform parameters.");const N=Y(Object.hasOwn(e,"midpoint")?e.midpoint:r?.midpoint,c,l.domain);if(N!==void 0){if(t!=="quantitative")throw new Error("Scale midpoint requires quantitative color consumers.");l.midpoint=N}return w(x(l,e,r,S),{...r,...e},n)}function Pe(i,t,e,n="color"){y(e);const a=g(i,e.id??n),o=a!==void 0&&(E(a.type)||["quantize","quantile","threshold"].includes(a.type)),r=e.type??(o?a.type:"sequential");if(o&&a.type!==r){if(f(e,E(r)?$:[...D,...S],"scale"),t!=="quantitative")throw new Error("Color scale type transition requires quantitative color.");return{id:a.id,...U(i,a,n,[],e)}}if(E(r))return ye(i,t,e,n);if(f(e,[...D,...S],"scale"),t!=="quantitative")throw new Error(`Scale type "${r}" requires quantitative color.`);I(r,R.discretizedColor),z(e),b(e,S,r);const c=p(e.id??n,"Scale id"),m=a?.type===r?a:void 0,l=e.palette===void 0?e.range:{palette:e.palette},s={id:c,type:r,domain:te(r,e.domain??m?.domain??"auto"),range:ne(l??m?.range??{palette:"viridis"})};return w(x(s,e,m,S),{...a,...e},n)}function W(i,t,e,n){y(t),f(t,M,"scale");const a=p(t.id??e,"Scale id"),o=g(i,a);return w({id:a,type:j(t.type??o?.type??"ordinal"),domain:h(t.domain??o?.domain??"auto"),range:n(t.range??o?.range??"auto")},{...o,...t},e)}function Re(i,t){return W(i,t,"strokeDash",J)}function Ie(i,t,e,n="quantitative"){if(y(e),t==="size"){f(e,pe,"scale");const a=p(e.id??t,"Scale id"),o=g(i,a),r=["nominal","ordinal"].includes(n),c=re({previous:o,patch:{...e,type:e.type??o?.type??(r?"ordinal":"linear")}});if(c.type==="ordinal"!==r)throw new Error("Size scale type must match its field type.");return{id:a,...c}}return W(i,e,t,G)}function ze(i,t){y(t),f(t,fe,"scale");const e=p(t.id??"opacity","Scale id"),n=g(i,e),a=_(t.type??n?.type??"linear");b(t,O,a);const o={id:e,type:a,domain:P(t.domain??n?.domain??"auto"),range:Z(t.range??n?.range??"auto")};return w(x(o,t,n,O),{...n,...t},"opacity")}function Ne(i,t){y(t),f(t,me,"scale");const e=p(t.id??"strokeWidth","Scale id"),n=g(i,e),a=t.type??n?.type??"linear";I(a,R.quantitativePosition),b(t,O,a);for(const c of q)t[c]!==void 0&&L(a,c);const o=P(t.domain??n?.domain??"auto");if(o!=="auto"&&o.some(c=>c<0))throw new RangeError("StrokeWidth scale domain cannot contain negative values.");const r={id:e,type:a,domain:o,range:X(t.range??n?.range??"auto")};return x(r,t,n,[...O,...q])}function _e(i,t,e="xOffset"){y(t),f(t,["id","type","domain","range","reverse","padding","paddingInner","paddingOuter","align"],"scale");const n=p(t.id??e,"Scale id"),a=g(i,n),o=t.type??a?.type??"ordinal";if(o!=="ordinal")throw new Error(`Scale type "${o}" is not valid for ${e}.`);if(Object.hasOwn(t,"range")&&t.range!=="auto")throw new Error(`${e} scale range is derived from its parent categorical slot.`);const r=se(i,n).filter(s=>s.channel===e),c=de({scale:a,consumers:r,markConfigs:i.markConfigs,id:n,channel:e}),m=le(t,c,e),l=Object.fromEntries(Object.entries(t).filter(([s])=>!["id","padding"].includes(s)));return{id:n,...A({type:o,previous:a,patch:{...l,...m},allowOrdinalBandParameters:!0,validateDomain:(s,v)=>h(v),validateRange:(s,v)=>k(v)})}}export{Ie as resolveAppearanceScaleDefinition,xe as resolveColorScaleDefinition,_e as resolveOffsetScaleDefinition,ze as resolveOpacityScaleDefinition,be as resolvePositionScaleDefinition,Pe as resolveQuantitativeColorScaleDefinition,ye as resolveSequentialColorScaleDefinition,Re as resolveStrokeDashScaleDefinition,Ne as resolveStrokeWidthScaleDefinition};
@@ -28,7 +28,7 @@ import { hasExactLegendSamplingForScale } from
28
28
  const OPTIONS = Object.freeze([
29
29
  "id", "type", "domain", "range", "emptyDomain", "nice", "zero", "clamp", "reverse",
30
30
  "base", "exponent", "constant", "paddingInner", "paddingOuter", "padding",
31
- "align", "interpolate", "midpoint", "unknown", "palette", "radialMapping"
31
+ "align", "interpolate", "whitePoint", "gamma", "midpoint", "unknown", "palette", "radialMapping"
32
32
  ]);
33
33
  const EDITABLE = Object.freeze(
34
34
  OPTIONS.filter(option => !["id", "palette"].includes(option))
@@ -12,6 +12,7 @@ import {
12
12
  validateDiscretizedColorDomain,
13
13
  validateDiscretizedColorRange,
14
14
  validateOrdinalDomain,
15
+ validateOrdinalRange,
15
16
  validateScaleDomain,
16
17
  validateScaleRange,
17
18
  validateScaleType,
@@ -66,6 +67,7 @@ function validateRangeForChannel(scale, channel, value) {
66
67
  if (channel === "strokeDash") return validateStrokeDashRange(value);
67
68
  if (channel === "size") return validateSizeRange(value);
68
69
  if (channel === "strokeWidth") return validateStrokeWidthRange(value);
70
+ if (channel === undefined) return validateOrdinalRange(value);
69
71
  return validateScaleRange(value);
70
72
  }
71
73
 
@@ -217,6 +219,7 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
217
219
  patch: normalizedPatch,
218
220
  retainCoreOnTypeChange: true,
219
221
  retainCompatibleOnTypeChange: true,
222
+ allowColorReset: true,
220
223
  allowOrdinalBandParameters: offset,
221
224
  validateDomain: (scaleType, value) =>
222
225
  isDiscretizedColorScaleType(scaleType)
@@ -1,7 +1,7 @@
1
1
  const SCALE_EDIT_PROPERTIES = Object.freeze([
2
2
  "type", "domain", "range", "emptyDomain", "nice", "zero", "clamp", "reverse",
3
3
  "base", "exponent", "constant", "paddingInner", "paddingOuter",
4
- "padding", "align", "interpolate", "midpoint", "unknown"
4
+ "padding", "align", "interpolate", "whitePoint", "gamma", "midpoint", "unknown"
5
5
  ]);
6
6
 
7
7
  function sameValue(left, right) {
@@ -7,7 +7,7 @@ const OPTIONS = Object.freeze([
7
7
  "type",
8
8
  "domain",
9
9
  "range",
10
- "interpolate", "midpoint", "base", "constant",
10
+ "interpolate", "whitePoint", "gamma", "midpoint", "base", "exponent", "constant",
11
11
  "clamp",
12
12
  "reverse",
13
13
  "unknown"
@@ -19,7 +19,7 @@ export const setQuantitativeColorScale = /* @__PURE__ */ closedAction(
19
19
  description: "Create or update an internal quantitative color scale."
20
20
  }, OPTIONS,
21
21
  function (args = {}) {
22
- if (!["sequential", "log", "sqrt", "symlog", "quantize", "quantile", "threshold"].includes(args.type)) {
22
+ if (!["sequential", "log", "pow", "sqrt", "symlog", "quantize", "quantile", "threshold"].includes(args.type)) {
23
23
  throw new Error(`Unsupported quantitative color scale type "${args.type}".`);
24
24
  }
25
25
  const existing = findSemanticScale(this, args.id);
@@ -30,11 +30,16 @@ export const setQuantitativeColorScale = /* @__PURE__ */ closedAction(
30
30
  return this.editScale(args);
31
31
  }
32
32
  let next = this;
33
+ for (const property of ["whitePoint", "gamma"]) {
34
+ if (existing?.[property] !== undefined && args[property] === undefined) {
35
+ next = next.editSemantic({ property: `scale[${args.id}].${property}`, remove: true });
36
+ }
37
+ }
33
38
  if (existing?.midpoint !== undefined && args.midpoint === undefined) {
34
39
  next = next.editSemantic({ property: `scale[${args.id}].midpoint`, remove: true });
35
40
  }
36
41
  for (const property of [
37
- "type", "domain", "range", "interpolate", "midpoint", "base", "constant", "clamp", "reverse", "unknown"
42
+ "type", "domain", "range", "interpolate", "whitePoint", "gamma", "midpoint", "base", "exponent", "constant", "clamp", "reverse", "unknown"
38
43
  ]) {
39
44
  if (!Object.hasOwn(args, property)) continue;
40
45
  if (existing?.[property] === args[property]) continue;