ggaction 0.0.17 → 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 (315) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -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 +20 -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 +8 -7
  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/derived.js +11 -1
  35. package/src/actions/data/edit.js +3 -1
  36. package/src/actions/data/index.js +4 -0
  37. package/src/actions/data/regression.js +25 -16
  38. package/src/actions/data/regressionStatistics.js +73 -0
  39. package/src/actions/data/shared.js +8 -4
  40. package/src/actions/data/stack.js +3 -1
  41. package/src/actions/distributions/revision.js +24 -18
  42. package/src/actions/encodings/appearance.js +37 -29
  43. package/src/actions/encodings/barWidth.js +36 -11
  44. package/src/actions/encodings/channels.js +0 -1
  45. package/src/actions/encodings/color/continuous.js +1 -1
  46. package/src/actions/encodings/color/index.js +1 -1
  47. package/src/actions/encodings/color/policy.js +10 -3
  48. package/src/actions/encodings/density/resolve.js +1 -6
  49. package/src/actions/encodings/position/policies/arc.js +12 -10
  50. package/src/actions/encodings/position/policies/area.js +3 -3
  51. package/src/actions/encodings/position/policies/bar.js +15 -6
  52. package/src/actions/encodings/position/policies/line.js +17 -6
  53. package/src/actions/encodings/position/resolve.js +3 -2
  54. package/src/actions/encodings/ranged.js +17 -0
  55. package/src/actions/encodings/remove.js +14 -8
  56. package/src/actions/encodings/seriesLayout.js +1 -0
  57. package/src/actions/encodings/shared.js +26 -24
  58. package/src/actions/encodings/text.js +6 -3
  59. package/src/actions/errorBands/create.js +2 -2
  60. package/src/actions/errorBands/edit.js +2 -1
  61. package/src/actions/facets/actions.js +176 -247
  62. package/src/actions/facets/guides.js +2 -1
  63. package/src/actions/gradientPlots/resolve.js +2 -17
  64. package/src/actions/guides/applicability.js +1 -1
  65. package/src/actions/guides/axes/axes.js +30 -11
  66. package/src/actions/guides/axes/axis.js +72 -10
  67. package/src/actions/guides/axes/components.js +13 -13
  68. package/src/actions/guides/axes/edit.js +36 -24
  69. package/src/actions/guides/axes/labels.js +155 -71
  70. package/src/actions/guides/axes/lines.js +27 -24
  71. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  72. package/src/actions/guides/axes/parallel/policy.js +8 -7
  73. package/src/actions/guides/axes/remove.js +13 -8
  74. package/src/actions/guides/axes/tickGroups.js +11 -7
  75. package/src/actions/guides/axes/ticks.js +36 -24
  76. package/src/actions/guides/axes/titles.js +107 -69
  77. package/src/actions/guides/basic.js +2 -0
  78. package/src/actions/guides/facadeAxes.js +33 -24
  79. package/src/actions/guides/facadeLegend.js +12 -4
  80. package/src/actions/guides/grids/grid.js +8 -2
  81. package/src/actions/guides/grids/resolve.js +102 -58
  82. package/src/actions/guides/legends/blocks.js +46 -20
  83. package/src/actions/guides/legends/categorical/actions.js +95 -19
  84. package/src/actions/guides/legends/categorical/components.js +15 -18
  85. package/src/actions/guides/legends/categorical/index.js +8 -0
  86. package/src/actions/guides/legends/categorical/layout.js +45 -20
  87. package/src/actions/guides/legends/categorical/options.js +39 -22
  88. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  89. package/src/actions/guides/legends/categorical/resolve.js +11 -4
  90. package/src/actions/guides/legends/categorical/symbols.js +38 -18
  91. package/src/actions/guides/legends/categorical/validation.js +1 -6
  92. package/src/actions/guides/legends/colorSize.js +110 -0
  93. package/src/actions/guides/legends/combine.js +37 -0
  94. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  95. package/src/actions/guides/legends/continuous/common.js +1 -447
  96. package/src/actions/guides/legends/continuous/gradient.js +73 -46
  97. package/src/actions/guides/legends/continuous/interval.js +4 -4
  98. package/src/actions/guides/legends/continuous/opacity.js +33 -21
  99. package/src/actions/guides/legends/creation.js +2 -0
  100. package/src/actions/guides/legends/edit.js +1 -604
  101. package/src/actions/guides/legends/focused.js +8 -7
  102. package/src/actions/guides/legends/group.js +1 -0
  103. package/src/actions/guides/legends/lane.js +1 -516
  104. package/src/actions/guides/legends/lifecycle.js +2 -1
  105. package/src/actions/guides/legends/remove.js +9 -3
  106. package/src/actions/guides/legends/sampling.js +1 -1
  107. package/src/actions/guides/legends/size.js +65 -47
  108. package/src/actions/guides/legends/strokeWidth.js +13 -29
  109. package/src/actions/guides/legends/target.js +2 -79
  110. package/src/actions/guides/legends/transition.js +44 -11
  111. package/src/actions/guides/polar/axes/labels.js +4 -2
  112. package/src/actions/guides/polar/axes/titles.js +7 -5
  113. package/src/actions/guides/polar/resolve.js +6 -14
  114. package/src/actions/guides/tickValues.js +8 -5
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +10 -4
  120. package/src/actions/marks/bar/edit.js +29 -14
  121. package/src/actions/marks/bar/materialize.js +15 -7
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/edit.js +1 -1
  125. package/src/actions/marks/point/materialize.js +5 -1
  126. package/src/actions/marks/rect/actions.js +3 -3
  127. package/src/actions/marks/remove.js +12 -10
  128. package/src/actions/marks/rule/actions.js +7 -4
  129. package/src/actions/marks/text/actions.js +17 -5
  130. package/src/actions/marks/text/layout.js +3 -0
  131. package/src/actions/primitives/createGraphics.js +3 -2
  132. package/src/actions/primitives/editGraphics.js +15 -11
  133. package/src/actions/primitives/graphicProperties.js +1 -6
  134. package/src/actions/primitives/semanticAction.js +27 -1
  135. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  136. package/src/actions/primitives/semanticValidation/layer.js +8 -0
  137. package/src/actions/regression/create.js +1 -1
  138. package/src/actions/regression/edit.js +18 -6
  139. package/src/actions/regression/resolve.js +2 -6
  140. package/src/actions/scales/channels.js +9 -4
  141. package/src/actions/scales/consumers/families.js +16 -5
  142. package/src/actions/scales/create.js +10 -5
  143. package/src/actions/scales/definitions.js +1 -425
  144. package/src/actions/scales/edit.js +3 -3
  145. package/src/actions/scales/editPolicy.js +12 -3
  146. package/src/actions/scales/materialize.js +1 -1
  147. package/src/actions/scales/patch.js +1 -1
  148. package/src/actions/scales/preview.js +21 -1
  149. package/src/actions/scales/quantitativeColor.js +8 -3
  150. package/src/actions/selection/actions.js +1 -13
  151. package/src/actions/theme/reconcile.js +126 -45
  152. package/src/actions/theme/state.js +12 -0
  153. package/src/actions/titles/actions.js +5 -18
  154. package/src/actions/titles/resolve.js +41 -19
  155. package/src/actions/violinPlots/edit.js +3 -20
  156. package/src/core/compositionState.js +6 -3
  157. package/src/core/font.js +9 -2
  158. package/src/core/graphicProperties.js +1 -0
  159. package/src/core/optionRequirements.js +1 -0
  160. package/src/core/programState.js +4 -0
  161. package/src/core/resourceReferences.js +5 -2
  162. package/src/core/textMetricProfile.js +3 -2
  163. package/src/core/textMetrics.js +41 -4
  164. package/src/core/validation.js +35 -0
  165. package/src/core/vocabulary.js +7 -0
  166. package/src/grammar/aggregate.js +4 -18
  167. package/src/grammar/arcs.js +1 -386
  168. package/src/grammar/areaPaint.js +10 -0
  169. package/src/grammar/areaSeries.js +18 -10
  170. package/src/grammar/axisTextPlacement.js +1 -0
  171. package/src/grammar/bars/geometry.js +30 -3
  172. package/src/grammar/bars/policy.js +12 -1
  173. package/src/grammar/bin.js +2 -12
  174. package/src/grammar/bin2d.js +1 -6
  175. package/src/grammar/boxPlot.js +56 -9
  176. package/src/grammar/categoryOrder.js +1 -6
  177. package/src/grammar/colorSizeLegend.js +39 -0
  178. package/src/grammar/complete.js +15 -19
  179. package/src/grammar/computed.js +115 -26
  180. package/src/grammar/curveCommands.js +29 -16
  181. package/src/grammar/datasetSchema.js +13 -9
  182. package/src/grammar/displayLabels.js +36 -0
  183. package/src/grammar/ecdf.js +2 -9
  184. package/src/grammar/facets/dependencies.js +6 -2
  185. package/src/grammar/facets/guides.js +58 -30
  186. package/src/grammar/facets/index.js +2 -5
  187. package/src/grammar/facets/legendIntent.js +65 -0
  188. package/src/grammar/fold.js +2 -12
  189. package/src/grammar/gradientProfile.js +1 -6
  190. package/src/grammar/guideDash.js +1 -0
  191. package/src/grammar/impute.js +21 -48
  192. package/src/grammar/interval.js +2 -9
  193. package/src/grammar/legendBlocks.js +1 -0
  194. package/src/grammar/legendBorder.js +9 -0
  195. package/src/grammar/legendClip.js +19 -0
  196. package/src/grammar/legendCombinations.js +45 -0
  197. package/src/grammar/legendGradient.js +27 -0
  198. package/src/grammar/legendGroups.js +1 -0
  199. package/src/grammar/legendOpacity.js +6 -0
  200. package/src/grammar/legendTitleStyle.js +22 -0
  201. package/src/grammar/lineSeries.js +32 -17
  202. package/src/grammar/markLabels.js +5 -2
  203. package/src/grammar/normalize.js +4 -54
  204. package/src/grammar/numeric.js +1 -290
  205. package/src/grammar/parallelCoordinates.js +1 -6
  206. package/src/grammar/parseTime.js +41 -0
  207. package/src/grammar/pathCommands.js +11 -4
  208. package/src/grammar/pointShapes.js +16 -2
  209. package/src/grammar/polar.js +1 -184
  210. package/src/grammar/positionCompatibility.js +3 -3
  211. package/src/grammar/regression/derive.js +16 -2
  212. package/src/grammar/regression/models.js +142 -54
  213. package/src/grammar/regression/parameters.js +23 -4
  214. package/src/grammar/regression/statistics.js +64 -0
  215. package/src/grammar/roundedRect.js +42 -54
  216. package/src/grammar/scales/appearance.js +1 -206
  217. package/src/grammar/scales/color.js +1 -382
  218. package/src/grammar/scales/colorConsumers.js +5 -2
  219. package/src/grammar/scales/definition.js +1 -197
  220. package/src/grammar/scales/discretized.js +1 -195
  221. package/src/grammar/scales/size.js +1 -456
  222. package/src/grammar/scales/temporal.js +2 -13
  223. package/src/grammar/scales/transformed.js +1 -383
  224. package/src/grammar/scales/types.js +1 -1
  225. package/src/grammar/scales/validation.js +1 -66
  226. package/src/grammar/schemas/concreteGraphic.js +14 -0
  227. package/src/grammar/schemas/graphic.js +6 -0
  228. package/src/grammar/schemas/graphicBounds.js +31 -7
  229. package/src/grammar/schemas/semanticPath.js +19 -3
  230. package/src/grammar/stack.js +2 -10
  231. package/src/grammar/statisticalReference.js +1 -6
  232. package/src/grammar/summary.js +4 -27
  233. package/src/grammar/text.js +19 -0
  234. package/src/grammar/timeUnit.js +22 -11
  235. package/src/grammar/transformKeys.js +47 -0
  236. package/src/grammar/transformTopology.js +1 -0
  237. package/src/grammar/transforms.js +15 -2
  238. package/src/grammar/valueFormat.js +17 -5
  239. package/src/grammar/weightedStatistics.js +1 -6
  240. package/src/grammar/window.js +4 -58
  241. package/src/inspectionDescriptors.js +1 -1
  242. package/src/layout/canvas.js +1 -110
  243. package/src/layout/facets.js +1 -244
  244. package/src/layout/guideCollisions.js +9 -1
  245. package/src/layout/labels.js +1 -0
  246. package/src/layout/legend.js +13 -0
  247. package/src/layout/legendGroup.js +1 -0
  248. package/src/layout/legendItems.js +59 -15
  249. package/src/layout/legendLane.js +1 -441
  250. package/src/layout/legendText.js +1 -0
  251. package/src/layout/title.js +35 -13
  252. package/src/materialization/bars/aggregate.js +11 -5
  253. package/src/materialization/bars/histogram.js +4 -1
  254. package/src/materialization/bars/ranged.js +19 -6
  255. package/src/materialization/bars/resolve.js +22 -1
  256. package/src/materialization/compositionSnapshot.js +2 -7
  257. package/src/materialization/coordinateBounds.js +21 -0
  258. package/src/materialization/dependencies.js +4 -44
  259. package/src/materialization/facetGuides/edit.js +30 -0
  260. package/src/materialization/facetGuides/layout.js +1 -0
  261. package/src/materialization/facetGuides/owner.js +125 -0
  262. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  263. package/src/materialization/facetGuides/placement.js +71 -14
  264. package/src/materialization/facetGuides/preparation.js +22 -7
  265. package/src/materialization/facetHeaders.js +1 -347
  266. package/src/materialization/facets.js +1 -185
  267. package/src/materialization/graphicHierarchy.js +5 -1
  268. package/src/materialization/graphicTranslation.js +73 -0
  269. package/src/materialization/guides/layout.js +1 -57
  270. package/src/materialization/guides/resources.js +1 -123
  271. package/src/materialization/layout.js +53 -0
  272. package/src/materialization/legendBorder.js +28 -0
  273. package/src/materialization/legendClip.js +76 -0
  274. package/src/materialization/legendGroups.js +1 -0
  275. package/src/materialization/legendText.js +1 -0
  276. package/src/materialization/legends.js +2 -2
  277. package/src/materialization/marks/capabilities.js +5 -6
  278. package/src/materialization/rect.js +1 -1
  279. package/src/materialization/scaleGuideDependencies.js +14 -15
  280. package/src/materialization/scales/map.js +6 -0
  281. package/src/materialization/scales/policies/arc.js +8 -7
  282. package/src/materialization/scales/policies/bar.js +19 -7
  283. package/src/materialization/scales/policies/series.js +2 -1
  284. package/src/materialization/scales/resolve.js +7 -3
  285. package/src/materialization/selection/items/arc.js +1 -1
  286. package/src/materialization/selection/items/bar.js +7 -6
  287. package/src/materialization/selection/items/path.js +7 -6
  288. package/src/materialization/selection/items/rect.js +2 -2
  289. package/src/materialization/text.js +13 -4
  290. package/src/materialization/textLines.js +15 -0
  291. package/src/materialization/typography.js +10 -2
  292. package/src/persistence/codec.js +37 -6
  293. package/src/persistence/validation.js +220 -0
  294. package/src/persistence.js +19 -6
  295. package/src/renderers/canvas/circle.js +6 -3
  296. package/src/renderers/canvas/fill.js +7 -1
  297. package/src/renderers/canvas/index.js +22 -11
  298. package/src/renderers/canvas/isolated.js +166 -0
  299. package/src/renderers/canvas/limits.js +3 -0
  300. package/src/renderers/canvas/line.js +1 -0
  301. package/src/renderers/canvas/path.js +1 -1
  302. package/src/renderers/canvas/rect.js +11 -8
  303. package/src/renderers/canvas/stroke.js +2 -1
  304. package/src/renderers/canvas/text.js +5 -3
  305. package/src/renderers/pdf.js +2 -2
  306. package/src/renderers/png.js +2 -2
  307. package/src/renderers/svg.js +14 -4
  308. package/src/selectors/axes.js +1 -0
  309. package/src/selectors/graphicCapacity.js +38 -0
  310. package/src/selectors/layers.js +19 -0
  311. package/src/theme/defaults.js +8 -2
  312. package/src/version.js +1 -1
  313. package/types/basic.d.ts +3 -0
  314. package/types/index.d.ts +12 -0
  315. package/types/program.d.ts +536 -129
