ggaction 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +2 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/edit.js +1 -1
  35. package/src/actions/data/regression.js +2 -1
  36. package/src/actions/data/shared.js +3 -2
  37. package/src/actions/data/stack.js +3 -1
  38. package/src/actions/encodings/appearance.js +37 -29
  39. package/src/actions/encodings/barWidth.js +36 -11
  40. package/src/actions/encodings/channels.js +0 -1
  41. package/src/actions/encodings/color/continuous.js +1 -1
  42. package/src/actions/encodings/color/index.js +1 -1
  43. package/src/actions/encodings/color/policy.js +10 -3
  44. package/src/actions/encodings/density/resolve.js +1 -6
  45. package/src/actions/encodings/position/policies/arc.js +12 -10
  46. package/src/actions/encodings/position/policies/area.js +3 -3
  47. package/src/actions/encodings/position/policies/bar.js +15 -6
  48. package/src/actions/encodings/position/policies/line.js +17 -6
  49. package/src/actions/encodings/position/resolve.js +3 -2
  50. package/src/actions/encodings/ranged.js +17 -0
  51. package/src/actions/encodings/remove.js +3 -2
  52. package/src/actions/encodings/seriesLayout.js +1 -0
  53. package/src/actions/encodings/text.js +6 -3
  54. package/src/actions/errorBands/create.js +2 -2
  55. package/src/actions/errorBands/edit.js +2 -1
  56. package/src/actions/facets/actions.js +158 -240
  57. package/src/actions/gradientPlots/resolve.js +2 -17
  58. package/src/actions/guides/applicability.js +1 -1
  59. package/src/actions/guides/axes/axis.js +16 -4
  60. package/src/actions/guides/axes/edit.js +4 -4
  61. package/src/actions/guides/axes/labels.js +120 -46
  62. package/src/actions/guides/axes/lines.js +6 -5
  63. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  64. package/src/actions/guides/axes/parallel/policy.js +8 -7
  65. package/src/actions/guides/axes/remove.js +3 -2
  66. package/src/actions/guides/axes/tickGroups.js +5 -2
  67. package/src/actions/guides/axes/ticks.js +6 -3
  68. package/src/actions/guides/axes/titles.js +71 -26
  69. package/src/actions/guides/basic.js +2 -0
  70. package/src/actions/guides/facadeLegend.js +10 -2
  71. package/src/actions/guides/grids/grid.js +7 -1
  72. package/src/actions/guides/grids/resolve.js +59 -12
  73. package/src/actions/guides/legends/blocks.js +3 -1
  74. package/src/actions/guides/legends/categorical/actions.js +70 -17
  75. package/src/actions/guides/legends/categorical/components.js +12 -8
  76. package/src/actions/guides/legends/categorical/index.js +6 -0
  77. package/src/actions/guides/legends/categorical/layout.js +37 -8
  78. package/src/actions/guides/legends/categorical/options.js +31 -20
  79. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  80. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  81. package/src/actions/guides/legends/categorical/validation.js +1 -6
  82. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  83. package/src/actions/guides/legends/continuous/common.js +1 -447
  84. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  85. package/src/actions/guides/legends/continuous/interval.js +4 -4
  86. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  87. package/src/actions/guides/legends/edit.js +1 -604
  88. package/src/actions/guides/legends/focused.js +2 -2
  89. package/src/actions/guides/legends/group.js +1 -0
  90. package/src/actions/guides/legends/lane.js +1 -516
  91. package/src/actions/guides/legends/remove.js +3 -0
  92. package/src/actions/guides/legends/sampling.js +1 -1
  93. package/src/actions/guides/legends/size.js +51 -43
  94. package/src/actions/guides/legends/strokeWidth.js +11 -29
  95. package/src/actions/guides/legends/target.js +3 -3
  96. package/src/actions/guides/legends/transition.js +21 -7
  97. package/src/actions/guides/polar/axes/labels.js +4 -2
  98. package/src/actions/guides/polar/axes/titles.js +7 -5
  99. package/src/actions/guides/polar/resolve.js +6 -14
  100. package/src/actions/index.js +16 -16
  101. package/src/actions/marks/arc/actions.js +3 -10
  102. package/src/actions/marks/area/actions.js +8 -5
  103. package/src/actions/marks/area/materialize.js +8 -9
  104. package/src/actions/marks/bar/create.js +10 -4
  105. package/src/actions/marks/bar/edit.js +8 -4
  106. package/src/actions/marks/bar/materialize.js +9 -6
  107. package/src/actions/marks/line/actions.js +11 -4
  108. package/src/actions/marks/line/materialize.js +9 -5
  109. package/src/actions/marks/point/edit.js +1 -1
  110. package/src/actions/marks/rect/actions.js +3 -3
  111. package/src/actions/marks/remove.js +2 -0
  112. package/src/actions/marks/rule/actions.js +7 -4
  113. package/src/actions/marks/text/actions.js +17 -5
  114. package/src/actions/marks/text/layout.js +3 -0
  115. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  116. package/src/actions/regression/create.js +1 -1
  117. package/src/actions/regression/edit.js +3 -3
  118. package/src/actions/regression/resolve.js +2 -6
  119. package/src/actions/scales/channels.js +9 -4
  120. package/src/actions/scales/consumers/families.js +16 -5
  121. package/src/actions/scales/create.js +8 -3
  122. package/src/actions/scales/definitions.js +1 -425
  123. package/src/actions/scales/edit.js +2 -2
  124. package/src/actions/scales/editPolicy.js +9 -3
  125. package/src/actions/scales/materialize.js +1 -1
  126. package/src/actions/scales/preview.js +21 -1
  127. package/src/actions/scales/quantitativeColor.js +3 -3
  128. package/src/actions/selection/actions.js +1 -13
  129. package/src/actions/theme/reconcile.js +81 -27
  130. package/src/actions/theme/state.js +12 -0
  131. package/src/actions/titles/actions.js +5 -18
  132. package/src/actions/titles/resolve.js +41 -19
  133. package/src/actions/violinPlots/edit.js +3 -20
  134. package/src/core/compositionState.js +4 -1
  135. package/src/core/font.js +9 -2
  136. package/src/core/optionRequirements.js +1 -0
  137. package/src/core/resourceReferences.js +3 -0
  138. package/src/core/textMetricProfile.js +3 -2
  139. package/src/core/textMetrics.js +41 -4
  140. package/src/core/validation.js +26 -0
  141. package/src/core/vocabulary.js +4 -0
  142. package/src/grammar/aggregate.js +4 -18
  143. package/src/grammar/arcs.js +1 -386
  144. package/src/grammar/areaPaint.js +10 -0
  145. package/src/grammar/areaSeries.js +18 -10
  146. package/src/grammar/bars/geometry.js +30 -3
  147. package/src/grammar/bars/policy.js +12 -1
  148. package/src/grammar/bin.js +2 -12
  149. package/src/grammar/bin2d.js +1 -6
  150. package/src/grammar/boxPlot.js +56 -9
  151. package/src/grammar/categoryOrder.js +1 -6
  152. package/src/grammar/complete.js +15 -19
  153. package/src/grammar/computed.js +92 -25
  154. package/src/grammar/curveCommands.js +29 -16
  155. package/src/grammar/datasetSchema.js +3 -7
  156. package/src/grammar/displayLabels.js +28 -0
  157. package/src/grammar/ecdf.js +2 -9
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/guides.js +5 -0
  160. package/src/grammar/facets/index.js +2 -5
  161. package/src/grammar/fold.js +2 -12
  162. package/src/grammar/gradientProfile.js +1 -6
  163. package/src/grammar/impute.js +21 -48
  164. package/src/grammar/interval.js +2 -9
  165. package/src/grammar/legendGradient.js +18 -0
  166. package/src/grammar/legendGroups.js +1 -0
  167. package/src/grammar/legendTitleStyle.js +22 -0
  168. package/src/grammar/lineSeries.js +32 -17
  169. package/src/grammar/markLabels.js +5 -2
  170. package/src/grammar/normalize.js +4 -54
  171. package/src/grammar/numeric.js +1 -290
  172. package/src/grammar/parallelCoordinates.js +1 -6
  173. package/src/grammar/parseTime.js +41 -0
  174. package/src/grammar/pathCommands.js +11 -4
  175. package/src/grammar/polar.js +1 -184
  176. package/src/grammar/positionCompatibility.js +3 -3
  177. package/src/grammar/regression/derive.js +7 -1
  178. package/src/grammar/regression/models.js +83 -55
  179. package/src/grammar/regression/parameters.js +14 -3
  180. package/src/grammar/roundedRect.js +42 -54
  181. package/src/grammar/scales/appearance.js +1 -206
  182. package/src/grammar/scales/color.js +1 -382
  183. package/src/grammar/scales/colorConsumers.js +5 -2
  184. package/src/grammar/scales/definition.js +1 -197
  185. package/src/grammar/scales/discretized.js +1 -195
  186. package/src/grammar/scales/size.js +1 -456
  187. package/src/grammar/scales/temporal.js +2 -13
  188. package/src/grammar/scales/transformed.js +1 -383
  189. package/src/grammar/scales/types.js +1 -1
  190. package/src/grammar/scales/validation.js +1 -66
  191. package/src/grammar/schemas/concreteGraphic.js +11 -0
  192. package/src/grammar/schemas/graphic.js +3 -0
  193. package/src/grammar/schemas/semanticPath.js +2 -1
  194. package/src/grammar/stack.js +2 -10
  195. package/src/grammar/statisticalReference.js +1 -6
  196. package/src/grammar/summary.js +3 -26
  197. package/src/grammar/text.js +19 -0
  198. package/src/grammar/timeUnit.js +16 -7
  199. package/src/grammar/transformKeys.js +47 -0
  200. package/src/grammar/transforms.js +4 -1
  201. package/src/grammar/valueFormat.js +17 -5
  202. package/src/grammar/weightedStatistics.js +1 -6
  203. package/src/grammar/window.js +4 -58
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -110
  206. package/src/layout/facets.js +1 -244
  207. package/src/layout/labels.js +1 -0
  208. package/src/layout/legend.js +13 -0
  209. package/src/layout/legendGroup.js +1 -0
  210. package/src/layout/legendItems.js +59 -15
  211. package/src/layout/legendLane.js +1 -441
  212. package/src/layout/legendText.js +1 -0
  213. package/src/layout/title.js +35 -13
  214. package/src/materialization/bars/aggregate.js +11 -5
  215. package/src/materialization/bars/histogram.js +4 -1
  216. package/src/materialization/bars/ranged.js +19 -6
  217. package/src/materialization/bars/resolve.js +22 -1
  218. package/src/materialization/coordinateBounds.js +8 -0
  219. package/src/materialization/dependencies.js +4 -44
  220. package/src/materialization/facetGuides/layout.js +1 -0
  221. package/src/materialization/facetGuides/placement.js +19 -8
  222. package/src/materialization/facetGuides/preparation.js +15 -7
  223. package/src/materialization/facetHeaders.js +1 -347
  224. package/src/materialization/facets.js +1 -185
  225. package/src/materialization/graphicHierarchy.js +1 -1
  226. package/src/materialization/guides/layout.js +1 -57
  227. package/src/materialization/guides/resources.js +1 -123
  228. package/src/materialization/layout.js +53 -0
  229. package/src/materialization/legendGroups.js +1 -0
  230. package/src/materialization/legendText.js +1 -0
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/marks/capabilities.js +5 -6
  233. package/src/materialization/rect.js +1 -1
  234. package/src/materialization/scaleGuideDependencies.js +1 -1
  235. package/src/materialization/scales/map.js +3 -0
  236. package/src/materialization/scales/policies/arc.js +8 -7
  237. package/src/materialization/scales/policies/bar.js +19 -7
  238. package/src/materialization/scales/policies/series.js +2 -1
  239. package/src/materialization/scales/resolve.js +5 -3
  240. package/src/materialization/selection/items/arc.js +1 -1
  241. package/src/materialization/selection/items/bar.js +7 -6
  242. package/src/materialization/selection/items/path.js +7 -6
  243. package/src/materialization/selection/items/rect.js +2 -2
  244. package/src/materialization/text.js +13 -4
  245. package/src/materialization/textLines.js +15 -0
  246. package/src/persistence/codec.js +37 -6
  247. package/src/persistence/validation.js +47 -0
  248. package/src/persistence.js +18 -5
  249. package/src/renderers/canvas/circle.js +6 -3
  250. package/src/renderers/canvas/fill.js +7 -1
  251. package/src/renderers/canvas/index.js +2 -1
  252. package/src/renderers/canvas/line.js +1 -0
  253. package/src/renderers/canvas/path.js +1 -1
  254. package/src/renderers/canvas/rect.js +11 -8
  255. package/src/renderers/canvas/stroke.js +2 -1
  256. package/src/renderers/canvas/text.js +5 -3
  257. package/src/renderers/svg.js +4 -1
  258. package/src/selectors/layers.js +19 -0
  259. package/src/theme/defaults.js +8 -2
  260. package/src/version.js +1 -1
  261. package/types/basic.d.ts +1 -0
  262. package/types/index.d.ts +7 -0
  263. package/types/program.d.ts +347 -85
