ggaction 0.0.5 → 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 +25 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/actions/boxPlots/edit.js +455 -3
- package/src/actions/boxPlots/options.js +3 -3
- package/src/actions/composition/actions.js +13 -3
- package/src/actions/data/bin2d.js +140 -17
- package/src/actions/data/index.js +6 -1
- package/src/actions/data/intervalEdit.js +76 -0
- package/src/actions/distributions/revision.js +326 -0
- package/src/actions/encodings/appearance.js +35 -1
- package/src/actions/encodings/density.js +215 -80
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/remove.js +268 -0
- 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 +136 -1
- package/src/actions/gradientPlots/edit.js +352 -4
- package/src/actions/guides/axes/edit.js +129 -47
- package/src/actions/guides/legends/categorical/actions.js +19 -8
- package/src/actions/guides/legends/continuous/common.js +3 -3
- package/src/actions/guides/legends/edit.js +61 -3
- package/src/actions/guides/legends/remove.js +124 -23
- package/src/actions/guides/legends/strokeWidth.js +42 -14
- package/src/actions/marks/arc/actions.js +46 -14
- package/src/actions/marks/point/edit.js +15 -3
- package/src/actions/marks/point/materialize.js +9 -3
- package/src/actions/marks/rule/actions.js +4 -1
- package/src/actions/regression/edit.js +235 -74
- package/src/actions/selection/actions.js +122 -2
- package/src/actions/selection/index.js +6 -0
- package/src/core/vocabulary.js +7 -0
- package/types/index.d.ts +1 -0
- package/types/program.d.ts +71 -7
|
@@ -3,6 +3,7 @@ import { validateOptionObject } from "../../../core/validation.js";
|
|
|
3
3
|
import { normalizeOptions } from "./categorical/options.js";
|
|
4
4
|
import { symbolGraphic } from "./categorical/layout.js";
|
|
5
5
|
import {
|
|
6
|
+
normalizeLegendTextOptions,
|
|
6
7
|
normalizeContinuousLegend,
|
|
7
8
|
validatePositive
|
|
8
9
|
} from "./continuous/common.js";
|
|
@@ -12,6 +13,10 @@ import { findLayer } from "../../../selectors/layers.js";
|
|
|
12
13
|
import { resolveLegendGraphicPlacement } from
|
|
13
14
|
"../../../materialization/graphicHierarchy.js";
|
|
14
15
|
import { resolveLegendTarget } from "./target.js";
|
|
16
|
+
import {
|
|
17
|
+
STROKE_WIDTH_LEGEND_LABELS,
|
|
18
|
+
STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
19
|
+
} from "./strokeWidth.js";
|
|
15
20
|
|
|
16
21
|
const OPTIONS = Object.freeze([
|
|
17
22
|
"target", "position", "align", "direction", "columns", "offset",
|
|
@@ -163,6 +168,61 @@ function editInterval(program, previous, args) {
|
|
|
163
168
|
return next.rematerializeIntervalLegend();
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
function editStrokeWidth(program, previous, args) {
|
|
172
|
+
const allowed = ["target", "title", "count", "labels", "titleStyle"];
|
|
173
|
+
for (const key of Object.keys(args)) {
|
|
174
|
+
if (!allowed.includes(key)) {
|
|
175
|
+
throw new Error(`stroke-width legend does not accept ${key}.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const count = args.count ?? previous.count;
|
|
179
|
+
if (!Number.isInteger(count) || count < 2) {
|
|
180
|
+
throw new RangeError(
|
|
181
|
+
"Stroke-width legend count must be an integer of at least 2."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const layer = findLayer(program, previous.target);
|
|
185
|
+
const titleMode = args.title;
|
|
186
|
+
const inferredTitle = titleMode === "auto"
|
|
187
|
+
? true
|
|
188
|
+
: typeof titleMode === "string" ? false : previous.inferredTitle;
|
|
189
|
+
const titleVisible = titleMode === false
|
|
190
|
+
? false
|
|
191
|
+
: titleMode === undefined ? previous.titleVisible !== false : true;
|
|
192
|
+
const title = titleMode === "auto"
|
|
193
|
+
? layer?.encoding?.strokeWidth?.field
|
|
194
|
+
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
195
|
+
const config = {
|
|
196
|
+
...previous,
|
|
197
|
+
title,
|
|
198
|
+
inferredTitle,
|
|
199
|
+
titleVisible,
|
|
200
|
+
count,
|
|
201
|
+
labels: normalizeLegendTextOptions(
|
|
202
|
+
args.labels,
|
|
203
|
+
"editLegend.labels",
|
|
204
|
+
previous.labels ?? STROKE_WIDTH_LEGEND_LABELS
|
|
205
|
+
),
|
|
206
|
+
titleStyle: normalizeLegendTextOptions(
|
|
207
|
+
args.titleStyle,
|
|
208
|
+
"editLegend.titleStyle",
|
|
209
|
+
previous.titleStyle ?? STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
210
|
+
)
|
|
211
|
+
};
|
|
212
|
+
let next = program;
|
|
213
|
+
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
214
|
+
next = next.editSemantic({
|
|
215
|
+
property: "guide.legend.strokeWidth.title",
|
|
216
|
+
value: title
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
next = next._withLegendConfig("strokeWidth", config);
|
|
220
|
+
next = reconcileGraphic(next, "strokeWidthLegendTitle", titleVisible, {
|
|
221
|
+
type: "text"
|
|
222
|
+
});
|
|
223
|
+
return next.rematerializeStrokeWidthLegend();
|
|
224
|
+
}
|
|
225
|
+
|
|
166
226
|
function categoricalSymbolIds(config) {
|
|
167
227
|
return new Set(config.symbol.layers.map(layer => symbolGraphic(config, layer.type)));
|
|
168
228
|
}
|
|
@@ -285,9 +345,7 @@ export const editLegend = action(
|
|
|
285
345
|
return editInterval(this, configs.interval, args);
|
|
286
346
|
}
|
|
287
347
|
if (configs.strokeWidth?.target === target) {
|
|
288
|
-
|
|
289
|
-
"editLegend does not yet support stroke-width legends; edit the owning scale instead."
|
|
290
|
-
);
|
|
348
|
+
return editStrokeWidth(this, configs.strokeWidth, args);
|
|
291
349
|
}
|
|
292
350
|
const continuousKind = ["gradient", "opacity"].find(
|
|
293
351
|
kind => configs[kind]?.target === target
|
|
@@ -2,38 +2,139 @@ import { action } from "../../../core/action.js";
|
|
|
2
2
|
import { validateKeys } from "../../../core/validation.js";
|
|
3
3
|
import { legendGraphicIds, legendResourcePolicy } from
|
|
4
4
|
"../../../materialization/guides/resources.js";
|
|
5
|
+
import { LEGEND_CHANNELS } from "../../../core/vocabulary.js";
|
|
5
6
|
import { resolveLegendTarget } from "./target.js";
|
|
6
7
|
|
|
7
|
-
const OPTIONS = Object.freeze(["target"]);
|
|
8
|
+
const OPTIONS = Object.freeze(["target", "channels"]);
|
|
9
|
+
|
|
10
|
+
function legendKindChannels(kind, config) {
|
|
11
|
+
if (["series", "color"].includes(kind)) return config.channels;
|
|
12
|
+
return {
|
|
13
|
+
size: ["size"],
|
|
14
|
+
gradient: ["color"],
|
|
15
|
+
interval: ["color"],
|
|
16
|
+
opacity: ["opacity"],
|
|
17
|
+
strokeWidth: ["strokeWidth"]
|
|
18
|
+
}[kind];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function validateRequestedChannels(channels) {
|
|
22
|
+
if (!Array.isArray(channels)) {
|
|
23
|
+
throw new TypeError("removeLegend channels must be an array.");
|
|
24
|
+
}
|
|
25
|
+
if (channels.length === 0) {
|
|
26
|
+
throw new Error("removeLegend channels must select at least one channel.");
|
|
27
|
+
}
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
for (const channel of channels) {
|
|
30
|
+
if (!LEGEND_CHANNELS.includes(channel)) {
|
|
31
|
+
throw new Error(`Unsupported legend channel "${channel}".`);
|
|
32
|
+
}
|
|
33
|
+
if (seen.has(channel)) {
|
|
34
|
+
throw new Error(`removeLegend channels contains duplicate "${channel}".`);
|
|
35
|
+
}
|
|
36
|
+
seen.add(channel);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveRequestedKinds(program, target, channels) {
|
|
41
|
+
validateRequestedChannels(channels);
|
|
42
|
+
const requested = new Set(channels);
|
|
43
|
+
const matched = new Set();
|
|
44
|
+
const kinds = [];
|
|
45
|
+
for (const [kind, config] of Object.entries(program.guideConfigs.legend ?? {})) {
|
|
46
|
+
if (config?.target !== target) continue;
|
|
47
|
+
const owned = legendKindChannels(kind, config);
|
|
48
|
+
const overlap = owned.filter(channel => requested.has(channel));
|
|
49
|
+
if (overlap.length > 0 && overlap.length !== owned.length) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Legend channels ${owned.map(channel => `"${channel}"`).join(", ")} ` +
|
|
52
|
+
"belong to one combined block and must be removed together."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (overlap.length === owned.length) {
|
|
56
|
+
kinds.push(kind);
|
|
57
|
+
for (const channel of owned) matched.add(channel);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const missing = channels.filter(channel => !matched.has(channel));
|
|
61
|
+
if (missing.length > 0) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Legend target "${target}" has no complete block for channel "${missing[0]}".`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return kinds;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function removeLegendKinds(program, kinds) {
|
|
70
|
+
const removed = new Set(kinds);
|
|
71
|
+
const retainedSemanticKinds = new Set(
|
|
72
|
+
Object.entries(program.guideConfigs.legend ?? {})
|
|
73
|
+
.filter(([kind]) => !removed.has(kind))
|
|
74
|
+
.map(([kind]) => legendResourcePolicy(kind).semanticKind)
|
|
75
|
+
);
|
|
76
|
+
const semanticKinds = new Set(
|
|
77
|
+
kinds.map(kind => legendResourcePolicy(kind).semanticKind)
|
|
78
|
+
);
|
|
79
|
+
let next = program;
|
|
80
|
+
for (const kind of semanticKinds) {
|
|
81
|
+
if (
|
|
82
|
+
!retainedSemanticKinds.has(kind) &&
|
|
83
|
+
next.semanticSpec.guides.legend?.[kind] !== undefined
|
|
84
|
+
) {
|
|
85
|
+
next = next.editSemantic({
|
|
86
|
+
property: `guide.legend.${kind}`,
|
|
87
|
+
remove: true
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const id of new Set(kinds.flatMap(legendGraphicIds))) {
|
|
92
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
93
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const kind of kinds) {
|
|
97
|
+
next = next._withoutMaterializationConfig(["guides", "legend", kind]);
|
|
98
|
+
}
|
|
99
|
+
return next;
|
|
100
|
+
}
|
|
8
101
|
|
|
9
102
|
export const removeLegend = action(
|
|
10
103
|
{ op: "removeLegend", description: "Remove every legend block owned by one mark." },
|
|
11
104
|
function (args = {}) {
|
|
12
105
|
validateKeys(args, OPTIONS, "removeLegend");
|
|
13
106
|
const target = resolveLegendTarget(this, args.target, "removeLegend");
|
|
14
|
-
const
|
|
107
|
+
const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
|
|
15
108
|
.filter(([, config]) => config?.target === target)
|
|
16
109
|
.map(([kind]) => kind);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let next = this;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
110
|
+
const kinds = args.channels === undefined
|
|
111
|
+
? targetKinds
|
|
112
|
+
: resolveRequestedKinds(this, target, args.channels);
|
|
113
|
+
let next = removeLegendKinds(this, kinds);
|
|
114
|
+
const retainedKinds = Object.keys(next.guideConfigs.legend ?? {});
|
|
115
|
+
if (args.channels === undefined) {
|
|
116
|
+
const removedSemanticKinds = new Set(
|
|
117
|
+
kinds.map(kind => legendResourcePolicy(kind).semanticKind)
|
|
118
|
+
);
|
|
119
|
+
const hasSharedSemanticKind = retainedKinds.some(kind =>
|
|
120
|
+
removedSemanticKinds.has(legendResourcePolicy(kind).semanticKind)
|
|
121
|
+
);
|
|
122
|
+
return hasSharedSemanticKind ? next.rematerializeLegend() : next;
|
|
123
|
+
}
|
|
124
|
+
const remainingTargetKinds = targetKinds.filter(kind => !kinds.includes(kind));
|
|
125
|
+
const removedCategorical = kinds.some(kind => ["series", "color"].includes(kind));
|
|
126
|
+
if (
|
|
127
|
+
removedCategorical &&
|
|
128
|
+
remainingTargetKinds.includes("size") &&
|
|
129
|
+
next.guideConfigs.legend.size.inheritAppearance === true
|
|
130
|
+
) {
|
|
131
|
+
next = next._withLegendConfig("size", {
|
|
132
|
+
...next.guideConfigs.legend.size,
|
|
133
|
+
inheritAppearance: false
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return retainedKinds.length === 0
|
|
137
|
+
? next
|
|
138
|
+
: next.rematerializeLegend();
|
|
38
139
|
}
|
|
39
140
|
);
|
|
@@ -10,6 +10,21 @@ import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
|
10
10
|
|
|
11
11
|
const OPTIONS = Object.freeze(["target", "count"]);
|
|
12
12
|
|
|
13
|
+
export const STROKE_WIDTH_LEGEND_LABELS = Object.freeze({
|
|
14
|
+
offset: 12,
|
|
15
|
+
color: DEFAULT_COLORS.text,
|
|
16
|
+
fontSize: 12,
|
|
17
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
18
|
+
fontWeight: "normal"
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const STROKE_WIDTH_LEGEND_TITLE_STYLE = Object.freeze({
|
|
22
|
+
color: DEFAULT_COLORS.strongText,
|
|
23
|
+
fontSize: 13,
|
|
24
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
25
|
+
fontWeight: 600
|
|
26
|
+
});
|
|
27
|
+
|
|
13
28
|
export function isStrokeWidthLegendLayer(layer) {
|
|
14
29
|
return ["line", "rule"].includes(layer?.mark?.type) &&
|
|
15
30
|
layer.encoding?.strokeWidth?.scale !== undefined;
|
|
@@ -41,14 +56,12 @@ function requireScale(program, id) {
|
|
|
41
56
|
return scale;
|
|
42
57
|
}
|
|
43
58
|
|
|
44
|
-
function styleText(program, id,
|
|
59
|
+
function styleText(program, id, style) {
|
|
45
60
|
return program
|
|
46
|
-
.editGraphics({ target: id, property: "fill", value:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.editGraphics({ target: id, property: "
|
|
50
|
-
.editGraphics({ target: id, property: "fontFamily", value: DEFAULT_FONT_FAMILY })
|
|
51
|
-
.editGraphics({ target: id, property: "fontWeight", value: title ? 600 : "normal" })
|
|
61
|
+
.editGraphics({ target: id, property: "fill", value: style.color })
|
|
62
|
+
.editGraphics({ target: id, property: "fontSize", value: style.fontSize })
|
|
63
|
+
.editGraphics({ target: id, property: "fontFamily", value: style.fontFamily })
|
|
64
|
+
.editGraphics({ target: id, property: "fontWeight", value: style.fontWeight })
|
|
52
65
|
.editGraphics({ target: id, property: "textAlign", value: "left" })
|
|
53
66
|
.editGraphics({ target: id, property: "textBaseline", value: "middle" });
|
|
54
67
|
}
|
|
@@ -60,10 +73,16 @@ export const rematerializeStrokeWidthLegend = action(
|
|
|
60
73
|
},
|
|
61
74
|
function (args = {}) {
|
|
62
75
|
validateKeys(args, [], "rematerializeStrokeWidthLegend");
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
76
|
+
const stored = this.guideConfigs.legend?.strokeWidth;
|
|
77
|
+
if (stored === undefined) {
|
|
65
78
|
throw new Error("Stroke-width legend requires stored configuration.");
|
|
66
79
|
}
|
|
80
|
+
const config = {
|
|
81
|
+
...stored,
|
|
82
|
+
labels: stored.labels ?? { ...STROKE_WIDTH_LEGEND_LABELS },
|
|
83
|
+
titleStyle: stored.titleStyle ?? { ...STROKE_WIDTH_LEGEND_TITLE_STYLE },
|
|
84
|
+
titleVisible: stored.titleVisible !== false
|
|
85
|
+
};
|
|
67
86
|
const layer = findLayer(this, config.target);
|
|
68
87
|
const encoding = layer?.encoding?.strokeWidth;
|
|
69
88
|
if (encoding?.scale === undefined) {
|
|
@@ -101,18 +120,24 @@ export const rematerializeStrokeWidthLegend = action(
|
|
|
101
120
|
.editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke", value: DEFAULT_COLORS.mark })
|
|
102
121
|
.editGraphics({ target: "strokeWidthLegendSymbols", property: "strokeWidth", value: widths })
|
|
103
122
|
.editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: values.length })
|
|
104
|
-
.editGraphics({
|
|
123
|
+
.editGraphics({
|
|
124
|
+
target: "strokeWidthLegendLabels",
|
|
125
|
+
property: "x",
|
|
126
|
+
value: values.map(() => originX + 32 + config.labels.offset)
|
|
127
|
+
})
|
|
105
128
|
.editGraphics({ target: "strokeWidthLegendLabels", property: "y", value: y })
|
|
106
129
|
.editGraphics({
|
|
107
130
|
target: "strokeWidthLegendLabels",
|
|
108
131
|
property: "text",
|
|
109
132
|
value: values.map(value => String(+value.toPrecision(3)))
|
|
110
|
-
})
|
|
133
|
+
});
|
|
134
|
+
next = styleText(next, "strokeWidthLegendLabels", config.labels);
|
|
135
|
+
if (config.titleVisible === false) return next;
|
|
136
|
+
next = next
|
|
111
137
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: originX })
|
|
112
138
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: titleY })
|
|
113
139
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
|
|
114
|
-
|
|
115
|
-
return styleText(next, "strokeWidthLegendTitle", { title: true });
|
|
140
|
+
return styleText(next, "strokeWidthLegendTitle", config.titleStyle);
|
|
116
141
|
}
|
|
117
142
|
);
|
|
118
143
|
|
|
@@ -140,7 +165,10 @@ export const createStrokeWidthLegend = action(
|
|
|
140
165
|
scale: encoding.scale,
|
|
141
166
|
title: encoding.field,
|
|
142
167
|
inferredTitle: true,
|
|
143
|
-
count
|
|
168
|
+
count,
|
|
169
|
+
labels: { ...STROKE_WIDTH_LEGEND_LABELS },
|
|
170
|
+
titleStyle: { ...STROKE_WIDTH_LEGEND_TITLE_STYLE },
|
|
171
|
+
titleVisible: true
|
|
144
172
|
})
|
|
145
173
|
.createGraphics({
|
|
146
174
|
id: "strokeWidthLegendSymbols",
|
|
@@ -46,8 +46,8 @@ function validateInnerRadius(value) {
|
|
|
46
46
|
return value;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function normalizeConfig(args, previous = {}) {
|
|
50
|
-
|
|
49
|
+
function normalizeConfig(args, previous = {}, { allowStrokeRemoval = false } = {}) {
|
|
50
|
+
let config = {
|
|
51
51
|
...previous,
|
|
52
52
|
...(Object.hasOwn(args, "innerRadius")
|
|
53
53
|
? { innerRadius: validateInnerRadius(args.innerRadius) }
|
|
@@ -61,18 +61,28 @@ function normalizeConfig(args, previous = {}) {
|
|
|
61
61
|
...(Object.hasOwn(args, "opacity")
|
|
62
62
|
? { opacity: validateUnitInterval(args.opacity, "Arc opacity") }
|
|
63
63
|
: {}),
|
|
64
|
-
...(Object.hasOwn(args, "stroke")
|
|
64
|
+
...(Object.hasOwn(args, "stroke") && args.stroke !== false
|
|
65
65
|
? { stroke: validateNonEmptyString(args.stroke, "Arc stroke") }
|
|
66
|
-
: {}),
|
|
67
|
-
...(Object.hasOwn(args, "strokeWidth")
|
|
68
|
-
? {
|
|
69
|
-
strokeWidth: validateNonNegativeFinite(
|
|
70
|
-
args.strokeWidth,
|
|
71
|
-
"Arc strokeWidth"
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
66
|
: {})
|
|
75
67
|
};
|
|
68
|
+
if (args.stroke === false) {
|
|
69
|
+
if (!allowStrokeRemoval) {
|
|
70
|
+
throw new TypeError("Arc stroke must be a non-empty string.");
|
|
71
|
+
}
|
|
72
|
+
config.stroke = false;
|
|
73
|
+
delete config.strokeWidth;
|
|
74
|
+
} else if (Object.hasOwn(args, "stroke")) {
|
|
75
|
+
if (previous.stroke === false && !Object.hasOwn(args, "strokeWidth")) {
|
|
76
|
+
config.strokeWidth = 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (Object.hasOwn(args, "strokeWidth")) {
|
|
80
|
+
config.strokeWidth = validateNonNegativeFinite(
|
|
81
|
+
args.strokeWidth,
|
|
82
|
+
"Arc strokeWidth"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return config;
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
const createArcMark = action(
|
|
@@ -189,8 +199,16 @@ const rematerializeArcMark = action(
|
|
|
189
199
|
.editGraphics({ target: id, property: "commands", value: commands })
|
|
190
200
|
.editGraphics({ target: id, property: "fill", value: fills })
|
|
191
201
|
.editGraphics({ target: id, property: "opacity", value: config.opacity ?? 1 })
|
|
192
|
-
.editGraphics({
|
|
193
|
-
|
|
202
|
+
.editGraphics({
|
|
203
|
+
target: id,
|
|
204
|
+
property: "stroke",
|
|
205
|
+
value: config.stroke === false ? "transparent" : config.stroke ?? "#ffffff"
|
|
206
|
+
})
|
|
207
|
+
.editGraphics({
|
|
208
|
+
target: id,
|
|
209
|
+
property: "strokeWidth",
|
|
210
|
+
value: config.stroke === false ? 0 : config.strokeWidth ?? 1
|
|
211
|
+
})
|
|
194
212
|
.editGraphics({
|
|
195
213
|
target: id,
|
|
196
214
|
property: "strokeDash",
|
|
@@ -222,9 +240,23 @@ const editArcMark = action(
|
|
|
222
240
|
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
223
241
|
throw new Error("editArcMark fill cannot be combined with a color encoding.");
|
|
224
242
|
}
|
|
243
|
+
if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"editArcMark cannot set strokeWidth while removing stroke."
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (
|
|
249
|
+
Object.hasOwn(args, "strokeWidth") &&
|
|
250
|
+
!Object.hasOwn(args, "stroke") &&
|
|
251
|
+
this.markConfigs[layer.id]?.stroke === false
|
|
252
|
+
) {
|
|
253
|
+
throw new Error("editArcMark strokeWidth requires an active stroke.");
|
|
254
|
+
}
|
|
225
255
|
const next = this._withMarkConfig(
|
|
226
256
|
layer.id,
|
|
227
|
-
normalizeConfig(args, this.markConfigs[layer.id]
|
|
257
|
+
normalizeConfig(args, this.markConfigs[layer.id], {
|
|
258
|
+
allowStrokeRemoval: true
|
|
259
|
+
})
|
|
228
260
|
);
|
|
229
261
|
return canMaterializeArc(next, layer)
|
|
230
262
|
? next.rematerializeArcMark({ id: layer.id })
|
|
@@ -61,12 +61,24 @@ export const editPointMark = action(
|
|
|
61
61
|
if (Object.hasOwn(args, "opacity")) {
|
|
62
62
|
config.opacity = validateUnitInterval(args.opacity, "Point opacity");
|
|
63
63
|
}
|
|
64
|
+
if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"editPointMark cannot set strokeWidth while removing stroke."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
64
69
|
if (Object.hasOwn(args, "stroke")) {
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
if (args.stroke === false) {
|
|
71
|
+
config.stroke = false;
|
|
72
|
+
delete config.strokeWidth;
|
|
73
|
+
} else {
|
|
74
|
+
const restoresStroke = config.stroke === false;
|
|
75
|
+
config.stroke = validateNonEmptyString(args.stroke, "Point stroke");
|
|
76
|
+
if (restoresStroke) config.strokeWidth = 1;
|
|
77
|
+
else config.strokeWidth ??= 1;
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
80
|
if (Object.hasOwn(args, "strokeWidth")) {
|
|
69
|
-
if (config.stroke
|
|
81
|
+
if (typeof config.stroke !== "string") {
|
|
70
82
|
throw new Error("Point strokeWidth requires an active stroke.");
|
|
71
83
|
}
|
|
72
84
|
config.strokeWidth = validateNonNegativeFinite(
|
|
@@ -129,7 +129,9 @@ function resolveJitterEntries({
|
|
|
129
129
|
const extent = resolvePointShapeExtent({
|
|
130
130
|
shape: shapes[index],
|
|
131
131
|
area: resolvedArea,
|
|
132
|
-
strokeWidth: config.stroke ===
|
|
132
|
+
strokeWidth: typeof config.stroke === "string"
|
|
133
|
+
? config.strokeWidth ?? 1
|
|
134
|
+
: 0
|
|
133
135
|
});
|
|
134
136
|
return [{
|
|
135
137
|
index,
|
|
@@ -289,7 +291,7 @@ export const rematerializePointMark = action(
|
|
|
289
291
|
y: centerY,
|
|
290
292
|
area: resolvedArea,
|
|
291
293
|
fill: color,
|
|
292
|
-
...(config.stroke ===
|
|
294
|
+
...(typeof config.stroke === "string" ? { stroke: config.stroke } : {}),
|
|
293
295
|
...(config.strokeWidth === undefined
|
|
294
296
|
? {}
|
|
295
297
|
: { strokeWidth: config.strokeWidth }),
|
|
@@ -371,7 +373,11 @@ export const rematerializePointMark = action(
|
|
|
371
373
|
} else if (encodedOpacity !== undefined) {
|
|
372
374
|
next = next.editGraphics({ target: id, property: "opacity", value: encodedOpacity });
|
|
373
375
|
}
|
|
374
|
-
if (config.stroke
|
|
376
|
+
if (config.stroke === false) {
|
|
377
|
+
next = next
|
|
378
|
+
.editGraphics({ target: id, property: "stroke", value: "transparent" })
|
|
379
|
+
.editGraphics({ target: id, property: "strokeWidth", value: 0 });
|
|
380
|
+
} else if (config.stroke !== undefined) {
|
|
375
381
|
next = next.editGraphics({ target: id, property: "stroke", value: config.stroke });
|
|
376
382
|
next = next.editGraphics({
|
|
377
383
|
target: id,
|
|
@@ -238,7 +238,10 @@ const rematerializeRuleMark = action(
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
const resolvedConfig =
|
|
241
|
+
const resolvedConfig = {
|
|
242
|
+
...DEFAULT_RULE_CONFIG,
|
|
243
|
+
...resolved.markConfigs[id]
|
|
244
|
+
};
|
|
242
245
|
const dashEncoding = layer.encoding?.strokeDash;
|
|
243
246
|
const opacityEncoding = layer.encoding?.opacity;
|
|
244
247
|
const strokeWidthEncoding = layer.encoding?.strokeWidth;
|