ggaction 0.0.16 → 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 +66 -0
- package/README.md +3 -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 +10 -4
- 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/bin.js +1 -1
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/create.js +18 -5
- package/src/actions/data/density.js +1 -1
- package/src/actions/data/derived.js +15 -1
- package/src/actions/data/edit.js +20 -7
- package/src/actions/data/filter.js +15 -4
- package/src/actions/data/index.js +3 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/regression.js +6 -2
- package/src/actions/data/revise.js +37 -5
- package/src/actions/data/shared.js +33 -4
- package/src/actions/data/sort.js +21 -0
- package/src/actions/data/stack.js +3 -1
- package/src/actions/data/summary.js +1 -1
- package/src/actions/encodings/angle.js +4 -1
- package/src/actions/encodings/appearance.js +43 -31
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +5 -2
- package/src/actions/encodings/color/index.js +5 -2
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/offset.js +4 -1
- 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 +9 -3
- package/src/actions/encodings/ranged.js +23 -1
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/ruleAppearance.js +4 -1
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/stroke.js +4 -1
- package/src/actions/encodings/strokeDash.js +6 -1
- package/src/actions/encodings/text.js +12 -4
- 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 +17 -4
- package/src/actions/marks/bar/edit.js +18 -7
- 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/create.js +8 -1
- package/src/actions/marks/point/edit.js +13 -6
- package/src/actions/marks/point/materialize.js +4 -2
- package/src/actions/marks/rect/actions.js +26 -11
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +31 -12
- package/src/actions/marks/text/actions.js +42 -11
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/marks/tick/actions.js +23 -8
- package/src/actions/primitives/semanticAction.js +37 -3
- package/src/actions/primitives/semanticValidation/dataset.js +13 -1
- package/src/actions/primitives/semanticValidation/layer.js +26 -2
- package/src/actions/primitives/semanticValidation/scale.js +6 -0
- package/src/actions/regression/create.js +26 -5
- package/src/actions/regression/edit.js +50 -15
- package/src/actions/regression/follow.js +22 -0
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +20 -15
- package/src/actions/scales/consumers/common.js +2 -1
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -4
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/parallel.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +49 -2
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +83 -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/action.js +3 -2
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +5 -1
- 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 +41 -22
- 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 +382 -0
- package/src/grammar/density.js +53 -5
- 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/filter.js +133 -39
- 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 +34 -10
- package/src/grammar/itemMissing.js +59 -0
- 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 +63 -16
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/models.js +116 -59
- package/src/grammar/regression/parameters.js +69 -6
- 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 -190
- 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 +5 -3
- package/src/grammar/sort.js +88 -0
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +120 -45
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +25 -12
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspection.js +495 -0
- package/src/inspectionDescriptors.js +3 -0
- 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 +26 -3
- 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/revisionIdentity.js +19 -0
- 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 +72 -7
- 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 +17 -6
- 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 +94 -8
- 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/inspection.d.ts +17 -0
- package/types/program.d.ts +494 -112
|
@@ -29,7 +29,7 @@ function requireLineEncoding(layer) {
|
|
|
29
29
|
const y = layer.encoding?.y;
|
|
30
30
|
|
|
31
31
|
if (x === undefined) {
|
|
32
|
-
throw new Error(`Line mark "${layer.id}" requires
|
|
32
|
+
throw new Error(`Line mark "${layer.id}" requires an x encoding.`);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const binnedAggregate =
|
|
@@ -37,10 +37,10 @@ function requireLineEncoding(layer) {
|
|
|
37
37
|
x.bin !== undefined &&
|
|
38
38
|
isAggregate(y?.aggregate);
|
|
39
39
|
const aggregateMode =
|
|
40
|
-
(
|
|
40
|
+
(["temporal", "nominal", "ordinal"].includes(x?.fieldType) && isAggregate(y?.aggregate)) ||
|
|
41
41
|
binnedAggregate;
|
|
42
|
-
const
|
|
43
|
-
|
|
42
|
+
const directNumericY =
|
|
43
|
+
["quantitative", "nominal", "ordinal"].includes(x?.fieldType) &&
|
|
44
44
|
x.bin === undefined &&
|
|
45
45
|
y?.fieldType === "quantitative" &&
|
|
46
46
|
y.aggregate === undefined;
|
|
@@ -48,14 +48,14 @@ function requireLineEncoding(layer) {
|
|
|
48
48
|
y?.aggregate === undefined &&
|
|
49
49
|
((x?.fieldType === "temporal" && y?.fieldType === "quantitative") ||
|
|
50
50
|
(x?.fieldType === "quantitative" && y?.fieldType === "temporal"));
|
|
51
|
-
if (!aggregateMode && !
|
|
51
|
+
if (!aggregateMode && !directNumericY && !directTemporal) {
|
|
52
52
|
if (x?.fieldType === "temporal") {
|
|
53
53
|
throw new Error(
|
|
54
54
|
`Line mark "${layer.id}" requires a supported aggregate y encoding.`
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
throw new Error(
|
|
58
|
-
`Line mark "${layer.id}" requires temporal
|
|
58
|
+
`Line mark "${layer.id}" requires categorical/temporal aggregate or compatible direct x/y encodings.`
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
if (aggregateMode) {
|
|
@@ -66,7 +66,7 @@ function requireLineEncoding(layer) {
|
|
|
66
66
|
y,
|
|
67
67
|
isAggregate: aggregateMode,
|
|
68
68
|
binnedAggregate,
|
|
69
|
-
directRows: directTemporal ||
|
|
69
|
+
directRows: directTemporal || directNumericY,
|
|
70
70
|
directTemporal
|
|
71
71
|
};
|
|
72
72
|
}
|
|
@@ -90,9 +90,17 @@ function groupedSeries(groups, fields, dimensions) {
|
|
|
90
90
|
return series;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function freezeCartesianSeries(series) {
|
|
93
|
+
function freezeCartesianSeries(series, categoryOrder) {
|
|
94
|
+
const observedX = series.flatMap(item => item.values.map(value => value.x));
|
|
95
|
+
const observedSet = new Set(observedX);
|
|
96
|
+
if (categoryOrder !== undefined) {
|
|
97
|
+
const domain = new Set(categoryOrder);
|
|
98
|
+
if (observedX.some(value => !domain.has(value))) {
|
|
99
|
+
throw new Error("Line categorical x domain must contain every observed vertex.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
94
102
|
return cloneAndFreeze({
|
|
95
|
-
xValues:
|
|
103
|
+
xValues: categoryOrder === undefined ? observedX : categoryOrder.filter(value => observedSet.has(value)),
|
|
96
104
|
yValues: series.flatMap(item => item.values.map(value => value.y)),
|
|
97
105
|
series
|
|
98
106
|
});
|
|
@@ -122,7 +130,9 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
|
|
|
122
130
|
if (isAggregate) {
|
|
123
131
|
validateAggregateFieldValues(rows, y.field, y.fieldType);
|
|
124
132
|
}
|
|
125
|
-
const
|
|
133
|
+
const categorical = ["nominal", "ordinal"].includes(x.fieldType);
|
|
134
|
+
const xValues = categorical ? readNominalField(rows, x.field)
|
|
135
|
+
: x.fieldType === "temporal"
|
|
126
136
|
? readTemporalField(rows, x.field, x.temporalUnit)
|
|
127
137
|
: readQuantitativeField(rows, x.field);
|
|
128
138
|
const yValues = isAggregate
|
|
@@ -130,6 +140,11 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
|
|
|
130
140
|
: y.fieldType === "temporal"
|
|
131
141
|
? readTemporalField(rows, y.field, y.temporalUnit)
|
|
132
142
|
: readQuantitativeField(rows, y.field);
|
|
143
|
+
const categoryOrder = categorical ? options.xDomain ?? [...new Set(xValues)] : undefined;
|
|
144
|
+
const ranks = new Map(categoryOrder?.map((value, index) => [value, index]));
|
|
145
|
+
const compareX = categorical
|
|
146
|
+
? (left, right) => ranks.get(left.x) - ranks.get(right.x)
|
|
147
|
+
: (left, right) => left.x - right.x;
|
|
133
148
|
const seriesFields = validatePathSeriesAppearance(rows, layer);
|
|
134
149
|
const binBoundaries = binnedAggregate
|
|
135
150
|
? options.xBinBoundaries ?? resolveLineBins(rows, layer).boundaries
|
|
@@ -159,14 +174,14 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
|
|
|
159
174
|
item.orderValues,
|
|
160
175
|
pathOrder.order
|
|
161
176
|
);
|
|
162
|
-
return
|
|
177
|
+
return [{ key: item.key, values }];
|
|
163
178
|
});
|
|
164
179
|
if (series.length === 0) {
|
|
165
180
|
throw new Error(
|
|
166
181
|
`Line series on mark "${layer.id}" requires at least two ordered points.`
|
|
167
182
|
);
|
|
168
183
|
}
|
|
169
|
-
return freezeCartesianSeries(series);
|
|
184
|
+
return freezeCartesianSeries(series, categoryOrder);
|
|
170
185
|
}
|
|
171
186
|
|
|
172
187
|
if (directRows) {
|
|
@@ -181,16 +196,16 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
|
|
|
181
196
|
const orderBy = directTemporal && y.fieldType === "temporal" ? "y" : "x";
|
|
182
197
|
const series = [...groups.values()].flatMap(item => {
|
|
183
198
|
const values = item.values.sort(
|
|
184
|
-
(left, right) => left
|
|
199
|
+
orderBy === "x" ? compareX : (left, right) => left.y - right.y
|
|
185
200
|
);
|
|
186
|
-
return
|
|
201
|
+
return [{ key: item.key, values }];
|
|
187
202
|
});
|
|
188
203
|
if (series.length === 0) {
|
|
189
204
|
throw new Error(
|
|
190
205
|
`Line series on mark "${layer.id}" requires at least two direct points.`
|
|
191
206
|
);
|
|
192
207
|
}
|
|
193
|
-
return freezeCartesianSeries(series);
|
|
208
|
+
return freezeCartesianSeries(series, categoryOrder);
|
|
194
209
|
}
|
|
195
210
|
const aggregateGroups = new Map();
|
|
196
211
|
|
|
@@ -241,7 +256,7 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
|
|
|
241
256
|
}
|
|
242
257
|
|
|
243
258
|
const series = [...seriesGroups.values()].flatMap(item => {
|
|
244
|
-
const values = item.values.sort(
|
|
259
|
+
const values = item.values.sort(compareX);
|
|
245
260
|
|
|
246
261
|
if (values.length < 2) {
|
|
247
262
|
return [];
|
|
@@ -256,7 +271,7 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
|
|
|
256
271
|
);
|
|
257
272
|
}
|
|
258
273
|
|
|
259
|
-
return freezeCartesianSeries(series);
|
|
274
|
+
return freezeCartesianSeries(series, categoryOrder);
|
|
260
275
|
}
|
|
261
276
|
|
|
262
277
|
function requirePolarLineEncoding(layer) {
|
|
@@ -35,7 +35,8 @@ export function normalizeMarkLabelContent(source, { content, normalizeBy }) {
|
|
|
35
35
|
}
|
|
36
36
|
if (source.mark.type === "bar") {
|
|
37
37
|
const grain = resolveBarGrain(source);
|
|
38
|
-
if (grain === BAR_GRAINS.ranged
|
|
38
|
+
if (grain === BAR_GRAINS.ranged || grain === BAR_GRAINS.centered &&
|
|
39
|
+
source.encoding[`${resolveBarChannels(source).measure}2`] !== undefined) {
|
|
39
40
|
throw new Error("Ranged Bar text requires an explicit field or value.");
|
|
40
41
|
}
|
|
41
42
|
if (content === "category" && grain === BAR_GRAINS.histogram) {
|
|
@@ -63,10 +64,12 @@ function itemValue(source, item) {
|
|
|
63
64
|
);
|
|
64
65
|
}
|
|
65
66
|
const measure = source.encoding[resolveBarChannels(source).measure];
|
|
67
|
+
if (resolveBarGrain(source) === BAR_GRAINS.centered) return item.channels[`${resolveBarChannels(source).measure}2`];
|
|
66
68
|
return measure.aggregate === "count" ? item.members.length
|
|
67
69
|
: aggregateRows(item.members, measure.field, measure.aggregate);
|
|
68
70
|
}
|
|
69
|
-
if (source.encoding?.radius !== undefined
|
|
71
|
+
if (source.encoding?.radius !== undefined && source.encoding.theta.fieldType !== "quantitative" &&
|
|
72
|
+
source.encoding.theta.aggregate === undefined) return item.channels.radius;
|
|
70
73
|
const theta = source.encoding.theta;
|
|
71
74
|
if (theta.fieldType === "quantitative") return item.channels.theta;
|
|
72
75
|
return theta.aggregate === "count" ? item.members.length
|
package/src/grammar/normalize.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
|
inverseLerp,
|
|
@@ -15,23 +18,9 @@ const METHODS = Object.freeze([
|
|
|
15
18
|
"share", "zscore", "minmax", "index", "change", "percentChange"
|
|
16
19
|
]);
|
|
17
20
|
const ZERO_POLICIES = Object.freeze(["error", "null", "zero"]);
|
|
18
|
-
const SORT_KEYS = Object.freeze(["field", "order"]);
|
|
19
|
-
const ORDERS = Object.freeze(["ascending", "descending"]);
|
|
20
21
|
const BASELINE_KEYS = Object.freeze(["position", "value"]);
|
|
21
22
|
const BASELINE_METHODS = new Set(["index", "change", "percentChange"]);
|
|
22
23
|
|
|
23
|
-
function rejectUnknownKeys(value, supported, label) {
|
|
24
|
-
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
25
|
-
if (unknown !== undefined) throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function requireField(value, label) {
|
|
29
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
30
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
31
|
-
}
|
|
32
|
-
return value;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
24
|
function normalizeGroupBy(value) {
|
|
36
25
|
if (value === undefined) return [];
|
|
37
26
|
return Array.isArray(value) ? [...value] : [value];
|
|
@@ -48,33 +37,6 @@ function validateGroupBy(groupBy) {
|
|
|
48
37
|
}
|
|
49
38
|
}
|
|
50
39
|
|
|
51
|
-
function normalizeSortBy(value) {
|
|
52
|
-
if (value === undefined) return [];
|
|
53
|
-
if (!Array.isArray(value)) return value;
|
|
54
|
-
return value.map(sort => isPlainObject(sort)
|
|
55
|
-
? { ...sort, order: sort.order ?? "ascending" }
|
|
56
|
-
: sort
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function validateSortBy(sortBy) {
|
|
61
|
-
if (!Array.isArray(sortBy)) throw new TypeError("Normalize sortBy must be an array.");
|
|
62
|
-
const fields = [];
|
|
63
|
-
sortBy.forEach((sort, index) => {
|
|
64
|
-
if (!isPlainObject(sort)) {
|
|
65
|
-
throw new TypeError(`Normalize sortBy[${index}] must be a plain object.`);
|
|
66
|
-
}
|
|
67
|
-
rejectUnknownKeys(sort, SORT_KEYS, `normalize sortBy[${index}]`);
|
|
68
|
-
fields.push(requireField(sort.field, `Normalize sortBy[${index}].field`));
|
|
69
|
-
if (!ORDERS.includes(sort.order)) {
|
|
70
|
-
throw new Error(`Unsupported normalize sort order "${sort.order}".`);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
if (new Set(fields).size !== fields.length) {
|
|
74
|
-
throw new Error("Normalize sortBy fields must be unique.");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
40
|
function normalizeBaseline(value) {
|
|
79
41
|
if (!isPlainObject(value)) return value;
|
|
80
42
|
return { ...value };
|
|
@@ -136,7 +98,7 @@ export function validateNormalizeTransform(transform) {
|
|
|
136
98
|
throw new Error("Normalize zeroDenominator is not available for change.");
|
|
137
99
|
}
|
|
138
100
|
if (BASELINE_METHODS.has(transform.method)) {
|
|
139
|
-
validateSortBy(transform.sortBy);
|
|
101
|
+
validateSortBy(transform.sortBy, "Normalize");
|
|
140
102
|
validateBaseline(transform.baseline, transform.sortBy);
|
|
141
103
|
} else {
|
|
142
104
|
if (hasBaseline) {
|
|
@@ -184,18 +146,6 @@ export function normalizeNormalizeTransform(args = {}) {
|
|
|
184
146
|
return cloneAndFreeze(transform);
|
|
185
147
|
}
|
|
186
148
|
|
|
187
|
-
function scalarKey(value, label) {
|
|
188
|
-
if (value === null) return "null";
|
|
189
|
-
if (typeof value === "string") return `string:${value.length}:${value}`;
|
|
190
|
-
if (typeof value === "boolean") return `boolean:${value}`;
|
|
191
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
192
|
-
return `number:${Object.is(value, -0) ? 0 : value}`;
|
|
193
|
-
}
|
|
194
|
-
throw new TypeError(
|
|
195
|
-
`${label} must contain null, strings, booleans, or finite numbers.`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
149
|
function compareScalar(left, right, label) {
|
|
200
150
|
const leftMissing = left === null || left === undefined;
|
|
201
151
|
const rightMissing = right === null || right === undefined;
|
package/src/grammar/numeric.js
CHANGED
|
@@ -1,290 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function finiteArray(values, label) {
|
|
4
|
-
if (!Array.isArray(values) || !values.every(Number.isFinite)) {
|
|
5
|
-
throw new TypeError(`${label} must contain finite numbers.`);
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function rangeError(label) {
|
|
10
|
-
return new RangeError(`${label} is outside the finite numeric range.`);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function compensatedSum(values, scale = 1) {
|
|
14
|
-
let total = 0;
|
|
15
|
-
let correction = 0;
|
|
16
|
-
for (const source of values) {
|
|
17
|
-
const value = source / scale;
|
|
18
|
-
const next = total + value;
|
|
19
|
-
correction += Math.abs(total) >= Math.abs(value)
|
|
20
|
-
? total - next + value
|
|
21
|
-
: value - next + total;
|
|
22
|
-
total = next;
|
|
23
|
-
}
|
|
24
|
-
return total + correction;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function maximumMagnitude(values) {
|
|
28
|
-
let maximum = 0;
|
|
29
|
-
for (const value of values) maximum = Math.max(maximum, Math.abs(value));
|
|
30
|
-
return maximum;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function normalizedFiniteSum(values, label = "Numeric values") {
|
|
34
|
-
finiteArray(values, label);
|
|
35
|
-
const scale = maximumMagnitude(values) || 1;
|
|
36
|
-
return { scale, total: compensatedSum(values, scale) };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function requireFiniteResult(value, label = "Numeric result") {
|
|
40
|
-
if (!Number.isFinite(value)) throw rangeError(label);
|
|
41
|
-
return value === 0 ? 0 : value;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function restoreFiniteScale(value, scale, label = "Numeric result") {
|
|
45
|
-
if (!Number.isFinite(value) || !Number.isFinite(scale) || scale < 0) {
|
|
46
|
-
throw rangeError(label);
|
|
47
|
-
}
|
|
48
|
-
return requireFiniteResult(value * scale, label);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function stableReducedSum(values, label, divisor = 1) {
|
|
52
|
-
finiteArray(values, label);
|
|
53
|
-
const ordinary = values.reduce((sum, value) => sum + value, 0);
|
|
54
|
-
if (Number.isFinite(ordinary)) {
|
|
55
|
-
return ordinary === 0 ? 0 : ordinary / divisor;
|
|
56
|
-
}
|
|
57
|
-
const scale = maximumMagnitude(values) || 1;
|
|
58
|
-
return restoreFiniteScale(
|
|
59
|
-
compensatedSum(values, scale) / divisor, scale, label
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function stableFiniteSum(values, label = "Numeric sum") {
|
|
64
|
-
return stableReducedSum(values, label);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function stableFinitePrefixSums(values, label = "Numeric sum") {
|
|
68
|
-
const normalized = normalizedFiniteSum(values, label);
|
|
69
|
-
let ordinary = 0;
|
|
70
|
-
let sum = 0;
|
|
71
|
-
let correction = 0;
|
|
72
|
-
return values.map(value => {
|
|
73
|
-
ordinary += value;
|
|
74
|
-
const scaled = value / normalized.scale;
|
|
75
|
-
const next = sum + scaled;
|
|
76
|
-
correction += Math.abs(sum) >= Math.abs(scaled)
|
|
77
|
-
? sum - next + scaled
|
|
78
|
-
: scaled - next + sum;
|
|
79
|
-
sum = next;
|
|
80
|
-
return Number.isFinite(ordinary)
|
|
81
|
-
? ordinary === 0 ? 0 : ordinary
|
|
82
|
-
: restoreFiniteScale(sum + correction, normalized.scale, label);
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function stableFiniteMean(values, label = "Numeric mean") {
|
|
87
|
-
finiteArray(values, label);
|
|
88
|
-
if (values.length === 0) {
|
|
89
|
-
throw new RangeError(`${label} requires at least one value.`);
|
|
90
|
-
}
|
|
91
|
-
return stableReducedSum(values, label, values.length);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function stableFiniteSquareSum(values, label = "Numeric square sum") {
|
|
95
|
-
finiteArray(values, label);
|
|
96
|
-
const ordinary = values.reduce((sum, value) => sum + value ** 2, 0);
|
|
97
|
-
if (Number.isFinite(ordinary)) return ordinary;
|
|
98
|
-
const scale = maximumMagnitude(values);
|
|
99
|
-
if (scale === 0) return 0;
|
|
100
|
-
const normalized = values.reduce(
|
|
101
|
-
(sum, value) => sum + (value / scale) ** 2,
|
|
102
|
-
0
|
|
103
|
-
);
|
|
104
|
-
const magnitude = restoreFiniteScale(Math.sqrt(normalized), scale, label);
|
|
105
|
-
return requireFiniteResult(magnitude ** 2, label);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function stableFiniteDeviation(values, {
|
|
109
|
-
sample = false,
|
|
110
|
-
divisor = 1,
|
|
111
|
-
label = "Numeric deviation"
|
|
112
|
-
} = {}) {
|
|
113
|
-
if (!Number.isFinite(divisor) || divisor <= 0) {
|
|
114
|
-
throw new RangeError(`${label} divisor must be positive and finite.`);
|
|
115
|
-
}
|
|
116
|
-
const denominator = values.length - (sample ? 1 : 0);
|
|
117
|
-
if (denominator <= 0) {
|
|
118
|
-
throw new RangeError(`${label} requires more values.`);
|
|
119
|
-
}
|
|
120
|
-
const mean = stableFiniteMean(values, `${label} mean`);
|
|
121
|
-
const differences = values.map(value => value - mean);
|
|
122
|
-
const squared = differences.reduce(
|
|
123
|
-
(sum, difference) => sum + difference ** 2,
|
|
124
|
-
0
|
|
125
|
-
);
|
|
126
|
-
if (Number.isFinite(squared)) {
|
|
127
|
-
return {
|
|
128
|
-
mean,
|
|
129
|
-
deviation: Math.sqrt(squared / denominator) / divisor,
|
|
130
|
-
squared
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
const finiteDifferences = differences.every(Number.isFinite);
|
|
134
|
-
const scale = finiteDifferences
|
|
135
|
-
? maximumMagnitude(differences)
|
|
136
|
-
: Math.max(Math.abs(mean), maximumMagnitude(values));
|
|
137
|
-
if (scale === 0) return { mean, deviation: 0 };
|
|
138
|
-
const normalizedSquares = values.reduce((sum, value, index) => {
|
|
139
|
-
const difference = finiteDifferences
|
|
140
|
-
? differences[index] / scale
|
|
141
|
-
: value / scale - mean / scale;
|
|
142
|
-
return sum + difference ** 2;
|
|
143
|
-
}, 0);
|
|
144
|
-
const deviation = restoreFiniteScale(
|
|
145
|
-
Math.sqrt(normalizedSquares / denominator) / divisor,
|
|
146
|
-
scale,
|
|
147
|
-
label
|
|
148
|
-
);
|
|
149
|
-
return { mean, deviation };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function numericExtent(values) {
|
|
153
|
-
let minimum = Infinity;
|
|
154
|
-
let maximum = -Infinity;
|
|
155
|
-
for (const value of values) {
|
|
156
|
-
minimum = Math.min(minimum, value);
|
|
157
|
-
maximum = Math.max(maximum, value);
|
|
158
|
-
}
|
|
159
|
-
return [minimum, maximum];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function normalizeNumericRange(start, end) {
|
|
163
|
-
const magnitude = Math.max(Math.abs(start), Math.abs(end));
|
|
164
|
-
if (magnitude === 0) {
|
|
165
|
-
return { scale: 1, start: 0, end: 0, span: 0 };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const decimalScale = 10 ** Math.floor(Math.log10(magnitude));
|
|
169
|
-
const scale = Number.isFinite(decimalScale) && decimalScale > 0
|
|
170
|
-
? decimalScale
|
|
171
|
-
: magnitude;
|
|
172
|
-
start /= scale;
|
|
173
|
-
end /= scale;
|
|
174
|
-
return {
|
|
175
|
-
scale,
|
|
176
|
-
start,
|
|
177
|
-
end,
|
|
178
|
-
span: end - start
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function niceNumericStep(
|
|
183
|
-
span,
|
|
184
|
-
count = 5,
|
|
185
|
-
factors = NICE_FACTORS
|
|
186
|
-
) {
|
|
187
|
-
if (!Number.isFinite(span) || span <= 0) {
|
|
188
|
-
throw new RangeError("Nice numeric span must be positive and finite.");
|
|
189
|
-
}
|
|
190
|
-
const rough = span / Math.max(1, count);
|
|
191
|
-
const power = 10 ** Math.floor(Math.log10(rough));
|
|
192
|
-
const fraction = rough / power;
|
|
193
|
-
const factor = factors.find(candidate => candidate >= fraction) ?? 10;
|
|
194
|
-
return factor * power;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function alignNumericStep(value, step, direction) {
|
|
198
|
-
let quotient = value / step;
|
|
199
|
-
const nearest = Math.round(quotient);
|
|
200
|
-
if (Math.abs(quotient - nearest) <= 1e-10) quotient = nearest;
|
|
201
|
-
return (direction === "floor" ? Math.floor(quotient) : Math.ceil(quotient)) * step;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function cleanNumericValue(value, resolution) {
|
|
205
|
-
if (!Number.isFinite(value) || value === 0) return value === 0 ? 0 : value;
|
|
206
|
-
const rounded = Number(value.toPrecision(15));
|
|
207
|
-
return Number.isFinite(resolution) && resolution > 0 &&
|
|
208
|
-
Math.abs(rounded - value) <= resolution * 1e-10
|
|
209
|
-
? rounded
|
|
210
|
-
: value;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export function inverseLerp(value, start, end) {
|
|
214
|
-
if (value === start) return 0;
|
|
215
|
-
if (value === end) return 1;
|
|
216
|
-
|
|
217
|
-
const span = end - start;
|
|
218
|
-
const offset = value - start;
|
|
219
|
-
if (Number.isFinite(span) && span !== 0 && Number.isFinite(offset)) {
|
|
220
|
-
return offset / span;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const scale = Math.max(Math.abs(value), Math.abs(start), Math.abs(end));
|
|
224
|
-
return (value / scale - start / scale) / (end / scale - start / scale);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function interpolateNumber(start, end, proportion) {
|
|
228
|
-
let result = proportion === 0 ? start : proportion === 1 ? end : undefined;
|
|
229
|
-
if (result === undefined) {
|
|
230
|
-
const span = end - start;
|
|
231
|
-
if (Number.isFinite(span)) {
|
|
232
|
-
result = start + proportion * span;
|
|
233
|
-
} else {
|
|
234
|
-
const scale = Math.max(Math.abs(start), Math.abs(end));
|
|
235
|
-
result = scale * (
|
|
236
|
-
start / scale * (1 - proportion) + end / scale * proportion
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return requireFiniteResult(result, "Numeric interpolation");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function finiteMidpoint(start, end) {
|
|
244
|
-
const sum = start + end;
|
|
245
|
-
const result = Number.isFinite(sum) ? sum / 2 : start / 2 + end / 2;
|
|
246
|
-
return requireFiniteResult(result, "Numeric midpoint");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function sampleNumericRange(start, end, count, label = "Numeric range") {
|
|
250
|
-
const resolution = Math.abs((end - start) / (count - 1));
|
|
251
|
-
const values = [];
|
|
252
|
-
const direction = Math.sign(end - start);
|
|
253
|
-
for (let index = 0; index < count; index += 1) {
|
|
254
|
-
const value = cleanNumericValue(
|
|
255
|
-
interpolateNumber(start, end, index / (count - 1)),
|
|
256
|
-
resolution
|
|
257
|
-
);
|
|
258
|
-
if (
|
|
259
|
-
!Number.isFinite(value) ||
|
|
260
|
-
(direction !== 0 && index > 0 &&
|
|
261
|
-
direction * (value - values[index - 1]) <= 0)
|
|
262
|
-
) {
|
|
263
|
-
throw new RangeError(`${label} cannot represent ${count} distinct samples.`);
|
|
264
|
-
}
|
|
265
|
-
values.push(value);
|
|
266
|
-
}
|
|
267
|
-
return values;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export function formatDistinctNumericSamples(values, initialFormat) {
|
|
271
|
-
let labels = values.map(initialFormat ?? (value =>
|
|
272
|
-
String(+value.toPrecision(3))
|
|
273
|
-
));
|
|
274
|
-
const distinct = () => labels.every((label, index) =>
|
|
275
|
-
index === 0 || values[index] === values[index - 1] ||
|
|
276
|
-
label !== labels[index - 1]
|
|
277
|
-
);
|
|
278
|
-
for (let precision = 4; !distinct() && precision <= 17; precision += 1) {
|
|
279
|
-
labels = values.map(value => String(+value.toPrecision(precision)));
|
|
280
|
-
}
|
|
281
|
-
return distinct() ? labels : values.map(String);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function stableDecimal(value, places = 12) {
|
|
285
|
-
if (value === 0) return 0;
|
|
286
|
-
const threshold = 10 ** -places;
|
|
287
|
-
return Math.abs(value) < threshold
|
|
288
|
-
? Number(value.toPrecision(places))
|
|
289
|
-
: Number(value.toFixed(places));
|
|
290
|
-
}
|
|
1
|
+
const q=Object.freeze([1,2,3,5]);function h(t,e){if(!Array.isArray(t)||!t.every(Number.isFinite))throw new TypeError(`${e} must contain finite numbers.`)}function p(t){return new RangeError(`${t} is outside the finite numeric range.`)}function x(t,e=1){let n=0,r=0;for(const i of t){const o=i/e,c=n+o;r+=Math.abs(n)>=Math.abs(o)?n-c+o:o-c+n,n=c}return n+r}function f(t){let e=0;for(const n of t)e=Math.max(e,Math.abs(n));return e}function y(t,e="Numeric values"){h(t,e);const n=f(t)||1;return{scale:n,total:x(t,n)}}function b(t,e="Numeric result"){if(!Number.isFinite(t))throw p(e);return t===0?0:t}function N(t,e,n="Numeric result"){if(!Number.isFinite(t)||!Number.isFinite(e)||e<0)throw p(n);return b(t*e,n)}function d(t,e,n=1){h(t,e);const r=t.reduce((o,c)=>o+c,0);if(Number.isFinite(r))return r===0?0:r/n;const i=f(t)||1;return N(x(t,i)/n,i,e)}function $(t,e="Numeric sum"){return d(t,e)}function z(t,e="Numeric sum"){const n=y(t,e);let r=0,i=0,o=0;return t.map(c=>{r+=c;const u=c/n.scale,s=i+u;return o+=Math.abs(i)>=Math.abs(u)?i-s+u:u-s+i,i=s,Number.isFinite(r)?r===0?0:r:N(i+o,n.scale,e)})}function R(t,e="Numeric mean"){if(h(t,e),t.length===0)throw new RangeError(`${e} requires at least one value.`);return d(t,e,t.length)}function P(t,e="Numeric square sum"){h(t,e);const n=t.reduce((c,u)=>c+u**2,0);if(Number.isFinite(n))return n;const r=f(t);if(r===0)return 0;const i=t.reduce((c,u)=>c+(u/r)**2,0),o=N(Math.sqrt(i),r,e);return b(o**2,e)}function A(t,{sample:e=!1,divisor:n=1,label:r="Numeric deviation"}={}){if(!Number.isFinite(n)||n<=0)throw new RangeError(`${r} divisor must be positive and finite.`);const i=t.length-(e?1:0);if(i<=0)throw new RangeError(`${r} requires more values.`);const o=R(t,`${r} mean`),c=t.map(a=>a-o),u=c.reduce((a,M)=>a+M**2,0);if(Number.isFinite(u))return{mean:o,deviation:Math.sqrt(u/i)/n,squared:u};const s=c.every(Number.isFinite),m=s?f(c):Math.max(Math.abs(o),f(t));if(m===0)return{mean:o,deviation:0};const g=t.reduce((a,M,S)=>{const w=s?c[S]/m:M/m-o/m;return a+w**2},0),l=N(Math.sqrt(g/i)/n,m,r);return{mean:o,deviation:l}}function D(t,e){const n=(t.length-1)*e,r=Math.floor(n),i=Math.ceil(n);return F(t[r],t[i],n-r)}function I(t){let e=1/0,n=-1/0;for(const r of t)e=Math.min(e,r),n=Math.max(n,r);return[e,n]}function C(t,e){const n=Math.max(Math.abs(t),Math.abs(e));if(n===0)return{scale:1,start:0,end:0,span:0};const r=10**Math.floor(Math.log10(n)),i=Number.isFinite(r)&&r>0?r:n;return t/=i,e/=i,{scale:i,start:t,end:e,span:e-t}}function O(t,e=5,n=q){if(!Number.isFinite(t)||t<=0)throw new RangeError("Nice numeric span must be positive and finite.");const r=t/Math.max(1,e),i=10**Math.floor(Math.log10(r)),o=r/i;return(n.find(u=>u>=o)??10)*i}function T(t,e,n){let r=t/e;const i=Math.round(r);return Math.abs(r-i)<=1e-10&&(r=i),(n==="floor"?Math.floor(r):Math.ceil(r))*e}function E(t,e){if(!Number.isFinite(t)||t===0)return t===0?0:t;const n=Number(t.toPrecision(15));return Number.isFinite(e)&&e>0&&Math.abs(n-t)<=e*1e-10?n:t}function j(t,e,n){if(t===e)return 0;if(t===n)return 1;const r=n-e,i=t-e;if(Number.isFinite(r)&&r!==0&&Number.isFinite(i))return i/r;const o=Math.max(Math.abs(t),Math.abs(e),Math.abs(n));return(t/o-e/o)/(n/o-e/o)}function F(t,e,n){let r=n===0?t:n===1?e:void 0;if(r===void 0){const i=e-t;if(Number.isFinite(i))r=t+n*i;else{const o=Math.max(Math.abs(t),Math.abs(e));r=o*(t/o*(1-n)+e/o*n)}}return b(r,"Numeric interpolation")}function L(t,e){const n=t+e,r=Number.isFinite(n)?n/2:t/2+e/2;return b(r,"Numeric midpoint")}function V(t,e,n,r="Numeric range"){const i=Math.abs((e-t)/(n-1)),o=[],c=Math.sign(e-t);for(let u=0;u<n;u+=1){const s=E(F(t,e,u/(n-1)),i);if(!Number.isFinite(s)||c!==0&&u>0&&c*(s-o[u-1])<=0)throw new RangeError(`${r} cannot represent ${n} distinct samples.`);o.push(s)}return o}function _(t,e){let n=t.map(e??(i=>String(+i.toPrecision(3))));const r=()=>n.every((i,o)=>o===0||t[o]===t[o-1]||i!==n[o-1]);for(let i=4;!r()&&i<=17;i+=1)n=t.map(o=>String(+o.toPrecision(i)));return r()?n:t.map(String)}function k(t,e=12){if(t===0)return 0;const n=10**-e;return Math.abs(t)<n?Number(t.toPrecision(e)):Number(t.toFixed(e))}export{q as NICE_FACTORS,T as alignNumericStep,E as cleanNumericValue,L as finiteMidpoint,_ as formatDistinctNumericSamples,F as interpolateNumber,j as inverseLerp,f as maximumMagnitude,O as niceNumericStep,C as normalizeNumericRange,y as normalizedFiniteSum,I as numericExtent,D as quantileSorted,b as requireFiniteResult,N as restoreFiniteScale,V as sampleNumericRange,k as stableDecimal,A as stableFiniteDeviation,R as stableFiniteMean,z as stableFinitePrefixSums,P as stableFiniteSquareSum,$ as stableFiniteSum};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requireStringValue as nonEmptyString } from "../core/validation.js";
|
|
1
2
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
3
|
import { validateUserId } from "../core/identifiers.js";
|
|
3
4
|
import { validateOptionObject } from "../core/validation.js";
|
|
@@ -18,12 +19,6 @@ const DIMENSION_SCALE_OPTIONS = Object.freeze([
|
|
|
18
19
|
"padding", "align", "unknown"
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
|
-
function nonEmptyString(value, label) {
|
|
22
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
23
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
22
|
|
|
28
23
|
export function validateParallelKeyField(value) {
|
|
29
24
|
return nonEmptyString(value, "Parallel key field");
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { normalizeTemporalValue } from "./scales/fields.js";
|
|
2
|
+
|
|
3
|
+
export const PARSE_TIME_FORMATS = Object.freeze([
|
|
4
|
+
"auto", "%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y"
|
|
5
|
+
]);
|
|
6
|
+
|
|
7
|
+
export function validateParseTimeFormat(format = "auto") {
|
|
8
|
+
if (!PARSE_TIME_FORMATS.includes(format)) {
|
|
9
|
+
throw new Error(`Unsupported parseTime format "${format}".`);
|
|
10
|
+
}
|
|
11
|
+
return format;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Explicit calendar formats are UTC and reject rollover, rather than guessing
|
|
15
|
+
// the day/month order. Invalid or nullish values remain missing data as null.
|
|
16
|
+
export function parseTimeValue(value, format = "auto") {
|
|
17
|
+
validateParseTimeFormat(format);
|
|
18
|
+
if (value === null || value === undefined) return null;
|
|
19
|
+
if (format === "auto") {
|
|
20
|
+
try { return normalizeTemporalValue(value); }
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error instanceof TypeError || error instanceof RangeError) return null;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (typeof value !== "string") return null;
|
|
27
|
+
const match = (format === "%Y-%m-%d"
|
|
28
|
+
? /^(\d{4})-(\d{1,2})-(\d{1,2})$/
|
|
29
|
+
: /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/).exec(value.trim());
|
|
30
|
+
if (!match) return null;
|
|
31
|
+
const numbers = match.slice(1).map(Number);
|
|
32
|
+
const [year, month, day] = format === "%Y-%m-%d" ? numbers
|
|
33
|
+
: format === "%d/%m/%Y" ? [numbers[2], numbers[1], numbers[0]]
|
|
34
|
+
: [numbers[2], numbers[0], numbers[1]];
|
|
35
|
+
const date = new Date(0);
|
|
36
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
37
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
38
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1
|
|
39
|
+
&& date.getUTCDate() === day && Number.isFinite(date.getTime())
|
|
40
|
+
? date.getTime() : null;
|
|
41
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { freezeOwned, isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { validateGeneratedItemLimit } from "../core/validation.js";
|
|
3
3
|
|
|
4
|
+
export const MAX_PATH_COMMANDS = 20_000;
|
|
5
|
+
|
|
6
|
+
export function validatePathCommandCount(count, label) {
|
|
7
|
+
return validateGeneratedItemLimit(count, label, MAX_PATH_COMMANDS);
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
const COMMAND_KEYS = Object.freeze({
|
|
5
11
|
M: Object.freeze(["op", "x", "y"]),
|
|
6
12
|
L: Object.freeze(["op", "x", "y"]),
|
|
@@ -35,14 +41,15 @@ function validateCommand(command, index) {
|
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export function validatePathCommands(commands) {
|
|
38
|
-
if (!Array.isArray(commands) || commands.length <
|
|
39
|
-
throw new TypeError("path.commands must contain at least
|
|
44
|
+
if (!Array.isArray(commands) || commands.length < 1) {
|
|
45
|
+
throw new TypeError("path.commands must contain at least one command.");
|
|
40
46
|
}
|
|
41
|
-
|
|
47
|
+
validatePathCommandCount(commands.length, "path.commands length");
|
|
42
48
|
commands.forEach(validateCommand);
|
|
43
49
|
if (commands[0].op !== "M") {
|
|
44
50
|
throw new Error("path.commands must start with M.");
|
|
45
51
|
}
|
|
52
|
+
if (commands.length === 1) return commands;
|
|
46
53
|
if (commands.slice(1).some(command => command.op === "M")) {
|
|
47
54
|
throw new Error("path.commands supports one subpath and only one initial M.");
|
|
48
55
|
}
|
|
@@ -79,7 +86,7 @@ export function buildLinearPathCommands(points, { close = false } = {}) {
|
|
|
79
86
|
if (typeof close !== "boolean") {
|
|
80
87
|
throw new TypeError("Linear path close must be a boolean.");
|
|
81
88
|
}
|
|
82
|
-
|
|
89
|
+
validatePathCommandCount(
|
|
83
90
|
points.length + Number(close),
|
|
84
91
|
"Linear path command count"
|
|
85
92
|
);
|