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
package/src/actions/data/edit.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import {resolveDatasetTransformInputs} from "./shared.js";
|
|
1
2
|
import { action, closedAction } from "../../core/action.js";
|
|
2
3
|
import { isPlainObject } from "../../core/immutable.js";
|
|
3
4
|
import { validateUserId } from "../../core/identifiers.js";
|
|
4
5
|
import { validateKeys } from "../../core/validation.js";
|
|
6
|
+
import {orderedDatasetDependents} from "../../grammar/datasetDependencies.js";
|
|
7
|
+
import {rebindTransformDatasetInputs} from "../../core/datasetReferences.js";
|
|
5
8
|
import {
|
|
6
9
|
findTransformPolicy,
|
|
7
10
|
normalizeDatasetTransformEdit,
|
|
@@ -33,6 +36,8 @@ const CREATOR_BY_TYPE = Object.freeze({
|
|
|
33
36
|
density: "createDensityData",
|
|
34
37
|
ecdf: "createECDFData",
|
|
35
38
|
filter: "filterData",
|
|
39
|
+
flatten: "createFlattenData",
|
|
40
|
+
lookup:"createLookupData",
|
|
36
41
|
fold: "createFoldData",
|
|
37
42
|
impute: "createImputedData",
|
|
38
43
|
interval: "createIntervalData",
|
|
@@ -53,6 +58,8 @@ const REVISION_ROLE = Object.freeze({
|
|
|
53
58
|
density: "DensityData",
|
|
54
59
|
ecdf: "ECDFData",
|
|
55
60
|
filter: "FilteredData",
|
|
61
|
+
flatten: "FlattenData",
|
|
62
|
+
lookup:"LookupData",
|
|
56
63
|
fold: "FoldData",
|
|
57
64
|
impute: "ImputedData",
|
|
58
65
|
interval: "IntervalData",
|
|
@@ -168,30 +175,9 @@ export function normalizeDerivedDependentPolicy(value) {
|
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
function downstreamDatasets(program, root) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
174
|
-
const bySource = new Map();
|
|
175
|
-
for (const dataset of program.semanticSpec.datasets) {
|
|
176
|
-
if (dataset.source === undefined) continue;
|
|
177
|
-
const children = bySource.get(dataset.source) ?? [];
|
|
178
|
-
children.push(dataset);
|
|
179
|
-
bySource.set(dataset.source, children);
|
|
180
|
-
}
|
|
181
|
-
for (const children of bySource.values()) {
|
|
182
|
-
children.sort((left, right) => order.get(left.id) - order.get(right.id));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const result = [];
|
|
186
|
-
const visiting = new Set();
|
|
187
|
-
const visited = new Set();
|
|
188
|
-
function visit(id) {
|
|
189
|
-
if (visiting.has(id)) {
|
|
190
|
-
throw new Error(`Derived dataset dependency graph contains a cycle at "${id}".`);
|
|
191
|
-
}
|
|
192
|
-
if (visited.has(id)) return;
|
|
193
|
-
visiting.add(id);
|
|
194
|
-
for (const child of bySource.get(id) ?? []) {
|
|
178
|
+
// Statistical reference data is an owned cache; its mark lifecycle replays it.
|
|
179
|
+
const result=orderedDatasetDependents(program.semanticSpec.datasets,root,{skip:dataset=>dataset.transform?.[0]?.type==="statisticalReference"});
|
|
180
|
+
for(const child of result){
|
|
195
181
|
if (child.transform?.length !== 1) {
|
|
196
182
|
throw new Error(
|
|
197
183
|
`Derived dataset "${child.id}" requires one replayable transform.`
|
|
@@ -204,16 +190,7 @@ function downstreamDatasets(program, root) {
|
|
|
204
190
|
`"${child.transform[0].type ?? "unknown"}".`
|
|
205
191
|
);
|
|
206
192
|
}
|
|
207
|
-
// Statistical reference data is an owned cache. Its mark lifecycle
|
|
208
|
-
// reconnects and recomputes it after the visible source mark is rebound.
|
|
209
|
-
if (child.transform[0].type === "statisticalReference") continue;
|
|
210
|
-
result.push(child);
|
|
211
|
-
visit(child.id);
|
|
212
|
-
}
|
|
213
|
-
visiting.delete(id);
|
|
214
|
-
visited.add(id);
|
|
215
193
|
}
|
|
216
|
-
visit(root);
|
|
217
194
|
return result;
|
|
218
195
|
}
|
|
219
196
|
|
|
@@ -260,6 +237,7 @@ function buildRevisionPlan(
|
|
|
260
237
|
`Derived dataset "${resolved.owner}" cannot use one of its dependents as source.`
|
|
261
238
|
);
|
|
262
239
|
}
|
|
240
|
+
if(definition?.type==="lookup"&&[resolved.current,...downstream.map(d=>d.id)].includes(definition.lookup.data))throw new Error(`Derived dataset "${resolved.owner}" cannot use its own result or dependent as lookup data.`);
|
|
263
241
|
const reserved = new Set(resolved.revision === undefined ? [] : [resolved.revision]);
|
|
264
242
|
const revisions = [];
|
|
265
243
|
const replacements = new Map();
|
|
@@ -273,7 +251,7 @@ function buildRevisionPlan(
|
|
|
273
251
|
old: dataset.id,
|
|
274
252
|
id,
|
|
275
253
|
source,
|
|
276
|
-
transform,
|
|
254
|
+
transform:rebindTransformDatasetInputs(transform,replacements),
|
|
277
255
|
family: ownerRecord?.family,
|
|
278
256
|
owner: ownerRecord?.owner
|
|
279
257
|
};
|
|
@@ -422,7 +400,7 @@ export const editDerivedData = /* @__PURE__ */ closedAction(
|
|
|
422
400
|
args.target,
|
|
423
401
|
args.definition.type
|
|
424
402
|
);
|
|
425
|
-
const definition = requestedDatasetTransform(args.definition);
|
|
403
|
+
const definition = resolveDatasetTransformInputs(this,requestedDatasetTransform(args.definition));
|
|
426
404
|
findTransformPolicy(definition.type).validate(definition);
|
|
427
405
|
const previous = requestedDatasetTransform(resolved.dataset.transform[0]);
|
|
428
406
|
if (sameRequested(definition, previous)) return this;
|
|
@@ -477,6 +455,12 @@ export const editComputedData = /* @__PURE__ */ focusedEditor(
|
|
|
477
455
|
export const editFilteredData = /* @__PURE__ */ focusedEditor(
|
|
478
456
|
"editFilteredData", "filter", ["field", "oneOf", "noneOf", "predicate", "range", "nulls"]
|
|
479
457
|
);
|
|
458
|
+
export const editFlattenData = /* @__PURE__ */ focusedEditor(
|
|
459
|
+
"editFlattenData", "flatten", ["fields", "as"]
|
|
460
|
+
);
|
|
461
|
+
export const editLookupData=/* @__PURE__ */focusedEditor(
|
|
462
|
+
"editLookupData","lookup",["field","lookup"]
|
|
463
|
+
);
|
|
480
464
|
export const editFoldData = /* @__PURE__ */ focusedEditor(
|
|
481
465
|
"editFoldData", "fold", ["fields", "as"]
|
|
482
466
|
);
|
|
@@ -492,7 +476,7 @@ export const editBinData = /* @__PURE__ */ focusedEditor(
|
|
|
492
476
|
export const editTimeUnitData = /* @__PURE__ */ focusedEditor(
|
|
493
477
|
"editTimeUnitData", "timeUnit", [
|
|
494
478
|
"field", "unit", "as", "temporalUnit", "timeZone", "weekStartsOn",
|
|
495
|
-
"weekRule"
|
|
479
|
+
"weekRule", "missing"
|
|
496
480
|
]
|
|
497
481
|
);
|
|
498
482
|
export const editWindowData = /* @__PURE__ */ focusedEditor(
|
|
@@ -549,6 +533,8 @@ export const EDIT_DERIVED_DATA_ACTIONS = Object.freeze({
|
|
|
549
533
|
editDerivedData,
|
|
550
534
|
editComputedData,
|
|
551
535
|
editFilteredData,
|
|
536
|
+
editFlattenData,
|
|
537
|
+
editLookupData,
|
|
552
538
|
editFoldData,
|
|
553
539
|
editSummaryData,
|
|
554
540
|
editBinData,
|
|
@@ -1,376 +1 @@
|
|
|
1
|
-
import { closedAction }
|
|
2
|
-
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
-
|
|
4
|
-
import { withPreviewDatasetValues } from
|
|
5
|
-
"../primitives/semanticAction.js";
|
|
6
|
-
import {
|
|
7
|
-
deriveFilteredRows,
|
|
8
|
-
normalizeFilterTransform
|
|
9
|
-
} from "../../grammar/filter.js";
|
|
10
|
-
import {
|
|
11
|
-
deriveMarkFilteredRows,
|
|
12
|
-
markFilterSelectors,
|
|
13
|
-
normalizeMarkFilterTransform
|
|
14
|
-
} from "../../grammar/markFilter.js";
|
|
15
|
-
import { resolveMarkFilterSelection } from "../../materialization/selection/filter.js";
|
|
16
|
-
import { findSelectionPolicy } from
|
|
17
|
-
"../../materialization/selection/policies/index.js";
|
|
18
|
-
import {
|
|
19
|
-
applyLayerDataRematerialization,
|
|
20
|
-
applyLayerEmptyDataView
|
|
21
|
-
} from "../../materialization/dependencies.js";
|
|
22
|
-
import {
|
|
23
|
-
hasDataset,
|
|
24
|
-
findDataset,
|
|
25
|
-
resolveDatasetReference,
|
|
26
|
-
requireLayer,
|
|
27
|
-
resolveEligibleLayer
|
|
28
|
-
} from "../../selectors/index.js";
|
|
29
|
-
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
30
|
-
import {
|
|
31
|
-
assertFieldsAvailable,
|
|
32
|
-
deriveTransformSchema,
|
|
33
|
-
transformInputFields
|
|
34
|
-
} from "../../grammar/datasetSchema.js";
|
|
35
|
-
|
|
36
|
-
const OPTIONS = Object.freeze([
|
|
37
|
-
"id", "source", "field", "oneOf", "noneOf", "predicate", "range", "nulls"
|
|
38
|
-
]);
|
|
39
|
-
const MARK_SELECTOR_KEYS = Object.freeze([
|
|
40
|
-
"grain", "field", "channel", "property", "op", "value", "values",
|
|
41
|
-
"min", "max", "inclusive", "count", "groupBy", "ties"
|
|
42
|
-
]);
|
|
43
|
-
const MARK_OPTIONS = Object.freeze(["target", "mode", ...MARK_SELECTOR_KEYS]);
|
|
44
|
-
const REMOVE_MARK_FILTER_OPTIONS = Object.freeze(["target"]);
|
|
45
|
-
const EMPTY_MARK_OPTIONS = Object.freeze(["id"]);
|
|
46
|
-
|
|
47
|
-
function retainedHistogramBoundaries(layer, items) {
|
|
48
|
-
if (layer.mark?.type !== "bar" || layer.encoding?.x?.bin === undefined) {
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
const boundaries = [...new Set(items.flatMap(item => [
|
|
52
|
-
item.channels.x,
|
|
53
|
-
item.channels.x2
|
|
54
|
-
]).filter(Number.isFinite))].sort((left, right) => left - right);
|
|
55
|
-
return boundaries.length < 2 ? undefined : boundaries;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const materializeFilteredData = /* @__PURE__ */ closedAction(
|
|
59
|
-
{ op: "materializeFilteredData", description: "Materialize one filtered derived dataset." }, MATERIALIZE_OPTIONS,
|
|
60
|
-
function (args = {}) {
|
|
61
|
-
const { id, source, transform } = requireDerivedDataset(
|
|
62
|
-
this,
|
|
63
|
-
args.id,
|
|
64
|
-
"filter"
|
|
65
|
-
);
|
|
66
|
-
assertFieldsAvailable(source.schema, transformInputFields(transform), {
|
|
67
|
-
data: source.id,
|
|
68
|
-
operation: "materializeFilteredData"
|
|
69
|
-
});
|
|
70
|
-
const values = deriveFilteredRows(source.values, transform);
|
|
71
|
-
const schema = deriveTransformSchema(source.schema, transform, values, {
|
|
72
|
-
sourceId: source.id,
|
|
73
|
-
ownerId: id
|
|
74
|
-
});
|
|
75
|
-
return this.editSemantic({ property: `dataset[${id}].values`, value: values });
|
|
76
|
-
}
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
export const filterData = /* @__PURE__ */ closedAction(
|
|
80
|
-
{ op: "filterData", description: "Create a named dataset from one field filter." }, OPTIONS,
|
|
81
|
-
function (args = {}) {
|
|
82
|
-
const id = validateUserId(args.id, "Filtered dataset id");
|
|
83
|
-
const requestedSource = validateUserId(
|
|
84
|
-
args.source ?? this.context.currentData,
|
|
85
|
-
"Source dataset id"
|
|
86
|
-
);
|
|
87
|
-
const source = resolveDatasetReference(
|
|
88
|
-
this,
|
|
89
|
-
requestedSource,
|
|
90
|
-
"Filter source dataset"
|
|
91
|
-
).id;
|
|
92
|
-
const transform = normalizeFilterTransform(args);
|
|
93
|
-
const next = this
|
|
94
|
-
.createDerivedData({
|
|
95
|
-
id,
|
|
96
|
-
source,
|
|
97
|
-
transform: [transform]
|
|
98
|
-
})
|
|
99
|
-
.materializeFilteredData({ id });
|
|
100
|
-
return this.actionStack.length === 1
|
|
101
|
-
? next._withMaterializationConfig(
|
|
102
|
-
["data", "filter", id],
|
|
103
|
-
{ current: id }
|
|
104
|
-
)
|
|
105
|
-
: next;
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
export const materializeMarkFilteredData = /* @__PURE__ */ closedAction(
|
|
110
|
-
{
|
|
111
|
-
op: "materializeMarkFilteredData",
|
|
112
|
-
description: "Materialize member rows retained by one final-item mark selection."
|
|
113
|
-
}, MATERIALIZE_OPTIONS,
|
|
114
|
-
function (args = {}) {
|
|
115
|
-
const { id, dataset, source, transform } = requireDerivedDataset(
|
|
116
|
-
this,
|
|
117
|
-
args.id,
|
|
118
|
-
"markFilter"
|
|
119
|
-
);
|
|
120
|
-
const layer = requireLayer(this, transform.target);
|
|
121
|
-
if (layer?.data !== dataset.source) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Mark filter target "${transform.target}" must still use source dataset "${dataset.source}".`
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
let preview = this;
|
|
127
|
-
let currentRows = source.values;
|
|
128
|
-
const selectors = markFilterSelectors(transform);
|
|
129
|
-
for (const [index, selector] of selectors.entries()) {
|
|
130
|
-
const resolved = resolveMarkFilterSelection(
|
|
131
|
-
preview,
|
|
132
|
-
transform.target,
|
|
133
|
-
selector
|
|
134
|
-
);
|
|
135
|
-
currentRows = deriveMarkFilteredRows(
|
|
136
|
-
currentRows,
|
|
137
|
-
resolved.items,
|
|
138
|
-
resolved.keys
|
|
139
|
-
);
|
|
140
|
-
if (index < selectors.length - 1) {
|
|
141
|
-
preview = withPreviewDatasetValues(preview, {
|
|
142
|
-
id,
|
|
143
|
-
values: currentRows,
|
|
144
|
-
target: transform.target
|
|
145
|
-
});
|
|
146
|
-
preview = currentRows.length === 0
|
|
147
|
-
? applyLayerEmptyDataView(preview, transform.target)
|
|
148
|
-
: applyLayerDataRematerialization(preview, transform.target);
|
|
149
|
-
currentRows = findDataset(preview, id).values;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (selectors.length > 1) {
|
|
153
|
-
preview = withPreviewDatasetValues(preview, {
|
|
154
|
-
id,
|
|
155
|
-
omitValues: true
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
let next = preview.editSemantic({
|
|
159
|
-
property: `dataset[${id}].values`,
|
|
160
|
-
value: currentRows
|
|
161
|
-
});
|
|
162
|
-
if (requireLayer(next, transform.target).data !== id) {
|
|
163
|
-
next = next.editSemantic({
|
|
164
|
-
property: `layer[${transform.target}].data`,
|
|
165
|
-
value: id
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
next = next._withMarkConfig(transform.target, {
|
|
169
|
-
...next.markConfigs[transform.target],
|
|
170
|
-
markFilter: {
|
|
171
|
-
...next.markConfigs[transform.target].markFilter,
|
|
172
|
-
empty: currentRows.length === 0
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
return currentRows.length === 0
|
|
176
|
-
? next.materializeEmptyMark({ id: transform.target })
|
|
177
|
-
: applyLayerDataRematerialization(next, transform.target);
|
|
178
|
-
}
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
export const materializeEmptyMark = /* @__PURE__ */ closedAction(
|
|
182
|
-
{
|
|
183
|
-
op: "materializeEmptyMark",
|
|
184
|
-
description: "Clear one active empty-filter mark without changing its domains."
|
|
185
|
-
}, EMPTY_MARK_OPTIONS,
|
|
186
|
-
function (args = {}) {
|
|
187
|
-
const id = validateUserId(args.id, "Empty mark id");
|
|
188
|
-
const layer = requireLayer(this, id);
|
|
189
|
-
const owner = this.markConfigs[id]?.markFilter;
|
|
190
|
-
const dataset = findDataset(this, layer.data);
|
|
191
|
-
if (owner?.empty !== true || dataset?.values?.length !== 0) {
|
|
192
|
-
throw new Error(`Mark "${id}" does not own an active empty filter.`);
|
|
193
|
-
}
|
|
194
|
-
return applyLayerEmptyDataView(this, id);
|
|
195
|
-
}
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
function activeMarkFilter(program, layer) {
|
|
199
|
-
const configured = program.markConfigs[layer.id]?.markFilter;
|
|
200
|
-
const dataset = findDataset(program, layer.data);
|
|
201
|
-
const transform = dataset?.transform?.[0];
|
|
202
|
-
if (
|
|
203
|
-
dataset?.source === undefined ||
|
|
204
|
-
transform?.type !== "markFilter" ||
|
|
205
|
-
transform.target !== layer.id
|
|
206
|
-
) {
|
|
207
|
-
return undefined;
|
|
208
|
-
}
|
|
209
|
-
return {
|
|
210
|
-
dataset: dataset.id,
|
|
211
|
-
source: dataset.source,
|
|
212
|
-
selectors: markFilterSelectors(transform),
|
|
213
|
-
...(configured?.originalBin === undefined
|
|
214
|
-
? {}
|
|
215
|
-
: { originalBin: configured.originalBin })
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function withoutMarkFilterConfig(program, id) {
|
|
220
|
-
const config = program.markConfigs[id] ?? {};
|
|
221
|
-
const { markFilter: unused, ...remaining } = config;
|
|
222
|
-
void unused;
|
|
223
|
-
return program._withMarkConfig(id, remaining);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function restoreHistogramBin(program, layer, originalBin) {
|
|
227
|
-
let next = program;
|
|
228
|
-
const current = layer.encoding?.x?.bin ?? {};
|
|
229
|
-
for (const property of ["maxBins", "step", "boundaries"]) {
|
|
230
|
-
if (Object.hasOwn(originalBin, property)) {
|
|
231
|
-
next = next.editSemantic({
|
|
232
|
-
property: `layer[${layer.id}].encoding.x.bin.${property}`,
|
|
233
|
-
value: originalBin[property]
|
|
234
|
-
});
|
|
235
|
-
} else if (Object.hasOwn(current, property)) {
|
|
236
|
-
next = next.editSemantic({
|
|
237
|
-
property: `layer[${layer.id}].encoding.x.bin.${property}`,
|
|
238
|
-
remove: true
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return next;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function removeActiveMarkFilter(program, layer, owner) {
|
|
246
|
-
let next = program.editSemantic({
|
|
247
|
-
property: `layer[${layer.id}].data`,
|
|
248
|
-
value: owner.source
|
|
249
|
-
});
|
|
250
|
-
if (owner.originalBin !== undefined) {
|
|
251
|
-
next = restoreHistogramBin(next, requireLayer(next, layer.id), owner.originalBin);
|
|
252
|
-
}
|
|
253
|
-
next = withoutMarkFilterConfig(next, layer.id)
|
|
254
|
-
.releaseDerivedData({ id: owner.dataset })
|
|
255
|
-
._withContext({ currentData: owner.source });
|
|
256
|
-
return applyLayerDataRematerialization(next, layer.id);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function nextMarkFilterDatasetId(program, layerId) {
|
|
260
|
-
const base = `${layerId}FilteredData`;
|
|
261
|
-
if (!hasDataset(program, base)) return base;
|
|
262
|
-
let revision = 2;
|
|
263
|
-
while (hasDataset(program, `${base}${revision}`)) revision += 1;
|
|
264
|
-
return `${base}${revision}`;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function sameSelector(left, right) {
|
|
268
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export const removeMarkFilter = /* @__PURE__ */ closedAction(
|
|
272
|
-
{
|
|
273
|
-
op: "removeMarkFilter",
|
|
274
|
-
description: "Remove one active final-item filter and restore its source."
|
|
275
|
-
}, REMOVE_MARK_FILTER_OPTIONS,
|
|
276
|
-
function (args = {}) {
|
|
277
|
-
const target = args.target === undefined
|
|
278
|
-
? undefined
|
|
279
|
-
: validateUserId(args.target, "Mark filter target id");
|
|
280
|
-
const layer = resolveEligibleLayer(this, {
|
|
281
|
-
target,
|
|
282
|
-
label: "mark filter",
|
|
283
|
-
predicate: candidate => activeMarkFilter(this, candidate) !== undefined
|
|
284
|
-
});
|
|
285
|
-
return removeActiveMarkFilter(this, layer, activeMarkFilter(this, layer));
|
|
286
|
-
}
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
export const filterMarks = /* @__PURE__ */ closedAction(
|
|
290
|
-
{
|
|
291
|
-
op: "filterMarks",
|
|
292
|
-
description: "Retain selected final mark items through immutable derived data."
|
|
293
|
-
}, MARK_OPTIONS,
|
|
294
|
-
function (args = {}) {
|
|
295
|
-
if (args.mode !== undefined && !["replace", "compose"].includes(args.mode)) {
|
|
296
|
-
throw new Error(`Unknown mark filter mode "${args.mode}".`);
|
|
297
|
-
}
|
|
298
|
-
const target = args.target === undefined
|
|
299
|
-
? undefined
|
|
300
|
-
: validateUserId(args.target, "Filter mark target id");
|
|
301
|
-
const layer = resolveEligibleLayer(this, {
|
|
302
|
-
target,
|
|
303
|
-
label: "filter mark",
|
|
304
|
-
predicate: candidate =>
|
|
305
|
-
candidate.data !== undefined &&
|
|
306
|
-
findSelectionPolicy(candidate.mark?.type) !== undefined
|
|
307
|
-
});
|
|
308
|
-
if (this.markConfigs[layer.id]?.itemFilterable === false) {
|
|
309
|
-
throw new Error(
|
|
310
|
-
`filterMarks does not support composite mark "${layer.id}"; filter its source dataset first.`
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
const owner = activeMarkFilter(this, layer);
|
|
314
|
-
const selector = Object.fromEntries(
|
|
315
|
-
MARK_SELECTOR_KEYS.flatMap(key =>
|
|
316
|
-
Object.hasOwn(args, key) ? [[key, args[key]]] : []
|
|
317
|
-
)
|
|
318
|
-
);
|
|
319
|
-
const resolved = resolveMarkFilterSelection(this, layer.id, selector);
|
|
320
|
-
const previousSelector = owner?.selectors.at(-1);
|
|
321
|
-
if (previousSelector !== undefined && sameSelector(previousSelector, resolved.selector)) {
|
|
322
|
-
return this;
|
|
323
|
-
}
|
|
324
|
-
if (owner !== undefined && args.mode === undefined) {
|
|
325
|
-
throw new Error(
|
|
326
|
-
"Repeated filterMarks requires mode \"replace\" or \"compose\"."
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
const selectors = owner === undefined || args.mode === "replace"
|
|
330
|
-
? [resolved.selector]
|
|
331
|
-
: [...owner.selectors, resolved.selector];
|
|
332
|
-
let next = owner === undefined
|
|
333
|
-
? this
|
|
334
|
-
: removeActiveMarkFilter(this, layer, owner);
|
|
335
|
-
const baseLayer = requireLayer(next, layer.id);
|
|
336
|
-
const first = resolveMarkFilterSelection(next, layer.id, selectors[0]);
|
|
337
|
-
const transform = normalizeMarkFilterTransform(layer.id, selectors);
|
|
338
|
-
const boundaries = retainedHistogramBoundaries(baseLayer, first.items);
|
|
339
|
-
const originalBin = baseLayer.mark?.type === "bar" &&
|
|
340
|
-
baseLayer.encoding?.x?.bin !== undefined
|
|
341
|
-
? baseLayer.encoding.x.bin
|
|
342
|
-
: undefined;
|
|
343
|
-
const derivedId = nextMarkFilterDatasetId(next, layer.id);
|
|
344
|
-
next = next
|
|
345
|
-
.createDerivedData({
|
|
346
|
-
id: derivedId,
|
|
347
|
-
source: baseLayer.data,
|
|
348
|
-
transform: [transform]
|
|
349
|
-
});
|
|
350
|
-
if (boundaries !== undefined) {
|
|
351
|
-
for (const property of ["maxBins", "step"]) {
|
|
352
|
-
if (Object.hasOwn(baseLayer.encoding.x.bin, property)) {
|
|
353
|
-
next = next.editSemantic({
|
|
354
|
-
property: `layer[${baseLayer.id}].encoding.x.bin.${property}`,
|
|
355
|
-
remove: true
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
next = next.editSemantic({
|
|
360
|
-
property: `layer[${baseLayer.id}].encoding.x.bin.boundaries`,
|
|
361
|
-
value: boundaries
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
next = next._withMarkConfig(baseLayer.id, {
|
|
365
|
-
...next.markConfigs[baseLayer.id],
|
|
366
|
-
markFilter: {
|
|
367
|
-
dataset: derivedId,
|
|
368
|
-
source: baseLayer.data,
|
|
369
|
-
selectors,
|
|
370
|
-
empty: false,
|
|
371
|
-
...(originalBin === undefined ? {} : { originalBin })
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
return next.materializeMarkFilteredData({ id: derivedId });
|
|
375
|
-
}
|
|
376
|
-
);
|
|
1
|
+
import{findRaincloudParent as b}from"../../selectors/raincloud.js";import{filterRaincloud as T,materializeRaincloudFilter as B,removeRaincloudFilter as A}from"./raincloudFilter.js";import{closedAction as m}from"../../core/action.js";import{validateUserId as p}from"../../core/identifiers.js";import{withPreviewDatasetValues as z}from"../primitives/semanticAction.js";import{deriveFilteredRows as P,normalizeFilterTransform as L}from"../../grammar/filter.js";import{deriveMarkFilteredRows as N,markFilterSelectors as E,normalizeMarkFilterTransform as K}from"../../grammar/markFilter.js";import{resolveMarkFilterSelection as M}from"../../materialization/selection/filter.js";import{synchronizeBoxFilter as y}from"../boxPlots/components.js";import{findSelectionPolicy as q}from"../../materialization/selection/policies/index.js";import{applyLayerDataRematerialization as w,applyLayerEmptyDataView as $}from"../../materialization/dependencies.js";import{hasDataset as R,findDataset as O,resolveDatasetReference as V,requireLayer as h,resolveEligibleLayer as C}from"../../selectors/index.js";import{MATERIALIZE_OPTIONS as I,requireDerivedDataset as _}from"./shared.js";import{assertFieldsAvailable as H,deriveTransformSchema as J,transformInputFields as U}from"../../grammar/datasetSchema.js";const Y=Object.freeze(["id","source","field","oneOf","noneOf","predicate","range","nulls"]),x=Object.freeze(["grain","field","channel","property","op","value","values","min","max","inclusive","count","groupBy","ties"]),Z=Object.freeze(["target","mode",...x]),G=Object.freeze(["target"]),Q=Object.freeze(["id"]);function W(i,t){if(i.mark?.type!=="bar"||i.encoding?.x?.bin===void 0)return;const n=[...new Set(t.flatMap(e=>[e.channels.x,e.channels.x2]).filter(Number.isFinite))].sort((e,r)=>e-r);return n.length<2?void 0:n}const ke=m({op:"materializeFilteredData",description:"Materialize one filtered derived dataset."},I,function(i={}){const{id:t,source:n,transform:e}=_(this,i.id,"filter");H(n.schema,U(e),{data:n.id,operation:"materializeFilteredData"});const r=P(n.values,e),d=J(n.schema,e,r,{sourceId:n.id,ownerId:t});return this.editSemantic({property:`dataset[${t}].values`,value:r})}),ve=m({op:"filterData",description:"Create a named dataset from one field filter."},Y,function(i={}){const t=p(i.id,"Filtered dataset id"),n=p(i.source??this.context.currentData,"Source dataset id"),e=V(this,n,"Filter source dataset").id,r=L(i),d=this.createDerivedData({id:t,source:e,transform:[r]}).materializeFilteredData({id:t});return this.actionStack.length===1?d._withMaterializationConfig(["data","filter",t],{current:t}):d}),Fe=m({op:"materializeMarkFilteredData",description:"Materialize member rows retained by one final-item mark selection."},I,function(i={}){const{id:t,dataset:n,source:e,transform:r}=_(this,i.id,"markFilter"),d=b(this,r.target);if(d!==void 0)return B(this,{id:t,source:e,transform:r},d);const f=h(this,r.target);if(f?.data!==n.source)throw new Error(`Mark filter target "${r.target}" must still use source dataset "${n.source}".`);let s=this,c=e.values;const o=E(r);for(const[g,k]of o.entries()){const u=M(s,r.target,k);c=N(c,u.items,u.keys,f),g<o.length-1&&(s=z(s,{id:t,values:c,target:r.target}),s=y(s,r.target,t),s=c.length===0?$(s,r.target):w(s,r.target),c=O(s,t).values)}o.length>1&&(s=z(s,{id:t,omitValues:!0}));let a=s.editSemantic({property:`dataset[${t}].values`,value:c});return h(a,r.target).data!==t&&(a=a.editSemantic({property:`layer[${r.target}].data`,value:t})),a=a._withMarkConfig(r.target,{...a.markConfigs[r.target],markFilter:{...a.markConfigs[r.target].markFilter,empty:c.length===0}}),a=y(a,r.target,t),c.length===0?a.materializeEmptyMark({id:r.target}):w(a,r.target)}),be=m({op:"materializeEmptyMark",description:"Clear one active empty-filter mark without changing its domains."},Q,function(i={}){const t=p(i.id,"Empty mark id"),n=h(this,t),e=this.markConfigs[t]?.markFilter,r=O(this,n.data);if(e?.empty!==!0||r?.values?.length!==0)throw new Error(`Mark "${t}" does not own an active empty filter.`);return $(this,t)});function D(i,t){const n=i.markConfigs[t.id]?.markFilter,e=O(i,t.data),r=e?.transform?.[0];if(!(e?.source===void 0||r?.type!=="markFilter"||r.target!==t.id))return{dataset:e.id,source:e.source,selectors:E(r),...n?.originalBin===void 0?{}:{originalBin:n.originalBin}}}function X(i,t){const n=i.markConfigs[t]??{},{markFilter:e,...r}=n;return i._withMarkConfig(t,r)}function ee(i,t,n){let e=i;const r=t.encoding?.x?.bin??{};for(const d of["maxBins","step","boundaries"])Object.hasOwn(n,d)?e=e.editSemantic({property:`layer[${t.id}].encoding.x.bin.${d}`,value:n[d]}):Object.hasOwn(r,d)&&(e=e.editSemantic({property:`layer[${t.id}].encoding.x.bin.${d}`,remove:!0}));return e}function j(i,t,n){let e=y(i,t.id).editSemantic({property:`layer[${t.id}].data`,value:n.source});return n.originalBin!==void 0&&(e=ee(e,h(e,t.id),n.originalBin)),e=X(e,t.id).releaseDerivedData({id:n.dataset})._withContext({currentData:n.source}),w(e,t.id)}function te(i,t){const n=`${t}FilteredData`;if(!R(i,n))return n;let e=2;for(;R(i,`${n}${e}`);)e+=1;return`${n}${e}`}function re(i,t){return JSON.stringify(i)===JSON.stringify(t)}const Me=m({op:"removeMarkFilter",description:"Remove one active final-item filter and restore its source."},G,function(i={}){const t=i.target===void 0?void 0:p(i.target,"Mark filter target id"),n=b(this,t);if(n!==void 0)return A(this,n);const e=C(this,{target:t,label:"mark filter",predicate:r=>D(this,r)!==void 0});return j(this,e,D(this,e))}),ye=m({op:"filterMarks",description:"Retain selected final mark items through immutable derived data."},Z,function(i={}){if(i.mode!==void 0&&!["replace","compose"].includes(i.mode))throw new Error(`Unknown mark filter mode "${i.mode}".`);const t=i.target===void 0?void 0:p(i.target,"Filter mark target id"),n=b(this,t);if(n!==void 0){const l=Object.fromEntries(x.flatMap(F=>Object.hasOwn(i,F)?[[F,i[F]]]:[]));return T(this,i,n,l)}const e=C(this,{target:t,label:"filter mark",predicate:l=>l.data!==void 0&&q(l.mark?.type)!==void 0});if(this.markConfigs[e.id]?.itemFilterable===!1)throw new Error(`filterMarks does not support composite mark "${e.id}"; filter its source dataset first.`);const r=D(this,e),d=Object.fromEntries(x.flatMap(l=>Object.hasOwn(i,l)?[[l,i[l]]]:[])),f=M(this,e.id,d),s=r?.selectors.at(-1);if(s!==void 0&&re(s,f.selector))return this;if(r!==void 0&&i.mode===void 0)throw new Error('Repeated filterMarks requires mode "replace" or "compose".');const c=r===void 0||i.mode==="replace"?[f.selector]:[...r.selectors,f.selector];let o=r===void 0?this:j(this,e,r);const a=h(o,e.id),g=M(o,e.id,c[0]),k=K(e.id,c),u=W(a,g.items),S=a.mark?.type==="bar"&&a.encoding?.x?.bin!==void 0?a.encoding.x.bin:void 0,v=te(o,e.id);if(o=o.createDerivedData({id:v,source:a.data,transform:[k]}),u!==void 0){for(const l of["maxBins","step"])Object.hasOwn(a.encoding.x.bin,l)&&(o=o.editSemantic({property:`layer[${a.id}].encoding.x.bin.${l}`,remove:!0}));o=o.editSemantic({property:`layer[${a.id}].encoding.x.bin.boundaries`,value:u})}return o=o._withMarkConfig(a.id,{...o.markConfigs[a.id],markFilter:{dataset:v,source:a.data,selectors:c,empty:!1,...S===void 0?{}:{originalBin:S}}}),o.materializeMarkFilteredData({id:v})});export{ve as filterData,ye as filterMarks,be as materializeEmptyMark,ke as materializeFilteredData,Fe as materializeMarkFilteredData,Me as removeMarkFilter};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { deriveFlattenRows, normalizeFlattenTransform } from "../../grammar/flatten.js";
|
|
2
|
+
import { derivedCreator, derivedMaterializer } from "./shared.js";
|
|
3
|
+
|
|
4
|
+
const OPTIONS = Object.freeze(["id", "source", "fields", "as"]);
|
|
5
|
+
export const materializeFlattenData = /* @__PURE__ */ derivedMaterializer(
|
|
6
|
+
"materializeFlattenData",
|
|
7
|
+
"Materialize aligned array elements as immutable rows.",
|
|
8
|
+
"flatten",
|
|
9
|
+
deriveFlattenRows
|
|
10
|
+
);
|
|
11
|
+
export const createFlattenData = /* @__PURE__ */ derivedCreator(
|
|
12
|
+
"createFlattenData",
|
|
13
|
+
"Expand parallel array fields in source-row and array-index order.",
|
|
14
|
+
OPTIONS,
|
|
15
|
+
"Flatten dataset id",
|
|
16
|
+
"Flatten source dataset id",
|
|
17
|
+
normalizeFlattenTransform,
|
|
18
|
+
"materializeFlattenData"
|
|
19
|
+
);
|
|
@@ -41,6 +41,8 @@ import { createWindowData, materializeWindowData } from "./window.js";
|
|
|
41
41
|
import { createSummaryData, materializeSummaryData } from "./summary.js";
|
|
42
42
|
import { createBinData, materializeBinData } from "./bin.js";
|
|
43
43
|
import { createFoldData, materializeFoldData } from "./fold.js";
|
|
44
|
+
import { createFlattenData, materializeFlattenData } from "./flatten.js";
|
|
45
|
+
import {createLookupData,materializeLookupData} from "./lookup.js";
|
|
44
46
|
import { createComputedData, materializeComputedData } from "./computed.js";
|
|
45
47
|
import {
|
|
46
48
|
createNormalizedData,
|
|
@@ -89,6 +91,10 @@ export function registerDataActions(ProgramClass) {
|
|
|
89
91
|
materializeSummaryData,
|
|
90
92
|
createBinData,
|
|
91
93
|
materializeBinData,
|
|
94
|
+
createFlattenData,
|
|
95
|
+
materializeFlattenData,
|
|
96
|
+
createLookupData,
|
|
97
|
+
materializeLookupData,
|
|
92
98
|
createFoldData,
|
|
93
99
|
materializeFoldData,
|
|
94
100
|
createComputedData,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { closedAction } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { deriveLookupRows, normalizeLookupTransform } from "../../grammar/lookup.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveTransformSchema, datasetSchemaContext, validateRowsAgainstSchema
|
|
6
|
+
} from "../../grammar/datasetSchema.js";
|
|
7
|
+
import { orderedDatasetAncestors } from "../../grammar/datasetDependencies.js";
|
|
8
|
+
import { resolveDatasetReference, requireDataset } from "../../selectors/datasets.js";
|
|
9
|
+
import {
|
|
10
|
+
requireDerivedDataset, MATERIALIZE_OPTIONS, resolveDatasetTransformInputs
|
|
11
|
+
} from "./shared.js";
|
|
12
|
+
|
|
13
|
+
function lookupResult(program, source, transform, id) {
|
|
14
|
+
if (!Array.isArray(source.values)) throw new Error(`Source dataset "${source.id}" has no values.`);
|
|
15
|
+
const right = requireDataset(program, transform.lookup.data);
|
|
16
|
+
if (!Array.isArray(right.values)) throw new Error(`Lookup dataset "${right.id}" has no values.`);
|
|
17
|
+
const context = datasetSchemaContext(source, transform, id, reference => requireDataset(program, reference));
|
|
18
|
+
const schema = deriveTransformSchema(source.schema, transform, undefined, context);
|
|
19
|
+
const values = deriveLookupRows(source.values, right.values, transform);
|
|
20
|
+
validateRowsAgainstSchema(values, schema, `Lookup dataset "${id}"`);
|
|
21
|
+
return values;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const materializeLookupData = /* @__PURE__ */ closedAction(
|
|
25
|
+
{ op: "materializeLookupData", description: "Materialize one typed named-dataset lookup." },
|
|
26
|
+
MATERIALIZE_OPTIONS,
|
|
27
|
+
function(args = {}) {
|
|
28
|
+
const { id, source, transform } = requireDerivedDataset(this, args.id, "lookup");
|
|
29
|
+
orderedDatasetAncestors(this.semanticSpec.datasets, id);
|
|
30
|
+
const values = lookupResult(this, source, transform, id);
|
|
31
|
+
return this.editSemantic({ property: `dataset[${id}].values`, value: values });
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const createLookupData = /* @__PURE__ */ closedAction(
|
|
36
|
+
{ op: "createLookupData", description: "Copy fields from one named dataset without changing primary rows." },
|
|
37
|
+
["id", "source", "field", "lookup"],
|
|
38
|
+
function(args = {}) {
|
|
39
|
+
const id = validateUserId(args.id, "Lookup dataset id");
|
|
40
|
+
const source = resolveDatasetReference(this,
|
|
41
|
+
validateUserId(args.source ?? this.context.currentData, "Lookup source dataset id"),
|
|
42
|
+
"Lookup source dataset");
|
|
43
|
+
const transform = resolveDatasetTransformInputs(this, normalizeLookupTransform(args));
|
|
44
|
+
lookupResult(this, source, transform, id);
|
|
45
|
+
const standalone = this.actionStack.length === 1;
|
|
46
|
+
const next = this.createDerivedData({ id, source: source.id, transform: [transform] })
|
|
47
|
+
.materializeLookupData({ id });
|
|
48
|
+
return standalone ? next._withMaterializationConfig(["data", "lookup", id], { current: id }) : next;
|
|
49
|
+
}
|
|
50
|
+
);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { findDataset, hasDataset } from "../../selectors/datasets.js";
|
|
2
|
+
import { findRaincloudParent } from "../../selectors/raincloud.js";
|
|
3
|
+
import { normalizeMarkSelector, selectMarkItemKeys } from "../../grammar/markSelection.js";
|
|
4
|
+
import { deriveMarkFilteredRows, normalizeMarkFilterTransform } from "../../grammar/markFilter.js";
|
|
5
|
+
|
|
6
|
+
export function deriveRaincloudFilteredRows(values, selectors) {
|
|
7
|
+
let rows = values;
|
|
8
|
+
for (const selector of selectors) {
|
|
9
|
+
if (selector.field === undefined || selector.grain !== "item") {
|
|
10
|
+
throw new Error("Raincloud parent filters require an item-grain observation field selector; use a child target for rendered channels or properties.");
|
|
11
|
+
}
|
|
12
|
+
const items = rows.map((row, index) => ({ key: String(index), fields: row,
|
|
13
|
+
channels: {}, properties: {}, members: [row] }));
|
|
14
|
+
rows = deriveMarkFilteredRows(rows, items, selectMarkItemKeys(items, selector));
|
|
15
|
+
}
|
|
16
|
+
return rows;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function materializeRaincloudFilter(program, { id, source, transform }, owner) {
|
|
20
|
+
const values = deriveRaincloudFilteredRows(source.values, transform.selectors);
|
|
21
|
+
let next = program.editSemantic({ property: `dataset[${id}].values`, value: values });
|
|
22
|
+
next = next.editRaincloudPlot({ target: owner.id, data: id });
|
|
23
|
+
const revised = findRaincloudParent(next, owner.id);
|
|
24
|
+
return next._withMarkConfig(revised.ownerId, {
|
|
25
|
+
...next.markConfigs[revised.ownerId], raincloudPlot: { ...revised,
|
|
26
|
+
sourceFilter: { dataset: id, source: source.id, selectors: transform.selectors } }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function filterRaincloud(program, args, owner, selectorOptions) {
|
|
31
|
+
const selector = normalizeMarkSelector(selectorOptions);
|
|
32
|
+
const previous = owner.sourceFilter;
|
|
33
|
+
if (previous !== undefined && JSON.stringify(previous.selectors.at(-1)) === JSON.stringify(selector)) return program;
|
|
34
|
+
if (previous !== undefined && args.mode === undefined) {
|
|
35
|
+
throw new Error('Repeated filterMarks requires mode "replace" or "compose".');
|
|
36
|
+
}
|
|
37
|
+
const selectors = previous === undefined || args.mode === "replace"
|
|
38
|
+
? [selector] : [...previous.selectors, selector];
|
|
39
|
+
const source = previous?.source ?? owner.source;
|
|
40
|
+
// Validate against observations before changing any owner or resource.
|
|
41
|
+
deriveRaincloudFilteredRows(findDataset(program, source).values, selectors);
|
|
42
|
+
const base = `${owner.id}FilteredData`;
|
|
43
|
+
let id = base;
|
|
44
|
+
for (let revision = 2; hasDataset(program, id); revision += 1) id = `${base}${revision}`;
|
|
45
|
+
let next = program.createDerivedData({ id, source,
|
|
46
|
+
transform: [normalizeMarkFilterTransform(owner.id, selectors)] })
|
|
47
|
+
.materializeMarkFilteredData({ id });
|
|
48
|
+
if (previous !== undefined) next = next.releaseDerivedData({ id: previous.dataset });
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function removeRaincloudFilter(program, owner) {
|
|
53
|
+
if (owner.sourceFilter === undefined) throw new Error(`Raincloud plot "${owner.id}" has no active mark filter.`);
|
|
54
|
+
return program.editRaincloudPlot({ target: owner.id, data: owner.sourceFilter.source })
|
|
55
|
+
.releaseDerivedData({ id: owner.sourceFilter.dataset });
|
|
56
|
+
}
|