@@ -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 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};
@@ -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,
@@ -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))
@@ -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,
@@ -11,6 +12,7 @@ import {
11
12
  validateDiscretizedColorDomain,
12
13
  validateDiscretizedColorRange,
13
14
  validateOrdinalDomain,
15
+ validateOrdinalRange,
14
16
  validateScaleDomain,
15
17
  validateScaleRange,
16
18
  validateScaleType,
@@ -53,7 +55,7 @@ function validateRangeForChannel(scale, channel, value) {
53
55
  if (channel === "size") return validateSizeRange(value);
54
56
  if (channel === "theta") return validateThetaRange(value);
55
57
  if (channel === "radius") return validateRadialRange(value);
56
- if (scale.type === "sequential") {
58
+ if (scale.type === "sequential" || (channel === "color" && isContinuousColorScaleType(scale.type))) {
57
59
  return validateSequentialColorRange(value);
58
60
  }
59
61
  if (isDiscretizedColorScaleType(scale.type)) {
@@ -65,6 +67,7 @@ function validateRangeForChannel(scale, channel, value) {
65
67
  if (channel === "strokeDash") return validateStrokeDashRange(value);
66
68
  if (channel === "size") return validateSizeRange(value);
67
69
  if (channel === "strokeWidth") return validateStrokeWidthRange(value);
70
+ if (channel === undefined) return validateOrdinalRange(value);
68
71
  return validateScaleRange(value);
69
72
  }
70
73
 
@@ -72,7 +75,8 @@ function validateTypeTransition(scale, nextType, channel, consumers) {
72
75
  if (nextType === scale.type) return;
73
76
  validateScaleType(nextType);
74
77
  if (consumers.length === 0) return;
75
- if (nextType === "sequential" || isDiscretizedColorScaleType(nextType)) {
78
+ if (nextType === "sequential" || isDiscretizedColorScaleType(nextType) ||
79
+ (channel === "color" && isContinuousColorScaleType(nextType))) {
76
80
  if (channel !== "color") {
77
81
  throw new Error(`Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`);
78
82
  }
@@ -162,7 +166,9 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
162
166
  if (channel === "size") {
163
167
  if (consumers.some(consumer =>
164
168
  consumer.layer.mark?.type !== "point" ||
165
- consumer.encoding.fieldType !== "quantitative"
169
+ ((patch.type ?? scale.type) === "ordinal"
170
+ ? !["nominal", "ordinal"].includes(consumer.encoding.fieldType)
171
+ : consumer.encoding.fieldType !== "quantitative")
166
172
  )) {
167
173
  throw new Error(
168
174
  `Scale "${scale.id}" has a consumer incompatible with size mapping.`
@@ -208,10 +214,12 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
208
214
  : patch;
209
215
  const definition = normalizeScaleDefinition({
210
216
  type,
217
+ color: channel === "color" || scale.interpolate !== undefined,
211
218
  previous: scale,
212
219
  patch: normalizedPatch,
213
220
  retainCoreOnTypeChange: true,
214
221
  retainCompatibleOnTypeChange: true,
222
+ allowColorReset: true,
215
223
  allowOrdinalBandParameters: offset,
216
224
  validateDomain: (scaleType, value) =>
217
225
  isDiscretizedColorScaleType(scaleType)
@@ -252,6 +260,7 @@ function normalizeDefinition(program, scale, channel, consumers, patch) {
252
260
  }
253
261
 
254
262
  export function prepareScaleEdit(program, scale, channel, consumers, args) {
263
+ channel ??= scale.interpolate === undefined ? undefined : "color";
255
264
  const hasPalette = Object.hasOwn(args, "palette");
256
265
  if (hasPalette && Object.hasOwn(args, "range")) {
257
266
  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);
@@ -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) {
@@ -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
  ));
@@ -7,7 +7,7 @@ const OPTIONS = Object.freeze([
7
7
  "type",
8
8
  "domain",
9
9
  "range",
10
- "interpolate", "midpoint",
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", "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", "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;