ggaction 0.0.3 → 0.0.5

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 (223) hide show
  1. package/CHANGELOG.md +58 -3
  2. package/README.md +117 -83
  3. package/package.json +27 -11
  4. package/src/actions/boxPlots/create.js +13 -7
  5. package/src/actions/boxPlots/edit.js +31 -35
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +8 -1
  8. package/src/actions/boxPlots/resolve.js +10 -2
  9. package/src/actions/charts/bar.js +50 -0
  10. package/src/actions/charts/heatmap.js +205 -0
  11. package/src/actions/charts/histogram.js +58 -0
  12. package/src/actions/charts/index.js +15 -0
  13. package/src/actions/charts/line.js +69 -0
  14. package/src/actions/charts/parallel.js +76 -0
  15. package/src/actions/charts/scatter.js +56 -0
  16. package/src/actions/charts/shared.js +121 -0
  17. package/src/actions/coordinates/parallel.js +50 -0
  18. package/src/actions/data/bin2d.js +137 -0
  19. package/src/actions/data/density.js +54 -13
  20. package/src/actions/data/derived.js +22 -0
  21. package/src/actions/data/filter.js +5 -0
  22. package/src/actions/data/gradientProfile.js +65 -0
  23. package/src/actions/data/horizon.js +71 -0
  24. package/src/actions/data/index.js +29 -2
  25. package/src/actions/data/regression.js +1 -1
  26. package/src/actions/data/window.js +50 -0
  27. package/src/actions/encodings/appearance.js +1 -1
  28. package/src/actions/encodings/color/continuous.js +137 -0
  29. package/src/actions/encodings/color/index.js +167 -0
  30. package/src/actions/encodings/color/layout.js +99 -0
  31. package/src/actions/encodings/color/policy.js +88 -0
  32. package/src/actions/encodings/density/resolve.js +109 -0
  33. package/src/actions/encodings/density.js +193 -88
  34. package/src/actions/encodings/horizon/resolve.js +283 -0
  35. package/src/actions/encodings/horizon.js +364 -0
  36. package/src/actions/encodings/index.js +8 -2
  37. package/src/actions/encodings/offset.js +1 -1
  38. package/src/actions/encodings/parallel.js +115 -0
  39. package/src/actions/encodings/pathOrder.js +131 -0
  40. package/src/actions/encodings/position/apply.js +73 -4
  41. package/src/actions/encodings/{position.js → position/index.js} +21 -8
  42. package/src/actions/encodings/position/policies/arc.js +27 -4
  43. package/src/actions/encodings/position/policies/area.js +33 -2
  44. package/src/actions/encodings/position/policies/index.js +7 -2
  45. package/src/actions/encodings/position/policies/line.js +18 -2
  46. package/src/actions/encodings/position/resolve.js +25 -3
  47. package/src/actions/encodings/ranged.js +5 -3
  48. package/src/actions/encodings/ruleAppearance.js +88 -10
  49. package/src/actions/encodings/strokeDash.js +1 -1
  50. package/src/actions/encodings/text.js +1 -1
  51. package/src/actions/errorBands/options.js +1 -1
  52. package/src/actions/errorBars/options.js +1 -1
  53. package/src/actions/facets/actions.js +2 -3
  54. package/src/actions/facets/derive.js +35 -5
  55. package/src/actions/facets/guides.js +1 -1
  56. package/src/actions/facets/replay.js +7 -36
  57. package/src/actions/gradientPlots/components.js +177 -0
  58. package/src/actions/gradientPlots/create.js +96 -0
  59. package/src/actions/gradientPlots/edit.js +153 -0
  60. package/src/actions/gradientPlots/index.js +24 -0
  61. package/src/actions/gradientPlots/materialize.js +287 -0
  62. package/src/actions/gradientPlots/options.js +120 -0
  63. package/src/actions/gradientPlots/paint.js +107 -0
  64. package/src/actions/gradientPlots/rebind.js +51 -0
  65. package/src/actions/gradientPlots/resolve.js +81 -0
  66. package/src/actions/guides/applicability.js +44 -17
  67. package/src/actions/guides/axes/axes.js +37 -3
  68. package/src/actions/guides/axes/index.js +2 -0
  69. package/src/actions/guides/axes/labels.js +12 -7
  70. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  71. package/src/actions/guides/axes/parallel.js +228 -0
  72. package/src/actions/guides/axes/ticks.js +1 -1
  73. package/src/actions/guides/axes/titles.js +12 -9
  74. package/src/actions/guides/grids/resolve.js +1 -1
  75. package/src/actions/guides/guides.js +60 -5
  76. package/src/actions/guides/legends/categorical/actions.js +48 -11
  77. package/src/actions/guides/legends/categorical/layout.js +4 -2
  78. package/src/actions/guides/legends/continuous/interval.js +1 -1
  79. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  80. package/src/actions/guides/legends/edit.js +7 -20
  81. package/src/actions/guides/legends/index.js +2 -0
  82. package/src/actions/guides/legends/remove.js +6 -31
  83. package/src/actions/guides/legends/size.js +1 -1
  84. package/src/actions/guides/legends/strokeWidth.js +170 -0
  85. package/src/actions/guides/legends/target.js +25 -0
  86. package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
  87. package/src/actions/guides/polar/index.js +1 -1
  88. package/src/actions/guides/polar/resolve.js +1 -1
  89. package/src/actions/guides/tickValues.js +1 -1
  90. package/src/actions/index.js +6 -0
  91. package/src/actions/intervals/resolve.js +1 -1
  92. package/src/actions/marks/arc/actions.js +2 -2
  93. package/src/actions/marks/area/actions.js +61 -137
  94. package/src/actions/marks/area/materialize.js +163 -0
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/index.js +5 -1
  97. package/src/actions/marks/line/actions.js +100 -81
  98. package/src/actions/marks/line/materialize.js +146 -0
  99. package/src/actions/marks/point/create.js +76 -0
  100. package/src/actions/marks/point/edit.js +85 -0
  101. package/src/actions/marks/point/index.js +11 -1
  102. package/src/actions/marks/point/jitter.js +72 -0
  103. package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
  104. package/src/actions/marks/rect/actions.js +4 -1
  105. package/src/actions/marks/remove.js +13 -2
  106. package/src/actions/marks/rule/actions.js +26 -4
  107. package/src/actions/marks/shared.js +1 -1
  108. package/src/actions/marks/text/actions.js +20 -5
  109. package/src/actions/marks/text/index.js +7 -1
  110. package/src/actions/marks/text/layout.js +321 -0
  111. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  112. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  113. package/src/actions/primitives/semanticValidation/index.js +23 -0
  114. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  115. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  116. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  117. package/src/actions/primitives/semanticValue.js +1 -204
  118. package/src/actions/regression/components.js +1 -1
  119. package/src/actions/regression/create.js +1 -1
  120. package/src/actions/regression/edit.js +20 -22
  121. package/src/actions/scales/consumers/common.js +109 -0
  122. package/src/actions/scales/consumers/families.js +58 -0
  123. package/src/actions/scales/consumers/index.js +41 -0
  124. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
  125. package/src/actions/scales/create.js +1 -1
  126. package/src/actions/scales/definitions.js +32 -2
  127. package/src/actions/scales/edit.js +8 -206
  128. package/src/actions/scales/editPolicy.js +214 -0
  129. package/src/actions/scales/materialize.js +3 -3
  130. package/src/actions/scales/patch.js +26 -0
  131. package/src/actions/selection/actions.js +28 -3
  132. package/src/actions/violinPlots/create.js +198 -0
  133. package/src/actions/violinPlots/index.js +5 -0
  134. package/src/core/ChartProgram.js +22 -236
  135. package/src/core/compositionState.js +169 -0
  136. package/src/core/materializationState.js +65 -0
  137. package/src/core/programState.js +28 -0
  138. package/src/core/textMetrics.js +47 -0
  139. package/src/core/vocabulary.js +7 -2
  140. package/src/grammar/arcs.js +50 -13
  141. package/src/grammar/areaSeries.js +42 -6
  142. package/src/grammar/bars/aggregate.js +1 -1
  143. package/src/grammar/bin2d.js +333 -0
  144. package/src/grammar/categoricalDensity.js +192 -0
  145. package/src/grammar/coordinates.js +1 -1
  146. package/src/grammar/curveCommands.js +3 -1
  147. package/src/grammar/density.js +253 -27
  148. package/src/grammar/facets/dependencies.js +3 -11
  149. package/src/grammar/{facets.js → facets/index.js} +8 -6
  150. package/src/grammar/gradientProfile.js +213 -0
  151. package/src/grammar/horizon.js +455 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/jitter.js +197 -0
  154. package/src/grammar/lineSeries.js +70 -1
  155. package/src/grammar/paint.js +88 -0
  156. package/src/grammar/palettes.js +5 -7
  157. package/src/grammar/parallelCoordinates.js +213 -0
  158. package/src/grammar/pathOrder.js +35 -0
  159. package/src/grammar/pointShapes.js +24 -0
  160. package/src/grammar/polarLineCommands.js +1 -1
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +2 -1
  166. package/src/grammar/scales/appearance.js +21 -0
  167. package/src/grammar/scales/color.js +4 -2
  168. package/src/grammar/schemas/concreteGraphic.js +11 -1
  169. package/src/grammar/schemas/graphicBounds.js +19 -37
  170. package/src/grammar/schemas/semanticPath.js +17 -2
  171. package/src/grammar/statistics/studentT.js +98 -0
  172. package/src/grammar/transforms.js +101 -12
  173. package/src/grammar/window.js +339 -0
  174. package/src/layout/labels.js +270 -0
  175. package/src/layout/text.js +4 -1
  176. package/src/layout/title.js +10 -20
  177. package/src/materialization/bars/aggregate.js +1 -1
  178. package/src/materialization/bars/grouped.js +1 -1
  179. package/src/materialization/bars/histogram.js +1 -1
  180. package/src/materialization/bars/ranged.js +1 -1
  181. package/src/materialization/dataProvenance.js +56 -2
  182. package/src/materialization/density.js +1 -1
  183. package/src/materialization/dependencies.js +15 -101
  184. package/src/materialization/encodings.js +2 -2
  185. package/src/materialization/facetGuides/index.js +2 -0
  186. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  187. package/src/materialization/facetGuides/placement.js +126 -0
  188. package/src/materialization/facetGuides/preparation.js +188 -0
  189. package/src/materialization/facets.js +1 -1
  190. package/src/materialization/guides/resources.js +74 -24
  191. package/src/materialization/horizon.js +29 -0
  192. package/src/materialization/layout.js +15 -0
  193. package/src/materialization/marks/capabilities.js +161 -0
  194. package/src/materialization/marks/index.js +108 -0
  195. package/src/materialization/marks/pathOrder.js +71 -0
  196. package/src/materialization/marks/policies.js +91 -0
  197. package/src/materialization/rect.js +1 -1
  198. package/src/materialization/rowEncoding.js +1 -1
  199. package/src/materialization/scaleGuideDependencies.js +102 -0
  200. package/src/materialization/scales/map.js +1 -1
  201. package/src/materialization/scales/policies/series.js +1 -1
  202. package/src/materialization/scales/resolve.js +3 -1
  203. package/src/materialization/selection/items/bar.js +1 -1
  204. package/src/materialization/selection/items/path.js +27 -1
  205. package/src/materialization/selection/items/point.js +1 -5
  206. package/src/materialization/selection/items/rect.js +21 -0
  207. package/src/materialization/selection/styles.js +21 -10
  208. package/src/materialization/text.js +12 -4
  209. package/src/renderers/canvas/fill.js +33 -0
  210. package/src/renderers/canvas/index.js +7 -0
  211. package/src/renderers/canvas/path.js +7 -4
  212. package/src/renderers/canvas/rect.js +9 -3
  213. package/src/renderers/canvas/text.js +14 -1
  214. package/src/selectors/datasets.js +4 -0
  215. package/src/theme/defaults.js +4 -0
  216. package/types/extension.d.ts +10 -1
  217. package/types/index.d.ts +76 -0
  218. package/types/program.d.ts +632 -10
  219. package/src/actions/encodings/color.js +0 -424
  220. package/src/grammar/regression.js +0 -578
  221. package/src/grammar/scales.js +0 -1
  222. package/src/materialization/facetGuides.js +0 -426
  223. package/src/materialization/marks.js +0 -316
