ggaction 0.0.18 → 0.0.20

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 (164) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +6 -2
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +1 -1
  7. package/src/actions/canvas/fitting.js +6 -2
  8. package/src/actions/charts/heatmap.js +4 -3
  9. package/src/actions/data/derived.js +11 -1
  10. package/src/actions/data/edit.js +2 -0
  11. package/src/actions/data/index.js +4 -0
  12. package/src/actions/data/regression.js +24 -16
  13. package/src/actions/data/regressionStatistics.js +73 -0
  14. package/src/actions/data/shared.js +5 -2
  15. package/src/actions/distributions/revision.js +24 -18
  16. package/src/actions/encodings/color/continuous.js +7 -1
  17. package/src/actions/encodings/remove.js +11 -6
  18. package/src/actions/encodings/shared.js +26 -24
  19. package/src/actions/facets/actions.js +18 -7
  20. package/src/actions/facets/guides.js +2 -1
  21. package/src/actions/guides/axes/axes.js +30 -11
  22. package/src/actions/guides/axes/axis.js +58 -8
  23. package/src/actions/guides/axes/components.js +13 -13
  24. package/src/actions/guides/axes/edit.js +36 -24
  25. package/src/actions/guides/axes/labels.js +37 -27
  26. package/src/actions/guides/axes/lines.js +22 -20
  27. package/src/actions/guides/axes/remove.js +10 -6
  28. package/src/actions/guides/axes/tickGroups.js +8 -7
  29. package/src/actions/guides/axes/ticks.js +31 -22
  30. package/src/actions/guides/axes/titles.js +53 -60
  31. package/src/actions/guides/facadeAxes.js +33 -24
  32. package/src/actions/guides/facadeLegend.js +3 -3
  33. package/src/actions/guides/grids/grid.js +1 -1
  34. package/src/actions/guides/grids/resolve.js +48 -51
  35. package/src/actions/guides/legends/blocks.js +43 -19
  36. package/src/actions/guides/legends/categorical/actions.js +32 -9
  37. package/src/actions/guides/legends/categorical/components.js +3 -10
  38. package/src/actions/guides/legends/categorical/index.js +2 -0
  39. package/src/actions/guides/legends/categorical/layout.js +9 -13
  40. package/src/actions/guides/legends/categorical/options.js +8 -2
  41. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  42. package/src/actions/guides/legends/categorical/resolve.js +9 -2
  43. package/src/actions/guides/legends/categorical/symbols.js +22 -8
  44. package/src/actions/guides/legends/colorSize.js +110 -0
  45. package/src/actions/guides/legends/combine.js +37 -0
  46. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -1
  47. package/src/actions/guides/legends/continuous/common.js +1 -1
  48. package/src/actions/guides/legends/continuous/gradient.js +27 -13
  49. package/src/actions/guides/legends/continuous/opacity.js +16 -11
  50. package/src/actions/guides/legends/creation.js +2 -0
  51. package/src/actions/guides/legends/edit.js +1 -1
  52. package/src/actions/guides/legends/focused.js +6 -5
  53. package/src/actions/guides/legends/lane.js +1 -1
  54. package/src/actions/guides/legends/lifecycle.js +2 -1
  55. package/src/actions/guides/legends/remove.js +6 -3
  56. package/src/actions/guides/legends/size.js +16 -6
  57. package/src/actions/guides/legends/strokeWidth.js +2 -0
  58. package/src/actions/guides/legends/target.js +2 -79
  59. package/src/actions/guides/legends/transition.js +24 -5
  60. package/src/actions/guides/tickValues.js +8 -5
  61. package/src/actions/marks/area/materialize.js +1 -190
  62. package/src/actions/marks/bar/edit.js +23 -12
  63. package/src/actions/marks/bar/materialize.js +6 -1
  64. package/src/actions/marks/point/materialize.js +5 -1
  65. package/src/actions/marks/remove.js +10 -10
  66. package/src/actions/primitives/createGraphics.js +3 -2
  67. package/src/actions/primitives/editGraphics.js +15 -11
  68. package/src/actions/primitives/graphicProperties.js +1 -6
  69. package/src/actions/primitives/semanticAction.js +27 -1
  70. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  71. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  72. package/src/actions/regression/edit.js +15 -3
  73. package/src/actions/scales/channels.js +2 -2
  74. package/src/actions/scales/consumers/common.js +7 -1
  75. package/src/actions/scales/create.js +3 -3
  76. package/src/actions/scales/definitions.js +1 -1
  77. package/src/actions/scales/edit.js +1 -1
  78. package/src/actions/scales/editPolicy.js +3 -0
  79. package/src/actions/scales/patch.js +1 -1
  80. package/src/actions/scales/quantitativeColor.js +8 -3
  81. package/src/actions/theme/reconcile.js +50 -23
  82. package/src/core/compositionState.js +2 -2
  83. package/src/core/graphicProperties.js +1 -0
  84. package/src/core/programState.js +4 -0
  85. package/src/core/resourceReferences.js +2 -2
  86. package/src/core/validation.js +9 -0
  87. package/src/core/vocabulary.js +3 -0
  88. package/src/grammar/aggregate.js +3 -2
  89. package/src/grammar/areaSeries.js +1 -415
  90. package/src/grammar/axisTextPlacement.js +1 -0
  91. package/src/grammar/colorSizeLegend.js +39 -0
  92. package/src/grammar/computed.js +23 -1
  93. package/src/grammar/datasetSchema.js +10 -2
  94. package/src/grammar/displayLabels.js +8 -0
  95. package/src/grammar/facets/guides.js +54 -31
  96. package/src/grammar/facets/legendIntent.js +65 -0
  97. package/src/grammar/guideDash.js +1 -0
  98. package/src/grammar/interval.js +22 -6
  99. package/src/grammar/legendBlocks.js +1 -0
  100. package/src/grammar/legendBorder.js +9 -0
  101. package/src/grammar/legendClip.js +19 -0
  102. package/src/grammar/legendCombinations.js +45 -0
  103. package/src/grammar/legendGradient.js +9 -0
  104. package/src/grammar/legendGroups.js +1 -1
  105. package/src/grammar/legendOpacity.js +6 -0
  106. package/src/grammar/pathSeries.js +2 -2
  107. package/src/grammar/pointShapes.js +16 -2
  108. package/src/grammar/regression/derive.js +9 -1
  109. package/src/grammar/regression/models.js +60 -0
  110. package/src/grammar/regression/parameters.js +9 -1
  111. package/src/grammar/regression/statistics.js +64 -0
  112. package/src/grammar/scales/color.js +1 -1
  113. package/src/grammar/scales/colorConsumers.js +6 -3
  114. package/src/grammar/scales/definition.js +1 -1
  115. package/src/grammar/scales/types.js +1 -1
  116. package/src/grammar/schemas/concreteGraphic.js +3 -0
  117. package/src/grammar/schemas/graphic.js +3 -0
  118. package/src/grammar/schemas/graphicBounds.js +31 -7
  119. package/src/grammar/schemas/semanticPath.js +17 -2
  120. package/src/grammar/statistics/confidenceInterval.js +1 -101
  121. package/src/grammar/summary.js +1 -320
  122. package/src/grammar/timeUnit.js +6 -4
  123. package/src/grammar/transformTopology.js +1 -0
  124. package/src/grammar/transforms.js +11 -1
  125. package/src/inspectionDescriptors.js +1 -1
  126. package/src/layout/guideCollisions.js +9 -1
  127. package/src/layout/legendGroup.js +1 -1
  128. package/src/materialization/compositionSnapshot.js +2 -7
  129. package/src/materialization/coordinateBounds.js +17 -4
  130. package/src/materialization/facetGuides/edit.js +30 -0
  131. package/src/materialization/facetGuides/layout.js +1 -1
  132. package/src/materialization/facetGuides/owner.js +125 -0
  133. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  134. package/src/materialization/facetGuides/placement.js +52 -6
  135. package/src/materialization/facetGuides/preparation.js +7 -0
  136. package/src/materialization/facets.js +1 -1
  137. package/src/materialization/graphicHierarchy.js +5 -1
  138. package/src/materialization/graphicTranslation.js +73 -0
  139. package/src/materialization/guides/layout.js +1 -1
  140. package/src/materialization/guides/resources.js +1 -1
  141. package/src/materialization/legendBorder.js +28 -0
  142. package/src/materialization/legendClip.js +76 -0
  143. package/src/materialization/legendGroups.js +1 -1
  144. package/src/materialization/legends.js +1 -1
  145. package/src/materialization/marks/grouping.js +1 -1
  146. package/src/materialization/scaleGuideDependencies.js +13 -14
  147. package/src/materialization/scales/map.js +3 -0
  148. package/src/materialization/scales/resolve.js +2 -0
  149. package/src/materialization/typography.js +10 -2
  150. package/src/persistence/validation.js +174 -1
  151. package/src/persistence.js +1 -1
  152. package/src/renderers/canvas/index.js +20 -10
  153. package/src/renderers/canvas/isolated.js +166 -0
  154. package/src/renderers/canvas/limits.js +3 -0
  155. package/src/renderers/canvas/rect.js +1 -1
  156. package/src/renderers/pdf.js +2 -2
  157. package/src/renderers/png.js +2 -2
  158. package/src/renderers/svg.js +10 -3
  159. package/src/selectors/axes.js +1 -0
  160. package/src/selectors/graphicCapacity.js +38 -0
  161. package/src/version.js +1 -1
  162. package/types/basic.d.ts +2 -0
  163. package/types/index.d.ts +7 -0
  164. package/types/program.d.ts +220 -64
