ggaction 0.0.2 → 0.0.3
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 +28 -0
- package/README.md +12 -1
- package/package.json +6 -1
- package/src/actions/boxPlots/components.js +3 -3
- package/src/actions/boxPlots/edit.js +277 -0
- package/src/actions/boxPlots/index.js +2 -0
- package/src/actions/boxPlots/materialize.js +12 -1
- package/src/actions/boxPlots/options.js +25 -14
- package/src/actions/canvas/actions.js +21 -17
- package/src/actions/composition/actions.js +247 -0
- package/src/actions/composition/index.js +1 -0
- package/src/actions/coordinates/actions.js +2 -5
- package/src/actions/data/density.js +7 -1
- package/src/actions/data/filter.js +3 -1
- package/src/actions/encodings/appearance.js +11 -0
- package/src/actions/encodings/barWidth.js +5 -3
- package/src/actions/encodings/color.js +120 -25
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +90 -72
- package/src/actions/encodings/position/apply.js +66 -0
- package/src/actions/encodings/position/policies/arc.js +26 -0
- package/src/actions/encodings/position/policies/bar.js +9 -5
- package/src/actions/encodings/position/policies/index.js +5 -1
- package/src/actions/encodings/position/policies/line.js +20 -3
- package/src/actions/encodings/position/resolve.js +57 -8
- package/src/actions/encodings/position.js +44 -90
- package/src/actions/encodings/ranged.js +28 -6
- package/src/actions/encodings/shared.js +17 -21
- package/src/actions/encodings/text.js +73 -0
- package/src/actions/errorBands/create.js +37 -29
- package/src/actions/errorBands/edit.js +206 -0
- package/src/actions/errorBands/index.js +9 -0
- package/src/actions/errorBands/options.js +39 -0
- package/src/actions/errorBars/create.js +50 -43
- package/src/actions/errorBars/edit.js +130 -0
- package/src/actions/errorBars/index.js +3 -0
- package/src/actions/errorBars/options.js +52 -0
- package/src/actions/facets/actions.js +162 -0
- package/src/actions/facets/derive.js +200 -0
- package/src/actions/facets/guides.js +16 -0
- package/src/actions/facets/index.js +1 -0
- package/src/actions/facets/replay.js +69 -0
- package/src/actions/guides/applicability.js +113 -0
- package/src/actions/guides/axes/axes.js +70 -16
- package/src/actions/guides/axes/edit.js +126 -0
- package/src/actions/guides/axes/index.js +12 -0
- package/src/actions/guides/axes/labels.js +21 -18
- package/src/actions/guides/axes/remove.js +60 -0
- package/src/actions/guides/axes/tickGroups.js +7 -5
- package/src/actions/guides/axes/ticks.js +9 -4
- package/src/actions/guides/axes/titles.js +7 -5
- package/src/actions/guides/grids/grid.js +144 -15
- package/src/actions/guides/grids/resolve.js +5 -13
- package/src/actions/guides/guides.js +17 -75
- package/src/actions/guides/index.js +6 -0
- package/src/actions/guides/legends/categorical/actions.js +48 -9
- package/src/actions/guides/legends/categorical/components.js +16 -16
- package/src/actions/guides/legends/categorical/index.js +14 -14
- package/src/actions/guides/legends/categorical/layout.js +3 -1
- package/src/actions/guides/legends/categorical/options.js +2 -5
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +14 -14
- package/src/actions/guides/legends/continuous/common.js +7 -7
- package/src/actions/guides/legends/continuous/interval.js +5 -5
- package/src/actions/guides/legends/edit.js +2 -6
- package/src/actions/guides/legends/focused.js +63 -0
- package/src/actions/guides/legends/index.js +4 -0
- package/src/actions/guides/legends/remove.js +64 -0
- package/src/actions/guides/legends/size.js +12 -14
- package/src/actions/guides/polar/axes/facade.js +219 -0
- package/src/actions/guides/polar/axes/labels.js +188 -0
- package/src/actions/guides/polar/axes/lines.js +113 -0
- package/src/actions/guides/polar/axes/shared.js +112 -0
- package/src/actions/guides/polar/axes/ticks.js +164 -0
- package/src/actions/guides/polar/axes/titles.js +165 -0
- package/src/actions/guides/polar/axes.js +55 -0
- package/src/actions/guides/polar/grids.js +252 -0
- package/src/actions/guides/polar/index.js +2 -0
- package/src/actions/guides/polar/resolve.js +269 -0
- package/src/actions/index.js +4 -0
- package/src/actions/marks/arc/actions.js +239 -0
- package/src/actions/marks/arc/index.js +1 -0
- package/src/actions/marks/{area.js → area/actions.js} +51 -38
- package/src/actions/marks/area/index.js +4 -0
- package/src/actions/marks/bar/create.js +26 -5
- package/src/actions/marks/bar/materialize.js +25 -31
- package/src/actions/marks/index.js +12 -4
- package/src/actions/marks/lifecycle.js +27 -0
- package/src/actions/marks/line/actions.js +373 -0
- package/src/actions/marks/line/index.js +1 -0
- package/src/actions/marks/{point.js → point/actions.js} +67 -108
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/rect/actions.js +165 -0
- package/src/actions/marks/rect/index.js +1 -0
- package/src/actions/marks/remove.js +214 -0
- package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
- package/src/actions/marks/rule/index.js +1 -0
- package/src/actions/marks/shared.js +156 -25
- package/src/actions/marks/text/actions.js +192 -0
- package/src/actions/marks/text/index.js +1 -0
- package/src/actions/primitives/createGraphics.js +13 -6
- package/src/actions/primitives/editGraphics.js +10 -3
- package/src/actions/primitives/semantic.js +26 -3
- package/src/actions/primitives/semanticValue.js +25 -3
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +18 -1
- package/src/actions/regression/edit.js +247 -0
- package/src/actions/regression/index.js +3 -0
- package/src/actions/regression/resolve.js +3 -1
- package/src/actions/scales/consumers.js +15 -6
- package/src/actions/scales/definitions.js +13 -4
- package/src/actions/scales/edit.js +42 -19
- package/src/actions/scales/materialize.js +57 -408
- package/src/actions/selection/actions.js +38 -24
- package/src/actions/selection/index.js +2 -0
- package/src/actions/titles/actions.js +63 -2
- package/src/actions/titles/index.js +2 -0
- package/src/actions/titles/resolve.js +12 -17
- package/src/composition.js +10 -0
- package/src/core/ChartProgram.js +200 -2
- package/src/core/action.js +10 -0
- package/src/core/validation.js +16 -0
- package/src/core/vocabulary.js +154 -4
- package/src/grammar/arcs.js +154 -0
- package/src/grammar/bars/aggregate.js +2 -2
- package/src/grammar/bars/geometry.js +3 -3
- package/src/grammar/bars/policy.js +7 -1
- package/src/grammar/coordinates.js +3 -7
- package/src/grammar/density.js +20 -1
- package/src/grammar/facets/dependencies.js +185 -0
- package/src/grammar/facets/guides.js +257 -0
- package/src/grammar/facets/scales.js +254 -0
- package/src/grammar/facets.js +122 -0
- package/src/grammar/lineSeries.js +111 -3
- package/src/grammar/polar.js +102 -0
- package/src/grammar/polarGuides.js +243 -0
- package/src/grammar/polarLineCommands.js +43 -0
- package/src/grammar/polarPaths.js +161 -0
- package/src/grammar/positionCompatibility.js +18 -2
- package/src/grammar/rects.js +26 -0
- package/src/grammar/scales/continuous.js +6 -1
- package/src/grammar/scales/fields.js +7 -0
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +1 -1
- package/src/grammar/schemas/graphic.js +1 -1
- package/src/grammar/schemas/graphicBounds.js +64 -9
- package/src/grammar/schemas/graphicTree.js +44 -10
- package/src/grammar/schemas/semanticPath.js +31 -9
- package/src/grammar/seriesLayout.js +13 -2
- package/src/grammar/text.js +71 -0
- package/src/grammar/ticks.js +50 -13
- package/src/index.js +1 -0
- package/src/layout/composition.js +310 -0
- package/src/layout/facets.js +131 -0
- package/src/layout/title.js +6 -0
- package/src/materialization/bars/aggregate.js +5 -2
- package/src/materialization/bars/grouped.js +75 -37
- package/src/materialization/composition.js +152 -0
- package/src/materialization/compositionSnapshot.js +114 -0
- package/src/materialization/dependencies.js +71 -16
- package/src/materialization/encodings.js +14 -28
- package/src/materialization/facetGuides.js +426 -0
- package/src/materialization/facets.js +214 -0
- package/src/materialization/graphicHierarchy.js +15 -5
- package/src/materialization/guides/resources.js +46 -0
- package/src/materialization/marks.js +220 -14
- package/src/materialization/planner.js +105 -12
- package/src/materialization/rect.js +138 -0
- package/src/materialization/rectConfig.js +38 -0
- package/src/materialization/rowEncoding.js +33 -0
- package/src/materialization/scales/map.js +40 -0
- package/src/materialization/scales/policies/arc.js +37 -0
- package/src/materialization/scales/policies/bar.js +132 -0
- package/src/materialization/scales/policies/series.js +76 -0
- package/src/materialization/scales/resolve.js +326 -0
- package/src/materialization/selection/items/arc.js +29 -0
- package/src/materialization/selection/items/bar.js +255 -0
- package/src/materialization/selection/items/common.js +131 -0
- package/src/materialization/selection/items/index.js +6 -0
- package/src/materialization/selection/items/path.js +59 -0
- package/src/materialization/selection/items/point.js +55 -0
- package/src/materialization/selection/items/rect.js +23 -0
- package/src/materialization/selection/items/rule.js +25 -0
- package/src/materialization/selection/policies/arc.js +10 -0
- package/src/materialization/selection/policies/area.js +1 -1
- package/src/materialization/selection/policies/bar.js +1 -1
- package/src/materialization/selection/policies/index.js +5 -1
- package/src/materialization/selection/policies/line.js +1 -1
- package/src/materialization/selection/policies/point.js +1 -1
- package/src/materialization/selection/policies/rect.js +10 -0
- package/src/materialization/selection/policies/rule.js +1 -1
- package/src/materialization/selection/styles.js +24 -16
- package/src/materialization/text.js +156 -0
- package/src/renderers/canvas/index.js +68 -22
- package/types/extension.d.ts +1 -0
- package/types/index.d.ts +56 -1
- package/types/program.d.ts +664 -27
- package/src/actions/marks/line.js +0 -248
- package/src/materialization/selection/items.js +0 -503
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_RECT_MARK,
|
|
5
|
+
normalizeRectMarkConfig
|
|
6
|
+
} from "../../../materialization/rectConfig.js";
|
|
7
|
+
import {
|
|
8
|
+
canMaterializeRect
|
|
9
|
+
} from "../../../materialization/marks.js";
|
|
10
|
+
import { resolveRectGraphicItems } from "../../../materialization/rect.js";
|
|
11
|
+
import { findDataset } from "../../../selectors/datasets.js";
|
|
12
|
+
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
13
|
+
import {
|
|
14
|
+
assertMarkAvailable,
|
|
15
|
+
applyLayeredMarkInheritance,
|
|
16
|
+
materializeInheritedMark,
|
|
17
|
+
resolveLayeredMarkInheritance,
|
|
18
|
+
resolveMarkData,
|
|
19
|
+
resolveMarkId,
|
|
20
|
+
validateMarkOptions
|
|
21
|
+
} from "../shared.js";
|
|
22
|
+
import { resolveMarkGraphicPlacement } from
|
|
23
|
+
"../../../materialization/graphicHierarchy.js";
|
|
24
|
+
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
25
|
+
|
|
26
|
+
const STYLE_OPTIONS = Object.freeze([
|
|
27
|
+
"fill", "opacity", "stroke", "strokeWidth"
|
|
28
|
+
]);
|
|
29
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", ...STYLE_OPTIONS]);
|
|
30
|
+
const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
|
|
31
|
+
const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
|
|
32
|
+
|
|
33
|
+
function requireRectLayer(program, requested, operation) {
|
|
34
|
+
const target = requested === undefined
|
|
35
|
+
? undefined
|
|
36
|
+
: validateUserId(requested, "Rect mark id");
|
|
37
|
+
return resolveEligibleLayer(program, {
|
|
38
|
+
target,
|
|
39
|
+
predicate: layer => layer.mark?.type === "rect",
|
|
40
|
+
label: `${operation} rect mark`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const createRectMark = action(
|
|
45
|
+
{ op: "createRectMark", description: "Create a semantic rectangular cell layer." },
|
|
46
|
+
function (args = {}) {
|
|
47
|
+
validateMarkOptions(args, CREATE_OPTIONS, "createRectMark");
|
|
48
|
+
const id = resolveMarkId(this, args.id, {
|
|
49
|
+
defaultId: "rect",
|
|
50
|
+
label: "Rect mark id",
|
|
51
|
+
markType: "rect",
|
|
52
|
+
operation: "createRectMark"
|
|
53
|
+
});
|
|
54
|
+
const inherited = resolveLayeredMarkInheritance(this, args, "rect");
|
|
55
|
+
const { data } = resolveMarkData(this, {
|
|
56
|
+
...args,
|
|
57
|
+
...(args.data === undefined && this.context.currentData === undefined &&
|
|
58
|
+
inherited?.data !== undefined ? { data: inherited.data } : {})
|
|
59
|
+
});
|
|
60
|
+
assertMarkAvailable(this, id);
|
|
61
|
+
|
|
62
|
+
let next = this
|
|
63
|
+
.editSemantic({ property: `layer[${id}].mark.type`, value: "rect" })
|
|
64
|
+
.editSemantic({ property: `layer[${id}].data`, value: data });
|
|
65
|
+
next = applyLayeredMarkInheritance(next, id, inherited)
|
|
66
|
+
.createGraphics({
|
|
67
|
+
id,
|
|
68
|
+
type: "rect",
|
|
69
|
+
length: 0,
|
|
70
|
+
...resolveMarkGraphicPlacement(next, { data, markType: "rect" })
|
|
71
|
+
})
|
|
72
|
+
._withMarkConfig(id, { ...DEFAULT_RECT_MARK, fillExplicit: false });
|
|
73
|
+
next = materializeInheritedMark(next, id);
|
|
74
|
+
const style = Object.fromEntries(
|
|
75
|
+
STYLE_OPTIONS.filter(option => Object.hasOwn(args, option))
|
|
76
|
+
.map(option => [option, args[option]])
|
|
77
|
+
);
|
|
78
|
+
return Object.keys(style).length === 0
|
|
79
|
+
? next
|
|
80
|
+
: next.editRectMark({ target: id, ...style });
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const rematerializeRectMark = action(
|
|
85
|
+
{ op: "rematerializeRectMark", description: "Recompute concrete rectangular cells." },
|
|
86
|
+
function (args = {}) {
|
|
87
|
+
validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeRectMark");
|
|
88
|
+
const id = validateUserId(args.id, "Rect mark id");
|
|
89
|
+
if (args.scales !== undefined && typeof args.scales !== "boolean") {
|
|
90
|
+
throw new TypeError("rematerializeRectMark scales must be a boolean.");
|
|
91
|
+
}
|
|
92
|
+
const highlighted = rematerializeHighlightBaseline(this, {
|
|
93
|
+
target: id,
|
|
94
|
+
operation: "rematerializeRectMark",
|
|
95
|
+
resetProperty: "items",
|
|
96
|
+
resetValue: []
|
|
97
|
+
});
|
|
98
|
+
if (highlighted !== undefined) return highlighted;
|
|
99
|
+
const layer = findLayer(this, id);
|
|
100
|
+
const graphic = this.graphicSpec.objects[id];
|
|
101
|
+
if (layer?.mark?.type !== "rect") throw new Error(`Unknown rect mark "${id}".`);
|
|
102
|
+
if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
|
|
103
|
+
throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
|
|
104
|
+
}
|
|
105
|
+
const dataset = findDataset(this, layer.data);
|
|
106
|
+
if (dataset === undefined) {
|
|
107
|
+
throw new Error(`Rect mark "${id}" requires an existing dataset.`);
|
|
108
|
+
}
|
|
109
|
+
if (!canMaterializeRect(this, layer)) {
|
|
110
|
+
return graphic.items.length === 0
|
|
111
|
+
? this
|
|
112
|
+
: this.editGraphics({ target: id, property: "items", value: [] });
|
|
113
|
+
}
|
|
114
|
+
let next = this;
|
|
115
|
+
if (args.scales !== false) {
|
|
116
|
+
const ids = new Set(["x", "y", "x2", "y2", "color"].flatMap(channel => {
|
|
117
|
+
const scale = layer.encoding?.[channel]?.scale;
|
|
118
|
+
return scale === undefined ? [] : [scale];
|
|
119
|
+
}));
|
|
120
|
+
for (const scale of ids) {
|
|
121
|
+
next = next.rematerializeScale({ id: scale, marks: false });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return next.editGraphics({
|
|
125
|
+
target: id,
|
|
126
|
+
property: "items",
|
|
127
|
+
value: resolveRectGraphicItems(next, layer, dataset)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const editRectMark = action(
|
|
133
|
+
{ op: "editRectMark", description: "Edit rectangular cell appearance." },
|
|
134
|
+
function (args = {}) {
|
|
135
|
+
validateMarkOptions(args, EDIT_OPTIONS, "editRectMark");
|
|
136
|
+
if (!STYLE_OPTIONS.some(option => Object.hasOwn(args, option))) {
|
|
137
|
+
throw new Error("editRectMark requires at least one editable property.");
|
|
138
|
+
}
|
|
139
|
+
const layer = requireRectLayer(this, args.target, "editRectMark");
|
|
140
|
+
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
141
|
+
throw new Error("editRectMark fill cannot be combined with a color encoding.");
|
|
142
|
+
}
|
|
143
|
+
const next = this._withMarkConfig(
|
|
144
|
+
layer.id,
|
|
145
|
+
{
|
|
146
|
+
...normalizeRectMarkConfig(
|
|
147
|
+
args,
|
|
148
|
+
this.markConfigs[layer.id] ?? DEFAULT_RECT_MARK
|
|
149
|
+
),
|
|
150
|
+
fillExplicit: Object.hasOwn(args, "fill")
|
|
151
|
+
? true
|
|
152
|
+
: this.markConfigs[layer.id]?.fillExplicit ?? false
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
return canMaterializeRect(next, layer)
|
|
156
|
+
? next.rematerializeRectMark({ id: layer.id })
|
|
157
|
+
: next;
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
export function registerRectMarkActions(ProgramClass) {
|
|
162
|
+
ProgramClass.prototype.createRectMark = createRectMark;
|
|
163
|
+
ProgramClass.prototype.editRectMark = editRectMark;
|
|
164
|
+
ProgramClass.prototype.rematerializeRectMark = rematerializeRectMark;
|
|
165
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerRectMarkActions } from "./actions.js";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
5
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
6
|
+
import {
|
|
7
|
+
getPositionChannelDefinition,
|
|
8
|
+
POSITION_CHANNELS
|
|
9
|
+
} from "../../core/vocabulary.js";
|
|
10
|
+
|
|
11
|
+
const OPTIONS = Object.freeze(["target"]);
|
|
12
|
+
const REMOVE_AXIS = Object.freeze({
|
|
13
|
+
x: "removeXAxis",
|
|
14
|
+
y: "removeYAxis",
|
|
15
|
+
theta: "removeThetaAxis",
|
|
16
|
+
radius: "removeRadialAxis"
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function ownedChildren(program, id) {
|
|
20
|
+
const config = program.markConfigs[id] ?? {};
|
|
21
|
+
return [
|
|
22
|
+
config.errorBar?.lowerCapId,
|
|
23
|
+
config.errorBar?.upperCapId,
|
|
24
|
+
config.errorBand?.lowerBoundaryId,
|
|
25
|
+
config.errorBand?.upperBoundaryId,
|
|
26
|
+
config.regression?.bandId,
|
|
27
|
+
config.regression?.lineId,
|
|
28
|
+
config.boxPlot?.whiskerId,
|
|
29
|
+
config.boxPlot?.medianId,
|
|
30
|
+
config.boxPlot?.outlierId
|
|
31
|
+
].concat(
|
|
32
|
+
program.semanticSpec.layers
|
|
33
|
+
.filter(layer => layer.source === id)
|
|
34
|
+
.map(layer => layer.id)
|
|
35
|
+
).filter(child => child !== undefined && findLayer(program, child) !== undefined);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ownership(program) {
|
|
39
|
+
const ownerByChild = new Map();
|
|
40
|
+
for (const layer of program.semanticSpec.layers) {
|
|
41
|
+
for (const child of ownedChildren(program, layer.id)) {
|
|
42
|
+
ownerByChild.set(child, layer.id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return ownerByChild;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveOwner(program, requested) {
|
|
49
|
+
const ownerByChild = ownership(program);
|
|
50
|
+
const stable = program.semanticSpec.layers.filter(
|
|
51
|
+
layer => !ownerByChild.has(layer.id)
|
|
52
|
+
);
|
|
53
|
+
if (requested !== undefined) {
|
|
54
|
+
const id = validateUserId(requested, "Mark owner id");
|
|
55
|
+
const layer = findLayer(program, id);
|
|
56
|
+
if (layer === undefined) throw new Error(`Unknown mark target "${id}".`);
|
|
57
|
+
if (ownerByChild.has(id)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Mark "${id}" is owned by "${ownerByChild.get(id)}"; remove its stable owner.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return layer;
|
|
63
|
+
}
|
|
64
|
+
const current = findLayer(program, program.context.currentMark);
|
|
65
|
+
if (current !== undefined && stable.includes(current)) return current;
|
|
66
|
+
if (stable.length === 1) return stable[0];
|
|
67
|
+
if (stable.length === 0) throw new Error("removeMark requires an existing mark.");
|
|
68
|
+
throw new Error("removeMark target is ambiguous; provide target.");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function collectClosure(program, root) {
|
|
72
|
+
const result = [];
|
|
73
|
+
const visit = id => {
|
|
74
|
+
for (const child of ownedChildren(program, id)) visit(child);
|
|
75
|
+
if (!result.includes(id)) result.push(id);
|
|
76
|
+
};
|
|
77
|
+
visit(root);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ownedDerivedData(program, ids) {
|
|
82
|
+
const candidates = new Set();
|
|
83
|
+
for (const id of ids) {
|
|
84
|
+
const layer = findLayer(program, id);
|
|
85
|
+
const config = program.markConfigs[id] ?? {};
|
|
86
|
+
for (const candidate of [
|
|
87
|
+
config.errorBar?.data,
|
|
88
|
+
config.errorBand?.data,
|
|
89
|
+
config.regression?.dataId,
|
|
90
|
+
config.boxPlot?.summaryId,
|
|
91
|
+
config.boxPlot?.outlierDataId
|
|
92
|
+
]) {
|
|
93
|
+
if (candidate !== undefined) candidates.add(candidate);
|
|
94
|
+
}
|
|
95
|
+
const dataset = findDataset(program, layer?.data);
|
|
96
|
+
if (dataset?.transform?.[0]?.type === "markFilter") {
|
|
97
|
+
candidates.add(dataset.id);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return candidates;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function usedPositionScales(program, ids) {
|
|
104
|
+
const scales = Object.fromEntries(
|
|
105
|
+
POSITION_CHANNELS.map(channel => [channel, new Set()])
|
|
106
|
+
);
|
|
107
|
+
for (const id of ids) {
|
|
108
|
+
const layer = findLayer(program, id);
|
|
109
|
+
for (const channel of POSITION_CHANNELS) {
|
|
110
|
+
const scale = layer?.encoding?.[channel]?.scale;
|
|
111
|
+
if (scale !== undefined) scales[channel].add(scale);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return scales;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function cleanupSelectionState(program, ids) {
|
|
118
|
+
const targets = new Set(ids);
|
|
119
|
+
const selectionIds = Object.entries(program.materializationConfigs.selections ?? {})
|
|
120
|
+
.filter(([, config]) => targets.has(config.target))
|
|
121
|
+
.map(([id]) => id);
|
|
122
|
+
let next = program;
|
|
123
|
+
for (const [id, config] of Object.entries(
|
|
124
|
+
program.materializationConfigs.highlights ?? {}
|
|
125
|
+
)) {
|
|
126
|
+
if (targets.has(config.target) || selectionIds.includes(config.selection)) {
|
|
127
|
+
next = next._withoutMaterializationConfig(["highlights", id]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const id of selectionIds) {
|
|
131
|
+
next = next._withoutMaterializationConfig(["selections", id]);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
program: next,
|
|
135
|
+
selectionIds
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hasScaleConsumer(program, channel, scale) {
|
|
140
|
+
return program.semanticSpec.layers.some(
|
|
141
|
+
layer => layer.encoding?.[channel]?.scale === scale
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function cleanupPositionGuides(program, scales) {
|
|
146
|
+
let next = program;
|
|
147
|
+
for (const channel of POSITION_CHANNELS) {
|
|
148
|
+
const definition = getPositionChannelDefinition(channel);
|
|
149
|
+
const axis = next.semanticSpec.guides.axis?.[channel];
|
|
150
|
+
if (
|
|
151
|
+
axis !== undefined &&
|
|
152
|
+
scales[channel].has(axis.scale) &&
|
|
153
|
+
!hasScaleConsumer(next, channel, axis.scale)
|
|
154
|
+
) {
|
|
155
|
+
next = next[REMOVE_AXIS[channel]]({
|
|
156
|
+
scale: axis.scale,
|
|
157
|
+
...(axis.coordinate === undefined ? {} : { coordinate: axis.coordinate })
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const grid = next.semanticSpec.guides.grid?.[definition.gridDirection];
|
|
161
|
+
if (
|
|
162
|
+
grid !== undefined &&
|
|
163
|
+
scales[channel].has(grid.scale) &&
|
|
164
|
+
!hasScaleConsumer(next, channel, grid.scale)
|
|
165
|
+
) {
|
|
166
|
+
next = next.removeGrid({ [definition.gridDirection]: true });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return next;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const removeMark = action(
|
|
173
|
+
{ op: "removeMark", description: "Remove one stable mark owner and owned state." },
|
|
174
|
+
function (args = {}) {
|
|
175
|
+
validateKeys(args, OPTIONS, "removeMark");
|
|
176
|
+
const owner = resolveOwner(this, args.target);
|
|
177
|
+
const ids = collectClosure(this, owner.id);
|
|
178
|
+
const derived = ownedDerivedData(this, ids);
|
|
179
|
+
const positionScales = usedPositionScales(this, ids);
|
|
180
|
+
let next = this;
|
|
181
|
+
|
|
182
|
+
const legendTargets = [...new Set(Object.values(next.guideConfigs.legend ?? {})
|
|
183
|
+
.filter(config => ids.includes(config?.target))
|
|
184
|
+
.map(config => config.target))];
|
|
185
|
+
for (const target of legendTargets) {
|
|
186
|
+
next = next.removeLegend({ target });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const selectionCleanup = cleanupSelectionState(next, ids);
|
|
190
|
+
next = selectionCleanup.program;
|
|
191
|
+
for (const id of ids) {
|
|
192
|
+
if (findLayer(next, id) !== undefined) {
|
|
193
|
+
next = next.editSemantic({ property: `layer[${id}]`, remove: true });
|
|
194
|
+
}
|
|
195
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
196
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
197
|
+
}
|
|
198
|
+
next = next._withoutMaterializationConfig(["marks", id]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
next = cleanupPositionGuides(next, positionScales);
|
|
202
|
+
for (const id of derived) {
|
|
203
|
+
if (findDataset(next, id)?.source !== undefined) {
|
|
204
|
+
next = next.releaseDerivedData({ id });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return next._withContext({
|
|
208
|
+
...(ids.includes(this.context.currentMark) ? { currentMark: undefined } : {}),
|
|
209
|
+
...(selectionCleanup.selectionIds.includes(this.context.currentSelection)
|
|
210
|
+
? { currentSelection: undefined }
|
|
211
|
+
: {})
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
);
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
import { action } from "
|
|
2
|
-
import { validateUserId } from "
|
|
3
|
-
import { validatePositiveFinite } from "
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import { validatePositiveFinite } from "../../../core/validation.js";
|
|
4
4
|
import {
|
|
5
5
|
mapContinuousScaleValues,
|
|
6
6
|
mapOrdinalPositionValues,
|
|
7
7
|
mapOrdinalValues,
|
|
8
8
|
normalizeStrokeDashPattern
|
|
9
|
-
} from "
|
|
10
|
-
import { deriveRuleValues, resolveRuleMode } from "
|
|
11
|
-
import { resolveGraphicBounds } from "
|
|
12
|
-
import { findDataset } from "
|
|
13
|
-
import { findLayer } from "
|
|
14
|
-
import { DEFAULT_COLORS } from "
|
|
9
|
+
} from "../../../grammar/scales.js";
|
|
10
|
+
import { deriveRuleValues, resolveRuleMode } from "../../../grammar/rules.js";
|
|
11
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
12
|
+
import { findDataset } from "../../../selectors/datasets.js";
|
|
13
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
14
|
+
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
15
15
|
import {
|
|
16
16
|
assertMarkAvailable,
|
|
17
|
+
applyLayeredMarkInheritance,
|
|
18
|
+
materializeInheritedMark,
|
|
19
|
+
resolveLayeredMarkInheritance,
|
|
17
20
|
resolveMarkData,
|
|
18
21
|
resolveMarkId,
|
|
19
22
|
validateMarkOptions
|
|
20
|
-
} from "
|
|
23
|
+
} from "../shared.js";
|
|
21
24
|
import { resolveMarkGraphicPlacement } from
|
|
22
|
-
"
|
|
25
|
+
"../../../materialization/graphicHierarchy.js";
|
|
26
|
+
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
23
27
|
|
|
24
28
|
const CREATE_OPTIONS = Object.freeze(["id", "data"]);
|
|
25
29
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
@@ -81,19 +85,27 @@ const createRuleMark = action(
|
|
|
81
85
|
markType: "rule",
|
|
82
86
|
operation: "createRuleMark"
|
|
83
87
|
});
|
|
84
|
-
const
|
|
88
|
+
const inherited = resolveLayeredMarkInheritance(this, args, "rule");
|
|
89
|
+
const { data } = resolveMarkData(this, {
|
|
90
|
+
...args,
|
|
91
|
+
...(args.data === undefined && this.context.currentData === undefined &&
|
|
92
|
+
inherited?.data !== undefined ? { data: inherited.data } : {})
|
|
93
|
+
});
|
|
85
94
|
assertMarkAvailable(this, id);
|
|
86
95
|
|
|
87
|
-
|
|
96
|
+
let created = this
|
|
88
97
|
.editSemantic({ property: `layer[${id}].mark.type`, value: "rule" })
|
|
89
|
-
.editSemantic({ property: `layer[${id}].data`, value: data })
|
|
98
|
+
.editSemantic({ property: `layer[${id}].data`, value: data });
|
|
99
|
+
created = applyLayeredMarkInheritance(created, id, inherited);
|
|
100
|
+
created = created
|
|
90
101
|
.createGraphics({
|
|
91
102
|
id,
|
|
92
103
|
type: "line",
|
|
93
104
|
length: 0,
|
|
94
|
-
...resolveMarkGraphicPlacement(
|
|
105
|
+
...resolveMarkGraphicPlacement(created, { data, markType: "rule" })
|
|
95
106
|
})
|
|
96
107
|
._withMarkConfig(id, DEFAULT_RULE_CONFIG);
|
|
108
|
+
return materializeInheritedMark(created, id);
|
|
97
109
|
}
|
|
98
110
|
);
|
|
99
111
|
|
|
@@ -105,19 +117,13 @@ const rematerializeRuleMark = action(
|
|
|
105
117
|
function (args = {}) {
|
|
106
118
|
validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeRuleMark");
|
|
107
119
|
const id = validateUserId(args.id, "Rule mark id");
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
return baseline
|
|
117
|
-
.editGraphics({ target: id, property: "length", value: 0 })
|
|
118
|
-
.rematerializeRuleMark({ id })
|
|
119
|
-
.rematerializeMarkHighlights({ target: id, highlights });
|
|
120
|
-
}
|
|
120
|
+
const highlighted = rematerializeHighlightBaseline(this, {
|
|
121
|
+
target: id,
|
|
122
|
+
operation: "rematerializeRuleMark",
|
|
123
|
+
resetProperty: "length",
|
|
124
|
+
resetValue: 0
|
|
125
|
+
});
|
|
126
|
+
if (highlighted !== undefined) return highlighted;
|
|
121
127
|
const { dataset, graphic, layer } = requireRule(this, id);
|
|
122
128
|
validateEndpointBindings(layer);
|
|
123
129
|
const config = this.markConfigs[id] ?? DEFAULT_RULE_CONFIG;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerRuleMarkActions } from "./actions.js";
|