ggaction 0.0.13 → 0.0.16
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 +146 -0
- package/README.md +41 -12
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-packet.schema.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +39 -7
- package/src/BasicChartProgram.js +3 -1
- package/src/ChartProgram.js +2 -0
- package/src/accessibility.js +157 -0
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +20 -12
- package/src/actions/boxPlots/create.js +4 -5
- package/src/actions/boxPlots/edit.js +34 -9
- package/src/actions/boxPlots/materialize.js +12 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -3
- package/src/actions/canvas/fitting.js +1 -1
- package/src/actions/categoryOrder/index.js +21 -12
- package/src/actions/charts/area.js +6 -2
- package/src/actions/charts/bar.js +3 -2
- package/src/actions/charts/beeswarm.js +1 -1
- package/src/actions/charts/density.js +7 -3
- package/src/actions/charts/ecdf.js +6 -3
- package/src/actions/charts/endpoints.js +29 -7
- package/src/actions/charts/heatmap.js +3 -2
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +4 -2
- package/src/actions/charts/interval-regression.js +7 -4
- package/src/actions/charts/line.js +4 -2
- package/src/actions/charts/parallel.js +9 -3
- package/src/actions/charts/pie.js +11 -3
- package/src/actions/charts/polar.js +17 -6
- package/src/actions/charts/radar.js +9 -3
- package/src/actions/charts/radial.js +12 -4
- package/src/actions/charts/raincloud.js +2 -2
- package/src/actions/charts/rug-strip.js +8 -4
- package/src/actions/charts/scatter.js +4 -2
- package/src/actions/composition/actions.js +29 -24
- package/src/actions/coordinates/actions.js +2 -2
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/basicBin2d.js +4 -5
- package/src/actions/data/bin.js +3 -3
- package/src/actions/data/bin2d.js +63 -193
- package/src/actions/data/bin2dMaterialize.js +4 -5
- package/src/actions/data/box.js +4 -4
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +4 -4
- package/src/actions/data/create.js +7 -14
- package/src/actions/data/density.js +15 -23
- package/src/actions/data/derived.js +57 -31
- package/src/actions/data/ecdf.js +2 -2
- package/src/actions/data/edit.js +551 -0
- package/src/actions/data/filter.js +28 -22
- package/src/actions/data/fold.js +2 -2
- package/src/actions/data/gradientProfile.js +2 -2
- package/src/actions/data/horizon.js +2 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +62 -47
- package/src/actions/data/interval.js +2 -2
- package/src/actions/data/intervalEdit.js +26 -18
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +21 -27
- package/src/actions/data/revise.js +48 -0
- package/src/actions/data/revisionBindings.js +125 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +2 -2
- package/src/actions/data/summary.js +3 -3
- package/src/actions/data/timeUnit.js +6 -3
- package/src/actions/data/window.js +3 -3
- package/src/actions/encodings/angle.js +1 -1
- package/src/actions/encodings/appearance.js +4 -4
- package/src/actions/encodings/barWidth.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +3 -2
- package/src/actions/encodings/density.js +7 -4
- package/src/actions/encodings/histogram.js +4 -3
- package/src/actions/encodings/horizon.js +6 -8
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +22 -15
- package/src/actions/encodings/parallel.js +4 -5
- package/src/actions/encodings/pathOrder.js +6 -11
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +14 -6
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/ranged.js +5 -5
- package/src/actions/encodings/remove.js +13 -9
- package/src/actions/encodings/ruleAppearance.js +2 -24
- package/src/actions/encodings/seriesLayout.js +2 -1
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +2 -2
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/create.js +34 -20
- package/src/actions/errorBands/edit.js +34 -137
- package/src/actions/errorBars/create.js +43 -20
- package/src/actions/errorBars/edit.js +29 -125
- package/src/actions/facets/actions.js +267 -73
- package/src/actions/facets/derive.js +99 -28
- package/src/actions/facets/guides.js +4 -5
- package/src/actions/facets/replay.js +4 -5
- package/src/actions/gradientPlots/components.js +15 -8
- package/src/actions/gradientPlots/create.js +4 -5
- package/src/actions/gradientPlots/edit.js +18 -9
- package/src/actions/gradientPlots/materialize.js +5 -3
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/gradientPlots/rebind.js +4 -5
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/axes.js +23 -4
- package/src/actions/guides/axes/axis.js +2 -2
- package/src/actions/guides/axes/edit.js +2 -2
- package/src/actions/guides/axes/labels.js +67 -21
- package/src/actions/guides/axes/lines.js +15 -9
- package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/parallel.js +5 -5
- package/src/actions/guides/axes/remove.js +4 -4
- package/src/actions/guides/axes/tickGroups.js +6 -5
- package/src/actions/guides/axes/ticks.js +19 -6
- package/src/actions/guides/axes/titles.js +21 -13
- package/src/actions/guides/grids/grid.js +18 -29
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +20 -4
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +138 -52
- package/src/actions/guides/legends/categorical/components.js +55 -45
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +98 -16
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +104 -52
- package/src/actions/guides/legends/continuous/common.js +76 -20
- package/src/actions/guides/legends/continuous/gradient.js +95 -158
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +21 -16
- package/src/actions/guides/legends/continuous/opacity.js +37 -83
- package/src/actions/guides/legends/continuous/stroke.js +225 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +159 -60
- package/src/actions/guides/legends/focused.js +5 -5
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +59 -24
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +17 -8
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +100 -108
- package/src/actions/guides/legends/strokeWidth.js +38 -92
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +8 -8
- package/src/actions/guides/polar/axes/labels.js +29 -10
- package/src/actions/guides/polar/axes/lines.js +19 -19
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +10 -16
- package/src/actions/guides/polar/axes/titles.js +6 -6
- package/src/actions/guides/polar/grids.js +12 -14
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +16 -3
- package/src/actions/intervals/revision.js +126 -0
- package/src/actions/marks/arc/actions.js +80 -22
- package/src/actions/marks/area/actions.js +38 -13
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +12 -3
- package/src/actions/marks/bar/edit.js +21 -4
- package/src/actions/marks/bar/materialize.js +34 -40
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +40 -17
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +9 -3
- package/src/actions/marks/point/edit.js +19 -7
- package/src/actions/marks/point/jitter.js +6 -8
- package/src/actions/marks/point/materialize.js +48 -22
- package/src/actions/marks/point/packing.js +6 -8
- package/src/actions/marks/rect/actions.js +36 -15
- package/src/actions/marks/references.js +602 -33
- package/src/actions/marks/remove.js +67 -41
- package/src/actions/marks/rule/actions.js +51 -14
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +79 -14
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +157 -17
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +72 -0
- package/src/actions/marks/tick/actions.js +25 -8
- package/src/actions/primitives/createGraphics.js +5 -4
- package/src/actions/primitives/editGraphics.js +31 -4
- package/src/actions/primitives/graphicProperties.js +6 -0
- package/src/actions/primitives/semantic.js +11 -3
- package/src/actions/primitives/semanticAction.js +90 -14
- package/src/actions/primitives/semanticValidation/dataset.js +2 -4
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +36 -25
- package/src/actions/regression/create.js +13 -6
- package/src/actions/regression/edit.js +24 -11
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +173 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +55 -23
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +67 -12
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +5 -4
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/scales/quantitativeColor.js +4 -5
- package/src/actions/selection/actions.js +179 -66
- package/src/actions/selection/index.js +3 -0
- package/src/actions/textMetrics/index.js +56 -0
- package/src/actions/theme/actions.js +70 -15
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +313 -121
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +8 -8
- package/src/actions/titles/resolve.js +3 -3
- package/src/actions/violinPlots/create.js +6 -3
- package/src/actions/violinPlots/edit.js +2 -1
- package/src/core/action.js +117 -35
- package/src/core/compositionState.js +6 -1
- package/src/core/diagnostics.js +16 -0
- package/src/core/extensionRegistry.js +27 -0
- package/src/core/font.js +18 -0
- package/src/core/identifiers.js +5 -4
- package/src/core/immutable.js +10 -3
- package/src/core/optionRequirements.js +8 -0
- package/src/core/programState.js +21 -1
- package/src/core/resourceReferences.js +341 -0
- package/src/core/textMetricProfile.js +28 -0
- package/src/core/textMetrics.js +24 -3
- package/src/core/validation.js +22 -10
- package/src/core/vocabulary.js +16 -3
- package/src/diagnostics.js +1 -0
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +341 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +8 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +190 -41
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +351 -7
- package/src/layout/legend.js +4 -4
- package/src/layout/legendItems.js +6 -6
- package/src/layout/text.js +10 -9
- package/src/layout/title.js +9 -9
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +9 -3
- package/src/materialization/facetGuides/preparation.js +13 -8
- package/src/materialization/facetHeaders.js +347 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/layout.js +3 -3
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scaleGuideDependencies.js +10 -0
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +20 -5
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/materialization/typography.js +31 -0
- package/src/mcp/cli.js +5 -3
- package/src/persistence/codec.js +65 -0
- package/src/persistence/validation.js +185 -0
- package/src/persistence.js +74 -0
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/index.js +5 -4
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/nativeBackend.js +10 -0
- package/src/renderers/options.js +13 -0
- package/src/renderers/pdf.js +25 -54
- package/src/renderers/png.js +22 -17
- package/src/renderers/svg.js +29 -14
- package/src/renderers/text.js +1 -11
- package/src/selectors/coordinates.js +3 -1
- package/src/selectors/datasets.js +38 -5
- package/src/selectors/layers.js +10 -4
- package/src/selectors/markOwners.js +46 -0
- package/src/selectors/scales.js +6 -4
- package/src/theme/defaults.js +103 -1
- package/src/version.js +2 -0
- package/types/accessibility.d.ts +37 -0
- package/types/basic.d.ts +10 -2
- package/types/diagnostics.d.ts +15 -0
- package/types/index.d.ts +90 -0
- package/types/pdf.d.ts +9 -0
- package/types/persistence.d.ts +7 -0
- package/types/png.d.ts +9 -0
- package/types/program.d.ts +944 -118
package/src/grammar/summary.js
CHANGED
|
@@ -5,9 +5,18 @@ import {
|
|
|
5
5
|
validateAggregate,
|
|
6
6
|
validateAggregateFieldValues
|
|
7
7
|
} from "./aggregate.js";
|
|
8
|
+
import {
|
|
9
|
+
calculateWeightedAggregate,
|
|
10
|
+
normalizeStatisticalWeight,
|
|
11
|
+
readStatisticalWeights,
|
|
12
|
+
summarizeStatisticalWeights,
|
|
13
|
+
validateWeightedAggregate,
|
|
14
|
+
validateWeightedNumericFields,
|
|
15
|
+
weightedRows
|
|
16
|
+
} from "./weightedStatistics.js";
|
|
8
17
|
|
|
9
18
|
const TRANSFORM_KEYS = Object.freeze([
|
|
10
|
-
"type", "groupBy", "aggregates", "members"
|
|
19
|
+
"type", "groupBy", "aggregates", "members", "weight"
|
|
11
20
|
]);
|
|
12
21
|
const AGGREGATE_KEYS = Object.freeze(["op", "field", "as"]);
|
|
13
22
|
const NOMINAL_OPERATIONS = new Set(["distinct", "valid", "missing"]);
|
|
@@ -42,14 +51,17 @@ function normalizeAggregate(value) {
|
|
|
42
51
|
};
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
export function normalizeSummaryTransform({ groupBy, aggregates, members } = {}) {
|
|
54
|
+
export function normalizeSummaryTransform({ groupBy, aggregates, members, weight } = {}) {
|
|
46
55
|
const transform = {
|
|
47
56
|
type: "summary",
|
|
48
57
|
groupBy: normalizeGroupBy(groupBy),
|
|
49
58
|
aggregates: Array.isArray(aggregates)
|
|
50
59
|
? aggregates.map(normalizeAggregate)
|
|
51
60
|
: aggregates,
|
|
52
|
-
...(members === undefined ? {} : { members })
|
|
61
|
+
...(members === undefined ? {} : { members }),
|
|
62
|
+
...(weight === undefined
|
|
63
|
+
? {}
|
|
64
|
+
: { weight: normalizeStatisticalWeight(weight, "Summary weight") })
|
|
53
65
|
};
|
|
54
66
|
validateSummaryTransform(transform);
|
|
55
67
|
return cloneAndFreeze(transform);
|
|
@@ -85,6 +97,7 @@ export function validateSummaryTransform(transform) {
|
|
|
85
97
|
}
|
|
86
98
|
rejectUnknownKeys(aggregate, AGGREGATE_KEYS, `summary aggregate ${index}`);
|
|
87
99
|
const operation = validateAggregate(aggregate.op);
|
|
100
|
+
if (transform.weight !== undefined) validateWeightedAggregate(operation);
|
|
88
101
|
if (operation === "count") {
|
|
89
102
|
if (Object.hasOwn(aggregate, "field")) {
|
|
90
103
|
throw new Error("Summary count does not accept a field.");
|
|
@@ -104,6 +117,9 @@ export function validateSummaryTransform(transform) {
|
|
|
104
117
|
throw new Error(`Summary members field "${members}" collides with another output.`);
|
|
105
118
|
}
|
|
106
119
|
}
|
|
120
|
+
if (transform.weight !== undefined) {
|
|
121
|
+
normalizeStatisticalWeight(transform.weight, "Summary weight");
|
|
122
|
+
}
|
|
107
123
|
return transform;
|
|
108
124
|
}
|
|
109
125
|
|
|
@@ -136,6 +152,9 @@ function requireSourceFields(rows, transform) {
|
|
|
136
152
|
throw new Error(`Summary source does not contain order field "${orderBy}".`);
|
|
137
153
|
}
|
|
138
154
|
}
|
|
155
|
+
if (transform.weight !== undefined && !fields.has(transform.weight.field)) {
|
|
156
|
+
throw new Error(`Summary source does not contain weight field "${transform.weight.field}".`);
|
|
157
|
+
}
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
function validateAggregateValues(rows, aggregate) {
|
|
@@ -150,21 +169,42 @@ function validateAggregateValues(rows, aggregate) {
|
|
|
150
169
|
export function deriveSummaryRows(rows, transform) {
|
|
151
170
|
validateSummaryTransform(transform);
|
|
152
171
|
requireSourceFields(rows, transform);
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
let weightEntries;
|
|
173
|
+
if (transform.weight === undefined) {
|
|
174
|
+
for (const aggregate of transform.aggregates) {
|
|
175
|
+
validateAggregateValues(rows, aggregate);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
weightEntries = readStatisticalWeights(rows, transform.weight, "Summary").entries;
|
|
179
|
+
validateWeightedNumericFields(
|
|
180
|
+
weightEntries,
|
|
181
|
+
[...new Set(transform.aggregates.flatMap(aggregate =>
|
|
182
|
+
aggregate.field === undefined ? [] : [aggregate.field]
|
|
183
|
+
))],
|
|
184
|
+
"Summary"
|
|
185
|
+
);
|
|
155
186
|
}
|
|
156
187
|
|
|
157
188
|
const groups = new Map();
|
|
158
189
|
if (transform.groupBy.length === 0) {
|
|
159
|
-
groups.set("all", {
|
|
190
|
+
groups.set("all", {
|
|
191
|
+
values: {},
|
|
192
|
+
rows,
|
|
193
|
+
...(weightEntries === undefined ? {} : { entries: weightEntries })
|
|
194
|
+
});
|
|
160
195
|
} else {
|
|
161
|
-
rows.forEach(row => {
|
|
196
|
+
rows.forEach((row, index) => {
|
|
162
197
|
const values = Object.fromEntries(transform.groupBy.map(field => [field, row[field]]));
|
|
163
198
|
const key = transform.groupBy.map(field =>
|
|
164
199
|
scalarKey(row[field], `Summary group field "${field}"`)
|
|
165
200
|
).join("|");
|
|
166
|
-
const group = groups.get(key) ?? {
|
|
201
|
+
const group = groups.get(key) ?? {
|
|
202
|
+
values,
|
|
203
|
+
rows: [],
|
|
204
|
+
...(weightEntries === undefined ? {} : { entries: [] })
|
|
205
|
+
};
|
|
167
206
|
group.rows.push(row);
|
|
207
|
+
if (weightEntries !== undefined) group.entries.push(weightEntries[index]);
|
|
168
208
|
groups.set(key, group);
|
|
169
209
|
});
|
|
170
210
|
}
|
|
@@ -172,12 +212,34 @@ export function deriveSummaryRows(rows, transform) {
|
|
|
172
212
|
throw new RangeError(`Summary output cannot exceed ${MAX_OUTPUT_ROWS} groups.`);
|
|
173
213
|
}
|
|
174
214
|
|
|
175
|
-
return [...groups.values()].map(group =>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
215
|
+
return [...groups.values()].map(group => {
|
|
216
|
+
const weightSummary = transform.weight === undefined
|
|
217
|
+
? undefined
|
|
218
|
+
: summarizeStatisticalWeights(
|
|
219
|
+
group.entries,
|
|
220
|
+
transform.weight.kind,
|
|
221
|
+
"Summary group"
|
|
222
|
+
);
|
|
223
|
+
return {
|
|
224
|
+
...group.values,
|
|
225
|
+
...Object.fromEntries(transform.aggregates.map(aggregate => [
|
|
226
|
+
aggregate.as,
|
|
227
|
+
weightSummary === undefined
|
|
228
|
+
? aggregateRows(group.rows, aggregate.field ?? "__row", aggregate.op)
|
|
229
|
+
: calculateWeightedAggregate(
|
|
230
|
+
weightSummary,
|
|
231
|
+
aggregate.field,
|
|
232
|
+
aggregate.op,
|
|
233
|
+
`Summary aggregate "${aggregate.as}"`
|
|
234
|
+
)
|
|
235
|
+
])),
|
|
236
|
+
...(transform.members === undefined
|
|
237
|
+
? {}
|
|
238
|
+
: {
|
|
239
|
+
[transform.members]: weightSummary === undefined
|
|
240
|
+
? group.rows
|
|
241
|
+
: weightedRows(weightSummary)
|
|
242
|
+
})
|
|
243
|
+
};
|
|
244
|
+
});
|
|
183
245
|
}
|
package/src/grammar/timeUnit.js
CHANGED
|
@@ -33,10 +33,47 @@ export const TIME_UNITS = cloneAndFreeze([
|
|
|
33
33
|
"day",
|
|
34
34
|
"hour",
|
|
35
35
|
"minute",
|
|
36
|
-
"second"
|
|
36
|
+
"second",
|
|
37
|
+
"week",
|
|
38
|
+
"weekday"
|
|
37
39
|
]);
|
|
38
40
|
|
|
39
|
-
const TRANSFORM_KEYS = Object.freeze([
|
|
41
|
+
const TRANSFORM_KEYS = Object.freeze([
|
|
42
|
+
"type", "field", "unit", "as", "temporalUnit", "timeZone",
|
|
43
|
+
"weekStartsOn", "weekRule"
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const CALENDAR_FORMATTERS = new Map();
|
|
47
|
+
|
|
48
|
+
function formatter(timeZone) {
|
|
49
|
+
if (!CALENDAR_FORMATTERS.has(timeZone)) {
|
|
50
|
+
let value;
|
|
51
|
+
try {
|
|
52
|
+
value = new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
|
|
53
|
+
timeZone,
|
|
54
|
+
year: "numeric",
|
|
55
|
+
month: "2-digit",
|
|
56
|
+
day: "2-digit",
|
|
57
|
+
hour: "2-digit",
|
|
58
|
+
minute: "2-digit",
|
|
59
|
+
second: "2-digit",
|
|
60
|
+
hourCycle: "h23"
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
throw new RangeError(`Unsupported time zone "${timeZone}".`);
|
|
64
|
+
}
|
|
65
|
+
CALENDAR_FORMATTERS.set(timeZone, value);
|
|
66
|
+
}
|
|
67
|
+
return CALENDAR_FORMATTERS.get(timeZone);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateTimeZone(timeZone) {
|
|
71
|
+
if (typeof timeZone !== "string" || timeZone.length === 0) {
|
|
72
|
+
throw new TypeError("Time-unit timeZone must be a non-empty IANA time-zone name.");
|
|
73
|
+
}
|
|
74
|
+
formatter(timeZone);
|
|
75
|
+
return timeZone;
|
|
76
|
+
}
|
|
40
77
|
|
|
41
78
|
function requireField(value, label) {
|
|
42
79
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -69,12 +106,38 @@ export function validateTimeUnitTransform(transform) {
|
|
|
69
106
|
if (!TIME_UNITS.includes(transform.unit)) {
|
|
70
107
|
throw new Error(`Unsupported time unit "${transform.unit}".`);
|
|
71
108
|
}
|
|
109
|
+
if (Object.hasOwn(transform, "timeZone")) validateTimeZone(transform.timeZone);
|
|
110
|
+
const hasWeekStartsOn = Object.hasOwn(transform, "weekStartsOn");
|
|
111
|
+
const hasWeekRule = Object.hasOwn(transform, "weekRule");
|
|
112
|
+
if (transform.unit === "week") {
|
|
113
|
+
if (!Number.isInteger(transform.weekStartsOn) ||
|
|
114
|
+
transform.weekStartsOn < 0 || transform.weekStartsOn > 6) {
|
|
115
|
+
throw new RangeError("Time-unit weekStartsOn must be an integer from 0 through 6.");
|
|
116
|
+
}
|
|
117
|
+
if (!["calendar", "iso"].includes(transform.weekRule)) {
|
|
118
|
+
throw new Error(`Unsupported time-unit weekRule "${transform.weekRule}".`);
|
|
119
|
+
}
|
|
120
|
+
if (transform.weekRule === "iso" && transform.weekStartsOn !== 1) {
|
|
121
|
+
throw new Error("Time-unit ISO weeks require weekStartsOn 1 (Monday).");
|
|
122
|
+
}
|
|
123
|
+
} else if (hasWeekStartsOn || hasWeekRule) {
|
|
124
|
+
throw new Error("Time-unit weekStartsOn and weekRule require unit week.");
|
|
125
|
+
}
|
|
72
126
|
return transform;
|
|
73
127
|
}
|
|
74
128
|
|
|
75
|
-
export function normalizeTimeUnitTransform(
|
|
129
|
+
export function normalizeTimeUnitTransform(args = {}) {
|
|
130
|
+
const { field, unit, as, temporalUnit, timeZone } = args;
|
|
76
131
|
const transform = { type: "timeUnit", field, unit, as,
|
|
77
|
-
...(temporalUnit === undefined ? {} : { temporalUnit })
|
|
132
|
+
...(temporalUnit === undefined ? {} : { temporalUnit }),
|
|
133
|
+
...(timeZone === undefined ? {} : { timeZone }) };
|
|
134
|
+
if (unit === "week") {
|
|
135
|
+
transform.weekStartsOn = args.weekStartsOn ?? 1;
|
|
136
|
+
transform.weekRule = args.weekRule ?? "calendar";
|
|
137
|
+
} else {
|
|
138
|
+
if (Object.hasOwn(args, "weekStartsOn")) transform.weekStartsOn = args.weekStartsOn;
|
|
139
|
+
if (Object.hasOwn(args, "weekRule")) transform.weekRule = args.weekRule;
|
|
140
|
+
}
|
|
78
141
|
validateTimeUnitTransform(transform);
|
|
79
142
|
return cloneAndFreeze(transform);
|
|
80
143
|
}
|
|
@@ -96,6 +159,10 @@ export function floorUtcTimeUnit(timestamp, unit) {
|
|
|
96
159
|
if (unit === "quarter") return utcBucket(year, Math.floor(month / 3) * 3);
|
|
97
160
|
if (unit === "month") return utcBucket(year, month);
|
|
98
161
|
const day = date.getUTCDate();
|
|
162
|
+
if (unit === "weekday") return date.getUTCDay();
|
|
163
|
+
if (unit === "week") {
|
|
164
|
+
throw new Error("floorUtcTimeUnit requires a calendar week policy.");
|
|
165
|
+
}
|
|
99
166
|
if (unit === "day") return utcBucket(year, month, day);
|
|
100
167
|
const hour = date.getUTCHours();
|
|
101
168
|
if (unit === "hour") return utcBucket(year, month, day, hour);
|
|
@@ -104,6 +171,150 @@ export function floorUtcTimeUnit(timestamp, unit) {
|
|
|
104
171
|
return Math.floor(timestamp / 1000) * 1000;
|
|
105
172
|
}
|
|
106
173
|
|
|
174
|
+
function weekdayFromDate(year, month, day) {
|
|
175
|
+
return new Date(utcTimestamp(year, month - 1, day)).getUTCDay();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function calendarParts(timestamp, timeZone) {
|
|
179
|
+
if (!validTimestamp(timestamp)) {
|
|
180
|
+
throw new TypeError("Time-unit timestamp must represent a valid date.");
|
|
181
|
+
}
|
|
182
|
+
validateTimeZone(timeZone);
|
|
183
|
+
const parts = Object.fromEntries(formatter(timeZone).formatToParts(timestamp)
|
|
184
|
+
.filter(part => part.type !== "literal")
|
|
185
|
+
.map(part => [part.type, Number(part.value)]));
|
|
186
|
+
const millisecond = ((timestamp % 1000) + 1000) % 1000;
|
|
187
|
+
return {
|
|
188
|
+
year: parts.year,
|
|
189
|
+
month: parts.month,
|
|
190
|
+
day: parts.day,
|
|
191
|
+
hour: parts.hour,
|
|
192
|
+
minute: parts.minute,
|
|
193
|
+
second: parts.second,
|
|
194
|
+
millisecond,
|
|
195
|
+
weekday: weekdayFromDate(parts.year, parts.month, parts.day)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function civilTimestamp(parts) {
|
|
200
|
+
const timestamp = utcTimestamp(
|
|
201
|
+
parts.year,
|
|
202
|
+
parts.month - 1,
|
|
203
|
+
parts.day,
|
|
204
|
+
parts.hour ?? 0,
|
|
205
|
+
parts.minute ?? 0
|
|
206
|
+
) + (parts.second ?? 0) * 1000 + (parts.millisecond ?? 0);
|
|
207
|
+
if (!validTimestamp(timestamp)) {
|
|
208
|
+
throw new RangeError("Time-unit calendar boundary is outside the supported Date range.");
|
|
209
|
+
}
|
|
210
|
+
return timestamp;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function sameCivil(left, right) {
|
|
214
|
+
return ["year", "month", "day", "hour", "minute", "second", "millisecond"]
|
|
215
|
+
.every(key => left[key] === right[key]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function sameBucket(candidate, target, unit) {
|
|
219
|
+
const keys = unit === "year" ? ["year"]
|
|
220
|
+
: unit === "quarter" ? ["year", "month"]
|
|
221
|
+
: unit === "month" ? ["year", "month"]
|
|
222
|
+
: unit === "day" || unit === "week" ? ["year", "month", "day"]
|
|
223
|
+
: unit === "hour" ? ["year", "month", "day", "hour"]
|
|
224
|
+
: unit === "minute" ? ["year", "month", "day", "hour", "minute"]
|
|
225
|
+
: ["year", "month", "day", "hour", "minute", "second"];
|
|
226
|
+
if (unit === "quarter") {
|
|
227
|
+
return candidate.year === target.year &&
|
|
228
|
+
Math.floor((candidate.month - 1) / 3) === Math.floor((target.month - 1) / 3);
|
|
229
|
+
}
|
|
230
|
+
return keys.every(key => candidate[key] === target[key]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function resolveCalendarBoundary(localParts, timeZone, unit) {
|
|
234
|
+
const naive = civilTimestamp(localParts);
|
|
235
|
+
const offsets = new Set();
|
|
236
|
+
for (let delta = -72; delta <= 72; delta += 6) {
|
|
237
|
+
const sample = naive + delta * 3_600_000;
|
|
238
|
+
if (!validTimestamp(sample)) continue;
|
|
239
|
+
offsets.add(civilTimestamp(calendarParts(sample, timeZone)) - sample);
|
|
240
|
+
}
|
|
241
|
+
const exact = [];
|
|
242
|
+
const gaps = [];
|
|
243
|
+
for (const offset of offsets) {
|
|
244
|
+
const candidate = naive - offset;
|
|
245
|
+
if (!validTimestamp(candidate)) continue;
|
|
246
|
+
const roundTrip = calendarParts(candidate, timeZone);
|
|
247
|
+
if (sameCivil(roundTrip, localParts)) exact.push(candidate);
|
|
248
|
+
else if (sameBucket(roundTrip, localParts, unit) &&
|
|
249
|
+
civilTimestamp(roundTrip) >= naive) {
|
|
250
|
+
gaps.push({ candidate, local: civilTimestamp(roundTrip) });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (exact.length > 0) return Math.min(...exact);
|
|
254
|
+
if (gaps.length > 0) {
|
|
255
|
+
gaps.sort((left, right) => left.local - right.local || left.candidate - right.candidate);
|
|
256
|
+
return gaps[0].candidate;
|
|
257
|
+
}
|
|
258
|
+
throw new RangeError(`Time-unit ${unit} boundary does not exist in time zone "${timeZone}".`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function addCalendarDays(parts, delta) {
|
|
262
|
+
const date = new Date(utcTimestamp(parts.year, parts.month - 1, parts.day));
|
|
263
|
+
date.setUTCDate(date.getUTCDate() + delta);
|
|
264
|
+
return {
|
|
265
|
+
...parts,
|
|
266
|
+
year: date.getUTCFullYear(),
|
|
267
|
+
month: date.getUTCMonth() + 1,
|
|
268
|
+
day: date.getUTCDate(),
|
|
269
|
+
weekday: date.getUTCDay()
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function boundaryParts(parts, transform) {
|
|
274
|
+
const result = {
|
|
275
|
+
year: parts.year,
|
|
276
|
+
month: parts.month,
|
|
277
|
+
day: parts.day,
|
|
278
|
+
hour: parts.hour,
|
|
279
|
+
minute: parts.minute,
|
|
280
|
+
second: parts.second,
|
|
281
|
+
millisecond: 0
|
|
282
|
+
};
|
|
283
|
+
if (transform.unit === "year") Object.assign(result, { month: 1, day: 1, hour: 0, minute: 0, second: 0 });
|
|
284
|
+
else if (transform.unit === "quarter") Object.assign(result, {
|
|
285
|
+
month: Math.floor((parts.month - 1) / 3) * 3 + 1,
|
|
286
|
+
day: 1, hour: 0, minute: 0, second: 0
|
|
287
|
+
});
|
|
288
|
+
else if (transform.unit === "month") Object.assign(result, { day: 1, hour: 0, minute: 0, second: 0 });
|
|
289
|
+
else if (["day", "week"].includes(transform.unit)) Object.assign(result, { hour: 0, minute: 0, second: 0 });
|
|
290
|
+
else if (transform.unit === "hour") Object.assign(result, { minute: 0, second: 0 });
|
|
291
|
+
else if (transform.unit === "minute") result.second = 0;
|
|
292
|
+
if (transform.unit === "week") {
|
|
293
|
+
const distance = (parts.weekday - transform.weekStartsOn + 7) % 7;
|
|
294
|
+
return addCalendarDays(result, -distance);
|
|
295
|
+
}
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function bucketTime(timestamp, transform) {
|
|
300
|
+
const timeZone = transform.timeZone ?? "UTC";
|
|
301
|
+
if (transform.unit === "weekday") {
|
|
302
|
+
return timeZone === "UTC"
|
|
303
|
+
? new Date(timestamp).getUTCDay()
|
|
304
|
+
: calendarParts(timestamp, timeZone).weekday;
|
|
305
|
+
}
|
|
306
|
+
if (timeZone === "UTC") {
|
|
307
|
+
if (transform.unit === "week") {
|
|
308
|
+
const date = new Date(timestamp);
|
|
309
|
+
const distance = (date.getUTCDay() - transform.weekStartsOn + 7) % 7;
|
|
310
|
+
return utcBucket(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() - distance);
|
|
311
|
+
}
|
|
312
|
+
return floorUtcTimeUnit(timestamp, transform.unit);
|
|
313
|
+
}
|
|
314
|
+
const parts = calendarParts(timestamp, timeZone);
|
|
315
|
+
return resolveCalendarBoundary(boundaryParts(parts, transform), timeZone, transform.unit);
|
|
316
|
+
}
|
|
317
|
+
|
|
107
318
|
export function deriveTimeUnitRows(rows, transform) {
|
|
108
319
|
validateTimeUnitTransform(transform);
|
|
109
320
|
if (!Array.isArray(rows) || !rows.every(isPlainObject)) {
|
|
@@ -121,7 +332,7 @@ export function deriveTimeUnitRows(rows, transform) {
|
|
|
121
332
|
const timestamp = normalizeTemporalValue(row[transform.field], transform.field, index, transform.temporalUnit);
|
|
122
333
|
return {
|
|
123
334
|
...row,
|
|
124
|
-
[transform.as]:
|
|
335
|
+
[transform.as]: bucketTime(timestamp, transform)
|
|
125
336
|
};
|
|
126
337
|
});
|
|
127
338
|
return cloneAndFreeze(values);
|
|
@@ -2,6 +2,7 @@ const TRANSFORM_TOPOLOGY = Object.freeze({
|
|
|
2
2
|
bin: Object.freeze({ facetTopology: "statistical" }),
|
|
3
3
|
bin2d: Object.freeze({ facetTopology: "statistical" }),
|
|
4
4
|
computed: Object.freeze({ facetTopology: "rowPreserving" }),
|
|
5
|
+
complete: Object.freeze({ facetTopology: "statistical" }),
|
|
5
6
|
boxOutlier: Object.freeze({ facetTopology: "statistical" }),
|
|
6
7
|
boxSummary: Object.freeze({ facetTopology: "statistical" }),
|
|
7
8
|
density: Object.freeze({ facetTopology: "statistical" }),
|
|
@@ -11,10 +12,13 @@ const TRANSFORM_TOPOLOGY = Object.freeze({
|
|
|
11
12
|
gradientProfile: Object.freeze({ facetTopology: "statistical" }),
|
|
12
13
|
horizon: Object.freeze({ facetTopology: "statistical" }),
|
|
13
14
|
interval: Object.freeze({ facetTopology: "statistical" }),
|
|
15
|
+
impute: Object.freeze({ facetTopology: "statistical" }),
|
|
14
16
|
markFilter: Object.freeze({ provenanceTransparent: true }),
|
|
17
|
+
normalize: Object.freeze({ facetTopology: "statistical" }),
|
|
15
18
|
regression: Object.freeze({ facetTopology: "statistical" }),
|
|
16
19
|
summary: Object.freeze({ facetTopology: "statistical" }),
|
|
17
20
|
stack: Object.freeze({ facetTopology: "statistical" }),
|
|
21
|
+
statisticalReference: Object.freeze({ facetTopology: "statistical" }),
|
|
18
22
|
timeUnit: Object.freeze({ facetTopology: "rowPreserving" }),
|
|
19
23
|
window: Object.freeze({ facetTopology: "statistical" })
|
|
20
24
|
});
|