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
@@ -0,0 +1,33 @@
1
+ import { POSITION_CHANNELS } from "../core/vocabulary.js";
2
+ import {
3
+ mapOrdinalPositionValues,
4
+ readNominalField,
5
+ readQuantitativeField,
6
+ readScaleField,
7
+ readTemporalField
8
+ } from "../grammar/scales/index.js";
9
+ import { mapScaleConsumerValues } from "./scales/map.js";
10
+
11
+ export function resolveRowEncodingValues(program, layer, dataset, channel) {
12
+ const encoding = layer.encoding?.[channel];
13
+ if (encoding === undefined) return undefined;
14
+ const scale = program.resolvedScales[encoding.scale];
15
+ if (scale === undefined) {
16
+ throw new Error(
17
+ `${layer.mark.type} mark "${layer.id}" requires resolved ${channel} scale "${encoding.scale}".`
18
+ );
19
+ }
20
+ const categorical = ["nominal", "ordinal"].includes(encoding.fieldType);
21
+ const values = Object.hasOwn(scale, "unknown")
22
+ ? readScaleField(dataset.values, encoding.field, encoding.fieldType, {
23
+ allowUnknown: true
24
+ })
25
+ : categorical
26
+ ? readNominalField(dataset.values, encoding.field)
27
+ : encoding.fieldType === "temporal"
28
+ ? readTemporalField(dataset.values, encoding.field)
29
+ : readQuantitativeField(dataset.values, encoding.field);
30
+ return categorical && POSITION_CHANNELS.includes(channel)
31
+ ? mapOrdinalPositionValues(values, scale)
32
+ : mapScaleConsumerValues(values, scale, channel);
33
+ }
@@ -0,0 +1,94 @@
1
+ import { POSITION_ENCODING_CHANNELS } from "../core/vocabulary.js";
2
+ import { materializedLegendUsesScale } from "./legends.js";
3
+ import { buildMaterializationPlan } from "./planner.js";
4
+
5
+ const CARTESIAN_AXES = Object.freeze([
6
+ Object.freeze({ channel: "x", prefix: "X", lineObject: "xAxisLine" }),
7
+ Object.freeze({ channel: "y", prefix: "Y", lineObject: "yAxisLine" })
8
+ ]);
9
+ const POLAR_AXES = Object.freeze([
10
+ Object.freeze({ channel: "theta", prefix: "Theta" }),
11
+ Object.freeze({ channel: "radius", prefix: "Radial" })
12
+ ]);
13
+ const GRIDS = Object.freeze([
14
+ Object.freeze({ role: "horizontal", op: "rematerializeHorizontalGrid" }),
15
+ Object.freeze({ role: "vertical", op: "rematerializeVerticalGrid" }),
16
+ Object.freeze({ role: "theta", op: "rematerializeThetaGrid" }),
17
+ Object.freeze({ role: "radial", op: "rematerializeRadialGrid" })
18
+ ]);
19
+
20
+ function usesPositionalScale(program, id) {
21
+ return program.semanticSpec.layers.some(layer =>
22
+ POSITION_ENCODING_CHANNELS.some(
23
+ channel => layer.encoding?.[channel]?.scale === id
24
+ )
25
+ );
26
+ }
27
+
28
+ function usesRadialScale(program, id) {
29
+ return program.semanticSpec.layers.some(layer =>
30
+ layer.encoding?.radius?.scale === id
31
+ );
32
+ }
33
+
34
+ function semanticGuideUsesScale(program, id) {
35
+ return [
36
+ ...CARTESIAN_AXES.map(({ channel }) =>
37
+ program.semanticSpec.guides.axis?.[channel]
38
+ ),
39
+ ...POLAR_AXES.map(({ channel }) =>
40
+ program.semanticSpec.guides.axis?.[channel]
41
+ ),
42
+ ...GRIDS.map(({ role }) => program.semanticSpec.guides.grid?.[role])
43
+ ].some(guide => guide?.scale === id);
44
+ }
45
+
46
+ export function needsCanvasScaleRematerialization(program, scale) {
47
+ return (
48
+ (scale.range === "auto" ||
49
+ usesRadialScale(program, scale.id) ||
50
+ semanticGuideUsesScale(program, scale.id)) &&
51
+ program.resolvedScales[scale.id] !== undefined &&
52
+ usesPositionalScale(program, scale.id)
53
+ );
54
+ }
55
+
56
+ function componentEditOperation(prefix, component) {
57
+ const suffix = component[0].toUpperCase() + component.slice(1);
58
+ return `edit${prefix}Axis${suffix}`;
59
+ }
60
+
61
+ export function planScaleGuideRematerialization(program, id) {
62
+ const guides = [];
63
+ const objects = program.graphicSpec?.objects ?? {};
64
+ const guideConfigs = program.guideConfigs ?? {};
65
+ for (const axis of CARTESIAN_AXES) {
66
+ if (
67
+ objects[axis.lineObject] &&
68
+ program.semanticSpec.guides.axis?.[axis.channel]?.scale === id
69
+ ) {
70
+ guides.push({ op: componentEditOperation(axis.prefix, "line") });
71
+ }
72
+ for (const component of ["ticks", "labels", "title"]) {
73
+ if (guideConfigs.axis?.[axis.channel]?.[component]?.scale === id) {
74
+ guides.push({ op: componentEditOperation(axis.prefix, component) });
75
+ }
76
+ }
77
+ }
78
+ for (const axis of POLAR_AXES) {
79
+ for (const component of ["line", "ticks", "labels", "title"]) {
80
+ if (guideConfigs.axis?.[axis.channel]?.[component]?.scale === id) {
81
+ guides.push({ op: componentEditOperation(axis.prefix, component) });
82
+ }
83
+ }
84
+ }
85
+ for (const grid of GRIDS) {
86
+ if (guideConfigs.grid?.[grid.role]?.scale === id) {
87
+ guides.push({ op: grid.op });
88
+ }
89
+ }
90
+ if (materializedLegendUsesScale(program, id)) {
91
+ guides.push({ op: "rematerializeLegend" });
92
+ }
93
+ return buildMaterializationPlan({ guides });
94
+ }
@@ -0,0 +1,40 @@
1
+ import {
2
+ isContinuousColorScaleType,
3
+ isDiscretizedColorScaleType,
4
+ isOrdinalScaleType,
5
+ mapContinuousScaleValues,
6
+ mapDiscretizedColors,
7
+ mapOrdinalValues,
8
+ mapSequentialColors
9
+ } from "../../grammar/scales/index.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/index.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
+ }