ggaction 0.0.12 → 0.0.13

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 (281) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +2 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/action-cards.schema.json +1 -1
  6. package/knowledge/extension-authoring.md +2 -1
  7. package/knowledge/intent-taxonomy.json +1 -1
  8. package/knowledge/mcp-resources.json +1 -1
  9. package/knowledge/task-resolver.js +1 -1
  10. package/package.json +4 -2
  11. package/src/actions/basic.js +2 -0
  12. package/src/actions/boxPlots/materialize.js +2 -1
  13. package/src/actions/canvas/actions.js +3 -1
  14. package/src/actions/canvas/fitting.js +211 -0
  15. package/src/actions/canvas/index.js +2 -0
  16. package/src/actions/categoryOrder/index.js +3 -2
  17. package/src/actions/charts/area.js +47 -0
  18. package/src/actions/charts/bar.js +11 -5
  19. package/src/actions/charts/beeswarm.js +70 -0
  20. package/src/actions/charts/density.js +44 -0
  21. package/src/actions/charts/ecdf.js +183 -0
  22. package/src/actions/charts/endpoints.js +451 -0
  23. package/src/actions/charts/heatmap.js +2 -2
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +71 -0
  26. package/src/actions/charts/index.js +37 -0
  27. package/src/actions/charts/interval-regression.js +169 -0
  28. package/src/actions/charts/line.js +5 -5
  29. package/src/actions/charts/parallel.js +1 -1
  30. package/src/actions/charts/pie.js +44 -0
  31. package/src/actions/charts/polar.js +123 -0
  32. package/src/actions/charts/radar.js +299 -0
  33. package/src/actions/charts/radial.js +42 -0
  34. package/src/actions/charts/raincloud.js +422 -0
  35. package/src/actions/charts/rug-strip.js +279 -0
  36. package/src/actions/charts/scatter.js +9 -3
  37. package/src/actions/charts/shared.js +69 -10
  38. package/src/actions/composition/actions.js +118 -1
  39. package/src/actions/data/bin.js +29 -0
  40. package/src/actions/data/computed.js +24 -0
  41. package/src/actions/data/derived.js +66 -0
  42. package/src/actions/data/ecdf.js +38 -0
  43. package/src/actions/data/filter.js +212 -28
  44. package/src/actions/data/fold.js +24 -0
  45. package/src/actions/data/index.js +24 -0
  46. package/src/actions/data/interval.js +2 -0
  47. package/src/actions/data/intervalEdit.js +86 -4
  48. package/src/actions/data/regression.js +5 -2
  49. package/src/actions/data/stack.js +26 -0
  50. package/src/actions/data/summary.js +26 -0
  51. package/src/actions/data/timeUnit.js +1 -1
  52. package/src/actions/encodings/appearance.js +32 -13
  53. package/src/actions/encodings/barWidth.js +8 -0
  54. package/src/actions/encodings/basic.js +2 -0
  55. package/src/actions/encodings/color/continuous.js +19 -59
  56. package/src/actions/encodings/color/index.js +28 -22
  57. package/src/actions/encodings/color/layout.js +3 -97
  58. package/src/actions/encodings/color/policy.js +5 -8
  59. package/src/actions/encodings/density/resolve.js +37 -5
  60. package/src/actions/encodings/density.js +120 -33
  61. package/src/actions/encodings/horizon/resolve.js +8 -4
  62. package/src/actions/encodings/horizon.js +18 -8
  63. package/src/actions/encodings/index.js +2 -0
  64. package/src/actions/encodings/offset.js +10 -16
  65. package/src/actions/encodings/parallel.js +2 -8
  66. package/src/actions/encodings/position/apply.js +10 -4
  67. package/src/actions/encodings/position/index.js +43 -5
  68. package/src/actions/encodings/position/policies/arc.js +14 -0
  69. package/src/actions/encodings/position/policies/area.js +4 -42
  70. package/src/actions/encodings/position/policies/bar.js +48 -13
  71. package/src/actions/encodings/position/resolve.js +67 -22
  72. package/src/actions/encodings/ranged.js +123 -97
  73. package/src/actions/encodings/remove.js +37 -32
  74. package/src/actions/encodings/ruleAppearance.js +23 -9
  75. package/src/actions/encodings/seriesLayout.js +104 -0
  76. package/src/actions/encodings/shared.js +25 -23
  77. package/src/actions/encodings/strokeDash.js +9 -6
  78. package/src/actions/encodings/temporal.js +7 -0
  79. package/src/actions/encodings/text.js +30 -15
  80. package/src/actions/errorBands/create.js +18 -5
  81. package/src/actions/errorBands/edit.js +341 -21
  82. package/src/actions/errorBars/create.js +21 -10
  83. package/src/actions/errorBars/edit.js +355 -19
  84. package/src/actions/facets/actions.js +410 -12
  85. package/src/actions/facets/derive.js +57 -8
  86. package/src/actions/gradientPlots/components.js +33 -11
  87. package/src/actions/gradientPlots/materialize.js +5 -7
  88. package/src/actions/guides/applicability.js +20 -17
  89. package/src/actions/guides/axes/axes.js +4 -3
  90. package/src/actions/guides/axes/axis.js +18 -18
  91. package/src/actions/guides/axes/components.js +43 -0
  92. package/src/actions/guides/axes/edit.js +16 -51
  93. package/src/actions/guides/axes/labels.js +129 -19
  94. package/src/actions/guides/axes/lines.js +7 -5
  95. package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
  96. package/src/actions/guides/axes/parallel/policy.js +137 -0
  97. package/src/actions/guides/axes/parallel/resolve.js +44 -23
  98. package/src/actions/guides/axes/parallel.js +93 -217
  99. package/src/actions/guides/axes/policy.js +21 -67
  100. package/src/actions/guides/axes/tickGroups.js +14 -8
  101. package/src/actions/guides/axes/ticks.js +5 -4
  102. package/src/actions/guides/axes/titles.js +21 -8
  103. package/src/actions/guides/basic.js +2 -0
  104. package/src/actions/guides/facade.js +106 -0
  105. package/src/actions/guides/facadeAxes.js +200 -0
  106. package/src/actions/guides/facadeLegend.js +63 -0
  107. package/src/actions/guides/grids/grid.js +37 -33
  108. package/src/actions/guides/grids/resolve.js +10 -2
  109. package/src/actions/guides/guides.js +35 -28
  110. package/src/actions/guides/legends/categorical/actions.js +193 -249
  111. package/src/actions/guides/legends/categorical/components.js +4 -5
  112. package/src/actions/guides/legends/categorical/layout.js +36 -349
  113. package/src/actions/guides/legends/categorical/options.js +30 -14
  114. package/src/actions/guides/legends/categorical/recipes.js +14 -0
  115. package/src/actions/guides/legends/categorical/resolve.js +21 -10
  116. package/src/actions/guides/legends/categorical/symbols.js +2 -3
  117. package/src/actions/guides/legends/continuous/common.js +69 -14
  118. package/src/actions/guides/legends/continuous/gradient.js +111 -83
  119. package/src/actions/guides/legends/continuous/index.js +6 -2
  120. package/src/actions/guides/legends/continuous/interval.js +71 -92
  121. package/src/actions/guides/legends/continuous/opacity.js +105 -84
  122. package/src/actions/guides/legends/creation.js +27 -0
  123. package/src/actions/guides/legends/edit.js +198 -66
  124. package/src/actions/guides/legends/focused.js +2 -2
  125. package/src/actions/guides/legends/lane.js +98 -86
  126. package/src/actions/guides/legends/lifecycle.js +132 -0
  127. package/src/actions/guides/legends/remove.js +31 -76
  128. package/src/actions/guides/legends/size.js +149 -105
  129. package/src/actions/guides/legends/strokeWidth.js +106 -64
  130. package/src/actions/guides/legends/target.js +12 -0
  131. package/src/actions/guides/legends/transition.js +43 -0
  132. package/src/actions/guides/polar/axes/facade.js +77 -62
  133. package/src/actions/guides/polar/axes/labels.js +5 -4
  134. package/src/actions/guides/polar/axes/lines.js +2 -1
  135. package/src/actions/guides/polar/axes/shared.js +6 -0
  136. package/src/actions/guides/polar/axes/ticks.js +5 -4
  137. package/src/actions/guides/polar/axes/titles.js +5 -4
  138. package/src/actions/guides/polar/grids.js +7 -7
  139. package/src/actions/guides/reuse.js +44 -0
  140. package/src/actions/guides/tickValues.js +19 -21
  141. package/src/actions/index.js +2 -0
  142. package/src/actions/intervals/resolve.js +35 -15
  143. package/src/actions/marks/arc/actions.js +1 -1
  144. package/src/actions/marks/area/actions.js +18 -4
  145. package/src/actions/marks/area/materialize.js +5 -2
  146. package/src/actions/marks/bar/materialize.js +11 -7
  147. package/src/actions/marks/index.js +5 -0
  148. package/src/actions/marks/line/actions.js +43 -108
  149. package/src/actions/marks/line/materialize.js +34 -52
  150. package/src/actions/marks/point/edit.js +11 -15
  151. package/src/actions/marks/point/index.js +1 -0
  152. package/src/actions/marks/point/jitter.js +3 -0
  153. package/src/actions/marks/point/materialize.js +106 -2
  154. package/src/actions/marks/point/packing.js +68 -0
  155. package/src/actions/marks/references.js +87 -0
  156. package/src/actions/marks/remove.js +17 -4
  157. package/src/actions/marks/rule/actions.js +34 -3
  158. package/src/actions/marks/rule/appearance.js +26 -0
  159. package/src/actions/marks/shared.js +12 -12
  160. package/src/actions/marks/text/actions.js +224 -6
  161. package/src/actions/marks/text/layout.js +2 -2
  162. package/src/actions/primitives/createGraphics.js +3 -1
  163. package/src/actions/primitives/semantic.js +1 -1
  164. package/src/actions/primitives/semanticAction.js +39 -2
  165. package/src/actions/primitives/semanticValidation/guide.js +10 -0
  166. package/src/actions/primitives/semanticValidation/layer.js +18 -1
  167. package/src/actions/primitives/semanticValidation/scale.js +10 -1
  168. package/src/actions/regression/create.js +1 -1
  169. package/src/actions/regression/edit.js +19 -6
  170. package/src/actions/regression/resolve.js +1 -1
  171. package/src/actions/scales/consumers/common.js +20 -53
  172. package/src/actions/scales/consumers/families.js +27 -14
  173. package/src/actions/scales/consumers/index.js +23 -2
  174. package/src/actions/scales/consumers/seriesLayout.js +33 -2
  175. package/src/actions/scales/create.js +4 -4
  176. package/src/actions/scales/definitions.js +19 -3
  177. package/src/actions/scales/edit.js +46 -34
  178. package/src/actions/scales/editPolicy.js +21 -31
  179. package/src/actions/scales/materialize.js +10 -46
  180. package/src/actions/scales/patch.js +1 -1
  181. package/src/actions/scales/preview.js +39 -0
  182. package/src/actions/scales/quantitativeColor.js +9 -5
  183. package/src/actions/theme/actions.js +43 -0
  184. package/src/actions/theme/index.js +9 -0
  185. package/src/actions/theme/reconcile.js +882 -0
  186. package/src/actions/titles/actions.js +11 -10
  187. package/src/actions/titles/resolve.js +1 -40
  188. package/src/actions/violinPlots/create.js +86 -42
  189. package/src/actions/violinPlots/edit.js +191 -0
  190. package/src/actions/violinPlots/index.js +2 -0
  191. package/src/core/ChartProgram.js +7 -0
  192. package/src/core/action.js +22 -2
  193. package/src/core/compositionState.js +136 -8
  194. package/src/grammar/aggregate.js +66 -23
  195. package/src/grammar/arcs.js +58 -1
  196. package/src/grammar/areaEndpoints.js +46 -0
  197. package/src/grammar/areaSeries.js +87 -41
  198. package/src/grammar/bars/aggregate.js +33 -12
  199. package/src/grammar/bars/policy.js +9 -7
  200. package/src/grammar/bin.js +181 -0
  201. package/src/grammar/categoryOrder.js +34 -0
  202. package/src/grammar/computed.js +150 -0
  203. package/src/grammar/ecdf.js +198 -0
  204. package/src/grammar/facets/index.js +117 -6
  205. package/src/grammar/fold.js +123 -0
  206. package/src/grammar/horizon.js +6 -4
  207. package/src/grammar/interval.js +37 -20
  208. package/src/grammar/lineSeries.js +20 -63
  209. package/src/grammar/markFilter.js +22 -4
  210. package/src/grammar/markLabels.js +93 -0
  211. package/src/grammar/parallelAxisTitles.js +16 -0
  212. package/src/grammar/pathSeries.js +91 -0
  213. package/src/grammar/pointPacking.js +183 -0
  214. package/src/grammar/positionDatum.js +12 -0
  215. package/src/grammar/rects.js +9 -1
  216. package/src/grammar/regression/derive.js +4 -0
  217. package/src/grammar/regression/index.js +7 -2
  218. package/src/grammar/regression/models.js +22 -7
  219. package/src/grammar/regression/parameters.js +40 -13
  220. package/src/grammar/rotation.js +29 -0
  221. package/src/grammar/rules.js +7 -21
  222. package/src/grammar/scales/color.js +23 -1
  223. package/src/grammar/scales/colorConsumers.js +38 -0
  224. package/src/grammar/scales/definition.js +15 -1
  225. package/src/grammar/scales/discretized.js +11 -5
  226. package/src/grammar/scales/fields.js +38 -8
  227. package/src/grammar/scales/mapping.js +2 -0
  228. package/src/grammar/scales/radial.js +55 -0
  229. package/src/grammar/scales/types.js +1 -0
  230. package/src/grammar/schemas/semanticPath.js +17 -3
  231. package/src/grammar/stack.js +170 -0
  232. package/src/grammar/statistics/confidenceInterval.js +101 -0
  233. package/src/grammar/summary.js +183 -0
  234. package/src/grammar/text.js +22 -16
  235. package/src/grammar/timeUnit.js +21 -11
  236. package/src/grammar/transformTopology.js +6 -0
  237. package/src/grammar/transforms.js +36 -0
  238. package/src/grammar/valueFormat.js +112 -0
  239. package/src/grammar/window.js +13 -7
  240. package/src/layout/facets.js +41 -7
  241. package/src/layout/guideCollisions.js +12 -0
  242. package/src/layout/legend.js +2 -10
  243. package/src/layout/legendItems.js +102 -0
  244. package/src/layout/legendLane.js +100 -11
  245. package/src/materialization/bars/aggregate.js +38 -152
  246. package/src/materialization/bars/histogram.js +24 -0
  247. package/src/materialization/categorySlotOffset.js +40 -0
  248. package/src/materialization/compositionSnapshot.js +1 -1
  249. package/src/materialization/dependencies.js +57 -16
  250. package/src/materialization/encodings.js +1 -1
  251. package/src/materialization/facets.js +8 -1
  252. package/src/materialization/guides/layout.js +57 -0
  253. package/src/materialization/guides/resources.js +2 -2
  254. package/src/materialization/legends.js +10 -1
  255. package/src/materialization/marks/capabilities.js +20 -5
  256. package/src/materialization/marks/grouping.js +52 -0
  257. package/src/materialization/marks/index.js +58 -23
  258. package/src/materialization/marks/policies.js +2 -0
  259. package/src/materialization/rect.js +33 -33
  260. package/src/materialization/rowEncoding.js +13 -13
  261. package/src/materialization/scaleGuideDependencies.js +8 -5
  262. package/src/materialization/scales/map.js +1 -0
  263. package/src/materialization/scales/policies/arc.js +42 -0
  264. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  265. package/src/materialization/scales/policies/series.js +2 -1
  266. package/src/materialization/scales/resolve.js +46 -14
  267. package/src/materialization/selection/compatibility.js +11 -0
  268. package/src/materialization/selection/items/bar.js +12 -68
  269. package/src/materialization/selection/items/common.js +8 -2
  270. package/src/materialization/selection/items/path.js +3 -13
  271. package/src/materialization/selection/items/rect.js +7 -4
  272. package/src/materialization/text.js +64 -5
  273. package/src/mcp/adapter.js +2 -2
  274. package/src/selectors/datasets.js +13 -0
  275. package/src/selectors/layers.js +3 -2
  276. package/src/selectors/scales.js +6 -0
  277. package/src/theme/defaults.js +39 -0
  278. package/types/basic.d.ts +11 -2
  279. package/types/index.d.ts +126 -0
  280. package/types/program.d.ts +1320 -135
  281. package/src/materialization/bars/grouped.js +0 -144
