ggaction 0.0.21 → 0.0.23
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 +90 -0
- package/README.md +83 -90
- 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/package.json +11 -4
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +3 -3
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +22 -36
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/components.js +1 -4
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/regression/create.js +6 -1
- package/src/actions/regression/edit.js +10 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +26 -7
- package/src/core/datasetReferences.js +17 -0
- package/src/core/facetAxisPolicy.js +30 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/curveCommands.js +35 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +32 -2
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +7 -5
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/regression/parameters.js +7 -0
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/timeUnit.js +9 -3
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/guideCollisions.js +9 -3
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetContentBounds.js +96 -0
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +39 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +30 -1
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +114 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +286 -70
- package/types/scene.d.ts +35 -0
|
@@ -1,376 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
readNominalField,
|
|
4
|
-
readQuantitativeField,
|
|
5
|
-
readTemporalField
|
|
6
|
-
} from "./scales/index.js";
|
|
7
|
-
import {
|
|
8
|
-
aggregateRows,
|
|
9
|
-
isAggregate,
|
|
10
|
-
validateAggregateFieldType,
|
|
11
|
-
validateAggregateFieldValues
|
|
12
|
-
} from "./aggregate.js";
|
|
13
|
-
import { stableOrderPathValues } from "./pathOrder.js";
|
|
14
|
-
import { finiteMidpoint } from "./numeric.js";
|
|
15
|
-
import {
|
|
16
|
-
findHistogramBinIndex,
|
|
17
|
-
resolveHistogramBins
|
|
18
|
-
} from "./histogram.js";
|
|
19
|
-
import {
|
|
20
|
-
validatePathSeriesAppearance
|
|
21
|
-
} from "./pathSeries.js";
|
|
22
|
-
|
|
23
|
-
function requireLineEncoding(layer) {
|
|
24
|
-
if (layer?.mark?.type !== "line") {
|
|
25
|
-
throw new Error("Line series derivation requires a semantic line mark.");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const x = layer.encoding?.x;
|
|
29
|
-
const y = layer.encoding?.y;
|
|
30
|
-
|
|
31
|
-
if (x === undefined) {
|
|
32
|
-
throw new Error(`Line mark "${layer.id}" requires an x encoding.`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const binnedAggregate =
|
|
36
|
-
x?.fieldType === "quantitative" &&
|
|
37
|
-
x.bin !== undefined &&
|
|
38
|
-
isAggregate(y?.aggregate);
|
|
39
|
-
const aggregateMode =
|
|
40
|
-
(["temporal", "nominal", "ordinal"].includes(x?.fieldType) && isAggregate(y?.aggregate)) ||
|
|
41
|
-
binnedAggregate;
|
|
42
|
-
const directNumericY =
|
|
43
|
-
["quantitative", "nominal", "ordinal"].includes(x?.fieldType) &&
|
|
44
|
-
x.bin === undefined &&
|
|
45
|
-
y?.fieldType === "quantitative" &&
|
|
46
|
-
y.aggregate === undefined;
|
|
47
|
-
const directTemporal =
|
|
48
|
-
y?.aggregate === undefined &&
|
|
49
|
-
((x?.fieldType === "temporal" && y?.fieldType === "quantitative") ||
|
|
50
|
-
(x?.fieldType === "quantitative" && y?.fieldType === "temporal"));
|
|
51
|
-
if (!aggregateMode && !directNumericY && !directTemporal) {
|
|
52
|
-
if (x?.fieldType === "temporal") {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Line mark "${layer.id}" requires a supported aggregate y encoding.`
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
throw new Error(
|
|
58
|
-
`Line mark "${layer.id}" requires categorical/temporal aggregate or compatible direct x/y encodings.`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
if (aggregateMode) {
|
|
62
|
-
validateAggregateFieldType(y.aggregate, y.fieldType);
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
x,
|
|
66
|
-
y,
|
|
67
|
-
isAggregate: aggregateMode,
|
|
68
|
-
binnedAggregate,
|
|
69
|
-
directRows: directTemporal || directNumericY,
|
|
70
|
-
directTemporal
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function groupKey(values) {
|
|
75
|
-
return JSON.stringify(values);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function groupedSeries(groups, fields, dimensions) {
|
|
79
|
-
const id = groupKey(dimensions);
|
|
80
|
-
let series = groups.get(id);
|
|
81
|
-
if (series === undefined) {
|
|
82
|
-
series = {
|
|
83
|
-
key: Object.fromEntries(
|
|
84
|
-
fields.map((field, index) => [field, dimensions[index]])
|
|
85
|
-
),
|
|
86
|
-
values: []
|
|
87
|
-
};
|
|
88
|
-
groups.set(id, series);
|
|
89
|
-
}
|
|
90
|
-
return series;
|
|
91
|
-
}
|
|
92
|
-
|
|
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
|
-
}
|
|
102
|
-
return cloneAndFreeze({
|
|
103
|
-
xValues: categoryOrder === undefined ? observedX : categoryOrder.filter(value => observedSet.has(value)),
|
|
104
|
-
yValues: series.flatMap(item => item.values.map(value => value.y)),
|
|
105
|
-
series
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function resolveLineBins(rows, layer, scale = {}) {
|
|
110
|
-
const x = layer?.encoding?.x;
|
|
111
|
-
if (
|
|
112
|
-
layer?.mark?.type !== "line" ||
|
|
113
|
-
x?.fieldType !== "quantitative" ||
|
|
114
|
-
x.bin === undefined
|
|
115
|
-
) {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
return resolveHistogramBins({
|
|
119
|
-
values: readQuantitativeField(rows, x.field),
|
|
120
|
-
bin: x.bin,
|
|
121
|
-
domain: scale.domain ?? "auto",
|
|
122
|
-
nice: scale.nice ?? true,
|
|
123
|
-
zero: scale.zero ?? false
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function deriveCartesianLineSeries(rows, layer, options = {}) {
|
|
128
|
-
const { x, y, isAggregate, binnedAggregate, directRows, directTemporal } =
|
|
129
|
-
requireLineEncoding(layer);
|
|
130
|
-
if (isAggregate) {
|
|
131
|
-
validateAggregateFieldValues(rows, y.field, y.fieldType);
|
|
132
|
-
}
|
|
133
|
-
const categorical = ["nominal", "ordinal"].includes(x.fieldType);
|
|
134
|
-
const xValues = categorical ? readNominalField(rows, x.field)
|
|
135
|
-
: x.fieldType === "temporal"
|
|
136
|
-
? readTemporalField(rows, x.field, x.temporalUnit)
|
|
137
|
-
: readQuantitativeField(rows, x.field);
|
|
138
|
-
const yValues = isAggregate
|
|
139
|
-
? undefined
|
|
140
|
-
: y.fieldType === "temporal"
|
|
141
|
-
? readTemporalField(rows, y.field, y.temporalUnit)
|
|
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;
|
|
148
|
-
const seriesFields = validatePathSeriesAppearance(rows, layer);
|
|
149
|
-
const binBoundaries = binnedAggregate
|
|
150
|
-
? options.xBinBoundaries ?? resolveLineBins(rows, layer).boundaries
|
|
151
|
-
: undefined;
|
|
152
|
-
|
|
153
|
-
const pathOrder = layer.encoding?.pathOrder;
|
|
154
|
-
if (pathOrder !== undefined) {
|
|
155
|
-
if (isAggregate) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
`Line mark "${layer.id}" cannot combine aggregate y and path order.`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
const orderValues = readQuantitativeField(rows, pathOrder.field);
|
|
161
|
-
const groups = new Map();
|
|
162
|
-
for (let index = 0; index < rows.length; index += 1) {
|
|
163
|
-
const dimensions = seriesFields.map(
|
|
164
|
-
field => rows[index][field]
|
|
165
|
-
);
|
|
166
|
-
const series = groupedSeries(groups, seriesFields, dimensions);
|
|
167
|
-
series.values.push({ x: xValues[index], y: yValues[index] });
|
|
168
|
-
series.orderValues ??= [];
|
|
169
|
-
series.orderValues.push(orderValues[index]);
|
|
170
|
-
}
|
|
171
|
-
const series = [...groups.values()].flatMap(item => {
|
|
172
|
-
const values = stableOrderPathValues(
|
|
173
|
-
item.values,
|
|
174
|
-
item.orderValues,
|
|
175
|
-
pathOrder.order
|
|
176
|
-
);
|
|
177
|
-
return [{ key: item.key, values }];
|
|
178
|
-
});
|
|
179
|
-
if (series.length === 0) {
|
|
180
|
-
throw new Error(
|
|
181
|
-
`Line series on mark "${layer.id}" requires at least two ordered points.`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
return freezeCartesianSeries(series, categoryOrder);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (directRows) {
|
|
188
|
-
const groups = new Map();
|
|
189
|
-
for (let index = 0; index < rows.length; index += 1) {
|
|
190
|
-
const dimensions = seriesFields.map(
|
|
191
|
-
field => rows[index][field]
|
|
192
|
-
);
|
|
193
|
-
const series = groupedSeries(groups, seriesFields, dimensions);
|
|
194
|
-
series.values.push({ x: xValues[index], y: yValues[index] });
|
|
195
|
-
}
|
|
196
|
-
const orderBy = directTemporal && y.fieldType === "temporal" ? "y" : "x";
|
|
197
|
-
const series = [...groups.values()].flatMap(item => {
|
|
198
|
-
const values = item.values.sort(
|
|
199
|
-
orderBy === "x" ? compareX : (left, right) => left.y - right.y
|
|
200
|
-
);
|
|
201
|
-
return [{ key: item.key, values }];
|
|
202
|
-
});
|
|
203
|
-
if (series.length === 0) {
|
|
204
|
-
throw new Error(
|
|
205
|
-
`Line series on mark "${layer.id}" requires at least two direct points.`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return freezeCartesianSeries(series, categoryOrder);
|
|
209
|
-
}
|
|
210
|
-
const aggregateGroups = new Map();
|
|
211
|
-
|
|
212
|
-
for (let index = 0; index < rows.length; index += 1) {
|
|
213
|
-
const dimensions = seriesFields.map(
|
|
214
|
-
field => rows[index][field]
|
|
215
|
-
);
|
|
216
|
-
const binIndex = binnedAggregate
|
|
217
|
-
? findHistogramBinIndex(xValues[index], binBoundaries)
|
|
218
|
-
: undefined;
|
|
219
|
-
if (binIndex === -1) continue;
|
|
220
|
-
const position = binnedAggregate
|
|
221
|
-
? finiteMidpoint(
|
|
222
|
-
binBoundaries[binIndex],
|
|
223
|
-
binBoundaries[binIndex + 1]
|
|
224
|
-
)
|
|
225
|
-
: xValues[index];
|
|
226
|
-
const key = groupKey([binnedAggregate ? binIndex : position, ...dimensions]);
|
|
227
|
-
const group = aggregateGroups.get(key) ?? {
|
|
228
|
-
x: position,
|
|
229
|
-
dimensions,
|
|
230
|
-
rows: []
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
group.rows.push(rows[index]);
|
|
234
|
-
aggregateGroups.set(key, group);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (aggregateGroups.size === 0) {
|
|
238
|
-
throw new Error(`Line mark "${layer.id}" has no values to aggregate.`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const seriesGroups = new Map();
|
|
242
|
-
|
|
243
|
-
for (const group of aggregateGroups.values()) {
|
|
244
|
-
const value = aggregateRows(group.rows, y.field, y.aggregate);
|
|
245
|
-
if (value === undefined) continue;
|
|
246
|
-
const series = groupedSeries(
|
|
247
|
-
seriesGroups,
|
|
248
|
-
seriesFields,
|
|
249
|
-
group.dimensions
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
series.values.push({
|
|
253
|
-
x: group.x,
|
|
254
|
-
y: value
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const series = [...seriesGroups.values()].flatMap(item => {
|
|
259
|
-
const values = item.values.sort(compareX);
|
|
260
|
-
|
|
261
|
-
if (values.length < 2) {
|
|
262
|
-
return [];
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return [{ key: item.key, values }];
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
if (series.length === 0) {
|
|
269
|
-
throw new Error(
|
|
270
|
-
`Line series on mark "${layer.id}" requires at least two aggregate points.`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return freezeCartesianSeries(series, categoryOrder);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function requirePolarLineEncoding(layer) {
|
|
278
|
-
if (layer?.mark?.type !== "line") {
|
|
279
|
-
throw new Error("Polar line series derivation requires a semantic line mark.");
|
|
280
|
-
}
|
|
281
|
-
const theta = layer.encoding?.theta;
|
|
282
|
-
const radius = layer.encoding?.radius;
|
|
283
|
-
if (theta === undefined || radius === undefined) {
|
|
284
|
-
throw new Error(
|
|
285
|
-
`Polar line mark "${layer.id}" requires theta and radius encodings.`
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
if (!["quantitative", "temporal", "ordinal", "nominal"].includes(
|
|
289
|
-
theta.fieldType
|
|
290
|
-
)) {
|
|
291
|
-
throw new Error(
|
|
292
|
-
`Polar line mark "${layer.id}" has unsupported theta field type.`
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
if (radius.fieldType !== "quantitative") {
|
|
296
|
-
throw new Error(
|
|
297
|
-
`Polar line mark "${layer.id}" requires a quantitative radius encoding.`
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
return { theta, radius };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function readThetaValues(rows, encoding) {
|
|
304
|
-
if (["nominal", "ordinal"].includes(encoding.fieldType)) {
|
|
305
|
-
return readNominalField(rows, encoding.field);
|
|
306
|
-
}
|
|
307
|
-
return encoding.fieldType === "temporal"
|
|
308
|
-
? readTemporalField(rows, encoding.field, encoding.temporalUnit)
|
|
309
|
-
: readQuantitativeField(rows, encoding.field);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function thetaOrder(values, fieldType, domain) {
|
|
313
|
-
if (!["nominal", "ordinal"].includes(fieldType)) return values;
|
|
314
|
-
const order = domain ?? [...new Set(values)];
|
|
315
|
-
const indices = new Map(order.map((value, index) => [value, index]));
|
|
316
|
-
for (const value of values) {
|
|
317
|
-
if (!indices.has(value)) {
|
|
318
|
-
throw new Error(`Polar line theta domain does not contain "${value}".`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return values.map(value => indices.get(value));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export function derivePolarLineSeries(rows, layer, { thetaDomain } = {}) {
|
|
325
|
-
if (layer.encoding?.pathOrder !== undefined) {
|
|
326
|
-
throw new Error(
|
|
327
|
-
`Polar line mark "${layer.id}" does not support path order.`
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
const { theta, radius } = requirePolarLineEncoding(layer);
|
|
331
|
-
const thetaValues = readThetaValues(rows, theta);
|
|
332
|
-
const radiusValues = readQuantitativeField(rows, radius.field);
|
|
333
|
-
const sortValues = thetaOrder(thetaValues, theta.fieldType, thetaDomain);
|
|
334
|
-
const seriesFields = validatePathSeriesAppearance(rows, layer);
|
|
335
|
-
const groups = new Map();
|
|
336
|
-
|
|
337
|
-
for (let index = 0; index < rows.length; index += 1) {
|
|
338
|
-
const dimensions = seriesFields.map(
|
|
339
|
-
field => rows[index][field]
|
|
340
|
-
);
|
|
341
|
-
const series = groupedSeries(groups, seriesFields, dimensions);
|
|
342
|
-
series.values.push({
|
|
343
|
-
theta: thetaValues[index],
|
|
344
|
-
radius: radiusValues[index],
|
|
345
|
-
order: sortValues[index],
|
|
346
|
-
sourceIndex: index
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const series = [...groups.values()].flatMap(item => {
|
|
351
|
-
const values = item.values.sort(
|
|
352
|
-
(left, right) => left.order - right.order ||
|
|
353
|
-
left.sourceIndex - right.sourceIndex
|
|
354
|
-
);
|
|
355
|
-
return values.length < 2 ? [] : [{ key: item.key, values }];
|
|
356
|
-
});
|
|
357
|
-
if (series.length === 0) {
|
|
358
|
-
throw new Error(
|
|
359
|
-
`Polar line series on mark "${layer.id}" requires at least two points.`
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
return cloneAndFreeze({
|
|
363
|
-
thetaFieldType: theta.fieldType,
|
|
364
|
-
thetaValues: series.flatMap(item => item.values.map(value => value.theta)),
|
|
365
|
-
radiusValues: series.flatMap(item => item.values.map(value => value.radius)),
|
|
366
|
-
series
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function deriveLineSeries(rows, layer, options) {
|
|
371
|
-
const polar = layer?.encoding?.theta !== undefined ||
|
|
372
|
-
layer?.encoding?.radius !== undefined;
|
|
373
|
-
return polar
|
|
374
|
-
? derivePolarLineSeries(rows, layer, options)
|
|
375
|
-
: deriveCartesianLineSeries(rows, layer, options);
|
|
376
|
-
}
|
|
1
|
+
import{cloneAndFreeze as $}from"../core/immutable.js";import{readLinePosition as R,validateLineMissingScope as N,orderLineGapValues as X,splitLineGapValues as j}from"./lineMissing.js";import{readNominalField as U,readQuantitativeField as k,readTemporalField as P}from"./scales/index.js";import{aggregateRows as D,isAggregate as C,validateAggregateFieldType as Q,validateAggregateFieldValues as Y}from"./aggregate.js";import{stableOrderPathValues as _}from"./pathOrder.js";import{finiteMidpoint as J}from"./numeric.js";import{findHistogramBinIndex as W,resolveHistogramBins as Z}from"./histogram.js";import{validatePathSeriesAppearance as I,pathSeriesKey as H}from"./pathSeries.js";function ee(e){if(e?.mark?.type!=="line")throw new Error("Line series derivation requires a semantic line mark.");const n=e.encoding?.x,i=e.encoding?.y;if(n===void 0)throw new Error(`Line mark "${e.id}" requires an x encoding.`);const a=n?.fieldType==="quantitative"&&n.bin!==void 0&&C(i?.aggregate),r=["temporal","nominal","ordinal"].includes(n?.fieldType)&&C(i?.aggregate)||a,s=["quantitative","nominal","ordinal"].includes(n?.fieldType)&&n.bin===void 0&&i?.fieldType==="quantitative"&&i.aggregate===void 0,c=i?.aggregate===void 0&&(n?.fieldType==="temporal"&&i?.fieldType==="quantitative"||n?.fieldType==="quantitative"&&i?.fieldType==="temporal");if(!r&&!s&&!c)throw n?.fieldType==="temporal"?new Error(`Line mark "${e.id}" requires a supported aggregate y encoding.`):new Error(`Line mark "${e.id}" requires categorical/temporal aggregate or compatible direct x/y encodings.`);return r&&Q(i.aggregate,i.fieldType),{x:n,y:i,isAggregate:r,binnedAggregate:a,directRows:c||s,directTemporal:c}}function q(e,n,i){const a=H(i);let r=e.get(a);return r===void 0&&(r={key:Object.fromEntries(n.map((s,c)=>[s,i[c]])),values:[]},e.set(a,r)),r}function A(e,n){const i=e.flatMap(r=>r.values.map(s=>s.x)),a=new Set(i);if(n!==void 0){const r=new Set(n);if(i.some(s=>!r.has(s)))throw new Error("Line categorical x domain must contain every observed vertex.")}return $({xValues:n===void 0?i:n.filter(r=>a.has(r)),yValues:e.flatMap(r=>r.values.map(s=>s.y)),series:e})}function ne(e,n,i={}){const a=n?.encoding?.x;if(!(n?.mark?.type!=="line"||a?.fieldType!=="quantitative"||a.bin===void 0))return Z({values:k(e,a.field),bin:a.bin,domain:i.domain??"auto",nice:i.nice??!0,zero:i.zero??!1})}function ie(e,n,i={}){const{x:a,y:r,isAggregate:s,binnedAggregate:c,directRows:M,directTemporal:V}=ee(n);if(n.mark.missing==="break"){const t=R(e,a),d=R(e,r),f=I(e,n,i.scales),o=n.encoding?.pathOrder,g=o===void 0?V&&r.fieldType==="temporal"?d:t:k(e,o.field),m=new Map;for(let l=0;l<e.length;l+=1){const L=q(m,f,f.map(O=>e[l][O]));L.values.push({x:t[l],y:d[l],sourceIndex:l}),(L.orderValues??=[]).push(g[l])}const v=[...m.values()].flatMap(l=>{const L=X(l.values,l.orderValues,o?.order);return j(L).map(z=>({key:l.key,values:z.map(({sourceIndex:G,...K})=>K),sourceIndices:z.map(G=>G.sourceIndex)}))});return $({xValues:t.filter(l=>l!==null),yValues:d.filter(l=>l!==null),series:v})}s&&Y(e,r.field,r.fieldType);const y=["nominal","ordinal"].includes(a.fieldType),h=y?U(e,a.field):a.fieldType==="temporal"?P(e,a.field,a.temporalUnit):k(e,a.field),u=s?void 0:r.fieldType==="temporal"?P(e,r.field,r.temporalUnit):k(e,r.field),p=y?i.xDomain??[...new Set(h)]:void 0,w=new Map(p?.map((t,d)=>[t,d])),x=y?(t,d)=>w.get(t.x)-w.get(d.x):(t,d)=>t.x-d.x,T=I(e,n,i.scales),b=c?i.xBinBoundaries??ne(e,n).boundaries:void 0,S=n.encoding?.pathOrder;if(S!==void 0){if(s)throw new Error(`Line mark "${n.id}" cannot combine aggregate y and path order.`);const t=k(e,S.field),d=new Map;for(let o=0;o<e.length;o+=1){const g=T.map(v=>e[o][v]),m=q(d,T,g);m.values.push({x:h[o],y:u[o]}),m.orderValues??=[],m.orderValues.push(t[o])}const f=[...d.values()].flatMap(o=>{const g=_(o.values,o.orderValues,S.order);return[{key:o.key,values:g}]});if(f.length===0)throw new Error(`Line series on mark "${n.id}" requires at least two ordered points.`);return A(f,p)}if(M){const t=new Map;for(let o=0;o<e.length;o+=1){const g=T.map(v=>e[o][v]);q(t,T,g).values.push({x:h[o],y:u[o]})}const d=V&&r.fieldType==="temporal"?"y":"x",f=[...t.values()].flatMap(o=>{const g=o.values.sort(d==="x"?x:(m,v)=>m.y-v.y);return[{key:o.key,values:g}]});if(f.length===0)throw new Error(`Line series on mark "${n.id}" requires at least two direct points.`);return A(f,p)}const E=new Map;for(let t=0;t<e.length;t+=1){const d=T.map(v=>e[t][v]),f=c?W(h[t],b):void 0;if(f===-1)continue;const o=c?J(b[f],b[f+1]):h[t],g=H([c?f:o,...d]),m=E.get(g)??{x:o,dimensions:d,rows:[]};m.rows.push(e[t]),E.set(g,m)}if(E.size===0)throw new Error(`Line mark "${n.id}" has no values to aggregate.`);const F=new Map;for(const t of E.values()){const d=D(t.rows,r.field,r.aggregate);if(d===void 0)continue;q(F,T,t.dimensions).values.push({x:t.x,y:d})}const B=[...F.values()].flatMap(t=>{const d=t.values.sort(x);return d.length<(i.allowSingleton===!0?1:2)?[]:[{key:t.key,values:d}]});if(B.length===0)throw new Error(`Line series on mark "${n.id}" requires at least two aggregate points.`);return A(B,p)}function re(e){if(e?.mark?.type!=="line")throw new Error("Polar line series derivation requires a semantic line mark.");const n=e.encoding?.theta,i=e.encoding?.radius;if(n===void 0||i===void 0)throw new Error(`Polar line mark "${e.id}" requires theta and radius encodings.`);if(!["quantitative","temporal","ordinal","nominal"].includes(n.fieldType))throw new Error(`Polar line mark "${e.id}" has unsupported theta field type.`);if(i.fieldType!=="quantitative")throw new Error(`Polar line mark "${e.id}" requires a quantitative radius encoding.`);return{theta:n,radius:i}}function te(e,n){return["nominal","ordinal"].includes(n.fieldType)?U(e,n.field):n.fieldType==="temporal"?P(e,n.field,n.temporalUnit):k(e,n.field)}function ae(e,n,i){if(!["nominal","ordinal"].includes(n))return e;const a=i??[...new Set(e)],r=new Map(a.map((s,c)=>[s,c]));for(const s of e)if(!r.has(s))throw new Error(`Polar line theta domain does not contain "${s}".`);return e.map(s=>r.get(s))}function oe(e,n,{thetaDomain:i}={}){if(n.encoding?.pathOrder!==void 0)throw new Error(`Polar line mark "${n.id}" does not support path order.`);const{theta:a,radius:r}=re(n),s=te(e,a),c=k(e,r.field),M=ae(s,a.fieldType,i),V=I(e,n),y=new Map;for(let u=0;u<e.length;u+=1){const p=V.map(x=>e[u][x]);q(y,V,p).values.push({theta:s[u],radius:c[u],order:M[u],sourceIndex:u})}const h=[...y.values()].flatMap(u=>{const p=u.values.sort((w,x)=>w.order-x.order||w.sourceIndex-x.sourceIndex);return p.length<2?[]:[{key:u.key,values:p}]});if(h.length===0)throw new Error(`Polar line series on mark "${n.id}" requires at least two points.`);return $({thetaFieldType:a.fieldType,thetaValues:h.flatMap(u=>u.values.map(p=>p.theta)),radiusValues:h.flatMap(u=>u.values.map(p=>p.radius)),series:h})}function me(e,n,i){return N(n),n?.encoding?.theta!==void 0||n?.encoding?.radius!==void 0?oe(e,n,i):ie(e,n,i)}export{me as deriveLineSeries,oe as derivePolarLineSeries,ne as resolveLineBins};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {cloneAndFreeze, isPlainObject} from "../core/immutable.js";
|
|
2
|
+
import {validateUserId} from "../core/identifiers.js";
|
|
3
|
+
import {
|
|
4
|
+
rejectUnknownProperties, requireStringValue, validateDataRows, validateWorkLimit
|
|
5
|
+
} from "../core/validation.js";
|
|
6
|
+
|
|
7
|
+
export function normalizeLookupTransform({field, lookup} = {}) {
|
|
8
|
+
if (!isPlainObject(lookup)) throw new TypeError("Lookup requires a lookup object.");
|
|
9
|
+
const definition = {
|
|
10
|
+
type: "lookup", field,
|
|
11
|
+
lookup: {
|
|
12
|
+
...lookup,
|
|
13
|
+
fields: Array.isArray(lookup.fields) ? [...lookup.fields] : lookup.fields,
|
|
14
|
+
as: Array.isArray(lookup.as === undefined ? lookup.fields : lookup.as) ? [...(lookup.as === undefined ? lookup.fields : lookup.as)] : lookup.as
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
validateLookupTransform(definition);
|
|
18
|
+
return cloneAndFreeze(definition);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateLookupTransform(transform) {
|
|
22
|
+
if (!isPlainObject(transform)) throw new TypeError("Lookup transform must be a plain object.");
|
|
23
|
+
rejectUnknownProperties(transform, ["type", "field", "lookup"], "lookup transform");
|
|
24
|
+
if (transform.type !== "lookup") throw new Error(`Unsupported lookup transform "${transform.type}".`);
|
|
25
|
+
requireStringValue(transform.field, "Lookup field");
|
|
26
|
+
const lookup = transform.lookup;
|
|
27
|
+
if (!isPlainObject(lookup)) throw new TypeError("Lookup requires a lookup object.");
|
|
28
|
+
rejectUnknownProperties(lookup, ["data", "key", "fields", "as"], "lookup definition");
|
|
29
|
+
validateUserId(lookup.data, "Lookup dataset id");
|
|
30
|
+
requireStringValue(lookup.key, "Lookup key");
|
|
31
|
+
if (!Array.isArray(lookup.fields) || lookup.fields.length === 0) {
|
|
32
|
+
throw new TypeError("Lookup fields must be a non-empty array.");
|
|
33
|
+
}
|
|
34
|
+
if (!Array.isArray(lookup.as) || lookup.as.length !== lookup.fields.length) {
|
|
35
|
+
throw new TypeError("Lookup as requires one alias per field.");
|
|
36
|
+
}
|
|
37
|
+
for (const key of ["fields", "as"]) {
|
|
38
|
+
for (let i = 0; i < lookup[key].length; i += 1) {
|
|
39
|
+
if (!Object.hasOwn(lookup[key], i)) throw new TypeError(`Lookup ${key} must be dense.`);
|
|
40
|
+
requireStringValue(lookup[key][i], `Lookup ${key}[${i}]`);
|
|
41
|
+
}
|
|
42
|
+
if (new Set(lookup[key]).size !== lookup[key].length) {
|
|
43
|
+
throw new Error(`Lookup ${key} must be unique.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return transform;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function keyValue(row, field) {
|
|
50
|
+
const key = Object.hasOwn(row, field) ? row[field] : undefined;
|
|
51
|
+
if (key === undefined || key === null) return undefined;
|
|
52
|
+
if (typeof key !== "string" && typeof key !== "boolean" && !Number.isFinite(key)) {
|
|
53
|
+
throw new TypeError(`Lookup key field "${field}" requires a string, finite number or boolean.`);
|
|
54
|
+
}
|
|
55
|
+
return key;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function deriveLookupRows(rows, lookupRows, transform) {
|
|
59
|
+
validateLookupTransform(transform);
|
|
60
|
+
validateDataRows(rows, "Lookup primary data");
|
|
61
|
+
validateDataRows(lookupRows, "Lookup reference data");
|
|
62
|
+
const lookup = transform.lookup;
|
|
63
|
+
validateWorkLimit(lookupRows.length + rows.length * (lookup.fields.length + 1), "Lookup work");
|
|
64
|
+
const existing = new Set(rows.flatMap(row => Object.keys(row)));
|
|
65
|
+
for (const name of lookup.as) {
|
|
66
|
+
if (existing.has(name)) throw new Error(`Lookup output field "${name}" already exists in primary data.`);
|
|
67
|
+
}
|
|
68
|
+
const index = new Map();
|
|
69
|
+
for (const row of lookupRows) {
|
|
70
|
+
const key = keyValue(row, lookup.key);
|
|
71
|
+
if (key === undefined) continue;
|
|
72
|
+
if (index.has(key)) throw new Error(`Lookup key field "${lookup.key}" contains duplicate keys.`);
|
|
73
|
+
index.set(key, row);
|
|
74
|
+
}
|
|
75
|
+
return cloneAndFreeze(rows.map(row => {
|
|
76
|
+
const key = keyValue(row, transform.field);
|
|
77
|
+
const match = key === undefined ? undefined : index.get(key);
|
|
78
|
+
const output = {...row};
|
|
79
|
+
for (let i = 0; i < lookup.fields.length; i += 1) {
|
|
80
|
+
const field = lookup.fields[i];
|
|
81
|
+
Object.defineProperty(output, lookup.as[i], {
|
|
82
|
+
value: match !== undefined && Object.hasOwn(match, field) ? match[field] ?? null : null,
|
|
83
|
+
enumerable: true, configurable: true, writable: true
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return output;
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function sameLookupValue(left, right) {
|
|
91
|
+
if (Object.is(left, right)) return true;
|
|
92
|
+
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
|
|
93
|
+
if (Array.isArray(left) !== Array.isArray(right)) return false;
|
|
94
|
+
const keys = Object.keys(left), other = Object.keys(right);
|
|
95
|
+
return keys.length === other.length && keys.every((key, index) =>
|
|
96
|
+
key === other[index] && sameLookupValue(left[key], right[key]));
|
|
97
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { normalizeMarkSelector } from "./markSelection.js";
|
|
3
|
+
import { retainLineGapBoundaries } from "./lineMissing.js";
|
|
3
4
|
|
|
4
5
|
const TRANSFORM_KEYS = Object.freeze([
|
|
5
6
|
"type", "target", "selector", "selectors"
|
|
@@ -50,7 +51,7 @@ export function normalizeMarkFilterTransform(target, selectors) {
|
|
|
50
51
|
return cloneAndFreeze(transform);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
export function deriveMarkFilteredRows(sourceRows, items, selectedKeys) {
|
|
54
|
+
export function deriveMarkFilteredRows(sourceRows, items, selectedKeys, layer) {
|
|
54
55
|
if (!Array.isArray(sourceRows)) {
|
|
55
56
|
throw new TypeError("Mark filter source values must be an array.");
|
|
56
57
|
}
|
|
@@ -60,5 +61,6 @@ export function deriveMarkFilteredRows(sourceRows, items, selectedKeys) {
|
|
|
60
61
|
.filter(item => selected.has(item.key))
|
|
61
62
|
.flatMap(item => item.members)
|
|
62
63
|
);
|
|
63
|
-
|
|
64
|
+
const retained = retainLineGapBoundaries(sourceRows, members, layer);
|
|
65
|
+
return cloneAndFreeze(sourceRows.filter(row => retained.has(row)));
|
|
64
66
|
}
|
|
@@ -39,9 +39,6 @@ export function normalizeMarkLabelContent(source, { content, normalizeBy }) {
|
|
|
39
39
|
source.encoding[`${resolveBarChannels(source).measure}2`] !== undefined) {
|
|
40
40
|
throw new Error("Ranged Bar text requires an explicit field or value.");
|
|
41
41
|
}
|
|
42
|
-
if (content === "category" && grain === BAR_GRAINS.histogram) {
|
|
43
|
-
throw new Error("Histogram categories are intervals; use an explicit text field or value.");
|
|
44
|
-
}
|
|
45
42
|
} else if (content === "category" && source.encoding?.theta?.fieldType === "quantitative") {
|
|
46
43
|
throw new Error("Category text requires categorical Arc theta.");
|
|
47
44
|
}
|
|
@@ -51,6 +48,8 @@ export function normalizeMarkLabelContent(source, { content, normalizeBy }) {
|
|
|
51
48
|
function itemValue(source, item) {
|
|
52
49
|
if (source.mark.type === "bar") {
|
|
53
50
|
if (resolveBarGrain(source) === BAR_GRAINS.histogram) {
|
|
51
|
+
const measure = source.encoding.y;
|
|
52
|
+
if (measure.aggregate !== "count") return aggregateRows(item.members, measure.field, measure.aggregate);
|
|
54
53
|
const weight = source.encoding.x.weight;
|
|
55
54
|
if (weight === undefined || item.members.length === 0) return item.members.length;
|
|
56
55
|
const weighted = readStatisticalWeights(item.members, weight, "Histogram label");
|
|
@@ -77,6 +76,12 @@ function itemValue(source, item) {
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
function categoryValue(source, item) {
|
|
79
|
+
if (source.mark.type === "bar" && resolveBarGrain(source) === BAR_GRAINS.histogram) {
|
|
80
|
+
// Describe the semantic bin, independently of stacking/reversal and its
|
|
81
|
+
// concrete pixel width. Suppress binary floating-point boundary artifacts.
|
|
82
|
+
const boundary = value => String(Number(value.toPrecision(12)));
|
|
83
|
+
return `${boundary(item.channels.x)}–${boundary(item.channels.x2)}`;
|
|
84
|
+
}
|
|
80
85
|
return item.channels[source.mark.type === "arc" ? "theta" : resolveBarChannels(source).category];
|
|
81
86
|
}
|
|
82
87
|
|
package/src/grammar/numeric.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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 M(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 M(t*e,n)}function F(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 F(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 s=c/n.scale,u=i+s;return o+=Math.abs(i)>=Math.abs(s)?i-u+s:s-u+i,i=u,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 F(t,e,t.length)}function P(t,e="Numeric square sum"){h(t,e);const n=t.reduce((c,s)=>c+s**2,0);if(Number.isFinite(n))return n;const r=f(t);if(r===0)return 0;const i=t.reduce((c,s)=>c+(s/r)**2,0),o=N(Math.sqrt(i),r,e);return M(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(m=>m-o),s=c.reduce((m,b)=>m+b**2,0);if(Number.isFinite(s))return{mean:o,deviation:Math.sqrt(s/i)/n,squared:s};const u=c.every(Number.isFinite),a=u?f(c):Math.max(Math.abs(o),f(t));if(a===0)return{mean:o,deviation:0};const g=t.reduce((m,b,w)=>{const S=u?c[w]/a:b/a-o/a;return m+S**2},0),l=N(Math.sqrt(g/i)/n,a,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 d(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){const n=t/e,r=Math.floor(Math.log10(n)),i=10**r;if(!Number.isFinite(i)||i===0)return null;const o=n/i,c=o>=Math.sqrt(50)?10:o>=Math.sqrt(10)?5:o>=Math.sqrt(2)?2:1,s=r<0?-(10**-r)/c:c*i;return Number.isFinite(s)&&s!==0?s:null}function T(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(s=>s>=o)??10)*i}function j(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 L(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 d(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 M(r,"Numeric interpolation")}function V(t,e){const n=t+e,r=Number.isFinite(n)?n/2:t/2+e/2;return M(r,"Numeric midpoint")}function _(t,e,n,r="Numeric range"){const i=Math.abs((e-t)/(n-1)),o=[],c=Math.sign(e-t);for(let s=0;s<n;s+=1){const u=E(d(t,e,s/(n-1)),i);if(!Number.isFinite(u)||c!==0&&s>0&&c*(u-o[s-1])<=0)throw new RangeError(`${r} cannot represent ${n} distinct samples.`);o.push(u)}return o}function k(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 B(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,j as alignNumericStep,E as cleanNumericValue,V as finiteMidpoint,k as formatDistinctNumericSamples,d as interpolateNumber,L as inverseLerp,f as maximumMagnitude,O as nearestNumericIncrement,T as niceNumericStep,C as normalizeNumericRange,y as normalizedFiniteSum,I as numericExtent,D as quantileSorted,M as requireFiniteResult,N as restoreFiniteScale,_ as sampleNumericRange,B as stableDecimal,A as stableFiniteDeviation,R as stableFiniteMean,z as stableFinitePrefixSums,P as stableFiniteSquareSum,$ as stableFiniteSum};
|
|
@@ -1,7 +1,7 @@
|
|
|
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 =
|
|
4
|
+
export const MAX_PATH_COMMANDS = 30_000;
|
|
5
5
|
|
|
6
6
|
export function validatePathCommandCount(count, label) {
|
|
7
7
|
return validateGeneratedItemLimit(count, label, MAX_PATH_COMMANDS);
|