ggaction 0.0.12 → 0.0.13
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 +49 -0
- package/README.md +2 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -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 +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +10 -16
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +10 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +67 -22
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +21 -10
- package/src/actions/errorBars/edit.js +355 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +44 -23
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +106 -2
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +34 -3
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +7 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +41 -7
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facets.js +8 -1
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +58 -23
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +46 -14
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1320 -135
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -4,10 +4,22 @@ import { validateKeys } from "../../core/validation.js";
|
|
|
4
4
|
import { hasDataset } from "../../selectors/index.js";
|
|
5
5
|
import { findDataset } from "../../selectors/datasets.js";
|
|
6
6
|
import { requireLayer } from "../../selectors/layers.js";
|
|
7
|
+
import { applyLayerDataRematerialization } from
|
|
8
|
+
"../../materialization/dependencies.js";
|
|
7
9
|
|
|
8
10
|
const OPTIONS = Object.freeze(["id", "source", "transform"]);
|
|
9
11
|
const RELEASE_OPTIONS = Object.freeze(["id"]);
|
|
10
12
|
const REBIND_OPTIONS = Object.freeze(["id", "data"]);
|
|
13
|
+
const BIND_OPTIONS = Object.freeze(["target", "data"]);
|
|
14
|
+
const OWNED_MARK_ROLES = Object.freeze([
|
|
15
|
+
"boxPlot",
|
|
16
|
+
"errorBand",
|
|
17
|
+
"errorBandBoundary",
|
|
18
|
+
"errorBar",
|
|
19
|
+
"gradientPlot",
|
|
20
|
+
"ecdfPlot",
|
|
21
|
+
"regression"
|
|
22
|
+
]);
|
|
11
23
|
|
|
12
24
|
export const createDerivedData = action(
|
|
13
25
|
{ op: "createDerivedData", description: "Create an immutable derived dataset definition." },
|
|
@@ -72,3 +84,57 @@ export const rebindLayerData = action(
|
|
|
72
84
|
});
|
|
73
85
|
}
|
|
74
86
|
);
|
|
87
|
+
|
|
88
|
+
function assertIndependentMark(program, layer) {
|
|
89
|
+
const config = program.markConfigs[layer.id] ?? {};
|
|
90
|
+
const role = OWNED_MARK_ROLES.find(key => config[key] !== undefined);
|
|
91
|
+
if (role !== undefined) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Mark "${layer.id}" is owned by its ${role} lifecycle; use that ` +
|
|
94
|
+
"resource's edit action to change data roles."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const source = findDataset(program, layer.data);
|
|
98
|
+
const transform = source?.transform?.[0]?.type;
|
|
99
|
+
if (["density", "ecdf", "horizon", "markFilter"].includes(transform)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Mark "${layer.id}" consumes owned ${transform} data; use its ` +
|
|
102
|
+
"data or filter lifecycle action to change the source."
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function applyMarkDataBinding(program, target, data) {
|
|
108
|
+
const rebound = program.rebindLayerData({ id: target, data });
|
|
109
|
+
return applyLayerDataRematerialization(rebound, target);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const bindMarkData = action(
|
|
113
|
+
{
|
|
114
|
+
op: "bindMarkData",
|
|
115
|
+
description: "Atomically bind one independent mark to materialized data."
|
|
116
|
+
},
|
|
117
|
+
function (args = {}) {
|
|
118
|
+
validateKeys(args, BIND_OPTIONS, "bindMarkData");
|
|
119
|
+
const target = validateUserId(args.target, "Mark target id");
|
|
120
|
+
const data = validateUserId(args.data, "Mark dataset id");
|
|
121
|
+
const layer = requireLayer(this, target);
|
|
122
|
+
const dataset = findDataset(this, data);
|
|
123
|
+
if (dataset === undefined) {
|
|
124
|
+
throw new Error(`Mark dataset "${data}" does not exist.`);
|
|
125
|
+
}
|
|
126
|
+
if (!Array.isArray(dataset.values)) {
|
|
127
|
+
throw new Error(`Mark dataset "${data}" requires materialized values.`);
|
|
128
|
+
}
|
|
129
|
+
if (layer.data === data) {
|
|
130
|
+
throw new Error(`Mark "${target}" already uses dataset "${data}".`);
|
|
131
|
+
}
|
|
132
|
+
assertIndependentMark(this, layer);
|
|
133
|
+
|
|
134
|
+
// Finish the entire dependency plan on an immutable speculative branch.
|
|
135
|
+
// A field, type, coordinate, scale, guide, label, selection, or highlight
|
|
136
|
+
// incompatibility therefore rejects before this action returns any state.
|
|
137
|
+
applyMarkDataBinding(this, target, data);
|
|
138
|
+
return applyMarkDataBinding(this, target, data);
|
|
139
|
+
}
|
|
140
|
+
);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveECDF,
|
|
3
|
+
normalizeECDFTransform,
|
|
4
|
+
resolveECDFTransform
|
|
5
|
+
} from "../../grammar/ecdf.js";
|
|
6
|
+
import { derivedCreator, derivedMaterializer } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
const OPTIONS = Object.freeze([
|
|
9
|
+
"id", "source", "field", "groupBy", "weight", "missing", "as"
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export const materializeECDFData = derivedMaterializer(
|
|
13
|
+
"materializeECDFData",
|
|
14
|
+
"Materialize one empirical cumulative distribution dataset.",
|
|
15
|
+
"ecdf",
|
|
16
|
+
deriveECDF,
|
|
17
|
+
resolveECDFTransform
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const createECDFData = derivedCreator(
|
|
21
|
+
"createECDFData",
|
|
22
|
+
"Create immutable empirical cumulative distribution values.",
|
|
23
|
+
OPTIONS,
|
|
24
|
+
"ECDF dataset id",
|
|
25
|
+
"ECDF source dataset id",
|
|
26
|
+
(args, id) => normalizeECDFTransform({
|
|
27
|
+
field: args.field,
|
|
28
|
+
groupBy: args.groupBy,
|
|
29
|
+
weight: args.weight,
|
|
30
|
+
missing: args.missing,
|
|
31
|
+
as: args.as ?? {
|
|
32
|
+
value: `__${id}_value`,
|
|
33
|
+
cumulative: `__${id}_cumulative`,
|
|
34
|
+
probability: `__${id}_probability`
|
|
35
|
+
}
|
|
36
|
+
}),
|
|
37
|
+
"materializeECDFData"
|
|
38
|
+
);
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
3
|
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import { withPreviewDatasetValues } from
|
|
5
|
+
"../primitives/semanticAction.js";
|
|
4
6
|
import {
|
|
5
7
|
deriveFilteredRows,
|
|
6
8
|
normalizeFilterTransform
|
|
7
9
|
} from "../../grammar/filter.js";
|
|
8
10
|
import {
|
|
9
11
|
deriveMarkFilteredRows,
|
|
12
|
+
markFilterSelectors,
|
|
10
13
|
normalizeMarkFilterTransform
|
|
11
14
|
} from "../../grammar/markFilter.js";
|
|
12
15
|
import { resolveMarkFilterSelection } from "../../materialization/selection/filter.js";
|
|
13
16
|
import { findSelectionPolicy } from
|
|
14
17
|
"../../materialization/selection/policies/index.js";
|
|
15
18
|
import {
|
|
16
|
-
applyLayerDataRematerialization
|
|
19
|
+
applyLayerDataRematerialization,
|
|
20
|
+
applyLayerEmptyDataView
|
|
17
21
|
} from "../../materialization/dependencies.js";
|
|
18
22
|
import {
|
|
19
23
|
hasDataset,
|
|
24
|
+
findDataset,
|
|
20
25
|
requireLayer,
|
|
21
26
|
resolveEligibleLayer
|
|
22
27
|
} from "../../selectors/index.js";
|
|
@@ -29,7 +34,9 @@ const MARK_SELECTOR_KEYS = Object.freeze([
|
|
|
29
34
|
"grain", "field", "channel", "property", "op", "value", "values",
|
|
30
35
|
"min", "max", "inclusive", "count", "groupBy", "ties"
|
|
31
36
|
]);
|
|
32
|
-
const MARK_OPTIONS = Object.freeze(["target", ...MARK_SELECTOR_KEYS]);
|
|
37
|
+
const MARK_OPTIONS = Object.freeze(["target", "mode", ...MARK_SELECTOR_KEYS]);
|
|
38
|
+
const REMOVE_MARK_FILTER_OPTIONS = Object.freeze(["target"]);
|
|
39
|
+
const EMPTY_MARK_OPTIONS = Object.freeze(["id"]);
|
|
33
40
|
|
|
34
41
|
function retainedHistogramBoundaries(layer, items) {
|
|
35
42
|
if (layer.mark?.type !== "bar" || layer.encoding?.x?.bin === undefined) {
|
|
@@ -96,15 +103,168 @@ export const materializeMarkFilteredData = action(
|
|
|
96
103
|
`Mark filter target "${transform.target}" must still use source dataset "${dataset.source}".`
|
|
97
104
|
);
|
|
98
105
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
let preview = this;
|
|
107
|
+
let currentRows = source.values;
|
|
108
|
+
const selectors = markFilterSelectors(transform);
|
|
109
|
+
for (const [index, selector] of selectors.entries()) {
|
|
110
|
+
const resolved = resolveMarkFilterSelection(
|
|
111
|
+
preview,
|
|
112
|
+
transform.target,
|
|
113
|
+
selector
|
|
114
|
+
);
|
|
115
|
+
currentRows = deriveMarkFilteredRows(
|
|
116
|
+
currentRows,
|
|
117
|
+
resolved.items,
|
|
118
|
+
resolved.keys
|
|
119
|
+
);
|
|
120
|
+
if (index < selectors.length - 1) {
|
|
121
|
+
preview = withPreviewDatasetValues(preview, {
|
|
122
|
+
id,
|
|
123
|
+
values: currentRows,
|
|
124
|
+
target: transform.target
|
|
125
|
+
});
|
|
126
|
+
preview = currentRows.length === 0
|
|
127
|
+
? applyLayerEmptyDataView(preview, transform.target)
|
|
128
|
+
: applyLayerDataRematerialization(preview, transform.target);
|
|
129
|
+
currentRows = findDataset(preview, id).values;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (selectors.length > 1) {
|
|
133
|
+
preview = withPreviewDatasetValues(preview, {
|
|
134
|
+
id,
|
|
135
|
+
omitValues: true
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
let next = preview.editSemantic({
|
|
105
139
|
property: `dataset[${id}].values`,
|
|
106
|
-
value:
|
|
140
|
+
value: currentRows
|
|
141
|
+
});
|
|
142
|
+
if (requireLayer(next, transform.target).data !== id) {
|
|
143
|
+
next = next.editSemantic({
|
|
144
|
+
property: `layer[${transform.target}].data`,
|
|
145
|
+
value: id
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
next = next._withMarkConfig(transform.target, {
|
|
149
|
+
...next.markConfigs[transform.target],
|
|
150
|
+
markFilter: {
|
|
151
|
+
...next.markConfigs[transform.target].markFilter,
|
|
152
|
+
empty: currentRows.length === 0
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return currentRows.length === 0
|
|
156
|
+
? next.materializeEmptyMark({ id: transform.target })
|
|
157
|
+
: applyLayerDataRematerialization(next, transform.target);
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
export const materializeEmptyMark = action(
|
|
162
|
+
{
|
|
163
|
+
op: "materializeEmptyMark",
|
|
164
|
+
description: "Clear one active empty-filter mark without changing its domains."
|
|
165
|
+
},
|
|
166
|
+
function (args = {}) {
|
|
167
|
+
validateKeys(args, EMPTY_MARK_OPTIONS, "materializeEmptyMark");
|
|
168
|
+
const id = validateUserId(args.id, "Empty mark id");
|
|
169
|
+
const layer = requireLayer(this, id);
|
|
170
|
+
const owner = this.markConfigs[id]?.markFilter;
|
|
171
|
+
const dataset = findDataset(this, layer.data);
|
|
172
|
+
if (owner?.empty !== true || dataset?.values?.length !== 0) {
|
|
173
|
+
throw new Error(`Mark "${id}" does not own an active empty filter.`);
|
|
174
|
+
}
|
|
175
|
+
return applyLayerEmptyDataView(this, id);
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
function activeMarkFilter(program, layer) {
|
|
180
|
+
const configured = program.markConfigs[layer.id]?.markFilter;
|
|
181
|
+
const dataset = findDataset(program, layer.data);
|
|
182
|
+
const transform = dataset?.transform?.[0];
|
|
183
|
+
if (
|
|
184
|
+
dataset?.source === undefined ||
|
|
185
|
+
transform?.type !== "markFilter" ||
|
|
186
|
+
transform.target !== layer.id
|
|
187
|
+
) {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
dataset: dataset.id,
|
|
192
|
+
source: dataset.source,
|
|
193
|
+
selectors: markFilterSelectors(transform),
|
|
194
|
+
...(configured?.originalBin === undefined
|
|
195
|
+
? {}
|
|
196
|
+
: { originalBin: configured.originalBin })
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function withoutMarkFilterConfig(program, id) {
|
|
201
|
+
const config = program.markConfigs[id] ?? {};
|
|
202
|
+
const { markFilter: unused, ...remaining } = config;
|
|
203
|
+
void unused;
|
|
204
|
+
return program._withMarkConfig(id, remaining);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function restoreHistogramBin(program, layer, originalBin) {
|
|
208
|
+
let next = program;
|
|
209
|
+
const current = layer.encoding?.x?.bin ?? {};
|
|
210
|
+
for (const property of ["maxBins", "step", "boundaries"]) {
|
|
211
|
+
if (Object.hasOwn(originalBin, property)) {
|
|
212
|
+
next = next.editSemantic({
|
|
213
|
+
property: `layer[${layer.id}].encoding.x.bin.${property}`,
|
|
214
|
+
value: originalBin[property]
|
|
215
|
+
});
|
|
216
|
+
} else if (Object.hasOwn(current, property)) {
|
|
217
|
+
next = next.editSemantic({
|
|
218
|
+
property: `layer[${layer.id}].encoding.x.bin.${property}`,
|
|
219
|
+
remove: true
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return next;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function removeActiveMarkFilter(program, layer, owner) {
|
|
227
|
+
let next = program.editSemantic({
|
|
228
|
+
property: `layer[${layer.id}].data`,
|
|
229
|
+
value: owner.source
|
|
230
|
+
});
|
|
231
|
+
if (owner.originalBin !== undefined) {
|
|
232
|
+
next = restoreHistogramBin(next, requireLayer(next, layer.id), owner.originalBin);
|
|
233
|
+
}
|
|
234
|
+
next = withoutMarkFilterConfig(next, layer.id)
|
|
235
|
+
.releaseDerivedData({ id: owner.dataset })
|
|
236
|
+
._withContext({ currentData: owner.source });
|
|
237
|
+
return applyLayerDataRematerialization(next, layer.id);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function nextMarkFilterDatasetId(program, layerId) {
|
|
241
|
+
const base = `${layerId}FilteredData`;
|
|
242
|
+
if (!hasDataset(program, base)) return base;
|
|
243
|
+
let revision = 2;
|
|
244
|
+
while (hasDataset(program, `${base}${revision}`)) revision += 1;
|
|
245
|
+
return `${base}${revision}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function sameSelector(left, right) {
|
|
249
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const removeMarkFilter = action(
|
|
253
|
+
{
|
|
254
|
+
op: "removeMarkFilter",
|
|
255
|
+
description: "Remove one active final-item filter and restore its source."
|
|
256
|
+
},
|
|
257
|
+
function (args = {}) {
|
|
258
|
+
validateKeys(args, REMOVE_MARK_FILTER_OPTIONS, "removeMarkFilter");
|
|
259
|
+
const target = args.target === undefined
|
|
260
|
+
? undefined
|
|
261
|
+
: validateUserId(args.target, "Mark filter target id");
|
|
262
|
+
const layer = resolveEligibleLayer(this, {
|
|
263
|
+
target,
|
|
264
|
+
label: "mark filter",
|
|
265
|
+
predicate: candidate => activeMarkFilter(this, candidate) !== undefined
|
|
107
266
|
});
|
|
267
|
+
return removeActiveMarkFilter(this, layer, activeMarkFilter(this, layer));
|
|
108
268
|
}
|
|
109
269
|
);
|
|
110
270
|
|
|
@@ -115,6 +275,9 @@ export const filterMarks = action(
|
|
|
115
275
|
},
|
|
116
276
|
function (args = {}) {
|
|
117
277
|
validateKeys(args, MARK_OPTIONS, "filterMarks");
|
|
278
|
+
if (args.mode !== undefined && !["replace", "compose"].includes(args.mode)) {
|
|
279
|
+
throw new Error(`Unknown mark filter mode "${args.mode}".`);
|
|
280
|
+
}
|
|
118
281
|
const target = args.target === undefined
|
|
119
282
|
? undefined
|
|
120
283
|
: validateUserId(args.target, "Filter mark target id");
|
|
@@ -130,46 +293,67 @@ export const filterMarks = action(
|
|
|
130
293
|
`filterMarks does not support composite mark "${layer.id}"; filter its source dataset first.`
|
|
131
294
|
);
|
|
132
295
|
}
|
|
133
|
-
const
|
|
134
|
-
if (hasDataset(this, derivedId)) {
|
|
135
|
-
throw new Error(`Dataset "${derivedId}" already exists.`);
|
|
136
|
-
}
|
|
296
|
+
const owner = activeMarkFilter(this, layer);
|
|
137
297
|
const selector = Object.fromEntries(
|
|
138
298
|
MARK_SELECTOR_KEYS.flatMap(key =>
|
|
139
299
|
Object.hasOwn(args, key) ? [[key, args[key]]] : []
|
|
140
300
|
)
|
|
141
301
|
);
|
|
142
302
|
const resolved = resolveMarkFilterSelection(this, layer.id, selector);
|
|
143
|
-
|
|
144
|
-
|
|
303
|
+
const previousSelector = owner?.selectors.at(-1);
|
|
304
|
+
if (previousSelector !== undefined && sameSelector(previousSelector, resolved.selector)) {
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
if (owner !== undefined && args.mode === undefined) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
"Repeated filterMarks requires mode \"replace\" or \"compose\"."
|
|
310
|
+
);
|
|
145
311
|
}
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
312
|
+
const selectors = owner === undefined || args.mode === "replace"
|
|
313
|
+
? [resolved.selector]
|
|
314
|
+
: [...owner.selectors, resolved.selector];
|
|
315
|
+
let next = owner === undefined
|
|
316
|
+
? this
|
|
317
|
+
: removeActiveMarkFilter(this, layer, owner);
|
|
318
|
+
const baseLayer = requireLayer(next, layer.id);
|
|
319
|
+
const first = resolveMarkFilterSelection(next, layer.id, selectors[0]);
|
|
320
|
+
const transform = normalizeMarkFilterTransform(layer.id, selectors);
|
|
321
|
+
const boundaries = retainedHistogramBoundaries(baseLayer, first.items);
|
|
322
|
+
const originalBin = baseLayer.mark?.type === "bar" &&
|
|
323
|
+
baseLayer.encoding?.x?.bin !== undefined
|
|
324
|
+
? baseLayer.encoding.x.bin
|
|
325
|
+
: undefined;
|
|
326
|
+
const derivedId = nextMarkFilterDatasetId(next, layer.id);
|
|
327
|
+
next = next
|
|
149
328
|
.createDerivedData({
|
|
150
329
|
id: derivedId,
|
|
151
|
-
source:
|
|
330
|
+
source: baseLayer.data,
|
|
152
331
|
transform: [transform]
|
|
153
|
-
})
|
|
154
|
-
.materializeMarkFilteredData({ id: derivedId });
|
|
332
|
+
});
|
|
155
333
|
if (boundaries !== undefined) {
|
|
156
334
|
for (const property of ["maxBins", "step"]) {
|
|
157
|
-
if (Object.hasOwn(
|
|
335
|
+
if (Object.hasOwn(baseLayer.encoding.x.bin, property)) {
|
|
158
336
|
next = next.editSemantic({
|
|
159
|
-
property: `layer[${
|
|
337
|
+
property: `layer[${baseLayer.id}].encoding.x.bin.${property}`,
|
|
160
338
|
remove: true
|
|
161
339
|
});
|
|
162
340
|
}
|
|
163
341
|
}
|
|
164
342
|
next = next.editSemantic({
|
|
165
|
-
property: `layer[${
|
|
343
|
+
property: `layer[${baseLayer.id}].encoding.x.bin.boundaries`,
|
|
166
344
|
value: boundaries
|
|
167
345
|
});
|
|
168
346
|
}
|
|
169
|
-
next = next.
|
|
170
|
-
|
|
171
|
-
|
|
347
|
+
next = next._withMarkConfig(baseLayer.id, {
|
|
348
|
+
...next.markConfigs[baseLayer.id],
|
|
349
|
+
markFilter: {
|
|
350
|
+
dataset: derivedId,
|
|
351
|
+
source: baseLayer.data,
|
|
352
|
+
selectors,
|
|
353
|
+
empty: false,
|
|
354
|
+
...(originalBin === undefined ? {} : { originalBin })
|
|
355
|
+
}
|
|
172
356
|
});
|
|
173
|
-
return
|
|
357
|
+
return next.materializeMarkFilteredData({ id: derivedId });
|
|
174
358
|
}
|
|
175
359
|
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveFoldRows,
|
|
3
|
+
normalizeFoldTransform
|
|
4
|
+
} from "../../grammar/fold.js";
|
|
5
|
+
import { derivedCreator, derivedMaterializer } from "./shared.js";
|
|
6
|
+
|
|
7
|
+
const OPTIONS = Object.freeze(["id", "source", "fields", "as"]);
|
|
8
|
+
|
|
9
|
+
export const materializeFoldData = derivedMaterializer(
|
|
10
|
+
"materializeFoldData",
|
|
11
|
+
"Materialize one immutable wide-to-long dataset.",
|
|
12
|
+
"fold",
|
|
13
|
+
deriveFoldRows
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const createFoldData = derivedCreator(
|
|
17
|
+
"createFoldData",
|
|
18
|
+
"Create reusable key/value rows from selected wide fields.",
|
|
19
|
+
OPTIONS,
|
|
20
|
+
"Fold dataset id",
|
|
21
|
+
"Fold source dataset id",
|
|
22
|
+
normalizeFoldTransform,
|
|
23
|
+
"materializeFoldData"
|
|
24
|
+
);
|
|
@@ -4,12 +4,15 @@ import {
|
|
|
4
4
|
} from "./timeUnit.js";
|
|
5
5
|
import {
|
|
6
6
|
createDerivedData,
|
|
7
|
+
bindMarkData,
|
|
7
8
|
rebindLayerData,
|
|
8
9
|
releaseDerivedData
|
|
9
10
|
} from "./derived.js";
|
|
10
11
|
import {
|
|
11
12
|
filterData,
|
|
12
13
|
filterMarks,
|
|
14
|
+
materializeEmptyMark,
|
|
15
|
+
removeMarkFilter,
|
|
13
16
|
materializeFilteredData,
|
|
14
17
|
materializeMarkFilteredData
|
|
15
18
|
} from "./filter.js";
|
|
@@ -24,6 +27,7 @@ import {
|
|
|
24
27
|
materializeDensityData
|
|
25
28
|
} from "./density.js";
|
|
26
29
|
import { createIntervalData, materializeIntervalData } from "./interval.js";
|
|
30
|
+
import { createECDFData, materializeECDFData } from "./ecdf.js";
|
|
27
31
|
import { createHorizonData, materializeHorizonData } from "./horizon.js";
|
|
28
32
|
import {
|
|
29
33
|
createBin2DData,
|
|
@@ -32,11 +36,17 @@ import {
|
|
|
32
36
|
} from "./bin2d.js";
|
|
33
37
|
import { createData } from "./create.js";
|
|
34
38
|
import { createWindowData, materializeWindowData } from "./window.js";
|
|
39
|
+
import { createSummaryData, materializeSummaryData } from "./summary.js";
|
|
40
|
+
import { createBinData, materializeBinData } from "./bin.js";
|
|
41
|
+
import { createFoldData, materializeFoldData } from "./fold.js";
|
|
42
|
+
import { createComputedData, materializeComputedData } from "./computed.js";
|
|
43
|
+
import { createStackData, materializeStackData } from "./stack.js";
|
|
35
44
|
import { createBoxSummaryData, createBoxOutlierData, materializeBoxSummaryData, materializeBoxOutlierData } from "./box.js";
|
|
36
45
|
|
|
37
46
|
export function registerDataActions(ProgramClass) {
|
|
38
47
|
ProgramClass.prototype.createData = createData;
|
|
39
48
|
ProgramClass.prototype.createDerivedData = createDerivedData;
|
|
49
|
+
ProgramClass.prototype.bindMarkData = bindMarkData;
|
|
40
50
|
ProgramClass.prototype.releaseDerivedData = releaseDerivedData;
|
|
41
51
|
ProgramClass.prototype.rebindLayerData = rebindLayerData;
|
|
42
52
|
ProgramClass.prototype.createDensityData = createDensityData;
|
|
@@ -46,6 +56,8 @@ export function registerDataActions(ProgramClass) {
|
|
|
46
56
|
ProgramClass.prototype.materializeMarkFilteredData = materializeMarkFilteredData;
|
|
47
57
|
ProgramClass.prototype.filterData = filterData;
|
|
48
58
|
ProgramClass.prototype.filterMarks = filterMarks;
|
|
59
|
+
ProgramClass.prototype.materializeEmptyMark = materializeEmptyMark;
|
|
60
|
+
ProgramClass.prototype.removeMarkFilter = removeMarkFilter;
|
|
49
61
|
ProgramClass.prototype.materializeRegressionData = materializeRegressionData;
|
|
50
62
|
ProgramClass.prototype.materializeDensityData = materializeDensityData;
|
|
51
63
|
ProgramClass.prototype.createGradientProfileData = createGradientProfileData;
|
|
@@ -54,10 +66,22 @@ export function registerDataActions(ProgramClass) {
|
|
|
54
66
|
ProgramClass.prototype.createRegressionData = createRegressionData;
|
|
55
67
|
ProgramClass.prototype.materializeIntervalData = materializeIntervalData;
|
|
56
68
|
ProgramClass.prototype.createIntervalData = createIntervalData;
|
|
69
|
+
ProgramClass.prototype.createECDFData = createECDFData;
|
|
70
|
+
ProgramClass.prototype.materializeECDFData = materializeECDFData;
|
|
57
71
|
ProgramClass.prototype.createHorizonData = createHorizonData;
|
|
58
72
|
ProgramClass.prototype.materializeHorizonData = materializeHorizonData;
|
|
59
73
|
ProgramClass.prototype.createWindowData = createWindowData;
|
|
60
74
|
ProgramClass.prototype.materializeWindowData = materializeWindowData;
|
|
75
|
+
ProgramClass.prototype.createSummaryData = createSummaryData;
|
|
76
|
+
ProgramClass.prototype.materializeSummaryData = materializeSummaryData;
|
|
77
|
+
ProgramClass.prototype.createBinData = createBinData;
|
|
78
|
+
ProgramClass.prototype.materializeBinData = materializeBinData;
|
|
79
|
+
ProgramClass.prototype.createFoldData = createFoldData;
|
|
80
|
+
ProgramClass.prototype.materializeFoldData = materializeFoldData;
|
|
81
|
+
ProgramClass.prototype.createComputedData = createComputedData;
|
|
82
|
+
ProgramClass.prototype.materializeComputedData = materializeComputedData;
|
|
83
|
+
ProgramClass.prototype.createStackData = createStackData;
|
|
84
|
+
ProgramClass.prototype.materializeStackData = materializeStackData;
|
|
61
85
|
ProgramClass.prototype.createTimeUnitData = createTimeUnitData;
|
|
62
86
|
ProgramClass.prototype.materializeTimeUnitData = materializeTimeUnitData;
|
|
63
87
|
ProgramClass.prototype.createBin2DData = createBin2DData;
|
|
@@ -11,6 +11,7 @@ const OPTIONS = Object.freeze([
|
|
|
11
11
|
"groupBy",
|
|
12
12
|
"center",
|
|
13
13
|
"extent",
|
|
14
|
+
"method",
|
|
14
15
|
"level",
|
|
15
16
|
"as"
|
|
16
17
|
]);
|
|
@@ -39,6 +40,7 @@ export const createIntervalData = derivedCreator(
|
|
|
39
40
|
groupBy: args.groupBy,
|
|
40
41
|
center: args.center,
|
|
41
42
|
extent: args.extent,
|
|
43
|
+
method: args.method,
|
|
42
44
|
level: args.level,
|
|
43
45
|
as
|
|
44
46
|
});
|
|
@@ -7,7 +7,7 @@ import { planDerivedDataRevision } from
|
|
|
7
7
|
import { findDataset } from "../../selectors/datasets.js";
|
|
8
8
|
import { resolveEligibleLayer } from "../../selectors/layers.js";
|
|
9
9
|
|
|
10
|
-
const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "level"]);
|
|
10
|
+
const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "method", "level"]);
|
|
11
11
|
|
|
12
12
|
export function ownOptions(value, options) {
|
|
13
13
|
return Object.fromEntries(
|
|
@@ -42,6 +42,7 @@ export function createResolvedIntervalData(program, resolved) {
|
|
|
42
42
|
groupBy: resolved.groupBy,
|
|
43
43
|
center: resolved.interval.center,
|
|
44
44
|
extent: resolved.interval.extent,
|
|
45
|
+
method: resolved.interval.method,
|
|
45
46
|
level: resolved.interval.level,
|
|
46
47
|
as: resolved.fields
|
|
47
48
|
});
|
|
@@ -49,7 +50,9 @@ export function createResolvedIntervalData(program, resolved) {
|
|
|
49
50
|
|
|
50
51
|
export function applyIntervalRevision(program, interval) {
|
|
51
52
|
if (!interval.changed) return program;
|
|
52
|
-
let next =
|
|
53
|
+
let next = interval.dataArgs === undefined
|
|
54
|
+
? program
|
|
55
|
+
: program.createIntervalData(interval.dataArgs);
|
|
53
56
|
for (const rebind of interval.revision.rebinds) {
|
|
54
57
|
next = next.rebindLayerData(rebind);
|
|
55
58
|
}
|
|
@@ -57,11 +60,84 @@ export function applyIntervalRevision(program, interval) {
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export function releaseIntervalRevision(program, interval) {
|
|
60
|
-
return interval.changed
|
|
63
|
+
return interval.changed && interval.revision.release !== undefined
|
|
61
64
|
? program.releaseDerivedData(interval.revision.release)
|
|
62
65
|
: program;
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
export function findIntervalTransform(program, data) {
|
|
69
|
+
const dataset = findDataset(program, data);
|
|
70
|
+
return dataset?.transform?.length === 1 &&
|
|
71
|
+
dataset.transform[0].type === "interval"
|
|
72
|
+
? dataset.transform[0]
|
|
73
|
+
: undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function collectIntervalConsumers(program, owners) {
|
|
77
|
+
const consumers = new Set(owners);
|
|
78
|
+
let changed = true;
|
|
79
|
+
while (changed) {
|
|
80
|
+
changed = false;
|
|
81
|
+
for (const layer of program.semanticSpec.layers ?? []) {
|
|
82
|
+
if (
|
|
83
|
+
layer.source !== undefined &&
|
|
84
|
+
consumers.has(layer.source) &&
|
|
85
|
+
!consumers.has(layer.id)
|
|
86
|
+
) {
|
|
87
|
+
consumers.add(layer.id);
|
|
88
|
+
changed = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...consumers];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function planIntervalRoleData(program, {
|
|
96
|
+
owner,
|
|
97
|
+
currentData,
|
|
98
|
+
candidate,
|
|
99
|
+
consumers
|
|
100
|
+
}) {
|
|
101
|
+
const previousTransform = findIntervalTransform(program, currentData);
|
|
102
|
+
if (candidate.interval.mode === "statistical") {
|
|
103
|
+
const revision = planDerivedDataRevision(program, {
|
|
104
|
+
owner,
|
|
105
|
+
role: "IntervalData",
|
|
106
|
+
...(previousTransform === undefined ? {} : { previous: currentData }),
|
|
107
|
+
consumers
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
changed: true,
|
|
111
|
+
revision,
|
|
112
|
+
dataId: revision.id,
|
|
113
|
+
dataArgs: {
|
|
114
|
+
id: revision.id,
|
|
115
|
+
source: candidate.source,
|
|
116
|
+
field: candidate.interval.field,
|
|
117
|
+
groupBy: candidate.groupBy,
|
|
118
|
+
center: candidate.interval.center,
|
|
119
|
+
extent: candidate.interval.extent,
|
|
120
|
+
method: candidate.interval.method,
|
|
121
|
+
level: candidate.interval.level,
|
|
122
|
+
as: candidate.fields
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const changed = currentData !== candidate.source;
|
|
127
|
+
return {
|
|
128
|
+
changed,
|
|
129
|
+
dataId: candidate.source,
|
|
130
|
+
revision: {
|
|
131
|
+
rebinds: changed
|
|
132
|
+
? consumers.map(id => ({ id, data: candidate.source }))
|
|
133
|
+
: [],
|
|
134
|
+
...(previousTransform === undefined
|
|
135
|
+
? {}
|
|
136
|
+
: { release: { id: currentData } })
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
65
141
|
export function planIntervalEdit(program, {
|
|
66
142
|
owner,
|
|
67
143
|
data,
|
|
@@ -75,7 +151,7 @@ export function planIntervalEdit(program, {
|
|
|
75
151
|
validateKeys(statistics, STATISTICS_OPTIONS, `${operation} statistics`);
|
|
76
152
|
if (!STATISTICS_OPTIONS.some(key => Object.hasOwn(statistics, key))) {
|
|
77
153
|
throw new Error(
|
|
78
|
-
`${operation} statistics requires center, extent, or level.`
|
|
154
|
+
`${operation} statistics requires center, extent, method, or level.`
|
|
79
155
|
);
|
|
80
156
|
}
|
|
81
157
|
const previous = findDataset(program, data);
|
|
@@ -95,6 +171,11 @@ export function planIntervalEdit(program, {
|
|
|
95
171
|
? statistics.extent
|
|
96
172
|
: transform.extent;
|
|
97
173
|
const raw = { center, extent };
|
|
174
|
+
if (Object.hasOwn(statistics, "method")) {
|
|
175
|
+
raw.method = statistics.method;
|
|
176
|
+
} else if (extent === "ci" && transform.extent === "ci") {
|
|
177
|
+
raw.method = transform.method;
|
|
178
|
+
}
|
|
98
179
|
if (Object.hasOwn(statistics, "level")) {
|
|
99
180
|
raw.level = statistics.level;
|
|
100
181
|
} else if (extent === "ci" && transform.extent === "ci") {
|
|
@@ -104,6 +185,7 @@ export function planIntervalEdit(program, {
|
|
|
104
185
|
const current = {
|
|
105
186
|
center: transform.center,
|
|
106
187
|
extent: transform.extent,
|
|
188
|
+
...(transform.method === undefined ? {} : { method: transform.method }),
|
|
107
189
|
...(transform.level === undefined ? {} : { level: transform.level })
|
|
108
190
|
};
|
|
109
191
|
const changed = JSON.stringify(parameters) !== JSON.stringify(current);
|