ggaction 0.0.4 → 0.0.6
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 +55 -0
- package/README.md +35 -13
- package/package.json +18 -7
- package/src/actions/boxPlots/create.js +13 -7
- package/src/actions/boxPlots/edit.js +484 -36
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +11 -4
- package/src/actions/boxPlots/resolve.js +10 -2
- package/src/actions/charts/bar.js +50 -0
- package/src/actions/charts/heatmap.js +205 -0
- package/src/actions/charts/histogram.js +58 -0
- package/src/actions/charts/index.js +15 -0
- package/src/actions/charts/line.js +69 -0
- package/src/actions/charts/parallel.js +76 -0
- package/src/actions/charts/scatter.js +56 -0
- package/src/actions/charts/shared.js +121 -0
- package/src/actions/composition/actions.js +13 -3
- package/src/actions/coordinates/parallel.js +50 -0
- package/src/actions/data/bin2d.js +260 -0
- package/src/actions/data/density.js +54 -13
- package/src/actions/data/derived.js +22 -0
- package/src/actions/data/filter.js +5 -0
- package/src/actions/data/gradientProfile.js +65 -0
- package/src/actions/data/horizon.js +71 -0
- package/src/actions/data/index.js +34 -2
- package/src/actions/data/intervalEdit.js +76 -0
- package/src/actions/data/window.js +50 -0
- package/src/actions/distributions/revision.js +326 -0
- package/src/actions/encodings/appearance.js +35 -1
- package/src/actions/encodings/color/index.js +17 -1
- package/src/actions/encodings/color/layout.js +8 -0
- package/src/actions/encodings/density/resolve.js +109 -0
- package/src/actions/encodings/density.js +330 -90
- package/src/actions/encodings/horizon/resolve.js +283 -0
- package/src/actions/encodings/horizon.js +364 -0
- package/src/actions/encodings/index.js +8 -0
- package/src/actions/encodings/parallel.js +115 -0
- package/src/actions/encodings/pathOrder.js +131 -0
- package/src/actions/encodings/position/apply.js +73 -4
- package/src/actions/encodings/position/index.js +15 -2
- package/src/actions/encodings/position/policies/arc.js +27 -4
- package/src/actions/encodings/position/policies/area.js +33 -2
- package/src/actions/encodings/position/policies/index.js +7 -2
- package/src/actions/encodings/position/policies/line.js +18 -2
- package/src/actions/encodings/position/resolve.js +24 -2
- package/src/actions/encodings/ranged.js +3 -1
- package/src/actions/encodings/remove.js +268 -0
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/errorBands/edit.js +121 -13
- package/src/actions/errorBars/edit.js +45 -8
- package/src/actions/errorBars/options.js +7 -1
- package/src/actions/facets/actions.js +137 -3
- package/src/actions/facets/derive.js +33 -3
- package/src/actions/facets/replay.js +7 -36
- package/src/actions/gradientPlots/components.js +177 -0
- package/src/actions/gradientPlots/create.js +96 -0
- package/src/actions/gradientPlots/edit.js +501 -0
- package/src/actions/gradientPlots/index.js +24 -0
- package/src/actions/gradientPlots/materialize.js +287 -0
- package/src/actions/gradientPlots/options.js +120 -0
- package/src/actions/gradientPlots/paint.js +107 -0
- package/src/actions/gradientPlots/rebind.js +51 -0
- package/src/actions/gradientPlots/resolve.js +81 -0
- package/src/actions/guides/applicability.js +44 -17
- package/src/actions/guides/axes/axes.js +37 -3
- package/src/actions/guides/axes/edit.js +129 -47
- package/src/actions/guides/axes/index.js +2 -0
- package/src/actions/guides/axes/labels.js +11 -6
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/titles.js +11 -8
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +67 -19
- package/src/actions/guides/legends/continuous/common.js +3 -3
- package/src/actions/guides/legends/edit.js +63 -0
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +124 -30
- package/src/actions/guides/legends/strokeWidth.js +198 -0
- package/src/actions/index.js +6 -0
- package/src/actions/marks/arc/actions.js +46 -14
- package/src/actions/marks/area/actions.js +60 -136
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +99 -80
- package/src/actions/marks/line/materialize.js +146 -0
- package/src/actions/marks/point/create.js +76 -0
- package/src/actions/marks/point/edit.js +97 -0
- package/src/actions/marks/point/index.js +11 -1
- package/src/actions/marks/point/jitter.js +72 -0
- package/src/actions/marks/point/{actions.js → materialize.js} +183 -172
- package/src/actions/marks/rect/actions.js +3 -0
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +29 -4
- package/src/actions/marks/text/actions.js +19 -4
- package/src/actions/marks/text/index.js +7 -1
- package/src/actions/marks/text/layout.js +321 -0
- package/src/actions/primitives/semanticValidation/dataset.js +22 -0
- package/src/actions/primitives/semanticValidation/guide.js +43 -0
- package/src/actions/primitives/semanticValidation/index.js +23 -0
- package/src/actions/primitives/semanticValidation/layer.js +74 -0
- package/src/actions/primitives/semanticValidation/scale.js +74 -0
- package/src/actions/primitives/semanticValidation/shared.js +5 -0
- package/src/actions/primitives/semanticValue.js +1 -204
- package/src/actions/regression/edit.js +247 -88
- package/src/actions/scales/consumers/common.js +18 -0
- package/src/actions/scales/consumers/families.js +14 -3
- package/src/actions/scales/definitions.js +30 -0
- package/src/actions/scales/edit.js +6 -204
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +26 -0
- package/src/actions/selection/actions.js +150 -5
- package/src/actions/selection/index.js +6 -0
- package/src/actions/violinPlots/create.js +198 -0
- package/src/actions/violinPlots/index.js +5 -0
- package/src/core/textMetrics.js +47 -0
- package/src/core/vocabulary.js +14 -2
- package/src/grammar/arcs.js +49 -12
- package/src/grammar/areaSeries.js +41 -5
- package/src/grammar/bin2d.js +333 -0
- package/src/grammar/categoricalDensity.js +192 -0
- package/src/grammar/coordinates.js +1 -1
- package/src/grammar/curveCommands.js +3 -1
- package/src/grammar/density.js +252 -26
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/facets/index.js +3 -1
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +69 -0
- package/src/grammar/paint.js +88 -0
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/appearance.js +21 -0
- package/src/grammar/schemas/concreteGraphic.js +11 -1
- package/src/grammar/schemas/graphicBounds.js +19 -37
- package/src/grammar/schemas/semanticPath.js +17 -2
- package/src/grammar/transforms.js +100 -11
- package/src/grammar/window.js +339 -0
- package/src/layout/labels.js +270 -0
- package/src/layout/text.js +4 -1
- package/src/layout/title.js +10 -20
- package/src/materialization/dataProvenance.js +56 -2
- package/src/materialization/dependencies.js +9 -14
- package/src/materialization/guides/resources.js +74 -24
- package/src/materialization/horizon.js +29 -0
- package/src/materialization/layout.js +15 -0
- package/src/materialization/marks/capabilities.js +21 -1
- package/src/materialization/marks/index.js +3 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +6 -2
- package/src/materialization/scaleGuideDependencies.js +8 -0
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/path.js +27 -1
- package/src/materialization/selection/items/point.js +1 -5
- package/src/materialization/selection/items/rect.js +21 -0
- package/src/materialization/selection/styles.js +20 -9
- package/src/materialization/text.js +12 -4
- package/src/renderers/canvas/fill.js +33 -0
- package/src/renderers/canvas/index.js +7 -0
- package/src/renderers/canvas/path.js +7 -4
- package/src/renderers/canvas/rect.js +9 -3
- package/src/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +6 -0
- package/types/index.d.ts +77 -0
- package/types/program.d.ts +703 -17
|
@@ -16,7 +16,9 @@ import { materializeFacetGraphics } from "../../materialization/facets.js";
|
|
|
16
16
|
const CONCAT_OPTIONS = Object.freeze([
|
|
17
17
|
"id", "programs", "gap", "align", "padding"
|
|
18
18
|
]);
|
|
19
|
-
const LAYOUT_EDIT_OPTIONS = Object.freeze([
|
|
19
|
+
const LAYOUT_EDIT_OPTIONS = Object.freeze([
|
|
20
|
+
"columns", "gap", "align", "padding"
|
|
21
|
+
]);
|
|
20
22
|
const REPLACEMENT_OPTIONS = Object.freeze(["target", "program"]);
|
|
21
23
|
|
|
22
24
|
function normalizeChildEntry(entry, index) {
|
|
@@ -149,7 +151,7 @@ export const vconcatAction = concatAction("vertical", "vconcat");
|
|
|
149
151
|
const editCompositionLayout = action(
|
|
150
152
|
{
|
|
151
153
|
op: "editCompositionLayout",
|
|
152
|
-
description: "Edit composition
|
|
154
|
+
description: "Edit composition layout.",
|
|
153
155
|
scope: "composition"
|
|
154
156
|
},
|
|
155
157
|
function (args = {}) {
|
|
@@ -164,7 +166,9 @@ const editCompositionLayout = action(
|
|
|
164
166
|
...childDescriptor({ id, program: this.children[id] }),
|
|
165
167
|
value: current.facet.values[index]
|
|
166
168
|
})),
|
|
167
|
-
columns:
|
|
169
|
+
columns: Object.hasOwn(args, "columns")
|
|
170
|
+
? args.columns
|
|
171
|
+
: current.columns,
|
|
168
172
|
gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
|
|
169
173
|
align: Object.hasOwn(args, "align") ? args.align : current.align,
|
|
170
174
|
padding: Object.hasOwn(args, "padding")
|
|
@@ -176,12 +180,18 @@ const editCompositionLayout = action(
|
|
|
176
180
|
children: this.children,
|
|
177
181
|
compositionSpec: {
|
|
178
182
|
...current,
|
|
183
|
+
columns: layout.columns,
|
|
179
184
|
gap: layout.gap,
|
|
180
185
|
align: layout.align,
|
|
181
186
|
padding: layout.padding
|
|
182
187
|
}
|
|
183
188
|
}).materializeComposition();
|
|
184
189
|
}
|
|
190
|
+
if (Object.hasOwn(args, "columns")) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
"editCompositionLayout columns is available only on a facet composition."
|
|
193
|
+
);
|
|
194
|
+
}
|
|
185
195
|
const padding = Object.hasOwn(args, "padding")
|
|
186
196
|
? normalizeCompositionPadding(args.padding, current.padding)
|
|
187
197
|
: current.padding;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
2
|
+
import { findCoordinate } from "../../selectors/coordinates.js";
|
|
3
|
+
|
|
4
|
+
export function resolveParallelCoordinate(program, {
|
|
5
|
+
requested,
|
|
6
|
+
layer,
|
|
7
|
+
operation,
|
|
8
|
+
useCurrent = false
|
|
9
|
+
}) {
|
|
10
|
+
const explicit = requested === undefined
|
|
11
|
+
? undefined
|
|
12
|
+
: validateUserId(requested, "Parallel coordinate id");
|
|
13
|
+
if (layer?.coordinate !== undefined) {
|
|
14
|
+
if (explicit !== undefined && explicit !== layer.coordinate) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Layer "${layer.id}" already uses coordinate "${layer.coordinate}".`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const coordinate = findCoordinate(program, layer.coordinate);
|
|
20
|
+
if (coordinate?.type !== "parallel") {
|
|
21
|
+
throw new Error(`Coordinate "${layer.coordinate}" is not Parallel.`);
|
|
22
|
+
}
|
|
23
|
+
return { id: layer.coordinate, create: false };
|
|
24
|
+
}
|
|
25
|
+
if (explicit !== undefined) {
|
|
26
|
+
const coordinate = findCoordinate(program, explicit);
|
|
27
|
+
if (coordinate !== undefined && coordinate.type !== "parallel") {
|
|
28
|
+
throw new Error(`Coordinate "${explicit}" is not Parallel.`);
|
|
29
|
+
}
|
|
30
|
+
return { id: explicit, create: coordinate === undefined };
|
|
31
|
+
}
|
|
32
|
+
const current = useCurrent ? program.context.currentCoordinate : undefined;
|
|
33
|
+
if (current !== undefined && findCoordinate(program, current)?.type === "parallel") {
|
|
34
|
+
return { id: current, create: false };
|
|
35
|
+
}
|
|
36
|
+
const compatible = program.semanticSpec.coordinates.filter(
|
|
37
|
+
coordinate => coordinate.type === "parallel"
|
|
38
|
+
);
|
|
39
|
+
if (compatible.length > 1) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`${operation} requires coordinate when multiple Parallel coordinates are available.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (compatible.length === 1) return { id: compatible[0].id, create: false };
|
|
45
|
+
const conflict = findCoordinate(program, "parallel");
|
|
46
|
+
if (conflict !== undefined) {
|
|
47
|
+
throw new Error('Coordinate "parallel" already exists with a different type.');
|
|
48
|
+
}
|
|
49
|
+
return { id: "parallel", create: true };
|
|
50
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveBin2DRows,
|
|
6
|
+
normalizeBin2DTransform,
|
|
7
|
+
requestedBin2DTransform
|
|
8
|
+
} from "../../grammar/bin2d.js";
|
|
9
|
+
import { applyLayerDataRematerialization } from
|
|
10
|
+
"../../materialization/dependencies.js";
|
|
11
|
+
import { planDerivedDataRevision } from
|
|
12
|
+
"../../materialization/dataProvenance.js";
|
|
13
|
+
import {
|
|
14
|
+
findDataset,
|
|
15
|
+
findDatasetConsumer
|
|
16
|
+
} from "../../selectors/datasets.js";
|
|
17
|
+
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
18
|
+
|
|
19
|
+
const OPTIONS = Object.freeze([
|
|
20
|
+
"id", "source", "x", "y", "bins", "extent", "includeEmpty", "members", "as"
|
|
21
|
+
]);
|
|
22
|
+
const EDIT_OPTIONS = Object.freeze([
|
|
23
|
+
"target", "source", "x", "y", "bins", "extent", "includeEmpty", "members", "as"
|
|
24
|
+
]);
|
|
25
|
+
const EDITABLE = Object.freeze(EDIT_OPTIONS.slice(1));
|
|
26
|
+
const OUTPUT_FIELDS = Object.freeze([
|
|
27
|
+
"x0", "x1", "y0", "y1", "count"
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
function ownerConfig(program, id) {
|
|
31
|
+
return program.materializationConfigs.data?.bin2d?.[id];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ownerEntries(program) {
|
|
35
|
+
return Object.entries(program.materializationConfigs.data?.bin2d ?? {});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveBin2DOwner(program, requested) {
|
|
39
|
+
if (requested !== undefined) {
|
|
40
|
+
const owner = validateUserId(requested, "2D bin owner id");
|
|
41
|
+
if (ownerConfig(program, owner) === undefined) {
|
|
42
|
+
throw new Error(`Unknown 2D bin owner "${owner}".`);
|
|
43
|
+
}
|
|
44
|
+
return owner;
|
|
45
|
+
}
|
|
46
|
+
const owners = ownerEntries(program);
|
|
47
|
+
const current = owners.filter(([, config]) =>
|
|
48
|
+
config?.current === program.context.currentData
|
|
49
|
+
);
|
|
50
|
+
if (current.length === 1) return current[0][0];
|
|
51
|
+
if (current.length > 1) {
|
|
52
|
+
throw new Error("2D bin owner is ambiguous; provide target.");
|
|
53
|
+
}
|
|
54
|
+
if (owners.length === 1) return owners[0][0];
|
|
55
|
+
if (owners.length === 0) throw new Error("No 2D bin owner is available.");
|
|
56
|
+
throw new Error("2D bin owner is ambiguous; provide target.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function requireCurrentBin2D(program, id, config) {
|
|
60
|
+
const current = findDataset(program, config.current);
|
|
61
|
+
if (
|
|
62
|
+
current?.source === undefined ||
|
|
63
|
+
current.transform?.length !== 1 ||
|
|
64
|
+
current.transform[0].type !== "bin2d"
|
|
65
|
+
) {
|
|
66
|
+
throw new Error(`2D bin owner "${id}" has no current derived dataset.`);
|
|
67
|
+
}
|
|
68
|
+
return current;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function directLayerConsumers(program, data) {
|
|
72
|
+
return program.semanticSpec.layers
|
|
73
|
+
.filter(layer => layer.data === data)
|
|
74
|
+
.map(layer => layer.id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function rejectDerivedConsumers(program, data) {
|
|
78
|
+
const dependent = findDatasetConsumer(program, data);
|
|
79
|
+
if (dependent !== undefined) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Cannot replace 2D bin dataset "${data}" while derived dataset ` +
|
|
82
|
+
`"${dependent.id}" depends on it.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function preflight(program, sourceId, transform) {
|
|
88
|
+
const source = findDataset(program, sourceId);
|
|
89
|
+
if (source?.values === undefined) {
|
|
90
|
+
throw new Error(`Source dataset "${sourceId}" has no values.`);
|
|
91
|
+
}
|
|
92
|
+
deriveBin2DRows(source.values, transform);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requireCompleteEditOutputFields(value, members) {
|
|
96
|
+
const required = [...OUTPUT_FIELDS, ...(members ? ["members"] : [])];
|
|
97
|
+
const missing = required.find(field => !Object.hasOwn(value, field));
|
|
98
|
+
if (missing !== undefined) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`editBin2DData as requires the complete "${missing}" output field.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function editedTransform(owner, previous, args) {
|
|
106
|
+
const prior = requestedBin2DTransform(previous.transform[0]);
|
|
107
|
+
const option = property => Object.hasOwn(args, property)
|
|
108
|
+
? args[property]
|
|
109
|
+
: prior[property];
|
|
110
|
+
const members = option("members");
|
|
111
|
+
let as = option("as");
|
|
112
|
+
if (!Object.hasOwn(args, "as")) {
|
|
113
|
+
as = { ...as };
|
|
114
|
+
if (members && as.members === undefined) {
|
|
115
|
+
as.members = `__${owner}_members`;
|
|
116
|
+
} else if (!members) {
|
|
117
|
+
delete as.members;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const transform = normalizeBin2DTransform({
|
|
121
|
+
id: owner,
|
|
122
|
+
x: option("x"),
|
|
123
|
+
y: option("y"),
|
|
124
|
+
bins: option("bins"),
|
|
125
|
+
extent: option("extent"),
|
|
126
|
+
includeEmpty: option("includeEmpty"),
|
|
127
|
+
members,
|
|
128
|
+
as
|
|
129
|
+
});
|
|
130
|
+
if (Object.hasOwn(args, "as")) {
|
|
131
|
+
requireCompleteEditOutputFields(args.as, members);
|
|
132
|
+
}
|
|
133
|
+
return transform;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function sameRequestedTransform(left, right) {
|
|
137
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function applyBin2DRevision(program, { owner, previous, source, transform }) {
|
|
141
|
+
rejectDerivedConsumers(program, previous.id);
|
|
142
|
+
const consumers = directLayerConsumers(program, previous.id);
|
|
143
|
+
const revision = planDerivedDataRevision(program, {
|
|
144
|
+
owner,
|
|
145
|
+
role: "Bin2DData",
|
|
146
|
+
previous: previous.id,
|
|
147
|
+
consumers
|
|
148
|
+
});
|
|
149
|
+
let next = program
|
|
150
|
+
.createDerivedData({ id: revision.id, source, transform: [transform] })
|
|
151
|
+
.materializeBin2DData({ id: revision.id });
|
|
152
|
+
for (const rebind of revision.rebinds) {
|
|
153
|
+
next = next.rebindLayerData(rebind);
|
|
154
|
+
next = applyLayerDataRematerialization(next, rebind.id);
|
|
155
|
+
}
|
|
156
|
+
next = next.releaseDerivedData(revision.release);
|
|
157
|
+
return next._withMaterializationConfig(
|
|
158
|
+
["data", "bin2d", owner],
|
|
159
|
+
{ current: revision.id }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const materializeBin2DData = action(
|
|
164
|
+
{
|
|
165
|
+
op: "materializeBin2DData",
|
|
166
|
+
description: "Materialize one immutable rectangular 2D-bin dataset."
|
|
167
|
+
},
|
|
168
|
+
function (args = {}) {
|
|
169
|
+
validateKeys(args, MATERIALIZE_OPTIONS, "materializeBin2DData");
|
|
170
|
+
const { id, source, transform } = requireDerivedDataset(
|
|
171
|
+
this,
|
|
172
|
+
args.id,
|
|
173
|
+
"bin2d"
|
|
174
|
+
);
|
|
175
|
+
const result = deriveBin2DRows(source.values, transform);
|
|
176
|
+
return this
|
|
177
|
+
.editSemantic({
|
|
178
|
+
property: `dataset[${id}].transform`,
|
|
179
|
+
value: [{ ...transform, resolved: result.resolved }]
|
|
180
|
+
})
|
|
181
|
+
.editSemantic({
|
|
182
|
+
property: `dataset[${id}].values`,
|
|
183
|
+
value: result.values
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
export const createBin2DData = action(
|
|
189
|
+
{
|
|
190
|
+
op: "createBin2DData",
|
|
191
|
+
description: "Create or revise immutable rectangular 2D-bin values."
|
|
192
|
+
},
|
|
193
|
+
function (args = {}) {
|
|
194
|
+
validateKeys(args, OPTIONS, "createBin2DData");
|
|
195
|
+
const owner = validateUserId(args.id, "2D bin dataset id");
|
|
196
|
+
const config = ownerConfig(this, owner);
|
|
197
|
+
const previous = config === undefined
|
|
198
|
+
? undefined
|
|
199
|
+
: requireCurrentBin2D(this, owner, config);
|
|
200
|
+
const source = validateUserId(
|
|
201
|
+
args.source ?? previous?.source ?? this.context.currentData,
|
|
202
|
+
"Source dataset id"
|
|
203
|
+
);
|
|
204
|
+
const transform = normalizeBin2DTransform({ ...args, id: owner });
|
|
205
|
+
preflight(this, source, transform);
|
|
206
|
+
|
|
207
|
+
if (previous === undefined) {
|
|
208
|
+
return this
|
|
209
|
+
.createDerivedData({ id: owner, source, transform: [transform] })
|
|
210
|
+
.materializeBin2DData({ id: owner })
|
|
211
|
+
._withMaterializationConfig(
|
|
212
|
+
["data", "bin2d", owner],
|
|
213
|
+
{ current: owner }
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return applyBin2DRevision(this, {
|
|
218
|
+
owner,
|
|
219
|
+
previous,
|
|
220
|
+
source,
|
|
221
|
+
transform
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
export const editBin2DData = action(
|
|
227
|
+
{
|
|
228
|
+
op: "editBin2DData",
|
|
229
|
+
description: "Partially revise one logical rectangular 2D-bin owner."
|
|
230
|
+
},
|
|
231
|
+
function (args = {}) {
|
|
232
|
+
validateKeys(args, EDIT_OPTIONS, "editBin2DData");
|
|
233
|
+
if (!EDITABLE.some(option => Object.hasOwn(args, option))) {
|
|
234
|
+
throw new Error("editBin2DData requires at least one transform or source option.");
|
|
235
|
+
}
|
|
236
|
+
const owner = resolveBin2DOwner(this, args.target);
|
|
237
|
+
const previous = requireCurrentBin2D(this, owner, ownerConfig(this, owner));
|
|
238
|
+
const source = validateUserId(
|
|
239
|
+
args.source ?? previous.source,
|
|
240
|
+
"2D bin source dataset id"
|
|
241
|
+
);
|
|
242
|
+
const transform = editedTransform(owner, previous, args);
|
|
243
|
+
if (
|
|
244
|
+
source === previous.source &&
|
|
245
|
+
sameRequestedTransform(
|
|
246
|
+
transform,
|
|
247
|
+
requestedBin2DTransform(previous.transform[0])
|
|
248
|
+
)
|
|
249
|
+
) {
|
|
250
|
+
throw new Error("editBin2DData requires an actual transform or source change.");
|
|
251
|
+
}
|
|
252
|
+
preflight(this, source, transform);
|
|
253
|
+
const revision = { owner, previous, source, transform };
|
|
254
|
+
|
|
255
|
+
// Execute one speculative immutable branch so every consumer plan is known
|
|
256
|
+
// to succeed before the returned action trace records its first child.
|
|
257
|
+
applyBin2DRevision(this, revision);
|
|
258
|
+
return applyBin2DRevision(this, revision);
|
|
259
|
+
}
|
|
260
|
+
);
|
|
@@ -8,6 +8,23 @@ const OPTIONS = Object.freeze([
|
|
|
8
8
|
"id", "source", "field", "groupBy", "bandwidth", "extent", "steps",
|
|
9
9
|
"kernel", "normalization", "as"
|
|
10
10
|
]);
|
|
11
|
+
const CATEGORICAL_OPTIONS = Object.freeze([...OPTIONS, "placement"]);
|
|
12
|
+
|
|
13
|
+
function densityTransform(args, placement) {
|
|
14
|
+
return {
|
|
15
|
+
type: "density",
|
|
16
|
+
field: args.field,
|
|
17
|
+
...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
|
|
18
|
+
bandwidth: args.bandwidth ?? "auto",
|
|
19
|
+
extent: args.extent ?? "auto",
|
|
20
|
+
steps: args.steps ?? 100,
|
|
21
|
+
kernel: args.kernel ?? "gaussian",
|
|
22
|
+
normalization: args.normalization ?? "unit",
|
|
23
|
+
as: args.as ?? [`${args.field}_value`, `${args.field}_density`],
|
|
24
|
+
resolve: "shared",
|
|
25
|
+
...(placement === undefined ? {} : { placement })
|
|
26
|
+
};
|
|
27
|
+
}
|
|
11
28
|
|
|
12
29
|
export const materializeDensityData = action(
|
|
13
30
|
{ op: "materializeDensityData", description: "Materialize one grouped kernel-density dataset." },
|
|
@@ -26,7 +43,10 @@ export const materializeDensityData = action(
|
|
|
26
43
|
...transform,
|
|
27
44
|
resolved: {
|
|
28
45
|
bandwidth: result.bandwidth,
|
|
29
|
-
extent: result.extent
|
|
46
|
+
extent: result.extent,
|
|
47
|
+
...(result.splitDomain === undefined
|
|
48
|
+
? {}
|
|
49
|
+
: { splitDomain: result.splitDomain })
|
|
30
50
|
}
|
|
31
51
|
}]
|
|
32
52
|
})
|
|
@@ -49,20 +69,41 @@ export const createDensityData = action(
|
|
|
49
69
|
if (typeof args.field !== "string" || args.field.length === 0) {
|
|
50
70
|
throw new TypeError("createDensityData requires a non-empty field string.");
|
|
51
71
|
}
|
|
52
|
-
const transform =
|
|
53
|
-
type: "density",
|
|
54
|
-
field: args.field,
|
|
55
|
-
...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
|
|
56
|
-
bandwidth: args.bandwidth ?? "auto",
|
|
57
|
-
extent: args.extent ?? "auto",
|
|
58
|
-
steps: args.steps ?? 100,
|
|
59
|
-
kernel: args.kernel ?? "gaussian",
|
|
60
|
-
normalization: args.normalization ?? "unit",
|
|
61
|
-
as: args.as ?? [`${args.field}_value`, `${args.field}_density`],
|
|
62
|
-
resolve: "shared"
|
|
63
|
-
};
|
|
72
|
+
const transform = densityTransform(args);
|
|
64
73
|
return this
|
|
65
74
|
.createDerivedData({ id, source, transform: [transform] })
|
|
66
75
|
.materializeDensityData({ id });
|
|
67
76
|
}
|
|
68
77
|
);
|
|
78
|
+
|
|
79
|
+
export const createCategoricalDensityData = action(
|
|
80
|
+
{
|
|
81
|
+
op: "createCategoricalDensityData",
|
|
82
|
+
description: "Create category-placed kernel-density values."
|
|
83
|
+
},
|
|
84
|
+
function (args = {}) {
|
|
85
|
+
validateKeys(args, CATEGORICAL_OPTIONS, "createCategoricalDensityData");
|
|
86
|
+
const id = validateUserId(args.id, "Density dataset id");
|
|
87
|
+
const source = validateUserId(
|
|
88
|
+
args.source ?? this.context.currentData,
|
|
89
|
+
"Source dataset id"
|
|
90
|
+
);
|
|
91
|
+
if (typeof args.field !== "string" || args.field.length === 0) {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
"createCategoricalDensityData requires a non-empty field string."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (args.placement?.type !== "category") {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"createCategoricalDensityData requires normalized category placement."
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return this
|
|
102
|
+
.createDerivedData({
|
|
103
|
+
id,
|
|
104
|
+
source,
|
|
105
|
+
transform: [densityTransform(args, args.placement)]
|
|
106
|
+
})
|
|
107
|
+
.materializeDensityData({ id });
|
|
108
|
+
}
|
|
109
|
+
);
|
|
@@ -3,9 +3,11 @@ import { validateUserId } from "../../core/identifiers.js";
|
|
|
3
3
|
import { validateKeys } from "../../core/validation.js";
|
|
4
4
|
import { hasDataset } from "../../selectors/index.js";
|
|
5
5
|
import { findDataset } from "../../selectors/datasets.js";
|
|
6
|
+
import { requireLayer } from "../../selectors/layers.js";
|
|
6
7
|
|
|
7
8
|
const OPTIONS = Object.freeze(["id", "source", "transform"]);
|
|
8
9
|
const RELEASE_OPTIONS = Object.freeze(["id"]);
|
|
10
|
+
const REBIND_OPTIONS = Object.freeze(["id", "data"]);
|
|
9
11
|
|
|
10
12
|
export const createDerivedData = action(
|
|
11
13
|
{ op: "createDerivedData", description: "Create an immutable derived dataset definition." },
|
|
@@ -50,3 +52,23 @@ export const releaseDerivedData = action(
|
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
);
|
|
55
|
+
|
|
56
|
+
export const rebindLayerData = action(
|
|
57
|
+
{
|
|
58
|
+
op: "rebindLayerData",
|
|
59
|
+
description: "Rebind one semantic layer to an existing dataset."
|
|
60
|
+
},
|
|
61
|
+
function (args = {}) {
|
|
62
|
+
validateKeys(args, REBIND_OPTIONS, "rebindLayerData");
|
|
63
|
+
const id = validateUserId(args.id, "Layer id");
|
|
64
|
+
const data = validateUserId(args.data, "Layer dataset id");
|
|
65
|
+
requireLayer(this, id);
|
|
66
|
+
if (!hasDataset(this, data)) {
|
|
67
|
+
throw new Error(`Layer dataset "${data}" does not exist.`);
|
|
68
|
+
}
|
|
69
|
+
return this.editSemantic({
|
|
70
|
+
property: `layer[${id}].data`,
|
|
71
|
+
value: data
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
);
|
|
@@ -125,6 +125,11 @@ export const filterMarks = action(
|
|
|
125
125
|
candidate.data !== undefined &&
|
|
126
126
|
findSelectionPolicy(candidate.mark?.type) !== undefined
|
|
127
127
|
});
|
|
128
|
+
if (this.markConfigs[layer.id]?.itemFilterable === false) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`filterMarks does not support composite mark "${layer.id}"; filter its source dataset first.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
128
133
|
const derivedId = `${layer.id}FilteredData`;
|
|
129
134
|
if (hasDataset(this, derivedId)) {
|
|
130
135
|
throw new Error(`Dataset "${derivedId}" already exists.`);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveGradientProfiles,
|
|
6
|
+
normalizeGradientProfileTransform
|
|
7
|
+
} from "../../grammar/gradientProfile.js";
|
|
8
|
+
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
9
|
+
|
|
10
|
+
const OPTIONS = Object.freeze([
|
|
11
|
+
"id", "source", "category", "field", "bandwidth", "extent", "steps",
|
|
12
|
+
"kernel", "normalization", "center", "as"
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export const materializeGradientProfileData = action(
|
|
16
|
+
{
|
|
17
|
+
op: "materializeGradientProfileData",
|
|
18
|
+
description: "Materialize one categorical density-profile dataset."
|
|
19
|
+
},
|
|
20
|
+
function (args = {}) {
|
|
21
|
+
validateKeys(
|
|
22
|
+
args,
|
|
23
|
+
MATERIALIZE_OPTIONS,
|
|
24
|
+
"materializeGradientProfileData"
|
|
25
|
+
);
|
|
26
|
+
const { id, source, transform } = requireDerivedDataset(
|
|
27
|
+
this,
|
|
28
|
+
args.id,
|
|
29
|
+
"gradientProfile"
|
|
30
|
+
);
|
|
31
|
+
const result = deriveGradientProfiles(source.values, transform);
|
|
32
|
+
return this
|
|
33
|
+
.editSemantic({
|
|
34
|
+
property: `dataset[${id}].transform`,
|
|
35
|
+
value: [{
|
|
36
|
+
...transform,
|
|
37
|
+
resolved: {
|
|
38
|
+
bandwidth: result.bandwidth,
|
|
39
|
+
extent: result.extent,
|
|
40
|
+
intensityDomain: result.intensityDomain
|
|
41
|
+
}
|
|
42
|
+
}]
|
|
43
|
+
})
|
|
44
|
+
.editSemantic({ property: `dataset[${id}].values`, value: result.values });
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export const createGradientProfileData = action(
|
|
49
|
+
{
|
|
50
|
+
op: "createGradientProfileData",
|
|
51
|
+
description: "Create one immutable categorical density-profile dataset."
|
|
52
|
+
},
|
|
53
|
+
function (args = {}) {
|
|
54
|
+
validateKeys(args, OPTIONS, "createGradientProfileData");
|
|
55
|
+
const id = validateUserId(args.id, "Gradient profile dataset id");
|
|
56
|
+
const source = validateUserId(
|
|
57
|
+
args.source ?? this.context.currentData,
|
|
58
|
+
"Gradient profile source dataset id"
|
|
59
|
+
);
|
|
60
|
+
const transform = normalizeGradientProfileTransform(args);
|
|
61
|
+
return this
|
|
62
|
+
.createDerivedData({ id, source, transform: [transform] })
|
|
63
|
+
.materializeGradientProfileData({ id });
|
|
64
|
+
}
|
|
65
|
+
);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveHorizon,
|
|
6
|
+
validateHorizonTransform
|
|
7
|
+
} from "../../grammar/horizon.js";
|
|
8
|
+
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
9
|
+
|
|
10
|
+
const OPTIONS = Object.freeze([
|
|
11
|
+
"id", "source", "x", "y", "groupBy", "bands", "baseline", "extent",
|
|
12
|
+
"resolve", "missing", "overflow", "palette", "as"
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function requestedTransform(args) {
|
|
16
|
+
return validateHorizonTransform({
|
|
17
|
+
type: "horizon",
|
|
18
|
+
x: args.x,
|
|
19
|
+
y: args.y,
|
|
20
|
+
...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
|
|
21
|
+
bands: args.bands ?? 3,
|
|
22
|
+
baseline: args.baseline ?? 0,
|
|
23
|
+
extent: args.extent ?? "auto",
|
|
24
|
+
resolve: args.resolve ?? "shared",
|
|
25
|
+
missing: args.missing ?? "break",
|
|
26
|
+
overflow: args.overflow ?? "clip",
|
|
27
|
+
palette: args.palette ?? {},
|
|
28
|
+
as: args.as
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const materializeHorizonData = action(
|
|
33
|
+
{
|
|
34
|
+
op: "materializeHorizonData",
|
|
35
|
+
description: "Materialize one immutable Horizon band dataset."
|
|
36
|
+
},
|
|
37
|
+
function (args = {}) {
|
|
38
|
+
validateKeys(args, MATERIALIZE_OPTIONS, "materializeHorizonData");
|
|
39
|
+
const { id, source, transform } = requireDerivedDataset(
|
|
40
|
+
this,
|
|
41
|
+
args.id,
|
|
42
|
+
"horizon"
|
|
43
|
+
);
|
|
44
|
+
const result = deriveHorizon(source.values, transform);
|
|
45
|
+
return this
|
|
46
|
+
.editSemantic({
|
|
47
|
+
property: `dataset[${id}].transform`,
|
|
48
|
+
value: [{ ...result.transform, resolved: result.resolved }]
|
|
49
|
+
})
|
|
50
|
+
.editSemantic({
|
|
51
|
+
property: `dataset[${id}].values`,
|
|
52
|
+
value: result.values
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
export const createHorizonData = action(
|
|
58
|
+
{
|
|
59
|
+
op: "createHorizonData",
|
|
60
|
+
description: "Create one immutable Horizon band dataset."
|
|
61
|
+
},
|
|
62
|
+
function (args = {}) {
|
|
63
|
+
validateKeys(args, OPTIONS, "createHorizonData");
|
|
64
|
+
const id = validateUserId(args.id, "Horizon dataset id");
|
|
65
|
+
const source = validateUserId(args.source, "Horizon source dataset id");
|
|
66
|
+
const transform = requestedTransform(args);
|
|
67
|
+
return this
|
|
68
|
+
.createDerivedData({ id, source, transform: [transform] })
|
|
69
|
+
.materializeHorizonData({ id });
|
|
70
|
+
}
|
|
71
|
+
);
|