ggaction 0.0.17 → 0.0.19
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 +96 -0
- package/README.md +6 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -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 +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +20 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +8 -7
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/derived.js +11 -1
- package/src/actions/data/edit.js +3 -1
- package/src/actions/data/index.js +4 -0
- package/src/actions/data/regression.js +25 -16
- package/src/actions/data/regressionStatistics.js +73 -0
- package/src/actions/data/shared.js +8 -4
- package/src/actions/data/stack.js +3 -1
- package/src/actions/distributions/revision.js +24 -18
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +14 -8
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +26 -24
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +176 -247
- package/src/actions/facets/guides.js +2 -1
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axes.js +30 -11
- package/src/actions/guides/axes/axis.js +72 -10
- package/src/actions/guides/axes/components.js +13 -13
- package/src/actions/guides/axes/edit.js +36 -24
- package/src/actions/guides/axes/labels.js +155 -71
- package/src/actions/guides/axes/lines.js +27 -24
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +13 -8
- package/src/actions/guides/axes/tickGroups.js +11 -7
- package/src/actions/guides/axes/ticks.js +36 -24
- package/src/actions/guides/axes/titles.js +107 -69
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeAxes.js +33 -24
- package/src/actions/guides/facadeLegend.js +12 -4
- package/src/actions/guides/grids/grid.js +8 -2
- package/src/actions/guides/grids/resolve.js +102 -58
- package/src/actions/guides/legends/blocks.js +46 -20
- package/src/actions/guides/legends/categorical/actions.js +95 -19
- package/src/actions/guides/legends/categorical/components.js +15 -18
- package/src/actions/guides/legends/categorical/index.js +8 -0
- package/src/actions/guides/legends/categorical/layout.js +45 -20
- package/src/actions/guides/legends/categorical/options.js +39 -22
- package/src/actions/guides/legends/categorical/recipes.js +7 -3
- package/src/actions/guides/legends/categorical/resolve.js +11 -4
- package/src/actions/guides/legends/categorical/symbols.js +38 -18
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/colorSize.js +110 -0
- package/src/actions/guides/legends/combine.js +37 -0
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +73 -46
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +33 -21
- package/src/actions/guides/legends/creation.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +8 -7
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/lifecycle.js +2 -1
- package/src/actions/guides/legends/remove.js +9 -3
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +65 -47
- package/src/actions/guides/legends/strokeWidth.js +13 -29
- package/src/actions/guides/legends/target.js +2 -79
- package/src/actions/guides/legends/transition.js +44 -11
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/guides/tickValues.js +8 -5
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +29 -14
- package/src/actions/marks/bar/materialize.js +15 -7
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/point/materialize.js +5 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +12 -10
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/createGraphics.js +3 -2
- package/src/actions/primitives/editGraphics.js +15 -11
- package/src/actions/primitives/graphicProperties.js +1 -6
- package/src/actions/primitives/semanticAction.js +27 -1
- package/src/actions/primitives/semanticValidation/guide.js +11 -1
- package/src/actions/primitives/semanticValidation/layer.js +8 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +18 -6
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -5
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +12 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +8 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +126 -45
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +6 -3
- package/src/core/font.js +9 -2
- package/src/core/graphicProperties.js +1 -0
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +4 -0
- package/src/core/resourceReferences.js +5 -2
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +35 -0
- package/src/core/vocabulary.js +7 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/axisTextPlacement.js +1 -0
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/colorSizeLegend.js +39 -0
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +115 -26
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +13 -9
- package/src/grammar/displayLabels.js +36 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +58 -30
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/facets/legendIntent.js +65 -0
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/guideDash.js +1 -0
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendBlocks.js +1 -0
- package/src/grammar/legendBorder.js +9 -0
- package/src/grammar/legendClip.js +19 -0
- package/src/grammar/legendCombinations.js +45 -0
- package/src/grammar/legendGradient.js +27 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendOpacity.js +6 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/pointShapes.js +16 -2
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +16 -2
- package/src/grammar/regression/models.js +142 -54
- package/src/grammar/regression/parameters.js +23 -4
- package/src/grammar/regression/statistics.js +64 -0
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +14 -0
- package/src/grammar/schemas/graphic.js +6 -0
- package/src/grammar/schemas/graphicBounds.js +31 -7
- package/src/grammar/schemas/semanticPath.js +19 -3
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +4 -27
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +22 -11
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +15 -2
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/guideCollisions.js +9 -1
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/compositionSnapshot.js +2 -7
- package/src/materialization/coordinateBounds.js +21 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/edit.js +30 -0
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/owner.js +125 -0
- package/src/materialization/facetGuides/ownerLayout.js +71 -0
- package/src/materialization/facetGuides/placement.js +71 -14
- package/src/materialization/facetGuides/preparation.js +22 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +5 -1
- package/src/materialization/graphicTranslation.js +73 -0
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendBorder.js +28 -0
- package/src/materialization/legendClip.js +76 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +2 -2
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +14 -15
- package/src/materialization/scales/map.js +6 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +7 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/materialization/typography.js +10 -2
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +220 -0
- package/src/persistence.js +19 -6
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +22 -11
- package/src/renderers/canvas/isolated.js +166 -0
- package/src/renderers/canvas/limits.js +3 -0
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/pdf.js +2 -2
- package/src/renderers/png.js +2 -2
- package/src/renderers/svg.js +14 -4
- package/src/selectors/axes.js +1 -0
- package/src/selectors/graphicCapacity.js +38 -0
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +3 -0
- package/types/index.d.ts +12 -0
- package/types/program.d.ts +536 -129
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { USER_ID_SOURCE } from "../../core/identifiers.js";
|
|
2
2
|
import {
|
|
3
|
+
AXIS_RESOURCE_CHANNELS,
|
|
3
4
|
CARTESIAN_POSITION_CHANNELS,
|
|
4
5
|
ENCODING_CHANNELS,
|
|
5
6
|
SCALED_ENCODING_CHANNELS
|
|
@@ -23,7 +24,7 @@ const ENCODING_PATHS = Object.freeze([
|
|
|
23
24
|
"encoding.color.aggregate",
|
|
24
25
|
"encoding.group.fields",
|
|
25
26
|
"encoding.group.inferredFrom",
|
|
26
|
-
...["x", "y", "x2", "y2", "theta", "color"].map(channel =>
|
|
27
|
+
...["x", "y", "x2", "y2", "theta", "color", "stroke"].map(channel =>
|
|
27
28
|
`encoding.${channel}.temporalUnit`
|
|
28
29
|
),
|
|
29
30
|
"encoding.pathOrder.field",
|
|
@@ -64,6 +65,8 @@ const ENTITY_PATHS = Object.freeze({
|
|
|
64
65
|
"transform",
|
|
65
66
|
"mark.type",
|
|
66
67
|
"mark.missing",
|
|
68
|
+
"mark.orientation",
|
|
69
|
+
"mark.blend",
|
|
67
70
|
"derivedBindings.regression",
|
|
68
71
|
"layout.mode",
|
|
69
72
|
...ENCODING_PATHS
|
|
@@ -73,7 +76,7 @@ const ENTITY_PATHS = Object.freeze({
|
|
|
73
76
|
collection: "scales",
|
|
74
77
|
properties: new Set([
|
|
75
78
|
"type", "domain", "range", "emptyDomain", "nice", "zero", "clamp", "reverse",
|
|
76
|
-
"unknown", "base", "exponent", "constant", "interpolate", "radialMapping", "midpoint",
|
|
79
|
+
"unknown", "base", "exponent", "constant", "interpolate", "whitePoint", "gamma", "radialMapping", "midpoint",
|
|
77
80
|
"paddingInner", "paddingOuter", "padding", "align"
|
|
78
81
|
])
|
|
79
82
|
},
|
|
@@ -100,6 +103,9 @@ const GUIDE_PATHS = new Set([
|
|
|
100
103
|
"axis.parallel.coordinate",
|
|
101
104
|
"axis.parallel.scales",
|
|
102
105
|
"axis.parallel.titles",
|
|
106
|
+
"legend.colorSize.colorScale",
|
|
107
|
+
"legend.colorSize.sizeScale",
|
|
108
|
+
"legend.colorSize.title",
|
|
103
109
|
"legend.color.scale",
|
|
104
110
|
"legend.color.title",
|
|
105
111
|
"legend.color.order",
|
|
@@ -136,6 +142,7 @@ const GUIDE_REMOVABLE_CONTAINERS = new Set([
|
|
|
136
142
|
"grid.vertical",
|
|
137
143
|
"grid.theta",
|
|
138
144
|
"grid.radial",
|
|
145
|
+
"legend.colorSize",
|
|
139
146
|
"legend.color",
|
|
140
147
|
"legend.stroke",
|
|
141
148
|
"legend.size",
|
|
@@ -146,6 +153,13 @@ const GUIDE_REMOVABLE_CONTAINERS = new Set([
|
|
|
146
153
|
|
|
147
154
|
const TITLE_PATHS = new Set(["text", "subtitle"]);
|
|
148
155
|
|
|
156
|
+
function isNamedAxisPath(path, container = false) {
|
|
157
|
+
const match = path.match(new RegExp(container
|
|
158
|
+
? `^axis\\.(${USER_ID_SOURCE})$`
|
|
159
|
+
: `^axis\\.(${USER_ID_SOURCE})\\.(scale|coordinate|title|channel|position)$`));
|
|
160
|
+
return match !== null && !AXIS_RESOURCE_CHANNELS.includes(match[1]);
|
|
161
|
+
}
|
|
162
|
+
|
|
149
163
|
function splitPropertyPath(property) {
|
|
150
164
|
return property.split(".");
|
|
151
165
|
}
|
|
@@ -200,7 +214,9 @@ export function parseSemanticPath(property, { allowContainer = false } = {}) {
|
|
|
200
214
|
|
|
201
215
|
if (
|
|
202
216
|
!GUIDE_PATHS.has(propertyPath) &&
|
|
203
|
-
!(
|
|
217
|
+
!isNamedAxisPath(propertyPath) &&
|
|
218
|
+
!(allowContainer && (GUIDE_REMOVABLE_CONTAINERS.has(propertyPath) ||
|
|
219
|
+
isNamedAxisPath(propertyPath, true)))
|
|
204
220
|
) {
|
|
205
221
|
throw new Error(`Unknown semantic property "${property}".`);
|
|
206
222
|
}
|
package/src/grammar/stack.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
|
|
2
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
3
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
4
|
import { layoutSeriesPartition } from "./seriesLayout.js";
|
|
3
5
|
|
|
@@ -8,17 +10,7 @@ const AS_KEYS = Object.freeze(["start", "end", "value", "share"]);
|
|
|
8
10
|
const MODES = new Set(["stack", "fill", "center", "diverging"]);
|
|
9
11
|
const MAX_OUTPUT_ROWS = 10_000;
|
|
10
12
|
|
|
11
|
-
function rejectUnknownKeys(value, supported, label) {
|
|
12
|
-
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
13
|
-
if (unknown !== undefined) throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
14
|
-
}
|
|
15
13
|
|
|
16
|
-
function requireField(value, label) {
|
|
17
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
18
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
19
|
-
}
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
14
|
|
|
23
15
|
function normalizeAs(value, field) {
|
|
24
16
|
const requested = value ?? {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
|
|
1
2
|
import { aggregateRows } from "./aggregate.js";
|
|
2
3
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
3
4
|
|
|
@@ -6,12 +7,6 @@ const SIMPLE_STATISTICS = new Set(["mean", "median", "min", "max"]);
|
|
|
6
7
|
const POPULATIONS = new Set(["boundData", "visibleItems"]);
|
|
7
8
|
const TRANSFORM_KEYS = Object.freeze(["type", "target"]);
|
|
8
9
|
|
|
9
|
-
function rejectUnknownKeys(value, supported, label) {
|
|
10
|
-
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
11
|
-
if (unknown !== undefined) {
|
|
12
|
-
throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
10
|
|
|
16
11
|
export function normalizeReferenceStatistic(value, label = "Reference statistic") {
|
|
17
12
|
if (!isPlainObject(value)) {
|
package/src/grammar/summary.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { scalarKey } from "./transformKeys.js";
|
|
2
|
+
import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
|
|
3
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
4
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
5
|
import {
|
|
3
6
|
aggregateRows,
|
|
@@ -20,21 +23,7 @@ const TRANSFORM_KEYS = Object.freeze([
|
|
|
20
23
|
]);
|
|
21
24
|
const AGGREGATE_KEYS = Object.freeze(["op", "field", "as"]);
|
|
22
25
|
const NOMINAL_OPERATIONS = new Set(["distinct", "valid", "missing"]);
|
|
23
|
-
const MAX_OUTPUT_ROWS =
|
|
24
|
-
|
|
25
|
-
function rejectUnknownKeys(value, supported, label) {
|
|
26
|
-
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
27
|
-
if (unknown !== undefined) {
|
|
28
|
-
throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function requireField(value, label) {
|
|
33
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
34
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
26
|
+
const MAX_OUTPUT_ROWS = 30_000;
|
|
38
27
|
|
|
39
28
|
function normalizeGroupBy(value) {
|
|
40
29
|
if (value === undefined) return [];
|
|
@@ -131,18 +120,6 @@ export function validateSummaryTransform(transform) {
|
|
|
131
120
|
return transform;
|
|
132
121
|
}
|
|
133
122
|
|
|
134
|
-
function scalarKey(value, label) {
|
|
135
|
-
if (value === null) return "null";
|
|
136
|
-
if (typeof value === "string") return `string:${value.length}:${value}`;
|
|
137
|
-
if (typeof value === "boolean") return `boolean:${value}`;
|
|
138
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
139
|
-
return `number:${Object.is(value, -0) ? 0 : value}`;
|
|
140
|
-
}
|
|
141
|
-
throw new TypeError(
|
|
142
|
-
`${label} must contain null, strings, booleans, or finite numbers.`
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
123
|
function requireSourceFields(rows, transform) {
|
|
147
124
|
if (rows.length === 0) return;
|
|
148
125
|
const fields = new Set(rows.flatMap(row => Object.keys(row)));
|
package/src/grammar/text.js
CHANGED
|
@@ -11,6 +11,11 @@ export function isSourceOwnedText(layer) {
|
|
|
11
11
|
return layer?.mark?.type === "text" && layer.source !== undefined;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export const TEXT_MARK_STYLE_OPTIONS = Object.freeze([
|
|
15
|
+
"fill", "opacity", "fontSize", "fontFamily", "fontWeight",
|
|
16
|
+
"align", "baseline", "rotation", "dx", "dy", "inheritColor", "lineHeight", "blockAlign"
|
|
17
|
+
]);
|
|
18
|
+
|
|
14
19
|
export const DEFAULT_TEXT_MARK = cloneAndFreeze({
|
|
15
20
|
fill: "#334155",
|
|
16
21
|
opacity: 1,
|
|
@@ -49,6 +54,18 @@ export function formatTextValue(value, format = "auto") {
|
|
|
49
54
|
|
|
50
55
|
export function normalizeTextMarkConfig(options, base = DEFAULT_TEXT_MARK) {
|
|
51
56
|
const config = { ...base };
|
|
57
|
+
if (Object.hasOwn(options, "lineHeight")) {
|
|
58
|
+
if (!Number.isFinite(options.lineHeight) || options.lineHeight <= 0) throw new RangeError("Text lineHeight must be positive and finite.");
|
|
59
|
+
config.lineHeight = options.lineHeight;
|
|
60
|
+
}
|
|
61
|
+
if (Object.hasOwn(options, "blockAlign")) {
|
|
62
|
+
if (!["first", "middle"].includes(options.blockAlign)) throw new Error("Text blockAlign must be first or middle.");
|
|
63
|
+
config.blockAlign = options.blockAlign;
|
|
64
|
+
}
|
|
65
|
+
if (Object.hasOwn(options, "inheritColor")) {
|
|
66
|
+
if (![false, "fill", "stroke"].includes(options.inheritColor)) throw new Error("Text inheritColor must be fill, stroke, or false.");
|
|
67
|
+
config.inheritColor = options.inheritColor;
|
|
68
|
+
}
|
|
52
69
|
const mapping = {
|
|
53
70
|
fill: "fill",
|
|
54
71
|
opacity: "opacity",
|
|
@@ -75,3 +92,5 @@ export function normalizeTextMarkConfig(options, base = DEFAULT_TEXT_MARK) {
|
|
|
75
92
|
}
|
|
76
93
|
return cloneAndFreeze(config);
|
|
77
94
|
}
|
|
95
|
+
|
|
96
|
+
export { resolveTextLines } from "../core/textMetrics.js";
|
package/src/grammar/timeUnit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
2
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
3
|
import { normalizeTemporalValue, validateTemporalUnit } from "./scales/fields.js";
|
|
3
4
|
|
|
@@ -51,6 +52,7 @@ function formatter(timeZone) {
|
|
|
51
52
|
try {
|
|
52
53
|
value = new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
|
|
53
54
|
timeZone,
|
|
55
|
+
era: "short",
|
|
54
56
|
year: "numeric",
|
|
55
57
|
month: "2-digit",
|
|
56
58
|
day: "2-digit",
|
|
@@ -67,7 +69,7 @@ function formatter(timeZone) {
|
|
|
67
69
|
return CALENDAR_FORMATTERS.get(timeZone);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
function validateTimeZone(timeZone) {
|
|
72
|
+
export function validateTimeZone(timeZone) {
|
|
71
73
|
if (typeof timeZone !== "string" || timeZone.length === 0) {
|
|
72
74
|
throw new TypeError("Time-unit timeZone must be a non-empty IANA time-zone name.");
|
|
73
75
|
}
|
|
@@ -75,12 +77,6 @@ function validateTimeZone(timeZone) {
|
|
|
75
77
|
return timeZone;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
function requireField(value, label) {
|
|
79
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
80
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
81
|
-
}
|
|
82
|
-
return value;
|
|
83
|
-
}
|
|
84
80
|
|
|
85
81
|
function rejectUnknownKeys(value) {
|
|
86
82
|
const unknown = Object.keys(value).find(key => !TRANSFORM_KEYS.includes(key));
|
|
@@ -127,7 +123,8 @@ export function validateTimeUnitTransform(transform) {
|
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
export function normalizeTimeUnitTransform(args = {}) {
|
|
130
|
-
const { field, unit,
|
|
126
|
+
const { field, unit, temporalUnit, timeZone } = args;
|
|
127
|
+
const as = args.as === undefined ? `${field}_${unit}` : args.as;
|
|
131
128
|
const transform = { type: "timeUnit", field, unit, as,
|
|
132
129
|
...(temporalUnit === undefined ? {} : { temporalUnit }),
|
|
133
130
|
...(timeZone === undefined ? {} : { timeZone }) };
|
|
@@ -182,17 +179,18 @@ export function calendarParts(timestamp, timeZone) {
|
|
|
182
179
|
validateTimeZone(timeZone);
|
|
183
180
|
const parts = Object.fromEntries(formatter(timeZone).formatToParts(timestamp)
|
|
184
181
|
.filter(part => part.type !== "literal")
|
|
185
|
-
.map(part => [part.type, Number(part.value)]));
|
|
182
|
+
.map(part => [part.type, part.type === "era" ? part.value : Number(part.value)]));
|
|
183
|
+
const year = parts.era === "BC" ? 1 - parts.year : parts.year;
|
|
186
184
|
const millisecond = ((timestamp % 1000) + 1000) % 1000;
|
|
187
185
|
return {
|
|
188
|
-
year
|
|
186
|
+
year,
|
|
189
187
|
month: parts.month,
|
|
190
188
|
day: parts.day,
|
|
191
189
|
hour: parts.hour,
|
|
192
190
|
minute: parts.minute,
|
|
193
191
|
second: parts.second,
|
|
194
192
|
millisecond,
|
|
195
|
-
weekday: weekdayFromDate(
|
|
193
|
+
weekday: weekdayFromDate(year, parts.month, parts.day)
|
|
196
194
|
};
|
|
197
195
|
}
|
|
198
196
|
|
|
@@ -337,3 +335,16 @@ export function deriveTimeUnitRows(rows, transform) {
|
|
|
337
335
|
});
|
|
338
336
|
return cloneAndFreeze(values);
|
|
339
337
|
}
|
|
338
|
+
|
|
339
|
+
export function offsetUtcTimeUnit(timestamp, unit) {
|
|
340
|
+
const date = new Date(timestamp);
|
|
341
|
+
if (unit === "year") return utcTimestamp(date.getUTCFullYear() + 1);
|
|
342
|
+
if (unit === "quarter" || unit === "month") {
|
|
343
|
+
return utcTimestamp(date.getUTCFullYear(), date.getUTCMonth() + (unit === "quarter" ? 3 : 1));
|
|
344
|
+
}
|
|
345
|
+
if (unit === "day") return timestamp + 24 * 60 * 60 * 1000;
|
|
346
|
+
if (unit === "hour") return timestamp + 60 * 60 * 1000;
|
|
347
|
+
if (unit === "minute") return timestamp + 60 * 1000;
|
|
348
|
+
return timestamp + 1000;
|
|
349
|
+
}
|
|
350
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import { requireStringValue, rejectUnknownProperties } from "../core/validation.js";
|
|
3
|
+
|
|
4
|
+
export function scalarKey(value, label) {
|
|
5
|
+
if (value === null) return "null";
|
|
6
|
+
if (typeof value === "string") return `string:${value.length}:${value}`;
|
|
7
|
+
if (typeof value === "boolean") return `boolean:${value}`;
|
|
8
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
9
|
+
return `number:${Object.is(value, -0) ? 0 : value}`;
|
|
10
|
+
}
|
|
11
|
+
throw new TypeError(
|
|
12
|
+
`${label} must contain null, strings, booleans, or finite numbers.`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function normalizeSortBy(value) {
|
|
17
|
+
if (value === undefined) return [];
|
|
18
|
+
if (!Array.isArray(value)) return value;
|
|
19
|
+
return value.map(sort => isPlainObject(sort)
|
|
20
|
+
? { ...sort, order: sort.order ?? "ascending" }
|
|
21
|
+
: sort
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function validateSortBy(sortBy, label) {
|
|
26
|
+
const kind = label.toLowerCase();
|
|
27
|
+
if (!Array.isArray(sortBy)) throw new TypeError(`${label} sortBy must be an array.`);
|
|
28
|
+
const fields = [];
|
|
29
|
+
sortBy.forEach((sort, index) => {
|
|
30
|
+
if (!isPlainObject(sort)) {
|
|
31
|
+
throw new TypeError(`${label} sortBy[${index}] must be a plain object.`);
|
|
32
|
+
}
|
|
33
|
+
rejectUnknownProperties(sort, ["field", "order"], `${kind} sortBy[${index}]`);
|
|
34
|
+
fields.push(requireStringValue(sort.field, `${label} sortBy[${index}].field`));
|
|
35
|
+
if (!["ascending", "descending"].includes(sort.order)) {
|
|
36
|
+
throw new Error(`Unsupported ${kind} sort order "${sort.order}".`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
if (new Set(fields).size !== fields.length) {
|
|
40
|
+
throw new Error(`${label} sortBy fields must be unique.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isGroupValue(value) {
|
|
45
|
+
return typeof value === "string" || typeof value === "boolean" ||
|
|
46
|
+
(typeof value === "number" && Number.isFinite(value));
|
|
47
|
+
}
|
|
@@ -16,6 +16,7 @@ const TRANSFORM_TOPOLOGY = Object.freeze({
|
|
|
16
16
|
markFilter: Object.freeze({ provenanceTransparent: true }),
|
|
17
17
|
normalize: Object.freeze({ facetTopology: "statistical" }),
|
|
18
18
|
regression: Object.freeze({ facetTopology: "statistical" }),
|
|
19
|
+
regressionStatistics: Object.freeze({ facetTopology: "statistical" }),
|
|
19
20
|
sort: Object.freeze({ facetTopology: "statistical" }),
|
|
20
21
|
summary: Object.freeze({ facetTopology: "statistical" }),
|
|
21
22
|
stack: Object.freeze({ facetTopology: "statistical" }),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import { validateRegressionStatisticsTransform } from "./regression/statistics.js";
|
|
2
3
|
import { validateBoxTransform } from "./boxPlot.js";
|
|
3
4
|
import { normalizeBinTransform, validateBinTransform } from "./bin.js";
|
|
4
5
|
import {
|
|
@@ -199,8 +200,11 @@ function normalizeRegressionEdit(transform, patch) {
|
|
|
199
200
|
const method = patch.method ?? base.method;
|
|
200
201
|
if (method !== base.method) {
|
|
201
202
|
if (method !== "polynomial") delete base.degree;
|
|
202
|
-
if (method !== "loess")
|
|
203
|
-
|
|
203
|
+
if (method !== "loess") {
|
|
204
|
+
delete base.span;
|
|
205
|
+
delete base.robustIterations;
|
|
206
|
+
}
|
|
207
|
+
if (["loess", "exp"].includes(method) || ["loess", "exp"].includes(base.method)) {
|
|
204
208
|
for (const key of [
|
|
205
209
|
"confidenceMethod", "level", "confidence", "interval"
|
|
206
210
|
]) delete base[key];
|
|
@@ -443,6 +447,15 @@ const TRANSFORM_POLICIES = Object.freeze({
|
|
|
443
447
|
normalizeEdit: normalizeRegressionEdit,
|
|
444
448
|
outputRoles: () => ({})
|
|
445
449
|
}),
|
|
450
|
+
regressionStatistics: Object.freeze({
|
|
451
|
+
...findTransformTopology("regressionStatistics"),
|
|
452
|
+
validate: validateRegressionStatisticsTransform,
|
|
453
|
+
materializeOp: "materializeRegressionStatisticsData",
|
|
454
|
+
replayTransform: requestedTransform,
|
|
455
|
+
editable: true,
|
|
456
|
+
normalizeEdit: (transform, patch) => validateRegressionStatisticsTransform({ ...transform, ...patch }),
|
|
457
|
+
outputRoles: () => ({ count: "count", intercept: "intercept", slope: "slope", rSquared: "rSquared" })
|
|
458
|
+
}),
|
|
446
459
|
sort: Object.freeze({
|
|
447
460
|
...findTransformTopology("sort"),
|
|
448
461
|
validate: validateSortTransform,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const NUMERIC_FORMAT = /^\.(0?\d|1[0-2])(f|%|e)$/;
|
|
2
|
-
const
|
|
2
|
+
const GROUPED_NUMERIC_FORMAT = /^,(?:\.(0?\d|1[0-2])f)?$/;
|
|
3
|
+
const WEEKDAY_NAMES = Object.freeze(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]);
|
|
4
|
+
const TIME_DIRECTIVES = new Set(["Y", "m", "d", "b", "a", "H", "M", "S", "L", "%"]);
|
|
3
5
|
const MONTH_NAMES = Object.freeze([
|
|
4
6
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
5
7
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
@@ -33,12 +35,12 @@ export function validateValueFormat(format, label = "Label format", {
|
|
|
33
35
|
if (allowDecimalsObject) return format;
|
|
34
36
|
}
|
|
35
37
|
if (typeof format === "string" && (
|
|
36
|
-
NUMERIC_FORMAT.test(format) || isUtcValueFormat(format)
|
|
38
|
+
NUMERIC_FORMAT.test(format) || GROUPED_NUMERIC_FORMAT.test(format) || isUtcValueFormat(format)
|
|
37
39
|
)) {
|
|
38
40
|
return format;
|
|
39
41
|
}
|
|
40
42
|
const numericCandidate = typeof format === "string"
|
|
41
|
-
? format.match(
|
|
43
|
+
? format.match(/^,?\.(\d+)(f|%|e)$/)
|
|
42
44
|
: undefined;
|
|
43
45
|
if (numericCandidate && Number(numericCandidate[1]) > 12) {
|
|
44
46
|
throw new RangeError(`${label} numeric format supports at most 12 decimals.`);
|
|
@@ -61,9 +63,14 @@ function formatUtcValue(value, format, label) {
|
|
|
61
63
|
Y: pad(date.getUTCFullYear(), 4),
|
|
62
64
|
m: pad(date.getUTCMonth() + 1),
|
|
63
65
|
d: pad(date.getUTCDate()),
|
|
64
|
-
b: MONTH_NAMES[date.getUTCMonth()]
|
|
66
|
+
b: MONTH_NAMES[date.getUTCMonth()],
|
|
67
|
+
a: WEEKDAY_NAMES[date.getUTCDay()],
|
|
68
|
+
H: pad(date.getUTCHours()),
|
|
69
|
+
M: pad(date.getUTCMinutes()),
|
|
70
|
+
S: pad(date.getUTCSeconds()),
|
|
71
|
+
L: pad(date.getUTCMilliseconds(), 3)
|
|
65
72
|
};
|
|
66
|
-
return format.replace(/%([
|
|
73
|
+
return format.replace(/%([YmdbaHMSL%])/g, (_, directive) =>
|
|
67
74
|
directive === "%" ? "%" : values[directive]
|
|
68
75
|
);
|
|
69
76
|
}
|
|
@@ -98,6 +105,11 @@ export function formatValue(value, {
|
|
|
98
105
|
throw new TypeError(`${label} "${typeof resolved === "string" ? resolved : "decimals"}" requires a finite number.`);
|
|
99
106
|
}
|
|
100
107
|
if (typeof resolved === "object") return value.toFixed(resolved.decimals);
|
|
108
|
+
const grouped = resolved.match(GROUPED_NUMERIC_FORMAT);
|
|
109
|
+
if (grouped) {
|
|
110
|
+
const precision = grouped[1] === undefined ? 0 : Number(grouped[1]);
|
|
111
|
+
return new Intl.NumberFormat("en-US", { useGrouping: true, minimumFractionDigits: precision, maximumFractionDigits: precision }).format(value);
|
|
112
|
+
}
|
|
101
113
|
const match = resolved.match(NUMERIC_FORMAT);
|
|
102
114
|
const precision = Number(match[1]);
|
|
103
115
|
if (match[2] === "f") return value.toFixed(precision);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
2
|
import { isPlainObject } from "../core/immutable.js";
|
|
2
3
|
import {
|
|
3
4
|
interpolateNumber,
|
|
@@ -15,12 +16,6 @@ export const WEIGHTED_AGGREGATE_OPERATIONS = Object.freeze([
|
|
|
15
16
|
"stderr", "median", "q1", "q3", "quantile"
|
|
16
17
|
]);
|
|
17
18
|
|
|
18
|
-
function requireField(value, label) {
|
|
19
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
20
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
19
|
|
|
25
20
|
export function normalizeStatisticalWeight(value, label = "Statistical weight") {
|
|
26
21
|
if (!isPlainObject(value)) {
|
package/src/grammar/window.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { scalarKey, normalizeSortBy, validateSortBy } from "./transformKeys.js";
|
|
2
|
+
import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
|
|
3
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
4
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
5
|
import {
|
|
3
6
|
maximumMagnitude,
|
|
@@ -11,8 +14,6 @@ import { normalizeTemporalValue, validateTemporalUnit } from "./scales/fields.js
|
|
|
11
14
|
const TRANSFORM_KEYS = [
|
|
12
15
|
"type", "partitionBy", "sortBy", "operations", "temporalUnit"
|
|
13
16
|
];
|
|
14
|
-
const SORT_KEYS = ["field", "order"];
|
|
15
|
-
const ORDER_VALUES = ["ascending", "descending"];
|
|
16
17
|
const OPERATION_VALUES = [
|
|
17
18
|
"rowNumber", "rank", "denseRank", "cumulativeSum", "lag", "lead",
|
|
18
19
|
"movingMean", "movingSum"
|
|
@@ -30,20 +31,6 @@ const DURATION_UNITS = Object.freeze({
|
|
|
30
31
|
day: 86_400_000
|
|
31
32
|
});
|
|
32
33
|
|
|
33
|
-
function requireField(value, label) {
|
|
34
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
35
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
36
|
-
}
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function rejectUnknownKeys(value, supported, label) {
|
|
41
|
-
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
42
|
-
if (unknown !== undefined) {
|
|
43
|
-
throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
34
|
function validateFieldList(value, label) {
|
|
48
35
|
if (
|
|
49
36
|
!Array.isArray(value) ||
|
|
@@ -56,26 +43,6 @@ function validateFieldList(value, label) {
|
|
|
56
43
|
}
|
|
57
44
|
}
|
|
58
45
|
|
|
59
|
-
function validateSortBy(sortBy) {
|
|
60
|
-
if (!Array.isArray(sortBy)) {
|
|
61
|
-
throw new TypeError("Window sortBy must be an array.");
|
|
62
|
-
}
|
|
63
|
-
const fields = [];
|
|
64
|
-
sortBy.forEach((sort, index) => {
|
|
65
|
-
if (!isPlainObject(sort)) {
|
|
66
|
-
throw new TypeError(`Window sortBy[${index}] must be a plain object.`);
|
|
67
|
-
}
|
|
68
|
-
rejectUnknownKeys(sort, SORT_KEYS, `window sortBy[${index}]`);
|
|
69
|
-
fields.push(requireField(sort.field, `Window sortBy[${index}].field`));
|
|
70
|
-
if (!ORDER_VALUES.includes(sort.order)) {
|
|
71
|
-
throw new Error(`Unsupported window sort order "${sort.order}".`);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
if (new Set(fields).size !== fields.length) {
|
|
75
|
-
throw new Error("Window sortBy fields must be unique.");
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
46
|
function operationKeys(operation) {
|
|
80
47
|
if (POSITION_OPERATIONS.has(operation.op)) return ["op", "as"];
|
|
81
48
|
if (operation.op === "cumulativeSum") return ["op", "field", "as"];
|
|
@@ -177,7 +144,7 @@ export function validateWindowTransform(transform) {
|
|
|
177
144
|
throw new Error(`Unsupported window transform "${transform.type}".`);
|
|
178
145
|
}
|
|
179
146
|
validateFieldList(transform.partitionBy, "Window partitionBy");
|
|
180
|
-
validateSortBy(transform.sortBy);
|
|
147
|
+
validateSortBy(transform.sortBy, "Window");
|
|
181
148
|
validateOperations(transform.operations, transform.sortBy);
|
|
182
149
|
const durationOperations = transform.operations.filter(operation =>
|
|
183
150
|
MOVING_OPERATIONS.has(operation.op) && Object.hasOwn(operation.frame, "duration")
|
|
@@ -199,15 +166,6 @@ function normalizePartitionBy(value) {
|
|
|
199
166
|
return Array.isArray(value) ? [...value] : [value];
|
|
200
167
|
}
|
|
201
168
|
|
|
202
|
-
function normalizeSortBy(value) {
|
|
203
|
-
if (value === undefined) return [];
|
|
204
|
-
if (!Array.isArray(value)) return value;
|
|
205
|
-
return value.map(sort => isPlainObject(sort)
|
|
206
|
-
? { ...sort, order: sort.order ?? "ascending" }
|
|
207
|
-
: sort
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
169
|
function normalizeOperations(value) {
|
|
212
170
|
if (!Array.isArray(value)) return value;
|
|
213
171
|
return value.map(operation => {
|
|
@@ -259,18 +217,6 @@ export function normalizeWindowTransform({
|
|
|
259
217
|
return cloneAndFreeze(transform);
|
|
260
218
|
}
|
|
261
219
|
|
|
262
|
-
function scalarKey(value, label) {
|
|
263
|
-
if (value === null) return "null";
|
|
264
|
-
if (typeof value === "string") return `string:${value.length}:${value}`;
|
|
265
|
-
if (typeof value === "boolean") return `boolean:${value}`;
|
|
266
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
267
|
-
return `number:${Object.is(value, -0) ? 0 : value}`;
|
|
268
|
-
}
|
|
269
|
-
throw new TypeError(
|
|
270
|
-
`${label} must contain null, strings, booleans, or finite numbers.`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
220
|
function compareScalar(left, right, label) {
|
|
275
221
|
const leftMissing = left === null || left === undefined;
|
|
276
222
|
const rightMissing = right === null || right === undefined;
|