ggaction 0.0.2 → 0.0.4

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 (242) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +95 -72
  3. package/package.json +15 -5
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/data/regression.js +1 -1
  16. package/src/actions/encodings/appearance.js +12 -1
  17. package/src/actions/encodings/barWidth.js +5 -3
  18. package/src/actions/encodings/color/continuous.js +137 -0
  19. package/src/actions/encodings/color/index.js +151 -0
  20. package/src/actions/encodings/color/layout.js +91 -0
  21. package/src/actions/encodings/color/policy.js +88 -0
  22. package/src/actions/encodings/index.js +4 -2
  23. package/src/actions/encodings/offset.js +91 -73
  24. package/src/actions/encodings/position/apply.js +66 -0
  25. package/src/actions/encodings/position/index.js +130 -0
  26. package/src/actions/encodings/position/policies/arc.js +26 -0
  27. package/src/actions/encodings/position/policies/bar.js +9 -5
  28. package/src/actions/encodings/position/policies/index.js +5 -1
  29. package/src/actions/encodings/position/policies/line.js +20 -3
  30. package/src/actions/encodings/position/resolve.js +58 -9
  31. package/src/actions/encodings/ranged.js +30 -8
  32. package/src/actions/encodings/shared.js +17 -21
  33. package/src/actions/encodings/strokeDash.js +1 -1
  34. package/src/actions/encodings/text.js +73 -0
  35. package/src/actions/errorBands/create.js +37 -29
  36. package/src/actions/errorBands/edit.js +206 -0
  37. package/src/actions/errorBands/index.js +9 -0
  38. package/src/actions/errorBands/options.js +39 -0
  39. package/src/actions/errorBars/create.js +50 -43
  40. package/src/actions/errorBars/edit.js +130 -0
  41. package/src/actions/errorBars/index.js +3 -0
  42. package/src/actions/errorBars/options.js +52 -0
  43. package/src/actions/facets/actions.js +162 -0
  44. package/src/actions/facets/derive.js +200 -0
  45. package/src/actions/facets/guides.js +16 -0
  46. package/src/actions/facets/index.js +1 -0
  47. package/src/actions/facets/replay.js +69 -0
  48. package/src/actions/guides/applicability.js +113 -0
  49. package/src/actions/guides/axes/axes.js +70 -16
  50. package/src/actions/guides/axes/edit.js +126 -0
  51. package/src/actions/guides/axes/index.js +12 -0
  52. package/src/actions/guides/axes/labels.js +22 -19
  53. package/src/actions/guides/axes/remove.js +60 -0
  54. package/src/actions/guides/axes/tickGroups.js +7 -5
  55. package/src/actions/guides/axes/ticks.js +10 -5
  56. package/src/actions/guides/axes/titles.js +8 -6
  57. package/src/actions/guides/grids/grid.js +144 -15
  58. package/src/actions/guides/grids/resolve.js +6 -14
  59. package/src/actions/guides/guides.js +17 -75
  60. package/src/actions/guides/index.js +6 -0
  61. package/src/actions/guides/legends/categorical/actions.js +48 -9
  62. package/src/actions/guides/legends/categorical/components.js +16 -16
  63. package/src/actions/guides/legends/categorical/index.js +14 -14
  64. package/src/actions/guides/legends/categorical/layout.js +7 -3
  65. package/src/actions/guides/legends/categorical/options.js +2 -5
  66. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  67. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  68. package/src/actions/guides/legends/continuous/common.js +7 -7
  69. package/src/actions/guides/legends/continuous/interval.js +6 -6
  70. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  71. package/src/actions/guides/legends/edit.js +4 -26
  72. package/src/actions/guides/legends/focused.js +63 -0
  73. package/src/actions/guides/legends/index.js +4 -0
  74. package/src/actions/guides/legends/remove.js +46 -0
  75. package/src/actions/guides/legends/size.js +13 -15
  76. package/src/actions/guides/legends/target.js +25 -0
  77. package/src/actions/guides/polar/axes/facade.js +219 -0
  78. package/src/actions/guides/polar/axes/index.js +55 -0
  79. package/src/actions/guides/polar/axes/labels.js +188 -0
  80. package/src/actions/guides/polar/axes/lines.js +113 -0
  81. package/src/actions/guides/polar/axes/shared.js +112 -0
  82. package/src/actions/guides/polar/axes/ticks.js +164 -0
  83. package/src/actions/guides/polar/axes/titles.js +165 -0
  84. package/src/actions/guides/polar/grids.js +252 -0
  85. package/src/actions/guides/polar/index.js +2 -0
  86. package/src/actions/guides/polar/resolve.js +269 -0
  87. package/src/actions/guides/tickValues.js +1 -1
  88. package/src/actions/index.js +4 -0
  89. package/src/actions/intervals/resolve.js +1 -1
  90. package/src/actions/marks/arc/actions.js +239 -0
  91. package/src/actions/marks/arc/index.js +1 -0
  92. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  93. package/src/actions/marks/area/index.js +4 -0
  94. package/src/actions/marks/bar/create.js +26 -5
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/bar/materialize.js +25 -31
  97. package/src/actions/marks/index.js +12 -4
  98. package/src/actions/marks/lifecycle.js +27 -0
  99. package/src/actions/marks/line/actions.js +373 -0
  100. package/src/actions/marks/line/index.js +1 -0
  101. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  102. package/src/actions/marks/point/index.js +1 -0
  103. package/src/actions/marks/rect/actions.js +165 -0
  104. package/src/actions/marks/rect/index.js +1 -0
  105. package/src/actions/marks/remove.js +214 -0
  106. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  107. package/src/actions/marks/rule/index.js +1 -0
  108. package/src/actions/marks/shared.js +156 -25
  109. package/src/actions/marks/text/actions.js +192 -0
  110. package/src/actions/marks/text/index.js +1 -0
  111. package/src/actions/primitives/createGraphics.js +13 -6
  112. package/src/actions/primitives/editGraphics.js +10 -3
  113. package/src/actions/primitives/semantic.js +26 -3
  114. package/src/actions/primitives/semanticValue.js +26 -4
  115. package/src/actions/regression/components.js +2 -2
  116. package/src/actions/regression/create.js +19 -2
  117. package/src/actions/regression/edit.js +247 -0
  118. package/src/actions/regression/index.js +3 -0
  119. package/src/actions/regression/resolve.js +3 -1
  120. package/src/actions/scales/consumers/common.js +91 -0
  121. package/src/actions/scales/consumers/families.js +47 -0
  122. package/src/actions/scales/consumers/index.js +41 -0
  123. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
  124. package/src/actions/scales/create.js +1 -1
  125. package/src/actions/scales/definitions.js +15 -6
  126. package/src/actions/scales/edit.js +45 -22
  127. package/src/actions/scales/materialize.js +58 -409
  128. package/src/actions/selection/actions.js +38 -24
  129. package/src/actions/selection/index.js +2 -0
  130. package/src/actions/titles/actions.js +63 -2
  131. package/src/actions/titles/index.js +2 -0
  132. package/src/actions/titles/resolve.js +12 -17
  133. package/src/composition.js +10 -0
  134. package/src/core/ChartProgram.js +49 -65
  135. package/src/core/action.js +10 -0
  136. package/src/core/compositionState.js +169 -0
  137. package/src/core/materializationState.js +65 -0
  138. package/src/core/programState.js +28 -0
  139. package/src/core/validation.js +16 -0
  140. package/src/core/vocabulary.js +154 -4
  141. package/src/grammar/arcs.js +154 -0
  142. package/src/grammar/areaSeries.js +1 -1
  143. package/src/grammar/bars/aggregate.js +3 -3
  144. package/src/grammar/bars/geometry.js +3 -3
  145. package/src/grammar/bars/policy.js +7 -1
  146. package/src/grammar/coordinates.js +3 -7
  147. package/src/grammar/density.js +21 -2
  148. package/src/grammar/facets/dependencies.js +185 -0
  149. package/src/grammar/facets/guides.js +257 -0
  150. package/src/grammar/facets/index.js +122 -0
  151. package/src/grammar/facets/scales.js +254 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/lineSeries.js +112 -4
  154. package/src/grammar/palettes.js +5 -7
  155. package/src/grammar/polar.js +102 -0
  156. package/src/grammar/polarGuides.js +243 -0
  157. package/src/grammar/polarLineCommands.js +43 -0
  158. package/src/grammar/polarPaths.js +161 -0
  159. package/src/grammar/positionCompatibility.js +18 -2
  160. package/src/grammar/rects.js +26 -0
  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 +1 -1
  166. package/src/grammar/scales/color.js +4 -2
  167. package/src/grammar/scales/continuous.js +6 -1
  168. package/src/grammar/scales/fields.js +7 -0
  169. package/src/grammar/scales/ordinal.js +5 -2
  170. package/src/grammar/scales/policies.js +1 -1
  171. package/src/grammar/schemas/graphic.js +1 -1
  172. package/src/grammar/schemas/graphicBounds.js +64 -9
  173. package/src/grammar/schemas/graphicTree.js +44 -10
  174. package/src/grammar/schemas/semanticPath.js +31 -9
  175. package/src/grammar/seriesLayout.js +13 -2
  176. package/src/grammar/statistics/studentT.js +98 -0
  177. package/src/grammar/text.js +71 -0
  178. package/src/grammar/ticks.js +50 -13
  179. package/src/grammar/transforms.js +1 -1
  180. package/src/index.js +1 -0
  181. package/src/layout/composition.js +310 -0
  182. package/src/layout/facets.js +131 -0
  183. package/src/layout/title.js +6 -0
  184. package/src/materialization/bars/aggregate.js +6 -3
  185. package/src/materialization/bars/grouped.js +75 -37
  186. package/src/materialization/bars/histogram.js +1 -1
  187. package/src/materialization/bars/ranged.js +1 -1
  188. package/src/materialization/composition.js +152 -0
  189. package/src/materialization/compositionSnapshot.js +114 -0
  190. package/src/materialization/density.js +1 -1
  191. package/src/materialization/dependencies.js +47 -73
  192. package/src/materialization/encodings.js +14 -28
  193. package/src/materialization/facetGuides/index.js +2 -0
  194. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  195. package/src/materialization/facetGuides/placement.js +126 -0
  196. package/src/materialization/facetGuides/preparation.js +188 -0
  197. package/src/materialization/facets.js +214 -0
  198. package/src/materialization/graphicHierarchy.js +15 -5
  199. package/src/materialization/guides/resources.js +46 -0
  200. package/src/materialization/marks/capabilities.js +141 -0
  201. package/src/materialization/marks/index.js +105 -0
  202. package/src/materialization/marks/policies.js +87 -0
  203. package/src/materialization/planner.js +105 -12
  204. package/src/materialization/rect.js +138 -0
  205. package/src/materialization/rectConfig.js +38 -0
  206. package/src/materialization/rowEncoding.js +33 -0
  207. package/src/materialization/scaleGuideDependencies.js +94 -0
  208. package/src/materialization/scales/map.js +40 -0
  209. package/src/materialization/scales/policies/arc.js +37 -0
  210. package/src/materialization/scales/policies/bar.js +132 -0
  211. package/src/materialization/scales/policies/series.js +76 -0
  212. package/src/materialization/scales/resolve.js +326 -0
  213. package/src/materialization/selection/items/arc.js +29 -0
  214. package/src/materialization/selection/items/bar.js +255 -0
  215. package/src/materialization/selection/items/common.js +131 -0
  216. package/src/materialization/selection/items/index.js +6 -0
  217. package/src/materialization/selection/items/path.js +59 -0
  218. package/src/materialization/selection/items/point.js +55 -0
  219. package/src/materialization/selection/items/rect.js +23 -0
  220. package/src/materialization/selection/items/rule.js +25 -0
  221. package/src/materialization/selection/policies/arc.js +10 -0
  222. package/src/materialization/selection/policies/area.js +1 -1
  223. package/src/materialization/selection/policies/bar.js +1 -1
  224. package/src/materialization/selection/policies/index.js +5 -1
  225. package/src/materialization/selection/policies/line.js +1 -1
  226. package/src/materialization/selection/policies/point.js +1 -1
  227. package/src/materialization/selection/policies/rect.js +10 -0
  228. package/src/materialization/selection/policies/rule.js +1 -1
  229. package/src/materialization/selection/styles.js +25 -17
  230. package/src/materialization/text.js +156 -0
  231. package/src/renderers/canvas/index.js +68 -22
  232. package/src/renderers/canvas/text.js +14 -1
  233. package/types/extension.d.ts +5 -1
  234. package/types/index.d.ts +56 -1
  235. package/types/program.d.ts +664 -27
  236. package/src/actions/encodings/color.js +0 -329
  237. package/src/actions/encodings/position.js +0 -176
  238. package/src/actions/marks/line.js +0 -248
  239. package/src/grammar/regression.js +0 -578
  240. package/src/grammar/scales.js +0 -1
  241. package/src/materialization/marks.js +0 -110
  242. package/src/materialization/selection/items.js +0 -503
