ggaction 0.0.2 → 0.0.3

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 (199) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +12 -1
  3. package/package.json +6 -1
  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/encodings/appearance.js +11 -0
  16. package/src/actions/encodings/barWidth.js +5 -3
  17. package/src/actions/encodings/color.js +120 -25
  18. package/src/actions/encodings/index.js +2 -0
  19. package/src/actions/encodings/offset.js +90 -72
  20. package/src/actions/encodings/position/apply.js +66 -0
  21. package/src/actions/encodings/position/policies/arc.js +26 -0
  22. package/src/actions/encodings/position/policies/bar.js +9 -5
  23. package/src/actions/encodings/position/policies/index.js +5 -1
  24. package/src/actions/encodings/position/policies/line.js +20 -3
  25. package/src/actions/encodings/position/resolve.js +57 -8
  26. package/src/actions/encodings/position.js +44 -90
  27. package/src/actions/encodings/ranged.js +28 -6
  28. package/src/actions/encodings/shared.js +17 -21
  29. package/src/actions/encodings/text.js +73 -0
  30. package/src/actions/errorBands/create.js +37 -29
  31. package/src/actions/errorBands/edit.js +206 -0
  32. package/src/actions/errorBands/index.js +9 -0
  33. package/src/actions/errorBands/options.js +39 -0
  34. package/src/actions/errorBars/create.js +50 -43
  35. package/src/actions/errorBars/edit.js +130 -0
  36. package/src/actions/errorBars/index.js +3 -0
  37. package/src/actions/errorBars/options.js +52 -0
  38. package/src/actions/facets/actions.js +162 -0
  39. package/src/actions/facets/derive.js +200 -0
  40. package/src/actions/facets/guides.js +16 -0
  41. package/src/actions/facets/index.js +1 -0
  42. package/src/actions/facets/replay.js +69 -0
  43. package/src/actions/guides/applicability.js +113 -0
  44. package/src/actions/guides/axes/axes.js +70 -16
  45. package/src/actions/guides/axes/edit.js +126 -0
  46. package/src/actions/guides/axes/index.js +12 -0
  47. package/src/actions/guides/axes/labels.js +21 -18
  48. package/src/actions/guides/axes/remove.js +60 -0
  49. package/src/actions/guides/axes/tickGroups.js +7 -5
  50. package/src/actions/guides/axes/ticks.js +9 -4
  51. package/src/actions/guides/axes/titles.js +7 -5
  52. package/src/actions/guides/grids/grid.js +144 -15
  53. package/src/actions/guides/grids/resolve.js +5 -13
  54. package/src/actions/guides/guides.js +17 -75
  55. package/src/actions/guides/index.js +6 -0
  56. package/src/actions/guides/legends/categorical/actions.js +48 -9
  57. package/src/actions/guides/legends/categorical/components.js +16 -16
  58. package/src/actions/guides/legends/categorical/index.js +14 -14
  59. package/src/actions/guides/legends/categorical/layout.js +3 -1
  60. package/src/actions/guides/legends/categorical/options.js +2 -5
  61. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  62. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  63. package/src/actions/guides/legends/continuous/common.js +7 -7
  64. package/src/actions/guides/legends/continuous/interval.js +5 -5
  65. package/src/actions/guides/legends/edit.js +2 -6
  66. package/src/actions/guides/legends/focused.js +63 -0
  67. package/src/actions/guides/legends/index.js +4 -0
  68. package/src/actions/guides/legends/remove.js +64 -0
  69. package/src/actions/guides/legends/size.js +12 -14
  70. package/src/actions/guides/polar/axes/facade.js +219 -0
  71. package/src/actions/guides/polar/axes/labels.js +188 -0
  72. package/src/actions/guides/polar/axes/lines.js +113 -0
  73. package/src/actions/guides/polar/axes/shared.js +112 -0
  74. package/src/actions/guides/polar/axes/ticks.js +164 -0
  75. package/src/actions/guides/polar/axes/titles.js +165 -0
  76. package/src/actions/guides/polar/axes.js +55 -0
  77. package/src/actions/guides/polar/grids.js +252 -0
  78. package/src/actions/guides/polar/index.js +2 -0
  79. package/src/actions/guides/polar/resolve.js +269 -0
  80. package/src/actions/index.js +4 -0
  81. package/src/actions/marks/arc/actions.js +239 -0
  82. package/src/actions/marks/arc/index.js +1 -0
  83. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  84. package/src/actions/marks/area/index.js +4 -0
  85. package/src/actions/marks/bar/create.js +26 -5
  86. package/src/actions/marks/bar/materialize.js +25 -31
  87. package/src/actions/marks/index.js +12 -4
  88. package/src/actions/marks/lifecycle.js +27 -0
  89. package/src/actions/marks/line/actions.js +373 -0
  90. package/src/actions/marks/line/index.js +1 -0
  91. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  92. package/src/actions/marks/point/index.js +1 -0
  93. package/src/actions/marks/rect/actions.js +165 -0
  94. package/src/actions/marks/rect/index.js +1 -0
  95. package/src/actions/marks/remove.js +214 -0
  96. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  97. package/src/actions/marks/rule/index.js +1 -0
  98. package/src/actions/marks/shared.js +156 -25
  99. package/src/actions/marks/text/actions.js +192 -0
  100. package/src/actions/marks/text/index.js +1 -0
  101. package/src/actions/primitives/createGraphics.js +13 -6
  102. package/src/actions/primitives/editGraphics.js +10 -3
  103. package/src/actions/primitives/semantic.js +26 -3
  104. package/src/actions/primitives/semanticValue.js +25 -3
  105. package/src/actions/regression/components.js +1 -1
  106. package/src/actions/regression/create.js +18 -1
  107. package/src/actions/regression/edit.js +247 -0
  108. package/src/actions/regression/index.js +3 -0
  109. package/src/actions/regression/resolve.js +3 -1
  110. package/src/actions/scales/consumers.js +15 -6
  111. package/src/actions/scales/definitions.js +13 -4
  112. package/src/actions/scales/edit.js +42 -19
  113. package/src/actions/scales/materialize.js +57 -408
  114. package/src/actions/selection/actions.js +38 -24
  115. package/src/actions/selection/index.js +2 -0
  116. package/src/actions/titles/actions.js +63 -2
  117. package/src/actions/titles/index.js +2 -0
  118. package/src/actions/titles/resolve.js +12 -17
  119. package/src/composition.js +10 -0
  120. package/src/core/ChartProgram.js +200 -2
  121. package/src/core/action.js +10 -0
  122. package/src/core/validation.js +16 -0
  123. package/src/core/vocabulary.js +154 -4
  124. package/src/grammar/arcs.js +154 -0
  125. package/src/grammar/bars/aggregate.js +2 -2
  126. package/src/grammar/bars/geometry.js +3 -3
  127. package/src/grammar/bars/policy.js +7 -1
  128. package/src/grammar/coordinates.js +3 -7
  129. package/src/grammar/density.js +20 -1
  130. package/src/grammar/facets/dependencies.js +185 -0
  131. package/src/grammar/facets/guides.js +257 -0
  132. package/src/grammar/facets/scales.js +254 -0
  133. package/src/grammar/facets.js +122 -0
  134. package/src/grammar/lineSeries.js +111 -3
  135. package/src/grammar/polar.js +102 -0
  136. package/src/grammar/polarGuides.js +243 -0
  137. package/src/grammar/polarLineCommands.js +43 -0
  138. package/src/grammar/polarPaths.js +161 -0
  139. package/src/grammar/positionCompatibility.js +18 -2
  140. package/src/grammar/rects.js +26 -0
  141. package/src/grammar/scales/continuous.js +6 -1
  142. package/src/grammar/scales/fields.js +7 -0
  143. package/src/grammar/scales/ordinal.js +5 -2
  144. package/src/grammar/scales/policies.js +1 -1
  145. package/src/grammar/schemas/graphic.js +1 -1
  146. package/src/grammar/schemas/graphicBounds.js +64 -9
  147. package/src/grammar/schemas/graphicTree.js +44 -10
  148. package/src/grammar/schemas/semanticPath.js +31 -9
  149. package/src/grammar/seriesLayout.js +13 -2
  150. package/src/grammar/text.js +71 -0
  151. package/src/grammar/ticks.js +50 -13
  152. package/src/index.js +1 -0
  153. package/src/layout/composition.js +310 -0
  154. package/src/layout/facets.js +131 -0
  155. package/src/layout/title.js +6 -0
  156. package/src/materialization/bars/aggregate.js +5 -2
  157. package/src/materialization/bars/grouped.js +75 -37
  158. package/src/materialization/composition.js +152 -0
  159. package/src/materialization/compositionSnapshot.js +114 -0
  160. package/src/materialization/dependencies.js +71 -16
  161. package/src/materialization/encodings.js +14 -28
  162. package/src/materialization/facetGuides.js +426 -0
  163. package/src/materialization/facets.js +214 -0
  164. package/src/materialization/graphicHierarchy.js +15 -5
  165. package/src/materialization/guides/resources.js +46 -0
  166. package/src/materialization/marks.js +220 -14
  167. package/src/materialization/planner.js +105 -12
  168. package/src/materialization/rect.js +138 -0
  169. package/src/materialization/rectConfig.js +38 -0
  170. package/src/materialization/rowEncoding.js +33 -0
  171. package/src/materialization/scales/map.js +40 -0
  172. package/src/materialization/scales/policies/arc.js +37 -0
  173. package/src/materialization/scales/policies/bar.js +132 -0
  174. package/src/materialization/scales/policies/series.js +76 -0
  175. package/src/materialization/scales/resolve.js +326 -0
  176. package/src/materialization/selection/items/arc.js +29 -0
  177. package/src/materialization/selection/items/bar.js +255 -0
  178. package/src/materialization/selection/items/common.js +131 -0
  179. package/src/materialization/selection/items/index.js +6 -0
  180. package/src/materialization/selection/items/path.js +59 -0
  181. package/src/materialization/selection/items/point.js +55 -0
  182. package/src/materialization/selection/items/rect.js +23 -0
  183. package/src/materialization/selection/items/rule.js +25 -0
  184. package/src/materialization/selection/policies/arc.js +10 -0
  185. package/src/materialization/selection/policies/area.js +1 -1
  186. package/src/materialization/selection/policies/bar.js +1 -1
  187. package/src/materialization/selection/policies/index.js +5 -1
  188. package/src/materialization/selection/policies/line.js +1 -1
  189. package/src/materialization/selection/policies/point.js +1 -1
  190. package/src/materialization/selection/policies/rect.js +10 -0
  191. package/src/materialization/selection/policies/rule.js +1 -1
  192. package/src/materialization/selection/styles.js +24 -16
  193. package/src/materialization/text.js +156 -0
  194. package/src/renderers/canvas/index.js +68 -22
  195. package/types/extension.d.ts +1 -0
  196. package/types/index.d.ts +56 -1
  197. package/types/program.d.ts +664 -27
  198. package/src/actions/marks/line.js +0 -248
  199. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,40 @@
