ggaction 0.0.5 → 0.0.7
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 +48 -0
- package/README.md +77 -14
- package/package.json +19 -2
- package/src/BasicChartProgram.js +10 -0
- package/src/actions/basic.js +21 -0
- package/src/actions/boxPlots/edit.js +455 -3
- package/src/actions/boxPlots/options.js +3 -3
- package/src/actions/canvas/actions.js +42 -0
- package/src/actions/canvas/index.js +5 -1
- package/src/actions/charts/basic.js +13 -0
- package/src/actions/composition/actions.js +13 -3
- package/src/actions/data/basic.js +11 -0
- package/src/actions/data/basicBin2d.js +43 -0
- package/src/actions/data/bin2d.js +138 -40
- package/src/actions/data/bin2dMaterialize.js +29 -0
- 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 +41 -3
- package/src/actions/encodings/basic.js +21 -0
- package/src/actions/encodings/density.js +215 -80
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/position/index.js +5 -1
- package/src/actions/encodings/ranged.js +6 -2
- 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/axes/index.js +10 -2
- package/src/actions/guides/basic.js +19 -0
- package/src/actions/guides/grids/grid.js +7 -3
- package/src/actions/guides/grids/index.js +1 -1
- package/src/actions/guides/legends/categorical/actions.js +19 -8
- package/src/actions/guides/legends/categorical/index.js +6 -2
- package/src/actions/guides/legends/continuous/common.js +3 -3
- package/src/actions/guides/legends/continuous/index.js +6 -2
- 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/bar/index.js +5 -1
- package/src/actions/marks/basic.js +11 -0
- package/src/actions/marks/line/actions.js +5 -1
- package/src/actions/marks/line/index.js +4 -1
- package/src/actions/marks/point/edit.js +15 -3
- package/src/actions/marks/point/index.js +5 -1
- package/src/actions/marks/point/materialize.js +9 -3
- package/src/actions/marks/rect/actions.js +5 -1
- package/src/actions/marks/rect/index.js +4 -1
- package/src/actions/marks/rule/actions.js +4 -1
- package/src/actions/regression/edit.js +235 -74
- package/src/actions/scales/index.js +5 -1
- package/src/actions/selection/actions.js +122 -2
- package/src/actions/selection/index.js +6 -0
- package/src/basic.js +2 -0
- package/src/core/vocabulary.js +7 -0
- package/src/renderers/canvas/index.js +96 -60
- package/src/renderers/canvas/text.js +3 -14
- package/src/renderers/pdf.js +112 -0
- package/src/renderers/svg.js +559 -0
- package/src/renderers/text.js +11 -0
- package/types/basic.d.ts +107 -0
- package/types/index.d.ts +1 -0
- package/types/pdf.d.ts +26 -0
- package/types/program.d.ts +71 -7
- package/types/svg.d.ts +11 -0
|
@@ -360,14 +360,25 @@ export const createLegend = action(
|
|
|
360
360
|
symbol
|
|
361
361
|
});
|
|
362
362
|
if (requestedPoint.encoding?.size?.scale !== undefined) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
363
|
+
const existingSize = next.guideConfigs.legend?.size;
|
|
364
|
+
if (existingSize === undefined) {
|
|
365
|
+
next = next.createSizeLegend({
|
|
366
|
+
target: requestedPoint.id,
|
|
367
|
+
...(count === undefined ? {} : { count }),
|
|
368
|
+
inheritAppearance:
|
|
369
|
+
categoricalArgs.position === "left" ||
|
|
370
|
+
categoricalArgs.labels !== undefined ||
|
|
371
|
+
categoricalArgs.titleStyle !== undefined
|
|
372
|
+
});
|
|
373
|
+
} else if (existingSize.target !== requestedPoint.id) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
"Combined point series legend requires the active size legend to share its target."
|
|
376
|
+
);
|
|
377
|
+
} else if (count !== undefined && count !== existingSize.count) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
"Existing size legend count must be edited before recreating the categorical block."
|
|
380
|
+
);
|
|
381
|
+
}
|
|
371
382
|
next = next.rematerializeLegend();
|
|
372
383
|
}
|
|
373
384
|
return next;
|
|
@@ -25,9 +25,8 @@ import {
|
|
|
25
25
|
} from "./symbols.js";
|
|
26
26
|
import { editLegend } from "../edit.js";
|
|
27
27
|
|
|
28
|
-
export function
|
|
28
|
+
export function registerBasicCategoricalLegendActions(ProgramClass) {
|
|
29
29
|
ProgramClass.prototype.createLegend = createLegend;
|
|
30
|
-
ProgramClass.prototype.editLegend = editLegend;
|
|
31
30
|
ProgramClass.prototype.createCategoricalLegend = createCategoricalLegend;
|
|
32
31
|
ProgramClass.prototype.removeCategoricalLegend = removeCategoricalLegend;
|
|
33
32
|
ProgramClass.prototype.createLegendBackground = createLegendBackground;
|
|
@@ -47,3 +46,8 @@ export function registerCategoricalLegendActions(ProgramClass) {
|
|
|
47
46
|
ProgramClass.prototype.rematerializeLegendTitle = rematerializeLegendTitle;
|
|
48
47
|
ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
|
|
49
48
|
}
|
|
49
|
+
|
|
50
|
+
export function registerCategoricalLegendActions(ProgramClass) {
|
|
51
|
+
registerBasicCategoricalLegendActions(ProgramClass);
|
|
52
|
+
ProgramClass.prototype.editLegend = editLegend;
|
|
53
|
+
}
|
|
@@ -49,7 +49,7 @@ const DEFAULT_BORDER = Object.freeze({
|
|
|
49
49
|
export const validatePositive = validatePositiveFinite;
|
|
50
50
|
export const validateNonNegative = validateNonNegativeFinite;
|
|
51
51
|
|
|
52
|
-
function
|
|
52
|
+
export function normalizeLegendTextOptions(value, label, defaults) {
|
|
53
53
|
if (value === undefined) return { ...defaults };
|
|
54
54
|
if (!isPlainObject(value)) {
|
|
55
55
|
throw new TypeError(`${label} must be a plain object.`);
|
|
@@ -132,12 +132,12 @@ export function normalizeContinuousLegend(args, kind) {
|
|
|
132
132
|
count,
|
|
133
133
|
title: args.title,
|
|
134
134
|
inferredTitle: args.title === undefined,
|
|
135
|
-
labels:
|
|
135
|
+
labels: normalizeLegendTextOptions(
|
|
136
136
|
args.labels,
|
|
137
137
|
"createLegend.labels",
|
|
138
138
|
DEFAULT_LABELS
|
|
139
139
|
),
|
|
140
|
-
titleStyle:
|
|
140
|
+
titleStyle: normalizeLegendTextOptions(
|
|
141
141
|
args.titleStyle,
|
|
142
142
|
"createLegend.titleStyle",
|
|
143
143
|
DEFAULT_TITLE
|
|
@@ -15,11 +15,15 @@ export {
|
|
|
15
15
|
export { createIntervalLegend, rematerializeIntervalLegend } from "./interval.js";
|
|
16
16
|
|
|
17
17
|
export function registerContinuousLegendActions(ProgramClass) {
|
|
18
|
-
ProgramClass
|
|
19
|
-
ProgramClass.prototype.rematerializeGradientLegend = rematerializeGradientLegend;
|
|
18
|
+
registerGradientLegendActions(ProgramClass);
|
|
20
19
|
ProgramClass.prototype.createOpacityLegend = createOpacityLegend;
|
|
21
20
|
ProgramClass.prototype.rematerializeOpacityLegend = rematerializeOpacityLegend;
|
|
22
21
|
ProgramClass.prototype.removeOpacityLegend = removeOpacityLegend;
|
|
23
22
|
ProgramClass.prototype.createIntervalLegend = createIntervalLegend;
|
|
24
23
|
ProgramClass.prototype.rematerializeIntervalLegend = rematerializeIntervalLegend;
|
|
25
24
|
}
|
|
25
|
+
|
|
26
|
+
export function registerGradientLegendActions(ProgramClass) {
|
|
27
|
+
ProgramClass.prototype.createGradientLegend = createGradientLegend;
|
|
28
|
+
ProgramClass.prototype.rematerializeGradientLegend = rematerializeGradientLegend;
|
|
29
|
+
}
|
|
@@ -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 })
|
|
@@ -3,7 +3,11 @@ import { editBarMark } from "./edit.js";
|
|
|
3
3
|
import { rematerializeBarMark } from "./materialize.js";
|
|
4
4
|
|
|
5
5
|
export function registerBarMarkActions(ProgramClass) {
|
|
6
|
-
ProgramClass.prototype.createBarMark = createBarMark;
|
|
7
6
|
ProgramClass.prototype.editBarMark = editBarMark;
|
|
7
|
+
registerBasicBarMarkActions(ProgramClass);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function registerBasicBarMarkActions(ProgramClass) {
|
|
11
|
+
ProgramClass.prototype.createBarMark = createBarMark;
|
|
8
12
|
ProgramClass.prototype.rematerializeBarMark = rematerializeBarMark;
|
|
9
13
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerBarMarkActions } from "./bar/index.js";
|
|
2
|
+
import { registerLineMarkActions } from "./line/index.js";
|
|
3
|
+
import { registerPointMarkActions } from "./point/index.js";
|
|
4
|
+
import { registerRectMarkActions } from "./rect/index.js";
|
|
5
|
+
|
|
6
|
+
export function registerBasicMarkActions(ProgramClass) {
|
|
7
|
+
registerPointMarkActions(ProgramClass);
|
|
8
|
+
registerLineMarkActions(ProgramClass);
|
|
9
|
+
registerBarMarkActions(ProgramClass);
|
|
10
|
+
registerRectMarkActions(ProgramClass);
|
|
11
|
+
}
|
|
@@ -386,7 +386,11 @@ const editLineMark = action(
|
|
|
386
386
|
);
|
|
387
387
|
|
|
388
388
|
export function registerLineMarkActions(ProgramClass) {
|
|
389
|
-
ProgramClass.prototype.createLineMark = createLineMark;
|
|
390
389
|
ProgramClass.prototype.editLineMark = editLineMark;
|
|
390
|
+
registerBasicLineMarkActions(ProgramClass);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function registerBasicLineMarkActions(ProgramClass) {
|
|
394
|
+
ProgramClass.prototype.createLineMark = createLineMark;
|
|
391
395
|
ProgramClass.prototype.rematerializeLineMark = rematerializeLineMark;
|
|
392
396
|
}
|
|
@@ -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(
|
|
@@ -5,7 +5,11 @@ import { rematerializePointMark } from "./materialize.js";
|
|
|
5
5
|
export { registerPointJitterActions } from "./jitter.js";
|
|
6
6
|
|
|
7
7
|
export function registerPointMarkActions(ProgramClass) {
|
|
8
|
-
ProgramClass.prototype.createPointMark = createPointMark;
|
|
9
8
|
ProgramClass.prototype.editPointMark = editPointMark;
|
|
9
|
+
registerBasicPointMarkActions(ProgramClass);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerBasicPointMarkActions(ProgramClass) {
|
|
13
|
+
ProgramClass.prototype.createPointMark = createPointMark;
|
|
10
14
|
ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
|
|
11
15
|
}
|
|
@@ -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,
|