@@ -1,103 +1,48 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateUserId } from "../../core/identifiers.js";
3
+ import { normalizePositionScaleChannel } from "../../core/vocabulary.js";
3
4
  import { validateKeys } from "../../core/validation.js";
4
- import { resolveHistogramBins } from "../../grammar/histogram.js";
5
5
  import { resolveGraphicBounds } from "../../layout/canvas.js";
6
- import { normalizeOffsetPadding } from "../../grammar/bars/geometry.js";
7
6
  import {
8
- BAR_GRAINS,
9
- resolveBarChannels,
10
- resolveBarGrain
11
- } from "../../grammar/bars/policy.js";
7
+ applyMaterializationPlan,
8
+ planScaleGuideRematerialization
9
+ } from "../../materialization/dependencies.js";
12
10
  import {
13
- mapContinuousScaleValues,
14
- mapDiscretizedColors,
15
- mapSequentialColors,
16
- mapOrdinalValues,
17
- resolveColorRange,
18
- resolveDiscretizedColorScale,
19
- resolveContinuousDomain,
20
- resolveOrdinalDomain,
21
- resolveOrdinalOffsetScale,
22
- resolveOrdinalPositionScale,
23
- resolveDiscretePositionScale,
24
- resolveScaleRange,
25
- resolveOpacityRange,
26
- resolveSequentialColorStops,
27
- resolveShapeRange,
28
- resolveSizeRange,
29
- resolveStrokeDashRange,
30
- validateLinearScaleType,
31
- validateOrdinalScaleType,
32
- validateTimeScaleType,
33
- isDiscretePositionScaleType,
34
- isDiscretizedColorScaleType,
35
- isContinuousColorScaleType,
36
- isOrdinalScaleType,
37
- isTransformedScaleType,
38
- normalizeTransformParameters,
39
- resolveTransformedDomain,
40
- SCALE_ROLES,
41
- validateScaleTypeForRole
42
- } from "../../grammar/scales.js";
11
+ canDeferScaleConsumerApplication,
12
+ getMarkRematerializationStep,
13
+ getScaleConsumerMaterializationMode
14
+ } from "../../materialization/marks/index.js";
15
+ import { mapScaleConsumerValues } from
16
+ "../../materialization/scales/map.js";
17
+ import { resolveScaleMaterialization } from
18
+ "../../materialization/scales/resolve.js";
43
19
  import {
44
20
  findScale,
45
21
  findScaleConsumers,
46
22
  resolveConsumerValues,
47
23
  resolveSeriesLayoutScaleValues
48
- } from "./consumers.js";
49
- import {
50
- applyMaterializationPlan,
51
- planScaleGuideRematerialization
52
- } from "../../materialization/dependencies.js";
24
+ } from "./consumers/index.js";
53
25
 
