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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
|
-
import { isPlainObject } from "../../core/immutable.js";
|
|
2
|
+
import { freezeOwned, isPlainObject } from "../../core/immutable.js";
|
|
3
3
|
import {
|
|
4
4
|
validateNonEmptyString,
|
|
5
5
|
validateNonNegativeFinite,
|
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
import { resolveFacetDefinition } from "../../grammar/facets/index.js";
|
|
10
10
|
import { normalizeFacetScalePolicies } from
|
|
11
11
|
"../../grammar/facets/scales.js";
|
|
12
|
+
import { FACET_SCALE_CHANNELS } from "../../grammar/facets/scales.js";
|
|
12
13
|
import { resolveFacetLayout } from "../../layout/facets.js";
|
|
13
14
|
import { compositionChildDescriptor } from
|
|
14
15
|
"../../materialization/composition.js";
|
|
15
16
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
|
|
16
17
|
import { deriveFacetChildren } from "./derive.js";
|
|
17
|
-
import {
|
|
18
|
+
import { replayDerivedData } from "./replay.js";
|
|
18
19
|
import { composeFacetGuides } from "./guides.js";
|
|
19
20
|
|
|
20
21
|
const FACET_OPTIONS = Object.freeze([
|
|
@@ -73,6 +74,76 @@ function requireFacetProgram(program, operation) {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
function currentFacetDefinition(program) {
|
|
78
|
+
const current = program.compositionSpec;
|
|
79
|
+
return resolveFacetDefinition(program.semanticSpec, {
|
|
80
|
+
id: current.id,
|
|
81
|
+
data: current.facet.data,
|
|
82
|
+
field: current.facet.field,
|
|
83
|
+
values: current.facet.values
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function facetUnitTemplate(program) {
|
|
88
|
+
const seed = program.children[program.compositionSpec.children[0]];
|
|
89
|
+
if (seed === undefined) {
|
|
90
|
+
throw new Error(`Facet "${program.compositionSpec.id}" requires a retained child.`);
|
|
91
|
+
}
|
|
92
|
+
const { facets: _facets, ...unitConfigs } = program.materializationConfigs;
|
|
93
|
+
return new program.constructor({
|
|
94
|
+
semanticSpec: program.semanticSpec,
|
|
95
|
+
graphicSpec: seed.graphicSpec,
|
|
96
|
+
resolvedScales: program.resolvedScales,
|
|
97
|
+
materializationConfigs: freezeOwned({
|
|
98
|
+
...unitConfigs,
|
|
99
|
+
canvas: seed.materializationConfigs.canvas
|
|
100
|
+
}),
|
|
101
|
+
children: {},
|
|
102
|
+
context: program.context,
|
|
103
|
+
trace: program.trace,
|
|
104
|
+
actionStack: program.actionStack,
|
|
105
|
+
actionSequence: program._actionSequence
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function applicableScaleRequest(program, channels) {
|
|
110
|
+
return Object.fromEntries(FACET_SCALE_CHANNELS.flatMap(channel =>
|
|
111
|
+
program.semanticSpec.layers.some(
|
|
112
|
+
layer => layer.encoding?.[channel]?.scale !== undefined
|
|
113
|
+
)
|
|
114
|
+
? [[channel, channels[channel]]]
|
|
115
|
+
: []
|
|
116
|
+
));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function rederiveFacet(program, { scales, guides }) {
|
|
120
|
+
const current = program.compositionSpec;
|
|
121
|
+
const definition = currentFacetDefinition(program);
|
|
122
|
+
const request = applicableScaleRequest(program, scales);
|
|
123
|
+
const normalized = normalizeFacetScalePolicies(program.semanticSpec, request);
|
|
124
|
+
const derived = deriveFacetChildren(facetUnitTemplate(program), definition, {
|
|
125
|
+
closeInheritedAction: true,
|
|
126
|
+
stripTitle: true,
|
|
127
|
+
scales: request
|
|
128
|
+
});
|
|
129
|
+
const compositionSpec = {
|
|
130
|
+
...current,
|
|
131
|
+
facet: {
|
|
132
|
+
...current.facet,
|
|
133
|
+
scales: normalized.channels,
|
|
134
|
+
guides
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
let next = program._withCompositionState({
|
|
138
|
+
children: derived.children,
|
|
139
|
+
compositionSpec
|
|
140
|
+
});
|
|
141
|
+
for (const id of compositionSpec.children) {
|
|
142
|
+
next = next.useProgram({ id });
|
|
143
|
+
}
|
|
144
|
+
return next.materializeComposition();
|
|
145
|
+
}
|
|
146
|
+
|
|
76
147
|
export const facet = action(
|
|
77
148
|
{
|
|
78
149
|
op: "facet",
|
|
@@ -153,10 +224,73 @@ export const editFacetHeaders = action(
|
|
|
153
224
|
}
|
|
154
225
|
);
|
|
155
226
|
|
|
227
|
+
export const editFacetScales = action(
|
|
228
|
+
{
|
|
229
|
+
op: "editFacetScales",
|
|
230
|
+
description: "Edit facet scale-resolution policies and rederive every cell.",
|
|
231
|
+
scope: "composition"
|
|
232
|
+
},
|
|
233
|
+
function (args = {}) {
|
|
234
|
+
requireFacetProgram(this, "editFacetScales");
|
|
235
|
+
validateOptionObject(args, FACET_SCALE_CHANNELS, "editFacetScales", {
|
|
236
|
+
allowEmpty: false,
|
|
237
|
+
emptyMessage: "editFacetScales requires at least one channel policy change."
|
|
238
|
+
});
|
|
239
|
+
for (const channel of Object.keys(args)) {
|
|
240
|
+
if (!this.semanticSpec.layers.some(
|
|
241
|
+
layer => layer.encoding?.[channel]?.scale !== undefined
|
|
242
|
+
)) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Facet scale channel "${channel}" is not used by an affected layer.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const current = this.compositionSpec.facet;
|
|
249
|
+
const scales = { ...current.scales, ...args };
|
|
250
|
+
if (FACET_SCALE_CHANNELS.every(
|
|
251
|
+
channel => scales[channel] === current.scales[channel]
|
|
252
|
+
)) {
|
|
253
|
+
throw new Error("editFacetScales requires at least one channel policy change.");
|
|
254
|
+
}
|
|
255
|
+
const request = applicableScaleRequest(this, scales);
|
|
256
|
+
const normalized = normalizeFacetScalePolicies(this.semanticSpec, request);
|
|
257
|
+
const applyEdit = program => rederiveFacet(program, {
|
|
258
|
+
scales: normalized.channels,
|
|
259
|
+
guides: current.guides
|
|
260
|
+
});
|
|
261
|
+
applyEdit(this);
|
|
262
|
+
return applyEdit(this);
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
export const editFacetGuides = action(
|
|
267
|
+
{
|
|
268
|
+
op: "editFacetGuides",
|
|
269
|
+
description: "Edit facet guide ownership and rederive every cell.",
|
|
270
|
+
scope: "composition"
|
|
271
|
+
},
|
|
272
|
+
function (args = {}) {
|
|
273
|
+
requireFacetProgram(this, "editFacetGuides");
|
|
274
|
+
validateOptionObject(args, GUIDE_OPTIONS, "editFacetGuides", {
|
|
275
|
+
allowEmpty: false,
|
|
276
|
+
emptyMessage: "editFacetGuides requires at least one guide policy."
|
|
277
|
+
});
|
|
278
|
+
const current = this.compositionSpec.facet;
|
|
279
|
+
const guides = normalizeGuides({ ...current.guides, ...args });
|
|
280
|
+
const applyEdit = program => rederiveFacet(program, {
|
|
281
|
+
scales: current.scales,
|
|
282
|
+
guides
|
|
283
|
+
});
|
|
284
|
+
applyEdit(this);
|
|
285
|
+
return applyEdit(this);
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
|
|
156
289
|
export function registerFacetActions(ProgramClass) {
|
|
157
290
|
ProgramClass.prototype.replayDerivedData = replayDerivedData;
|
|
158
|
-
ProgramClass.prototype.rebindLayerData = rebindLayerData;
|
|
159
291
|
ProgramClass.prototype.composeFacetGuides = composeFacetGuides;
|
|
160
292
|
ProgramClass.prototype.facet = facet;
|
|
161
293
|
ProgramClass.prototype.editFacetHeaders = editFacetHeaders;
|
|
294
|
+
ProgramClass.prototype.editFacetScales = editFacetScales;
|
|
295
|
+
ProgramClass.prototype.editFacetGuides = editFacetGuides;
|
|
162
296
|
}
|
|
@@ -12,6 +12,7 @@ import { buildMaterializationPlan } from "../../materialization/planner.js";
|
|
|
12
12
|
import { requireDataset } from "../../selectors/datasets.js";
|
|
13
13
|
import { requireLayer } from "../../selectors/layers.js";
|
|
14
14
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
15
|
+
import { findTransformPolicy } from "../../grammar/transforms.js";
|
|
15
16
|
|
|
16
17
|
function sharedHistogramBoundaries(program) {
|
|
17
18
|
const boundaries = new Map();
|
|
@@ -59,6 +60,23 @@ function replayDatasetId(cell, owner) {
|
|
|
59
60
|
return `${cell.id}-${owner}-data`;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
function rebindReplayedGradientPlots(child, base, datasets) {
|
|
64
|
+
let next = child;
|
|
65
|
+
for (const [id, markConfig] of Object.entries(base.markConfigs)) {
|
|
66
|
+
const gradientPlot = markConfig.gradientPlot;
|
|
67
|
+
if (gradientPlot?.materialized !== true) continue;
|
|
68
|
+
const profile = datasets.get(gradientPlot.profileId);
|
|
69
|
+
const source = datasets.get(gradientPlot.source);
|
|
70
|
+
if (profile === undefined || source === undefined) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Facet gradient plot "${id}" is missing replayed profile dependencies.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
next = next.rebindGradientPlotProfile({ id, profile, source });
|
|
76
|
+
}
|
|
77
|
+
return next;
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
function cellMaterializationPlan(program) {
|
|
63
81
|
const scaleIds = [...new Set(program.semanticSpec.layers.flatMap(layer =>
|
|
64
82
|
Object.values(layer.encoding ?? {})
|
|
@@ -72,7 +90,13 @@ function cellMaterializationPlan(program) {
|
|
|
72
90
|
return buildMaterializationPlan({ scales });
|
|
73
91
|
}
|
|
74
92
|
|
|
75
|
-
function deriveCellProgram(
|
|
93
|
+
function deriveCellProgram(
|
|
94
|
+
base,
|
|
95
|
+
definition,
|
|
96
|
+
cell,
|
|
97
|
+
histogramBoundaries,
|
|
98
|
+
scales
|
|
99
|
+
) {
|
|
76
100
|
let child = base.filterData({
|
|
77
101
|
id: cell.data,
|
|
78
102
|
source: definition.data,
|
|
@@ -88,10 +112,15 @@ function deriveCellProgram(base, definition, cell, histogramBoundaries) {
|
|
|
88
112
|
);
|
|
89
113
|
}
|
|
90
114
|
const id = replayDatasetId(cell, replay.id);
|
|
115
|
+
const policy = findTransformPolicy(replay.transform.type);
|
|
116
|
+
const transform = policy?.facetReplayTransform?.(
|
|
117
|
+
replay.transform,
|
|
118
|
+
{ scales }
|
|
119
|
+
) ?? replay.transform;
|
|
91
120
|
child = child.replayDerivedData({
|
|
92
121
|
id,
|
|
93
122
|
source,
|
|
94
|
-
transform
|
|
123
|
+
transform
|
|
95
124
|
});
|
|
96
125
|
datasets.set(replay.id, id);
|
|
97
126
|
}
|
|
@@ -109,6 +138,7 @@ function deriveCellProgram(base, definition, cell, histogramBoundaries) {
|
|
|
109
138
|
histogramBoundaries.get(layer.id)
|
|
110
139
|
);
|
|
111
140
|
}
|
|
141
|
+
child = rebindReplayedGradientPlots(child, base, datasets);
|
|
112
142
|
return applyMaterializationPlan(child, cellMaterializationPlan(child));
|
|
113
143
|
}
|
|
114
144
|
|
|
@@ -163,7 +193,7 @@ export function deriveFacetChildren(
|
|
|
163
193
|
: new Map();
|
|
164
194
|
const independentlyResolved = Object.fromEntries(definition.cells.map(cell => [
|
|
165
195
|
cell.id,
|
|
166
|
-
deriveCellProgram(template, definition, cell, bins)
|
|
196
|
+
deriveCellProgram(template, definition, cell, bins, resolutionRequest)
|
|
167
197
|
]));
|
|
168
198
|
const resolution = resolveFacetScaleDomains(
|
|
169
199
|
template.semanticSpec,
|
|
@@ -2,33 +2,25 @@ import { action } from "../../core/action.js";
|
|
|
2
2
|
import { isPlainObject } from "../../core/immutable.js";
|
|
3
3
|
import { validateUserId } from "../../core/identifiers.js";
|
|
4
4
|
import { validateKeys } from "../../core/validation.js";
|
|
5
|
+
import { findTransformPolicy } from "../../grammar/transforms.js";
|
|
5
6
|
import { hasDataset } from "../../selectors/datasets.js";
|
|
6
|
-
import { requireLayer } from "../../selectors/layers.js";
|
|
7
7
|
|
|
8
8
|
const REPLAY_OPTIONS = Object.freeze(["id", "source", "transform"]);
|
|
9
|
-
const REBIND_OPTIONS = Object.freeze(["id", "data"]);
|
|
10
|
-
const MATERIALIZERS = Object.freeze({
|
|
11
|
-
filter: "materializeFilteredData",
|
|
12
|
-
regression: "materializeRegressionData",
|
|
13
|
-
density: "materializeDensityData",
|
|
14
|
-
interval: "materializeIntervalData",
|
|
15
|
-
boxSummary: "materializeBoxSummaryData",
|
|
16
|
-
boxOutlier: "materializeBoxOutlierData"
|
|
17
|
-
});
|
|
18
9
|
|
|
19
10
|
function requestedTransform(transform) {
|
|
20
11
|
if (!isPlainObject(transform)) {
|
|
21
12
|
throw new TypeError("replayDerivedData transform must be a plain object.");
|
|
22
13
|
}
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
14
|
+
const policy = findTransformPolicy(transform.type);
|
|
15
|
+
if (policy?.facetTopology === undefined) {
|
|
25
16
|
throw new Error(
|
|
26
17
|
`replayDerivedData does not support transform "${transform.type ?? "unknown"}".`
|
|
27
18
|
);
|
|
28
19
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
return {
|
|
21
|
+
transform: policy.replayTransform?.(transform) ?? transform,
|
|
22
|
+
materialize: policy.materializeOp
|
|
23
|
+
};
|
|
32
24
|
}
|
|
33
25
|
|
|
34
26
|
export const replayDerivedData = action(
|
|
@@ -46,24 +38,3 @@ export const replayDerivedData = action(
|
|
|
46
38
|
[resolved.materialize]({ id });
|
|
47
39
|
}
|
|
48
40
|
);
|
|
49
|
-
|
|
50
|
-
export const rebindLayerData = action(
|
|
51
|
-
{
|
|
52
|
-
op: "rebindLayerData",
|
|
53
|
-
description: "Rebind one facet-cell layer to its replayed dataset."
|
|
54
|
-
},
|
|
55
|
-
function (args = {}) {
|
|
56
|
-
validateKeys(args, REBIND_OPTIONS, "rebindLayerData");
|
|
57
|
-
const id = validateUserId(args.id, "Facet layer id");
|
|
58
|
-
const data = validateUserId(args.data, "Facet layer dataset id");
|
|
59
|
-
requireLayer(this, id);
|
|
60
|
-
if (!hasDataset(this, data)) {
|
|
61
|
-
throw new Error(`Facet layer dataset "${data}" does not exist.`);
|
|
62
|
-
}
|
|
63
|
-
return this.editSemantic({
|
|
64
|
-
property: `layer[${id}].data`,
|
|
65
|
-
value: data
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
);
|
|
69
|
-
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateKeys } from "../../core/validation.js";
|
|
3
|
+
import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
|
|
4
|
+
import { resolveGraphicBounds } from "../../layout/canvas.js";
|
|
5
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
6
|
+
import { createDensityLegendPaint } from "./paint.js";
|
|
7
|
+
|
|
8
|
+
const CENTER_OPTIONS = Object.freeze([
|
|
9
|
+
"id", "owner", "data", "category", "categoryType", "coordinate",
|
|
10
|
+
"categoryScale", "measureScale", "orientation", "size", "stroke",
|
|
11
|
+
"strokeWidth"
|
|
12
|
+
]);
|
|
13
|
+
const LEGEND_OPTIONS = Object.freeze(["owner", "title", "position"]);
|
|
14
|
+
|
|
15
|
+
export const createGradientPlotCenter = action(
|
|
16
|
+
{
|
|
17
|
+
op: "createGradientPlotCenter",
|
|
18
|
+
description: "Create the optional center rule for one gradient plot."
|
|
19
|
+
},
|
|
20
|
+
function (args = {}) {
|
|
21
|
+
validateKeys(args, CENTER_OPTIONS, "createGradientPlotCenter");
|
|
22
|
+
const categoryAction = args.orientation === "vertical" ? "encodeX" : "encodeY";
|
|
23
|
+
const measureAction = args.orientation === "vertical" ? "encodeY" : "encodeX";
|
|
24
|
+
const spanOrientation = args.orientation === "vertical" ? "horizontal" : "vertical";
|
|
25
|
+
let next = this.createRuleMark({ id: args.id, data: args.data });
|
|
26
|
+
next = next[categoryAction]({
|
|
27
|
+
target: args.id,
|
|
28
|
+
field: args.category,
|
|
29
|
+
fieldType: args.categoryType,
|
|
30
|
+
coordinate: args.coordinate,
|
|
31
|
+
scale: { id: args.categoryScale }
|
|
32
|
+
});
|
|
33
|
+
next = next[measureAction]({
|
|
34
|
+
target: args.id,
|
|
35
|
+
field: GRADIENT_PROFILE_FIELDS.center,
|
|
36
|
+
fieldType: "quantitative",
|
|
37
|
+
coordinate: args.coordinate,
|
|
38
|
+
scale: { id: args.measureScale }
|
|
39
|
+
});
|
|
40
|
+
return next
|
|
41
|
+
.encodeStroke({ target: args.id, value: args.stroke })
|
|
42
|
+
.encodeStrokeWidth({ target: args.id, value: args.strokeWidth })
|
|
43
|
+
.materializeRuleSpan({
|
|
44
|
+
id: args.id,
|
|
45
|
+
orientation: spanOrientation,
|
|
46
|
+
size: args.size
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
function legendIds(owner) {
|
|
52
|
+
return {
|
|
53
|
+
strip: `${owner}DensityLegend`,
|
|
54
|
+
labels: `${owner}DensityLegendLabels`,
|
|
55
|
+
title: `${owner}DensityLegendTitle`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function requireLegendOwner(program, owner) {
|
|
60
|
+
const layer = findLayer(program, owner);
|
|
61
|
+
const config = program.markConfigs[owner]?.gradientPlot;
|
|
62
|
+
if (layer === undefined || config?.materialized !== true) {
|
|
63
|
+
throw new Error(`Unknown gradient-plot legend owner "${owner}".`);
|
|
64
|
+
}
|
|
65
|
+
return { layer, config };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const rematerializeGradientPlotLegend = action(
|
|
69
|
+
{
|
|
70
|
+
op: "rematerializeGradientPlotLegend",
|
|
71
|
+
description: "Rematerialize one gradient plot density legend."
|
|
72
|
+
},
|
|
73
|
+
function ({ owner } = {}) {
|
|
74
|
+
const { config } = requireLegendOwner(this, owner);
|
|
75
|
+
const ids = legendIds(owner);
|
|
76
|
+
const bounds = resolveGraphicBounds(this);
|
|
77
|
+
const position = config.guides.legend.position;
|
|
78
|
+
if (position !== "right") {
|
|
79
|
+
throw new Error('Gradient plot density legend currently requires position "right".');
|
|
80
|
+
}
|
|
81
|
+
const x = bounds.x + bounds.width + 50;
|
|
82
|
+
const y = bounds.y + 50;
|
|
83
|
+
const width = 22;
|
|
84
|
+
const height = Math.min(170, Math.max(80, bounds.height - 110));
|
|
85
|
+
return this
|
|
86
|
+
.editGraphics({ target: ids.strip, property: "x", value: x })
|
|
87
|
+
.editGraphics({ target: ids.strip, property: "y", value: y })
|
|
88
|
+
.editGraphics({ target: ids.strip, property: "width", value: width })
|
|
89
|
+
.editGraphics({ target: ids.strip, property: "height", value: height })
|
|
90
|
+
.editGraphics({
|
|
91
|
+
target: ids.strip,
|
|
92
|
+
property: "fill",
|
|
93
|
+
value: createDensityLegendPaint(config.gradient.opacity)
|
|
94
|
+
})
|
|
95
|
+
.editGraphics({ target: ids.strip, property: "stroke", value: "#cbd5e1" })
|
|
96
|
+
.editGraphics({ target: ids.strip, property: "strokeWidth", value: 1 })
|
|
97
|
+
.editGraphics({ target: ids.labels, property: "x", value: x + width + 10 })
|
|
98
|
+
.editGraphics({ target: ids.labels, property: "y", value: [y + height, y] })
|
|
99
|
+
.editGraphics({ target: ids.labels, property: "text", value: ["Low", "High"] })
|
|
100
|
+
.editGraphics({ target: ids.labels, property: "fill", value: "#64748b" })
|
|
101
|
+
.editGraphics({ target: ids.labels, property: "fontSize", value: 11 })
|
|
102
|
+
.editGraphics({ target: ids.labels, property: "fontFamily", value: "sans-serif" })
|
|
103
|
+
.editGraphics({ target: ids.labels, property: "textAlign", value: "left" })
|
|
104
|
+
.editGraphics({ target: ids.labels, property: "textBaseline", value: "middle" })
|
|
105
|
+
.editGraphics({ target: ids.title, property: "x", value: x })
|
|
106
|
+
.editGraphics({ target: ids.title, property: "y", value: y - 18 })
|
|
107
|
+
.editGraphics({ target: ids.title, property: "text", value: config.guides.legend.title })
|
|
108
|
+
.editGraphics({ target: ids.title, property: "fill", value: "#334155" })
|
|
109
|
+
.editGraphics({ target: ids.title, property: "fontSize", value: 12 })
|
|
110
|
+
.editGraphics({ target: ids.title, property: "fontFamily", value: "sans-serif" })
|
|
111
|
+
.editGraphics({ target: ids.title, property: "fontWeight", value: 600 })
|
|
112
|
+
.editGraphics({ target: ids.title, property: "textAlign", value: "left" })
|
|
113
|
+
.editGraphics({ target: ids.title, property: "textBaseline", value: "middle" });
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
export const createGradientPlotLegend = action(
|
|
118
|
+
{
|
|
119
|
+
op: "createGradientPlotLegend",
|
|
120
|
+
description: "Create the neutral density legend owned by one gradient plot."
|
|
121
|
+
},
|
|
122
|
+
function (args = {}) {
|
|
123
|
+
validateKeys(args, LEGEND_OPTIONS, "createGradientPlotLegend");
|
|
124
|
+
requireLegendOwner(this, args.owner);
|
|
125
|
+
if (args.position !== undefined && args.position !== "right") {
|
|
126
|
+
throw new Error('Gradient plot density legend currently requires position "right".');
|
|
127
|
+
}
|
|
128
|
+
if (args.title !== undefined && (typeof args.title !== "string" || args.title.length === 0)) {
|
|
129
|
+
throw new TypeError("Gradient plot density legend title must be a non-empty string.");
|
|
130
|
+
}
|
|
131
|
+
const ids = legendIds(args.owner);
|
|
132
|
+
const densityScale = `${args.owner}Density`;
|
|
133
|
+
let next = this
|
|
134
|
+
.editSemantic({ property: `scale[${densityScale}].type`, value: "sequential" })
|
|
135
|
+
.editSemantic({
|
|
136
|
+
property: `scale[${densityScale}].domain`,
|
|
137
|
+
value: this.markConfigs[args.owner].gradientPlot.intensityDomain
|
|
138
|
+
})
|
|
139
|
+
.editSemantic({
|
|
140
|
+
property: `scale[${densityScale}].range`,
|
|
141
|
+
value: { palette: { name: "greys" } }
|
|
142
|
+
})
|
|
143
|
+
.editSemantic({ property: "guide.legend.color.scale", value: densityScale })
|
|
144
|
+
.editSemantic({
|
|
145
|
+
property: "guide.legend.color.title",
|
|
146
|
+
value: args.title ?? "Relative density"
|
|
147
|
+
})
|
|
148
|
+
.createGraphics({ id: ids.strip, type: "rect", parent: "canvas" })
|
|
149
|
+
.createGraphics({ id: ids.labels, type: "text", length: 2, parent: "canvas" })
|
|
150
|
+
.createGraphics({ id: ids.title, type: "text", parent: "canvas" });
|
|
151
|
+
next = next._withMarkConfig(args.owner, {
|
|
152
|
+
...next.markConfigs[args.owner],
|
|
153
|
+
gradientPlot: {
|
|
154
|
+
...next.markConfigs[args.owner].gradientPlot,
|
|
155
|
+
guides: {
|
|
156
|
+
...next.markConfigs[args.owner].gradientPlot.guides,
|
|
157
|
+
legend: {
|
|
158
|
+
title: args.title ?? "Relative density",
|
|
159
|
+
position: args.position ?? "right"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return next.rematerializeGradientPlotLegend({ owner: args.owner });
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
export function removeGradientPlotLegend(program, owner) {
|
|
169
|
+
const ids = legendIds(owner);
|
|
170
|
+
let next = program;
|
|
171
|
+
for (const id of Object.values(ids)) {
|
|
172
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
173
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return next;
|
|
177
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateKeys } from "../../core/validation.js";
|
|
3
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
4
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
5
|
+
import {
|
|
6
|
+
GRADIENT_PLOT_OPTIONS,
|
|
7
|
+
gradientEncodingArgs,
|
|
8
|
+
resolveGradientAppearance,
|
|
9
|
+
resolveGradientCenter,
|
|
10
|
+
resolveGradientDensity,
|
|
11
|
+
resolveGradientGuides,
|
|
12
|
+
resolveGradientPosition,
|
|
13
|
+
resolveGradientWidth
|
|
14
|
+
} from "./options.js";
|
|
15
|
+
import {
|
|
16
|
+
normalizeGradientPositionTypes,
|
|
17
|
+
resolveGradientPlotId,
|
|
18
|
+
resolveGradientSourceLayer
|
|
19
|
+
} from "./resolve.js";
|
|
20
|
+
|
|
21
|
+
export const createGradientPlot = action(
|
|
22
|
+
{
|
|
23
|
+
op: "createGradientPlot",
|
|
24
|
+
description: "Create a categorical density gradient plot."
|
|
25
|
+
},
|
|
26
|
+
function (args = {}) {
|
|
27
|
+
validateKeys(args, GRADIENT_PLOT_OPTIONS, "createGradientPlot");
|
|
28
|
+
const id = resolveGradientPlotId(this, args.id);
|
|
29
|
+
const source = resolveGradientSourceLayer(this, args.target);
|
|
30
|
+
const data = args.data ?? source?.data ?? this.context.currentData ??
|
|
31
|
+
(this.semanticSpec.datasets.length === 1
|
|
32
|
+
? this.semanticSpec.datasets[0].id
|
|
33
|
+
: undefined);
|
|
34
|
+
if (findDataset(this, data) === undefined) {
|
|
35
|
+
throw new Error("createGradientPlot requires data or one inferable dataset.");
|
|
36
|
+
}
|
|
37
|
+
const positions = normalizeGradientPositionTypes(
|
|
38
|
+
resolveGradientPosition(args.x, "x") ?? source?.encoding?.x,
|
|
39
|
+
resolveGradientPosition(args.y, "y") ?? source?.encoding?.y
|
|
40
|
+
);
|
|
41
|
+
if (
|
|
42
|
+
positions.x !== undefined && positions.y !== undefined &&
|
|
43
|
+
positions.orientation === undefined
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"createGradientPlot requires one categorical axis and one quantitative axis."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const density = resolveGradientDensity(args.density);
|
|
50
|
+
const width = resolveGradientWidth(args.width);
|
|
51
|
+
const gradient = resolveGradientAppearance(args.gradient);
|
|
52
|
+
const center = resolveGradientCenter(args.center);
|
|
53
|
+
const guides = resolveGradientGuides(args.guides);
|
|
54
|
+
const category = positions.orientation === "vertical"
|
|
55
|
+
? positions.x
|
|
56
|
+
: positions.orientation === "horizontal"
|
|
57
|
+
? positions.y
|
|
58
|
+
: undefined;
|
|
59
|
+
const categoryScaleId = typeof category?.scale === "string"
|
|
60
|
+
? category.scale
|
|
61
|
+
: category?.scale?.id;
|
|
62
|
+
const categoryScale = findSemanticScale(this, categoryScaleId);
|
|
63
|
+
let next = categoryScale?.type === "point"
|
|
64
|
+
? this.editScale({ id: categoryScaleId, type: "band" })
|
|
65
|
+
: this;
|
|
66
|
+
next = next.createRectMark({ id, data })._withMarkConfig(id, {
|
|
67
|
+
...this.markConfigs[id],
|
|
68
|
+
gradientPlot: {
|
|
69
|
+
materialized: false,
|
|
70
|
+
density,
|
|
71
|
+
width,
|
|
72
|
+
gradient,
|
|
73
|
+
center,
|
|
74
|
+
guides
|
|
75
|
+
},
|
|
76
|
+
itemFilterable: false,
|
|
77
|
+
stroke: false,
|
|
78
|
+
strokeWidth: 0
|
|
79
|
+
});
|
|
80
|
+
if (positions.x !== undefined) {
|
|
81
|
+
next = next.encodeX({
|
|
82
|
+
...gradientEncodingArgs(positions.x),
|
|
83
|
+
target: id,
|
|
84
|
+
coordinate: args.coordinate ?? source?.coordinate
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (positions.y !== undefined) {
|
|
88
|
+
next = next.encodeY({
|
|
89
|
+
...gradientEncodingArgs(positions.y),
|
|
90
|
+
target: id,
|
|
91
|
+
coordinate: args.coordinate ?? source?.coordinate
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return next.materializeGradientPlot({ id });
|
|
95
|
+
}
|
|
96
|
+
);
|