@@ -1,144 +1,26 @@
1
- import { deriveBarAggregates } from "../../grammar/bars/aggregate.js";
1
+ import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
2
  import {
3
3
  BAR_GRAINS,
4
4
  resolveBarChannels,
5
5
  resolveBarColorLayout,
6
6
  resolveBarGrain
7
- } from "../../grammar/bars/policy.js";
7
+ } from "../../../grammar/bars/policy.js";
8
8
  import {
9
9
  countHistogramBins,
10
10
  findHistogramBinIndex,
11
11
  resolveHistogramBins
12
- } from "../../grammar/histogram.js";
13
- import { deriveDensityAreaSeries } from "../../grammar/areaSeries.js";
14
- import { deriveLineSeries } from "../../grammar/lineSeries.js";
15
- import { resolveSeriesLayoutDomainValues } from "../../grammar/seriesLayout.js";
12
+ } from "../../../grammar/histogram.js";
13
+ import { deriveDensityAreaSeries } from "../../../grammar/areaSeries.js";
14
+ import {
15
+ resolveSeriesLayoutDomainValues
16
+ } from "../../../grammar/seriesLayout.js";
16
17
  import {
17
18
  readNominalField,
18
19
  readQuantitativeField,
19
- readScaleField,
20
- readTemporalField,
21
20
  resolveOrdinalDomain
22
- } from "../../grammar/scales.js";
23
- import { findDataset } from "../../selectors/datasets.js";
24
- import { requireSemanticScale } from "../../selectors/scales.js";
25
- import { isAggregate } from "../../grammar/aggregate.js";
26
- import { normalizeRuleDatum } from "../../grammar/rules.js";
27
- import { SCALED_ENCODING_CHANNELS } from "../../core/vocabulary.js";
28
- import { resolveRectConsumerValues } from "../../materialization/rect.js";
29
-
30
- export function findScale(program, id) {
31
- return requireSemanticScale(program, id);
32
- }
33
-
34
- export function findScaleConsumers(program, id) {
35
- const consumers = [];
36
- for (const layer of program.semanticSpec.layers) {
37
- for (const channel of SCALED_ENCODING_CHANNELS) {
38
- const encoding = layer.encoding?.[channel];
39
- if (encoding?.scale === id) consumers.push({ layer, channel, encoding });
40
- }
41
- }
42
- return consumers;
43
- }
44
-
45
- export function resolveConsumerValues(program, consumer) {
46
- const dataset = findDataset(program, consumer.layer.data);
47
- if (dataset === undefined) {
48
- throw new Error(
49
- `Mark "${consumer.layer.id}" references unknown dataset "${consumer.layer.data}".`
50
- );
51
- }
52
-
53
- if (Object.hasOwn(consumer.encoding, "datum")) {
54
- return [normalizeRuleDatum(
55
- consumer.encoding.datum,
56
- consumer.encoding.fieldType,
57
- consumer.channel
58
- )];
59
- }
60
-
61
- if (
62
- consumer.channel === "strokeDash" &&
63
- !["line", "rule"].includes(consumer.layer.mark?.type)
64
- ) {
65
- throw new Error("strokeDash scale materialization requires a line mark or rule mark.");
66
- }
67
- const scale = findScale(program, consumer.encoding.scale);
68
- const allowUnknown = Object.hasOwn(scale, "unknown");
69
- if (consumer.layer.mark?.type === "rect") {
70
- return resolveRectConsumerValues(
71
- consumer.layer,
72
- dataset,
73
- consumer.channel
74
- );
75
- }
76
-
77
- if (
78
- ["color", "strokeDash", "xOffset", "yOffset", "shape"].includes(consumer.channel) &&
79
- consumer.encoding.fieldType === "nominal"
80
- ) {
81
- return allowUnknown
82
- ? readScaleField(
83
- dataset.values,
84
- consumer.encoding.field,
85
- consumer.encoding.fieldType,
86
- { allowUnknown: true }
87
- )
88
- : readNominalField(dataset.values, consumer.encoding.field);
89
- }
90
-
91
- if (
92
- consumer.layer.mark?.type === "line" &&
93
- isAggregate(consumer.layer.encoding?.y?.aggregate)
94
- ) {
95
- const derived = deriveLineSeries(dataset.values, consumer.layer);
96
- return consumer.channel === "x" ? derived.xValues : derived.yValues;
97
- }
98
-
99
- if (
100
- resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate
101
- ) {
102
- const derived = deriveBarAggregates(dataset.values, consumer.layer);
103
- if (consumer.channel === "color") {
104
- return derived.values.map(value => value.color);
105
- }
106
- return consumer.channel === "x" ? derived.xValues : derived.yValues;
107
- }
108
-
109
- if (consumer.encoding.fieldType === "temporal") {
110
- return allowUnknown
111
- ? readScaleField(dataset.values, consumer.encoding.field, "temporal", {
112
- allowUnknown: true
113
- })
114
- : readTemporalField(dataset.values, consumer.encoding.field);
115
- }
116
- if (["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
117
- if (!["ordinal", "band", "point"].includes(scale.type)) {
118
- throw new Error(
119
- `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
120
- );
121
- }
122
- return allowUnknown
123
- ? readScaleField(
124
- dataset.values,
125
- consumer.encoding.field,
126
- consumer.encoding.fieldType,
127
- { allowUnknown: true }
128
- )
129
- : readNominalField(dataset.values, consumer.encoding.field);
130
- }
131
- if (consumer.encoding.fieldType !== "quantitative") {
132
- throw new Error(
133
- `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
134
- );
135
- }
136
- return allowUnknown
137
- ? readScaleField(dataset.values, consumer.encoding.field, "quantitative", {
138
- allowUnknown: true
139
- })
140
- : readQuantitativeField(dataset.values, consumer.encoding.field);
141
- }
21
+ } from "../../../grammar/scales/index.js";
22
+ import { findDataset } from "../../../selectors/datasets.js";
23
+ import { findScale } from "./common.js";
142
24
 
143
25
  export function resolveHistogramCountValues(program, consumer) {
144
26
  const xEncoding = consumer.layer.encoding?.x;
@@ -146,7 +28,6 @@ export function resolveHistogramCountValues(program, consumer) {
146
28
  ? undefined
147
29
  : findScale(program, xEncoding.scale);
148
30
  const dataset = findDataset(program, consumer.layer.data);
149
-
150
31
  if (xEncoding?.bin === undefined || xScale === undefined) {
151
32
  throw new Error(
152
33
  `Histogram mark "${consumer.layer.id}" requires a binned x scale.`
@@ -157,7 +38,6 @@ export function resolveHistogramCountValues(program, consumer) {
157
38
  `Histogram mark "${consumer.layer.id}" requires an existing dataset.`
158
39
  );
159
40
  }
160
-
161
41
  const xValues = readQuantitativeField(dataset.values, xEncoding.field);
162
42
  const bins = resolveHistogramBins({
163
43
  values: xValues,
@@ -186,7 +66,6 @@ function resolveHistogramPartitions(program, consumer) {
186
66
  if (colorEncoding?.scale === undefined) {
187
67
  return countHistogramBins(xValues, bins.boundaries).map(value => [value]);
188
68
  }
189
-
190
69
  const colorScale = findScale(program, colorEncoding.scale);
191
70
  const colorValues = readNominalField(dataset.values, colorEncoding.field);
192
71
  const colorDomain = resolveOrdinalDomain(colorScale.domain, colorValues);
@@ -224,7 +103,6 @@ function resolveAggregatePartitions(program, consumer) {
224
103
  ]));
225
104
  return categoryDomain.map(value => [byCategory.get(value) ?? 0]);
226
105
  }
227
-
228
106
  const colorScale = findScale(program, colorEncoding.scale);
229
107
  const colorValues = readNominalField(dataset.values, colorEncoding.field);
230
108
  const colorDomain = resolveOrdinalDomain(colorScale.domain, colorValues);
@@ -15,7 +15,7 @@ import {
15
15
  isContinuousColorScaleType,
16
16
  isDiscretizedColorScaleType,
17
17
  normalizeScaleDefinition
18
- } from "../../grammar/scales.js";
18
+ } from "../../grammar/scales/index.js";
19
19
  import { findSemanticScale } from "../../selectors/scales.js";
20
20
 
21
21
  const CREATE_SCALE_OPTIONS = Object.freeze([
@@ -13,6 +13,7 @@ import {
13
13
  validateShapeRange,
14
14
  validateSizeRange,
15
15
  validateStrokeDashRange,
16
+ validateStrokeWidthRange,
16
17
  validateSequentialColorRange,
17
18
  validateDiscretizedColorDomain,
18
19
  validateDiscretizedColorRange,
@@ -22,12 +23,12 @@ import {
22
23
  validateScalePropertyForType,
23
24
  validateScaleTypeForRole,
24
25
  isDiscretePositionScaleType
25
- } from "../../grammar/scales.js";
26
+ } from "../../grammar/scales/index.js";
26
27
  import {
27
28
  validateRadialRange,
28
29
  validateThetaRange
29
30
  } from "../../grammar/polar.js";
30
- import { withScaleUnknown } from "../../grammar/scales.js";
31
+ import { withScaleUnknown } from "../../grammar/scales/index.js";
31
32
  import { findSemanticScale } from "../../selectors/scales.js";
32
33
 
33
34
  const BASE_OPTIONS = Object.freeze(["id", "type", "domain", "range"]);
@@ -314,6 +315,35 @@ export function resolveOpacityScaleDefinition(program, options) {
314
315
  return withScaleUnknown(scale, { ...existing, ...options }, "opacity");
315
316
  }
316
317
 
318
+ export function resolveStrokeWidthScaleDefinition(program, options) {
319
+ optionsObject(options);
320
+ validateKeys(options, OPACITY_OPTIONS, "scale");
321
+ const id = validateUserId(options.id ?? "strokeWidth", "Scale id");
322
+ const existing = findSemanticScale(program, id);
323
+ const type = options.type ?? existing?.type ?? "linear";
324
+ validateScaleTypeForRole(type, SCALE_ROLES.quantitativePosition);
325
+ for (const property of ["nice", "zero", "clamp", "reverse"]) {
326
+ if (options[property] !== undefined && typeof options[property] !== "boolean") {
327
+ throw new TypeError(`Scale ${property} must be a boolean.`);
328
+ }
329
+ }
330
+ const domain = validateScaleDomain(options.domain ?? existing?.domain ?? "auto");
331
+ if (domain !== "auto" && domain.some(value => value < 0)) {
332
+ throw new RangeError("StrokeWidth scale domain cannot contain negative values.");
333
+ }
334
+ const scale = {
335
+ id,
336
+ type,
337
+ domain,
338
+ range: validateStrokeWidthRange(options.range ?? existing?.range ?? "auto")
339
+ };
340
+ for (const property of ["nice", "zero", "clamp", "reverse", "base", "exponent", "constant"]) {
341
+ const value = options[property] ?? existing?.[property];
342
+ if (value !== undefined) scale[property] = value;
343
+ }
344
+ return scale;
345
+ }
346
+
317
347
  export function resolveOffsetScaleDefinition(program, options, channel = "xOffset") {
318
348
  optionsObject(options);
319
349
  validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");
@@ -1,32 +1,7 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
3
  import { validateOptionObject } from "../../core/validation.js";
4
- import {
5
- isTransformedScaleType,
6
- SCALE_ROLES,
7
- validateColorRange,
8
- validateDiscretizedColorDomain,
9
- validateDiscretizedColorRange,
10
- validateOrdinalDomain,
11
- validateScaleDomain,
12
- validateScaleRange,
13
- validateScaleType,
14
- validateScaleTypeForRole,
15
- validateShapeRange,
16
- validateSizeRange,
17
- validateStrokeDashRange,
18
- validateSequentialColorRange,
19
- validateScaleUnknown,
20
- isDiscretizedColorScaleType,
21
- isDiscretePositionScaleType,
22
- hasOrdinalDomain,
23
- normalizeScaleDefinition
24
- } from "../../grammar/scales.js";
25
- import {
26
- validateRadialRange,
27
- validateThetaRange
28
- } from "../../grammar/polar.js";
29
- import { getMarkMaterializationStep } from "../../materialization/marks.js";
4
+ import { getMarkMaterializationStep } from "../../materialization/marks/index.js";
30
5
  import {
31
6
  applyMaterializationPlan
32
7
  } from "../../materialization/dependencies.js";
@@ -34,8 +9,11 @@ import {
34
9
  findSemanticScale,
35
10
  requireSemanticScale
36
11
  } from "../../selectors/scales.js";
37
- import { findScaleConsumers } from "./consumers.js";
38
- import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
12
+ import { findScaleConsumers } from "./consumers/index.js";
13
+ import {
14
+ prepareScaleEdit,
15
+ resolveScaleConsumerChannel
16
+ } from "./editPolicy.js";
39
17
 
40
18
  const OPTIONS = Object.freeze([
41
19
  "id", "type", "domain", "range", "nice", "zero", "clamp", "reverse",
@@ -67,162 +45,6 @@ function resolveScaleId(program, requested) {
67
45
  );
68
46
  }
69
47
 
70
- function resolveChannel(consumers, id) {
71
- const channels = new Set(
72
- consumers.map(consumer => normalizePositionScaleChannel(consumer.channel))
73
- );
74
- if (channels.size > 1) {
75
- throw new Error(`Scale "${id}" cannot be shared across channels.`);
76
- }
77
- return channels.values().next().value;
78
- }
79
-
80
- function validateRangeForChannel(scale, channel, value) {
81
- if (value === "auto") return value;
82
- if (channel === "theta") return validateThetaRange(value);
83
- if (channel === "radius") return validateRadialRange(value);
84
- if (scale.type === "sequential") {
85
- return validateSequentialColorRange(value);
86
- }
87
- if (isDiscretizedColorScaleType(scale.type)) {
88
- return validateDiscretizedColorRange(value);
89
- }
90
- if (scale.type !== "ordinal") return validateScaleRange(value);
91
- if (channel === "color") return validateColorRange(value);
92
- if (channel === "shape") return validateShapeRange(value);
93
- if (channel === "strokeDash") return validateStrokeDashRange(value);
94
- if (channel === "size") return validateSizeRange(value);
95
- return validateScaleRange(value);
96
- }
97
-
98
- function validateTypeTransition(scale, nextType, channel, consumers) {
99
- if (nextType === scale.type) return;
100
- validateScaleType(nextType);
101
- if (consumers.length === 0) return;
102
- if (nextType === "sequential" || isDiscretizedColorScaleType(nextType)) {
103
- const discretized = isDiscretizedColorScaleType(nextType);
104
- if (channel !== "color" || consumers.some(consumer =>
105
- ["nominal", "ordinal"].includes(consumer.encoding.fieldType) ||
106
- (discretized && (
107
- consumer.encoding.fieldType !== "quantitative" ||
108
- consumer.layer.mark?.type !== "point"
109
- )) ||
110
- (!discretized && !["point", "bar"].includes(consumer.layer.mark?.type))
111
- )) {
112
- throw new Error(
113
- `Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`
114
- );
115
- }
116
- return;
117
- }
118
- if (nextType === "time") {
119
- if (
120
- consumers.length > 0 &&
121
- (!["x", "y", "theta"].includes(channel) ||
122
- consumers.some(consumer => consumer.encoding.fieldType !== "temporal"))
123
- ) {
124
- throw new Error(
125
- `Scale "${scale.id}" has a consumer incompatible with type "time".`
126
- );
127
- }
128
- return;
129
- }
130
- const discrete = isDiscretePositionScaleType(nextType);
131
- if (discrete) {
132
- if (
133
- consumers.length > 0 &&
134
- (!["x", "y", "theta"].includes(channel) || consumers.some(consumer =>
135
- !["nominal", "ordinal"].includes(consumer.encoding.fieldType)
136
- ))
137
- ) {
138
- throw new Error(
139
- `Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`
140
- );
141
- }
142
- if (
143
- nextType === "point" &&
144
- consumers.some(consumer => consumer.layer.mark?.type === "bar")
145
- ) {
146
- throw new Error("Point scales cannot provide bar bandwidth.");
147
- }
148
- return;
149
- }
150
- const quantitative = nextType === "linear" || isTransformedScaleType(nextType);
151
- if (
152
- !quantitative ||
153
- (consumers.length > 0 && !["x", "y", "theta", "radius"].includes(channel))
154
- ) {
155
- throw new Error(
156
- "editScale type transition currently requires a quantitative position scale."
157
- );
158
- }
159
- validateScaleTypeForRole(nextType, SCALE_ROLES.quantitativePosition);
160
- if (channel === "theta" && nextType !== "linear") {
161
- throw new Error("Theta quantitative position currently requires a linear scale.");
162
- }
163
- if (consumers.some(consumer => consumer.encoding.fieldType !== "quantitative")) {
164
- throw new Error(
165
- `Scale "${scale.id}" has a consumer incompatible with type "${nextType}".`
166
- );
167
- }
168
- }
169
-
170
- function validateLegendTypeTransition(program, scale, nextType) {
171
- if (nextType === scale.type) return;
172
- const legends = program.guideConfigs.legend ?? {};
173
- if (
174
- legends.gradient?.scale === scale.id &&
175
- nextType !== "sequential"
176
- ) {
177
- throw new Error(
178
- `Scale "${scale.id}" cannot change type while its gradient legend is active.`
179
- );
180
- }
181
- if (
182
- legends.interval?.scale === scale.id &&
183
- !isDiscretizedColorScaleType(nextType)
184
- ) {
185
- throw new Error(
186
- `Scale "${scale.id}" cannot change type while its interval legend is active.`
187
- );
188
- }
189
- }
190
-
191
- function normalizeDefinition(scale, channel, consumers, args) {
192
- const type = args.type ?? scale.type;
193
- validateTypeTransition(scale, type, channel, consumers);
194
- const definition = normalizeScaleDefinition({
195
- type,
196
- previous: scale,
197
- patch: args,
198
- retainCoreOnTypeChange: true,
199
- retainCompatibleOnTypeChange: true,
200
- validateDomain: (scaleType, value) =>
201
- isDiscretizedColorScaleType(scaleType)
202
- ? validateDiscretizedColorDomain(scaleType, value)
203
- : hasOrdinalDomain(scaleType)
204
- ? validateOrdinalDomain(value)
205
- : validateScaleDomain(value),
206
- validateRange: (scaleType, value) =>
207
- validateRangeForChannel({ type: scaleType }, channel, value)
208
- });
209
- const typeChanged = type !== scale.type;
210
- const unknown = Object.hasOwn(args, "unknown")
211
- ? args.unknown
212
- : typeChanged ? undefined : scale.unknown;
213
- if (unknown !== undefined) {
214
- if (consumers.some(consumer => consumer.layer.mark?.type !== "point")) {
215
- throw new Error(
216
- "Scale unknown currently requires row-owned point consumers."
217
- );
218
- }
219
- definition.unknown = consumers.length === 0
220
- ? unknown
221
- : validateScaleUnknown(channel, unknown);
222
- }
223
- return definition;
224
- }
225
-
226
48
  function sameValue(left, right) {
227
49
  return JSON.stringify(left) === JSON.stringify(right);
228
50
  }
@@ -254,31 +76,11 @@ export const editScale = action(
254
76
  if (!REQUESTED_CHANGES.some(property => Object.hasOwn(args, property))) {
255
77
  throw new Error("editScale requires at least one editable property.");
256
78
  }
257
- const hasPalette = Object.hasOwn(args, "palette");
258
- if (hasPalette && Object.hasOwn(args, "range")) {
259
- throw new Error("editScale cannot specify both palette and range.");
260
- }
261
-
262
79
  const id = resolveScaleId(this, args.id);
263
80
  const scale = requireSemanticScale(this, id);
264
81
  const consumers = findScaleConsumers(this, id);
265
- const channel = resolveChannel(consumers, id);
266
- if (
267
- hasPalette &&
268
- channel !== "color" &&
269
- scale.type !== "sequential" &&
270
- !isDiscretizedColorScaleType(scale.type)
271
- ) {
272
- throw new Error("editScale palette requires a color scale.");
273
- }
274
- const patch = !hasPalette
275
- ? args
276
- : Object.fromEntries([
277
- ...Object.entries(args).filter(([key]) => key !== "palette"),
278
- ["range", { palette: args.palette }]
279
- ]);
280
- validateLegendTypeTransition(this, scale, args.type ?? scale.type);
281
- const definition = normalizeDefinition(scale, channel, consumers, patch);
82
+ const channel = resolveScaleConsumerChannel(consumers, id);
83
+ const definition = prepareScaleEdit(this, scale, channel, consumers, args);
282
84
 
283
85
  let next = this;
284
86
  for (const property of EDITABLE) {