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.
- package/CHANGELOG.md +80 -0
- package/README.md +3 -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 +33 -27
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +40 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +37 -2
- 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 +79 -24
- 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 +153 -11
- package/src/actions/errorBars/edit.js +365 -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 +138 -14
- 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 +60 -4
- 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/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +213 -65
- 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/positionCompatibility.js +1 -1
- 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 +9 -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/ordinal.js +23 -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 +86 -23
- 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/facetGuides/legacyCategorical.js +7 -1
- package/src/materialization/facetGuides/placement.js +16 -1
- package/src/materialization/facetGuides/preparation.js +26 -1
- package/src/materialization/facets.js +11 -2
- 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 +30 -7
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +63 -25
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +70 -14
- 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/bar.js +0 -43
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +48 -18
- 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 +1327 -124
- 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
|
|
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"
|
|
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 <
|
|
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
|
|
217
|
+
: "compositionSpec.children requires at least one child ID."
|
|
90
218
|
);
|
|
91
219
|
}
|
|
92
220
|
if (new Set(compositionSpec.children).size !== compositionSpec.children.length) {
|
package/src/core/vocabulary.js
CHANGED
|
@@ -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",
|
package/src/grammar/aggregate.js
CHANGED
|
@@ -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 = [
|
|
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
|
-
|
|
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 (
|
|
228
|
+
if (scalarOperation === "count") return values.length;
|
|
203
229
|
const valid = values.filter(value => !isMissing(value));
|
|
204
|
-
if (
|
|
205
|
-
if (
|
|
206
|
-
if (
|
|
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 (
|
|
236
|
+
if (scalarOperation === "sum") {
|
|
211
237
|
return stableFiniteSum(finite, "Aggregate sum");
|
|
212
238
|
}
|
|
213
|
-
if (
|
|
214
|
-
if (
|
|
215
|
-
if (
|
|
216
|
-
if (
|
|
217
|
-
if (
|
|
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 (
|
|
221
|
-
if (["variance", "varianceP", "stdev", "stdevP"].includes(
|
|
222
|
-
const population =
|
|
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 (
|
|
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 (
|
|
244
|
-
|
|
245
|
-
|
|
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 (
|
|
248
|
-
return requireFiniteResult(mean +
|
|
284
|
+
if (scalarOperation === "ciUpper") {
|
|
285
|
+
return requireFiniteResult(mean + critical * stderr, "Aggregate upper CI");
|
|
249
286
|
}
|
|
250
|
-
throw new Error(`Unsupported scalar aggregate "${
|
|
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
|
}
|