@@ -1,320 +1 @@
1
- import { scalarKey } from "./transformKeys.js";
2
- import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
3
- import { requireStringValue as requireField } from "../core/validation.js";
4
- import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
5
- import {
6
- aggregateRows,
7
- isScalarAggregate,
8
- validateAggregate,
9
- validateAggregateFieldValues
10
- } from "./aggregate.js";
11
- import {
12
- calculateWeightedAggregate,
13
- normalizeStatisticalWeight,
14
- readStatisticalWeights,
15
- summarizeStatisticalWeights,
16
- validateWeightedAggregate,
17
- validateWeightedNumericFields,
18
- weightedRows
19
- } from "./weightedStatistics.js";
20
-
21
- const TRANSFORM_KEYS = Object.freeze([
22
- "type", "groupBy", "aggregates", "members", "weight", "missing", "empty"
23
- ]);
24
- const AGGREGATE_KEYS = Object.freeze(["op", "field", "as"]);
25
- const NOMINAL_OPERATIONS = new Set(["distinct", "valid", "missing"]);
26
- const MAX_OUTPUT_ROWS = 10_000;
27
-
28
- function normalizeGroupBy(value) {
29
- if (value === undefined) return [];
30
- return Array.isArray(value) ? [...value] : [value];
31
- }
32
-
33
- function normalizeAggregate(value) {
34
- if (!isPlainObject(value)) return value;
35
- return {
36
- ...value,
37
- op: validateAggregate(value.op),
38
- ...(Object.hasOwn(value, "field") ? { field: value.field } : {}),
39
- as: value.as
40
- };
41
- }
42
-
43
- export function normalizeSummaryTransform({ groupBy, aggregates, members, weight, missing, empty } = {}) {
44
- const transform = {
45
- type: "summary",
46
- groupBy: normalizeGroupBy(groupBy),
47
- aggregates: Array.isArray(aggregates)
48
- ? aggregates.map(normalizeAggregate)
49
- : aggregates,
50
- ...(members === undefined ? {} : { members }),
51
- ...(weight === undefined
52
- ? {}
53
- : { weight: normalizeStatisticalWeight(weight, "Summary weight") }),
54
- ...(missing === undefined ? {} : { missing }),
55
- ...(empty === undefined ? {} : { empty })
56
- };
57
- validateSummaryTransform(transform);
58
- return cloneAndFreeze(transform);
59
- }
60
-
61
- export function validateSummaryTransform(transform) {
62
- if (!isPlainObject(transform)) {
63
- throw new TypeError("Summary transform must be a plain object.");
64
- }
65
- rejectUnknownKeys(transform, TRANSFORM_KEYS, "summary transform");
66
- if (transform.type !== "summary") {
67
- throw new Error(`Unsupported summary transform "${transform.type}".`);
68
- }
69
- if (
70
- !Array.isArray(transform.groupBy) ||
71
- transform.groupBy.some(field => typeof field !== "string" || field.length === 0)
72
- ) {
73
- throw new TypeError("Summary groupBy must contain field names.");
74
- }
75
- if (new Set(transform.groupBy).size !== transform.groupBy.length) {
76
- throw new Error("Summary groupBy fields must be unique.");
77
- }
78
- if (!Array.isArray(transform.aggregates) || transform.aggregates.length === 0) {
79
- throw new TypeError("Summary aggregates must be a non-empty array.");
80
- }
81
- if (transform.aggregates.length > 64) {
82
- throw new RangeError("Summary aggregates cannot contain more than 64 outputs.");
83
- }
84
- const outputs = new Set(transform.groupBy);
85
- transform.aggregates.forEach((aggregate, index) => {
86
- if (!isPlainObject(aggregate)) {
87
- throw new TypeError(`Summary aggregate ${index} must be a plain object.`);
88
- }
89
- rejectUnknownKeys(aggregate, AGGREGATE_KEYS, `summary aggregate ${index}`);
90
- const operation = validateAggregate(aggregate.op);
91
- if (transform.weight !== undefined) validateWeightedAggregate(operation);
92
- if (operation === "count") {
93
- if (Object.hasOwn(aggregate, "field")) {
94
- throw new Error("Summary count does not accept a field.");
95
- }
96
- } else {
97
- requireField(aggregate.field, `Summary aggregate ${index} field`);
98
- }
99
- const output = requireField(aggregate.as, `Summary aggregate ${index} as`);
100
- if (outputs.has(output)) {
101
- throw new Error(`Summary output field "${output}" collides with another output.`);
102
- }
103
- outputs.add(output);
104
- });
105
- if (transform.members !== undefined) {
106
- const members = requireField(transform.members, "Summary members");
107
- if (outputs.has(members)) {
108
- throw new Error(`Summary members field "${members}" collides with another output.`);
109
- }
110
- }
111
- if (transform.weight !== undefined) {
112
- normalizeStatisticalWeight(transform.weight, "Summary weight");
113
- }
114
- if (transform.missing !== undefined && !["error", "drop"].includes(transform.missing)) {
115
- throw new Error('Summary missing must be "error" or "drop".');
116
- }
117
- if (transform.empty !== undefined && !["null", "identity"].includes(transform.empty)) {
118
- throw new Error('Summary empty must be "null" or "identity".');
119
- }
120
- return transform;
121
- }
122
-
123
- function requireSourceFields(rows, transform) {
124
- if (rows.length === 0) return;
125
- const fields = new Set(rows.flatMap(row => Object.keys(row)));
126
- for (const field of transform.groupBy) {
127
- if (!fields.has(field)) {
128
- throw new Error(`Summary source does not contain group field "${field}".`);
129
- }
130
- }
131
- for (const aggregate of transform.aggregates) {
132
- if (aggregate.field !== undefined && !fields.has(aggregate.field)) {
133
- throw new Error(`Summary source does not contain aggregate field "${aggregate.field}".`);
134
- }
135
- const orderBy = isPlainObject(aggregate.op) ? aggregate.op.orderBy : undefined;
136
- if (orderBy !== undefined && !fields.has(orderBy)) {
137
- throw new Error(`Summary source does not contain order field "${orderBy}".`);
138
- }
139
- }
140
- if (transform.weight !== undefined && !fields.has(transform.weight.field)) {
141
- throw new Error(`Summary source does not contain weight field "${transform.weight.field}".`);
142
- }
143
- }
144
-
145
- function validateAggregateValues(rows, aggregate) {
146
- if (aggregate.op === "count") return;
147
- if (isScalarAggregate(aggregate.op) && NOMINAL_OPERATIONS.has(aggregate.op)) {
148
- validateAggregateFieldValues(rows, aggregate.field, "nominal");
149
- return;
150
- }
151
- validateAggregateFieldValues(rows, aggregate.field, "quantitative");
152
- }
153
-
154
- export function deriveSummaryRows(rows, transform) {
155
- validateSummaryTransform(transform);
156
- requireSourceFields(rows, transform);
157
- const explicitWeighted = transform.weight !== undefined && transform.missing !== undefined;
158
- let weightEntries;
159
- if (transform.weight === undefined) {
160
- for (const aggregate of transform.aggregates) {
161
- validateAggregateValues(rows, aggregate);
162
- }
163
- } else if (!explicitWeighted) {
164
- weightEntries = readStatisticalWeights(rows, transform.weight, "Summary").entries;
165
- validateWeightedNumericFields(
166
- weightEntries,
167
- [...new Set(transform.aggregates.flatMap(aggregate =>
168
- aggregate.field === undefined ? [] : [aggregate.field]
169
- ))],
170
- "Summary"
171
- );
172
- } else {
173
- // Explicit missing policy is evaluated per measure. Validate every
174
- // nonmissing value now, while leaving nullish value/weight pairs for the
175
- // reason-priority accounting below.
176
- readStatisticalWeights(
177
- rows.filter(row => row[transform.weight.field] !== null && row[transform.weight.field] !== undefined),
178
- transform.weight,
179
- "Summary"
180
- );
181
- for (const aggregate of transform.aggregates) {
182
- if (aggregate.field === undefined) continue;
183
- validateAggregateValues(
184
- rows.filter(row => row[aggregate.field] !== null && row[aggregate.field] !== undefined),
185
- aggregate
186
- );
187
- }
188
- }
189
- if (transform.missing === "error") {
190
- if (transform.weight !== undefined) {
191
- const weightIndex = rows.findIndex(row =>
192
- row[transform.weight.field] === null || row[transform.weight.field] === undefined
193
- );
194
- if (weightIndex !== -1) {
195
- throw new TypeError(`Summary weight "${transform.weight.field}" is missing at row ${weightIndex}.`);
196
- }
197
- }
198
- for (const aggregate of transform.aggregates) {
199
- if (aggregate.field === undefined) continue;
200
- const index = rows.findIndex(row => row[aggregate.field] === null || row[aggregate.field] === undefined);
201
- if (index !== -1) throw new TypeError(`Summary field "${aggregate.field}" is missing at row ${index}.`);
202
- }
203
- }
204
-
205
- const groups = new Map();
206
- if (transform.groupBy.length === 0) {
207
- groups.set("all", {
208
- values: {},
209
- rows,
210
- ...(weightEntries === undefined ? {} : { entries: weightEntries })
211
- });
212
- } else {
213
- rows.forEach((row, index) => {
214
- const values = Object.fromEntries(transform.groupBy.map(field => [field, row[field]]));
215
- const key = transform.groupBy.map(field =>
216
- scalarKey(row[field], `Summary group field "${field}"`)
217
- ).join("|");
218
- const group = groups.get(key) ?? {
219
- values,
220
- rows: [],
221
- ...(weightEntries === undefined ? {} : { entries: [] })
222
- };
223
- group.rows.push(row);
224
- if (weightEntries !== undefined) group.entries.push(weightEntries[index]);
225
- groups.set(key, group);
226
- });
227
- }
228
- if (groups.size > MAX_OUTPUT_ROWS) {
229
- throw new RangeError(`Summary output cannot exceed ${MAX_OUTPUT_ROWS} groups.`);
230
- }
231
-
232
- const units = [];
233
- const values = [...groups.values()].map(group => {
234
- const sharedWeightSummary = transform.weight === undefined || explicitWeighted
235
- ? undefined
236
- : summarizeStatisticalWeights(
237
- group.entries,
238
- transform.weight.kind,
239
- "Summary group"
240
- );
241
- const aggregated = Object.fromEntries(transform.aggregates.map(aggregate => {
242
- let missingValue = 0;
243
- let missingWeight = 0;
244
- let zeroWeightRows = 0;
245
- let measureWeightSummary = sharedWeightSummary;
246
- if (explicitWeighted) {
247
- const eligible = group.rows.filter(row => {
248
- if (aggregate.field !== undefined &&
249
- (row[aggregate.field] === null || row[aggregate.field] === undefined)) {
250
- missingValue += 1;
251
- return false;
252
- }
253
- if (row[transform.weight.field] === null || row[transform.weight.field] === undefined) {
254
- missingWeight += 1;
255
- return false;
256
- }
257
- return true;
258
- });
259
- const entries = readStatisticalWeights(eligible, transform.weight, "Summary").entries;
260
- zeroWeightRows = entries.filter(entry => entry.weight === 0).length;
261
- measureWeightSummary = summarizeStatisticalWeights(
262
- entries,
263
- transform.weight.kind,
264
- "Summary group"
265
- );
266
- }
267
- let value = measureWeightSummary === undefined
268
- ? aggregateRows(group.rows, aggregate.field ?? "__row", aggregate.op)
269
- : calculateWeightedAggregate(
270
- measureWeightSummary,
271
- aggregate.field,
272
- aggregate.op,
273
- `Summary aggregate "${aggregate.as}"`
274
- );
275
- if (value === undefined && transform.empty !== undefined) {
276
- const op = typeof aggregate.op === "string" ? aggregate.op : aggregate.op.op;
277
- value = transform.empty === "identity" && op === "sum" ? 0 : null;
278
- }
279
- if (transform.missing !== undefined || transform.empty !== undefined) {
280
- const missing = explicitWeighted
281
- ? missingValue + missingWeight
282
- : aggregate.field === undefined ? 0 : group.rows.filter(row => row[aggregate.field] === null || row[aggregate.field] === undefined).length;
283
- units.push({
284
- role: aggregate.as,
285
- group: group.values,
286
- inputRows: group.rows.length,
287
- usedRows: group.rows.length - missing,
288
- excludedRows: missing,
289
- excludedByReason: missing === 0 ? {} : {
290
- ...(missingValue === 0 && explicitWeighted ? {} : { "missing-value": explicitWeighted ? missingValue : missing }),
291
- ...(missingWeight === 0 ? {} : { "missing-weight": missingWeight })
292
- },
293
- ...(explicitWeighted ? { zeroWeightRows } : {})
294
- });
295
- }
296
- return [aggregate.as, value];
297
- }));
298
- return {
299
- ...group.values,
300
- ...aggregated,
301
- ...(transform.members === undefined
302
- ? {}
303
- : {
304
- [transform.members]: sharedWeightSummary === undefined
305
- ? group.rows
306
- : weightedRows(sharedWeightSummary)
307
- })
308
- };
309
- });
310
- if (transform.missing === undefined && transform.empty === undefined) return values;
311
- return {
312
- values,
313
- report: cloneAndFreeze({
314
- version: 1,
315
- owner: { kind: "data", id: "pending" },
316
- inputs: [],
317
- units
318
- })
319
- };
320
- }
1
+ import{createBootstrapMeanSampler as q,bootstrapMeanBounds as I,validateBootstrapDrawCount as U}from"./statistics/confidenceInterval.js";import{scalarKey as V}from"./transformKeys.js";import{rejectUnknownProperties as W}from"../core/validation.js";import{requireStringValue as E}from"../core/validation.js";import{cloneAndFreeze as v,isPlainObject as g}from"../core/immutable.js";import{aggregateRows as G,isScalarAggregate as K,validateAggregate as B,validateAggregateFieldValues as z}from"./aggregate.js";import{calculateWeightedAggregate as P,normalizeStatisticalWeight as M,readStatisticalWeights as A,summarizeStatisticalWeights as k,validateWeightedAggregate as J,validateWeightedNumericFields as L,weightedRows as Y}from"./weightedStatistics.js";const C=Object.freeze(["type","groupBy","aggregates","members","weight","missing","empty"]),D=Object.freeze(["op","field","as"]),X=new Set(["distinct","valid","missing"]),N=3e4;function H(e){return e===void 0?[]:Array.isArray(e)?[...e]:[e]}function Q(e){return g(e)?{...e,op:B(e.op,{allowBootstrap:!0}),...Object.hasOwn(e,"field")?{field:e.field}:{},as:e.as}:e}function ue({groupBy:e,aggregates:i,members:n,weight:o,missing:u,empty:l}={}){const m={type:"summary",groupBy:H(e),aggregates:Array.isArray(i)?i.map(Q):i,...n===void 0?{}:{members:n},...o===void 0?{}:{weight:M(o,"Summary weight")},...u===void 0?{}:{missing:u},...l===void 0?{}:{empty:l}};return x(m),v(m)}function x(e){if(!g(e))throw new TypeError("Summary transform must be a plain object.");if(W(e,C,"summary transform"),e.type!=="summary")throw new Error(`Unsupported summary transform "${e.type}".`);if(!Array.isArray(e.groupBy)||e.groupBy.some(n=>typeof n!="string"||n.length===0))throw new TypeError("Summary groupBy must contain field names.");if(new Set(e.groupBy).size!==e.groupBy.length)throw new Error("Summary groupBy fields must be unique.");if(!Array.isArray(e.aggregates)||e.aggregates.length===0)throw new TypeError("Summary aggregates must be a non-empty array.");if(e.aggregates.length>64)throw new RangeError("Summary aggregates cannot contain more than 64 outputs.");const i=new Set(e.groupBy);if(e.aggregates.forEach((n,o)=>{if(!g(n))throw new TypeError(`Summary aggregate ${o} must be a plain object.`);W(n,D,`summary aggregate ${o}`);const u=B(n.op,{allowBootstrap:!0});if(e.weight!==void 0&&J(u),u==="count"){if(Object.hasOwn(n,"field"))throw new Error("Summary count does not accept a field.")}else E(n.field,`Summary aggregate ${o} field`);const l=E(n.as,`Summary aggregate ${o} as`);if(i.has(l))throw new Error(`Summary output field "${l}" collides with another output.`);i.add(l)}),e.members!==void 0){const n=E(e.members,"Summary members");if(i.has(n))throw new Error(`Summary members field "${n}" collides with another output.`)}if(e.weight!==void 0&&M(e.weight,"Summary weight"),e.missing!==void 0&&!["error","drop"].includes(e.missing))throw new Error('Summary missing must be "error" or "drop".');if(e.empty!==void 0&&!["null","identity"].includes(e.empty))throw new Error('Summary empty must be "null" or "identity".');return e}function Z(e,i){if(e.length===0)return;const n=new Set(e.flatMap(o=>Object.keys(o)));for(const o of i.groupBy)if(!n.has(o))throw new Error(`Summary source does not contain group field "${o}".`);for(const o of i.aggregates){if(o.field!==void 0&&!n.has(o.field))throw new Error(`Summary source does not contain aggregate field "${o.field}".`);const u=g(o.op)?o.op.orderBy:void 0;if(u!==void 0&&!n.has(u))throw new Error(`Summary source does not contain order field "${u}".`)}if(i.weight!==void 0&&!n.has(i.weight.field))throw new Error(`Summary source does not contain weight field "${i.weight.field}".`)}function F(e,i){if(i.op!=="count"){if(K(i.op)&&X.has(i.op)){z(e,i.field,"nominal");return}z(e,i.field,"quantitative")}}function se(e,i){x(i),Z(e,i);const n=i.aggregates.map(t=>({...t,op:B(t.op,{allowBootstrap:!0})})),o=i.weight!==void 0&&i.missing!==void 0;let u;if(i.weight===void 0)for(const t of n)F(e,t);else if(!o)u=A(e,i.weight,"Summary").entries,L(u,[...new Set(n.flatMap(t=>t.field===void 0?[]:[t.field]))],"Summary");else{A(e.filter(t=>t[i.weight.field]!==null&&t[i.weight.field]!==void 0),i.weight,"Summary");for(const t of n)t.field!==void 0&&F(e.filter(d=>d[t.field]!==null&&d[t.field]!==void 0),t)}if(i.missing==="error"){if(i.weight!==void 0){const t=e.findIndex(d=>d[i.weight.field]===null||d[i.weight.field]===void 0);if(t!==-1)throw new TypeError(`Summary weight "${i.weight.field}" is missing at row ${t}.`)}for(const t of n){if(t.field===void 0)continue;const d=e.findIndex(f=>f[t.field]===null||f[t.field]===void 0);if(d!==-1)throw new TypeError(`Summary field "${t.field}" is missing at row ${d}.`)}}const l=new Map;if(i.groupBy.length===0?l.set("all",{values:{},rows:e,...u===void 0?{}:{entries:u}}):e.forEach((t,d)=>{const f=Object.fromEntries(i.groupBy.map(a=>[a,t[a]])),c=i.groupBy.map(a=>V(t[a],`Summary group field "${a}"`)).join("|"),r=l.get(c)??{values:f,rows:[],...u===void 0?{}:{entries:[]}};r.rows.push(t),u!==void 0&&r.entries.push(u[d]),l.set(c,r)}),l.size>N)throw new RangeError(`Summary output cannot exceed ${N} groups.`);const m=new Map;let O=0;for(const t of n){const d=t.op?.method;if(d?.type!=="bootstrap")continue;const f=JSON.stringify([t.field,d.samples,d.seed]);m.has(f)||(O+=d.samples*e.filter(c=>Number.isFinite(c[t.field])).length,U(O),m.set(f,q(d)))}const $=[],R=[...l.values()].map(t=>{const d=new Map,f=i.weight===void 0||o?void 0:k(t.entries,i.weight.kind,"Summary group"),c=Object.fromEntries(n.map(r=>{let a=0,h=0,j=0,S=f;if(o){const s=t.rows.filter(p=>r.field!==void 0&&(p[r.field]===null||p[r.field]===void 0)?(a+=1,!1):p[i.weight.field]===null||p[i.weight.field]===void 0?(h+=1,!1):!0),y=A(s,i.weight,"Summary").entries;j=y.filter(p=>p.weight===0).length,S=k(y,i.weight.kind,"Summary group")}const w=r.op?.method;let T;if(w?.type==="bootstrap"){const s=JSON.stringify([r.field,w.samples,w.seed]);if(!d.has(s)){const p=t.rows.map(_=>_[r.field]).filter(Number.isFinite);d.set(s,m.get(s)(p))}T=I(d.get(s),r.op.level)?.[r.op.op==="ciLower"?"lower":"upper"]}let b=w?.type==="bootstrap"?T:S===void 0?G(t.rows,r.field??"__row",r.op):P(S,r.field,r.op,`Summary aggregate "${r.as}"`);if(b===void 0&&i.empty!==void 0){const s=typeof r.op=="string"?r.op:r.op.op;b=i.empty==="identity"&&s==="sum"?0:null}if(i.missing!==void 0||i.empty!==void 0){const s=o?a+h:r.field===void 0?0:t.rows.filter(y=>y[r.field]===null||y[r.field]===void 0).length;$.push({role:r.as,group:t.values,inputRows:t.rows.length,usedRows:t.rows.length-s,excludedRows:s,excludedByReason:s===0?{}:{...a===0&&o?{}:{"missing-value":o?a:s},...h===0?{}:{"missing-weight":h}},...o?{zeroWeightRows:j}:{}})}return[r.as,b]}));return{...t.values,...c,...i.members===void 0?{}:{[i.members]:f===void 0?t.rows:Y(f)}}});return i.missing===void 0&&i.empty===void 0?R:{values:R,report:v({version:1,owner:{kind:"data",id:"pending"},inputs:[],units:$})}}export{se as deriveSummaryRows,ue as normalizeSummaryTransform,x as validateSummaryTransform};
@@ -52,6 +52,7 @@ function formatter(timeZone) {
52
52
  try {
53
53
  value = new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
54
54
  timeZone,
55
+ era: "short",
55
56
  year: "numeric",
56
57
  month: "2-digit",
57
58
  day: "2-digit",
@@ -68,7 +69,7 @@ function formatter(timeZone) {
68
69
  return CALENDAR_FORMATTERS.get(timeZone);
69
70
  }
70
71
 
71
- function validateTimeZone(timeZone) {
72
+ export function validateTimeZone(timeZone) {
72
73
  if (typeof timeZone !== "string" || timeZone.length === 0) {
73
74
  throw new TypeError("Time-unit timeZone must be a non-empty IANA time-zone name.");
74
75
  }
@@ -178,17 +179,18 @@ export function calendarParts(timestamp, timeZone) {
178
179
  validateTimeZone(timeZone);
179
180
  const parts = Object.fromEntries(formatter(timeZone).formatToParts(timestamp)
180
181
  .filter(part => part.type !== "literal")
181
- .map(part => [part.type, Number(part.value)]));
182
+ .map(part => [part.type, part.type === "era" ? part.value : Number(part.value)]));
183
+ const year = parts.era === "BC" ? 1 - parts.year : parts.year;
182
184
  const millisecond = ((timestamp % 1000) + 1000) % 1000;
183
185
  return {
184
- year: parts.year,
186
+ year,
185
187
  month: parts.month,
186
188
  day: parts.day,
187
189
  hour: parts.hour,
188
190
  minute: parts.minute,
189
191
  second: parts.second,
190
192
  millisecond,
191
- weekday: weekdayFromDate(parts.year, parts.month, parts.day)
193
+ weekday: weekdayFromDate(year, parts.month, parts.day)
192
194
  };
193
195
  }
194
196
 
@@ -16,6 +16,7 @@ const TRANSFORM_TOPOLOGY = Object.freeze({
16
16
  markFilter: Object.freeze({ provenanceTransparent: true }),
17
17
  normalize: Object.freeze({ facetTopology: "statistical" }),
18
18
  regression: Object.freeze({ facetTopology: "statistical" }),
19
+ regressionStatistics: Object.freeze({ facetTopology: "statistical" }),
19
20
  sort: Object.freeze({ facetTopology: "statistical" }),
20
21
  summary: Object.freeze({ facetTopology: "statistical" }),
21
22
  stack: Object.freeze({ facetTopology: "statistical" }),
@@ -1,4 +1,5 @@
1
1
  import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
+ import { validateRegressionStatisticsTransform } from "./regression/statistics.js";
2
3
  import { validateBoxTransform } from "./boxPlot.js";
3
4
  import { normalizeBinTransform, validateBinTransform } from "./bin.js";
4
5
  import {
@@ -203,7 +204,7 @@ function normalizeRegressionEdit(transform, patch) {
203
204
  delete base.span;
204
205
  delete base.robustIterations;
205
206
  }
206
- if (method === "loess" || base.method === "loess") {
207
+ if (["loess", "exp"].includes(method) || ["loess", "exp"].includes(base.method)) {
207
208
  for (const key of [
208
209
  "confidenceMethod", "level", "confidence", "interval"
209
210
  ]) delete base[key];
@@ -446,6 +447,15 @@ const TRANSFORM_POLICIES = Object.freeze({
446
447
  normalizeEdit: normalizeRegressionEdit,
447
448
  outputRoles: () => ({})
448
449
  }),
450
+ regressionStatistics: Object.freeze({
451
+ ...findTransformTopology("regressionStatistics"),
452
+ validate: validateRegressionStatisticsTransform,
453
+ materializeOp: "materializeRegressionStatisticsData",
454
+ replayTransform: requestedTransform,
455
+ editable: true,
456
+ normalizeEdit: (transform, patch) => validateRegressionStatisticsTransform({ ...transform, ...patch }),
457
+ outputRoles: () => ({ count: "count", intercept: "intercept", slope: "slope", rSquared: "rSquared" })
458
+ }),
449
459
  sort: Object.freeze({
450
460
  ...findTransformTopology("sort"),
451
461
  validate: validateSortTransform,