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.
- package/CHANGELOG.md +49 -0
- package/README.md +2 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +10 -16
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +10 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +67 -22
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +21 -10
- package/src/actions/errorBars/edit.js +355 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +44 -23
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +106 -2
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +34 -3
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +7 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +41 -7
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facets.js +8 -1
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +58 -23
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +46 -14
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1320 -135
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import { stableDecimal } from "./numeric.js";
|
|
3
|
+
|
|
4
|
+
const TRANSFORM_KEYS = Object.freeze([
|
|
5
|
+
"type", "field", "groupBy", "weight", "missing", "as", "resolved"
|
|
6
|
+
]);
|
|
7
|
+
const OUTPUT_KEYS = Object.freeze(["value", "cumulative", "probability"]);
|
|
8
|
+
|
|
9
|
+
function nonEmptyString(value, label) {
|
|
10
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
11
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeGroupBy(groupBy) {
|
|
16
|
+
const fields = groupBy === undefined
|
|
17
|
+
? []
|
|
18
|
+
: Array.isArray(groupBy) ? [...groupBy] : [groupBy];
|
|
19
|
+
fields.forEach(field => nonEmptyString(field, "ECDF groupBy field"));
|
|
20
|
+
if (new Set(fields).size !== fields.length) {
|
|
21
|
+
throw new Error("ECDF groupBy fields must be distinct.");
|
|
22
|
+
}
|
|
23
|
+
return fields;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function validateOutputs(as, occupied) {
|
|
27
|
+
if (!isPlainObject(as)) throw new TypeError("ECDF as must be a plain object.");
|
|
28
|
+
const keys = Object.keys(as);
|
|
29
|
+
if (keys.length !== OUTPUT_KEYS.length ||
|
|
30
|
+
!OUTPUT_KEYS.every(key => Object.hasOwn(as, key))) {
|
|
31
|
+
throw new Error("ECDF as requires exactly value, cumulative, and probability.");
|
|
32
|
+
}
|
|
33
|
+
const values = OUTPUT_KEYS.map(key => as[key]);
|
|
34
|
+
values.forEach((field, index) => nonEmptyString(field, `ECDF as.${OUTPUT_KEYS[index]}`));
|
|
35
|
+
if (new Set(values).size !== values.length) {
|
|
36
|
+
throw new Error("ECDF output fields must be distinct.");
|
|
37
|
+
}
|
|
38
|
+
if (values.some(field => occupied.has(field))) {
|
|
39
|
+
throw new Error("ECDF output fields must not collide with input or grouping fields.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateResolved(resolved, groupBy) {
|
|
44
|
+
if (resolved === undefined) return;
|
|
45
|
+
if (!isPlainObject(resolved) || !Array.isArray(resolved.groups)) {
|
|
46
|
+
throw new TypeError("ECDF resolved provenance must contain a groups array.");
|
|
47
|
+
}
|
|
48
|
+
for (const group of resolved.groups) {
|
|
49
|
+
if (!isPlainObject(group) || !isPlainObject(group.keys) ||
|
|
50
|
+
!Number.isFinite(group.denominator) || group.denominator <= 0 ||
|
|
51
|
+
!Number.isInteger(group.validCount) || group.validCount <= 0 ||
|
|
52
|
+
Object.keys(group.keys).length !== groupBy.length ||
|
|
53
|
+
!groupBy.every(field => Object.hasOwn(group.keys, field))) {
|
|
54
|
+
throw new TypeError("ECDF resolved group provenance is invalid.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function validateECDFTransform(transform) {
|
|
60
|
+
if (!isPlainObject(transform)) {
|
|
61
|
+
throw new TypeError("ECDF transform must be a plain object.");
|
|
62
|
+
}
|
|
63
|
+
const unknown = Object.keys(transform).find(key => !TRANSFORM_KEYS.includes(key));
|
|
64
|
+
if (unknown !== undefined) {
|
|
65
|
+
throw new Error(`Unknown ECDF transform property "${unknown}".`);
|
|
66
|
+
}
|
|
67
|
+
if (transform.type !== "ecdf") {
|
|
68
|
+
throw new Error(`Unsupported ECDF transform "${transform.type}".`);
|
|
69
|
+
}
|
|
70
|
+
nonEmptyString(transform.field, "ECDF field");
|
|
71
|
+
const groupBy = normalizeGroupBy(transform.groupBy);
|
|
72
|
+
if (groupBy.includes(transform.field)) {
|
|
73
|
+
throw new Error("ECDF field must not also be a grouping field.");
|
|
74
|
+
}
|
|
75
|
+
if (transform.weight !== undefined) {
|
|
76
|
+
nonEmptyString(transform.weight, "ECDF weight field");
|
|
77
|
+
if (transform.weight === transform.field || groupBy.includes(transform.weight)) {
|
|
78
|
+
throw new Error("ECDF weight field must be distinct from value and grouping fields.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!["drop", "error"].includes(transform.missing)) {
|
|
82
|
+
throw new Error('ECDF missing must be "drop" or "error".');
|
|
83
|
+
}
|
|
84
|
+
validateOutputs(
|
|
85
|
+
transform.as,
|
|
86
|
+
new Set([transform.field, transform.weight, ...groupBy].filter(Boolean))
|
|
87
|
+
);
|
|
88
|
+
validateResolved(transform.resolved, groupBy);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function normalizeECDFTransform({ field, groupBy, weight, missing = "drop", as }) {
|
|
92
|
+
const transform = {
|
|
93
|
+
type: "ecdf",
|
|
94
|
+
field,
|
|
95
|
+
groupBy: normalizeGroupBy(groupBy),
|
|
96
|
+
...(weight === undefined ? {} : { weight }),
|
|
97
|
+
missing,
|
|
98
|
+
as
|
|
99
|
+
};
|
|
100
|
+
validateECDFTransform(transform);
|
|
101
|
+
return cloneAndFreeze(transform);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function groupValue(value) {
|
|
105
|
+
return typeof value === "string" || typeof value === "boolean" ||
|
|
106
|
+
(typeof value === "number" && Number.isFinite(value));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function rejectOrDrop(transform, index, reason) {
|
|
110
|
+
if (transform.missing === "error") {
|
|
111
|
+
throw new TypeError(`ECDF row ${index} ${reason}.`);
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function deriveECDF(rows, transform) {
|
|
117
|
+
if (!Array.isArray(rows)) throw new TypeError("ECDF rows must be an array.");
|
|
118
|
+
validateECDFTransform(transform);
|
|
119
|
+
const groups = new Map();
|
|
120
|
+
|
|
121
|
+
rows.forEach((row, index) => {
|
|
122
|
+
if (!isPlainObject(row)) {
|
|
123
|
+
rejectOrDrop(transform, index, "must be a plain object");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const value = row[transform.field];
|
|
127
|
+
if (!Number.isFinite(value)) {
|
|
128
|
+
rejectOrDrop(transform, index, `has an invalid value in "${transform.field}"`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const keys = transform.groupBy.map(field => row[field]);
|
|
132
|
+
if (!keys.every(groupValue)) {
|
|
133
|
+
rejectOrDrop(transform, index, "has an invalid grouping value");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const weight = transform.weight === undefined ? 1 : row[transform.weight];
|
|
137
|
+
if (!Number.isFinite(weight)) {
|
|
138
|
+
rejectOrDrop(transform, index, `has an invalid weight in "${transform.weight}"`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (weight < 0) {
|
|
142
|
+
throw new RangeError(`ECDF row ${index} has a negative weight.`);
|
|
143
|
+
}
|
|
144
|
+
const key = JSON.stringify(keys);
|
|
145
|
+
if (!groups.has(key)) {
|
|
146
|
+
groups.set(key, {
|
|
147
|
+
keys: Object.fromEntries(transform.groupBy.map((field, offset) => [field, keys[offset]])),
|
|
148
|
+
values: new Map(),
|
|
149
|
+
denominator: 0,
|
|
150
|
+
validCount: 0
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const group = groups.get(key);
|
|
154
|
+
if (weight === 0) return;
|
|
155
|
+
group.values.set(value, (group.values.get(value) ?? 0) + weight);
|
|
156
|
+
group.denominator += weight;
|
|
157
|
+
group.validCount += 1;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (groups.size === 0) {
|
|
161
|
+
throw new RangeError("ECDF denominator must be greater than zero.");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const values = [];
|
|
165
|
+
const provenance = [];
|
|
166
|
+
for (const group of groups.values()) {
|
|
167
|
+
if (!(group.denominator > 0) || !Number.isFinite(group.denominator)) {
|
|
168
|
+
throw new RangeError("ECDF denominator must be finite and greater than zero.");
|
|
169
|
+
}
|
|
170
|
+
const support = [...group.values.keys()].sort((left, right) => left - right);
|
|
171
|
+
values.push({
|
|
172
|
+
...group.keys,
|
|
173
|
+
[transform.as.value]: support[0],
|
|
174
|
+
[transform.as.cumulative]: 0,
|
|
175
|
+
[transform.as.probability]: 0
|
|
176
|
+
});
|
|
177
|
+
let cumulative = 0;
|
|
178
|
+
for (const value of support) {
|
|
179
|
+
cumulative += group.values.get(value);
|
|
180
|
+
values.push({
|
|
181
|
+
...group.keys,
|
|
182
|
+
[transform.as.value]: value,
|
|
183
|
+
[transform.as.cumulative]: stableDecimal(cumulative),
|
|
184
|
+
[transform.as.probability]: stableDecimal(cumulative / group.denominator)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
provenance.push({
|
|
188
|
+
keys: group.keys,
|
|
189
|
+
denominator: stableDecimal(group.denominator),
|
|
190
|
+
validCount: group.validCount
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return cloneAndFreeze({ values, resolved: { groups: provenance } });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function resolveECDFTransform(result, transform) {
|
|
197
|
+
return cloneAndFreeze([{ ...transform, resolved: result.resolved }]);
|
|
198
|
+
}
|
|
@@ -18,9 +18,9 @@ const SUPPORTED_BAR_GRAINS = new Set([
|
|
|
18
18
|
]);
|
|
19
19
|
const MAX_FACET_CHILDREN = 100;
|
|
20
20
|
|
|
21
|
-
function requireFacetField(field) {
|
|
21
|
+
function requireFacetField(field, label = "facet") {
|
|
22
22
|
if (typeof field !== "string" || field.length === 0) {
|
|
23
|
-
throw new TypeError(
|
|
23
|
+
throw new TypeError(`${label} requires a non-empty field.`);
|
|
24
24
|
}
|
|
25
25
|
return field;
|
|
26
26
|
}
|
|
@@ -74,25 +74,52 @@ function uniqueInOrder(values) {
|
|
|
74
74
|
return [...new Set(values)];
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function resolveValues(observed, requested) {
|
|
77
|
+
function resolveValues(observed, requested, label = "facet values") {
|
|
78
78
|
if (requested === undefined) return observed;
|
|
79
79
|
if (!Array.isArray(requested) || requested.length === 0) {
|
|
80
|
-
throw new TypeError(
|
|
80
|
+
throw new TypeError(`${label} must be a non-empty array when provided.`);
|
|
81
81
|
}
|
|
82
82
|
const normalized = readNominalField(
|
|
83
83
|
requested.map(value => ({ value })),
|
|
84
84
|
"value"
|
|
85
85
|
);
|
|
86
86
|
if (new Set(normalized).size !== normalized.length) {
|
|
87
|
-
throw new Error(
|
|
87
|
+
throw new Error(`${label} must be unique.`);
|
|
88
88
|
}
|
|
89
89
|
const missing = normalized.find(value => !observed.includes(value));
|
|
90
90
|
if (missing !== undefined) {
|
|
91
|
-
throw new Error(
|
|
91
|
+
throw new Error(
|
|
92
|
+
`${label} value ${JSON.stringify(missing)} is not present in the source field.`
|
|
93
|
+
);
|
|
92
94
|
}
|
|
93
95
|
return normalized;
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
function requireGridRole(value, label) {
|
|
99
|
+
if (!isPlainObject(value)) {
|
|
100
|
+
throw new TypeError(`facetGrid ${label} must be a plain object.`);
|
|
101
|
+
}
|
|
102
|
+
const unknown = Object.keys(value).find(key => !["field", "values"].includes(key));
|
|
103
|
+
if (unknown !== undefined) {
|
|
104
|
+
throw new Error(`Unknown facetGrid ${label} option "${unknown}".`);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
field: requireFacetField(value.field, `facetGrid ${label}`),
|
|
108
|
+
...(value.values === undefined ? {} : { values: value.values })
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function pairKey(row, column) {
|
|
113
|
+
return JSON.stringify([
|
|
114
|
+
[typeof row, row],
|
|
115
|
+
[typeof column, column]
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function displayValue(value) {
|
|
120
|
+
return value === "" ? "(empty)" : String(value);
|
|
121
|
+
}
|
|
122
|
+
|
|
96
123
|
export function resolveFacetDefinition(semanticSpec, options = {}) {
|
|
97
124
|
if (!isPlainObject(semanticSpec)) {
|
|
98
125
|
throw new TypeError("resolveFacetDefinition requires a semantic spec.");
|
|
@@ -136,3 +163,87 @@ export function resolveFacetDefinition(semanticSpec, options = {}) {
|
|
|
136
163
|
}))
|
|
137
164
|
});
|
|
138
165
|
}
|
|
166
|
+
|
|
167
|
+
export function resolveFacetGridDefinition(semanticSpec, options = {}) {
|
|
168
|
+
if (!isPlainObject(semanticSpec)) {
|
|
169
|
+
throw new TypeError("resolveFacetGridDefinition requires a semantic spec.");
|
|
170
|
+
}
|
|
171
|
+
if (!isPlainObject(options)) {
|
|
172
|
+
throw new TypeError("facetGrid options must be a plain object.");
|
|
173
|
+
}
|
|
174
|
+
const rows = requireGridRole(options.rows, "rows");
|
|
175
|
+
const columns = requireGridRole(options.columns, "columns");
|
|
176
|
+
if (rows.field === columns.field) {
|
|
177
|
+
throw new Error("facetGrid rows.field and columns.field must be different.");
|
|
178
|
+
}
|
|
179
|
+
const combinations = options.combinations ?? "observed";
|
|
180
|
+
if (!["observed", "full"].includes(combinations)) {
|
|
181
|
+
throw new Error('facetGrid combinations must be "observed" or "full".');
|
|
182
|
+
}
|
|
183
|
+
const id = validateUserId(options.id ?? "facetGrid", "Facet grid id");
|
|
184
|
+
requireSupportedLayers(semanticSpec);
|
|
185
|
+
const dependencies = planFacetDependencies(semanticSpec, {
|
|
186
|
+
field: rows.field,
|
|
187
|
+
...(options.data === undefined ? {} : { data: options.data })
|
|
188
|
+
});
|
|
189
|
+
const data = dependencies.anchor;
|
|
190
|
+
const dataset = requirePartitionDataset(semanticSpec, data);
|
|
191
|
+
const observedRows = readNominalField(dataset.values, rows.field);
|
|
192
|
+
const observedColumns = readNominalField(dataset.values, columns.field);
|
|
193
|
+
const rowValues = resolveValues(
|
|
194
|
+
uniqueInOrder(observedRows), rows.values, "facetGrid rows.values"
|
|
195
|
+
);
|
|
196
|
+
const columnValues = resolveValues(
|
|
197
|
+
uniqueInOrder(observedColumns), columns.values, "facetGrid columns.values"
|
|
198
|
+
);
|
|
199
|
+
const observedPairs = new Set(dataset.values.map((_, index) =>
|
|
200
|
+
pairKey(observedRows[index], observedColumns[index])
|
|
201
|
+
));
|
|
202
|
+
const candidates = rowValues.flatMap((rowValue, row) =>
|
|
203
|
+
columnValues.map((columnValue, column) => ({
|
|
204
|
+
row,
|
|
205
|
+
column,
|
|
206
|
+
rowValue,
|
|
207
|
+
columnValue,
|
|
208
|
+
empty: !observedPairs.has(pairKey(rowValue, columnValue))
|
|
209
|
+
}))
|
|
210
|
+
);
|
|
211
|
+
const selected = combinations === "full"
|
|
212
|
+
? candidates
|
|
213
|
+
: candidates.filter(cell => !cell.empty);
|
|
214
|
+
if (selected.length === 0) {
|
|
215
|
+
throw new Error("facetGrid has no observed row and column combinations.");
|
|
216
|
+
}
|
|
217
|
+
validateGeneratedItemLimit(
|
|
218
|
+
selected.length,
|
|
219
|
+
"Facet grid child count",
|
|
220
|
+
MAX_FACET_CHILDREN
|
|
221
|
+
);
|
|
222
|
+
validateWorkLimit(
|
|
223
|
+
dataset.values.length * selected.length,
|
|
224
|
+
"Facet grid partition work"
|
|
225
|
+
);
|
|
226
|
+
return cloneAndFreeze({
|
|
227
|
+
id,
|
|
228
|
+
data,
|
|
229
|
+
dependencies,
|
|
230
|
+
grid: {
|
|
231
|
+
rows: { field: rows.field, values: rowValues },
|
|
232
|
+
columns: { field: columns.field, values: columnValues },
|
|
233
|
+
combinations
|
|
234
|
+
},
|
|
235
|
+
cells: selected.map(cell => {
|
|
236
|
+
const cellId = `${id}-row-${cell.row + 1}-column-${cell.column + 1}`;
|
|
237
|
+
return {
|
|
238
|
+
...cell,
|
|
239
|
+
id: cellId,
|
|
240
|
+
data: `${cellId}-data`,
|
|
241
|
+
value: `${displayValue(cell.rowValue)} · ${displayValue(cell.columnValue)}`,
|
|
242
|
+
filters: [
|
|
243
|
+
{ field: rows.field, value: cell.rowValue },
|
|
244
|
+
{ field: columns.field, value: cell.columnValue }
|
|
245
|
+
]
|
|
246
|
+
};
|
|
247
|
+
})
|
|
248
|
+
});
|
|
249
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
|
|
3
|
+
const TRANSFORM_KEYS = Object.freeze(["type", "fields", "as"]);
|
|
4
|
+
const AS_KEYS = Object.freeze(["key", "value"]);
|
|
5
|
+
const MAX_FIELDS = 64;
|
|
6
|
+
const MAX_OUTPUT_ROWS = 10_000;
|
|
7
|
+
|
|
8
|
+
function rejectUnknownKeys(value, supported, label) {
|
|
9
|
+
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
10
|
+
if (unknown !== undefined) {
|
|
11
|
+
throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function requireField(value, label) {
|
|
16
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
17
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeAs(value) {
|
|
23
|
+
const requested = value ?? {};
|
|
24
|
+
if (!isPlainObject(requested)) return requested;
|
|
25
|
+
return {
|
|
26
|
+
...requested,
|
|
27
|
+
key: requested.key ?? "key",
|
|
28
|
+
value: requested.value ?? "value"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function normalizeFoldTransform({ fields, as } = {}) {
|
|
33
|
+
const transform = {
|
|
34
|
+
type: "fold",
|
|
35
|
+
fields: Array.isArray(fields) ? [...fields] : fields,
|
|
36
|
+
as: normalizeAs(as)
|
|
37
|
+
};
|
|
38
|
+
validateFoldTransform(transform);
|
|
39
|
+
return cloneAndFreeze(transform);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function validateFoldTransform(transform) {
|
|
43
|
+
if (!isPlainObject(transform)) {
|
|
44
|
+
throw new TypeError("Fold transform must be a plain object.");
|
|
45
|
+
}
|
|
46
|
+
rejectUnknownKeys(transform, TRANSFORM_KEYS, "fold transform");
|
|
47
|
+
if (transform.type !== "fold") {
|
|
48
|
+
throw new Error(`Unsupported fold transform "${transform.type}".`);
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(transform.fields) || transform.fields.length === 0) {
|
|
51
|
+
throw new TypeError("Fold fields must be a non-empty array.");
|
|
52
|
+
}
|
|
53
|
+
if (transform.fields.length > MAX_FIELDS) {
|
|
54
|
+
throw new RangeError(`Fold fields cannot contain more than ${MAX_FIELDS} entries.`);
|
|
55
|
+
}
|
|
56
|
+
transform.fields.forEach((field, index) => {
|
|
57
|
+
requireField(field, `Fold fields[${index}]`);
|
|
58
|
+
});
|
|
59
|
+
if (new Set(transform.fields).size !== transform.fields.length) {
|
|
60
|
+
throw new Error("Fold fields must be unique.");
|
|
61
|
+
}
|
|
62
|
+
if (!isPlainObject(transform.as)) {
|
|
63
|
+
throw new TypeError("Fold as must be a plain object.");
|
|
64
|
+
}
|
|
65
|
+
rejectUnknownKeys(transform.as, AS_KEYS, "fold as");
|
|
66
|
+
const key = requireField(transform.as.key, "Fold as.key");
|
|
67
|
+
const value = requireField(transform.as.value, "Fold as.value");
|
|
68
|
+
if (key === value) {
|
|
69
|
+
throw new Error("Fold output fields must be unique.");
|
|
70
|
+
}
|
|
71
|
+
return transform;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function valueType(value, field, row) {
|
|
75
|
+
if (value === null || value === undefined) {
|
|
76
|
+
throw new TypeError(`Fold field "${field}" is missing at row ${row}.`);
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === "number") {
|
|
79
|
+
if (!Number.isFinite(value)) {
|
|
80
|
+
throw new TypeError(`Fold field "${field}" must contain finite numbers.`);
|
|
81
|
+
}
|
|
82
|
+
return "number";
|
|
83
|
+
}
|
|
84
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
85
|
+
return typeof value;
|
|
86
|
+
}
|
|
87
|
+
throw new TypeError(
|
|
88
|
+
`Fold field "${field}" must contain finite numbers, strings, or booleans.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function deriveFoldRows(rows, transform) {
|
|
93
|
+
validateFoldTransform(transform);
|
|
94
|
+
if (rows.length * transform.fields.length > MAX_OUTPUT_ROWS) {
|
|
95
|
+
throw new RangeError(`Fold output cannot exceed ${MAX_OUTPUT_ROWS} rows.`);
|
|
96
|
+
}
|
|
97
|
+
const sourceFields = new Set(rows.flatMap(row => Object.keys(row)));
|
|
98
|
+
for (const output of [transform.as.key, transform.as.value]) {
|
|
99
|
+
if (sourceFields.has(output)) {
|
|
100
|
+
throw new Error(`Fold output field "${output}" already exists.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let expectedType;
|
|
104
|
+
const output = [];
|
|
105
|
+
rows.forEach((row, rowIndex) => {
|
|
106
|
+
for (const field of transform.fields) {
|
|
107
|
+
if (!Object.hasOwn(row, field)) {
|
|
108
|
+
throw new Error(`Fold source does not contain field "${field}" at row ${rowIndex}.`);
|
|
109
|
+
}
|
|
110
|
+
const type = valueType(row[field], field, rowIndex);
|
|
111
|
+
if (expectedType === undefined) expectedType = type;
|
|
112
|
+
else if (type !== expectedType) {
|
|
113
|
+
throw new TypeError("Fold fields must contain one common primitive type.");
|
|
114
|
+
}
|
|
115
|
+
output.push({
|
|
116
|
+
...row,
|
|
117
|
+
[transform.as.key]: field,
|
|
118
|
+
[transform.as.value]: row[field]
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return output;
|
|
123
|
+
}
|
package/src/grammar/horizon.js
CHANGED
|
@@ -8,14 +8,15 @@ import { interpolateNumber, inverseLerp } from "./numeric.js";
|
|
|
8
8
|
import { requireFiniteResult } from "./numeric.js";
|
|
9
9
|
import {
|
|
10
10
|
isNominalValue,
|
|
11
|
-
normalizeTemporalValue
|
|
11
|
+
normalizeTemporalValue,
|
|
12
|
+
resolveTemporalUnit
|
|
12
13
|
} from "./scales/index.js";
|
|
13
14
|
|
|
14
15
|
export const HORIZON_RESOLUTIONS = Object.freeze(["shared", "independent"]);
|
|
15
16
|
export const HORIZON_MISSING_POLICIES = Object.freeze(["break", "error"]);
|
|
16
17
|
export const HORIZON_OVERFLOW_POLICIES = Object.freeze(["clip", "error"]);
|
|
17
18
|
|
|
18
|
-
const ENCODING_PROPERTIES = Object.freeze(["field", "fieldType"]);
|
|
19
|
+
const ENCODING_PROPERTIES = Object.freeze(["field", "fieldType", "temporalUnit"]);
|
|
19
20
|
const OUTPUT_PROPERTIES = Object.freeze([
|
|
20
21
|
"x", "lower", "upper", "group", "color", "sign", "band", "segment"
|
|
21
22
|
]);
|
|
@@ -41,7 +42,8 @@ function validateEncoding(value, role) {
|
|
|
41
42
|
`Horizon ${role} fieldType must be quantitative${supportsTemporal ? " or temporal" : ""}.`
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
const temporalUnit = resolveTemporalUnit(value, fieldType);
|
|
46
|
+
return { field, fieldType, ...(temporalUnit === undefined ? {} : { temporalUnit }) };
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
function validateOutputFields(value) {
|
|
@@ -183,7 +185,7 @@ export function validateHorizonTransform(transform) {
|
|
|
183
185
|
|
|
184
186
|
function orderedX(value, encoding, rowIndex) {
|
|
185
187
|
if (encoding.fieldType === "temporal") {
|
|
186
|
-
return normalizeTemporalValue(value, encoding.field, rowIndex);
|
|
188
|
+
return normalizeTemporalValue(value, encoding.field, rowIndex, encoding.temporalUnit);
|
|
187
189
|
}
|
|
188
190
|
if (!Number.isFinite(value)) {
|
|
189
191
|
throw new TypeError(
|
package/src/grammar/interval.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { aggregateScalarValues } from "./aggregate.js";
|
|
3
3
|
import { requireFiniteResult, stableDecimal } from "./numeric.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
confidenceCriticalValue,
|
|
6
|
+
normalizeConfidenceInterval
|
|
7
|
+
} from "./statistics/confidenceInterval.js";
|
|
5
8
|
|
|
6
9
|
const CENTER_VALUES = ["mean", "median"];
|
|
7
10
|
const EXTENT_VALUES = ["stderr", "stdev", "ci", "iqr"];
|
|
@@ -11,6 +14,7 @@ const TRANSFORM_KEYS = [
|
|
|
11
14
|
"groupBy",
|
|
12
15
|
"center",
|
|
13
16
|
"extent",
|
|
17
|
+
"method",
|
|
14
18
|
"level",
|
|
15
19
|
"as"
|
|
16
20
|
];
|
|
@@ -84,13 +88,12 @@ export function validateIntervalTransform(transform) {
|
|
|
84
88
|
throw new Error("Median intervals require iqr, and iqr requires median.");
|
|
85
89
|
}
|
|
86
90
|
if (transform.extent === "ci") {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
91
|
+
normalizeConfidenceInterval(transform, {
|
|
92
|
+
defaultMethod: "student-t",
|
|
93
|
+
label: "Interval CI"
|
|
94
|
+
});
|
|
95
|
+
} else if (transform.method !== undefined) {
|
|
96
|
+
throw new Error("Interval method is supported only for ci extent.");
|
|
94
97
|
} else if (transform.level !== undefined) {
|
|
95
98
|
throw new Error("Interval level is supported only for ci extent.");
|
|
96
99
|
}
|
|
@@ -111,16 +114,27 @@ function normalizeGrouping(groupBy) {
|
|
|
111
114
|
export function normalizeIntervalParameters({
|
|
112
115
|
center = "mean",
|
|
113
116
|
extent = "ci",
|
|
117
|
+
method,
|
|
114
118
|
level
|
|
115
119
|
} = {}) {
|
|
116
|
-
const
|
|
120
|
+
const confidence = extent === "ci"
|
|
121
|
+
? normalizeConfidenceInterval({ method, level }, {
|
|
122
|
+
defaultMethod: "student-t",
|
|
123
|
+
label: "Interval CI"
|
|
124
|
+
})
|
|
125
|
+
: undefined;
|
|
117
126
|
const candidate = {
|
|
118
127
|
type: "interval",
|
|
119
128
|
field: "__interval_input",
|
|
120
129
|
groupBy: [],
|
|
121
130
|
center,
|
|
122
131
|
extent,
|
|
123
|
-
...(
|
|
132
|
+
...(confidence === undefined
|
|
133
|
+
? {
|
|
134
|
+
...(method === undefined ? {} : { method }),
|
|
135
|
+
...(level === undefined ? {} : { level })
|
|
136
|
+
}
|
|
137
|
+
: confidence),
|
|
124
138
|
as: {
|
|
125
139
|
center: "__interval_center",
|
|
126
140
|
lower: "__interval_lower",
|
|
@@ -131,18 +145,16 @@ export function normalizeIntervalParameters({
|
|
|
131
145
|
return cloneAndFreeze({
|
|
132
146
|
center,
|
|
133
147
|
extent,
|
|
134
|
-
...(
|
|
148
|
+
...(confidence === undefined ? {} : confidence)
|
|
135
149
|
});
|
|
136
150
|
}
|
|
137
151
|
|
|
138
152
|
export function studentTCritical(degreesOfFreedom, level) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
return studentTCriticalValue(level, degreesOfFreedom);
|
|
153
|
+
return confidenceCriticalValue({
|
|
154
|
+
method: "student-t",
|
|
155
|
+
level,
|
|
156
|
+
degreesOfFreedom
|
|
157
|
+
});
|
|
146
158
|
}
|
|
147
159
|
|
|
148
160
|
function isMissing(value) {
|
|
@@ -170,7 +182,11 @@ function deriveGroup(values, transform) {
|
|
|
170
182
|
? aggregateScalarValues(values, "stdev")
|
|
171
183
|
: transform.extent === "stderr"
|
|
172
184
|
? aggregateScalarValues(values, "stderr")
|
|
173
|
-
:
|
|
185
|
+
: confidenceCriticalValue({
|
|
186
|
+
method: transform.method,
|
|
187
|
+
level: transform.level,
|
|
188
|
+
degreesOfFreedom: values.length - 1
|
|
189
|
+
}) *
|
|
174
190
|
aggregateScalarValues(values, "stderr");
|
|
175
191
|
return {
|
|
176
192
|
center,
|
|
@@ -232,12 +248,13 @@ export function normalizeIntervalTransform({
|
|
|
232
248
|
groupBy,
|
|
233
249
|
center,
|
|
234
250
|
extent,
|
|
251
|
+
method,
|
|
235
252
|
level,
|
|
236
253
|
as
|
|
237
254
|
}) {
|
|
238
255
|
nonEmptyString(field, "Interval field");
|
|
239
256
|
const grouping = normalizeGrouping(groupBy);
|
|
240
|
-
const parameters = normalizeIntervalParameters({ center, extent, level });
|
|
257
|
+
const parameters = normalizeIntervalParameters({ center, extent, method, level });
|
|
241
258
|
const transform = {
|
|
242
259
|
type: "interval",
|
|
243
260
|
field,
|