ggaction 0.0.13 → 0.0.15
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 +78 -0
- package/README.md +20 -11
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +8 -3
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +11 -3
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/categoryOrder/index.js +15 -4
- package/src/actions/charts/area.js +5 -1
- package/src/actions/charts/bar.js +2 -1
- package/src/actions/charts/density.js +6 -2
- package/src/actions/charts/ecdf.js +4 -1
- package/src/actions/charts/endpoints.js +25 -3
- package/src/actions/charts/heatmap.js +2 -1
- package/src/actions/charts/histogram.js +4 -3
- package/src/actions/charts/horizon.js +3 -1
- package/src/actions/charts/interval-regression.js +5 -2
- package/src/actions/charts/line.js +3 -1
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +10 -2
- package/src/actions/charts/polar.js +15 -4
- package/src/actions/charts/radar.js +8 -2
- package/src/actions/charts/radial.js +10 -2
- package/src/actions/charts/rug-strip.js +6 -2
- package/src/actions/charts/scatter.js +3 -1
- package/src/actions/composition/actions.js +13 -4
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +2 -2
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +47 -17
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +14 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +14 -0
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +16 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/summary.js +1 -1
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +2 -1
- package/src/actions/encodings/density.js +5 -2
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +20 -13
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +10 -2
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/remove.js +9 -4
- package/src/actions/encodings/ruleAppearance.js +1 -23
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/errorBands/create.js +23 -3
- package/src/actions/errorBands/edit.js +21 -6
- package/src/actions/errorBars/create.js +35 -7
- package/src/actions/errorBars/edit.js +19 -4
- package/src/actions/facets/actions.js +237 -57
- package/src/actions/facets/derive.js +91 -26
- package/src/actions/gradientPlots/components.js +10 -2
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +3 -1
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/labels.js +58 -12
- package/src/actions/guides/axes/lines.js +6 -2
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/tickGroups.js +2 -1
- package/src/actions/guides/axes/ticks.js +6 -2
- package/src/actions/guides/axes/titles.js +11 -3
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +19 -3
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +134 -48
- package/src/actions/guides/legends/categorical/components.js +17 -12
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +97 -15
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +85 -30
- package/src/actions/guides/legends/continuous/common.js +30 -12
- package/src/actions/guides/legends/continuous/gradient.js +10 -7
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +8 -3
- package/src/actions/guides/legends/continuous/opacity.js +8 -5
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +158 -59
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +54 -19
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +13 -3
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +93 -40
- package/src/actions/guides/legends/strokeWidth.js +34 -28
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +4 -4
- package/src/actions/guides/polar/axes/labels.js +24 -5
- package/src/actions/guides/polar/axes/lines.js +10 -4
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +1 -1
- package/src/actions/guides/polar/axes/titles.js +1 -1
- package/src/actions/guides/polar/grids.js +1 -1
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +14 -3
- package/src/actions/marks/arc/actions.js +77 -19
- package/src/actions/marks/area/actions.js +34 -9
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +22 -2
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +37 -14
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +18 -6
- package/src/actions/marks/point/materialize.js +49 -15
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +601 -30
- package/src/actions/marks/remove.js +71 -1
- package/src/actions/marks/rule/actions.js +47 -10
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +74 -9
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +154 -6
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +2 -1
- package/src/actions/primitives/semanticAction.js +83 -10
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +10 -2
- package/src/actions/regression/edit.js +21 -7
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +54 -22
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +66 -11
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +4 -3
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +68 -13
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +315 -67
- package/src/actions/theme/state.js +160 -0
- package/src/actions/violinPlots/create.js +5 -2
- package/src/actions/violinPlots/edit.js +1 -0
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +6 -1
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +340 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +343 -0
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +19 -4
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +32 -1
- package/src/selectors/layers.js +4 -0
- package/src/theme/defaults.js +101 -0
- package/types/basic.d.ts +6 -0
- package/types/index.d.ts +85 -0
- package/types/program.d.ts +910 -117
|
@@ -3,8 +3,9 @@ import { validateUserId } from "../../core/identifiers.js";
|
|
|
3
3
|
import { validateKeys } from "../../core/validation.js";
|
|
4
4
|
import {
|
|
5
5
|
deriveRegression,
|
|
6
|
-
|
|
6
|
+
normalizeRegressionTransform
|
|
7
7
|
} from "../../grammar/regression/index.js";
|
|
8
|
+
import { resolveDatasetReference } from "../../selectors/datasets.js";
|
|
8
9
|
import { derivedMaterializer } from "./shared.js";
|
|
9
10
|
|
|
10
11
|
const OPTIONS = Object.freeze([
|
|
@@ -34,31 +35,25 @@ export const createRegressionData = action(
|
|
|
34
35
|
{ op: "createRegressionData", description: "Create grouped regression values and optional interval bounds." },
|
|
35
36
|
function (args = {}) {
|
|
36
37
|
validateKeys(args, OPTIONS, "createRegressionData");
|
|
37
|
-
const parameters = normalizeRegressionParameters(args);
|
|
38
38
|
const id = validateUserId(args.id, "Regression dataset id");
|
|
39
|
-
const
|
|
39
|
+
const requestedSource = validateUserId(
|
|
40
40
|
args.source ?? this.context.currentData,
|
|
41
41
|
"Source dataset id"
|
|
42
42
|
);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
? { degree: parameters.degree }
|
|
51
|
-
: {}),
|
|
52
|
-
...(parameters.method === "loess"
|
|
53
|
-
? { span: parameters.span }
|
|
54
|
-
: {
|
|
55
|
-
confidenceMethod: parameters.confidenceMethod,
|
|
56
|
-
level: parameters.level,
|
|
57
|
-
interval: parameters.interval
|
|
58
|
-
})
|
|
59
|
-
};
|
|
60
|
-
return this
|
|
43
|
+
const source = resolveDatasetReference(
|
|
44
|
+
this,
|
|
45
|
+
requestedSource,
|
|
46
|
+
"Regression source dataset"
|
|
47
|
+
).id;
|
|
48
|
+
const transform = normalizeRegressionTransform(args);
|
|
49
|
+
const next = this
|
|
61
50
|
.createDerivedData({ id, source, transform: [transform] })
|
|
62
51
|
.materializeRegressionData({ id });
|
|
52
|
+
return this.actionStack.length === 1
|
|
53
|
+
? next._withMaterializationConfig(
|
|
54
|
+
["data", "regression", id],
|
|
55
|
+
{ current: id }
|
|
56
|
+
)
|
|
57
|
+
: next;
|
|
63
58
|
}
|
|
64
59
|
);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
2
|
+
import { datasetTransformOutputRoles } from "../../grammar/transforms.js";
|
|
3
|
+
import { requireLayer } from "../../selectors/layers.js";
|
|
4
|
+
|
|
5
|
+
const DATA_REFERENCE_KEYS = new Set([
|
|
6
|
+
"data", "dataset", "dataId", "filteredData", "originalData",
|
|
7
|
+
"outlierDataId", "profileId", "source", "summaryId"
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export function outputFieldChanges(previous, next) {
|
|
11
|
+
const before = datasetTransformOutputRoles(previous);
|
|
12
|
+
const after = datasetTransformOutputRoles(next);
|
|
13
|
+
const family = previous.type === "bin2d" ? "2D bin " : "";
|
|
14
|
+
const counts = new Map();
|
|
15
|
+
const retainedFields = new Set(Object.values(after));
|
|
16
|
+
for (const field of Object.values(before)) {
|
|
17
|
+
counts.set(field, (counts.get(field) ?? 0) + 1);
|
|
18
|
+
}
|
|
19
|
+
return new Map(Object.entries(before).flatMap(([role, field]) => {
|
|
20
|
+
if (field === after[role] || retainedFields.has(field)) return [];
|
|
21
|
+
if (counts.get(field) !== 1) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Cannot rebind ambiguous derived output field "${field}".`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return [[field, { role, next: after[role], family }]];
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function changedConsumerField(field, changes) {
|
|
31
|
+
const change = changes.get(field);
|
|
32
|
+
if (change === undefined) return field;
|
|
33
|
+
if (change.next === undefined) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Cannot remove referenced ${change.family}${change.role} output field "${field}".`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return change.next;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function rebindLayerOutputFields(program, id, changes) {
|
|
42
|
+
const layer = requireLayer(program, id);
|
|
43
|
+
let next = program;
|
|
44
|
+
const changed = field => changedConsumerField(field, changes);
|
|
45
|
+
const editField = (property, field) => {
|
|
46
|
+
if (field === undefined) return;
|
|
47
|
+
const value = changed(field);
|
|
48
|
+
if (value !== field) next = next.editSemantic({ property, value });
|
|
49
|
+
};
|
|
50
|
+
for (const [channel, encoding] of Object.entries(layer.encoding ?? {})) {
|
|
51
|
+
editField(`layer[${id}].encoding.${channel}.field`, encoding?.field);
|
|
52
|
+
const summary = encoding?.categoryOrder?.by;
|
|
53
|
+
if (summary?.field !== undefined) {
|
|
54
|
+
const field = changed(summary.field);
|
|
55
|
+
if (field !== summary.field) {
|
|
56
|
+
next = next.editSemantic({
|
|
57
|
+
property: `layer[${id}].encoding.${channel}.categoryOrder`,
|
|
58
|
+
value: { ...encoding.categoryOrder, by: { ...summary, field } }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
editField(
|
|
64
|
+
`layer[${id}].encoding.theta.weight`,
|
|
65
|
+
layer.encoding?.theta?.weight
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const parallel = layer.encoding?.parallel;
|
|
69
|
+
if (parallel?.dimensions !== undefined) {
|
|
70
|
+
const dimensions = parallel.dimensions.map(dimension => ({
|
|
71
|
+
...dimension,
|
|
72
|
+
field: changed(dimension.field)
|
|
73
|
+
}));
|
|
74
|
+
if (dimensions.some((dimension, index) =>
|
|
75
|
+
dimension.field !== parallel.dimensions[index].field
|
|
76
|
+
)) {
|
|
77
|
+
next = next.editSemantic({
|
|
78
|
+
property: `layer[${id}].encoding.parallel.dimensions`,
|
|
79
|
+
value: dimensions
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
editField(`layer[${id}].encoding.parallel.key`, parallel?.key);
|
|
84
|
+
|
|
85
|
+
for (const [selectionId, config] of Object.entries(
|
|
86
|
+
program.materializationConfigs.selections ?? {}
|
|
87
|
+
)) {
|
|
88
|
+
if (config.target !== id) continue;
|
|
89
|
+
const selector = { ...config.selector };
|
|
90
|
+
if (selector.field !== undefined) selector.field = changed(selector.field);
|
|
91
|
+
if (selector.groupBy !== undefined) {
|
|
92
|
+
selector.groupBy = selector.groupBy.map(changed);
|
|
93
|
+
}
|
|
94
|
+
if (JSON.stringify(selector) !== JSON.stringify(config.selector)) {
|
|
95
|
+
next = next._withSelectionConfig(selectionId, { ...config, selector });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const jitter = program.materializationConfigs.jitters?.[id];
|
|
100
|
+
const key = jitter?.key === undefined ? undefined : changed(jitter.key);
|
|
101
|
+
if (key !== undefined && key !== jitter.key) {
|
|
102
|
+
next = next._withMaterializationConfig(
|
|
103
|
+
["jitters", id],
|
|
104
|
+
{ ...jitter, key }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return next;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function replaceReferences(value, replacements, key) {
|
|
111
|
+
if (Array.isArray(value)) {
|
|
112
|
+
let changed = false;
|
|
113
|
+
const items = value.map(item => {
|
|
114
|
+
const next = replaceReferences(item, replacements);
|
|
115
|
+
changed ||= next !== item;
|
|
116
|
+
return next;
|
|
117
|
+
});
|
|
118
|
+
return changed ? items : value;
|
|
119
|
+
}
|
|
120
|
+
if (!isPlainObject(value)) {
|
|
121
|
+
return DATA_REFERENCE_KEYS.has(key) && replacements.has(value)
|
|
122
|
+
? replacements.get(value)
|
|
123
|
+
: value;
|
|
124
|
+
}
|
|
125
|
+
let changed = false;
|
|
126
|
+
const result = {};
|
|
127
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
128
|
+
const next = replaceReferences(child, replacements, childKey);
|
|
129
|
+
changed ||= next !== child;
|
|
130
|
+
result[childKey] = next;
|
|
131
|
+
}
|
|
132
|
+
return changed ? result : value;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function rebindMaterializationDataReferences(program, replacements) {
|
|
136
|
+
if (replacements.size === 0) return program;
|
|
137
|
+
let next = program;
|
|
138
|
+
for (const [key, config] of Object.entries(program.materializationConfigs)) {
|
|
139
|
+
const replaced = replaceReferences(config, replacements, key);
|
|
140
|
+
if (replaced !== config) {
|
|
141
|
+
next = next._withMaterializationConfig([key], replaced);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return next;
|
|
145
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
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 {
|
|
4
|
+
import {
|
|
5
|
+
findDataset,
|
|
6
|
+
resolveDatasetReference
|
|
7
|
+
} from "../../selectors/datasets.js";
|
|
5
8
|
|
|
6
9
|
export const MATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
7
10
|
|
|
@@ -61,12 +64,25 @@ export function derivedCreator(
|
|
|
61
64
|
return action({ op, description }, function (args = {}) {
|
|
62
65
|
validateKeys(args, options, op);
|
|
63
66
|
const id = validateUserId(args.id, idLabel);
|
|
64
|
-
const
|
|
67
|
+
const requestedSource = validateUserId(
|
|
65
68
|
requireSource ? args.source : args.source ?? this.context.currentData,
|
|
66
69
|
sourceLabel
|
|
67
70
|
);
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
const source = resolveDatasetReference(
|
|
72
|
+
this,
|
|
73
|
+
requestedSource,
|
|
74
|
+
sourceLabel
|
|
75
|
+
).id;
|
|
76
|
+
const standalone = this.actionStack.length === 1;
|
|
77
|
+
const requestedTransform = transform(args, id);
|
|
78
|
+
const next = this
|
|
79
|
+
.createDerivedData({ id, source, transform: [requestedTransform] })
|
|
70
80
|
[materialize]({ id });
|
|
81
|
+
return standalone
|
|
82
|
+
? next._withMaterializationConfig(
|
|
83
|
+
["data", requestedTransform.type, id],
|
|
84
|
+
{ current: id }
|
|
85
|
+
)
|
|
86
|
+
: next;
|
|
71
87
|
});
|
|
72
88
|
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import { derivedCreator, derivedMaterializer } from "./shared.js";
|
|
6
6
|
|
|
7
7
|
const OPTIONS = Object.freeze([
|
|
8
|
-
"id", "source", "groupBy", "aggregates", "members"
|
|
8
|
+
"id", "source", "groupBy", "aggregates", "members", "weight"
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
11
|
export const materializeSummaryData = derivedMaterializer(
|
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
} from "../../grammar/timeUnit.js";
|
|
5
5
|
import { derivedCreator, derivedMaterializer } from "./shared.js";
|
|
6
6
|
|
|
7
|
-
const OPTIONS = Object.freeze([
|
|
7
|
+
const OPTIONS = Object.freeze([
|
|
8
|
+
"id", "source", "field", "unit", "as", "temporalUnit", "timeZone",
|
|
9
|
+
"weekStartsOn", "weekRule"
|
|
10
|
+
]);
|
|
8
11
|
|
|
9
12
|
export const materializeTimeUnitData = derivedMaterializer(
|
|
10
13
|
"materializeTimeUnitData",
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import { derivedCreator, derivedMaterializer } from "./shared.js";
|
|
6
6
|
|
|
7
7
|
const OPTIONS = Object.freeze([
|
|
8
|
-
"id", "source", "partitionBy", "sortBy", "operations"
|
|
8
|
+
"id", "source", "partitionBy", "sortBy", "operations", "temporalUnit"
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
11
|
export const materializeWindowData = derivedMaterializer(
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import {
|
|
2
|
+
action,
|
|
3
|
+
invokeWrappedActionImplementation
|
|
4
|
+
} from "../../core/action.js";
|
|
5
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
6
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
7
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
8
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
9
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
10
|
+
import { findScaleConsumers } from "../scales/consumers/index.js";
|
|
11
|
+
import { resolveScalePreview, validatePendingMeasuredScale } from
|
|
12
|
+
"../scales/preview.js";
|
|
13
|
+
import { isPendingMeasuredRadiusConsumer } from
|
|
14
|
+
"../../materialization/scales/policies/arc.js";
|
|
15
|
+
import {
|
|
16
|
+
getMarkRematerializationStep,
|
|
17
|
+
getScaleConsumerMarkSteps,
|
|
18
|
+
getSourceDependentMarkSteps
|
|
19
|
+
} from "../../materialization/marks/index.js";
|
|
20
|
+
import {
|
|
21
|
+
applyMaterializationPlan,
|
|
22
|
+
planScaleGuideRematerialization
|
|
23
|
+
} from "../../materialization/dependencies.js";
|
|
24
|
+
import { buildMaterializationPlan } from "../../materialization/planner.js";
|
|
25
|
+
import { hasMaterializedLegend } from "../../materialization/legends.js";
|
|
26
|
+
import { rebindPositionGuides } from "./shared.js";
|
|
27
|
+
import { withoutPreviewLayerEncodings } from
|
|
28
|
+
"../primitives/semanticAction.js";
|
|
29
|
+
|
|
30
|
+
export const ENCODING_CHANNEL_ORDER = Object.freeze([
|
|
31
|
+
"x", "y", "x2", "y2", "theta", "r", "xOffset", "yOffset",
|
|
32
|
+
"group", "pathOrder", "color", "stroke", "size", "shape",
|
|
33
|
+
"opacity", "strokeWidth", "strokeDash", "angle", "text"
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const CHANNEL_METHODS = Object.freeze({
|
|
37
|
+
x: "encodeX",
|
|
38
|
+
y: "encodeY",
|
|
39
|
+
x2: "encodeX2",
|
|
40
|
+
y2: "encodeY2",
|
|
41
|
+
theta: "encodeTheta",
|
|
42
|
+
r: "encodeR",
|
|
43
|
+
xOffset: "encodeXOffset",
|
|
44
|
+
yOffset: "encodeYOffset",
|
|
45
|
+
group: "encodeGroup",
|
|
46
|
+
pathOrder: "encodePathOrder",
|
|
47
|
+
color: "encodeColor",
|
|
48
|
+
stroke: "encodeStroke",
|
|
49
|
+
size: "encodeSize",
|
|
50
|
+
shape: "encodeShape",
|
|
51
|
+
opacity: "encodeOpacity",
|
|
52
|
+
strokeWidth: "encodeStrokeWidth",
|
|
53
|
+
strokeDash: "encodeStrokeDash",
|
|
54
|
+
angle: "encodeAngle",
|
|
55
|
+
text: "encodeText"
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const FORBIDDEN_PAYLOAD_KEYS = Object.freeze(["target", "coordinate", "id"]);
|
|
59
|
+
|
|
60
|
+
const DEFERRED_MARK_OPERATIONS = Object.freeze([
|
|
61
|
+
"materializeEmptyMark",
|
|
62
|
+
"rematerializePointMark",
|
|
63
|
+
"rematerializeTickMark",
|
|
64
|
+
"rematerializeLineMark",
|
|
65
|
+
"rematerializeAreaMark",
|
|
66
|
+
"rematerializeArcMark",
|
|
67
|
+
"rematerializeBarMark",
|
|
68
|
+
"rematerializeRuleMark",
|
|
69
|
+
"rematerializeTextMark",
|
|
70
|
+
"rematerializeRectMark",
|
|
71
|
+
"rematerializeMarkHighlights",
|
|
72
|
+
"materializeLabelLayout"
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
const deferredClassByProgram = new WeakMap();
|
|
76
|
+
|
|
77
|
+
function sameSetting(left, right) {
|
|
78
|
+
if (Object.is(left, right)) return true;
|
|
79
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
80
|
+
return left.length === right.length && left.every(
|
|
81
|
+
(value, index) => sameSetting(value, right[index])
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
85
|
+
const leftKeys = Object.keys(left);
|
|
86
|
+
const rightKeys = Object.keys(right);
|
|
87
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
88
|
+
key => Object.hasOwn(right, key) && sameSetting(left[key], right[key])
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function assertCompatibleScaleRequests(requests) {
|
|
95
|
+
const patches = new Map();
|
|
96
|
+
for (const { channel, payload } of requests) {
|
|
97
|
+
if (!isPlainObject(payload.scale) || payload.scale.id === undefined) continue;
|
|
98
|
+
const id = payload.scale.id;
|
|
99
|
+
const previous = patches.get(id) ?? new Map();
|
|
100
|
+
for (const [property, value] of Object.entries(payload.scale)) {
|
|
101
|
+
if (property === "id" || value === undefined) continue;
|
|
102
|
+
const existing = previous.get(property);
|
|
103
|
+
if (existing !== undefined && !sameSetting(existing.value, value)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`encodeChannels scale "${id}" has conflicting explicit ${property} requests for ${existing.channel} and ${channel}.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (existing === undefined) previous.set(property, { channel, value });
|
|
109
|
+
}
|
|
110
|
+
patches.set(id, previous);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function normalizeEncodeChannelsArgs(args) {
|
|
115
|
+
validateOptionObject(args, ["target", "channels"], "encodeChannels");
|
|
116
|
+
const target = validateUserId(args.target, "Mark id");
|
|
117
|
+
if (!isPlainObject(args.channels)) {
|
|
118
|
+
throw new TypeError("encodeChannels channels must be a plain object.");
|
|
119
|
+
}
|
|
120
|
+
const keys = Object.keys(args.channels);
|
|
121
|
+
if (keys.length === 0) {
|
|
122
|
+
throw new TypeError("encodeChannels channels must contain at least one channel.");
|
|
123
|
+
}
|
|
124
|
+
for (const key of keys) {
|
|
125
|
+
if (!Object.hasOwn(CHANNEL_METHODS, key)) {
|
|
126
|
+
throw new Error(`encodeChannels does not support channel "${key}".`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const requests = ENCODING_CHANNEL_ORDER
|
|
130
|
+
.filter(channel => Object.hasOwn(args.channels, channel))
|
|
131
|
+
.map(channel => {
|
|
132
|
+
const payload = args.channels[channel];
|
|
133
|
+
if (!isPlainObject(payload)) {
|
|
134
|
+
throw new TypeError(
|
|
135
|
+
`encodeChannels ${channel} payload must be a plain object.`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
for (const key of FORBIDDEN_PAYLOAD_KEYS) {
|
|
139
|
+
if (Object.hasOwn(payload, key)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`encodeChannels ${channel} payload cannot specify ${key}.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return Object.freeze({ channel, payload });
|
|
146
|
+
});
|
|
147
|
+
assertCompatibleScaleRequests(requests);
|
|
148
|
+
return Object.freeze({ target, requests: Object.freeze(requests) });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function resolveScaleOnly(program, id) {
|
|
152
|
+
const semanticScale = findSemanticScale(program, id);
|
|
153
|
+
if (semanticScale === undefined) {
|
|
154
|
+
throw new Error(`Unknown scale "${id}".`);
|
|
155
|
+
}
|
|
156
|
+
const consumers = findScaleConsumers(program, id);
|
|
157
|
+
if (
|
|
158
|
+
semanticScale.radialMapping !== undefined &&
|
|
159
|
+
consumers.length > 0 &&
|
|
160
|
+
consumers.every(isPendingMeasuredRadiusConsumer)
|
|
161
|
+
) {
|
|
162
|
+
validatePendingMeasuredScale(program, semanticScale, consumers);
|
|
163
|
+
return program._withoutResolvedScale(id);
|
|
164
|
+
}
|
|
165
|
+
return program._withResolvedScale(
|
|
166
|
+
id,
|
|
167
|
+
resolveScalePreview(program, id).resolvedScale
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function deferredProgramClass(ProgramClass) {
|
|
172
|
+
const existing = deferredClassByProgram.get(ProgramClass);
|
|
173
|
+
if (existing !== undefined) return existing;
|
|
174
|
+
|
|
175
|
+
class DeferredEncodingProgram extends ProgramClass {
|
|
176
|
+
rematerializeScale({ id } = {}) {
|
|
177
|
+
return resolveScaleOnly(this, id);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
rematerializeLegend() {
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const operation of DEFERRED_MARK_OPERATIONS) {
|
|
185
|
+
Object.defineProperty(DeferredEncodingProgram.prototype, operation, {
|
|
186
|
+
configurable: true,
|
|
187
|
+
value() {
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
deferredClassByProgram.set(ProgramClass, DeferredEncodingProgram);
|
|
193
|
+
return DeferredEncodingProgram;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function programState(program) {
|
|
197
|
+
return Object.freeze({
|
|
198
|
+
semanticSpec: program.semanticSpec,
|
|
199
|
+
graphicSpec: program.graphicSpec,
|
|
200
|
+
resolvedScales: program.resolvedScales,
|
|
201
|
+
materializationConfigs: program.materializationConfigs,
|
|
202
|
+
children: program.children,
|
|
203
|
+
compositionSpec: program.compositionSpec,
|
|
204
|
+
context: program.context,
|
|
205
|
+
trace: program.trace,
|
|
206
|
+
actionStack: program.actionStack,
|
|
207
|
+
actionSequence: program._actionSequence
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function instantiateProgram(state, ProgramClass) {
|
|
212
|
+
return new ProgramClass({
|
|
213
|
+
semanticSpec: state.semanticSpec,
|
|
214
|
+
graphicSpec: state.graphicSpec,
|
|
215
|
+
resolvedScales: state.resolvedScales,
|
|
216
|
+
materializationConfigs: state.materializationConfigs,
|
|
217
|
+
children: state.children,
|
|
218
|
+
compositionSpec: state.compositionSpec,
|
|
219
|
+
context: state.context,
|
|
220
|
+
trace: state.trace,
|
|
221
|
+
actionStack: state.actionStack,
|
|
222
|
+
actionSequence: state.actionSequence
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function createDeferredProgram(program) {
|
|
227
|
+
return instantiateProgram(
|
|
228
|
+
programState(program),
|
|
229
|
+
deferredProgramClass(program.constructor)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function requestedFieldType(request) {
|
|
234
|
+
if (request === undefined) return undefined;
|
|
235
|
+
return request.payload.fieldType ?? "quantitative";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isCategoricalBarType(fieldType) {
|
|
239
|
+
return ["nominal", "ordinal", "temporal"].includes(fieldType);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function stageFinalPositionRoles(program, target, requests) {
|
|
243
|
+
const layer = findLayer(program, target);
|
|
244
|
+
if (layer?.mark?.type !== "bar") return program;
|
|
245
|
+
const byChannel = new Map(requests.map(request => [request.channel, request]));
|
|
246
|
+
const nextX = requestedFieldType(byChannel.get("x"));
|
|
247
|
+
const nextY = requestedFieldType(byChannel.get("y"));
|
|
248
|
+
if (nextX === undefined || nextY === undefined) return program;
|
|
249
|
+
const currentX = layer.encoding?.x?.fieldType;
|
|
250
|
+
const currentY = layer.encoding?.y?.fieldType;
|
|
251
|
+
const changesOrientation =
|
|
252
|
+
isCategoricalBarType(currentX) !== isCategoricalBarType(nextX) &&
|
|
253
|
+
isCategoricalBarType(currentY) !== isCategoricalBarType(nextY);
|
|
254
|
+
if (!changesOrientation) return program;
|
|
255
|
+
|
|
256
|
+
return withoutPreviewLayerEncodings(program, {
|
|
257
|
+
id: target,
|
|
258
|
+
channels: ["x", "y"]
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const INTERNAL_SCALE_CHANNEL = Object.freeze({ r: "radius" });
|
|
263
|
+
|
|
264
|
+
function requestedScaleIds(originalLayer, finalLayer, requests) {
|
|
265
|
+
const requestedChannels = new Set(requests.map(({ channel }) =>
|
|
266
|
+
INTERNAL_SCALE_CHANNEL[channel] ?? channel
|
|
267
|
+
));
|
|
268
|
+
const ids = [];
|
|
269
|
+
for (const channel of [
|
|
270
|
+
"x", "y", "x2", "y2", "theta", "radius", "xOffset", "yOffset",
|
|
271
|
+
"color", "stroke", "size", "shape", "opacity", "strokeWidth",
|
|
272
|
+
"strokeDash", "angle", "text", "group", "pathOrder"
|
|
273
|
+
]) {
|
|
274
|
+
if (!requestedChannels.has(channel)) continue;
|
|
275
|
+
for (const layer of [originalLayer, finalLayer]) {
|
|
276
|
+
const id = layer?.encoding?.[channel]?.scale;
|
|
277
|
+
if (id !== undefined && !ids.includes(id)) ids.push(id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return ids;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function changedScaleIds(before, after) {
|
|
284
|
+
const beforeById = new Map(before.semanticSpec.scales.map(scale => [scale.id, scale]));
|
|
285
|
+
return after.semanticSpec.scales
|
|
286
|
+
.filter(scale => !sameSetting(scale, beforeById.get(scale.id)))
|
|
287
|
+
.map(scale => scale.id)
|
|
288
|
+
.sort();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function finalScaleIds(before, after, originalLayer, finalLayer, requests) {
|
|
292
|
+
const ids = [
|
|
293
|
+
...requestedScaleIds(originalLayer, finalLayer, requests),
|
|
294
|
+
...changedScaleIds(before, after)
|
|
295
|
+
];
|
|
296
|
+
return [...new Set(ids)].filter(id =>
|
|
297
|
+
findSemanticScale(after, id) !== undefined &&
|
|
298
|
+
findScaleConsumers(after, id).length > 0
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const rematerializeEncodingScales = action(
|
|
303
|
+
{
|
|
304
|
+
op: "rematerializeEncodingScales",
|
|
305
|
+
description: "Resolve every scale affected by one atomic encoding assignment."
|
|
306
|
+
},
|
|
307
|
+
function ({ ids } = {}) {
|
|
308
|
+
let next = this;
|
|
309
|
+
for (const id of ids) next = resolveScaleOnly(next, id);
|
|
310
|
+
return next;
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
function finalMaterializationPlan(program, target, scaleIds) {
|
|
315
|
+
const layer = findLayer(program, target);
|
|
316
|
+
const targetStep = layer === undefined
|
|
317
|
+
? undefined
|
|
318
|
+
: getMarkRematerializationStep(program, layer);
|
|
319
|
+
const directMarks = [
|
|
320
|
+
...(targetStep === undefined ? [] : [targetStep]),
|
|
321
|
+
...getScaleConsumerMarkSteps(program, scaleIds)
|
|
322
|
+
];
|
|
323
|
+
const marks = [
|
|
324
|
+
...directMarks,
|
|
325
|
+
...directMarks.flatMap(step =>
|
|
326
|
+
getSourceDependentMarkSteps(program, step.args.id)
|
|
327
|
+
)
|
|
328
|
+
];
|
|
329
|
+
const guides = scaleIds.flatMap(id =>
|
|
330
|
+
planScaleGuideRematerialization(program, id)
|
|
331
|
+
);
|
|
332
|
+
if (hasMaterializedLegend(program)) {
|
|
333
|
+
guides.push({ op: "rematerializeLegend" });
|
|
334
|
+
}
|
|
335
|
+
return buildMaterializationPlan({ marks, guides });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function applyEncodingDraft(program, target, requests) {
|
|
339
|
+
let draft = stageFinalPositionRoles(
|
|
340
|
+
createDeferredProgram(program),
|
|
341
|
+
target,
|
|
342
|
+
requests
|
|
343
|
+
);
|
|
344
|
+
for (const { channel, payload } of requests) {
|
|
345
|
+
const focusedAction = draft[CHANNEL_METHODS[channel]];
|
|
346
|
+
draft = invokeWrappedActionImplementation(
|
|
347
|
+
focusedAction,
|
|
348
|
+
draft,
|
|
349
|
+
{ ...payload, target }
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const originalLayer = findLayer(program, target);
|
|
353
|
+
const finalLayer = findLayer(draft, target);
|
|
354
|
+
for (const channel of ["x", "y"]) {
|
|
355
|
+
const previousScale = originalLayer?.encoding?.[channel]?.scale;
|
|
356
|
+
const nextScale = finalLayer?.encoding?.[channel]?.scale;
|
|
357
|
+
if (previousScale !== undefined && nextScale !== undefined) {
|
|
358
|
+
draft = rebindPositionGuides(
|
|
359
|
+
draft,
|
|
360
|
+
channel,
|
|
361
|
+
previousScale,
|
|
362
|
+
nextScale,
|
|
363
|
+
target
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return draft;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function planEncodingAssignments(program, target, requests) {
|
|
371
|
+
const originalLayer = findLayer(program, target);
|
|
372
|
+
const draft = applyEncodingDraft(program, target, requests);
|
|
373
|
+
const finalLayer = findLayer(draft, target);
|
|
374
|
+
return Object.freeze({
|
|
375
|
+
target,
|
|
376
|
+
originalLayer,
|
|
377
|
+
finalLayer,
|
|
378
|
+
state: programState(draft),
|
|
379
|
+
scaleIds: Object.freeze(finalScaleIds(
|
|
380
|
+
program,
|
|
381
|
+
draft,
|
|
382
|
+
originalLayer,
|
|
383
|
+
finalLayer,
|
|
384
|
+
requests
|
|
385
|
+
))
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function applyEncodingAssignments(program, plan) {
|
|
390
|
+
return instantiateProgram(plan.state, program.constructor);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export const encodeChannels = action(
|
|
394
|
+
{
|
|
395
|
+
op: "encodeChannels",
|
|
396
|
+
description: "Atomically assign multiple encoding channels to one mark."
|
|
397
|
+
},
|
|
398
|
+
function (args = {}) {
|
|
399
|
+
const { target, requests } = normalizeEncodeChannelsArgs(args);
|
|
400
|
+
const plan = planEncodingAssignments(this, target, requests);
|
|
401
|
+
let next = applyEncodingAssignments(this, plan);
|
|
402
|
+
const scaleIds = plan.scaleIds;
|
|
403
|
+
if (scaleIds.length > 0) {
|
|
404
|
+
next = rematerializeEncodingScales.call(next, { ids: scaleIds });
|
|
405
|
+
}
|
|
406
|
+
return applyMaterializationPlan(
|
|
407
|
+
next,
|
|
408
|
+
finalMaterializationPlan(next, target, scaleIds)
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
export function registerAtomicEncodingAction(ProgramClass) {
|
|
414
|
+
ProgramClass.prototype.encodeChannels = encodeChannels;
|
|
415
|
+
}
|