ggaction 0.0.11 → 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 (289) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +3 -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 +33 -27
  65. package/src/actions/encodings/parallel.js +2 -8
  66. package/src/actions/encodings/position/apply.js +40 -4
  67. package/src/actions/encodings/position/index.js +43 -5
  68. package/src/actions/encodings/position/policies/arc.js +37 -2
  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 +79 -24
  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 +153 -11
  83. package/src/actions/errorBars/edit.js +365 -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 +138 -14
  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 +60 -4
  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/core/vocabulary.js +2 -2
  195. package/src/grammar/aggregate.js +66 -23
  196. package/src/grammar/arcs.js +213 -65
  197. package/src/grammar/areaEndpoints.js +46 -0
  198. package/src/grammar/areaSeries.js +87 -41
  199. package/src/grammar/bars/aggregate.js +33 -12
  200. package/src/grammar/bars/policy.js +9 -7
  201. package/src/grammar/bin.js +181 -0
  202. package/src/grammar/categoryOrder.js +34 -0
  203. package/src/grammar/computed.js +150 -0
  204. package/src/grammar/ecdf.js +198 -0
  205. package/src/grammar/facets/index.js +117 -6
  206. package/src/grammar/fold.js +123 -0
  207. package/src/grammar/horizon.js +6 -4
  208. package/src/grammar/interval.js +37 -20
  209. package/src/grammar/lineSeries.js +20 -63
  210. package/src/grammar/markFilter.js +22 -4
  211. package/src/grammar/markLabels.js +93 -0
  212. package/src/grammar/parallelAxisTitles.js +16 -0
  213. package/src/grammar/pathSeries.js +91 -0
  214. package/src/grammar/pointPacking.js +183 -0
  215. package/src/grammar/positionCompatibility.js +1 -1
  216. package/src/grammar/positionDatum.js +12 -0
  217. package/src/grammar/rects.js +9 -1
  218. package/src/grammar/regression/derive.js +4 -0
  219. package/src/grammar/regression/index.js +7 -2
  220. package/src/grammar/regression/models.js +22 -7
  221. package/src/grammar/regression/parameters.js +40 -13
  222. package/src/grammar/rotation.js +29 -0
  223. package/src/grammar/rules.js +9 -21
  224. package/src/grammar/scales/color.js +23 -1
  225. package/src/grammar/scales/colorConsumers.js +38 -0
  226. package/src/grammar/scales/definition.js +15 -1
  227. package/src/grammar/scales/discretized.js +11 -5
  228. package/src/grammar/scales/fields.js +38 -8
  229. package/src/grammar/scales/mapping.js +2 -0
  230. package/src/grammar/scales/ordinal.js +23 -0
  231. package/src/grammar/scales/radial.js +55 -0
  232. package/src/grammar/scales/types.js +1 -0
  233. package/src/grammar/schemas/semanticPath.js +17 -3
  234. package/src/grammar/stack.js +170 -0
  235. package/src/grammar/statistics/confidenceInterval.js +101 -0
  236. package/src/grammar/summary.js +183 -0
  237. package/src/grammar/text.js +22 -16
  238. package/src/grammar/timeUnit.js +21 -11
  239. package/src/grammar/transformTopology.js +6 -0
  240. package/src/grammar/transforms.js +36 -0
  241. package/src/grammar/valueFormat.js +112 -0
  242. package/src/grammar/window.js +13 -7
  243. package/src/layout/facets.js +86 -23
  244. package/src/layout/guideCollisions.js +12 -0
  245. package/src/layout/legend.js +2 -10
  246. package/src/layout/legendItems.js +102 -0
  247. package/src/layout/legendLane.js +100 -11
  248. package/src/materialization/bars/aggregate.js +38 -152
  249. package/src/materialization/bars/histogram.js +24 -0
  250. package/src/materialization/categorySlotOffset.js +40 -0
  251. package/src/materialization/compositionSnapshot.js +1 -1
  252. package/src/materialization/dependencies.js +57 -16
  253. package/src/materialization/encodings.js +1 -1
  254. package/src/materialization/facetGuides/legacyCategorical.js +7 -1
  255. package/src/materialization/facetGuides/placement.js +16 -1
  256. package/src/materialization/facetGuides/preparation.js +26 -1
  257. package/src/materialization/facets.js +11 -2
  258. package/src/materialization/guides/layout.js +57 -0
  259. package/src/materialization/guides/resources.js +2 -2
  260. package/src/materialization/legends.js +10 -1
  261. package/src/materialization/marks/capabilities.js +30 -7
  262. package/src/materialization/marks/grouping.js +52 -0
  263. package/src/materialization/marks/index.js +63 -25
  264. package/src/materialization/marks/policies.js +2 -0
  265. package/src/materialization/rect.js +33 -33
  266. package/src/materialization/rowEncoding.js +70 -14
  267. package/src/materialization/scaleGuideDependencies.js +8 -5
  268. package/src/materialization/scales/map.js +1 -0
  269. package/src/materialization/scales/policies/arc.js +42 -0
  270. package/src/materialization/scales/policies/bar.js +0 -43
  271. package/src/materialization/scales/policies/binnedPosition.js +7 -4
  272. package/src/materialization/scales/policies/offset.js +54 -0
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +48 -18
  275. package/src/materialization/selection/compatibility.js +11 -0
  276. package/src/materialization/selection/items/bar.js +12 -68
  277. package/src/materialization/selection/items/common.js +8 -2
  278. package/src/materialization/selection/items/path.js +3 -13
  279. package/src/materialization/selection/items/rect.js +7 -4
  280. package/src/materialization/text.js +64 -5
  281. package/src/mcp/adapter.js +2 -2
  282. package/src/selectors/datasets.js +13 -0
  283. package/src/selectors/layers.js +3 -2
  284. package/src/selectors/scales.js +6 -0
  285. package/src/theme/defaults.js +39 -0
  286. package/types/basic.d.ts +11 -2
  287. package/types/index.d.ts +126 -0
  288. package/types/program.d.ts +1327 -124
  289. 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) {
@@ -86,14 +86,14 @@ export const POSITION_CHANNEL_DEFINITIONS = Object.freeze({
86
86
  role: "offset",
87
87
  scaleChannel: "xOffset",
88
88
  coordinate: Object.freeze({ id: "main", type: "cartesian" }),
89
- markTypes: Object.freeze(["bar"])
89
+ markTypes: Object.freeze(["bar", "point", "rule"])
90
90
  }),
91
91
  yOffset: Object.freeze({
92
92
  family: "cartesian",
93
93
  role: "offset",
94
94
  scaleChannel: "yOffset",
95
95
  coordinate: Object.freeze({ id: "main", type: "cartesian" }),
96
- markTypes: Object.freeze(["bar"])
96
+ markTypes: Object.freeze(["bar", "point", "rule"])
97
97
  }),
98
98
  theta: Object.freeze({
99
99
  family: "polar",
@@ -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
  }