@@ -1,8 +1,9 @@
1
1
  import { isPendingMeasuredRadiusConsumer } from "../../../materialization/scales/policies/arc.js";
2
- import { deriveMeasuredArcValues } from "../../../grammar/arcs.js";
2
+ import { deriveMeasuredArcValues, deriveProportionalArcRadiusValues } from "../../../grammar/arcs.js";
3
3
  import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
4
4
  import {
5
5
  BAR_GRAINS,
6
+ resolveBarChannels,
6
7
  resolveBarGrain
7
8
  } from "../../../grammar/bars/policy.js";
8
9
  import {
@@ -28,9 +29,15 @@ function lineDerivationOptions(program, consumer, dataset) {
28
29
  }
29
30
 
30
31
  export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
31
- if (consumer.layer.mark?.type === "arc" && consumer.channel === "radius" &&
32
- findScale(program, consumer.encoding.scale).radialMapping !== undefined) {
33
- return { matched: true, values: isPendingMeasuredRadiusConsumer(consumer) ? [] : deriveMeasuredArcValues(dataset.values, consumer.layer).map(item => item.radius) };
32
+ if (consumer.layer.mark?.type === "arc" && consumer.channel === "radius") {
33
+ let derive;
34
+ if (findScale(program, consumer.encoding.scale).radialMapping !== undefined) derive = deriveMeasuredArcValues;
35
+ else if (consumer.layer.encoding?.theta?.fieldType === "quantitative" ||
36
+ consumer.layer.encoding?.theta?.aggregate !== undefined || consumer.encoding.aggregate !== undefined) {
37
+ derive = deriveProportionalArcRadiusValues;
38
+ }
39
+ if (derive !== undefined) return { matched: true, values: isPendingMeasuredRadiusConsumer(consumer)
40
+ ? [] : derive(dataset.values, consumer.layer).map(item => item.radius) };
34
41
  }
35
42
 
36
43
  if (consumer.layer.mark?.type === "rect") {
@@ -44,6 +51,10 @@ export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
44
51
  };
45
52
  }
46
53
  const { layer, channel } = consumer;
54
+ if (resolveBarGrain(layer) === BAR_GRAINS.centered &&
55
+ channel === resolveBarChannels(layer).measure && layer.encoding[`${channel}2`] === undefined) {
56
+ return { matched: true, values: [0, ...dataset.values.map(row => row[consumer.encoding.field])] };
57
+ }
47
58
  const encoding = layer.encoding ?? {};
48
59
  const appearance = ["stroke", "strokeWidth", "opacity"].includes(channel);
49
60
  if (
@@ -75,7 +86,7 @@ export function resolveMarkFamilyConsumerValues(program, consumer, dataset) {
75
86
  : channel === "x" ? derived.xValues : derived.yValues
76
87
  };
77
88
  }
78
- if (resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate) {
89
+ if (resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate && ["x", "y", "color"].includes(consumer.channel)) {
79
90
  const derived = deriveBarAggregates(dataset.values, consumer.layer);
80
91
  if (consumer.channel === "color") {
81
92
  return {
@@ -87,14 +87,18 @@ export const createScale = /* @__PURE__ */ action(
87
87
  validateOptions(args);
88
88
  const id = validateUserId(args.id, "Scale id");
89
89
  const type = validateScaleType(args.type ?? "linear");
90
- const colorType = isColorScaleType(type);
90
+ const colorType = isColorScaleType(type) && (
91
+ !["log", "sqrt", "symlog"].includes(type) || args.palette !== undefined ||
92
+ args.interpolate !== undefined || (Array.isArray(args.range) && typeof args.range[0] === "string") ||
93
+ args.range?.palette !== undefined
94
+ );
91
95
  if (args.palette !== undefined && args.range !== undefined) {
92
96
  throw new Error("Color scale cannot specify both palette and range.");
93
97
  }
94
98
  if (args.palette !== undefined && !colorType) {
95
99
  throw new Error(`Scale type "${type}" does not support palette.`);
96
100
  }
97
- if (args.interpolate !== undefined && type !== "sequential") {
101
+ if (args.interpolate !== undefined && !colorType) {
98
102
  throw new Error(`Scale type "${type}" does not support interpolate.`);
99
103
  }
100
104
  const requestedRange = args.palette === undefined
@@ -129,6 +133,7 @@ export const createScale = /* @__PURE__ */ action(
129
133
  })
130
134
  : normalizeScaleDefinition({
131
135
  type,
136
+ color: colorType,
132
137
  patch: {
133
138
  ...args,
134
139
  ...(requestedRange === undefined ? {} : { range: requestedRange })
@@ -143,7 +148,7 @@ export const createScale = /* @__PURE__ */ action(
143
148
  validateRange: (scaleType, value) =>
144
149
  isDiscretizedColorScaleType(scaleType)
145
150
  ? validateDiscretizedColorRange(value)
146
- : isContinuousColorScaleType(scaleType)
151
+ : colorType && isContinuousColorScaleType(scaleType)
147
152
  ? validateSequentialColorRange(value)
148
153
  : scaleType === "ordinal"
149
154
  ? validateOrdinalRange(value)
@@ -1,425 +1 @@
1
- import { prepareScaleEdit } from "./editPolicy.js";
2
- import { validateUserId } from "../../core/identifiers.js";
3
- import { isPlainObject } from "../../core/immutable.js";
4
- import { validateKeys } from "../../core/validation.js";
5
- import {
6
- validateColorRange,
7
- validateContinuousColorInterpolation,
8
- validateSequentialMidpoint,
9
- validateLinearScaleType,
10
- validateOpacityRange,
11
- validateOrdinalDomain,
12
- validateOrdinalScaleType,
13
- validateScaleDomain,
14
- validateScaleRange,
15
- validateShapeRange,
16
- validateStrokeDashRange,
17
- validateStrokeWidthRange,
18
- validateSequentialColorRange,
19
- validateDiscretizedColorDomain,
20
- validateDiscretizedColorRange,
21
- validateTimeScaleType,
22
- normalizeScaleDefinition,
23
- SCALE_ROLES,
24
- validateScalePropertyForType,
25
- validateScaleTypeForRole,
26
- isDiscretePositionScaleType,
27
- normalizeSizeScaleDefinition,
28
- withScaleUnknown
29
- } from "../../grammar/scales/index.js";
30
- import {
31
- validateRadialRange,
32
- validateThetaRange
33
- } from "../../grammar/polar.js";
34
- import { findSemanticScale } from "../../selectors/scales.js";
35
- import { normalizeOffsetScalePolicy } from "../../grammar/bars/geometry.js";
36
- import { resolveRequestedOffsetPolicy } from
37
- "../../materialization/scales/policies/offset.js";
38
- import { findScaleConsumers } from "./consumers/index.js";
39
-
40
- const BASE_OPTIONS = ["id", "type", "domain", "range", "emptyDomain"];
41
- const UNKNOWN_OPTIONS = [...BASE_OPTIONS, "unknown"];
42
- const CLAMP_REVERSE = ["clamp", "reverse"];
43
- const BOOLEAN_OPTIONS = ["nice", "zero", ...CLAMP_REVERSE];
44
- const TRANSFORM_OPTIONS = ["base", "exponent", "constant"];
45
- const POSITION_OPTIONS = [
46
- ...BASE_OPTIONS,
47
- ...BOOLEAN_OPTIONS,
48
- ...TRANSFORM_OPTIONS,
49
- "paddingInner",
50
- "paddingOuter",
51
- "padding",
52
- "align",
53
- "unknown"
54
- ];
55
- const COLOR_OPTIONS = [...BASE_OPTIONS, "palette", "unknown"];
56
- const SEQUENTIAL_COLOR_OPTIONS = [
57
- ...COLOR_OPTIONS,
58
- "interpolate", "midpoint",
59
- ...CLAMP_REVERSE
60
- ];
61
- const OPACITY_OPTIONS = [...BASE_OPTIONS, ...BOOLEAN_OPTIONS, "unknown"];
62
- const STROKE_WIDTH_OPTIONS = [
63
- ...BASE_OPTIONS, ...BOOLEAN_OPTIONS, ...TRANSFORM_OPTIONS
64
- ];
65
- const SIZE_OPTIONS = [
66
- ...UNKNOWN_OPTIONS, "clamp", "reverse", "base", "exponent"
67
- ];
68
-
69
- function optionsObject(options) {
70
- if (!isPlainObject(options)) {
71
- throw new TypeError("Encoding scale must be a plain object.");
72
- }
73
- }
74
-
75
- function validateBooleanOptions(options, properties, type) {
76
- for (const property of properties) {
77
- if (options[property] !== undefined && typeof options[property] !== "boolean") {
78
- throw new TypeError(`Scale ${property} must be a boolean.`);
79
- }
80
- if (type !== undefined && options[property] !== undefined) {
81
- validateScalePropertyForType(type, property);
82
- }
83
- }
84
- }
85
-
86
- function assignOptions(scale, options, existing, properties) {
87
- for (const property of properties) {
88
- const value = options[property] ?? existing?.[property];
89
- if (value !== undefined) scale[property] = value;
90
- }
91
- return scale;
92
- }
93
-
94
- function validatePaletteRange(options) {
95
- if (options.palette !== undefined && options.range !== undefined) {
96
- throw new Error("Color scale cannot specify both palette and range.");
97
- }
98
- }
99
-
100
- export function resolvePositionScaleDefinition(
101
- program,
102
- channel,
103
- fieldType,
104
- options,
105
- defaults = {}
106
- ) {
107
- optionsObject(options);
108
- validateKeys(options, POSITION_OPTIONS, "scale");
109
- const id = validateUserId(options.id ?? channel, "Scale id");
110
- const existing = findSemanticScale(program, id);
111
- const discrete = ["ordinal", "nominal"].includes(fieldType);
112
- const expectedType = fieldType === "temporal"
113
- ? "time"
114
- : discrete
115
- ? defaults.discreteType ?? "point"
116
- : "linear";
117
- const type = options.type ?? existing?.type ?? expectedType;
118
- if (fieldType === "temporal") validateTimeScaleType(type);
119
- else if (discrete) {
120
- if (!isDiscretePositionScaleType(type)) {
121
- throw new Error(
122
- `Scale type "${type}" is not valid for discrete position.`
123
- );
124
- }
125
- }
126
- else if (channel === "theta") validateLinearScaleType(type);
127
- else validateScaleTypeForRole(type, SCALE_ROLES.quantitativePosition);
128
- const scale = {
129
- id,
130
- ...normalizeScaleDefinition({
131
- type,
132
- previous: existing,
133
- patch: { ...options, ...(defaults.radialMapping === undefined ? {} : { radialMapping: defaults.radialMapping }) },
134
- defaults: {
135
- ...(existing === undefined && defaults.nice !== undefined
136
- ? { nice: defaults.nice }
137
- : {}),
138
- ...(
139
- existing === undefined &&
140
- defaults.zero !== undefined &&
141
- (type !== "log" || defaults.zero)
142
- ? { zero: defaults.zero }
143
- : {})
144
- },
145
- retainCoreOnTypeChange: true,
146
- retainCompatibleOnTypeChange: true,
147
- validateDomain: (_scaleType, value) =>
148
- discrete
149
- ? validateOrdinalDomain(value)
150
- : validateScaleDomain(value),
151
- validateRange: (_scaleType, value) => channel === "theta"
152
- ? validateThetaRange(value)
153
- : channel === "radius"
154
- ? validateRadialRange(value)
155
- : validateScaleRange(value)
156
- })
157
- };
158
- return withScaleUnknown(scale, { ...existing, ...options }, channel);
159
- }
160
-
161
- export function resolveColorScaleDefinition(program, options, channel = "color") {
162
- optionsObject(options);
163
- validateKeys(options, COLOR_OPTIONS, "scale");
164
- validatePaletteRange(options);
165
- const id = validateUserId(options.id ?? channel, "Scale id");
166
- const existing = findSemanticScale(program, id);
167
- const previous = existing?.type === "ordinal" ? existing : undefined;
168
- const range = options.palette === undefined
169
- ? options.range
170
- : { palette: options.palette };
171
- return withScaleUnknown({
172
- id,
173
- type: validateOrdinalScaleType(options.type ?? previous?.type ?? "ordinal"),
174
- domain: validateOrdinalDomain(options.domain ?? previous?.domain ?? "auto"),
175
- range: validateColorRange(range ?? previous?.range ?? "auto")
176
- }, { ...previous, ...options }, channel);
177
- }
178
-
179
- function continuousDomain(value, fieldType) {
180
- if (value === "auto") return value;
181
- if (!Array.isArray(value) || value.length !== 2) {
182
- throw new TypeError("Continuous color domain must contain two values or auto.");
183
- }
184
- const normalized = value.map(item => {
185
- if (fieldType === "quantitative") return item;
186
- return typeof item === "string" ? Date.parse(item) : item;
187
- });
188
- if (!normalized.every(Number.isFinite) || normalized[0] === normalized[1]) {
189
- throw new TypeError("Continuous color domain requires two distinct valid values.");
190
- }
191
- return normalized;
192
- }
193
-
194
- export function resolveSequentialColorScaleDefinition(
195
- program,
196
- fieldType,
197
- options,
198
- channel = "color"
199
- ) {
200
- optionsObject(options);
201
- validateKeys(options, SEQUENTIAL_COLOR_OPTIONS, "scale");
202
- validatePaletteRange(options);
203
- const id = validateUserId(options.id ?? channel, "Scale id");
204
- const existing = findSemanticScale(program, id);
205
- const previous = existing?.type === "sequential" ? existing : undefined;
206
- const type = options.type ?? previous?.type ?? "sequential";
207
- if (type !== "sequential") {
208
- throw new Error(`Unsupported continuous color scale type "${type}".`);
209
- }
210
- validateBooleanOptions(options, CLAMP_REVERSE);
211
- const requestedRange = options.palette === undefined
212
- ? options.range
213
- : { palette: options.palette };
214
- const scale = {
215
- id,
216
- type,
217
- domain: continuousDomain(options.domain ?? previous?.domain ?? "auto", fieldType),
218
- range: validateSequentialColorRange(
219
- requestedRange ?? previous?.range ?? { palette: "viridis" }
220
- ),
221
- interpolate: validateContinuousColorInterpolation(
222
- options.interpolate ?? previous?.interpolate ?? "rgb"
223
- )
224
- };
225
- const midpoint = validateSequentialMidpoint(
226
- Object.hasOwn(options, "midpoint") ? options.midpoint : previous?.midpoint, type, scale.domain
227
- );
228
- if (midpoint !== undefined) {
229
- if (fieldType !== "quantitative") {
230
- throw new Error("Scale midpoint requires quantitative color consumers.");
231
- }
232
- scale.midpoint = midpoint;
233
- }
234
- return withScaleUnknown(
235
- assignOptions(scale, options, previous, CLAMP_REVERSE),
236
- { ...previous, ...options },
237
- channel
238
- );
239
- }
240
-
241
- export function resolveQuantitativeColorScaleDefinition(
242
- program,
243
- fieldType,
244
- options,
245
- channel = "color"
246
- ) {
247
- optionsObject(options);
248
- const existing = findSemanticScale(program, options.id ?? channel);
249
- const existingContinuous = existing !== undefined && (
250
- existing.type === "sequential" ||
251
- ["quantize", "quantile", "threshold"].includes(existing.type)
252
- );
253
- const type = options.type ?? (
254
- existingContinuous ? existing.type : "sequential"
255
- );
256
- if (existingContinuous && existing.type !== type) {
257
- validateKeys(options, type === "sequential" ? SEQUENTIAL_COLOR_OPTIONS : [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
258
- if (fieldType !== "quantitative") throw new Error("Color scale type transition requires quantitative color.");
259
- return { id: existing.id, ...prepareScaleEdit(program, existing, channel, [], options) };
260
- }
261
- if (type === "sequential") {
262
- return resolveSequentialColorScaleDefinition(program, fieldType, options, channel);
263
- }
264
- validateKeys(options, [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
265
- if (fieldType !== "quantitative") {
266
- throw new Error(`Scale type "${type}" requires quantitative color.`);
267
- }
268
- validateScaleTypeForRole(type, SCALE_ROLES.discretizedColor);
269
- validatePaletteRange(options);
270
- validateBooleanOptions(options, CLAMP_REVERSE, type);
271
- const id = validateUserId(options.id ?? channel, "Scale id");
272
- const previous = existing?.type === type ? existing : undefined;
273
- const requestedRange = options.palette === undefined
274
- ? options.range
275
- : { palette: options.palette };
276
- const scale = {
277
- id,
278
- type,
279
- domain: validateDiscretizedColorDomain(
280
- type,
281
- options.domain ?? previous?.domain ?? "auto"
282
- ),
283
- range: validateDiscretizedColorRange(
284
- requestedRange ?? previous?.range ?? { palette: "viridis" }
285
- )
286
- };
287
- return withScaleUnknown(
288
- assignOptions(scale, options, previous, CLAMP_REVERSE),
289
- { ...existing, ...options },
290
- channel
291
- );
292
- }
293
-
294
- function resolveOrdinalScaleDefinition(program, options, channel, rangeValidator) {
295
- optionsObject(options);
296
- validateKeys(options, UNKNOWN_OPTIONS, "scale");
297
- const id = validateUserId(options.id ?? channel, "Scale id");
298
- const existing = findSemanticScale(program, id);
299
- return withScaleUnknown({
300
- id,
301
- type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
302
- domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
303
- range: rangeValidator(options.range ?? existing?.range ?? "auto")
304
- }, { ...existing, ...options }, channel);
305
- }
306
-
307
- export function resolveStrokeDashScaleDefinition(program, options) {
308
- return resolveOrdinalScaleDefinition(
309
- program, options, "strokeDash", validateStrokeDashRange
310
- );
311
- }
312
-
313
- export function resolveAppearanceScaleDefinition(program, channel, options) {
314
- optionsObject(options);
315
- if (channel === "size") {
316
- validateKeys(options, SIZE_OPTIONS, "scale");
317
- const id = validateUserId(options.id ?? channel, "Scale id");
318
- const existing = findSemanticScale(program, id);
319
- return {
320
- id,
321
- ...normalizeSizeScaleDefinition({ previous: existing, patch: options })
322
- };
323
- }
324
- validateKeys(options, UNKNOWN_OPTIONS, "scale");
325
- const id = validateUserId(options.id ?? channel, "Scale id");
326
- const existing = findSemanticScale(program, id);
327
- return withScaleUnknown({
328
- id,
329
- type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
330
- domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
331
- range: validateShapeRange(options.range ?? existing?.range ?? "auto")
332
- }, { ...existing, ...options }, channel);
333
- }
334
-
335
- export function resolveOpacityScaleDefinition(program, options) {
336
- optionsObject(options);
337
- validateKeys(options, OPACITY_OPTIONS, "scale");
338
- const id = validateUserId(options.id ?? "opacity", "Scale id");
339
- const existing = findSemanticScale(program, id);
340
- const type = validateLinearScaleType(options.type ?? existing?.type ?? "linear");
341
- validateBooleanOptions(options, BOOLEAN_OPTIONS, type);
342
- const scale = {
343
- id,
344
- type,
345
- domain: validateScaleDomain(options.domain ?? existing?.domain ?? "auto"),
346
- range: validateOpacityRange(options.range ?? existing?.range ?? "auto")
347
- };
348
- return withScaleUnknown(
349
- assignOptions(scale, options, existing, BOOLEAN_OPTIONS),
350
- { ...existing, ...options },
351
- "opacity"
352
- );
353
- }
354
-
355
- export function resolveStrokeWidthScaleDefinition(program, options) {
356
- optionsObject(options);
357
- validateKeys(options, STROKE_WIDTH_OPTIONS, "scale");
358
- const id = validateUserId(options.id ?? "strokeWidth", "Scale id");
359
- const existing = findSemanticScale(program, id);
360
- const type = options.type ?? existing?.type ?? "linear";
361
- validateScaleTypeForRole(type, SCALE_ROLES.quantitativePosition);
362
- validateBooleanOptions(options, BOOLEAN_OPTIONS, type);
363
- for (const property of TRANSFORM_OPTIONS) {
364
- if (options[property] !== undefined) {
365
- validateScalePropertyForType(type, property);
366
- }
367
- }
368
- const domain = validateScaleDomain(options.domain ?? existing?.domain ?? "auto");
369
- if (domain !== "auto" && domain.some(value => value < 0)) {
370
- throw new RangeError("StrokeWidth scale domain cannot contain negative values.");
371
- }
372
- const scale = {
373
- id,
374
- type,
375
- domain,
376
- range: validateStrokeWidthRange(options.range ?? existing?.range ?? "auto")
377
- };
378
- return assignOptions(
379
- scale, options, existing, [...BOOLEAN_OPTIONS, ...TRANSFORM_OPTIONS]
380
- );
381
- }
382
-
383
- export function resolveOffsetScaleDefinition(program, options, channel = "xOffset") {
384
- optionsObject(options);
385
- validateKeys(options, [
386
- "id", "type", "domain", "range", "reverse",
387
- "padding", "paddingInner", "paddingOuter", "align"
388
- ], "scale");
389
- const id = validateUserId(options.id ?? channel, "Scale id");
390
- const existing = findSemanticScale(program, id);
391
- const type = options.type ?? existing?.type ?? "ordinal";
392
- if (type !== "ordinal") {
393
- throw new Error(`Scale type "${type}" is not valid for ${channel}.`);
394
- }
395
- if (Object.hasOwn(options, "range") && options.range !== "auto") {
396
- throw new Error(
397
- `${channel} scale range is derived from its parent categorical slot.`
398
- );
399
- }
400
- const consumers = findScaleConsumers(program, id).filter(
401
- consumer => consumer.channel === channel
402
- );
403
- const currentPolicy = resolveRequestedOffsetPolicy({
404
- scale: existing,
405
- consumers,
406
- markConfigs: program.markConfigs,
407
- id,
408
- channel
409
- });
410
- const policy = normalizeOffsetScalePolicy(options, currentPolicy, channel);
411
- const patch = Object.fromEntries(Object.entries(options).filter(
412
- ([property]) => !["id", "padding"].includes(property)
413
- ));
414
- return {
415
- id,
416
- ...normalizeScaleDefinition({
417
- type,
418
- previous: existing,
419
- patch: { ...patch, ...policy },
420
- allowOrdinalBandParameters: true,
421
- validateDomain: (_scaleType, value) => validateOrdinalDomain(value),
422
- validateRange: (_scaleType, value) => validateScaleRange(value)
423
- })
424
- };
425
- }
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};
@@ -11,7 +11,7 @@ import {
11
11
  applyMaterializationPlan,
12
12
  planCoordinateRematerialization
13
13
  } from "../../materialization/dependencies.js";
14
- import { findDataAspectCoordinatesForScale } from
14
+ import { axisScaleBindings, findDataAspectCoordinatesForScale } from
15
15
  "../../materialization/coordinateBounds.js";
16
16
  import {
17
17
  findSemanticScale,
@@ -115,7 +115,7 @@ function applyScaleEdit(program, {
115
115
  value: definition[property]
116
116
  });
117
117
  }
118
- if (consumers.length === 0) return next;
118
+ if (consumers.length === 0) return axisScaleBindings(next, id).length === 0 ? next : next.rematerializeScale({ id });
119
119
 
120
120
  const aspectCoordinates = findDataAspectCoordinatesForScale(next, id);
121
121
  if (aspectCoordinates.length > 0) {
@@ -1,6 +1,7 @@
1
1
  import { validateContinuousColorConsumer } from "../../grammar/scales/colorConsumers.js";
2
2
  import {
3
3
  hasOrdinalDomain,
4
+ isContinuousColorScaleType,
4
5
  isDiscretizedColorScaleType,
5
6
  isDiscretePositionScaleType,
6
7
  isTransformedScaleType,
@@ -53,7 +54,7 @@ function validateRangeForChannel(scale, channel, value) {
53
54
  if (channel === "size") return validateSizeRange(value);
54
55
  if (channel === "theta") return validateThetaRange(value);
55
56
  if (channel === "radius") return validateRadialRange(value);
56
- if (scale.type === "sequential") {
57
+ if (scale.type === "sequential" || (channel === "color" && isContinuousColorScaleType(scale.type))) {
57
58
  return validateSequentialColorRange(value);
58
59
  }
59
60
  if (isDiscretizedColorScaleType(scale.type)) {
@@ -72,7 +73,8 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
72
73
  if (nextType === scale.type) return;
73
74
  validateScaleType(nextType);
74
75
  if (consumers.length === 0) return;
75
- if (nextType === "sequential" || isDiscretizedColorScaleType(nextType)) {
76
+ if (nextType === "sequential" || isDiscretizedColorScaleType(nextType) ||
77
+ (channel === "color" && isContinuousColorScaleType(nextType))) {
76
78
  if (channel !== "color") {
77
79
  throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
78
80
  }
@@ -162,7 +164,9 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
162
164
  if (channel === "size") {
163
165
  if (consumers.some(consumer =>
164
166
  consumer.layer.mark?.type !== "point" ||
165
- consumer.encoding.fieldType !== "quantitative"
167
+ ((patch.type ?? scale.type) === "ordinal"
168
+ ? !["nominal", "ordinal"].includes(consumer.encoding.fieldType)
169
+ : consumer.encoding.fieldType !== "quantitative")
166
170
  )) {
167
171
  throw new Error(
168
172
  `Scale "${scale.id}" has a consumer incompatible with size mapping.`
@@ -208,6 +212,7 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
208
212
  : patch;
209
213
  const definition = normalizeScaleDefinition({
210
214
  type,
215
+ color: channel === "color" || scale.interpolate !== undefined,
211
216
  previous: scale,
212
217
  patch: normalizedPatch,
213
218
  retainCoreOnTypeChange: true,
@@ -252,6 +257,7 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
252
257
  }
253
258
 
254
259
  export function prepareScaleEdit(program, scale, channel, consumers, args) {
260
+ channel ??= scale.interpolate === undefined ? undefined : "color";
255
261
  const hasPalette = Object.hasOwn(args, "palette");
256
262
  if (hasPalette && Object.hasOwn(args, "range")) {
257
263
  throw new Error("editScale cannot specify both palette and range.");
@@ -37,7 +37,7 @@ export const rematerializeScale = /* @__PURE__ */ action(
37
37
  const id = validateUserId(args.id, "Scale id");
38
38
  const semanticScale = findScale(this, id);
39
39
  const consumers = findScaleConsumers(this, id);
40
- if (semanticScale.radialMapping !== undefined && consumers.length > 0 &&
40
+ if (consumers.length > 0 &&
41
41
  consumers.every(isPendingMeasuredRadiusConsumer)) {
42
42
  validatePendingMeasuredScale(this, semanticScale, consumers);
43
43
  return this._withoutResolvedScale(id);
@@ -5,6 +5,8 @@ import { findSemanticScale } from "../../selectors/scales.js";
5
5
  import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
6
6
  import { resolveGraphicBounds } from "../../layout/canvas.js";
7
7
  import {
8
+ axisScaleBindings,
9
+ resolveCoordinateBounds,
8
10
  resolveDataAspectScalePair,
9
11
  resolveScaleConsumerBounds
10
12
  } from "../../materialization/coordinateBounds.js";
@@ -19,10 +21,15 @@ import { findScale, findScaleConsumers, resolveConsumerCategoryOrder,
19
21
  function resolveScalePreviewAtBounds(program, id, bounds) {
20
22
  const scale = findScale(program, id);
21
23
  const consumers = findScaleConsumers(program, id);
22
- if (consumers.length === 0) throw new Error(`Scale "${id}" has no supported consumers.`);
24
+ const axes = axisScaleBindings(program, id);
25
+ if (consumers.length === 0 && axes.length === 0) throw new Error(`Scale "${id}" has no supported consumers.`);
26
+ if (consumers.length === 0 && (!Array.isArray(scale.domain) || scale.domain.length === 0)) {
27
+ throw new Error("Standalone axis scales require an explicit nonempty domain.");
28
+ }
23
29
  const consumerChannels = new Set(consumers.map(
24
30
  consumer => normalizePositionScaleChannel(consumer.channel)
25
31
  ));
32
+ for (const binding of axes) consumerChannels.add(binding.channel);
26
33
  const families = new Set([...consumerChannels].map(
27
34
  channel => channel === "stroke" ? "color" : channel
28
35
  ));
@@ -99,6 +106,12 @@ function dataAspectScaleIds(program) {
99
106
 
100
107
  export function resolveScalePreview(program, id) {
101
108
  const requestedConsumers = findScaleConsumers(program, id);
109
+ const axes = axisScaleBindings(program, id);
110
+ if (requestedConsumers.length === 0 && axes.length > 0) {
111
+ const coordinate = requireCoordinate(program, axes[0].coordinate);
112
+ if (coordinate.type !== "cartesian") throw new Error("Standalone axes require a Cartesian coordinate.");
113
+ return resolveScalePreviewAtBounds(program, id, resolveCoordinateBounds(program, coordinate.id));
114
+ }
102
115
  const positionChannels = new Set([
103
116
  "x", "y", "x2", "y2", "theta", "radius"
104
117
  ]);
@@ -126,6 +139,12 @@ export function resolveScalePreview(program, id) {
126
139
  provisional.consumers,
127
140
  { resolvedScales }
128
141
  );
142
+ for (const binding of axes) {
143
+ const guideBounds = resolveCoordinateBounds(program, binding.coordinate, { resolvedScales });
144
+ if (["x", "y", "width", "height"].some(key => Math.abs(guideBounds[key] - bounds[key]) > 1e-9)) {
145
+ throw new Error("Axis and mark scale consumers require matching coordinate bounds.");
146
+ }
147
+ }
129
148
  return resolveScalePreviewAtBounds(program, id, bounds);
130
149
  }
131
150
 
@@ -137,6 +156,7 @@ export function restoreResolvedScaleBindings(program) {
137
156
  const channels = new Set(consumers.map(consumer =>
138
157
  normalizePositionScaleChannel(consumer.channel)
139
158
  ));
159
+ for (const binding of axisScaleBindings(program, scale.id)) channels.add(binding.channel);
140
160
  const families = new Set([...channels].map(channel =>
141
161
  channel === "stroke" ? "color" : channel
142
162
  ));