ggaction 0.0.16 → 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 (299) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +3 -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 +10 -4
  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/bin.js +1 -1
  34. package/src/actions/data/box.js +2 -1
  35. package/src/actions/data/create.js +18 -5
  36. package/src/actions/data/density.js +1 -1
  37. package/src/actions/data/derived.js +15 -1
  38. package/src/actions/data/edit.js +20 -7
  39. package/src/actions/data/filter.js +15 -4
  40. package/src/actions/data/index.js +3 -0
  41. package/src/actions/data/interval.js +2 -0
  42. package/src/actions/data/regression.js +6 -2
  43. package/src/actions/data/revise.js +37 -5
  44. package/src/actions/data/shared.js +33 -4
  45. package/src/actions/data/sort.js +21 -0
  46. package/src/actions/data/stack.js +3 -1
  47. package/src/actions/data/summary.js +1 -1
  48. package/src/actions/encodings/angle.js +4 -1
  49. package/src/actions/encodings/appearance.js +43 -31
  50. package/src/actions/encodings/barWidth.js +36 -11
  51. package/src/actions/encodings/channels.js +0 -1
  52. package/src/actions/encodings/color/continuous.js +5 -2
  53. package/src/actions/encodings/color/index.js +5 -2
  54. package/src/actions/encodings/color/policy.js +10 -3
  55. package/src/actions/encodings/density/resolve.js +1 -6
  56. package/src/actions/encodings/offset.js +4 -1
  57. package/src/actions/encodings/position/policies/arc.js +12 -10
  58. package/src/actions/encodings/position/policies/area.js +3 -3
  59. package/src/actions/encodings/position/policies/bar.js +15 -6
  60. package/src/actions/encodings/position/policies/line.js +17 -6
  61. package/src/actions/encodings/position/resolve.js +9 -3
  62. package/src/actions/encodings/ranged.js +23 -1
  63. package/src/actions/encodings/remove.js +3 -2
  64. package/src/actions/encodings/ruleAppearance.js +4 -1
  65. package/src/actions/encodings/seriesLayout.js +1 -0
  66. package/src/actions/encodings/stroke.js +4 -1
  67. package/src/actions/encodings/strokeDash.js +6 -1
  68. package/src/actions/encodings/text.js +12 -4
  69. package/src/actions/errorBands/create.js +2 -2
  70. package/src/actions/errorBands/edit.js +2 -1
  71. package/src/actions/facets/actions.js +158 -240
  72. package/src/actions/gradientPlots/resolve.js +2 -17
  73. package/src/actions/guides/applicability.js +1 -1
  74. package/src/actions/guides/axes/axis.js +16 -4
  75. package/src/actions/guides/axes/edit.js +4 -4
  76. package/src/actions/guides/axes/labels.js +120 -46
  77. package/src/actions/guides/axes/lines.js +6 -5
  78. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  79. package/src/actions/guides/axes/parallel/policy.js +8 -7
  80. package/src/actions/guides/axes/remove.js +3 -2
  81. package/src/actions/guides/axes/tickGroups.js +5 -2
  82. package/src/actions/guides/axes/ticks.js +6 -3
  83. package/src/actions/guides/axes/titles.js +71 -26
  84. package/src/actions/guides/basic.js +2 -0
  85. package/src/actions/guides/facadeLegend.js +10 -2
  86. package/src/actions/guides/grids/grid.js +7 -1
  87. package/src/actions/guides/grids/resolve.js +59 -12
  88. package/src/actions/guides/legends/blocks.js +3 -1
  89. package/src/actions/guides/legends/categorical/actions.js +70 -17
  90. package/src/actions/guides/legends/categorical/components.js +12 -8
  91. package/src/actions/guides/legends/categorical/index.js +6 -0
  92. package/src/actions/guides/legends/categorical/layout.js +37 -8
  93. package/src/actions/guides/legends/categorical/options.js +31 -20
  94. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  95. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  96. package/src/actions/guides/legends/categorical/validation.js +1 -6
  97. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  98. package/src/actions/guides/legends/continuous/common.js +1 -447
  99. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  100. package/src/actions/guides/legends/continuous/interval.js +4 -4
  101. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  102. package/src/actions/guides/legends/edit.js +1 -604
  103. package/src/actions/guides/legends/focused.js +2 -2
  104. package/src/actions/guides/legends/group.js +1 -0
  105. package/src/actions/guides/legends/lane.js +1 -516
  106. package/src/actions/guides/legends/remove.js +3 -0
  107. package/src/actions/guides/legends/sampling.js +1 -1
  108. package/src/actions/guides/legends/size.js +51 -43
  109. package/src/actions/guides/legends/strokeWidth.js +11 -29
  110. package/src/actions/guides/legends/target.js +3 -3
  111. package/src/actions/guides/legends/transition.js +21 -7
  112. package/src/actions/guides/polar/axes/labels.js +4 -2
  113. package/src/actions/guides/polar/axes/titles.js +7 -5
  114. package/src/actions/guides/polar/resolve.js +6 -14
  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 +17 -4
  120. package/src/actions/marks/bar/edit.js +18 -7
  121. package/src/actions/marks/bar/materialize.js +9 -6
  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/create.js +8 -1
  125. package/src/actions/marks/point/edit.js +13 -6
  126. package/src/actions/marks/point/materialize.js +4 -2
  127. package/src/actions/marks/rect/actions.js +26 -11
  128. package/src/actions/marks/remove.js +2 -0
  129. package/src/actions/marks/rule/actions.js +31 -12
  130. package/src/actions/marks/text/actions.js +42 -11
  131. package/src/actions/marks/text/layout.js +3 -0
  132. package/src/actions/marks/tick/actions.js +23 -8
  133. package/src/actions/primitives/semanticAction.js +37 -3
  134. package/src/actions/primitives/semanticValidation/dataset.js +13 -1
  135. package/src/actions/primitives/semanticValidation/layer.js +26 -2
  136. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  137. package/src/actions/regression/create.js +26 -5
  138. package/src/actions/regression/edit.js +50 -15
  139. package/src/actions/regression/follow.js +22 -0
  140. package/src/actions/regression/resolve.js +2 -6
  141. package/src/actions/scales/channels.js +20 -15
  142. package/src/actions/scales/consumers/common.js +2 -1
  143. package/src/actions/scales/consumers/families.js +16 -5
  144. package/src/actions/scales/create.js +10 -4
  145. package/src/actions/scales/definitions.js +1 -425
  146. package/src/actions/scales/edit.js +3 -3
  147. package/src/actions/scales/editPolicy.js +9 -3
  148. package/src/actions/scales/materialize.js +1 -1
  149. package/src/actions/scales/parallel.js +1 -1
  150. package/src/actions/scales/patch.js +1 -1
  151. package/src/actions/scales/preview.js +49 -2
  152. package/src/actions/scales/quantitativeColor.js +3 -3
  153. package/src/actions/selection/actions.js +1 -13
  154. package/src/actions/theme/reconcile.js +83 -27
  155. package/src/actions/theme/state.js +12 -0
  156. package/src/actions/titles/actions.js +5 -18
  157. package/src/actions/titles/resolve.js +41 -19
  158. package/src/actions/violinPlots/edit.js +3 -20
  159. package/src/core/action.js +3 -2
  160. package/src/core/compositionState.js +4 -1
  161. package/src/core/font.js +9 -2
  162. package/src/core/optionRequirements.js +1 -0
  163. package/src/core/programState.js +5 -1
  164. package/src/core/resourceReferences.js +3 -0
  165. package/src/core/textMetricProfile.js +3 -2
  166. package/src/core/textMetrics.js +41 -4
  167. package/src/core/validation.js +26 -0
  168. package/src/core/vocabulary.js +4 -0
  169. package/src/grammar/aggregate.js +4 -18
  170. package/src/grammar/arcs.js +1 -386
  171. package/src/grammar/areaPaint.js +10 -0
  172. package/src/grammar/areaSeries.js +18 -10
  173. package/src/grammar/bars/geometry.js +30 -3
  174. package/src/grammar/bars/policy.js +12 -1
  175. package/src/grammar/bin.js +41 -22
  176. package/src/grammar/bin2d.js +1 -6
  177. package/src/grammar/boxPlot.js +56 -9
  178. package/src/grammar/categoryOrder.js +1 -6
  179. package/src/grammar/complete.js +15 -19
  180. package/src/grammar/computed.js +92 -25
  181. package/src/grammar/curveCommands.js +29 -16
  182. package/src/grammar/datasetSchema.js +382 -0
  183. package/src/grammar/density.js +53 -5
  184. package/src/grammar/displayLabels.js +28 -0
  185. package/src/grammar/ecdf.js +2 -9
  186. package/src/grammar/facets/dependencies.js +6 -2
  187. package/src/grammar/facets/guides.js +5 -0
  188. package/src/grammar/facets/index.js +2 -5
  189. package/src/grammar/filter.js +133 -39
  190. package/src/grammar/fold.js +2 -12
  191. package/src/grammar/gradientProfile.js +1 -6
  192. package/src/grammar/impute.js +21 -48
  193. package/src/grammar/interval.js +34 -10
  194. package/src/grammar/itemMissing.js +59 -0
  195. package/src/grammar/legendGradient.js +18 -0
  196. package/src/grammar/legendGroups.js +1 -0
  197. package/src/grammar/legendTitleStyle.js +22 -0
  198. package/src/grammar/lineSeries.js +32 -17
  199. package/src/grammar/markLabels.js +5 -2
  200. package/src/grammar/normalize.js +4 -54
  201. package/src/grammar/numeric.js +1 -290
  202. package/src/grammar/parallelCoordinates.js +1 -6
  203. package/src/grammar/parseTime.js +41 -0
  204. package/src/grammar/pathCommands.js +11 -4
  205. package/src/grammar/polar.js +1 -184
  206. package/src/grammar/positionCompatibility.js +3 -3
  207. package/src/grammar/regression/derive.js +63 -16
  208. package/src/grammar/regression/index.js +1 -0
  209. package/src/grammar/regression/models.js +116 -59
  210. package/src/grammar/regression/parameters.js +69 -6
  211. package/src/grammar/roundedRect.js +42 -54
  212. package/src/grammar/scales/appearance.js +1 -206
  213. package/src/grammar/scales/color.js +1 -382
  214. package/src/grammar/scales/colorConsumers.js +5 -2
  215. package/src/grammar/scales/definition.js +1 -190
  216. package/src/grammar/scales/discretized.js +1 -195
  217. package/src/grammar/scales/size.js +1 -456
  218. package/src/grammar/scales/temporal.js +2 -13
  219. package/src/grammar/scales/transformed.js +1 -383
  220. package/src/grammar/scales/types.js +1 -1
  221. package/src/grammar/scales/validation.js +1 -66
  222. package/src/grammar/schemas/concreteGraphic.js +11 -0
  223. package/src/grammar/schemas/graphic.js +3 -0
  224. package/src/grammar/schemas/semanticPath.js +5 -3
  225. package/src/grammar/sort.js +88 -0
  226. package/src/grammar/stack.js +2 -10
  227. package/src/grammar/statisticalReference.js +1 -6
  228. package/src/grammar/summary.js +120 -45
  229. package/src/grammar/text.js +19 -0
  230. package/src/grammar/timeUnit.js +16 -7
  231. package/src/grammar/transformKeys.js +47 -0
  232. package/src/grammar/transformTopology.js +1 -0
  233. package/src/grammar/transforms.js +25 -12
  234. package/src/grammar/valueFormat.js +17 -5
  235. package/src/grammar/weightedStatistics.js +1 -6
  236. package/src/grammar/window.js +4 -58
  237. package/src/inspection.js +495 -0
  238. package/src/inspectionDescriptors.js +3 -0
  239. package/src/layout/canvas.js +1 -110
  240. package/src/layout/facets.js +1 -244
  241. package/src/layout/labels.js +1 -0
  242. package/src/layout/legend.js +13 -0
  243. package/src/layout/legendGroup.js +1 -0
  244. package/src/layout/legendItems.js +59 -15
  245. package/src/layout/legendLane.js +1 -441
  246. package/src/layout/legendText.js +1 -0
  247. package/src/layout/title.js +35 -13
  248. package/src/materialization/bars/aggregate.js +11 -5
  249. package/src/materialization/bars/histogram.js +4 -1
  250. package/src/materialization/bars/ranged.js +19 -6
  251. package/src/materialization/bars/resolve.js +26 -3
  252. package/src/materialization/coordinateBounds.js +8 -0
  253. package/src/materialization/dependencies.js +4 -44
  254. package/src/materialization/facetGuides/layout.js +1 -0
  255. package/src/materialization/facetGuides/placement.js +19 -8
  256. package/src/materialization/facetGuides/preparation.js +15 -7
  257. package/src/materialization/facetHeaders.js +1 -347
  258. package/src/materialization/facets.js +1 -185
  259. package/src/materialization/graphicHierarchy.js +1 -1
  260. package/src/materialization/guides/layout.js +1 -57
  261. package/src/materialization/guides/resources.js +1 -123
  262. package/src/materialization/layout.js +53 -0
  263. package/src/materialization/legendGroups.js +1 -0
  264. package/src/materialization/legendText.js +1 -0
  265. package/src/materialization/legends.js +1 -1
  266. package/src/materialization/marks/capabilities.js +5 -6
  267. package/src/materialization/rect.js +1 -1
  268. package/src/materialization/revisionIdentity.js +19 -0
  269. package/src/materialization/scaleGuideDependencies.js +1 -1
  270. package/src/materialization/scales/map.js +3 -0
  271. package/src/materialization/scales/policies/arc.js +8 -7
  272. package/src/materialization/scales/policies/bar.js +19 -7
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +72 -7
  275. package/src/materialization/selection/items/arc.js +1 -1
  276. package/src/materialization/selection/items/bar.js +7 -6
  277. package/src/materialization/selection/items/path.js +7 -6
  278. package/src/materialization/selection/items/rect.js +2 -2
  279. package/src/materialization/text.js +17 -6
  280. package/src/materialization/textLines.js +15 -0
  281. package/src/persistence/codec.js +37 -6
  282. package/src/persistence/validation.js +47 -0
  283. package/src/persistence.js +94 -8
  284. package/src/renderers/canvas/circle.js +6 -3
  285. package/src/renderers/canvas/fill.js +7 -1
  286. package/src/renderers/canvas/index.js +2 -1
  287. package/src/renderers/canvas/line.js +1 -0
  288. package/src/renderers/canvas/path.js +1 -1
  289. package/src/renderers/canvas/rect.js +11 -8
  290. package/src/renderers/canvas/stroke.js +2 -1
  291. package/src/renderers/canvas/text.js +5 -3
  292. package/src/renderers/svg.js +4 -1
  293. package/src/selectors/layers.js +19 -0
  294. package/src/theme/defaults.js +8 -2
  295. package/src/version.js +1 -1
  296. package/types/basic.d.ts +1 -0
  297. package/types/index.d.ts +7 -0
  298. package/types/inspection.d.ts +17 -0
  299. package/types/program.d.ts +494 -112