@@ -11,24 +11,23 @@ function validateFacetSpec(compositionSpec) {
11
11
  }
12
12
  if (
13
13
  !Number.isInteger(compositionSpec.columns) ||
14
- compositionSpec.columns <= 0 ||
15
- compositionSpec.columns > compositionSpec.children.length
14
+ compositionSpec.columns <= 0
16
15
  ) {
17
16
  throw new RangeError(
18
- "Facet compositionSpec.columns must be a positive integer no larger than its children."
17
+ "Facet compositionSpec.columns must be a positive integer."
19
18
  );
20
19
  }
21
20
  if (!isPlainObject(compositionSpec.facet)) {
22
21
  throw new TypeError("Facet compositionSpec.facet must be a plain object.");
23
22
  }
24
- const facetKeys = ["data", "field", "values", "scales", "guides"];
23
+ const facetKeys = ["data", "field", "values", "grid", "repeat", "scales", "guides"];
25
24
  const unknownFacet = Object.keys(compositionSpec.facet).find(
26
25
  key => !facetKeys.includes(key)
27
26
  );
28
27
  if (unknownFacet !== undefined) {
29
28
  throw new Error(`Unknown compositionSpec.facet property "${unknownFacet}".`);
30
29
  }
31
- for (const property of ["data", "field"]) {
30
+ for (const property of ["data"]) {
32
31
  if (
33
32
  typeof compositionSpec.facet[property] !== "string" ||
34
33
  compositionSpec.facet[property].length === 0
@@ -36,6 +35,129 @@ function validateFacetSpec(compositionSpec) {
36
35
  throw new TypeError(`compositionSpec.facet.${property} must be a non-empty string.`);
37
36
  }
38
37
  }
38
+ const grid = compositionSpec.facet.grid;
39
+ const repeat = compositionSpec.facet.repeat;
40
+ if (grid !== undefined && repeat !== undefined) {
41
+ throw new Error("Facet compositionSpec cannot combine grid and repeat recipes.");
42
+ }
43
+ if (grid === undefined && repeat === undefined) {
44
+ if (compositionSpec.columns > compositionSpec.children.length) {
45
+ throw new RangeError(
46
+ "Facet compositionSpec.columns must be no larger than its children."
47
+ );
48
+ }
49
+ if (
50
+ typeof compositionSpec.facet.field !== "string" ||
51
+ compositionSpec.facet.field.length === 0
52
+ ) {
53
+ throw new TypeError("compositionSpec.facet.field must be a non-empty string.");
54
+ }
55
+ } else if (grid !== undefined) {
56
+ if (compositionSpec.columns !== grid.columns?.values?.length) {
57
+ throw new RangeError(
58
+ "Grid facet compositionSpec.columns must match its column domain."
59
+ );
60
+ }
61
+ if (compositionSpec.facet.field !== undefined) {
62
+ throw new Error("Grid facet compositionSpec does not use facet.field.");
63
+ }
64
+ if (!isPlainObject(grid)) {
65
+ throw new TypeError("compositionSpec.facet.grid must be a plain object.");
66
+ }
67
+ const gridKeys = ["rows", "columns", "combinations", "cells"];
68
+ const unknownGrid = Object.keys(grid).find(key => !gridKeys.includes(key));
69
+ if (unknownGrid !== undefined) {
70
+ throw new Error(`Unknown compositionSpec.facet.grid property "${unknownGrid}".`);
71
+ }
72
+ for (const role of ["rows", "columns"]) {
73
+ const value = grid[role];
74
+ if (!isPlainObject(value) || Object.keys(value).some(
75
+ key => !["field", "values"].includes(key)
76
+ )) {
77
+ throw new TypeError(
78
+ `compositionSpec.facet.grid.${role} requires field and values.`
79
+ );
80
+ }
81
+ if (typeof value.field !== "string" || value.field.length === 0) {
82
+ throw new TypeError(
83
+ `compositionSpec.facet.grid.${role}.field must be a non-empty string.`
84
+ );
85
+ }
86
+ if (
87
+ !Array.isArray(value.values) || value.values.length === 0 ||
88
+ value.values.some(item => !(
89
+ typeof item === "string" || typeof item === "boolean" ||
90
+ (typeof item === "number" && Number.isFinite(item))
91
+ )) || new Set(value.values).size !== value.values.length
92
+ ) {
93
+ throw new TypeError(
94
+ `compositionSpec.facet.grid.${role}.values must contain unique scalars.`
95
+ );
96
+ }
97
+ }
98
+ if (grid.rows.field === grid.columns.field) {
99
+ throw new Error("compositionSpec.facet.grid fields must be different.");
100
+ }
101
+ if (!["observed", "full"].includes(grid.combinations)) {
102
+ throw new Error(
103
+ 'compositionSpec.facet.grid.combinations must be "observed" or "full".'
104
+ );
105
+ }
106
+ if (!Array.isArray(grid.cells) || grid.cells.length !== compositionSpec.children.length) {
107
+ throw new TypeError(
108
+ "compositionSpec.facet.grid.cells must contain one descriptor per child."
109
+ );
110
+ }
111
+ const cellKeys = ["id", "row", "column", "rowValue", "columnValue", "empty"];
112
+ const coordinates = new Set();
113
+ for (const cell of grid.cells) {
114
+ if (!isPlainObject(cell) || Object.keys(cell).some(key => !cellKeys.includes(key))) {
115
+ throw new TypeError("compositionSpec.facet.grid cells must be canonical objects.");
116
+ }
117
+ if (!compositionSpec.children.includes(cell.id)) {
118
+ throw new Error(`Unknown grid facet child "${cell.id}".`);
119
+ }
120
+ if (!Number.isInteger(cell.row) || cell.row < 0 || cell.row >= grid.rows.values.length ||
121
+ !Number.isInteger(cell.column) || cell.column < 0 || cell.column >= grid.columns.values.length) {
122
+ throw new RangeError("Grid facet cell coordinates must be inside their domains.");
123
+ }
124
+ if (cell.rowValue !== grid.rows.values[cell.row] ||
125
+ cell.columnValue !== grid.columns.values[cell.column]) {
126
+ throw new Error("Grid facet cell values must match their row and column coordinates.");
127
+ }
128
+ if (typeof cell.empty !== "boolean") {
129
+ throw new TypeError("Grid facet cell empty must be a boolean.");
130
+ }
131
+ const key = `${cell.row}:${cell.column}`;
132
+ if (coordinates.has(key)) {
133
+ throw new Error("Grid facet cells must occupy unique coordinates.");
134
+ }
135
+ coordinates.add(key);
136
+ }
137
+ } else {
138
+ if (compositionSpec.facet.field !== undefined) {
139
+ throw new Error("Repeat compositionSpec does not use facet.field.");
140
+ }
141
+ if (compositionSpec.columns > compositionSpec.children.length) {
142
+ throw new RangeError(
143
+ "Repeat compositionSpec.columns must be no larger than its children."
144
+ );
145
+ }
146
+ if (!isPlainObject(repeat) || Object.keys(repeat).some(
147
+ key => !["target", "channel", "fields"].includes(key)
148
+ )) {
149
+ throw new TypeError(
150
+ "compositionSpec.facet.repeat requires target, channel, and fields."
151
+ );
152
+ }
153
+ if (typeof repeat.target !== "string" || repeat.target.length === 0 ||
154
+ !["x", "y"].includes(repeat.channel) ||
155
+ !Array.isArray(repeat.fields) || repeat.fields.length === 0 ||
156
+ repeat.fields.some(field => typeof field !== "string" || field.length === 0) ||
157
+ new Set(repeat.fields).size !== repeat.fields.length) {
158
+ throw new TypeError("compositionSpec.facet.repeat is not canonical.");
159
+ }
160
+ }
39
161
  const values = compositionSpec.facet.values;
40
162
  if (
41
163
  !Array.isArray(values) ||
@@ -45,12 +167,18 @@ function validateFacetSpec(compositionSpec) {
45
167
  typeof value === "boolean" ||
46
168
  (typeof value === "number" && Number.isFinite(value))
47
169
  )) ||
48
- new Set(values).size !== values.length
170
+ (grid === undefined && new Set(values).size !== values.length)
49
171
  ) {
50
172
  throw new TypeError(
51
173
  "compositionSpec.facet.values must contain one unique scalar per child."
52
174
  );
53
175
  }
176
+ if (repeat !== undefined && (
177
+ values.length !== repeat.fields.length ||
178
+ values.some((value, index) => value !== repeat.fields[index])
179
+ )) {
180
+ throw new Error("compositionSpec.facet.values must match repeat fields.");
181
+ }
54
182
  const scales = compositionSpec.facet.scales;
55
183
  if (
56
184
  !isPlainObject(scales) ||
@@ -80,13 +208,13 @@ function validateFacetSpec(compositionSpec) {
80
208
  function validateCompositionChildren(compositionSpec, childIds, facet) {
81
209
  if (
82
210
  !Array.isArray(compositionSpec.children) ||
83
- compositionSpec.children.length < (facet ? 1 : 2) ||
211
+ compositionSpec.children.length < 1 ||
84
212
  !compositionSpec.children.every(id => typeof id === "string" && id.length > 0)
85
213
  ) {
86
214
  throw new TypeError(
87
215
  facet
88
216
  ? "Facet compositionSpec.children requires at least one child ID."
89
- : "compositionSpec.children requires at least two child IDs."
217
+ : "compositionSpec.children requires at least one child ID."
90
218
  );
91
219
  }
92
220
  if (new Set(compositionSpec.children).size !== compositionSpec.children.length) {
@@ -7,6 +7,10 @@ import {
7
7
  stableFiniteMean,
8
8
  stableFiniteSum
9
9
  } from "./numeric.js";
10
+ import {
11
+ confidenceCriticalValue,
12
+ normalizeConfidenceInterval
13
+ } from "./statistics/confidenceInterval.js";
10
14
 
11
15
  export const SCALAR_AGGREGATE_OPERATIONS = Object.freeze([
12
16
  "count", "sum", "mean", "median", "min", "max",
@@ -17,7 +21,9 @@ export const SCALAR_AGGREGATE_OPERATIONS = Object.freeze([
17
21
 
18
22
  const SCALAR_OPERATIONS = new Set(SCALAR_AGGREGATE_OPERATIONS);
19
23
  const NOMINAL_OPERATIONS = ["count", "distinct", "valid", "missing"];
20
- const PARAMETERIZED_OPERATIONS = ["quantile", "first", "last"];
24
+ const PARAMETERIZED_OPERATIONS = [
25
+ "quantile", "first", "last", "ciLower", "ciUpper"
26
+ ];
21
27
 
22
28
  function nonEmptyString(value, label) {
23
29
  if (typeof value !== "string" || value.length === 0) {
@@ -83,6 +89,21 @@ export function validateAggregate(value) {
83
89
  order: value.order ?? "ascending"
84
90
  };
85
91
  }
92
+ if (value.op === "ciLower" || value.op === "ciUpper") {
93
+ const unknown = Object.keys(value).find(
94
+ key => !["op", "method", "level"].includes(key)
95
+ );
96
+ if (unknown !== undefined) {
97
+ throw new Error(`Unknown confidence aggregate property "${unknown}".`);
98
+ }
99
+ return {
100
+ op: value.op,
101
+ ...normalizeConfidenceInterval(value, {
102
+ defaultMethod: "normal",
103
+ label: "Aggregate confidence interval"
104
+ })
105
+ };
106
+ }
86
107
  throw new Error(`Unsupported aggregate "${value.op}".`);
87
108
  }
88
109
 
@@ -194,39 +215,44 @@ export function aggregateScalarValues(values, operation) {
194
215
  if (!Array.isArray(values)) {
195
216
  throw new TypeError("Aggregate values must be an array.");
196
217
  }
197
- validateAggregate(operation);
198
- if (!isScalarAggregate(operation)) {
218
+ const aggregate = validateAggregate(operation);
219
+ const scalarOperation = isScalarAggregate(aggregate)
220
+ ? aggregate
221
+ : ["ciLower", "ciUpper"].includes(aggregate.op)
222
+ ? aggregate.op
223
+ : undefined;
224
+ if (scalarOperation === undefined) {
199
225
  throw new Error("Scalar aggregate calculation requires a scalar operation.");
200
226
  }
201
227
 
202
- if (operation === "count") return values.length;
228
+ if (scalarOperation === "count") return values.length;
203
229
  const valid = values.filter(value => !isMissing(value));
204
- if (operation === "valid") return valid.length;
205
- if (operation === "missing") return values.length - valid.length;
206
- if (operation === "distinct") return new Set(valid).size;
230
+ if (scalarOperation === "valid") return valid.length;
231
+ if (scalarOperation === "missing") return values.length - valid.length;
232
+ if (scalarOperation === "distinct") return new Set(valid).size;
207
233
 
208
234
  const finite = finiteValues(values);
209
235
  if (finite.length === 0) return undefined;
210
- if (operation === "sum") {
236
+ if (scalarOperation === "sum") {
211
237
  return stableFiniteSum(finite, "Aggregate sum");
212
238
  }
213
- if (operation === "min") return numericExtent(finite)[0];
214
- if (operation === "max") return numericExtent(finite)[1];
215
- if (operation === "median") return quantile(finite, 0.5);
216
- if (operation === "q1") return quantile(finite, 0.25);
217
- if (operation === "q3") return quantile(finite, 0.75);
239
+ if (scalarOperation === "min") return numericExtent(finite)[0];
240
+ if (scalarOperation === "max") return numericExtent(finite)[1];
241
+ if (scalarOperation === "median") return quantile(finite, 0.5);
242
+ if (scalarOperation === "q1") return quantile(finite, 0.25);
243
+ if (scalarOperation === "q3") return quantile(finite, 0.75);
218
244
 
219
245
  const mean = stableFiniteMean(finite, "Aggregate mean");
220
- if (operation === "mean") return mean;
221
- if (["variance", "varianceP", "stdev", "stdevP"].includes(operation)) {
222
- const population = operation.endsWith("P");
246
+ if (scalarOperation === "mean") return mean;
247
+ if (["variance", "varianceP", "stdev", "stdevP"].includes(scalarOperation)) {
248
+ const population = scalarOperation.endsWith("P");
223
249
  if (!population && finite.length < 2) return undefined;
224
250
  const kind = population ? "population" : "sample";
225
251
  const deviation = stableFiniteDeviation(finite, {
226
252
  sample: !population,
227
253
  label: `Aggregate ${kind} deviation`
228
254
  });
229
- if (operation.startsWith("stdev")) return deviation.deviation;
255
+ if (scalarOperation.startsWith("stdev")) return deviation.deviation;
230
256
  return deviation.squared === undefined
231
257
  ? requireFiniteResult(
232
258
  deviation.deviation ** 2,
@@ -240,14 +266,25 @@ export function aggregateScalarValues(values, operation) {
240
266
  divisor: Math.sqrt(finite.length),
241
267
  label: "Aggregate standard error"
242
268
  }).deviation;
243
- if (operation === "stderr") return stderr;
244
- if (operation === "ciLower") {
245
- return requireFiniteResult(mean - 1.96 * stderr, "Aggregate lower CI");
269
+ if (scalarOperation === "stderr") return stderr;
270
+ const confidence = isScalarAggregate(aggregate)
271
+ ? normalizeConfidenceInterval({}, {
272
+ defaultMethod: "normal",
273
+ label: "Aggregate confidence interval"
274
+ })
275
+ : aggregate;
276
+ const critical = confidenceCriticalValue({
277
+ method: confidence.method,
278
+ level: confidence.level,
279
+ degreesOfFreedom: finite.length - 1
280
+ });
281
+ if (scalarOperation === "ciLower") {
282
+ return requireFiniteResult(mean - critical * stderr, "Aggregate lower CI");
246
283
  }
247
- if (operation === "ciUpper") {
248
- return requireFiniteResult(mean + 1.96 * stderr, "Aggregate upper CI");
284
+ if (scalarOperation === "ciUpper") {
285
+ return requireFiniteResult(mean + critical * stderr, "Aggregate upper CI");
249
286
  }
250
- throw new Error(`Unsupported scalar aggregate "${operation}".`);
287
+ throw new Error(`Unsupported scalar aggregate "${scalarOperation}".`);
251
288
  }
252
289
 
253
290
  export function aggregateRows(rows, field, operation) {
@@ -265,6 +302,9 @@ export function aggregateRows(rows, field, operation) {
265
302
  ? undefined
266
303
  : quantile(values, aggregate.probability);
267
304
  }
305
+ if (aggregate.op === "ciLower" || aggregate.op === "ciUpper") {
306
+ return aggregateScalarValues(rows.map(row => row[field]), aggregate);
307
+ }
268
308
  return aggregateOrderedRows(rows, field, aggregate);
269
309
  }
270
310
 
@@ -275,5 +315,8 @@ export function formatAggregateTitle(operation, field) {
275
315
  if (aggregate.op === "quantile") {
276
316
  return `quantile(${field}, ${aggregate.probability})`;
277
317
  }
318
+ if (aggregate.op === "ciLower" || aggregate.op === "ciUpper") {
319
+ return `${aggregate.op}(${field}, ${aggregate.method}, ${aggregate.level})`;
320
+ }
278
321
  return `${aggregate.op}(${field}, ${aggregate.orderBy} ${aggregate.order})`;
279
322
  }
@@ -241,6 +241,61 @@ function quantitativeSectors(rows, layer, thetaScale, frame, innerRadiusRatio) {
241
241
  }));
242
242
  }
243
243
 
244
+ export function deriveMeasuredArcValues(rows, layer) {
245
+ const theta = requireArcLayer(layer);
246
+ const radius = layer.encoding?.radius;
247
+ if (!["nominal", "ordinal"].includes(theta.fieldType) || theta.aggregate !== undefined ||
248
+ !["count", "sum"].includes(radius?.aggregate) || radius.fieldType !== "quantitative") {
249
+ throw new Error("Measured Arc requires equal-angle categorical theta and count or sum radius.");
250
+ }
251
+ if (radius.aggregate === "count" && Object.hasOwn(radius, "field")) {
252
+ throw new Error("Measured count radius does not accept a field.");
253
+ }
254
+ const categories = readNominalField(rows, theta.field);
255
+ const measures = radius.aggregate === "count" ? rows.map(() => 1)
256
+ : readQuantitativeField(rows, radius.field);
257
+ if (measures.some(value => value < 0)) {
258
+ throw new RangeError("Measured Arc radius inputs must be non-negative.");
259
+ }
260
+ const colors = colorValues(rows, layer.encoding?.color);
261
+ const groups = new Map();
262
+ for (const [index, category] of categories.entries()) {
263
+ const group = groups.get(category) ?? { key: category, sourceIndices: [], values: [], colors: [] };
264
+ group.sourceIndices.push(index);
265
+ group.values.push(measures[index]);
266
+ group.colors.push(colors[index]);
267
+ groups.set(category, group);
268
+ }
269
+ const result = [...groups.values()].map(group => {
270
+ const distinctColors = [...new Set(group.colors)];
271
+ if (distinctColors.length > 1) {
272
+ throw new Error(`Measured Arc category "${group.key}" must resolve to one color value.`);
273
+ }
274
+ return { key: group.key, theta: group.key, count: group.sourceIndices.length,
275
+ radius: stableFiniteSum(group.values, "Measured Arc radius aggregate"),
276
+ color: distinctColors[0], sourceIndices: group.sourceIndices };
277
+ });
278
+ if (!result.some(group => group.radius > 0)) {
279
+ throw new Error("Measured Arc radius requires at least one positive category aggregate.");
280
+ }
281
+ return cloneAndFreeze(result);
282
+ }
283
+
284
+ function measuredRadialSectors(rows, layer, thetaScale, radiusScale) {
285
+ const items = deriveMeasuredArcValues(rows, layer);
286
+ const centers = mapOrdinalPositionValues(items.map(item => item.theta), thetaScale);
287
+ const radii = mapContinuousScaleValues(items.map(item => item.radius), radiusScale);
288
+ const halfBand = (Math.sign(thetaScale.step) || 1) * thetaScale.bandwidth / 2;
289
+ const byCategory = new Map(items.map((item, index) => [item.theta, {
290
+ ...item, startTheta: centers[index] - halfBand, endTheta: centers[index] + halfBand,
291
+ innerRadius: radiusScale.range[0], outerRadius: radii[index]
292
+ }]));
293
+ return thetaScale.domain.flatMap(category => {
294
+ const item = byCategory.get(category);
295
+ return item === undefined || item.radius === 0 ? [] : [item];
296
+ });
297
+ }
298
+
244
299
  function radialSectors(rows, layer, thetaScale, radiusScale) {
245
300
  const theta = layer.encoding.theta;
246
301
  const radius = layer.encoding?.radius;
@@ -303,7 +358,9 @@ export function deriveArcSectors(rows, layer, {
303
358
  ) {
304
359
  throw new RangeError("Arc innerRadius must be from 0 (inclusive) to 1 (exclusive).");
305
360
  }
306
- const sectors = theta.fieldType === "quantitative"
361
+ const sectors = radiusScale?.radialMapping !== undefined
362
+ ? measuredRadialSectors(rows, layer, requireBandScale(thetaScale, `Arc mark "${layer.id}" theta`), radiusScale)
363
+ : theta.fieldType === "quantitative"
307
364
  ? quantitativeSectors(
308
365
  rows,
309
366
  layer,
@@ -0,0 +1,46 @@
1
+ import { validateKeys, validateNonEmptyString } from "../core/validation.js";
2
+ import { isPlainObject } from "../core/immutable.js";
3
+ import { readQuantitativeField } from "./scales/fields.js";
4
+
5
+ export function validateAreaMissing(value = "error") {
6
+ if (!["error", "break"].includes(value)) {
7
+ throw new Error('Area missing must be "error" or "break".');
8
+ }
9
+ return value;
10
+ }
11
+
12
+ export function normalizeAreaBound(bound) {
13
+ if (typeof bound === "string") return { field: validateNonEmptyString(bound, "Area endpoint field") };
14
+ if (!isPlainObject(bound)) throw new TypeError("Area bound must be a field or datum object.");
15
+ validateKeys(bound, ["datum"], "Area bound");
16
+ if (!Number.isFinite(bound.datum)) throw new TypeError("Area datum must be finite.");
17
+ return { datum: bound.datum };
18
+ }
19
+
20
+ export function readAreaEndpoint(rows, encoding, missing = "error") {
21
+ if (Object.hasOwn(encoding, "datum")) {
22
+ if (Object.hasOwn(encoding, "field") || !Number.isFinite(encoding.datum) || encoding.fieldType !== "quantitative") {
23
+ throw new TypeError("Area endpoint requires exactly one finite quantitative datum or field.");
24
+ }
25
+ return rows.map(() => encoding.datum);
26
+ }
27
+ if (missing !== "break") return readQuantitativeField(rows, encoding.field);
28
+ validateNonEmptyString(encoding.field, "Area endpoint field");
29
+ if (rows.length > 0 && !rows.some(row => Object.hasOwn(row, encoding.field))) {
30
+ throw new Error(`Area endpoint field "${encoding.field}" does not exist.`);
31
+ }
32
+ return rows.map((row, index) => {
33
+ const value = row[encoding.field];
34
+ if (value == null) return null;
35
+ if (!Number.isFinite(value)) {
36
+ throw new TypeError(`Field "${encoding.field}" must contain a finite number at row ${index}.`);
37
+ }
38
+ return value;
39
+ });
40
+ }
41
+
42
+ export function validateAreaEndpointPair(primary, secondary) {
43
+ if (primary?.datum !== undefined && secondary?.datum !== undefined) {
44
+ throw new Error("Area range requires at least one field endpoint.");
45
+ }
46
+ }
@@ -1,15 +1,11 @@
1
1
  import { cloneAndFreeze } from "../core/immutable.js";
2
- import {
3
- readNominalField,
4
- readQuantitativeField,
5
- readTemporalField
6
- } from "./scales/index.js";
7
- import {
8
- layoutSeriesPartition,
9
- validateColorLayout
10
- } from "./seriesLayout.js";
2
+ import { readNominalField, readQuantitativeField, readTemporalField } from "./scales/index.js";
3
+ import { layoutSeriesPartition, validateColorLayout } from "./seriesLayout.js";
11
4
  import { stableOrderPathValues } from "./pathOrder.js";
12
5
  import { deriveCategoricalDensitySeries } from "./categoricalDensity.js";
6
+ import { validatePathSeriesAppearance } from "./pathSeries.js";
7
+ import { readAreaEndpoint, validateAreaEndpointPair } from "./areaEndpoints.js";
8
+ import { validateGeneratedItemLimit } from "../core/validation.js";
13
9
 
14
10
  function indexSegments(segments) {
15
11
  const indexed = [];
@@ -21,7 +17,7 @@ export function deriveAreaSeries(rows, layer) {
21
17
  if (layer?.mark?.type !== "area") {
22
18
  throw new Error("Area series derivation requires a semantic area mark.");
23
19
  }
24
- const { x, y, x2, y2, group, color } = layer.encoding ?? {};
20
+ const { x, y, x2, y2 } = layer.encoding ?? {};
25
21
  const vertical =
26
22
  ["quantitative", "temporal"].includes(x?.fieldType) &&
27
23
  y?.fieldType === "quantitative" &&
@@ -37,26 +33,19 @@ export function deriveAreaSeries(rows, layer) {
37
33
  `Area mark "${layer.id}" requires exactly one quantitative x/x2 or y/y2 range and one quantitative or temporal independent position.`
38
34
  );
39
35
  }
40
- if (group !== undefined && group.fieldType !== "nominal") {
41
- throw new Error(`Area group encoding on mark "${layer.id}" must be nominal.`);
42
- }
36
+ const grouping = validatePathSeriesAppearance(rows, layer);
43
37
  const orientation = vertical ? "vertical" : "horizontal";
44
38
  const independent = vertical ? x : y;
45
39
  const independentValues = independent.fieldType === "temporal"
46
- ? readTemporalField(rows, independent.field)
40
+ ? readTemporalField(rows, independent.field, independent.temporalUnit)
47
41
  : readQuantitativeField(rows, independent.field);
48
- const lower = vertical
49
- ? readQuantitativeField(rows, y.field)
50
- : readQuantitativeField(rows, x.field);
51
- const upper = vertical
52
- ? readQuantitativeField(rows, y2.field)
53
- : readQuantitativeField(rows, x2.field);
54
- const groupValues = group === undefined
55
- ? rows.map(() => undefined)
56
- : readNominalField(rows, group.field);
57
- const colorValues = color === undefined
58
- ? rows.map(() => undefined)
59
- : readNominalField(rows, color.field);
42
+ if (Object.hasOwn(independent, "datum")) throw new Error("Area independent position requires a field.");
43
+ const primary = vertical ? y : x;
44
+ const secondary = vertical ? y2 : x2;
45
+ validateAreaEndpointPair(primary, secondary);
46
+ const missing = layer.mark.missing ?? "error";
47
+ const lower = readAreaEndpoint(rows, primary, missing);
48
+ const upper = readAreaEndpoint(rows, secondary, missing);
60
49
  const pathOrder = layer.encoding?.pathOrder;
61
50
  const orderValues = pathOrder === undefined
62
51
  ? undefined
@@ -64,32 +53,24 @@ export function deriveAreaSeries(rows, layer) {
64
53
  const groups = new Map();
65
54
 
66
55
  for (let index = 0; index < rows.length; index += 1) {
67
- const key = groupValues[index];
56
+ const tuple = grouping.map(field => rows[index][field]);
57
+ const key = JSON.stringify(tuple);
68
58
  const series = groups.get(key) ?? {
69
- key: {
70
- ...(group === undefined ? {} : { [group.field]: key }),
71
- ...(color === undefined ? {} : { [color.field]: colorValues[index] })
72
- },
59
+ key: Object.fromEntries(grouping.map((field, i) => [field, tuple[i]])),
73
60
  values: []
74
61
  };
75
- if (
76
- color !== undefined &&
77
- !Object.is(series.key[color.field], colorValues[index])
78
- ) {
79
- throw new Error(
80
- `Area series "${String(key)}" must have one color value.`
81
- );
82
- }
83
62
  series.values.push(vertical
84
63
  ? {
85
64
  x: independentValues[index],
86
65
  y: lower[index],
87
66
  y2: upper[index],
67
+ ...(missing === "break" ? { sourceIndex: index } : {}),
88
68
  ...(orderValues === undefined ? {} : { pathOrder: orderValues[index] })
89
69
  }
90
70
  : {
91
71
  x: lower[index],
92
72
  x2: upper[index],
73
+ ...(missing === "break" ? { sourceIndex: index } : {}),
93
74
  y: independentValues[index],
94
75
  ...(orderValues === undefined ? {} : { pathOrder: orderValues[index] })
95
76
  });
@@ -98,7 +79,8 @@ export function deriveAreaSeries(rows, layer) {
98
79
  if (groups.size === 0) {
99
80
  throw new Error(`Area mark "${layer.id}" has no values.`);
100
81
  }
101
- const series = [...groups.values()].map(item => {
82
+ layoutRawAreaGroups(groups, { layer, vertical, primary, secondary });
83
+ const series = [...groups.values()].flatMap(item => {
102
84
  const key = vertical ? "x" : "y";
103
85
  const values = pathOrder === undefined
104
86
  ? item.values.sort((left, right) => left[key] - right[key])
@@ -107,6 +89,22 @@ export function deriveAreaSeries(rows, layer) {
107
89
  item.values.map(value => value.pathOrder),
108
90
  pathOrder.order
109
91
  );
92
+ if (missing === "break") {
93
+ const segments = [];
94
+ let segment = [];
95
+ const emit = () => {
96
+ if (segment.length >= 2) segments.push({ key: item.key,
97
+ sourceIndices: segment.map(value => value.sourceIndex),
98
+ values: segment.map(({ sourceIndex, ...value }) => value) });
99
+ segment = [];
100
+ };
101
+ for (const value of values) {
102
+ if ((vertical ? value.y == null || value.y2 == null : value.x == null || value.x2 == null)) emit();
103
+ else segment.push(value);
104
+ }
105
+ emit();
106
+ return segments;
107
+ }
110
108
  if (values.length < 2) {
111
109
  throw new Error(
112
110
  `Area series on mark "${layer.id}" requires at least two points.`
@@ -114,6 +112,7 @@ export function deriveAreaSeries(rows, layer) {
114
112
  }
115
113
  return { key: item.key, values };
116
114
  });
115
+ if (series.length === 0) throw new Error(`Area mark "${layer.id}" has no valid segment with at least two points.`);
117
116
  return cloneAndFreeze({
118
117
  orientation,
119
118
  xValues: series.flatMap(item => item.values.flatMap(value =>
@@ -151,7 +150,7 @@ export function deriveCenteredAreaSeries(rows, layer) {
151
150
  );
152
151
  }
153
152
  const xValues = x.fieldType === "temporal"
154
- ? readTemporalField(rows, x.field)
153
+ ? readTemporalField(rows, x.field, x.temporalUnit)
155
154
  : readQuantitativeField(rows, x.field);
156
155
  const yValues = readQuantitativeField(rows, y.field);
157
156
  const groups = readNominalField(rows, group.field);
@@ -359,3 +358,50 @@ export function layoutDensityAreaSeries(derived, layout = "overlay") {
359
358
  }))
360
359
  });
361
360
  }
361
+
362
+ export function layoutRawAreaGroups(groups, { layer, vertical, primary, secondary }) {
363
+ const mode = layer.layout?.mode ?? "overlay";
364
+ validateColorLayout(mode);
365
+ if (mode === "overlay") return;
366
+ if (mode === "group" || (mode === "center" && !vertical)) {
367
+ throw new Error(`Area layout "${mode}" is incompatible with this orientation.`);
368
+ }
369
+ const primaryDatum = Object.hasOwn(primary, "datum");
370
+ const secondaryDatum = Object.hasOwn(secondary, "datum");
371
+ if (primaryDatum === secondaryDatum || (primaryDatum ? primary.datum : secondary.datum) !== 0) {
372
+ throw new Error("Stacked area requires one value field and one zero datum endpoint.");
373
+ }
374
+ const position = vertical ? "x" : "y";
375
+ const lower = vertical ? "y" : "x", upper = `${lower}2`;
376
+ const measure = primaryDatum ? upper : lower;
377
+ const series = [...groups.values()];
378
+ const indices = series.map(item => {
379
+ const index = new Map();
380
+ for (const value of item.values) {
381
+ if (index.has(value[position])) throw new Error("Area layout requires unique group/position rows.");
382
+ index.set(value[position], value);
383
+ }
384
+ return index;
385
+ });
386
+ const positions = [...new Set(series.flatMap(item => item.values.map(value => value[position])))].sort((a, b) => a - b);
387
+ validateGeneratedItemLimit(positions.length * series.length, "Area layout cell count");
388
+ if (indices.some(index => index.size !== positions.length || positions.some(value => !index.has(value)))) {
389
+ throw new Error("Area layout requires an aligned row for every group and position.");
390
+ }
391
+ for (const position of positions) {
392
+ const points = indices.map(index => index.get(position));
393
+ if (points.some(point => point[measure] == null)) {
394
+ for (const point of points) { point[lower] = null; point[upper] = null; }
395
+ continue;
396
+ }
397
+ const segments = layoutSeriesPartition(points.map(point => point[measure]), mode);
398
+ const byIndex = new Map(segments.map(segment => [segment.index, segment]));
399
+ let endpoint = mode === "center" ? segments[0]?.start ?? 0 : 0;
400
+ points.forEach((point, index) => {
401
+ const segment = byIndex.get(index);
402
+ point[lower] = segment?.start ?? (mode === "diverging" ? 0 : endpoint);
403
+ point[upper] = segment?.end ?? (mode === "diverging" ? 0 : endpoint);
404
+ if (segment !== undefined && mode !== "diverging") endpoint = segment.end;
405
+ });
406
+ }
407
+ }