54
- const OPTIONS = Object.freeze(["id", "guides"]);
26
+ const OPTIONS = Object.freeze(["id", "guides", "marks"]);
55
27
 
56
- function resolveTemporalBarBand(consumers, domain, range, values) {
57
- const temporalBars = consumers.filter(consumer => {
58
- const channels = resolveBarChannels(consumer.layer);
59
- return resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate &&
60
- channels?.category === consumer.channel &&
61
- consumer.encoding.fieldType === "temporal";
62
- });
63
- if (temporalBars.length === 0) return undefined;
64
- if (temporalBars.length !== consumers.length) {
65
- throw new Error("A temporal bar position scale cannot share a non-bar layout policy.");
66
- }
67
- const ordered = [...new Set(values)].sort((left, right) => left - right);
68
- if (ordered.length < 2) {
69
- throw new Error("Temporal bar position requires at least two distinct values.");
28
+ function validateOptions(args) {
29
+ validateKeys(args, OPTIONS, "rematerializeScale");
30
+ if (args.guides !== undefined && typeof args.guides !== "boolean") {
31
+ throw new TypeError("rematerializeScale guides must be a boolean.");
70
32
  }
71
- const minimumGap = Math.min(
72
- ...ordered.slice(1).map((value, index) => value - ordered[index])
73
- );
74
- const domainSpan = Math.abs(domain[1] - domain[0]);
75
- if (!(minimumGap > 0) || !(domainSpan > 0)) {
76
- throw new Error("Temporal bar position requires an increasing time domain.");
33
+ if (args.marks !== undefined && typeof args.marks !== "boolean") {
34
+ throw new TypeError("rematerializeScale marks must be a boolean.");
77
35
  }
78
- const direction = Math.sign(range[1] - range[0]) || 1;
79
- const estimatedBandwidth = Math.abs(range[1] - range[0]) * minimumGap /
80
- (domainSpan + minimumGap);
81
- const resolvedRange = [
82
- range[0] + direction * estimatedBandwidth / 2,
83
- range[1] - direction * estimatedBandwidth / 2
84
- ];
85
- const positions = ordered.map(value =>
86
- resolvedRange[0] +
87
- (value - domain[0]) / (domain[1] - domain[0]) *
88
- (resolvedRange[1] - resolvedRange[0])
89
- );
90
- const bandwidth = Math.min(
91
- ...positions.slice(1).map((value, index) => Math.abs(value - positions[index]))
92
- );
93
- return {
94
- bandwidth,
95
- range: resolvedRange
96
- };
97
36
  }
98
37
 
99
- function validateOptions(args) {
100
- validateKeys(args, OPTIONS, "rematerializeScale");
38
+ function resolveScaleChannel(id, consumers) {
39
+ const channels = new Set(
40
+ consumers.map(consumer => normalizePositionScaleChannel(consumer.channel))
41
+ );
42
+ if (channels.size !== 1) {
43
+ throw new Error(`Scale "${id}" cannot be shared across channels.`);
44
+ }
45
+ return normalizePositionScaleChannel(consumers[0].channel);
101
46
  }
102
47
 
103
48
  export const rematerializeScale = action(
@@ -107,352 +52,56 @@ export const rematerializeScale = action(
107
52
  },
108
53
  function (args = {}) {
109
54
  validateOptions(args);
110
- if (args.guides !== undefined && typeof args.guides !== "boolean") {
111
- throw new TypeError("rematerializeScale guides must be a boolean.");
112
- }
113
55
  const id = validateUserId(args.id, "Scale id");
114
56
  const scale = findScale(this, id);
115
57
  const consumers = findScaleConsumers(this, id);
116
-
117
58
  if (consumers.length === 0) {
118
59
  throw new Error(`Scale "${id}" has no supported consumers.`);
119
60
  }
120
- const channels = new Set(
121
- consumers.map(consumer =>
122
- consumer.channel === "x2"
123
- ? "x"
124
- : consumer.channel === "y2" ? "y" : consumer.channel
125
- )
126
- );
127
- if (channels.size !== 1) {
128
- throw new Error(`Scale "${id}" cannot be shared across channels.`);
129
- }
130
-
131
- const channel = consumers[0].channel === "x2"
132
- ? "x"
133
- : consumers[0].channel === "y2" ? "y" : consumers[0].channel;
61
+ const channel = resolveScaleChannel(id, consumers);
134
62
  const valuesByConsumer = consumers.map(consumer => ({
135
63
  consumer,
136
- values: resolveConsumerValues(this, consumer)
64
+ values: resolveConsumerValues(this, consumer),
65
+ seriesLayout: resolveSeriesLayoutScaleValues(this, consumer)
137
66
  }));
138
- const allValues = valuesByConsumer
139
- .flatMap(item => item.values)
140
- .filter(value => value !== undefined);
141
- const isSequentialColor = channel === "color" &&
142
- isContinuousColorScaleType(scale.type);
143
- const isDiscretizedColor = channel === "color" &&
144
- isDiscretizedColorScaleType(scale.type);
145
- const isOrdinalAppearance =
146
- ["color", "strokeDash", "shape"].includes(channel) &&
147
- isOrdinalScaleType(scale.type);
148
- const isOrdinalOffset = channel === "xOffset";
149
- const isOrdinalPosition =
150
- !isOrdinalAppearance && !isOrdinalOffset && isOrdinalScaleType(scale.type);
151
- const isDiscretePosition =
152
- !isOrdinalAppearance && !isOrdinalOffset &&
153
- isDiscretePositionScaleType(scale.type);
154
- const binnedBars = valuesByConsumer.filter(
155
- ({ consumer }) =>
156
- consumer.layer.mark?.type === "bar" &&
157
- consumer.encoding.bin !== undefined
158
- );
159
- const seriesLayouts = valuesByConsumer.map(({ consumer }) =>
160
- resolveSeriesLayoutScaleValues(this, consumer)
161
- );
162
- let domain;
163
-
164
- let discretizedScale;
165
- if (isDiscretizedColor) {
166
- discretizedScale = resolveDiscretizedColorScale({
167
- type: scale.type,
168
- domain: scale.domain,
169
- range: scale.range,
170
- values: allValues
171
- });
172
- domain = discretizedScale.domain;
173
- } else if (binnedBars.length > 0) {
174
- if (channel !== "x" || binnedBars.length !== valuesByConsumer.length) {
175
- throw new Error(
176
- `Binned scale "${id}" cannot be shared with an unbinned consumer.`
177
- );
178
- }
179
- const binDefinitions = new Set(
180
- binnedBars.map(({ consumer }) => JSON.stringify(consumer.encoding.bin))
181
- );
182
- if (binDefinitions.size !== 1) {
183
- throw new Error(
184
- `Binned scale "${id}" requires one shared bin definition.`
185
- );
186
- }
187
- domain = resolveHistogramBins({
188
- values: allValues,
189
- bin: binnedBars[0].consumer.encoding.bin,
190
- domain: scale.domain,
191
- nice: scale.nice ?? true,
192
- zero: scale.zero ?? false
193
- }).domain;
194
- } else if (seriesLayouts.some(Boolean)) {
195
- const activeLayouts = seriesLayouts.filter(Boolean);
196
- const layouts = new Set(activeLayouts.map(item => item.layout));
197
- if (layouts.size !== 1) {
198
- throw new Error(`Series layout scale "${id}" requires one layout policy.`);
199
- }
200
- const layoutConsumers = valuesByConsumer.filter(
201
- (_, index) => seriesLayouts[index] !== undefined
202
- );
203
- const directConsumers = valuesByConsumer.filter(
204
- (_, index) => seriesLayouts[index] === undefined
205
- );
206
- const compatibleDirect = directConsumers.every(({ consumer }) =>
207
- layoutConsumers.every(({ consumer: layoutConsumer }) => {
208
- const aggregate = layoutConsumer.encoding.aggregate;
209
- return aggregate !== "count" &&
210
- consumer.encoding.fieldType === "quantitative" &&
211
- consumer.encoding.field === layoutConsumer.encoding.field;
212
- })
213
- );
214
- if (!compatibleDirect) {
215
- throw new Error(
216
- `Series layout scale "${id}" cannot be shared with another policy.`
217
- );
218
- }
219
- const layout = activeLayouts[0].layout;
220
- const values = [
221
- ...activeLayouts.flatMap(item => item.values),
222
- ...directConsumers.flatMap(item => item.values)
223
- ];
224
- domain = resolveContinuousDomain({
225
- domain: scale.domain,
226
- values,
227
- type: scale.type,
228
- nice: layout === "fill" && scale.domain === "auto"
229
- ? false
230
- : scale.nice,
231
- zero: layout === "fill" && scale.domain === "auto"
232
- ? false
233
- : scale.zero
234
- });
235
- } else {
236
- domain = isOrdinalAppearance || isOrdinalOffset || isOrdinalPosition ||
237
- isDiscretePosition
238
- ? resolveOrdinalDomain(scale.domain, allValues)
239
- : isTransformedScaleType(scale.type)
240
- ? resolveTransformedDomain({
241
- type: scale.type,
242
- domain: scale.domain,
243
- values: allValues,
244
- nice: scale.nice ?? false,
245
- zero: scale.zero ?? false,
246
- ...(scale.base === undefined ? {} : { base: scale.base }),
247
- ...(scale.exponent === undefined ? {} : { exponent: scale.exponent }),
248
- ...(scale.constant === undefined ? {} : { constant: scale.constant })
249
- })
250
- : resolveContinuousDomain({
251
- domain: scale.domain,
252
- values: allValues,
253
- type: scale.type,
254
- nice: scale.nice,
255
- zero: scale.zero
256
- });
257
- }
258
-
259
- let range;
260
- if (channel === "color") {
261
- range = isDiscretizedColor
262
- ? discretizedScale.range
263
- : isSequentialColor
264
- ? resolveSequentialColorStops(scale.range)
265
- : resolveColorRange(scale.range, domain.length);
266
- }
267
- else if (channel === "strokeDash") range = resolveStrokeDashRange(scale.range);
268
- else if (channel === "shape") range = resolveShapeRange(scale.range);
269
- else if (channel === "size") range = resolveSizeRange(scale.range);
270
- else if (channel === "opacity") range = resolveOpacityRange(scale.range);
271
- else if (isOrdinalOffset) range = undefined;
272
- else {
273
- range = resolveScaleRange(
274
- scale.range,
275
- channel,
276
- resolveGraphicBounds(this)
277
- );
278
- }
279
- if (channel === "shape" && domain.length > range.length) {
280
- throw new Error(
281
- `Shape scale "${id}" requires at least one distinct shape per domain value.`
282
- );
283
- }
284
-
285
- let resolvedScale = isDiscretizedColor
286
- ? discretizedScale
287
- : isOrdinalOffset
288
- ? resolveOrdinalOffsetScale({
289
- domain: scale.domain,
290
- values: allValues,
291
- range: scale.range,
292
- parentBandwidth: (() => {
293
- const bandwidths = consumers.map(consumer => {
294
- if (consumer.layer.encoding?.x?.bin !== undefined) return 1;
295
- const xScaleId = consumer.layer.encoding?.x?.scale;
296
- return this.resolvedScales[xScaleId]?.bandwidth;
297
- });
298
- if (
299
- bandwidths.some(value => !Number.isFinite(value)) ||
300
- new Set(bandwidths).size !== 1
301
- ) {
302
- throw new Error(
303
- `xOffset scale "${id}" requires one shared resolved x bandwidth.`
304
- );
305
- }
306
- return bandwidths[0];
307
- })(),
308
- ...(() => {
309
- const paddings = consumers.map(consumer => normalizeOffsetPadding(
310
- this.markConfigs[consumer.layer.id]?.xOffset
311
- ));
312
- const signatures = new Set(
313
- paddings.map(padding => JSON.stringify(padding))
314
- );
315
- if (signatures.size !== 1) {
316
- throw new Error(
317
- `xOffset scale "${id}" requires one shared padding policy.`
318
- );
319
- }
320
- return paddings[0];
321
- })()
322
- })
323
- : isDiscretePosition
324
- ? resolveDiscretePositionScale({
325
- type: scale.type,
326
- domain: scale.domain,
327
- values: allValues,
328
- range: scale.range,
329
- channel,
330
- bounds: resolveGraphicBounds(this),
331
- ...(scale.type === "band"
332
- ? {
333
- paddingInner: scale.paddingInner,
334
- paddingOuter: scale.paddingOuter
335
- }
336
- : { padding: scale.padding }),
337
- align: scale.align,
338
- unknown: scale.unknown
339
- })
340
- : isOrdinalPosition
341
- ? resolveOrdinalPositionScale({
342
- domain: scale.domain,
343
- values: allValues,
344
- range: scale.range,
345
- channel,
346
- bounds: resolveGraphicBounds(this),
347
- unknown: scale.unknown
348
- })
349
- : {
350
- type: isSequentialColor
351
- ? "sequential"
352
- : isOrdinalAppearance
353
- ? validateOrdinalScaleType(scale.type)
354
- : scale.type === "time"
355
- ? validateTimeScaleType(scale.type)
356
- : isTransformedScaleType(scale.type)
357
- ? validateScaleTypeForRole(
358
- scale.type,
359
- SCALE_ROLES.quantitativePosition
360
- )
361
- : validateLinearScaleType(scale.type),
362
- domain,
363
- range,
364
- ...(scale.clamp === undefined ? {} : { clamp: scale.clamp }),
365
- ...(scale.type === "log"
366
- ? { base: normalizeTransformParameters("log", {
367
- ...(scale.base === undefined ? {} : { base: scale.base })
368
- }).base }
369
- : scale.type === "pow"
370
- ? { exponent: normalizeTransformParameters("pow", {
371
- ...(scale.exponent === undefined
372
- ? {}
373
- : { exponent: scale.exponent })
374
- }).exponent }
375
- : scale.type === "symlog"
376
- ? { constant: normalizeTransformParameters("symlog", {
377
- ...(scale.constant === undefined
378
- ? {}
379
- : { constant: scale.constant })
380
- }).constant }
381
- : {}),
382
- ...(isSequentialColor ? { interpolate: scale.interpolate ?? "rgb" } : {}),
383
- ...(Object.hasOwn(scale, "unknown")
384
- ? { unknown: scale.unknown }
385
- : {})
386
- };
387
- if (Object.hasOwn(scale, "unknown") && !Object.hasOwn(resolvedScale, "unknown")) {
388
- resolvedScale = { ...resolvedScale, unknown: scale.unknown };
389
- }
390
- if (scale.type === "time" && ["x", "y"].includes(channel)) {
391
- const temporalBarBand = resolveTemporalBarBand(
392
- consumers,
393
- domain,
394
- range,
395
- allValues
396
- );
397
- if (temporalBarBand !== undefined) {
398
- resolvedScale = {
399
- ...resolvedScale,
400
- range: temporalBarBand.range,
401
- bandwidth: temporalBarBand.bandwidth
402
- };
403
- }
404
- }
405
- if (scale.reverse === true) {
406
- const range = [...resolvedScale.range].reverse();
407
- const direction = Math.sign(range[1] - range[0]) || 1;
408
- const offset = resolvedScale.start === undefined
67
+ const resolvedScale = resolveScaleMaterialization({
68
+ id,
69
+ scale,
70
+ channel,
71
+ consumers,
72
+ valuesByConsumer,
73
+ bounds: ["color", "strokeDash", "shape", "size", "opacity", "xOffset", "yOffset"]
74
+ .includes(channel)
409
75
  ? undefined
410
- : Math.abs(resolvedScale.start - resolvedScale.range[0]);
411
- resolvedScale = {
412
- ...resolvedScale,
413
- range,
414
- ...(resolvedScale.step === undefined
415
- ? {}
416
- : { step: -resolvedScale.step }),
417
- ...(offset === undefined ? {} : { start: range[0] + direction * offset })
418
- };
419
- }
76
+ : resolveGraphicBounds(this),
77
+ resolvedScales: this.resolvedScales,
78
+ markConfigs: this.markConfigs
79
+ });
420
80
  let next = this._withResolvedScale(id, resolvedScale);
421
81
 
422
82
  for (const { consumer, values } of valuesByConsumer) {
423
83
  if (
424
- consumer.layer.mark?.type === "point" &&
425
- ["x", "y"].includes(channel)
84
+ args.marks === false &&
85
+ canDeferScaleConsumerApplication(consumer.layer)
426
86
  ) {
427
- next = next.rematerializePointMark({ id: consumer.layer.id });
428
87
  continue;
429
88
  }
430
- if (
431
- ["line", "bar", "area", "rule"].includes(consumer.layer.mark?.type) ||
432
- (consumer.layer.mark?.type === "point" &&
433
- ["x", "y"].includes(channel) && isOrdinalPosition) ||
434
- (consumer.layer.mark?.type === "point" && ["size", "shape"].includes(channel))
435
- ) continue;
89
+ const materializationMode = getScaleConsumerMaterializationMode(
90
+ consumer.layer,
91
+ channel
92
+ );
93
+ if (materializationMode === "rematerialize") {
94
+ if (args.marks !== false) {
95
+ const step = getMarkRematerializationStep(consumer.layer);
96
+ if (step !== undefined) next = next[step.op](step.args);
97
+ }
98
+ continue;
99
+ }
100
+ if (materializationMode === "defer") continue;
436
101
  next = next.editGraphics({
437
102
  target: consumer.layer.id,
438
103
  property: channel === "color" ? "fill" : channel,
439
- value: isDiscretizedColor
440
- ? mapDiscretizedColors(values, resolvedScale)
441
- : isSequentialColor
442
- ? mapSequentialColors(values, resolvedScale.domain, resolvedScale.range, {
443
- interpolation: resolvedScale.interpolate,
444
- clamp: resolvedScale.clamp ?? false,
445
- ...(Object.hasOwn(resolvedScale, "unknown")
446
- ? { unknown: resolvedScale.unknown }
447
- : {})
448
- })
449
- : isOrdinalAppearance
450
- ? mapOrdinalValues(values, domain, resolvedScale.range, {
451
- ...(Object.hasOwn(resolvedScale, "unknown")
452
- ? { unknown: resolvedScale.unknown }
453
- : {})
454
- })
455
- : mapContinuousScaleValues(values, resolvedScale)
104
+ value: mapScaleConsumerValues(values, resolvedScale, channel)
456
105
  });
457
106
  }
458
107
 
@@ -117,31 +117,42 @@ export const applyPointHighlight = action(
117
117
  }
118
118
  );
119
119
 
120
+ function applyRectangularHighlight(program, args, operation, markType) {
121
+ validateKeys(args, INTERNAL_SELECTION_OPTIONS, operation);
122
+ const resolved = resolveStoredSelection(program, args.selection);
123
+ const keys = selectedKeys(args, resolved);
124
+ if (resolved.items[0]?.markType !== markType && resolved.items.length > 0) {
125
+ throw new Error(`${operation} requires a ${markType} selection.`);
126
+ }
127
+ if (keys.length === 0) return program;
128
+ const selected = new Set(keys);
129
+ const graphic = program.graphicSpec.objects[resolved.definition.target];
130
+ const keyByGraphic = new Map(resolved.items.flatMap(item =>
131
+ item.graphicIds.map(id => [id, item.key])
132
+ ));
133
+ return program.editGraphics({
134
+ target: resolved.definition.target,
135
+ property: "items",
136
+ value: graphic.items.map(child => ({
137
+ type: child.type ?? graphic.type,
138
+ properties: selected.has(keyByGraphic.get(child.id))
139
+ ? { ...child.properties, ...args.style }
140
+ : child.properties
141
+ }))
142
+ });
143
+ }
144
+
120
145
  export const applyBarHighlight = action(
121
146
  { op: "applyBarHighlight", description: "Apply selected bar appearance." },
122
147
  function (args = {}) {
123
- validateKeys(args, INTERNAL_SELECTION_OPTIONS, "applyBarHighlight");
124
- const resolved = resolveStoredSelection(this, args.selection);
125
- const keys = selectedKeys(args, resolved);
126
- if (resolved.items[0]?.markType !== "bar" && resolved.items.length > 0) {
127
- throw new Error("applyBarHighlight requires a bar selection.");
128
- }
129
- if (keys.length === 0) return this;
130
- const selected = new Set(keys);
131
- const graphic = this.graphicSpec.objects[resolved.definition.target];
132
- const keyByGraphic = new Map(resolved.items.flatMap(item =>
133
- item.graphicIds.map(id => [id, item.key])
134
- ));
135
- return this.editGraphics({
136
- target: resolved.definition.target,
137
- property: "items",
138
- value: graphic.items.map(child => ({
139
- type: child.type ?? graphic.type,
140
- properties: selected.has(keyByGraphic.get(child.id))
141
- ? { ...child.properties, ...args.style }
142
- : child.properties
143
- }))
144
- });
148
+ return applyRectangularHighlight(this, args, "applyBarHighlight", "bar");
149
+ }
150
+ );
151
+
152
+ export const applyRectHighlight = action(
153
+ { op: "applyRectHighlight", description: "Apply selected rect appearance." },
154
+ function (args = {}) {
155
+ return applyRectangularHighlight(this, args, "applyRectHighlight", "rect");
145
156
  }
146
157
  );
147
158
 
@@ -152,8 +163,11 @@ export const applyPathHighlight = action(
152
163
  const resolved = resolveStoredSelection(this, args.selection);
153
164
  const keys = selectedKeys(args, resolved);
154
165
  const markType = resolved.items[0]?.markType;
155
- if (!["line", "area"].includes(markType) && resolved.items.length > 0) {
156
- throw new Error("applyPathHighlight requires a line or area selection.");
166
+ if (
167
+ resolved.items.length > 0 &&
168
+ requireSelectionPolicy(markType).applyHighlightOp !== "applyPathHighlight"
169
+ ) {
170
+ throw new Error("applyPathHighlight requires a path selection policy.");
157
171
  }
158
172
  if (keys.length === 0) return this;
159
173
  const selected = new Set(keys);
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  applyBarHighlight,
3
+ applyRectHighlight,
3
4
  applyPathHighlight,
4
5
  applyPointHighlight,
5
6
  applyRuleHighlight,
@@ -14,6 +15,7 @@ export function registerSelectionActions(ProgramClass) {
14
15
  ProgramClass.prototype.selectMarks = selectMarks;
15
16
  ProgramClass.prototype.highlightMarks = highlightMarks;
16
17
  ProgramClass.prototype.applyBarHighlight = applyBarHighlight;
18
+ ProgramClass.prototype.applyRectHighlight = applyRectHighlight;
17
19
  ProgramClass.prototype.applyPathHighlight = applyPathHighlight;
18
20
  ProgramClass.prototype.applyPointHighlight = applyPointHighlight;
19
21
  ProgramClass.prototype.applyRuleHighlight = applyRuleHighlight;