1
+ import {
2
+ isContinuousColorScaleType,
3
+ isDiscretizedColorScaleType,
4
+ isOrdinalScaleType,
5
+ mapContinuousScaleValues,
6
+ mapDiscretizedColors,
7
+ mapOrdinalValues,
8
+ mapSequentialColors
9
+ } from "../../grammar/scales.js";
10
+
11
+ export function mapScaleConsumerValues(values, resolvedScale, channel) {
12
+ if (channel === "color" && isDiscretizedColorScaleType(resolvedScale.type)) {
13
+ return mapDiscretizedColors(values, resolvedScale);
14
+ }
15
+ if (channel === "color" && isContinuousColorScaleType(resolvedScale.type)) {
16
+ return mapSequentialColors(
17
+ values,
18
+ resolvedScale.domain,
19
+ resolvedScale.range,
20
+ {
21
+ interpolation: resolvedScale.interpolate,
22
+ clamp: resolvedScale.clamp ?? false,
23
+ ...(Object.hasOwn(resolvedScale, "unknown")
24
+ ? { unknown: resolvedScale.unknown }
25
+ : {})
26
+ }
27
+ );
28
+ }
29
+ if (
30
+ ["color", "strokeDash", "shape"].includes(channel) &&
31
+ isOrdinalScaleType(resolvedScale.type)
32
+ ) {
33
+ return mapOrdinalValues(values, resolvedScale.domain, resolvedScale.range, {
34
+ ...(Object.hasOwn(resolvedScale, "unknown")
35
+ ? { unknown: resolvedScale.unknown }
36
+ : {})
37
+ });
38
+ }
39
+ return mapContinuousScaleValues(values, resolvedScale);
40
+ }
@@ -0,0 +1,37 @@
1
+ export function resolveArcAutoPositionRange({
2
+ consumers,
3
+ scale,
4
+ channel,
5
+ domain,
6
+ range,
7
+ markConfigs
8
+ }) {
9
+ if (
10
+ (scale.range !== "auto" && scale.range !== undefined) ||
11
+ consumers.length === 0 ||
12
+ !consumers.every(consumer => consumer.layer.mark?.type === "arc")
13
+ ) {
14
+ return range;
15
+ }
16
+ if (channel === "radius") {
17
+ const ratios = consumers.map(
18
+ consumer => markConfigs[consumer.layer.id]?.innerRadius ?? 0
19
+ );
20
+ if (new Set(ratios).size !== 1) {
21
+ throw new Error(
22
+ `Shared arc radius scale "${scale.id}" requires one innerRadius policy.`
23
+ );
24
+ }
25
+ const outer = Math.max(...range);
26
+ return [outer * ratios[0], outer];
27
+ }
28
+ if (
29
+ channel === "theta" &&
30
+ scale.type === "band" &&
31
+ consumers.every(consumer => consumer.encoding.aggregate === undefined)
32
+ ) {
33
+ const step = (range[1] - range[0]) / domain.length;
34
+ return [range[0] - step / 2, range[1] - step / 2];
35
+ }
36
+ return range;
37
+ }
@@ -0,0 +1,132 @@
1
+ import { normalizeOffsetPadding } from "../../../grammar/bars/geometry.js";
2
+ import {
3
+ BAR_GRAINS,
4
+ resolveBarChannels,
5
+ resolveBarGrain
6
+ } from "../../../grammar/bars/policy.js";
7
+ import { resolveHistogramBins } from "../../../grammar/histogram.js";
8
+
9
+ export function resolveBinnedBarDomain({
10
+ valuesByConsumer,
11
+ channel,
12
+ scale,
13
+ id,
14
+ allValues
15
+ }) {
16
+ const binnedBars = valuesByConsumer.filter(
17
+ ({ consumer }) =>
18
+ consumer.layer.mark?.type === "bar" &&
19
+ consumer.encoding.bin !== undefined
20
+ );
21
+ if (binnedBars.length === 0) return undefined;
22
+ if (channel !== "x" || binnedBars.length !== valuesByConsumer.length) {
23
+ throw new Error(
24
+ `Binned scale "${id}" cannot be shared with an unbinned consumer.`
25
+ );
26
+ }
27
+ const binDefinitions = new Set(
28
+ binnedBars.map(({ consumer }) => JSON.stringify(consumer.encoding.bin))
29
+ );
30
+ if (binDefinitions.size !== 1) {
31
+ throw new Error(`Binned scale "${id}" requires one shared bin definition.`);
32
+ }
33
+ return resolveHistogramBins({
34
+ values: allValues,
35
+ bin: binnedBars[0].consumer.encoding.bin,
36
+ domain: scale.domain,
37
+ nice: scale.nice ?? true,
38
+ zero: scale.zero ?? false
39
+ }).domain;
40
+ }
41
+
42
+ export function resolveOffsetScalePolicy({
43
+ consumers,
44
+ resolvedScales,
45
+ markConfigs,
46
+ id,
47
+ channel
48
+ }) {
49
+ const parentChannel = channel === "xOffset" ? "x" : "y";
50
+ const bandwidths = consumers.map(consumer => {
51
+ if (parentChannel === "x" && consumer.layer.encoding?.x?.bin !== undefined) {
52
+ return 1;
53
+ }
54
+ const parentScaleId = consumer.layer.encoding?.[parentChannel]?.scale;
55
+ return resolvedScales[parentScaleId]?.bandwidth;
56
+ });
57
+ if (
58
+ bandwidths.some(value => !Number.isFinite(value)) ||
59
+ new Set(bandwidths).size !== 1
60
+ ) {
61
+ throw new Error(
62
+ `${channel} scale "${id}" requires one shared resolved ${parentChannel} bandwidth.`
63
+ );
64
+ }
65
+ const paddings = consumers.map(consumer => normalizeOffsetPadding(
66
+ markConfigs[consumer.layer.id]?.[channel],
67
+ undefined,
68
+ channel
69
+ ));
70
+ const signatures = new Set(
71
+ paddings.map(padding => JSON.stringify(padding))
72
+ );
73
+ if (signatures.size !== 1) {
74
+ throw new Error(
75
+ `${channel} scale "${id}" requires one shared padding policy.`
76
+ );
77
+ }
78
+ return {
79
+ parentBandwidth: bandwidths[0],
80
+ ...paddings[0]
81
+ };
82
+ }
83
+
84
+ export function resolveTemporalBarBand(consumers, domain, range, values) {
85
+ const temporalBars = consumers.filter(consumer => {
86
+ const channels = resolveBarChannels(consumer.layer);
87
+ return resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate &&
88
+ channels?.category === consumer.channel &&
89
+ consumer.encoding.fieldType === "temporal";
90
+ });
91
+ if (temporalBars.length === 0) return undefined;
92
+ const temporalFields = new Set(
93
+ temporalBars.map(consumer => consumer.encoding.field)
94
+ );
95
+ const compatibleLines = consumers.filter(consumer =>
96
+ consumer.layer.mark?.type === "line" &&
97
+ consumer.encoding.fieldType === "temporal" &&
98
+ temporalFields.has(consumer.encoding.field)
99
+ );
100
+ if (temporalBars.length + compatibleLines.length !== consumers.length) {
101
+ throw new Error(
102
+ "A temporal bar position scale requires compatible bar or line consumers of one field."
103
+ );
104
+ }
105
+ const ordered = [...new Set(values)].sort((left, right) => left - right);
106
+ if (ordered.length < 2) {
107
+ throw new Error("Temporal bar position requires at least two distinct values.");
108
+ }
109
+ const minimumGap = Math.min(
110
+ ...ordered.slice(1).map((value, index) => value - ordered[index])
111
+ );
112
+ const domainSpan = Math.abs(domain[1] - domain[0]);
113
+ if (!(minimumGap > 0) || !(domainSpan > 0)) {
114
+ throw new Error("Temporal bar position requires an increasing time domain.");
115
+ }
116
+ const direction = Math.sign(range[1] - range[0]) || 1;
117
+ const estimatedBandwidth = Math.abs(range[1] - range[0]) * minimumGap /
118
+ (domainSpan + minimumGap);
119
+ const resolvedRange = [
120
+ range[0] + direction * estimatedBandwidth / 2,
121
+ range[1] - direction * estimatedBandwidth / 2
122
+ ];
123
+ const positions = ordered.map(value =>
124
+ resolvedRange[0] +
125
+ (value - domain[0]) / (domain[1] - domain[0]) *
126
+ (resolvedRange[1] - resolvedRange[0])
127
+ );
128
+ const bandwidth = Math.min(
129
+ ...positions.slice(1).map((value, index) => Math.abs(value - positions[index]))
130
+ );
131
+ return { bandwidth, range: resolvedRange };
132
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ isTransformedScaleType,
3
+ resolveContinuousDomain,
4
+ resolveTransformedDomain
5
+ } from "../../../grammar/scales.js";
6
+
7
+ export function resolveSeriesLayoutDomain({
8
+ id,
9
+ scale,
10
+ valuesByConsumer,
11
+ seriesLayouts
12
+ }) {
13
+ if (!seriesLayouts.some(Boolean)) return undefined;
14
+ const activeLayouts = seriesLayouts.filter(Boolean);
15
+ const layouts = new Set(activeLayouts.map(item => item.layout));
16
+ if (layouts.size !== 1) {
17
+ throw new Error(`Series layout scale "${id}" requires one layout policy.`);
18
+ }
19
+ const layoutConsumers = valuesByConsumer.filter(
20
+ (_, index) => seriesLayouts[index] !== undefined
21
+ );
22
+ const directConsumers = valuesByConsumer.filter(
23
+ (_, index) => seriesLayouts[index] === undefined
24
+ );
25
+ const compatibleDirect = directConsumers.every(({ consumer }) =>
26
+ layoutConsumers.every(({ consumer: layoutConsumer }) => {
27
+ const aggregate = layoutConsumer.encoding.aggregate;
28
+ return aggregate !== "count" &&
29
+ consumer.encoding.fieldType === "quantitative" &&
30
+ consumer.encoding.field === layoutConsumer.encoding.field;
31
+ })
32
+ );
33
+ if (!compatibleDirect) {
34
+ throw new Error(
35
+ `Series layout scale "${id}" cannot be shared with another policy.`
36
+ );
37
+ }
38
+ const layout = activeLayouts[0].layout;
39
+ const values = [
40
+ ...activeLayouts.flatMap(item => item.values),
41
+ ...directConsumers.flatMap(item => item.values)
42
+ ];
43
+ if (
44
+ ["group", "overlay"].includes(layout) &&
45
+ scale.domain !== "auto" &&
46
+ (Math.min(...scale.domain) > 0 || Math.max(...scale.domain) < 0)
47
+ ) {
48
+ throw new Error(
49
+ `Series layout scale "${id}" requires an explicit domain containing zero.`
50
+ );
51
+ }
52
+ const nice = layout === "fill" && scale.domain === "auto"
53
+ ? false
54
+ : scale.nice;
55
+ const zero = layout === "fill" && scale.domain === "auto"
56
+ ? false
57
+ : scale.zero;
58
+ return isTransformedScaleType(scale.type)
59
+ ? resolveTransformedDomain({
60
+ type: scale.type,
61
+ domain: scale.domain,
62
+ values,
63
+ nice: nice ?? false,
64
+ zero: zero ?? false,
65
+ ...(scale.base === undefined ? {} : { base: scale.base }),
66
+ ...(scale.exponent === undefined ? {} : { exponent: scale.exponent }),
67
+ ...(scale.constant === undefined ? {} : { constant: scale.constant })
68
+ })
69
+ : resolveContinuousDomain({
70
+ domain: scale.domain,
71
+ values,
72
+ type: scale.type,
73
+ nice,
74
+ zero
75
+ });
76
+ }
@@ -0,0 +1,326 @@
1
+ import {
2
+ isContinuousColorScaleType,
3
+ isDiscretePositionScaleType,
4
+ isDiscretizedColorScaleType,
5
+ isOrdinalScaleType,
6
+ isTransformedScaleType,
7
+ normalizeTransformParameters,
8
+ resolveColorRange,
9
+ resolveContinuousDomain,
10
+ resolveDiscretePositionScale,
11
+ resolveDiscretizedColorScale,
12
+ resolveOpacityRange,
13
+ resolveOrdinalDomain,
14
+ resolveOrdinalOffsetScale,
15
+ resolveOrdinalPositionScale,
16
+ resolveScaleRange,
17
+ resolveSequentialColorStops,
18
+ resolveShapeRange,
19
+ resolveSizeRange,
20
+ resolveStrokeDashRange,
21
+ resolveTransformedDomain,
22
+ SCALE_ROLES,
23
+ validateLinearScaleType,
24
+ validateOrdinalScaleType,
25
+ validateScaleTypeForRole,
26
+ validateTimeScaleType
27
+ } from "../../grammar/scales.js";
28
+ import { resolveArcAutoPositionRange } from "./policies/arc.js";
29
+ import {
30
+ resolveBinnedBarDomain,
31
+ resolveOffsetScalePolicy,
32
+ resolveTemporalBarBand
33
+ } from "./policies/bar.js";
34
+ import { resolveSeriesLayoutDomain } from "./policies/series.js";
35
+ import { OFFSET_POSITION_CHANNELS } from "../../core/vocabulary.js";
36
+
37
+ function resolveDefaultDomain({
38
+ scale,
39
+ allValues,
40
+ isOrdinalAppearance,
41
+ isOrdinalOffset,
42
+ isOrdinalPosition,
43
+ isDiscretePosition
44
+ }) {
45
+ if (
46
+ isOrdinalAppearance ||
47
+ isOrdinalOffset ||
48
+ isOrdinalPosition ||
49
+ isDiscretePosition
50
+ ) {
51
+ return resolveOrdinalDomain(scale.domain, allValues);
52
+ }
53
+ if (isTransformedScaleType(scale.type)) {
54
+ return resolveTransformedDomain({
55
+ type: scale.type,
56
+ domain: scale.domain,
57
+ values: allValues,
58
+ nice: scale.nice ?? false,
59
+ zero: scale.zero ?? false,
60
+ ...(scale.base === undefined ? {} : { base: scale.base }),
61
+ ...(scale.exponent === undefined ? {} : { exponent: scale.exponent }),
62
+ ...(scale.constant === undefined ? {} : { constant: scale.constant })
63
+ });
64
+ }
65
+ return resolveContinuousDomain({
66
+ domain: scale.domain,
67
+ values: allValues,
68
+ type: scale.type,
69
+ nice: scale.nice,
70
+ zero: scale.zero
71
+ });
72
+ }
73
+
74
+ function resolveRange({
75
+ scale,
76
+ channel,
77
+ domain,
78
+ bounds,
79
+ consumers,
80
+ markConfigs,
81
+ isSequentialColor,
82
+ isDiscretizedColor,
83
+ isOrdinalOffset,
84
+ discretizedScale
85
+ }) {
86
+ if (channel === "color") {
87
+ if (isDiscretizedColor) return discretizedScale.range;
88
+ if (isSequentialColor) return resolveSequentialColorStops(scale.range);
89
+ return resolveColorRange(scale.range, domain.length);
90
+ }
91
+ if (channel === "strokeDash") return resolveStrokeDashRange(scale.range);
92
+ if (channel === "shape") return resolveShapeRange(scale.range);
93
+ if (channel === "size") return resolveSizeRange(scale.range);
94
+ if (channel === "opacity") return resolveOpacityRange(scale.range);
95
+ if (isOrdinalOffset) return undefined;
96
+ return resolveArcAutoPositionRange({
97
+ consumers,
98
+ scale,
99
+ channel,
100
+ domain,
101
+ range: resolveScaleRange(scale.range, channel, bounds),
102
+ markConfigs
103
+ });
104
+ }
105
+
106
+ function resolveContinuousScale({
107
+ scale,
108
+ domain,
109
+ range,
110
+ isSequentialColor,
111
+ isOrdinalAppearance
112
+ }) {
113
+ return {
114
+ type: isSequentialColor
115
+ ? "sequential"
116
+ : isOrdinalAppearance
117
+ ? validateOrdinalScaleType(scale.type)
118
+ : scale.type === "time"
119
+ ? validateTimeScaleType(scale.type)
120
+ : isTransformedScaleType(scale.type)
121
+ ? validateScaleTypeForRole(
122
+ scale.type,
123
+ SCALE_ROLES.quantitativePosition
124
+ )
125
+ : validateLinearScaleType(scale.type),
126
+ domain,
127
+ range,
128
+ ...(scale.clamp === undefined ? {} : { clamp: scale.clamp }),
129
+ ...(scale.type === "log"
130
+ ? {
131
+ base: normalizeTransformParameters("log", {
132
+ ...(scale.base === undefined ? {} : { base: scale.base })
133
+ }).base
134
+ }
135
+ : scale.type === "pow"
136
+ ? {
137
+ exponent: normalizeTransformParameters("pow", {
138
+ ...(scale.exponent === undefined
139
+ ? {}
140
+ : { exponent: scale.exponent })
141
+ }).exponent
142
+ }
143
+ : scale.type === "symlog"
144
+ ? {
145
+ constant: normalizeTransformParameters("symlog", {
146
+ ...(scale.constant === undefined
147
+ ? {}
148
+ : { constant: scale.constant })
149
+ }).constant
150
+ }
151
+ : {}),
152
+ ...(isSequentialColor ? { interpolate: scale.interpolate ?? "rgb" } : {}),
153
+ ...(Object.hasOwn(scale, "unknown") ? { unknown: scale.unknown } : {})
154
+ };
155
+ }
156
+
157
+ function reverseResolvedScale(scale) {
158
+ const range = [...scale.range].reverse();
159
+ const direction = Math.sign(range[1] - range[0]) || 1;
160
+ const offset = scale.start === undefined
161
+ ? undefined
162
+ : Math.abs(scale.start - scale.range[0]);
163
+ return {
164
+ ...scale,
165
+ range,
166
+ ...(scale.step === undefined ? {} : { step: -scale.step }),
167
+ ...(offset === undefined ? {} : { start: range[0] + direction * offset })
168
+ };
169
+ }
170
+
171
+ export function resolveScaleMaterialization({
172
+ id,
173
+ scale,
174
+ channel,
175
+ consumers,
176
+ valuesByConsumer,
177
+ bounds,
178
+ resolvedScales,
179
+ markConfigs
180
+ }) {
181
+ const allValues = valuesByConsumer
182
+ .flatMap(item => item.values)
183
+ .filter(value => value !== undefined);
184
+ const isSequentialColor = channel === "color" &&
185
+ isContinuousColorScaleType(scale.type);
186
+ const isDiscretizedColor = channel === "color" &&
187
+ isDiscretizedColorScaleType(scale.type);
188
+ const isOrdinalAppearance =
189
+ ["color", "strokeDash", "shape"].includes(channel) &&
190
+ isOrdinalScaleType(scale.type);
191
+ const isOrdinalOffset = OFFSET_POSITION_CHANNELS.includes(channel);
192
+ const isOrdinalPosition =
193
+ !isOrdinalAppearance && !isOrdinalOffset && isOrdinalScaleType(scale.type);
194
+ const isDiscretePosition =
195
+ !isOrdinalAppearance &&
196
+ !isOrdinalOffset &&
197
+ isDiscretePositionScaleType(scale.type);
198
+
199
+ let discretizedScale;
200
+ if (isDiscretizedColor) {
201
+ discretizedScale = resolveDiscretizedColorScale({
202
+ type: scale.type,
203
+ domain: scale.domain,
204
+ range: scale.range,
205
+ values: allValues
206
+ });
207
+ }
208
+ const binnedDomain = isDiscretizedColor
209
+ ? undefined
210
+ : resolveBinnedBarDomain({
211
+ valuesByConsumer,
212
+ channel,
213
+ scale,
214
+ id,
215
+ allValues
216
+ });
217
+ const seriesLayouts = valuesByConsumer.map(item => item.seriesLayout);
218
+ const seriesDomain = isDiscretizedColor || binnedDomain !== undefined
219
+ ? undefined
220
+ : resolveSeriesLayoutDomain({
221
+ id,
222
+ scale,
223
+ valuesByConsumer,
224
+ seriesLayouts
225
+ });
226
+ const domain = isDiscretizedColor
227
+ ? discretizedScale.domain
228
+ : binnedDomain ?? seriesDomain ?? resolveDefaultDomain({
229
+ scale,
230
+ allValues,
231
+ isOrdinalAppearance,
232
+ isOrdinalOffset,
233
+ isOrdinalPosition,
234
+ isDiscretePosition
235
+ });
236
+ const range = resolveRange({
237
+ scale,
238
+ channel,
239
+ domain,
240
+ bounds,
241
+ consumers,
242
+ markConfigs,
243
+ isSequentialColor,
244
+ isDiscretizedColor,
245
+ isOrdinalOffset,
246
+ discretizedScale
247
+ });
248
+ if (channel === "shape" && domain.length > range.length) {
249
+ throw new Error(
250
+ `Shape scale "${id}" requires at least one distinct shape per domain value.`
251
+ );
252
+ }
253
+
254
+ let resolvedScale = isDiscretizedColor
255
+ ? discretizedScale
256
+ : isOrdinalOffset
257
+ ? resolveOrdinalOffsetScale({
258
+ domain: scale.domain,
259
+ values: allValues,
260
+ range: scale.range,
261
+ ...resolveOffsetScalePolicy({
262
+ consumers,
263
+ resolvedScales,
264
+ markConfigs,
265
+ id,
266
+ channel
267
+ }),
268
+ channel
269
+ })
270
+ : isDiscretePosition
271
+ ? resolveDiscretePositionScale({
272
+ type: scale.type,
273
+ domain: scale.domain,
274
+ values: allValues,
275
+ range: channel === "theta" && consumers.every(
276
+ consumer => consumer.layer.mark?.type === "arc"
277
+ ) ? range : scale.range,
278
+ channel,
279
+ bounds,
280
+ ...(scale.type === "band"
281
+ ? {
282
+ paddingInner: scale.paddingInner,
283
+ paddingOuter: scale.paddingOuter
284
+ }
285
+ : { padding: scale.padding }),
286
+ align: scale.align,
287
+ unknown: scale.unknown
288
+ })
289
+ : isOrdinalPosition
290
+ ? resolveOrdinalPositionScale({
291
+ domain: scale.domain,
292
+ values: allValues,
293
+ range: scale.range,
294
+ channel,
295
+ bounds,
296
+ unknown: scale.unknown
297
+ })
298
+ : resolveContinuousScale({
299
+ scale,
300
+ domain,
301
+ range,
302
+ isSequentialColor,
303
+ isOrdinalAppearance
304
+ });
305
+ if (Object.hasOwn(scale, "unknown") && !Object.hasOwn(resolvedScale, "unknown")) {
306
+ resolvedScale = { ...resolvedScale, unknown: scale.unknown };
307
+ }
308
+ if (scale.type === "time" && ["x", "y"].includes(channel)) {
309
+ const temporalBarBand = resolveTemporalBarBand(
310
+ consumers,
311
+ domain,
312
+ range,
313
+ allValues
314
+ );
315
+ if (temporalBarBand !== undefined) {
316
+ resolvedScale = {
317
+ ...resolvedScale,
318
+ range: temporalBarBand.range,
319
+ bandwidth: temporalBarBand.bandwidth
320
+ };
321
+ }
322
+ }
323
+ return scale.reverse === true
324
+ ? reverseResolvedScale(resolvedScale)
325
+ : resolvedScale;
326
+ }
@@ -0,0 +1,29 @@
1
+ import { deriveArcSectors } from "../../../grammar/arcs.js";
2
+ import { resolvePolarFrame } from "../../../grammar/polar.js";
3
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
4
+ import { finalizeItems, uniqueFields } from "./common.js";
5
+
6
+ export function resolveArcItems(program, layer, dataset) {
7
+ const thetaScale = program.resolvedScales[layer.encoding?.theta?.scale];
8
+ const radiusScale = program.resolvedScales[layer.encoding?.radius?.scale];
9
+ const frame = resolvePolarFrame(resolveGraphicBounds(program));
10
+ const derived = deriveArcSectors(dataset.values, layer, {
11
+ thetaScale,
12
+ ...(radiusScale === undefined ? {} : { radiusScale }),
13
+ frame,
14
+ innerRadiusRatio: program.markConfigs[layer.id]?.innerRadius ?? 0
15
+ });
16
+ const definitions = derived.sectors.map(sector => {
17
+ const members = sector.sourceIndices.map(index => dataset.values[index]);
18
+ return {
19
+ fields: uniqueFields(members),
20
+ channels: {
21
+ theta: sector.theta,
22
+ ...(sector.radius === undefined ? {} : { radius: sector.radius }),
23
+ ...(sector.color === undefined ? {} : { color: sector.color })
24
+ },
25
+ members
26
+ };
27
+ });
28
+ return finalizeItems(program, layer, "sector", definitions, "path");
29
+ }