ggaction 0.0.10 → 0.0.12

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 (224) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +5 -3
  3. package/knowledge/action-card.schema.json +1 -0
  4. package/knowledge/action-cards.json +1 -10941
  5. package/knowledge/action-cards.schema.json +1 -0
  6. package/knowledge/intent-taxonomy.json +1 -183
  7. package/knowledge/intent-taxonomy.schema.json +1 -0
  8. package/knowledge/mcp-resources.json +1 -95
  9. package/knowledge/mcp-resources.schema.json +1 -0
  10. package/knowledge/task-packet.schema.json +1 -159
  11. package/knowledge/task-resolver.js +1 -1230
  12. package/package.json +18 -2
  13. package/src/actions/boxPlots/edit.js +45 -298
  14. package/src/actions/boxPlots/options.js +46 -77
  15. package/src/actions/boxPlots/resolve.js +1 -1
  16. package/src/actions/charts/bar.js +3 -0
  17. package/src/actions/charts/heatmap.js +73 -83
  18. package/src/actions/charts/index.js +1 -1
  19. package/src/actions/charts/parallel.js +35 -1
  20. package/src/actions/charts/shared.js +20 -0
  21. package/src/actions/composition/actions.js +46 -49
  22. package/src/actions/data/bin2d.js +105 -2
  23. package/src/actions/data/box.js +17 -17
  24. package/src/actions/data/density.js +42 -78
  25. package/src/actions/data/gradientProfile.js +22 -52
  26. package/src/actions/data/horizon.js +16 -41
  27. package/src/actions/data/index.js +10 -10
  28. package/src/actions/data/interval.js +16 -38
  29. package/src/actions/data/intervalEdit.js +55 -0
  30. package/src/actions/data/regression.js +7 -17
  31. package/src/actions/data/shared.js +47 -0
  32. package/src/actions/data/timeUnit.js +14 -38
  33. package/src/actions/data/window.js +14 -38
  34. package/src/actions/distributions/revision.js +154 -34
  35. package/src/actions/encodings/angle.js +9 -10
  36. package/src/actions/encodings/appearance.js +17 -51
  37. package/src/actions/encodings/density/resolve.js +3 -1
  38. package/src/actions/encodings/density.js +1 -15
  39. package/src/actions/encodings/horizon/resolve.js +13 -3
  40. package/src/actions/encodings/horizon.js +8 -7
  41. package/src/actions/encodings/index.js +9 -9
  42. package/src/actions/encodings/offset.js +41 -45
  43. package/src/actions/encodings/parallel.js +9 -10
  44. package/src/actions/encodings/pathOrder.js +6 -13
  45. package/src/actions/encodings/position/apply.js +30 -0
  46. package/src/actions/encodings/position/policies/arc.js +23 -2
  47. package/src/actions/encodings/position/policies/index.js +4 -4
  48. package/src/actions/encodings/position/policies/line.js +47 -17
  49. package/src/actions/encodings/position/resolve.js +36 -7
  50. package/src/actions/encodings/ranged.js +18 -23
  51. package/src/actions/encodings/remove.js +15 -25
  52. package/src/actions/encodings/ruleAppearance.js +9 -24
  53. package/src/actions/encodings/shared.js +37 -0
  54. package/src/actions/encodings/strokeDash.js +8 -23
  55. package/src/actions/encodings/text.js +9 -10
  56. package/src/actions/errorBands/create.js +105 -39
  57. package/src/actions/errorBands/edit.js +89 -93
  58. package/src/actions/errorBands/index.js +1 -2
  59. package/src/actions/errorBars/create.js +212 -30
  60. package/src/actions/errorBars/edit.js +91 -48
  61. package/src/actions/errorBars/index.js +1 -2
  62. package/src/actions/facets/actions.js +45 -69
  63. package/src/actions/facets/derive.js +38 -53
  64. package/src/actions/facets/replay.js +0 -1
  65. package/src/actions/gradientPlots/components.js +59 -48
  66. package/src/actions/gradientPlots/edit.js +35 -216
  67. package/src/actions/gradientPlots/index.js +11 -10
  68. package/src/actions/gradientPlots/materialize.js +21 -35
  69. package/src/actions/gradientPlots/options.js +5 -7
  70. package/src/actions/gradientPlots/paint.js +9 -6
  71. package/src/actions/gradientPlots/resolve.js +1 -1
  72. package/src/actions/guides/axes/index.js +3 -3
  73. package/src/actions/guides/axes/labels.js +68 -26
  74. package/src/actions/guides/axes/lines.js +22 -2
  75. package/src/actions/guides/axes/policy.js +28 -8
  76. package/src/actions/guides/axes/ticks.js +27 -9
  77. package/src/actions/guides/axes/titles.js +113 -65
  78. package/src/actions/guides/grids/resolve.js +14 -0
  79. package/src/actions/guides/guides.js +2 -4
  80. package/src/actions/guides/index.js +2 -2
  81. package/src/actions/guides/legends/categorical/actions.js +44 -68
  82. package/src/actions/guides/legends/categorical/components.js +6 -1
  83. package/src/actions/guides/legends/categorical/index.js +2 -2
  84. package/src/actions/guides/legends/categorical/layout.js +148 -175
  85. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  86. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  87. package/src/actions/guides/legends/continuous/common.js +117 -66
  88. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  89. package/src/actions/guides/legends/continuous/interval.js +97 -76
  90. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  91. package/src/actions/guides/legends/edit.js +14 -1
  92. package/src/actions/guides/legends/index.js +1 -1
  93. package/src/actions/guides/legends/lane.js +2 -1
  94. package/src/actions/guides/legends/remove.js +8 -0
  95. package/src/actions/guides/legends/size.js +36 -43
  96. package/src/actions/guides/legends/strokeWidth.js +20 -26
  97. package/src/actions/guides/polar/axes/labels.js +17 -1
  98. package/src/actions/guides/polar/axes/shared.js +7 -1
  99. package/src/actions/guides/polar/axes/titles.js +19 -2
  100. package/src/actions/guides/polar/grids.js +4 -0
  101. package/src/actions/guides/polar/resolve.js +6 -0
  102. package/src/actions/index.js +13 -13
  103. package/src/actions/intervals/resolve.js +87 -117
  104. package/src/actions/marks/arc/actions.js +31 -36
  105. package/src/actions/marks/area/actions.js +23 -31
  106. package/src/actions/marks/index.js +6 -6
  107. package/src/actions/marks/line/actions.js +33 -52
  108. package/src/actions/marks/line/materialize.js +7 -5
  109. package/src/actions/marks/point/materialize.js +38 -16
  110. package/src/actions/marks/remove.js +21 -0
  111. package/src/actions/marks/rule/actions.js +79 -54
  112. package/src/actions/marks/shared.js +8 -4
  113. package/src/actions/marks/text/actions.js +14 -6
  114. package/src/actions/marks/text/layout.js +21 -45
  115. package/src/actions/marks/tick/actions.js +22 -21
  116. package/src/actions/primitives/createGraphics.js +4 -0
  117. package/src/actions/primitives/editGraphics.js +4 -0
  118. package/src/actions/primitives/index.js +3 -3
  119. package/src/actions/primitives/semantic.js +1 -1
  120. package/src/actions/primitives/semanticValidation/index.js +2 -2
  121. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  122. package/src/actions/regression/edit.js +3 -43
  123. package/src/actions/scales/consumers/families.js +24 -4
  124. package/src/actions/scales/consumers/index.js +1 -1
  125. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  126. package/src/actions/scales/definitions.js +100 -103
  127. package/src/actions/scales/materialize.js +5 -0
  128. package/src/actions/selection/actions.js +79 -121
  129. package/src/actions/titles/actions.js +48 -94
  130. package/src/actions/titles/resolve.js +14 -27
  131. package/src/actions/violinPlots/create.js +37 -44
  132. package/src/core/textMetrics.js +74 -37
  133. package/src/core/validation.js +18 -0
  134. package/src/core/vocabulary.js +2 -2
  135. package/src/grammar/aggregate.js +48 -28
  136. package/src/grammar/arcs.js +170 -32
  137. package/src/grammar/areaSeries.js +14 -16
  138. package/src/grammar/bin2d.js +37 -23
  139. package/src/grammar/boxPlot.js +14 -13
  140. package/src/grammar/categoricalDensity.js +14 -14
  141. package/src/grammar/categoryOrder.js +8 -14
  142. package/src/grammar/curveCommands.js +87 -20
  143. package/src/grammar/density.js +162 -141
  144. package/src/grammar/facets/index.js +14 -0
  145. package/src/grammar/facets/scales.js +7 -4
  146. package/src/grammar/gradientProfile.js +12 -15
  147. package/src/grammar/histogram.js +145 -83
  148. package/src/grammar/horizon.js +60 -23
  149. package/src/grammar/interval.js +14 -13
  150. package/src/grammar/lineSeries.js +91 -61
  151. package/src/grammar/numeric.js +290 -0
  152. package/src/grammar/paint.js +3 -4
  153. package/src/grammar/palettes.js +21 -32
  154. package/src/grammar/pathCommands.js +6 -0
  155. package/src/grammar/polarPaths.js +5 -5
  156. package/src/grammar/positionCompatibility.js +1 -1
  157. package/src/grammar/regression/derive.js +48 -9
  158. package/src/grammar/regression/models.js +248 -69
  159. package/src/grammar/regression/parameters.js +8 -0
  160. package/src/grammar/rules.js +2 -0
  161. package/src/grammar/scales/color.js +35 -26
  162. package/src/grammar/scales/continuous.js +71 -24
  163. package/src/grammar/scales/definition.js +1 -1
  164. package/src/grammar/scales/discretized.js +42 -12
  165. package/src/grammar/scales/fields.js +21 -4
  166. package/src/grammar/scales/ordinal.js +139 -60
  167. package/src/grammar/scales/temporal.js +40 -11
  168. package/src/grammar/scales/transformed.js +199 -52
  169. package/src/grammar/schemas/graphicBounds.js +270 -79
  170. package/src/grammar/seriesLayout.js +50 -18
  171. package/src/grammar/ticks.js +123 -53
  172. package/src/grammar/timeUnit.js +18 -8
  173. package/src/grammar/transforms.js +4 -1
  174. package/src/grammar/window.js +70 -57
  175. package/src/index.js +1 -1
  176. package/src/layout/canvas.js +4 -8
  177. package/src/layout/composition.js +39 -40
  178. package/src/layout/facets.js +55 -30
  179. package/src/layout/labels.js +91 -15
  180. package/src/layout/legend.js +22 -20
  181. package/src/layout/legendLane.js +54 -40
  182. package/src/layout/title.js +7 -7
  183. package/src/materialization/bars/aggregate.js +14 -40
  184. package/src/materialization/bars/grouped.js +7 -18
  185. package/src/materialization/bars/histogram.js +2 -19
  186. package/src/materialization/bars/ranged.js +20 -15
  187. package/src/materialization/bars/resolve.js +21 -0
  188. package/src/materialization/facetGuides/legacyCategorical.js +9 -2
  189. package/src/materialization/facetGuides/placement.js +49 -19
  190. package/src/materialization/facetGuides/preparation.js +35 -6
  191. package/src/materialization/facets.js +42 -28
  192. package/src/materialization/guides/resources.js +2 -1
  193. package/src/materialization/marks/capabilities.js +15 -4
  194. package/src/materialization/marks/index.js +29 -6
  195. package/src/materialization/rowEncoding.js +57 -1
  196. package/src/materialization/scales/policies/bar.js +61 -102
  197. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  198. package/src/materialization/scales/policies/offset.js +54 -0
  199. package/src/materialization/scales/policies/series.js +13 -7
  200. package/src/materialization/scales/resolve.js +6 -9
  201. package/src/materialization/selection/items/arc.js +16 -2
  202. package/src/materialization/selection/items/bar.js +4 -2
  203. package/src/materialization/selection/items/common.js +21 -1
  204. package/src/materialization/selection/items/path.js +19 -2
  205. package/src/materialization/selection/items/point.js +4 -16
  206. package/src/materialization/selection/items/tick.js +13 -21
  207. package/src/materialization/selection/point.js +11 -4
  208. package/src/materialization/selection/styles.js +19 -27
  209. package/src/materialization/text.js +4 -1
  210. package/src/mcp/adapter.js +8 -3
  211. package/src/renderers/canvas/fill.js +2 -2
  212. package/src/renderers/canvas/index.js +35 -2
  213. package/src/renderers/canvas/native.js +160 -0
  214. package/src/renderers/pdf.js +14 -0
  215. package/src/renderers/svg.js +37 -4
  216. package/types/index.d.ts +3 -0
  217. package/types/program.d.ts +523 -131
  218. package/types/svg.d.ts +1 -0
  219. package/src/actions/errorBands/components.js +0 -81
  220. package/src/actions/errorBands/options.js +0 -39
  221. package/src/actions/errorBands/resolve.js +0 -28
  222. package/src/actions/errorBars/components.js +0 -62
  223. package/src/actions/errorBars/options.js +0 -58
  224. package/src/actions/errorBars/resolve.js +0 -25
