ggaction 0.0.17 → 0.0.18
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 +46 -0
- package/README.md +2 -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 +16 -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 +4 -4
- 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/edit.js +1 -1
- package/src/actions/data/regression.js +2 -1
- package/src/actions/data/shared.js +3 -2
- package/src/actions/data/stack.js +3 -1
- 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 +3 -2
- package/src/actions/encodings/seriesLayout.js +1 -0
- 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 +158 -240
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axis.js +16 -4
- package/src/actions/guides/axes/edit.js +4 -4
- package/src/actions/guides/axes/labels.js +120 -46
- package/src/actions/guides/axes/lines.js +6 -5
- 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 +3 -2
- package/src/actions/guides/axes/tickGroups.js +5 -2
- package/src/actions/guides/axes/ticks.js +6 -3
- package/src/actions/guides/axes/titles.js +71 -26
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeLegend.js +10 -2
- package/src/actions/guides/grids/grid.js +7 -1
- package/src/actions/guides/grids/resolve.js +59 -12
- package/src/actions/guides/legends/blocks.js +3 -1
- package/src/actions/guides/legends/categorical/actions.js +70 -17
- package/src/actions/guides/legends/categorical/components.js +12 -8
- package/src/actions/guides/legends/categorical/index.js +6 -0
- package/src/actions/guides/legends/categorical/layout.js +37 -8
- package/src/actions/guides/legends/categorical/options.js +31 -20
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +18 -12
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- 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 +47 -34
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +19 -12
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/remove.js +3 -0
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +51 -43
- package/src/actions/guides/legends/strokeWidth.js +11 -29
- package/src/actions/guides/legends/target.js +3 -3
- package/src/actions/guides/legends/transition.js +21 -7
- 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/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 +8 -4
- package/src/actions/marks/bar/materialize.js +9 -6
- 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/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +2 -0
- 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/semanticValidation/layer.js +4 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +3 -3
- 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 +8 -3
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +2 -2
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +81 -27
- 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 +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/resourceReferences.js +3 -0
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +26 -0
- package/src/core/vocabulary.js +4 -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/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/complete.js +15 -19
- package/src/grammar/computed.js +92 -25
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +3 -7
- package/src/grammar/displayLabels.js +28 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +5 -0
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendGradient.js +18 -0
- package/src/grammar/legendGroups.js +1 -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/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +7 -1
- package/src/grammar/regression/models.js +83 -55
- package/src/grammar/regression/parameters.js +14 -3
- 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 +11 -0
- package/src/grammar/schemas/graphic.js +3 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +3 -26
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transforms.js +4 -1
- 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/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/coordinateBounds.js +8 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/placement.js +19 -8
- package/src/materialization/facetGuides/preparation.js +15 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +1 -1
- 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/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +1 -1
- package/src/materialization/scales/map.js +3 -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 +5 -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/persistence/codec.js +37 -6
- package/src/persistence/validation.js +47 -0
- package/src/persistence.js +18 -5
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +2 -1
- 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/svg.js +4 -1
- 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 +1 -0
- package/types/index.d.ts +7 -0
- package/types/program.d.ts +347 -85
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
|
|
|
@@ -75,12 +76,6 @@ function validateTimeZone(timeZone) {
|
|
|
75
76
|
return timeZone;
|
|
76
77
|
}
|
|
77
78
|
|
|
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
79
|
|
|
85
80
|
function rejectUnknownKeys(value) {
|
|
86
81
|
const unknown = Object.keys(value).find(key => !TRANSFORM_KEYS.includes(key));
|
|
@@ -127,7 +122,8 @@ export function validateTimeUnitTransform(transform) {
|
|
|
127
122
|
}
|
|
128
123
|
|
|
129
124
|
export function normalizeTimeUnitTransform(args = {}) {
|
|
130
|
-
const { field, unit,
|
|
125
|
+
const { field, unit, temporalUnit, timeZone } = args;
|
|
126
|
+
const as = args.as === undefined ? `${field}_${unit}` : args.as;
|
|
131
127
|
const transform = { type: "timeUnit", field, unit, as,
|
|
132
128
|
...(temporalUnit === undefined ? {} : { temporalUnit }),
|
|
133
129
|
...(timeZone === undefined ? {} : { timeZone }) };
|
|
@@ -337,3 +333,16 @@ export function deriveTimeUnitRows(rows, transform) {
|
|
|
337
333
|
});
|
|
338
334
|
return cloneAndFreeze(values);
|
|
339
335
|
}
|
|
336
|
+
|
|
337
|
+
export function offsetUtcTimeUnit(timestamp, unit) {
|
|
338
|
+
const date = new Date(timestamp);
|
|
339
|
+
if (unit === "year") return utcTimestamp(date.getUTCFullYear() + 1);
|
|
340
|
+
if (unit === "quarter" || unit === "month") {
|
|
341
|
+
return utcTimestamp(date.getUTCFullYear(), date.getUTCMonth() + (unit === "quarter" ? 3 : 1));
|
|
342
|
+
}
|
|
343
|
+
if (unit === "day") return timestamp + 24 * 60 * 60 * 1000;
|
|
344
|
+
if (unit === "hour") return timestamp + 60 * 60 * 1000;
|
|
345
|
+
if (unit === "minute") return timestamp + 60 * 1000;
|
|
346
|
+
return timestamp + 1000;
|
|
347
|
+
}
|
|
348
|
+
|
|
@@ -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
|
+
}
|
|
@@ -199,7 +199,10 @@ function normalizeRegressionEdit(transform, patch) {
|
|
|
199
199
|
const method = patch.method ?? base.method;
|
|
200
200
|
if (method !== base.method) {
|
|
201
201
|
if (method !== "polynomial") delete base.degree;
|
|
202
|
-
if (method !== "loess")
|
|
202
|
+
if (method !== "loess") {
|
|
203
|
+
delete base.span;
|
|
204
|
+
delete base.robustIterations;
|
|
205
|
+
}
|
|
203
206
|
if (method === "loess" || base.method === "loess") {
|
|
204
207
|
for (const key of [
|
|
205
208
|
"confidenceMethod", "level", "confidence", "interval"
|
|
@@ -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;
|