ggaction 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +35 -13
- package/package.json +18 -7
- package/src/actions/boxPlots/create.js +13 -7
- package/src/actions/boxPlots/edit.js +484 -36
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +11 -4
- package/src/actions/boxPlots/resolve.js +10 -2
- package/src/actions/charts/bar.js +50 -0
- package/src/actions/charts/heatmap.js +205 -0
- package/src/actions/charts/histogram.js +58 -0
- package/src/actions/charts/index.js +15 -0
- package/src/actions/charts/line.js +69 -0
- package/src/actions/charts/parallel.js +76 -0
- package/src/actions/charts/scatter.js +56 -0
- package/src/actions/charts/shared.js +121 -0
- package/src/actions/composition/actions.js +13 -3
- package/src/actions/coordinates/parallel.js +50 -0
- package/src/actions/data/bin2d.js +260 -0
- package/src/actions/data/density.js +54 -13
- package/src/actions/data/derived.js +22 -0
- package/src/actions/data/filter.js +5 -0
- package/src/actions/data/gradientProfile.js +65 -0
- package/src/actions/data/horizon.js +71 -0
- package/src/actions/data/index.js +34 -2
- package/src/actions/data/intervalEdit.js +76 -0
- package/src/actions/data/window.js +50 -0
- package/src/actions/distributions/revision.js +326 -0
- package/src/actions/encodings/appearance.js +35 -1
- package/src/actions/encodings/color/index.js +17 -1
- package/src/actions/encodings/color/layout.js +8 -0
- package/src/actions/encodings/density/resolve.js +109 -0
- package/src/actions/encodings/density.js +330 -90
- package/src/actions/encodings/horizon/resolve.js +283 -0
- package/src/actions/encodings/horizon.js +364 -0
- package/src/actions/encodings/index.js +8 -0
- package/src/actions/encodings/parallel.js +115 -0
- package/src/actions/encodings/pathOrder.js +131 -0
- package/src/actions/encodings/position/apply.js +73 -4
- package/src/actions/encodings/position/index.js +15 -2
- package/src/actions/encodings/position/policies/arc.js +27 -4
- package/src/actions/encodings/position/policies/area.js +33 -2
- package/src/actions/encodings/position/policies/index.js +7 -2
- package/src/actions/encodings/position/policies/line.js +18 -2
- package/src/actions/encodings/position/resolve.js +24 -2
- package/src/actions/encodings/ranged.js +3 -1
- package/src/actions/encodings/remove.js +268 -0
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/errorBands/edit.js +121 -13
- package/src/actions/errorBars/edit.js +45 -8
- package/src/actions/errorBars/options.js +7 -1
- package/src/actions/facets/actions.js +137 -3
- package/src/actions/facets/derive.js +33 -3
- package/src/actions/facets/replay.js +7 -36
- package/src/actions/gradientPlots/components.js +177 -0
- package/src/actions/gradientPlots/create.js +96 -0
- package/src/actions/gradientPlots/edit.js +501 -0
- package/src/actions/gradientPlots/index.js +24 -0
- package/src/actions/gradientPlots/materialize.js +287 -0
- package/src/actions/gradientPlots/options.js +120 -0
- package/src/actions/gradientPlots/paint.js +107 -0
- package/src/actions/gradientPlots/rebind.js +51 -0
- package/src/actions/gradientPlots/resolve.js +81 -0
- package/src/actions/guides/applicability.js +44 -17
- package/src/actions/guides/axes/axes.js +37 -3
- package/src/actions/guides/axes/edit.js +129 -47
- package/src/actions/guides/axes/index.js +2 -0
- package/src/actions/guides/axes/labels.js +11 -6
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/titles.js +11 -8
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +67 -19
- package/src/actions/guides/legends/continuous/common.js +3 -3
- package/src/actions/guides/legends/edit.js +63 -0
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +124 -30
- package/src/actions/guides/legends/strokeWidth.js +198 -0
- package/src/actions/index.js +6 -0
- package/src/actions/marks/arc/actions.js +46 -14
- package/src/actions/marks/area/actions.js +60 -136
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +99 -80
- package/src/actions/marks/line/materialize.js +146 -0
- package/src/actions/marks/point/create.js +76 -0
- package/src/actions/marks/point/edit.js +97 -0
- package/src/actions/marks/point/index.js +11 -1
- package/src/actions/marks/point/jitter.js +72 -0
- package/src/actions/marks/point/{actions.js → materialize.js} +183 -172
- package/src/actions/marks/rect/actions.js +3 -0
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +29 -4
- package/src/actions/marks/text/actions.js +19 -4
- package/src/actions/marks/text/index.js +7 -1
- package/src/actions/marks/text/layout.js +321 -0
- package/src/actions/primitives/semanticValidation/dataset.js +22 -0
- package/src/actions/primitives/semanticValidation/guide.js +43 -0
- package/src/actions/primitives/semanticValidation/index.js +23 -0
- package/src/actions/primitives/semanticValidation/layer.js +74 -0
- package/src/actions/primitives/semanticValidation/scale.js +74 -0
- package/src/actions/primitives/semanticValidation/shared.js +5 -0
- package/src/actions/primitives/semanticValue.js +1 -204
- package/src/actions/regression/edit.js +247 -88
- package/src/actions/scales/consumers/common.js +18 -0
- package/src/actions/scales/consumers/families.js +14 -3
- package/src/actions/scales/definitions.js +30 -0
- package/src/actions/scales/edit.js +6 -204
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +26 -0
- package/src/actions/selection/actions.js +150 -5
- package/src/actions/selection/index.js +6 -0
- package/src/actions/violinPlots/create.js +198 -0
- package/src/actions/violinPlots/index.js +5 -0
- package/src/core/textMetrics.js +47 -0
- package/src/core/vocabulary.js +14 -2
- package/src/grammar/arcs.js +49 -12
- package/src/grammar/areaSeries.js +41 -5
- package/src/grammar/bin2d.js +333 -0
- package/src/grammar/categoricalDensity.js +192 -0
- package/src/grammar/coordinates.js +1 -1
- package/src/grammar/curveCommands.js +3 -1
- package/src/grammar/density.js +252 -26
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/facets/index.js +3 -1
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +69 -0
- package/src/grammar/paint.js +88 -0
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/appearance.js +21 -0
- package/src/grammar/schemas/concreteGraphic.js +11 -1
- package/src/grammar/schemas/graphicBounds.js +19 -37
- package/src/grammar/schemas/semanticPath.js +17 -2
- package/src/grammar/transforms.js +100 -11
- package/src/grammar/window.js +339 -0
- package/src/layout/labels.js +270 -0
- package/src/layout/text.js +4 -1
- package/src/layout/title.js +10 -20
- package/src/materialization/dataProvenance.js +56 -2
- package/src/materialization/dependencies.js +9 -14
- package/src/materialization/guides/resources.js +74 -24
- package/src/materialization/horizon.js +29 -0
- package/src/materialization/layout.js +15 -0
- package/src/materialization/marks/capabilities.js +21 -1
- package/src/materialization/marks/index.js +3 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +6 -2
- package/src/materialization/scaleGuideDependencies.js +8 -0
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/path.js +27 -1
- package/src/materialization/selection/items/point.js +1 -5
- package/src/materialization/selection/items/rect.js +21 -0
- package/src/materialization/selection/styles.js +20 -9
- package/src/materialization/text.js +12 -4
- package/src/renderers/canvas/fill.js +33 -0
- package/src/renderers/canvas/index.js +7 -0
- package/src/renderers/canvas/path.js +7 -4
- package/src/renderers/canvas/rect.js +9 -3
- package/src/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +6 -0
- package/types/index.d.ts +77 -0
- package/types/program.d.ts +703 -17
|
@@ -1,46 +1,140 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import { validateKeys } from "../../../core/validation.js";
|
|
3
|
-
import { legendGraphicIds } from
|
|
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"]);
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
}
|
|
17
101
|
|
|
18
102
|
export const removeLegend = action(
|
|
19
103
|
{ op: "removeLegend", description: "Remove every legend block owned by one mark." },
|
|
20
104
|
function (args = {}) {
|
|
21
105
|
validateKeys(args, OPTIONS, "removeLegend");
|
|
22
106
|
const target = resolveLegendTarget(this, args.target, "removeLegend");
|
|
23
|
-
const
|
|
107
|
+
const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
|
|
24
108
|
.filter(([, config]) => config?.target === target)
|
|
25
109
|
.map(([kind]) => kind);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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();
|
|
45
139
|
}
|
|
46
140
|
);
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateKeys } from "../../../core/validation.js";
|
|
3
|
+
import { mapContinuousScaleValues } from "../../../grammar/scales/index.js";
|
|
4
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
5
|
+
import { resolveLegendGraphicPlacement } from
|
|
6
|
+
"../../../materialization/graphicHierarchy.js";
|
|
7
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
8
|
+
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
9
|
+
"../../../theme/defaults.js";
|
|
10
|
+
|
|
11
|
+
const OPTIONS = Object.freeze(["target", "count"]);
|
|
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
|
+
|
|
28
|
+
export function isStrokeWidthLegendLayer(layer) {
|
|
29
|
+
return ["line", "rule"].includes(layer?.mark?.type) &&
|
|
30
|
+
layer.encoding?.strokeWidth?.scale !== undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveLayer(program, requested) {
|
|
34
|
+
const candidates = program.semanticSpec.layers.filter(isStrokeWidthLegendLayer);
|
|
35
|
+
const layer = requested === undefined
|
|
36
|
+
? candidates.length === 1 ? candidates[0] : undefined
|
|
37
|
+
: (() => {
|
|
38
|
+
const candidate = findLayer(program, requested);
|
|
39
|
+
return candidates.includes(candidate) ? candidate : undefined;
|
|
40
|
+
})();
|
|
41
|
+
if (layer === undefined) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
requested === undefined
|
|
44
|
+
? "Stroke-width legend requires one eligible line or rule mark or an explicit target."
|
|
45
|
+
: `Unknown stroke-width legend target "${requested}".`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return layer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function requireScale(program, id) {
|
|
52
|
+
const scale = program.resolvedScales[id];
|
|
53
|
+
if (scale === undefined || !["linear", "log", "pow", "sqrt", "symlog"].includes(scale.type)) {
|
|
54
|
+
throw new Error(`Stroke-width legend requires resolved quantitative scale "${id}".`);
|
|
55
|
+
}
|
|
56
|
+
return scale;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function styleText(program, id, style) {
|
|
60
|
+
return program
|
|
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 })
|
|
65
|
+
.editGraphics({ target: id, property: "textAlign", value: "left" })
|
|
66
|
+
.editGraphics({ target: id, property: "textBaseline", value: "middle" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const rematerializeStrokeWidthLegend = action(
|
|
70
|
+
{
|
|
71
|
+
op: "rematerializeStrokeWidthLegend",
|
|
72
|
+
description: "Rematerialize a quantitative stroke-width legend."
|
|
73
|
+
},
|
|
74
|
+
function (args = {}) {
|
|
75
|
+
validateKeys(args, [], "rematerializeStrokeWidthLegend");
|
|
76
|
+
const stored = this.guideConfigs.legend?.strokeWidth;
|
|
77
|
+
if (stored === undefined) {
|
|
78
|
+
throw new Error("Stroke-width legend requires stored configuration.");
|
|
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
|
+
};
|
|
86
|
+
const layer = findLayer(this, config.target);
|
|
87
|
+
const encoding = layer?.encoding?.strokeWidth;
|
|
88
|
+
if (encoding?.scale === undefined) {
|
|
89
|
+
throw new Error("Stroke-width legend target requires a strokeWidth encoding.");
|
|
90
|
+
}
|
|
91
|
+
const scale = requireScale(this, encoding.scale);
|
|
92
|
+
const plot = resolveGraphicBounds(this);
|
|
93
|
+
if (plot === undefined) {
|
|
94
|
+
throw new Error("Stroke-width legend requires resolved plot bounds.");
|
|
95
|
+
}
|
|
96
|
+
const values = Array.from(
|
|
97
|
+
{ length: config.count },
|
|
98
|
+
(_, index) => scale.domain[0] +
|
|
99
|
+
index / (config.count - 1) * (scale.domain[1] - scale.domain[0])
|
|
100
|
+
);
|
|
101
|
+
const widths = mapContinuousScaleValues(values, scale);
|
|
102
|
+
const originX = plot.x + plot.width + 30;
|
|
103
|
+
const titleY = plot.y + 28;
|
|
104
|
+
const y = values.map((_, index) => titleY + 34 + index * 32);
|
|
105
|
+
const title = config.inferredTitle === true ? encoding.field : config.title;
|
|
106
|
+
let next = this
|
|
107
|
+
.editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
|
|
108
|
+
.editSemantic({ property: "guide.legend.strokeWidth.title", value: title })
|
|
109
|
+
._withLegendConfig("strokeWidth", {
|
|
110
|
+
...config,
|
|
111
|
+
scale: encoding.scale,
|
|
112
|
+
title,
|
|
113
|
+
domain: scale.domain
|
|
114
|
+
})
|
|
115
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "length", value: values.length })
|
|
116
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "x1", value: values.map(() => originX) })
|
|
117
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "x2", value: values.map(() => originX + 32) })
|
|
118
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "y1", value: y })
|
|
119
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "y2", value: y })
|
|
120
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke", value: DEFAULT_COLORS.mark })
|
|
121
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "strokeWidth", value: widths })
|
|
122
|
+
.editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: values.length })
|
|
123
|
+
.editGraphics({
|
|
124
|
+
target: "strokeWidthLegendLabels",
|
|
125
|
+
property: "x",
|
|
126
|
+
value: values.map(() => originX + 32 + config.labels.offset)
|
|
127
|
+
})
|
|
128
|
+
.editGraphics({ target: "strokeWidthLegendLabels", property: "y", value: y })
|
|
129
|
+
.editGraphics({
|
|
130
|
+
target: "strokeWidthLegendLabels",
|
|
131
|
+
property: "text",
|
|
132
|
+
value: values.map(value => String(+value.toPrecision(3)))
|
|
133
|
+
});
|
|
134
|
+
next = styleText(next, "strokeWidthLegendLabels", config.labels);
|
|
135
|
+
if (config.titleVisible === false) return next;
|
|
136
|
+
next = next
|
|
137
|
+
.editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: originX })
|
|
138
|
+
.editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: titleY })
|
|
139
|
+
.editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
|
|
140
|
+
return styleText(next, "strokeWidthLegendTitle", config.titleStyle);
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
export const createStrokeWidthLegend = action(
|
|
145
|
+
{
|
|
146
|
+
op: "createStrokeWidthLegend",
|
|
147
|
+
description: "Create a quantitative stroke-width legend."
|
|
148
|
+
},
|
|
149
|
+
function (args = {}) {
|
|
150
|
+
validateKeys(args, OPTIONS, "createStrokeWidthLegend");
|
|
151
|
+
const layer = resolveLayer(this, args.target);
|
|
152
|
+
const encoding = layer.encoding.strokeWidth;
|
|
153
|
+
requireScale(this, encoding.scale);
|
|
154
|
+
const count = args.count ?? 5;
|
|
155
|
+
if (!Number.isInteger(count) || count < 2) {
|
|
156
|
+
throw new RangeError(
|
|
157
|
+
"Stroke-width legend count must be an integer of at least 2."
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return this
|
|
161
|
+
.editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
|
|
162
|
+
.editSemantic({ property: "guide.legend.strokeWidth.title", value: encoding.field })
|
|
163
|
+
._withLegendConfig("strokeWidth", {
|
|
164
|
+
target: layer.id,
|
|
165
|
+
scale: encoding.scale,
|
|
166
|
+
title: encoding.field,
|
|
167
|
+
inferredTitle: true,
|
|
168
|
+
count,
|
|
169
|
+
labels: { ...STROKE_WIDTH_LEGEND_LABELS },
|
|
170
|
+
titleStyle: { ...STROKE_WIDTH_LEGEND_TITLE_STYLE },
|
|
171
|
+
titleVisible: true
|
|
172
|
+
})
|
|
173
|
+
.createGraphics({
|
|
174
|
+
id: "strokeWidthLegendSymbols",
|
|
175
|
+
type: "line",
|
|
176
|
+
length: count,
|
|
177
|
+
...resolveLegendGraphicPlacement(this)
|
|
178
|
+
})
|
|
179
|
+
.createGraphics({
|
|
180
|
+
id: "strokeWidthLegendLabels",
|
|
181
|
+
type: "text",
|
|
182
|
+
length: count,
|
|
183
|
+
...resolveLegendGraphicPlacement(this)
|
|
184
|
+
})
|
|
185
|
+
.createGraphics({
|
|
186
|
+
id: "strokeWidthLegendTitle",
|
|
187
|
+
type: "text",
|
|
188
|
+
...resolveLegendGraphicPlacement(this)
|
|
189
|
+
})
|
|
190
|
+
.rematerializeStrokeWidthLegend();
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
export function registerStrokeWidthLegendActions(ProgramClass) {
|
|
195
|
+
ProgramClass.prototype.createStrokeWidthLegend = createStrokeWidthLegend;
|
|
196
|
+
ProgramClass.prototype.rematerializeStrokeWidthLegend =
|
|
197
|
+
rematerializeStrokeWidthLegend;
|
|
198
|
+
}
|
package/src/actions/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerCanvasActions } from "./canvas/index.js";
|
|
2
|
+
import { registerChartActions } from "./charts/index.js";
|
|
2
3
|
import { registerCoordinateActions } from "./coordinates/index.js";
|
|
3
4
|
import { registerCompositionActions } from "./composition/index.js";
|
|
4
5
|
import { registerDataActions } from "./data/index.js";
|
|
@@ -8,12 +9,14 @@ import { registerErrorBarActions } from "./errorBars/index.js";
|
|
|
8
9
|
import { registerFacetActions } from "./facets/index.js";
|
|
9
10
|
import { registerBoxPlotActions } from "./boxPlots/index.js";
|
|
10
11
|
import { registerGuideActions } from "./guides/index.js";
|
|
12
|
+
import { registerGradientPlotActions } from "./gradientPlots/index.js";
|
|
11
13
|
import { registerMarkActions } from "./marks/index.js";
|
|
12
14
|
import { registerPrimitiveActions } from "./primitives/index.js";
|
|
13
15
|
import { registerRegressionActions } from "./regression/index.js";
|
|
14
16
|
import { registerScaleActions } from "./scales/index.js";
|
|
15
17
|
import { registerTitleActions } from "./titles/index.js";
|
|
16
18
|
import { registerSelectionActions } from "./selection/index.js";
|
|
19
|
+
import { registerViolinPlotActions } from "./violinPlots/index.js";
|
|
17
20
|
|
|
18
21
|
export function registerActions(ProgramClass) {
|
|
19
22
|
registerPrimitiveActions(ProgramClass);
|
|
@@ -31,5 +34,8 @@ export function registerActions(ProgramClass) {
|
|
|
31
34
|
registerErrorBarActions(ProgramClass);
|
|
32
35
|
registerErrorBandActions(ProgramClass);
|
|
33
36
|
registerBoxPlotActions(ProgramClass);
|
|
37
|
+
registerGradientPlotActions(ProgramClass);
|
|
38
|
+
registerViolinPlotActions(ProgramClass);
|
|
39
|
+
registerChartActions(ProgramClass);
|
|
34
40
|
registerSelectionActions(ProgramClass);
|
|
35
41
|
}
|
|
@@ -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 })
|