@@ -1,11 +1,19 @@
1
- import { POSITION_CHANNELS } from "../core/vocabulary.js";
2
1
  import {
2
+ OFFSET_POSITION_CHANNELS,
3
+ POSITION_CHANNELS
4
+ } from "../core/vocabulary.js";
5
+ import {
6
+ mapOrdinalOffsetValues,
3
7
  mapOrdinalPositionValues,
4
8
  readNominalField,
5
9
  readQuantitativeField,
6
10
  readScaleField,
7
11
  readTemporalField
8
12
  } from "../grammar/scales/index.js";
13
+ import {
14
+ finiteMidpoint,
15
+ requireFiniteResult
16
+ } from "../grammar/numeric.js";
9
17
  import { mapScaleConsumerValues } from "./scales/map.js";
10
18
 
11
19
  export function resolveRowEncodingValues(program, layer, dataset, channel) {
@@ -27,7 +35,55 @@ export function resolveRowEncodingValues(program, layer, dataset, channel) {
27
35
  : encoding.fieldType === "temporal"
28
36
  ? readTemporalField(dataset.values, encoding.field)
29
37
  : readQuantitativeField(dataset.values, encoding.field);
38
+ if (categorical && OFFSET_POSITION_CHANNELS.includes(channel)) {
39
+ return mapOrdinalOffsetValues(values, scale);
40
+ }
30
41
  return categorical && POSITION_CHANNELS.includes(channel)
31
42
  ? mapOrdinalPositionValues(values, scale)
32
43
  : mapScaleConsumerValues(values, scale, channel);
33
44
  }
45
+
46
+ export function applyOffsetPositionValues({
47
+ base,
48
+ offset,
49
+ parentScale,
50
+ offsetScale,
51
+ channel
52
+ }) {
53
+ if (base.length !== offset.length) {
54
+ throw new Error(`${channel} offset values must match the base position grain.`);
55
+ }
56
+ const midpoint = finiteMidpoint(offsetScale.range[0], offsetScale.range[1]);
57
+ const direction = Math.sign(parentScale.step) ||
58
+ Math.sign(parentScale.range[1] - parentScale.range[0]) || 1;
59
+ return Object.freeze(base.map((value, index) => {
60
+ if (!Number.isFinite(value) || !Number.isFinite(offset[index])) {
61
+ return undefined;
62
+ }
63
+ return requireFiniteResult(
64
+ value + direction * (offset[index] - midpoint),
65
+ `${channel} offset position`
66
+ );
67
+ }));
68
+ }
69
+
70
+ export function resolveRowPositionValues(program, layer, dataset, channel) {
71
+ const base = resolveRowEncodingValues(program, layer, dataset, channel);
72
+ if (base === undefined) return undefined;
73
+ const offsetChannel = channel === "x" ? "xOffset" : "yOffset";
74
+ const offsetEncoding = layer.encoding?.[offsetChannel];
75
+ if (offsetEncoding === undefined) return base;
76
+ const offset = resolveRowEncodingValues(
77
+ program,
78
+ layer,
79
+ dataset,
80
+ offsetChannel
81
+ );
82
+ return applyOffsetPositionValues({
83
+ base,
84
+ offset,
85
+ parentScale: program.resolvedScales[layer.encoding[channel].scale],
86
+ offsetScale: program.resolvedScales[offsetEncoding.scale],
87
+ channel
88
+ });
89
+ }
@@ -1,88 +1,14 @@
1
- import { normalizeOffsetPadding } from "../../../grammar/bars/geometry.js";
2
1
  import {
3
2
  BAR_GRAINS,
4
3
  resolveBarChannels,
5
4
  resolveBarGrain
6
5
  } 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 => {
6
+ import {
7
+ interpolateNumber,
8
+ inverseLerp
9
+ } from "../../../grammar/numeric.js";
10
+ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
11
+ const temporalBars = valuesByConsumer.filter(({ consumer }) => {
86
12
  const channels = resolveBarChannels(consumer.layer);
87
13
  return resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate &&
88
14
  channels?.category === consumer.channel &&
@@ -90,43 +16,76 @@ export function resolveTemporalBarBand(consumers, domain, range, values) {
90
16
  });
91
17
  if (temporalBars.length === 0) return undefined;
92
18
  const temporalFields = new Set(
93
- temporalBars.map(consumer => consumer.encoding.field)
19
+ temporalBars.map(({ consumer }) => consumer.encoding.field)
94
20
  );
95
- const compatibleLines = consumers.filter(consumer =>
96
- consumer.layer.mark?.type === "line" &&
97
- consumer.encoding.fieldType === "temporal" &&
98
- temporalFields.has(consumer.encoding.field)
21
+ const compatibleConsumers = valuesByConsumer.filter(({ consumer }) =>
22
+ ["line", "rule", "text"].includes(consumer.layer.mark?.type) &&
23
+ consumer.encoding.fieldType === "temporal" &&
24
+ temporalFields.has(consumer.encoding.field)
99
25
  );
100
- if (temporalBars.length + compatibleLines.length !== consumers.length) {
26
+ if (
27
+ temporalBars.length + compatibleConsumers.length !==
28
+ valuesByConsumer.length
29
+ ) {
101
30
  throw new Error(
102
- "A temporal bar position scale requires compatible bar or line consumers of one field."
31
+ "A temporal bar position scale requires compatible bar, line, rule, or text consumers of one field."
103
32
  );
104
33
  }
34
+ const values = temporalBars.flatMap(item => item.values);
105
35
  const ordered = [...new Set(values)].sort((left, right) => left - right);
106
36
  if (ordered.length < 2) {
107
37
  throw new Error("Temporal bar position requires at least two distinct values.");
108
38
  }
109
- const minimumGap = Math.min(
110
- ...ordered.slice(1).map((value, index) => value - ordered[index])
111
- );
39
+ let minimumGap = Infinity;
40
+ for (let index = 1; index < ordered.length; index += 1) {
41
+ minimumGap = Math.min(minimumGap, ordered[index] - ordered[index - 1]);
42
+ }
112
43
  const domainSpan = Math.abs(domain[1] - domain[0]);
113
44
  if (!(minimumGap > 0) || !(domainSpan > 0)) {
114
45
  throw new Error("Temporal bar position requires an increasing time domain.");
115
46
  }
116
47
  const direction = Math.sign(range[1] - range[0]) || 1;
117
- const estimatedBandwidth = Math.abs(range[1] - range[0]) * minimumGap /
48
+ const bandwidthFraction = minimumGap / (domainSpan + minimumGap);
49
+ const directBandwidth = Math.abs(range[1] - range[0]) * minimumGap /
118
50
  (domainSpan + minimumGap);
119
- const resolvedRange = [
120
- range[0] + direction * estimatedBandwidth / 2,
121
- range[1] - direction * estimatedBandwidth / 2
51
+ const directRange = [
52
+ range[0] + direction * directBandwidth / 2,
53
+ range[1] - direction * directBandwidth / 2
122
54
  ];
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
- );
55
+ const stable = !Number.isFinite(directBandwidth) ||
56
+ !directRange.every(Number.isFinite);
57
+ const estimatedBandwidth = stable
58
+ ? Math.abs(
59
+ interpolateNumber(range[0], range[1], bandwidthFraction) - range[0]
60
+ )
61
+ : directBandwidth;
62
+ const resolvedRange = stable
63
+ ? [
64
+ interpolateNumber(range[0], range[1], bandwidthFraction / 2),
65
+ interpolateNumber(range[0], range[1], 1 - bandwidthFraction / 2)
66
+ ]
67
+ : directRange;
68
+ const resolvedSpan = resolvedRange[1] - resolvedRange[0];
69
+ const positions = ordered.map(value => {
70
+ const direct = resolvedRange[0] +
71
+ (value - domain[0]) / (domain[1] - domain[0]) * resolvedSpan;
72
+ return Number.isFinite(direct)
73
+ ? direct
74
+ : interpolateNumber(
75
+ resolvedRange[0],
76
+ resolvedRange[1],
77
+ inverseLerp(value, domain[0], domain[1])
78
+ );
79
+ });
80
+ let bandwidth = Infinity;
81
+ for (let index = 1; index < positions.length; index += 1) {
82
+ bandwidth = Math.min(
83
+ bandwidth,
84
+ Math.abs(positions[index] - positions[index - 1])
85
+ );
86
+ }
87
+ if (![estimatedBandwidth, bandwidth, ...resolvedRange].every(Number.isFinite)) {
88
+ throw new RangeError("Temporal bar position exceeds the finite numeric range.");
89
+ }
131
90
  return { bandwidth, range: resolvedRange };
132
91
  }
@@ -0,0 +1,34 @@
1
+ import { resolveHistogramBins } from "../../../grammar/histogram.js";
2
+
3
+ export function resolveBinnedPositionDomain({
4
+ valuesByConsumer,
5
+ channel,
6
+ scale,
7
+ id,
8
+ allValues
9
+ }) {
10
+ const binnedPositions = valuesByConsumer.filter(
11
+ ({ consumer }) =>
12
+ ["bar", "line"].includes(consumer.layer.mark?.type) &&
13
+ consumer.encoding.bin !== undefined
14
+ );
15
+ if (binnedPositions.length === 0) return undefined;
16
+ if (channel !== "x" || binnedPositions.length !== valuesByConsumer.length) {
17
+ throw new Error(
18
+ `Binned scale "${id}" cannot be shared with an unbinned consumer.`
19
+ );
20
+ }
21
+ const binDefinitions = new Set(
22
+ binnedPositions.map(({ consumer }) => JSON.stringify(consumer.encoding.bin))
23
+ );
24
+ if (binDefinitions.size !== 1) {
25
+ throw new Error(`Binned scale "${id}" requires one shared bin definition.`);
26
+ }
27
+ return resolveHistogramBins({
28
+ values: allValues,
29
+ bin: binnedPositions[0].consumer.encoding.bin,
30
+ domain: scale.domain,
31
+ nice: scale.nice ?? true,
32
+ zero: scale.zero ?? false
33
+ }).domain;
34
+ }
@@ -0,0 +1,54 @@
1
+ import { normalizeOffsetPadding } from "../../../grammar/bars/geometry.js";
2
+
3
+ function resolveParentSlot(consumer, resolvedScales, parentChannel) {
4
+ if (parentChannel === "x" && consumer.layer.encoding?.x?.bin !== undefined) {
5
+ return 1;
6
+ }
7
+ const parentScaleId = consumer.layer.encoding?.[parentChannel]?.scale;
8
+ const parentScale = resolvedScales[parentScaleId];
9
+ if (Number.isFinite(parentScale?.bandwidth) && parentScale.bandwidth > 0) {
10
+ return parentScale.bandwidth;
11
+ }
12
+ if (parentScale?.type === "point" && Number.isFinite(parentScale.step)) {
13
+ return Math.abs(parentScale.step);
14
+ }
15
+ return undefined;
16
+ }
17
+
18
+ export function resolveOffsetScalePolicy({
19
+ consumers,
20
+ resolvedScales,
21
+ markConfigs,
22
+ id,
23
+ channel
24
+ }) {
25
+ const parentChannel = channel === "xOffset" ? "x" : "y";
26
+ const slots = consumers.map(consumer =>
27
+ resolveParentSlot(consumer, resolvedScales, parentChannel)
28
+ );
29
+ if (
30
+ slots.some(value => !Number.isFinite(value) || value <= 0) ||
31
+ new Set(slots).size !== 1
32
+ ) {
33
+ throw new Error(
34
+ `${channel} scale "${id}" requires one shared resolved ${parentChannel} categorical slot.`
35
+ );
36
+ }
37
+ const paddings = consumers.map(consumer => normalizeOffsetPadding(
38
+ markConfigs[consumer.layer.id]?.[channel],
39
+ undefined,
40
+ channel
41
+ ));
42
+ const signatures = new Set(
43
+ paddings.map(padding => JSON.stringify(padding))
44
+ );
45
+ if (signatures.size !== 1) {
46
+ throw new Error(
47
+ `${channel} scale "${id}" requires one shared padding policy.`
48
+ );
49
+ }
50
+ return {
51
+ parentBandwidth: slots[0],
52
+ ...paddings[0]
53
+ };
54
+ }
@@ -3,6 +3,7 @@ import {
3
3
  resolveContinuousDomain,
4
4
  resolveTransformedDomain
5
5
  } from "../../../grammar/scales/index.js";
6
+ import { numericExtent } from "../../../grammar/numeric.js";
6
7
 
7
8
  export function resolveSeriesLayoutDomain({
8
9
  id,
@@ -25,9 +26,14 @@ export function resolveSeriesLayoutDomain({
25
26
  const compatibleDirect = directConsumers.every(({ consumer }) =>
26
27
  layoutConsumers.every(({ consumer: layoutConsumer }) => {
27
28
  const aggregate = layoutConsumer.encoding.aggregate;
28
- return aggregate !== "count" &&
29
- consumer.encoding.fieldType === "quantitative" &&
30
- consumer.encoding.field === layoutConsumer.encoding.field;
29
+ const directEncoding = consumer.encoding;
30
+ return directEncoding.fieldType === "quantitative" && (
31
+ Object.hasOwn(directEncoding, "datum") ||
32
+ (
33
+ aggregate !== "count" &&
34
+ directEncoding.field === layoutConsumer.encoding.field
35
+ )
36
+ );
31
37
  })
32
38
  );
33
39
  if (!compatibleDirect) {
@@ -41,9 +47,9 @@ export function resolveSeriesLayoutDomain({
41
47
  ...directConsumers.flatMap(item => item.values)
42
48
  ];
43
49
  if (layout === "center" && scale.domain !== "auto") {
44
- const minimum = Math.min(...values);
45
- const maximum = Math.max(...values);
46
- if (Math.min(...scale.domain) > minimum || Math.max(...scale.domain) < maximum) {
50
+ const [minimum, maximum] = numericExtent(values);
51
+ const [domainMinimum, domainMaximum] = numericExtent(scale.domain);
52
+ if (domainMinimum > minimum || domainMaximum < maximum) {
47
53
  throw new Error(
48
54
  `Center layout scale "${id}" explicit domain must contain every centered bound.`
49
55
  );
@@ -52,7 +58,7 @@ export function resolveSeriesLayoutDomain({
52
58
  if (
53
59
  ["group", "overlay"].includes(layout) &&
54
60
  scale.domain !== "auto" &&
55
- (Math.min(...scale.domain) > 0 || Math.max(...scale.domain) < 0)
61
+ (numericExtent(scale.domain)[0] > 0 || numericExtent(scale.domain)[1] < 0)
56
62
  ) {
57
63
  throw new Error(
58
64
  `Series layout scale "${id}" requires an explicit domain containing zero.`
@@ -27,11 +27,9 @@ import {
27
27
  validateTimeScaleType
28
28
  } from "../../grammar/scales/index.js";
29
29
  import { resolveArcAutoPositionRange } from "./policies/arc.js";
30
- import {
31
- resolveBinnedBarDomain,
32
- resolveOffsetScalePolicy,
33
- resolveTemporalBarBand
34
- } from "./policies/bar.js";
30
+ import { resolveTemporalBarBand } from "./policies/bar.js";
31
+ import { resolveOffsetScalePolicy } from "./policies/offset.js";
32
+ import { resolveBinnedPositionDomain } from "./policies/binnedPosition.js";
35
33
  import { resolveSeriesLayoutDomain } from "./policies/series.js";
36
34
  import { OFFSET_POSITION_CHANNELS } from "../../core/vocabulary.js";
37
35
 
@@ -223,7 +221,7 @@ export function resolveScaleMaterialization({
223
221
  }
224
222
  const binnedDomain = isDiscretizedColor
225
223
  ? undefined
226
- : resolveBinnedBarDomain({
224
+ : resolveBinnedPositionDomain({
227
225
  valuesByConsumer,
228
226
  channel,
229
227
  scale,
@@ -331,10 +329,9 @@ export function resolveScaleMaterialization({
331
329
  }
332
330
  if (scale.type === "time" && ["x", "y"].includes(channel)) {
333
331
  const temporalBarBand = resolveTemporalBarBand(
334
- consumers,
332
+ valuesByConsumer,
335
333
  domain,
336
- range,
337
- allValues
334
+ range
338
335
  );
339
336
  if (temporalBarBand !== undefined) {
340
337
  resolvedScale = {
@@ -1,7 +1,12 @@
1
1
  import { deriveArcSectors } from "../../../grammar/arcs.js";
2
+ import { resolvePolarPoint } from "../../../grammar/polarPaths.js";
2
3
  import { resolvePolarFrame } from "../../../grammar/polar.js";
3
4
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
4
- import { finalizeItems, uniqueFields } from "./common.js";
5
+ import {
6
+ concreteProperties,
7
+ finalizeItems,
8
+ uniqueFields
9
+ } from "./common.js";
5
10
 
6
11
  export function resolveArcItems(program, layer, dataset) {
7
12
  const thetaScale = program.resolvedScales[layer.encoding?.theta?.scale];
@@ -13,7 +18,8 @@ export function resolveArcItems(program, layer, dataset) {
13
18
  frame,
14
19
  innerRadiusRatio: program.markConfigs[layer.id]?.innerRadius ?? 0
15
20
  });
16
- const definitions = derived.sectors.map(sector => {
21
+ const graphic = program.graphicSpec.objects[layer.id];
22
+ const definitions = derived.sectors.map((sector, index) => {
17
23
  const members = sector.sourceIndices.map(index => dataset.values[index]);
18
24
  return {
19
25
  fields: uniqueFields(members),
@@ -22,6 +28,14 @@ export function resolveArcItems(program, layer, dataset) {
22
28
  ...(sector.radius === undefined ? {} : { radius: sector.radius }),
23
29
  ...(sector.color === undefined ? {} : { color: sector.color })
24
30
  },
31
+ properties: {
32
+ ...concreteProperties(graphic.items[index]?.properties),
33
+ ...resolvePolarPoint(
34
+ frame,
35
+ (sector.startTheta + sector.endTheta) / 2,
36
+ (sector.innerRadius + sector.outerRadius) / 2
37
+ )
38
+ },
25
39
  members
26
40
  };
27
41
  });
@@ -6,6 +6,7 @@ import {
6
6
  resolveBarOffsetChannel,
7
7
  resolveBarGrain
8
8
  } from "../../../grammar/bars/policy.js";
9
+ import { numericExtent } from "../../../grammar/numeric.js";
9
10
  import { layoutSeriesPartition } from "../../../grammar/seriesLayout.js";
10
11
  import {
11
12
  readNominalField,
@@ -215,14 +216,15 @@ function barStackDefinitions(layer, barGrain, definitions) {
215
216
  `Bar mark "${layer.id}" stack ${index} has no resolved semantic endpoints.`
216
217
  );
217
218
  }
219
+ const [minimum, maximum] = numericExtent(endpoints);
218
220
  const members = grouped.flatMap(definition => definition.members);
219
221
  return {
220
222
  key: itemKey(layer, "stack", index),
221
223
  fields: uniqueFields(members),
222
224
  channels: {
223
225
  ...sharedChannels(grouped),
224
- [measure]: Math.min(...endpoints),
225
- [secondary]: Math.max(...endpoints)
226
+ [measure]: minimum,
227
+ [secondary]: maximum
226
228
  },
227
229
  members,
228
230
  graphicIndices: entries.map(entry => entry.index)
@@ -1,5 +1,8 @@
1
1
  import { cloneAndFreeze, isPlainObject } from "../../../core/immutable.js";
2
- import { MARK_GRAPHIC_PROPERTIES } from "../../../grammar/markSelection.js";
2
+ import {
3
+ MARK_GRAPHIC_PROPERTIES,
4
+ selectMarkItemKeys
5
+ } from "../../../grammar/markSelection.js";
3
6
  import { unionConcreteGraphicBounds } from
4
7
  "../../../grammar/schemas/graphicBounds.js";
5
8
 
@@ -129,3 +132,20 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
129
132
  });
130
133
  }));
131
134
  }
135
+
136
+ export function reorderHighlightedItems(program, layer, definitions) {
137
+ let ordered = definitions;
138
+ for (const config of Object.values(
139
+ program.materializationConfigs.highlights ?? {}
140
+ )) {
141
+ if (config.target !== layer.id || config.bringToFront !== true) continue;
142
+ const selection = program.materializationConfigs.selections?.[config.selection];
143
+ if (selection?.target !== layer.id) continue;
144
+ const selected = new Set(selectMarkItemKeys(ordered, selection.selector));
145
+ ordered = [
146
+ ...ordered.filter(definition => !selected.has(definition.key)),
147
+ ...ordered.filter(definition => selected.has(definition.key))
148
+ ];
149
+ }
150
+ return ordered;
151
+ }
@@ -3,8 +3,12 @@ import {
3
3
  deriveAreaSeries,
4
4
  deriveDensityAreaSeries
5
5
  } from "../../../grammar/areaSeries.js";
6
- import { deriveLineSeries } from "../../../grammar/lineSeries.js";
6
+ import {
7
+ deriveLineSeries,
8
+ resolveLineBins
9
+ } from "../../../grammar/lineSeries.js";
7
10
  import { findUpstreamTransform } from "../../dataProvenance.js";
11
+ import { requireSemanticScale } from "../../../selectors/scales.js";
8
12
  import {
9
13
  channelMapFromRow,
10
14
  finalizeItems,
@@ -61,7 +65,20 @@ export function resolveLineItems(program, layer, dataset) {
61
65
  });
62
66
  return finalizeItems(program, layer, "row", definitions, "path");
63
67
  }
64
- const derived = deriveLineSeries(dataset.values, layer);
68
+ const x = layer.encoding?.x;
69
+ const derived = deriveLineSeries(
70
+ dataset.values,
71
+ layer,
72
+ x?.bin === undefined
73
+ ? undefined
74
+ : {
75
+ xBinBoundaries: resolveLineBins(
76
+ dataset.values,
77
+ layer,
78
+ requireSemanticScale(program, x.scale)
79
+ ).boundaries
80
+ }
81
+ );
65
82
  return finalizeItems(
66
83
  program,
67
84
  layer,
@@ -1,10 +1,10 @@
1
- import { selectMarkItemKeys } from "../../../grammar/markSelection.js";
2
1
  import {
3
2
  channelMapFromRow,
4
3
  concreteProperties,
5
4
  finalizeItems,
6
5
  itemKey,
7
- ownFields
6
+ ownFields,
7
+ reorderHighlightedItems
8
8
  } from "./common.js";
9
9
 
10
10
  export function resolvePointItems(program, layer, dataset) {
@@ -22,30 +22,18 @@ export function resolvePointItems(program, layer, dataset) {
22
22
  ) {
23
23
  throw new Error(`Point mark "${layer.id}" is incomplete for selection.`);
24
24
  }
25
- let definitions = dataset.values.map((row, index) => ({
25
+ const definitions = dataset.values.map((row, index) => ({
26
26
  key: itemKey(layer, "point", index),
27
27
  fields: ownFields(row),
28
28
  channels: channelMapFromRow(row, layer),
29
29
  properties: concreteProperties(graphic.items[index]?.properties),
30
30
  members: [row]
31
31
  }));
32
- for (const config of Object.values(
33
- program.materializationConfigs.highlights ?? {}
34
- )) {
35
- if (config.target !== layer.id || config.bringToFront !== true) continue;
36
- const selection = program.materializationConfigs.selections?.[config.selection];
37
- if (selection?.target !== layer.id) continue;
38
- const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
39
- definitions = [
40
- ...definitions.filter(definition => !selected.has(definition.key)),
41
- ...definitions.filter(definition => selected.has(definition.key))
42
- ];
43
- }
44
32
  return finalizeItems(
45
33
  program,
46
34
  layer,
47
35
  "point",
48
- definitions,
36
+ reorderHighlightedItems(program, layer, definitions),
49
37
  program.graphicSpec.objects[layer.id]?.type
50
38
  );
51
39
  }
@@ -1,10 +1,10 @@
1
- import { selectMarkItemKeys } from "../../../grammar/markSelection.js";
2
1
  import {
3
2
  channelMapFromRow,
4
3
  concreteProperties,
5
4
  finalizeItems,
6
5
  itemKey,
7
- ownFields
6
+ ownFields,
7
+ reorderHighlightedItems
8
8
  } from "./common.js";
9
9
 
10
10
  export function resolveTickItems(program, layer, dataset) {
@@ -15,25 +15,17 @@ export function resolveTickItems(program, layer, dataset) {
15
15
  if (!Array.isArray(graphic?.items) || !completePosition) {
16
16
  throw new Error(`Tick mark "${layer.id}" is incomplete for selection.`);
17
17
  }
18
- let definitions = dataset.values.map((row, index) => ({
19
- key: itemKey(layer, "tick", index),
20
- fields: ownFields(row),
21
- channels: channelMapFromRow(row, layer),
22
- properties: concreteProperties(graphic.items[index]?.properties),
23
- members: [row]
24
- }));
25
- for (const config of Object.values(
26
- program.materializationConfigs.highlights ?? {}
27
- )) {
28
- if (config.target !== layer.id || config.bringToFront !== true) continue;
29
- const selection = program.materializationConfigs.selections?.[config.selection];
30
- if (selection?.target !== layer.id) continue;
31
- const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
32
- definitions = [
33
- ...definitions.filter(definition => !selected.has(definition.key)),
34
- ...definitions.filter(definition => selected.has(definition.key))
35
- ];
36
- }
18
+ let definitions = reorderHighlightedItems(
19
+ program,
20
+ layer,
21
+ dataset.values.map((row, index) => ({
22
+ key: itemKey(layer, "tick", index),
23
+ fields: ownFields(row),
24
+ channels: channelMapFromRow(row, layer),
25
+ properties: concreteProperties(graphic.items[index]?.properties),
26
+ members: [row]
27
+ }))
28
+ );
37
29
  definitions = definitions.map((definition, index) => ({
38
30
  ...definition,
39
31
  properties: concreteProperties(graphic.items[index]?.properties)