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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { isPlainObject } from "../../../core/immutable.js";
|
|
2
|
+
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
3
|
+
|
|
4
|
+
export function requireDensityField(value, label) {
|
|
5
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
6
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveDensityScaleOptions(value, defaults, label) {
|
|
12
|
+
if (value !== undefined && !isPlainObject(value)) {
|
|
13
|
+
throw new TypeError(`${label} must be a plain object.`);
|
|
14
|
+
}
|
|
15
|
+
return { ...defaults, ...(value ?? {}) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveDensityCategoryScaleOptions(value, fallbackId) {
|
|
19
|
+
const scale = resolveDensityScaleOptions(
|
|
20
|
+
value,
|
|
21
|
+
{ type: "band" },
|
|
22
|
+
"Density placement scale"
|
|
23
|
+
);
|
|
24
|
+
if ((scale.type ?? "band") !== "band") {
|
|
25
|
+
throw new Error('Density category placement requires scale type "band".');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...(fallbackId === undefined ? {} : { id: fallbackId }),
|
|
29
|
+
...scale,
|
|
30
|
+
type: "band"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function valueScaleForTransition(program, layer, transform) {
|
|
35
|
+
const densityChannel = transform.placement?.channel ?? (
|
|
36
|
+
layer.encoding.x.field === transform.as[1] ? "x" : "y"
|
|
37
|
+
);
|
|
38
|
+
const valueChannel = densityChannel === "x" ? "y" : "x";
|
|
39
|
+
return findSemanticScale(program, layer.encoding[valueChannel].scale);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveDensityTransitionScaleDefinitions(
|
|
43
|
+
program,
|
|
44
|
+
layer,
|
|
45
|
+
transform,
|
|
46
|
+
placement,
|
|
47
|
+
rawPlacement
|
|
48
|
+
) {
|
|
49
|
+
const valueScale = valueScaleForTransition(program, layer, transform);
|
|
50
|
+
if (valueScale === undefined) {
|
|
51
|
+
throw new Error(`Density area "${layer.id}" requires its value scale.`);
|
|
52
|
+
}
|
|
53
|
+
const { id: _valueId, range: _valueRange, ...valueDefinition } = valueScale;
|
|
54
|
+
void _valueId;
|
|
55
|
+
void _valueRange;
|
|
56
|
+
const valueChannel = placement?.channel === "x" ? "y" : "x";
|
|
57
|
+
const companionChannel = valueChannel === "x" ? "y" : "x";
|
|
58
|
+
const companionDefinition = placement === undefined
|
|
59
|
+
? { type: "linear", domain: "auto", range: "auto", nice: true, zero: true }
|
|
60
|
+
: resolveDensityCategoryScaleOptions(rawPlacement.scale);
|
|
61
|
+
return {
|
|
62
|
+
[valueChannel]: { ...valueDefinition, range: "auto" },
|
|
63
|
+
[companionChannel]: companionDefinition
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveDensityPositionDefinition({
|
|
68
|
+
layer,
|
|
69
|
+
output,
|
|
70
|
+
groupBy,
|
|
71
|
+
placement,
|
|
72
|
+
coordinate,
|
|
73
|
+
valueScale,
|
|
74
|
+
densityScale,
|
|
75
|
+
placementScale
|
|
76
|
+
}) {
|
|
77
|
+
if (placement === undefined) {
|
|
78
|
+
const xIsDensity = layer.densityChannel === "x";
|
|
79
|
+
return {
|
|
80
|
+
x: {
|
|
81
|
+
field: output[xIsDensity ? 1 : 0],
|
|
82
|
+
fieldType: "quantitative",
|
|
83
|
+
scale: xIsDensity ? densityScale : valueScale,
|
|
84
|
+
coordinate
|
|
85
|
+
},
|
|
86
|
+
y: {
|
|
87
|
+
field: output[xIsDensity ? 0 : 1],
|
|
88
|
+
fieldType: "quantitative",
|
|
89
|
+
scale: xIsDensity ? valueScale : densityScale,
|
|
90
|
+
coordinate
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const category = {
|
|
95
|
+
field: placement.categoryField,
|
|
96
|
+
fieldType: "nominal",
|
|
97
|
+
scale: placementScale,
|
|
98
|
+
coordinate
|
|
99
|
+
};
|
|
100
|
+
const value = {
|
|
101
|
+
field: output[0],
|
|
102
|
+
fieldType: "quantitative",
|
|
103
|
+
scale: valueScale,
|
|
104
|
+
coordinate
|
|
105
|
+
};
|
|
106
|
+
return placement.channel === "x"
|
|
107
|
+
? { x: category, y: value }
|
|
108
|
+
: { x: value, y: category };
|
|
109
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
-
import { isPlainObject } from "../../core/immutable.js";
|
|
4
3
|
import {
|
|
5
4
|
findDataset,
|
|
6
5
|
findLayer,
|
|
@@ -11,15 +10,28 @@ import { applyMaterializationPlan } from
|
|
|
11
10
|
"../../materialization/dependencies.js";
|
|
12
11
|
import { planDensityRematerialization } from
|
|
13
12
|
"../../materialization/density.js";
|
|
13
|
+
import { planDerivedDataRevision } from
|
|
14
|
+
"../../materialization/dataProvenance.js";
|
|
14
15
|
import { validateOptions } from "./shared.js";
|
|
16
|
+
import { normalizeDensityPlacement } from "../../grammar/density.js";
|
|
17
|
+
import { scaleEditPatch } from "../scales/patch.js";
|
|
18
|
+
import { removeLegendKinds } from "../guides/legends/remove.js";
|
|
19
|
+
import {
|
|
20
|
+
requireDensityField,
|
|
21
|
+
resolveDensityCategoryScaleOptions,
|
|
22
|
+
resolveDensityPositionDefinition,
|
|
23
|
+
resolveDensityScaleOptions,
|
|
24
|
+
resolveDensityTransitionScaleDefinitions
|
|
25
|
+
} from "./density/resolve.js";
|
|
15
26
|
|
|
16
27
|
const OPTIONS = Object.freeze([
|
|
17
28
|
"field", "target", "source", "groupBy", "bandwidth", "extent", "steps",
|
|
18
29
|
"kernel", "normalization", "as", "densityChannel", "coordinate",
|
|
19
|
-
"valueScale", "densityScale"
|
|
30
|
+
"valueScale", "densityScale", "placement"
|
|
20
31
|
]);
|
|
21
32
|
const EDIT_OPTIONS = Object.freeze([
|
|
22
|
-
"target", "
|
|
33
|
+
"target", "source", "field", "groupBy", "bandwidth", "extent", "steps",
|
|
34
|
+
"kernel", "normalization", "placement"
|
|
23
35
|
]);
|
|
24
36
|
const EDITABLE = Object.freeze(EDIT_OPTIONS.filter(option => option !== "target"));
|
|
25
37
|
|
|
@@ -42,32 +54,35 @@ function findArea(program, requested) {
|
|
|
42
54
|
throw new Error("encodeDensity target is ambiguous; provide target.");
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
function applyDensityPosition(program, layerId, definition, groupBy) {
|
|
58
|
+
const withCoordinate = value => ({
|
|
59
|
+
target: layerId,
|
|
60
|
+
field: value.field,
|
|
61
|
+
fieldType: value.fieldType,
|
|
62
|
+
scale: value.scale,
|
|
63
|
+
...(value.coordinate === undefined ? {} : { coordinate: value.coordinate })
|
|
64
|
+
});
|
|
65
|
+
let next = program
|
|
66
|
+
.encodeX(withCoordinate(definition.x))
|
|
67
|
+
.encodeY(withCoordinate(definition.y));
|
|
68
|
+
if (groupBy !== undefined) {
|
|
69
|
+
next = next.encodeGroup({ target: layerId, field: groupBy });
|
|
55
70
|
}
|
|
56
|
-
return
|
|
71
|
+
return next;
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
const encodeDensity = action(
|
|
60
75
|
{
|
|
61
76
|
op: "encodeDensity",
|
|
62
|
-
description: "Derive and encode a baseline
|
|
77
|
+
description: "Derive and encode a baseline or categorical kernel-density area."
|
|
63
78
|
},
|
|
64
79
|
function (args = {}) {
|
|
65
80
|
validateOptions(args, OPTIONS, "encodeDensity");
|
|
66
81
|
const layer = findArea(this, args.target);
|
|
67
|
-
const field =
|
|
82
|
+
const field = requireDensityField(args.field, "Density field");
|
|
68
83
|
const groupBy = args.groupBy === undefined
|
|
69
84
|
? undefined
|
|
70
|
-
:
|
|
85
|
+
: requireDensityField(args.groupBy, "Density groupBy");
|
|
71
86
|
const source = validateUserId(
|
|
72
87
|
args.source ?? layer.data,
|
|
73
88
|
"Density source dataset id"
|
|
@@ -82,7 +97,8 @@ const encodeDensity = action(
|
|
|
82
97
|
`Density area target "${layer.id}" already has positional or group encodings.`
|
|
83
98
|
);
|
|
84
99
|
}
|
|
85
|
-
const
|
|
100
|
+
const categoryPlacement = args.placement?.type === "category";
|
|
101
|
+
const densityChannel = args.densityChannel ?? (categoryPlacement ? "x" : "y");
|
|
86
102
|
if (!["x", "y"].includes(densityChannel)) {
|
|
87
103
|
throw new Error(`Unsupported densityChannel "${densityChannel}".`);
|
|
88
104
|
}
|
|
@@ -94,56 +110,67 @@ const encodeDensity = action(
|
|
|
94
110
|
if (hasDataset(this, derivedId)) {
|
|
95
111
|
throw new Error(`Dataset "${derivedId}" already exists.`);
|
|
96
112
|
}
|
|
97
|
-
const valueScale =
|
|
113
|
+
const valueScale = resolveDensityScaleOptions(
|
|
98
114
|
args.valueScale,
|
|
99
115
|
{ nice: false, zero: false },
|
|
100
116
|
"Density valueScale"
|
|
101
117
|
);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
const placement = args.placement === undefined
|
|
119
|
+
? undefined
|
|
120
|
+
: normalizeDensityPlacement(args.placement, {
|
|
121
|
+
densityChannel,
|
|
122
|
+
groupBy,
|
|
123
|
+
categoryField: groupBy ?? `${layer.id}DensityCategory`
|
|
124
|
+
});
|
|
125
|
+
if (placement !== undefined && args.densityScale !== undefined) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"Density category placement cannot be combined with densityScale."
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const densityScale = placement === undefined
|
|
131
|
+
? resolveDensityScaleOptions(
|
|
132
|
+
args.densityScale,
|
|
133
|
+
{ nice: true, zero: true },
|
|
134
|
+
"Density densityScale"
|
|
135
|
+
)
|
|
136
|
+
: undefined;
|
|
137
|
+
const placementScale = placement === undefined
|
|
138
|
+
? undefined
|
|
139
|
+
: resolveDensityCategoryScaleOptions(args.placement.scale);
|
|
112
140
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
const dataArgs = {
|
|
142
|
+
id: derivedId,
|
|
143
|
+
source,
|
|
144
|
+
field,
|
|
145
|
+
...(groupBy === undefined ? {} : { groupBy }),
|
|
146
|
+
...(args.bandwidth === undefined ? {} : { bandwidth: args.bandwidth }),
|
|
147
|
+
...(args.extent === undefined ? {} : { extent: args.extent }),
|
|
148
|
+
...(args.steps === undefined ? {} : { steps: args.steps }),
|
|
149
|
+
...(args.kernel === undefined ? {} : { kernel: args.kernel }),
|
|
150
|
+
...(args.normalization === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { normalization: args.normalization }),
|
|
153
|
+
...(args.as === undefined ? {} : { as: args.as }),
|
|
154
|
+
...(placement === undefined ? {} : { placement })
|
|
155
|
+
};
|
|
156
|
+
let next = placement === undefined
|
|
157
|
+
? this.createDensityData(dataArgs)
|
|
158
|
+
: this.createCategoricalDensityData(dataArgs);
|
|
159
|
+
next = next
|
|
128
160
|
.editSemantic({
|
|
129
161
|
property: `layer[${layer.id}].data`,
|
|
130
162
|
value: derivedId
|
|
131
|
-
})
|
|
132
|
-
.encodeX({
|
|
133
|
-
target: layer.id,
|
|
134
|
-
field: xField,
|
|
135
|
-
...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
|
|
136
|
-
scale: xScale
|
|
137
|
-
})
|
|
138
|
-
.encodeY({
|
|
139
|
-
target: layer.id,
|
|
140
|
-
field: yField,
|
|
141
|
-
...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
|
|
142
|
-
scale: yScale
|
|
143
163
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
next = applyDensityPosition(next, layer.id, resolveDensityPositionDefinition({
|
|
165
|
+
layer: { densityChannel },
|
|
166
|
+
output,
|
|
167
|
+
groupBy,
|
|
168
|
+
placement,
|
|
169
|
+
coordinate: args.coordinate,
|
|
170
|
+
valueScale,
|
|
171
|
+
densityScale,
|
|
172
|
+
placementScale
|
|
173
|
+
}), groupBy);
|
|
147
174
|
return next.rematerializeAreaMark({ id: layer.id });
|
|
148
175
|
}
|
|
149
176
|
);
|
|
@@ -164,12 +191,41 @@ function findDensityArea(program, requested) {
|
|
|
164
191
|
});
|
|
165
192
|
}
|
|
166
193
|
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
function densityCategoryField(layer, transform, groupBy) {
|
|
195
|
+
if (transform.placement === undefined) return undefined;
|
|
196
|
+
if (transform.placement.categoryField !== transform.groupBy) {
|
|
197
|
+
return transform.placement.categoryField;
|
|
171
198
|
}
|
|
172
|
-
return `${
|
|
199
|
+
return groupBy ?? `${layer.id}DensityCategory`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function assertGroupingSelectionCompatibility(program, layer, changesGroup) {
|
|
203
|
+
if (!changesGroup) return;
|
|
204
|
+
const referenced = Object.entries(
|
|
205
|
+
program.materializationConfigs.selections ?? {}
|
|
206
|
+
).find(([, selection]) =>
|
|
207
|
+
selection.target === layer.id &&
|
|
208
|
+
["group", "color"].includes(selection.selector?.channel)
|
|
209
|
+
);
|
|
210
|
+
if (referenced !== undefined) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`editDensity cannot change groupBy while selection "${referenced[0]}" ` +
|
|
213
|
+
`references ${referenced[1].selector.channel}.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function removeOwnedColorLegends(program, target) {
|
|
219
|
+
const kinds = Object.entries(program.guideConfigs.legend ?? {})
|
|
220
|
+
.filter(([kind, config]) =>
|
|
221
|
+
config?.target === target &&
|
|
222
|
+
(
|
|
223
|
+
config.channels?.includes("color") ||
|
|
224
|
+
["gradient", "interval"].includes(kind)
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
.map(([kind]) => kind);
|
|
228
|
+
return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
|
|
173
229
|
}
|
|
174
230
|
|
|
175
231
|
const editDensity = action(
|
|
@@ -185,37 +241,221 @@ const editDensity = action(
|
|
|
185
241
|
const layer = findDensityArea(this, args.target);
|
|
186
242
|
const previous = findDataset(this, layer.data);
|
|
187
243
|
const transform = previous.transform[0];
|
|
188
|
-
const
|
|
244
|
+
const source = Object.hasOwn(args, "source")
|
|
245
|
+
? validateUserId(args.source, "Density source dataset id")
|
|
246
|
+
: previous.source;
|
|
247
|
+
const field = Object.hasOwn(args, "field")
|
|
248
|
+
? requireDensityField(args.field, "Density field")
|
|
249
|
+
: transform.field;
|
|
250
|
+
const groupBy = Object.hasOwn(args, "groupBy")
|
|
251
|
+
? args.groupBy === false
|
|
252
|
+
? undefined
|
|
253
|
+
: requireDensityField(args.groupBy, "Density groupBy")
|
|
254
|
+
: transform.groupBy;
|
|
255
|
+
const changesGroup = groupBy !== transform.groupBy;
|
|
256
|
+
assertGroupingSelectionCompatibility(this, layer, changesGroup);
|
|
257
|
+
const categoryField = densityCategoryField(layer, transform, groupBy);
|
|
258
|
+
const requestedPlacement = Object.hasOwn(args, "placement")
|
|
259
|
+
? normalizeDensityPlacement(args.placement, {
|
|
260
|
+
densityChannel: transform.placement?.channel ?? "x",
|
|
261
|
+
groupBy,
|
|
262
|
+
categoryField: categoryField ?? groupBy ??
|
|
263
|
+
`${layer.id}DensityCategory`
|
|
264
|
+
})
|
|
265
|
+
: transform.placement === undefined || !changesGroup
|
|
266
|
+
? transform.placement
|
|
267
|
+
: normalizeDensityPlacement({
|
|
268
|
+
type: "category",
|
|
269
|
+
...(transform.placement.side === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: { side: transform.placement.side }),
|
|
272
|
+
width: transform.placement.width,
|
|
273
|
+
...(transform.placement.split === undefined
|
|
274
|
+
? {}
|
|
275
|
+
: { split: transform.placement.split })
|
|
276
|
+
}, {
|
|
277
|
+
densityChannel: transform.placement.channel,
|
|
278
|
+
groupBy,
|
|
279
|
+
categoryField
|
|
280
|
+
});
|
|
281
|
+
const colorField = layer.encoding?.color?.field;
|
|
282
|
+
const colorFollowsGroup = transform.groupBy !== undefined &&
|
|
283
|
+
colorField === transform.groupBy;
|
|
284
|
+
const availableColorFields = [
|
|
285
|
+
groupBy,
|
|
286
|
+
requestedPlacement?.split?.field
|
|
287
|
+
].filter(Boolean);
|
|
288
|
+
if (
|
|
289
|
+
colorField !== undefined &&
|
|
290
|
+
!colorFollowsGroup &&
|
|
291
|
+
!availableColorFields.includes(colorField)
|
|
292
|
+
) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`editDensity placement would remove color field "${colorField}" from the density series.`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const revision = planDerivedDataRevision(this, {
|
|
298
|
+
owner: layer.id,
|
|
299
|
+
role: "DensityData",
|
|
300
|
+
previous: previous.id,
|
|
301
|
+
consumers: [layer.id]
|
|
302
|
+
});
|
|
189
303
|
const option = property => Object.hasOwn(args, property)
|
|
190
304
|
? args[property]
|
|
191
305
|
: transform[property];
|
|
192
306
|
|
|
193
|
-
|
|
194
|
-
.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
307
|
+
const dataArgs = {
|
|
308
|
+
id: revision.id,
|
|
309
|
+
source,
|
|
310
|
+
field,
|
|
311
|
+
...(groupBy === undefined
|
|
312
|
+
? {}
|
|
313
|
+
: { groupBy }),
|
|
314
|
+
bandwidth: option("bandwidth"),
|
|
315
|
+
extent: option("extent"),
|
|
316
|
+
steps: option("steps"),
|
|
317
|
+
kernel: option("kernel") ?? "gaussian",
|
|
318
|
+
normalization: option("normalization") ?? "unit",
|
|
319
|
+
as: transform.as,
|
|
320
|
+
...(requestedPlacement === undefined
|
|
321
|
+
? {}
|
|
322
|
+
: { placement: requestedPlacement })
|
|
323
|
+
};
|
|
324
|
+
const changesPlacementMode = Object.hasOwn(args, "placement") &&
|
|
325
|
+
(transform.placement === undefined) !== (requestedPlacement === undefined);
|
|
326
|
+
const applyEdit = program => {
|
|
327
|
+
let next = requestedPlacement === undefined
|
|
328
|
+
? program.createDensityData(dataArgs)
|
|
329
|
+
: program.createCategoricalDensityData(dataArgs);
|
|
330
|
+
next = next.rebindLayerData(revision.rebinds[0])
|
|
331
|
+
.releaseDerivedData(revision.release);
|
|
213
332
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
333
|
+
if (changesGroup) {
|
|
334
|
+
if (groupBy === undefined) {
|
|
335
|
+
if (findLayer(next, layer.id).encoding?.group !== undefined) {
|
|
336
|
+
next = next.editSemantic({
|
|
337
|
+
property: `layer[${layer.id}].encoding.group`,
|
|
338
|
+
remove: true
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
if (colorFollowsGroup) {
|
|
342
|
+
next = next.editSemantic({
|
|
343
|
+
property: `layer[${layer.id}].encoding.color`,
|
|
344
|
+
remove: true
|
|
345
|
+
});
|
|
346
|
+
next = removeOwnedColorLegends(next, layer.id);
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
next = next
|
|
350
|
+
.editSemantic({
|
|
351
|
+
property: `layer[${layer.id}].encoding.group.field`,
|
|
352
|
+
value: groupBy
|
|
353
|
+
})
|
|
354
|
+
.editSemantic({
|
|
355
|
+
property: `layer[${layer.id}].encoding.group.fieldType`,
|
|
356
|
+
value: "nominal"
|
|
357
|
+
});
|
|
358
|
+
if (colorFollowsGroup) {
|
|
359
|
+
next = next.editSemantic({
|
|
360
|
+
property: `layer[${layer.id}].encoding.color.field`,
|
|
361
|
+
value: groupBy
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (
|
|
366
|
+
requestedPlacement !== undefined &&
|
|
367
|
+
transform.placement?.categoryField !== requestedPlacement.categoryField
|
|
368
|
+
) {
|
|
369
|
+
next = next.editSemantic({
|
|
370
|
+
property: `layer[${layer.id}].encoding.${requestedPlacement.channel}.field`,
|
|
371
|
+
value: requestedPlacement.categoryField
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (changesPlacementMode) {
|
|
377
|
+
const xScaleId = layer.encoding?.x?.scale;
|
|
378
|
+
const yScaleId = layer.encoding?.y?.scale;
|
|
379
|
+
const scaleDefinitions = resolveDensityTransitionScaleDefinitions(
|
|
380
|
+
program,
|
|
381
|
+
layer,
|
|
382
|
+
transform,
|
|
383
|
+
requestedPlacement,
|
|
384
|
+
args.placement
|
|
385
|
+
);
|
|
386
|
+
next = next
|
|
387
|
+
.editSemantic({
|
|
388
|
+
property: `layer[${layer.id}].encoding.x`,
|
|
389
|
+
remove: true
|
|
390
|
+
})
|
|
391
|
+
.editSemantic({
|
|
392
|
+
property: `layer[${layer.id}].encoding.y`,
|
|
393
|
+
remove: true
|
|
394
|
+
});
|
|
395
|
+
next = next
|
|
396
|
+
.editScale(scaleEditPatch(xScaleId, scaleDefinitions.x))
|
|
397
|
+
.editScale(scaleEditPatch(yScaleId, scaleDefinitions.y));
|
|
398
|
+
const densityChannel = requestedPlacement?.channel ?? (
|
|
399
|
+
layer.encoding.x.field === transform.as[1] ? "x" : "y"
|
|
400
|
+
);
|
|
401
|
+
const valueChannel = requestedPlacement?.channel === "x" ? "y" : "x";
|
|
402
|
+
const definition = resolveDensityPositionDefinition({
|
|
403
|
+
layer: { densityChannel },
|
|
404
|
+
output: transform.as,
|
|
405
|
+
groupBy,
|
|
406
|
+
placement: requestedPlacement,
|
|
407
|
+
coordinate: layer.coordinate,
|
|
408
|
+
valueScale: {
|
|
409
|
+
id: valueChannel === "y" ? yScaleId : xScaleId,
|
|
410
|
+
...scaleDefinitions[valueChannel]
|
|
411
|
+
},
|
|
412
|
+
densityScale: requestedPlacement === undefined
|
|
413
|
+
? {
|
|
414
|
+
id: densityChannel === "x" ? xScaleId : yScaleId,
|
|
415
|
+
...scaleDefinitions[densityChannel]
|
|
416
|
+
}
|
|
417
|
+
: undefined,
|
|
418
|
+
placementScale: requestedPlacement === undefined
|
|
419
|
+
? undefined
|
|
420
|
+
: {
|
|
421
|
+
id: requestedPlacement.channel === "x" ? xScaleId : yScaleId,
|
|
422
|
+
...scaleDefinitions[requestedPlacement.channel]
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
next = applyDensityPosition(next, layer.id, definition, groupBy);
|
|
426
|
+
} else if (
|
|
427
|
+
requestedPlacement !== undefined &&
|
|
428
|
+
Object.hasOwn(args.placement ?? {}, "scale")
|
|
429
|
+
) {
|
|
430
|
+
const channel = requestedPlacement.channel;
|
|
431
|
+
const scaleId = layer.encoding[channel].scale;
|
|
432
|
+
const requestedScale = resolveDensityCategoryScaleOptions(
|
|
433
|
+
args.placement.scale,
|
|
434
|
+
scaleId
|
|
435
|
+
);
|
|
436
|
+
if (requestedScale.id !== scaleId) {
|
|
437
|
+
throw new Error("editDensity placement scale cannot change its id.");
|
|
438
|
+
}
|
|
439
|
+
next = next.editScale(scaleEditPatch(scaleId, requestedScale));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
next = applyMaterializationPlan(
|
|
443
|
+
next,
|
|
444
|
+
planDensityRematerialization(next, layer.id)
|
|
445
|
+
);
|
|
446
|
+
if (
|
|
447
|
+
changesGroup &&
|
|
448
|
+
colorFollowsGroup &&
|
|
449
|
+
groupBy !== undefined &&
|
|
450
|
+
Object.values(next.guideConfigs.legend ?? {})
|
|
451
|
+
.some(config => config?.target === layer.id)
|
|
452
|
+
) {
|
|
453
|
+
next = next.rematerializeLegend();
|
|
454
|
+
}
|
|
455
|
+
return next;
|
|
456
|
+
};
|
|
457
|
+
applyEdit(this);
|
|
458
|
+
return applyEdit(this);
|
|
219
459
|
}
|
|
220
460
|
);
|
|
221
461
|
|