@@ -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"];
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,
@@ -26,7 +26,7 @@ import { hasExactLegendSamplingForScale } from
26
26
  "../guides/legends/sampling.js";
27
27
 
28
28
  const OPTIONS = Object.freeze([
29
- "id", "type", "domain", "range", "nice", "zero", "clamp", "reverse",
29
+ "id", "type", "domain", "range", "emptyDomain", "nice", "zero", "clamp", "reverse",
30
30
  "base", "exponent", "constant", "paddingInner", "paddingOuter", "padding",
31
31
  "align", "interpolate", "midpoint", "unknown", "palette", "radialMapping"
32
32
  ]);
@@ -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);
@@ -6,7 +6,7 @@ import { requireLayer } from "../../selectors/layers.js";
6
6
  import { requireSemanticScale } from "../../selectors/scales.js";
7
7
 
8
8
  const QUANTITATIVE_OPTIONS = Object.freeze([
9
- "type", "domain", "range", "nice", "zero", "clamp", "reverse",
9
+ "type", "domain", "range", "emptyDomain", "nice", "zero", "clamp", "reverse",
10
10
  "base", "exponent", "constant", "unknown"
11
11
  ]);
12
12
  const ORDINAL_OPTIONS = Object.freeze([
@@ -1,5 +1,5 @@
1
1
  const SCALE_EDIT_PROPERTIES = Object.freeze([
2
- "type", "domain", "range", "nice", "zero", "clamp", "reverse",
2
+ "type", "domain", "range", "emptyDomain", "nice", "zero", "clamp", "reverse",
3
3
  "base", "exponent", "constant", "paddingInner", "paddingOuter",
4
4
  "padding", "align", "interpolate", "midpoint", "unknown"
5
5
  ]);
@@ -5,21 +5,31 @@ 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";
11
13
  import { requireCoordinate } from "../../selectors/coordinates.js";
12
- import { resolveScaleMaterialization } from "../../materialization/scales/resolve.js";
14
+ import {
15
+ rememberResolvedScaleBinding,
16
+ resolveScaleMaterialization
17
+ } from "../../materialization/scales/resolve.js";
13
18
  import { findScale, findScaleConsumers, resolveConsumerCategoryOrder,
14
19
  resolveConsumerValues, resolveSeriesLayoutScaleValues } from "./consumers/index.js";
15
20
 
16
21
  function resolveScalePreviewAtBounds(program, id, bounds) {
17
22
  const scale = findScale(program, id);
18
23
  const consumers = findScaleConsumers(program, id);
19
- 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
+ }
20
29
  const consumerChannels = new Set(consumers.map(
21
30
  consumer => normalizePositionScaleChannel(consumer.channel)
22
31
  ));
32
+ for (const binding of axes) consumerChannels.add(binding.channel);
23
33
  const families = new Set([...consumerChannels].map(
24
34
  channel => channel === "stroke" ? "color" : channel
25
35
  ));
@@ -96,6 +106,12 @@ function dataAspectScaleIds(program) {
96
106
 
97
107
  export function resolveScalePreview(program, id) {
98
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
+ }
99
115
  const positionChannels = new Set([
100
116
  "x", "y", "x2", "y2", "theta", "radius"
101
117
  ]);
@@ -123,9 +139,40 @@ export function resolveScalePreview(program, id) {
123
139
  provisional.consumers,
124
140
  { resolvedScales }
125
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
+ }
126
148
  return resolveScalePreviewAtBounds(program, id, bounds);
127
149
  }
128
150
 
151
+ export function restoreResolvedScaleBindings(program) {
152
+ for (const scale of program.semanticSpec.scales) {
153
+ const resolved = program.resolvedScales[scale.id];
154
+ if (resolved === undefined) continue;
155
+ const consumers = findScaleConsumers(program, scale.id);
156
+ const channels = new Set(consumers.map(consumer =>
157
+ normalizePositionScaleChannel(consumer.channel)
158
+ ));
159
+ for (const binding of axisScaleBindings(program, scale.id)) channels.add(binding.channel);
160
+ const families = new Set([...channels].map(channel =>
161
+ channel === "stroke" ? "color" : channel
162
+ ));
163
+ if (families.size === 1) {
164
+ const channel = channels.size === 1
165
+ ? channels.values().next().value
166
+ : families.values().next().value;
167
+ rememberResolvedScaleBinding(resolved, channel, consumers);
168
+ }
169
+ }
170
+ for (const child of Object.values(program.children)) {
171
+ restoreResolvedScaleBindings(child);
172
+ }
173
+ return program;
174
+ }
175
+
129
176
  export function validatePendingMeasuredScale(program, scale, consumers) {
130
177
  const bounds = resolveScaleConsumerBounds(program, consumers);
131
178
  const range = resolveArcAutoPositionRange({ consumers, scale, channel: "radius",
@@ -7,7 +7,7 @@ const OPTIONS = Object.freeze([
7
7
  "type",
8
8
  "domain",
9
9
  "range",
10
- "interpolate", "midpoint",
10
+ "interpolate", "midpoint", "base", "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", "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);
@@ -34,7 +34,7 @@ export const setQuantitativeColorScale = /* @__PURE__ */ closedAction(
34
34
  next = next.editSemantic({ property: `scale[${args.id}].midpoint`, remove: true });
35
35
  }
36
36
  for (const property of [
37
- "type", "domain", "range", "interpolate", "midpoint", "clamp", "reverse", "unknown"
37
+ "type", "domain", "range", "interpolate", "midpoint", "base", "constant", "clamp", "reverse", "unknown"
38
38
  ]) {
39
39
  if (!Object.hasOwn(args, property)) continue;
40
40
  if (existing?.[property] === args[property]) continue;