ggaction 0.0.9 → 0.0.11
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 +84 -0
- package/README.md +5 -4
- package/knowledge/action-card.schema.json +1 -0
- package/knowledge/action-cards.json +1 -10941
- package/knowledge/action-cards.schema.json +1 -0
- package/knowledge/extension-authoring.md +248 -0
- package/knowledge/intent-taxonomy.json +1 -183
- package/knowledge/intent-taxonomy.schema.json +1 -0
- package/knowledge/mcp-resources.json +1 -95
- package/knowledge/mcp-resources.schema.json +1 -0
- package/knowledge/task-packet.schema.json +1 -159
- package/knowledge/task-resolver.js +1 -1230
- package/package.json +19 -2
- package/src/actions/boxPlots/edit.js +45 -298
- package/src/actions/boxPlots/options.js +46 -77
- package/src/actions/boxPlots/resolve.js +1 -1
- package/src/actions/charts/bar.js +3 -0
- package/src/actions/charts/heatmap.js +73 -83
- package/src/actions/charts/index.js +1 -1
- package/src/actions/charts/parallel.js +35 -1
- package/src/actions/charts/shared.js +20 -0
- package/src/actions/composition/actions.js +46 -49
- package/src/actions/data/bin2d.js +105 -2
- package/src/actions/data/box.js +17 -17
- package/src/actions/data/density.js +42 -78
- package/src/actions/data/gradientProfile.js +22 -52
- package/src/actions/data/horizon.js +16 -41
- package/src/actions/data/index.js +10 -10
- package/src/actions/data/interval.js +16 -38
- package/src/actions/data/intervalEdit.js +55 -0
- package/src/actions/data/regression.js +7 -17
- package/src/actions/data/shared.js +47 -0
- package/src/actions/data/timeUnit.js +14 -38
- package/src/actions/data/window.js +14 -38
- package/src/actions/distributions/revision.js +154 -34
- package/src/actions/encodings/angle.js +9 -10
- package/src/actions/encodings/appearance.js +17 -51
- package/src/actions/encodings/density/resolve.js +3 -1
- package/src/actions/encodings/density.js +1 -15
- package/src/actions/encodings/horizon/resolve.js +13 -3
- package/src/actions/encodings/horizon.js +8 -7
- package/src/actions/encodings/index.js +9 -9
- package/src/actions/encodings/offset.js +8 -24
- package/src/actions/encodings/parallel.js +9 -10
- package/src/actions/encodings/pathOrder.js +6 -13
- package/src/actions/encodings/position/policies/index.js +4 -4
- package/src/actions/encodings/position/policies/line.js +47 -17
- package/src/actions/encodings/position/resolve.js +24 -5
- package/src/actions/encodings/ranged.js +18 -23
- package/src/actions/encodings/remove.js +15 -25
- package/src/actions/encodings/ruleAppearance.js +9 -24
- package/src/actions/encodings/shared.js +37 -0
- package/src/actions/encodings/strokeDash.js +8 -23
- package/src/actions/encodings/text.js +9 -10
- package/src/actions/errorBands/create.js +105 -39
- package/src/actions/errorBands/edit.js +89 -93
- package/src/actions/errorBands/index.js +1 -2
- package/src/actions/errorBars/create.js +78 -27
- package/src/actions/errorBars/edit.js +81 -48
- package/src/actions/errorBars/index.js +1 -2
- package/src/actions/facets/actions.js +45 -69
- package/src/actions/facets/derive.js +38 -53
- package/src/actions/facets/replay.js +0 -1
- package/src/actions/gradientPlots/components.js +59 -48
- package/src/actions/gradientPlots/edit.js +35 -216
- package/src/actions/gradientPlots/index.js +11 -10
- package/src/actions/gradientPlots/materialize.js +21 -35
- package/src/actions/gradientPlots/options.js +5 -7
- package/src/actions/gradientPlots/paint.js +9 -6
- package/src/actions/gradientPlots/resolve.js +1 -1
- package/src/actions/guides/axes/index.js +3 -3
- package/src/actions/guides/axes/labels.js +68 -26
- package/src/actions/guides/axes/lines.js +22 -2
- package/src/actions/guides/axes/policy.js +28 -8
- package/src/actions/guides/axes/ticks.js +27 -9
- package/src/actions/guides/axes/titles.js +113 -65
- package/src/actions/guides/grids/resolve.js +14 -0
- package/src/actions/guides/guides.js +2 -4
- package/src/actions/guides/index.js +2 -2
- package/src/actions/guides/legends/categorical/actions.js +44 -68
- package/src/actions/guides/legends/categorical/components.js +6 -1
- package/src/actions/guides/legends/categorical/index.js +2 -2
- package/src/actions/guides/legends/categorical/layout.js +148 -175
- package/src/actions/guides/legends/categorical/recipes.js +5 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -1
- package/src/actions/guides/legends/continuous/common.js +117 -66
- package/src/actions/guides/legends/continuous/gradient.js +38 -37
- package/src/actions/guides/legends/continuous/interval.js +97 -76
- package/src/actions/guides/legends/continuous/opacity.js +28 -38
- package/src/actions/guides/legends/edit.js +14 -1
- package/src/actions/guides/legends/index.js +1 -1
- package/src/actions/guides/legends/lane.js +2 -1
- package/src/actions/guides/legends/remove.js +8 -0
- package/src/actions/guides/legends/size.js +36 -43
- package/src/actions/guides/legends/strokeWidth.js +20 -26
- package/src/actions/guides/polar/axes/labels.js +17 -1
- package/src/actions/guides/polar/axes/shared.js +7 -1
- package/src/actions/guides/polar/axes/titles.js +19 -2
- package/src/actions/guides/polar/grids.js +4 -0
- package/src/actions/guides/polar/resolve.js +6 -0
- package/src/actions/index.js +13 -13
- package/src/actions/intervals/resolve.js +87 -117
- package/src/actions/marks/arc/actions.js +31 -36
- package/src/actions/marks/area/actions.js +23 -31
- package/src/actions/marks/index.js +6 -6
- package/src/actions/marks/line/actions.js +33 -52
- package/src/actions/marks/line/materialize.js +7 -5
- package/src/actions/marks/point/materialize.js +5 -3
- package/src/actions/marks/remove.js +21 -0
- package/src/actions/marks/rule/actions.js +53 -53
- package/src/actions/marks/shared.js +8 -4
- package/src/actions/marks/text/actions.js +14 -6
- package/src/actions/marks/text/layout.js +21 -45
- package/src/actions/marks/tick/actions.js +22 -21
- package/src/actions/primitives/createGraphics.js +4 -0
- package/src/actions/primitives/editGraphics.js +4 -0
- package/src/actions/primitives/index.js +3 -3
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +2 -2
- package/src/actions/primitives/semanticValidation/layer.js +1 -1
- package/src/actions/regression/edit.js +3 -43
- package/src/actions/scales/consumers/families.js +24 -4
- package/src/actions/scales/consumers/index.js +1 -1
- package/src/actions/scales/consumers/seriesLayout.js +17 -0
- package/src/actions/scales/definitions.js +100 -103
- package/src/actions/scales/materialize.js +5 -0
- package/src/actions/selection/actions.js +79 -121
- package/src/actions/titles/actions.js +48 -94
- package/src/actions/titles/resolve.js +14 -27
- package/src/actions/violinPlots/create.js +37 -44
- package/src/core/action.js +20 -5
- package/src/core/extensionRegistry.js +104 -0
- package/src/core/textMetrics.js +74 -37
- package/src/core/validation.js +18 -0
- package/src/extension.js +8 -1
- package/src/grammar/aggregate.js +48 -28
- package/src/grammar/arcs.js +58 -11
- package/src/grammar/areaSeries.js +14 -16
- package/src/grammar/bin2d.js +37 -23
- package/src/grammar/boxPlot.js +14 -13
- package/src/grammar/categoricalDensity.js +14 -14
- package/src/grammar/categoryOrder.js +8 -14
- package/src/grammar/curveCommands.js +87 -20
- package/src/grammar/density.js +162 -141
- package/src/grammar/facets/index.js +14 -0
- package/src/grammar/facets/scales.js +7 -4
- package/src/grammar/gradientProfile.js +12 -15
- package/src/grammar/histogram.js +145 -83
- package/src/grammar/horizon.js +60 -23
- package/src/grammar/interval.js +14 -13
- package/src/grammar/lineSeries.js +91 -61
- package/src/grammar/numeric.js +290 -0
- package/src/grammar/paint.js +3 -4
- package/src/grammar/palettes.js +21 -32
- package/src/grammar/pathCommands.js +6 -0
- package/src/grammar/polarPaths.js +5 -5
- package/src/grammar/regression/derive.js +48 -9
- package/src/grammar/regression/models.js +248 -69
- package/src/grammar/regression/parameters.js +8 -0
- package/src/grammar/scales/color.js +35 -26
- package/src/grammar/scales/continuous.js +71 -24
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/discretized.js +42 -12
- package/src/grammar/scales/fields.js +21 -4
- package/src/grammar/scales/ordinal.js +117 -61
- package/src/grammar/scales/temporal.js +40 -11
- package/src/grammar/scales/transformed.js +199 -52
- package/src/grammar/schemas/graphicBounds.js +270 -79
- package/src/grammar/seriesLayout.js +50 -18
- package/src/grammar/ticks.js +123 -53
- package/src/grammar/timeUnit.js +18 -8
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/window.js +70 -57
- package/src/index.js +1 -1
- package/src/layout/canvas.js +4 -8
- package/src/layout/composition.js +39 -40
- package/src/layout/facets.js +10 -14
- package/src/layout/labels.js +91 -15
- package/src/layout/legend.js +22 -20
- package/src/layout/legendLane.js +54 -40
- package/src/layout/title.js +7 -7
- package/src/materialization/bars/aggregate.js +14 -40
- package/src/materialization/bars/grouped.js +7 -18
- package/src/materialization/bars/histogram.js +2 -19
- package/src/materialization/bars/ranged.js +20 -15
- package/src/materialization/bars/resolve.js +21 -0
- package/src/materialization/facetGuides/legacyCategorical.js +2 -1
- package/src/materialization/facetGuides/placement.js +34 -19
- package/src/materialization/facetGuides/preparation.js +9 -5
- package/src/materialization/facets.js +39 -27
- package/src/materialization/guides/resources.js +2 -1
- package/src/materialization/marks/capabilities.js +5 -2
- package/src/materialization/marks/index.js +24 -4
- package/src/materialization/scales/policies/bar.js +61 -59
- package/src/materialization/scales/policies/binnedPosition.js +34 -0
- package/src/materialization/scales/policies/series.js +13 -7
- package/src/materialization/scales/resolve.js +4 -5
- package/src/materialization/selection/items/arc.js +16 -2
- package/src/materialization/selection/items/bar.js +4 -2
- package/src/materialization/selection/items/common.js +21 -1
- package/src/materialization/selection/items/path.js +19 -2
- package/src/materialization/selection/items/point.js +4 -16
- package/src/materialization/selection/items/tick.js +13 -21
- package/src/materialization/selection/point.js +11 -4
- package/src/materialization/selection/styles.js +19 -27
- package/src/materialization/text.js +4 -1
- package/src/mcp/adapter.js +8 -3
- package/src/renderers/canvas/fill.js +2 -2
- package/src/renderers/canvas/index.js +35 -2
- package/src/renderers/canvas/native.js +160 -0
- package/src/renderers/pdf.js +14 -0
- package/src/renderers/svg.js +37 -4
- package/types/extension.d.ts +19 -0
- package/types/index.d.ts +3 -0
- package/types/program.d.ts +509 -131
- package/types/svg.d.ts +1 -0
- package/src/actions/errorBands/components.js +0 -81
- package/src/actions/errorBands/options.js +0 -39
- package/src/actions/errorBands/resolve.js +0 -28
- package/src/actions/errorBars/components.js +0 -62
- package/src/actions/errorBars/options.js +0 -58
- package/src/actions/errorBars/resolve.js +0 -25
|
@@ -8,9 +8,15 @@ import { formatDiscretizedIntervals } from "../../../../grammar/scales/index.js"
|
|
|
8
8
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
9
9
|
"../../../../theme/defaults.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
assertLegendBoundsInsideCanvas,
|
|
12
|
+
editGraphicProperties,
|
|
13
|
+
editLegendBackground,
|
|
14
|
+
normalizeLegendBorder,
|
|
15
|
+
normalizeLegendTextOptions,
|
|
12
16
|
resolveContinuousBounds,
|
|
13
17
|
resolveContinuousPoint,
|
|
18
|
+
resolveLegendBackgroundFromBounds,
|
|
19
|
+
resolveLegendTextBounds,
|
|
14
20
|
styleContinuousText,
|
|
15
21
|
validateNonNegative,
|
|
16
22
|
validatePositive
|
|
@@ -18,27 +24,13 @@ import {
|
|
|
18
24
|
import { resolveLegendGraphicPlacement } from
|
|
19
25
|
"../../../../materialization/graphicHierarchy.js";
|
|
20
26
|
|
|
21
|
-
const OPTIONS =
|
|
27
|
+
const OPTIONS = [
|
|
22
28
|
"target", "channels", "position", "align", "offset", "title",
|
|
23
29
|
"symbol", "labels", "titleStyle", "itemGap", "direction", "border"
|
|
24
|
-
]
|
|
25
|
-
const SYMBOL_OPTIONS =
|
|
30
|
+
];
|
|
31
|
+
const SYMBOL_OPTIONS = [
|
|
26
32
|
"width", "height", "stroke", "strokeWidth"
|
|
27
|
-
]
|
|
28
|
-
const TEXT_OPTIONS = Object.freeze([
|
|
29
|
-
"offset", "color", "fontSize", "fontFamily", "fontWeight"
|
|
30
|
-
]);
|
|
31
|
-
|
|
32
|
-
function textOptions(value, label, defaults) {
|
|
33
|
-
if (value !== undefined && !isPlainObject(value)) {
|
|
34
|
-
throw new TypeError(`${label} must be a plain object.`);
|
|
35
|
-
}
|
|
36
|
-
validateKeys(value ?? {}, TEXT_OPTIONS, label);
|
|
37
|
-
const result = { ...defaults, ...(value ?? {}) };
|
|
38
|
-
if (result.offset !== undefined) validateNonNegative(result.offset, `${label} offset`);
|
|
39
|
-
validatePositive(result.fontSize, `${label} fontSize`);
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
33
|
+
];
|
|
42
34
|
|
|
43
35
|
export function normalizeIntervalLegend(args) {
|
|
44
36
|
validateOptionObject(args, OPTIONS, "createLegend");
|
|
@@ -55,18 +47,16 @@ export function normalizeIntervalLegend(args) {
|
|
|
55
47
|
if ((args.direction ?? "vertical") !== "vertical") {
|
|
56
48
|
throw new Error('Interval legends currently support direction "vertical".');
|
|
57
49
|
}
|
|
58
|
-
if (args.border !== undefined && args.border !== false) {
|
|
59
|
-
throw new Error("Interval legend border is not implemented yet.");
|
|
60
|
-
}
|
|
61
50
|
if (args.symbol !== undefined && !isPlainObject(args.symbol)) {
|
|
62
51
|
throw new TypeError("createLegend.symbol must be a plain object.");
|
|
63
52
|
}
|
|
64
|
-
|
|
53
|
+
const source = args.symbol ?? {};
|
|
54
|
+
validateKeys(source, SYMBOL_OPTIONS, "createLegend.symbol");
|
|
65
55
|
const symbol = {
|
|
66
|
-
width:
|
|
67
|
-
height:
|
|
68
|
-
stroke:
|
|
69
|
-
strokeWidth:
|
|
56
|
+
width: source.width ?? 14,
|
|
57
|
+
height: source.height ?? 12,
|
|
58
|
+
stroke: source.stroke ?? "white",
|
|
59
|
+
strokeWidth: source.strokeWidth ?? 0.5
|
|
70
60
|
};
|
|
71
61
|
validatePositive(symbol.width, "Legend symbol width");
|
|
72
62
|
validatePositive(symbol.height, "Legend symbol height");
|
|
@@ -86,20 +76,20 @@ export function normalizeIntervalLegend(args) {
|
|
|
86
76
|
inferredTitle: args.title === undefined,
|
|
87
77
|
titleVisible: true,
|
|
88
78
|
symbol,
|
|
89
|
-
labels:
|
|
79
|
+
labels: normalizeLegendTextOptions(args.labels, "createLegend.labels", {
|
|
90
80
|
offset: 8,
|
|
91
81
|
color: DEFAULT_COLORS.text,
|
|
92
82
|
fontSize: 12,
|
|
93
83
|
fontFamily: DEFAULT_FONT_FAMILY,
|
|
94
84
|
fontWeight: "normal"
|
|
95
85
|
}),
|
|
96
|
-
titleStyle:
|
|
86
|
+
titleStyle: normalizeLegendTextOptions(args.titleStyle, "createLegend.titleStyle", {
|
|
97
87
|
color: DEFAULT_COLORS.text,
|
|
98
88
|
fontSize: 13,
|
|
99
89
|
fontFamily: DEFAULT_FONT_FAMILY,
|
|
100
90
|
fontWeight: 600
|
|
101
91
|
}),
|
|
102
|
-
border:
|
|
92
|
+
border: normalizeLegendBorder(args.border)
|
|
103
93
|
};
|
|
104
94
|
}
|
|
105
95
|
|
|
@@ -113,17 +103,12 @@ function resolveIntervalConfig(program, stored) {
|
|
|
113
103
|
if (!["quantize", "quantile", "threshold"].includes(scale?.type)) {
|
|
114
104
|
throw new Error(`Interval legend requires a resolved discretized scale "${encoding.scale}".`);
|
|
115
105
|
}
|
|
116
|
-
return {
|
|
117
|
-
layer,
|
|
118
|
-
encoding,
|
|
119
|
-
scale,
|
|
120
|
-
config: {
|
|
106
|
+
return { encoding, scale, config: {
|
|
121
107
|
...stored,
|
|
122
108
|
target: layer.id,
|
|
123
109
|
scale: encoding.scale,
|
|
124
110
|
title: stored.inferredTitle ? encoding.field : stored.title
|
|
125
|
-
}
|
|
126
|
-
};
|
|
111
|
+
} };
|
|
127
112
|
}
|
|
128
113
|
|
|
129
114
|
function resolveIntervalLayout(program, config, scale) {
|
|
@@ -133,14 +118,37 @@ function resolveIntervalLayout(program, config, scale) {
|
|
|
133
118
|
const itemY = labels.map((_, index) => plot.y + 52 + index * config.itemGap);
|
|
134
119
|
const labelX = symbolX + config.symbol.width + config.labels.offset;
|
|
135
120
|
const title = { x: symbolX, y: plot.y + 20 };
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
const strokeExtent = config.symbol.strokeWidth / 2;
|
|
122
|
+
const occupiedBounds = [
|
|
123
|
+
resolveLegendTextBounds(
|
|
124
|
+
{ ...title, align: "left" },
|
|
125
|
+
config.title,
|
|
126
|
+
config.titleStyle
|
|
127
|
+
),
|
|
128
|
+
...labels.map((label, index) => resolveLegendTextBounds(
|
|
129
|
+
{ x: labelX, y: itemY[index], align: "left" },
|
|
130
|
+
label,
|
|
131
|
+
config.labels
|
|
132
|
+
)),
|
|
133
|
+
...itemY.map(y => ({
|
|
134
|
+
left: symbolX - strokeExtent,
|
|
135
|
+
right: symbolX + config.symbol.width + strokeExtent,
|
|
136
|
+
top: y - config.symbol.height / 2 - strokeExtent,
|
|
137
|
+
bottom: y + config.symbol.height / 2 + strokeExtent
|
|
138
|
+
}))
|
|
139
|
+
];
|
|
140
|
+
assertLegendBoundsInsideCanvas(
|
|
141
|
+
occupiedBounds,
|
|
142
|
+
canvas,
|
|
143
|
+
"Interval legend layout"
|
|
144
|
+
);
|
|
145
|
+
const background = resolveLegendBackgroundFromBounds(
|
|
146
|
+
occupiedBounds,
|
|
147
|
+
config.border,
|
|
148
|
+
canvas,
|
|
149
|
+
"Interval legend"
|
|
150
|
+
);
|
|
151
|
+
return { labels, symbolX, labelX, itemY, title, background };
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
export const rematerializeIntervalLegend = action(
|
|
@@ -156,36 +164,38 @@ export const rematerializeIntervalLegend = action(
|
|
|
156
164
|
}
|
|
157
165
|
const { encoding, scale, config } = resolveIntervalConfig(this, stored);
|
|
158
166
|
const layout = resolveIntervalLayout(this, config, scale);
|
|
159
|
-
let next = this
|
|
167
|
+
let next = editGraphicProperties(this
|
|
160
168
|
.editSemantic({ property: "guide.legend.color.scale", value: encoding.scale })
|
|
161
169
|
.editSemantic({ property: "guide.legend.color.title", value: config.title })
|
|
162
|
-
._withLegendConfig("interval", config)
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
.
|
|
170
|
+
._withLegendConfig("interval", config), "colorLegendSymbols", {
|
|
171
|
+
length: scale.range.length,
|
|
172
|
+
x: layout.symbolX,
|
|
173
|
+
y: layout.itemY.map(value => value - config.symbol.height / 2),
|
|
174
|
+
width: config.symbol.width,
|
|
175
|
+
height: config.symbol.height,
|
|
176
|
+
fill: scale.range,
|
|
177
|
+
stroke: config.symbol.stroke,
|
|
178
|
+
strokeWidth: config.symbol.strokeWidth
|
|
179
|
+
});
|
|
180
|
+
next = editGraphicProperties(next, "colorLegendLabels", {
|
|
181
|
+
length: layout.labels.length,
|
|
182
|
+
x: layout.labelX,
|
|
183
|
+
y: layout.itemY,
|
|
184
|
+
text: layout.labels
|
|
185
|
+
});
|
|
186
|
+
next = editLegendBackground(
|
|
187
|
+
next,
|
|
188
|
+
"colorLegendBackground",
|
|
189
|
+
layout.background,
|
|
190
|
+
config.border
|
|
191
|
+
);
|
|
183
192
|
next = styleContinuousText(next, "colorLegendLabels", config.labels);
|
|
184
193
|
if (config.titleVisible === false) return next;
|
|
185
|
-
next = next
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
next = editGraphicProperties(next, "colorLegendTitle", {
|
|
195
|
+
x: layout.title.x,
|
|
196
|
+
y: layout.title.y,
|
|
197
|
+
text: config.title
|
|
198
|
+
});
|
|
189
199
|
return styleContinuousText(next, "colorLegendTitle", config.titleStyle);
|
|
190
200
|
}
|
|
191
201
|
);
|
|
@@ -202,7 +212,7 @@ export const createIntervalLegend = action(
|
|
|
202
212
|
if (this.graphicSpec.objects.colorLegendSymbols !== undefined) {
|
|
203
213
|
throw new Error("createIntervalLegend requires a missing interval legend.");
|
|
204
214
|
}
|
|
205
|
-
|
|
215
|
+
let next = this
|
|
206
216
|
.editSemantic({
|
|
207
217
|
property: "guide.legend.color.scale",
|
|
208
218
|
value: resolved.encoding.scale
|
|
@@ -211,23 +221,34 @@ export const createIntervalLegend = action(
|
|
|
211
221
|
property: "guide.legend.color.title",
|
|
212
222
|
value: resolved.config.title
|
|
213
223
|
})
|
|
214
|
-
._withLegendConfig("interval", resolved.config)
|
|
224
|
+
._withLegendConfig("interval", resolved.config);
|
|
225
|
+
const placement = resolveLegendGraphicPlacement(next);
|
|
226
|
+
if (resolved.config.border !== false) {
|
|
227
|
+
next = next.createGraphics({
|
|
228
|
+
id: "colorLegendBackground",
|
|
229
|
+
type: "rect",
|
|
230
|
+
...placement
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return next
|
|
215
234
|
.createGraphics({
|
|
216
235
|
id: "colorLegendSymbols",
|
|
217
236
|
type: "rect",
|
|
218
237
|
length: 0,
|
|
219
|
-
...resolveLegendGraphicPlacement(
|
|
238
|
+
...resolveLegendGraphicPlacement(next, resolved.config.border === false
|
|
239
|
+
? {}
|
|
240
|
+
: { after: "colorLegendBackground" })
|
|
220
241
|
})
|
|
221
242
|
.createGraphics({
|
|
222
243
|
id: "colorLegendLabels",
|
|
223
244
|
type: "text",
|
|
224
245
|
length: 0,
|
|
225
|
-
...
|
|
246
|
+
...placement
|
|
226
247
|
})
|
|
227
248
|
.createGraphics({
|
|
228
249
|
id: "colorLegendTitle",
|
|
229
250
|
type: "text",
|
|
230
|
-
...
|
|
251
|
+
...placement
|
|
231
252
|
})
|
|
232
253
|
.rematerializeIntervalLegend();
|
|
233
254
|
}
|
|
@@ -5,14 +5,15 @@ import { mapLinearValues } from "../../../../grammar/scales/index.js";
|
|
|
5
5
|
import { measureTextWidth } from "../../../../core/textMetrics.js";
|
|
6
6
|
import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
assertLegendBoundsInsideCanvas,
|
|
9
9
|
editLegendBackground,
|
|
10
10
|
formatContinuousValues,
|
|
11
11
|
normalizeContinuousLegend,
|
|
12
12
|
requireResolvedLegendScale,
|
|
13
13
|
resolveContinuousBounds,
|
|
14
14
|
resolveContinuousPoint,
|
|
15
|
-
|
|
15
|
+
resolveLegendBackgroundFromBounds,
|
|
16
|
+
resolveLegendTextBounds,
|
|
16
17
|
sampleContinuousValues,
|
|
17
18
|
styleContinuousText,
|
|
18
19
|
validateNonNegative,
|
|
@@ -170,45 +171,34 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
170
171
|
align: "center"
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
174
|
+
const strokeExtent = (config.symbol.strokeWidth ?? 0) / 2;
|
|
175
|
+
const symbolExtent = radius + strokeExtent;
|
|
176
|
+
const titleBounds = resolveLegendTextBounds(
|
|
177
|
+
title,
|
|
178
|
+
config.title,
|
|
179
|
+
config.titleStyle
|
|
180
|
+
);
|
|
181
|
+
const labelBounds = labels.map((label, index) =>
|
|
182
|
+
resolveLegendTextBounds(label, texts[index], config.labels)
|
|
183
|
+
);
|
|
184
|
+
const symbolBounds = symbols.map(symbol => ({
|
|
185
|
+
left: symbol.x - symbolExtent,
|
|
186
|
+
right: symbol.x + symbolExtent,
|
|
187
|
+
top: symbol.y - symbolExtent,
|
|
188
|
+
bottom: symbol.y + symbolExtent
|
|
189
|
+
}));
|
|
190
|
+
const occupiedBounds = [titleBounds, ...labelBounds, ...symbolBounds];
|
|
191
|
+
assertLegendBoundsInsideCanvas(
|
|
192
|
+
occupiedBounds,
|
|
189
193
|
canvas,
|
|
190
194
|
"Opacity legend layout"
|
|
191
195
|
);
|
|
192
|
-
const background =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
...symbols.map(symbol => ({
|
|
199
|
-
x: symbol.x - radius,
|
|
200
|
-
y: symbol.y - radius
|
|
201
|
-
})),
|
|
202
|
-
...labels.map((label, index) => ({
|
|
203
|
-
x: label.x + (label.align === "left"
|
|
204
|
-
? measureTextWidth(
|
|
205
|
-
texts[index],
|
|
206
|
-
config.labels
|
|
207
|
-
)
|
|
208
|
-
: label.align === "right" ? -42 : 0),
|
|
209
|
-
y: label.y
|
|
210
|
-
}))
|
|
211
|
-
], config.border, canvas, "Opacity legend");
|
|
196
|
+
const background = resolveLegendBackgroundFromBounds(
|
|
197
|
+
occupiedBounds,
|
|
198
|
+
config.border,
|
|
199
|
+
canvas,
|
|
200
|
+
"Opacity legend"
|
|
201
|
+
);
|
|
212
202
|
return { values, texts, symbols, labels, title, background };
|
|
213
203
|
}
|
|
214
204
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
validateGeneratedItemLimit,
|
|
4
|
+
validateOptionObject
|
|
5
|
+
} from "../../../core/validation.js";
|
|
3
6
|
import { normalizeOptions } from "./categorical/options.js";
|
|
4
7
|
import { symbolGraphic } from "./categorical/layout.js";
|
|
5
8
|
import {
|
|
@@ -170,6 +173,12 @@ function editInterval(program, previous, args) {
|
|
|
170
173
|
next = reconcileGraphic(next, "colorLegendTitle", titleVisible, {
|
|
171
174
|
type: "text"
|
|
172
175
|
});
|
|
176
|
+
next = reconcileGraphic(
|
|
177
|
+
next,
|
|
178
|
+
"colorLegendBackground",
|
|
179
|
+
normalized.border !== false,
|
|
180
|
+
{ type: "rect", before: "colorLegendSymbols" }
|
|
181
|
+
);
|
|
173
182
|
return next.rematerializeLegend();
|
|
174
183
|
}
|
|
175
184
|
|
|
@@ -186,6 +195,7 @@ function editStrokeWidth(program, previous, args) {
|
|
|
186
195
|
"Stroke-width legend count must be an integer of at least 2."
|
|
187
196
|
);
|
|
188
197
|
}
|
|
198
|
+
validateGeneratedItemLimit(count, "Stroke-width legend count");
|
|
189
199
|
const layer = findLayer(program, previous.target);
|
|
190
200
|
const titleMode = args.title;
|
|
191
201
|
const inferredTitle = titleMode === "auto"
|
|
@@ -310,6 +320,9 @@ function editCategorical(program, kind, previous, size, args) {
|
|
|
310
320
|
if (args.count !== undefined && (!Number.isInteger(args.count) || args.count < 2)) {
|
|
311
321
|
throw new RangeError("Size legend count must be an integer of at least 2.");
|
|
312
322
|
}
|
|
323
|
+
if (args.count !== undefined) {
|
|
324
|
+
validateGeneratedItemLimit(args.count, "Size legend count");
|
|
325
|
+
}
|
|
313
326
|
next = next._withLegendConfig("size", {
|
|
314
327
|
...size,
|
|
315
328
|
count: args.count ?? size.count,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { registerCategoricalLegendActions } from "./categorical/index.js";
|
|
2
1
|
import { registerContinuousLegendActions } from "./continuous/index.js";
|
|
2
|
+
import { registerCategoricalLegendActions } from "./categorical/index.js";
|
|
3
3
|
import { registerSizeLegendActions } from "./size.js";
|
|
4
4
|
import { registerStrokeWidthLegendActions } from "./strokeWidth.js";
|
|
5
5
|
import { registerFocusedLegendActions } from "./focused.js";
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
resolveConcreteGraphicBounds,
|
|
5
5
|
unionConcreteGraphicBounds
|
|
6
6
|
} from "../../../grammar/schemas/graphicBounds.js";
|
|
7
|
+
import { finiteMidpoint } from "../../../grammar/numeric.js";
|
|
7
8
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
8
9
|
import {
|
|
9
10
|
resolveHorizontalLegendLane,
|
|
@@ -120,7 +121,7 @@ function blockDescriptor(program, kind, config) {
|
|
|
120
121
|
bounds,
|
|
121
122
|
title,
|
|
122
123
|
symbol: {
|
|
123
|
-
centerX: (symbolAnchor.left
|
|
124
|
+
centerX: finiteMidpoint(symbolAnchor.left, symbolAnchor.right),
|
|
124
125
|
left: symbolBounds.left,
|
|
125
126
|
right: symbolBounds.right,
|
|
126
127
|
bounds: symbolAnchor
|
|
@@ -99,6 +99,14 @@ export function removeLegendKinds(program, kinds) {
|
|
|
99
99
|
return next;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
export function removeOwnedColorLegends(program, target) {
|
|
103
|
+
const kinds = Object.entries(program.guideConfigs.legend ?? {})
|
|
104
|
+
.filter(([kind, config]) => config?.target === target && (
|
|
105
|
+
config.channels?.includes("color") || ["gradient", "interval"].includes(kind)
|
|
106
|
+
)).map(([kind]) => kind);
|
|
107
|
+
return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
export const removeLegend = action(
|
|
103
111
|
{ op: "removeLegend", description: "Remove every legend block owned by one mark." },
|
|
104
112
|
function (args = {}) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
validateGeneratedItemLimit,
|
|
4
|
+
validateKeys
|
|
5
|
+
} from "../../../core/validation.js";
|
|
3
6
|
import { mapLinearValues } from "../../../grammar/scales/index.js";
|
|
4
7
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
5
8
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
@@ -9,6 +12,12 @@ import { resolveLayout as resolveCategoricalLayout } from
|
|
|
9
12
|
import { findLayer } from "../../../selectors/layers.js";
|
|
10
13
|
import { findCanvasGraphic, resolveLegendGraphicPlacement } from
|
|
11
14
|
"../../../materialization/graphicHierarchy.js";
|
|
15
|
+
import {
|
|
16
|
+
formatContinuousValues,
|
|
17
|
+
sampleContinuousValues,
|
|
18
|
+
selectLegendLayer,
|
|
19
|
+
styleContinuousText
|
|
20
|
+
} from "./continuous/common.js";
|
|
12
21
|
|
|
13
22
|
const SIZE_OPTIONS = Object.freeze(["target", "count"]);
|
|
14
23
|
|
|
@@ -18,13 +27,7 @@ export function isSizeLegendPoint(layer) {
|
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
export function resolveSizeLegendPoint(program, requested) {
|
|
21
|
-
const
|
|
22
|
-
const layer = requested === undefined
|
|
23
|
-
? candidates.length === 1 ? candidates[0] : undefined
|
|
24
|
-
: (() => {
|
|
25
|
-
const candidate = findLayer(program, requested);
|
|
26
|
-
return candidates.includes(candidate) ? candidate : undefined;
|
|
27
|
-
})();
|
|
30
|
+
const layer = selectLegendLayer(program, requested, isSizeLegendPoint);
|
|
28
31
|
if (layer === undefined) {
|
|
29
32
|
throw new Error(
|
|
30
33
|
requested === undefined
|
|
@@ -56,26 +59,6 @@ function bounds(program) {
|
|
|
56
59
|
return value;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
function styleText(program, id, { title = false, style } = {}) {
|
|
60
|
-
const resolved = style ?? {
|
|
61
|
-
color: title ? DEFAULT_COLORS.strongText : DEFAULT_COLORS.text,
|
|
62
|
-
fontSize: title ? 13 : 12,
|
|
63
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
64
|
-
fontWeight: title ? 600 : "normal"
|
|
65
|
-
};
|
|
66
|
-
return program
|
|
67
|
-
.editGraphics({
|
|
68
|
-
target: id,
|
|
69
|
-
property: "fill",
|
|
70
|
-
value: resolved.color
|
|
71
|
-
})
|
|
72
|
-
.editGraphics({ target: id, property: "fontSize", value: resolved.fontSize })
|
|
73
|
-
.editGraphics({ target: id, property: "fontFamily", value: resolved.fontFamily })
|
|
74
|
-
.editGraphics({ target: id, property: "fontWeight", value: resolved.fontWeight })
|
|
75
|
-
.editGraphics({ target: id, property: "textAlign", value: "left" })
|
|
76
|
-
.editGraphics({ target: id, property: "textBaseline", value: "middle" });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
62
|
export const rematerializeSizeLegend = action(
|
|
80
63
|
{
|
|
81
64
|
op: "rematerializeSizeLegend",
|
|
@@ -107,11 +90,7 @@ export const rematerializeSizeLegend = action(
|
|
|
107
90
|
const originX = leftLayout?.titleX ?? plot.x + plot.width + 30;
|
|
108
91
|
const seriesCount = categorical?.domain.length ?? 0;
|
|
109
92
|
const titleY = leftLayout?.titleY ?? plot.y + 56 + seriesCount * 34 + 22;
|
|
110
|
-
const values =
|
|
111
|
-
{ length: currentConfig.count },
|
|
112
|
-
(_, index) => scale.domain[0] +
|
|
113
|
-
index / (currentConfig.count - 1) * (scale.domain[1] - scale.domain[0])
|
|
114
|
-
);
|
|
93
|
+
const values = sampleContinuousValues(scale.domain, currentConfig.count);
|
|
115
94
|
const areas = mapLinearValues(values, scale.domain, scale.range, {
|
|
116
95
|
clamp: scale.clamp ?? false
|
|
117
96
|
});
|
|
@@ -143,22 +122,35 @@ export const rematerializeSizeLegend = action(
|
|
|
143
122
|
.editGraphics({
|
|
144
123
|
target: "sizeLegendLabels",
|
|
145
124
|
property: "text",
|
|
146
|
-
value: values.
|
|
125
|
+
value: formatContinuousValues(values, scale.domain, "quantitative")
|
|
147
126
|
})
|
|
148
127
|
.editGraphics({ target: "sizeLegendTitle", property: "x", value: originX })
|
|
149
128
|
.editGraphics({ target: "sizeLegendTitle", property: "y", value: titleY })
|
|
150
129
|
.editGraphics({ target: "sizeLegendTitle", property: "text", value: title });
|
|
151
|
-
next =
|
|
152
|
-
|
|
130
|
+
next = styleContinuousText(
|
|
131
|
+
next,
|
|
132
|
+
"sizeLegendLabels",
|
|
133
|
+
currentConfig.inheritAppearance === true
|
|
153
134
|
? categorical.labels
|
|
154
|
-
:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
135
|
+
: {
|
|
136
|
+
color: DEFAULT_COLORS.text,
|
|
137
|
+
fontSize: 12,
|
|
138
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
139
|
+
fontWeight: "normal"
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
return styleContinuousText(
|
|
143
|
+
next,
|
|
144
|
+
"sizeLegendTitle",
|
|
145
|
+
currentConfig.inheritAppearance === true
|
|
159
146
|
? categorical.titleStyle
|
|
160
|
-
:
|
|
161
|
-
|
|
147
|
+
: {
|
|
148
|
+
color: DEFAULT_COLORS.strongText,
|
|
149
|
+
fontSize: 13,
|
|
150
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
151
|
+
fontWeight: 600
|
|
152
|
+
}
|
|
153
|
+
);
|
|
162
154
|
}
|
|
163
155
|
);
|
|
164
156
|
|
|
@@ -179,6 +171,7 @@ export const createSizeLegend = action(
|
|
|
179
171
|
if (!Number.isInteger(count) || count < 2) {
|
|
180
172
|
throw new RangeError("Size legend count must be an integer of at least 2.");
|
|
181
173
|
}
|
|
174
|
+
validateGeneratedItemLimit(count, "Size legend count");
|
|
182
175
|
return this
|
|
183
176
|
.editSemantic({ property: "guide.legend.size.scale", value: encoding.scale })
|
|
184
177
|
.editSemantic({ property: "guide.legend.size.title", value: encoding.field })
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
validateGeneratedItemLimit,
|
|
4
|
+
validateKeys
|
|
5
|
+
} from "../../../core/validation.js";
|
|
3
6
|
import { mapContinuousScaleValues } from "../../../grammar/scales/index.js";
|
|
4
7
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
5
8
|
import { resolveLegendGraphicPlacement } from
|
|
@@ -7,6 +10,12 @@ import { resolveLegendGraphicPlacement } from
|
|
|
7
10
|
import { findLayer } from "../../../selectors/layers.js";
|
|
8
11
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
9
12
|
"../../../theme/defaults.js";
|
|
13
|
+
import {
|
|
14
|
+
formatContinuousValues,
|
|
15
|
+
sampleContinuousValues,
|
|
16
|
+
selectLegendLayer,
|
|
17
|
+
styleContinuousText
|
|
18
|
+
} from "./continuous/common.js";
|
|
10
19
|
|
|
11
20
|
const OPTIONS = Object.freeze(["target", "count"]);
|
|
12
21
|
|
|
@@ -31,13 +40,11 @@ export function isStrokeWidthLegendLayer(layer) {
|
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
function resolveLayer(program, requested) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return candidates.includes(candidate) ? candidate : undefined;
|
|
40
|
-
})();
|
|
43
|
+
const layer = selectLegendLayer(
|
|
44
|
+
program,
|
|
45
|
+
requested,
|
|
46
|
+
isStrokeWidthLegendLayer
|
|
47
|
+
);
|
|
41
48
|
if (layer === undefined) {
|
|
42
49
|
throw new Error(
|
|
43
50
|
requested === undefined
|
|
@@ -56,16 +63,6 @@ function requireScale(program, id) {
|
|
|
56
63
|
return scale;
|
|
57
64
|
}
|
|
58
65
|
|
|
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
66
|
export const rematerializeStrokeWidthLegend = action(
|
|
70
67
|
{
|
|
71
68
|
op: "rematerializeStrokeWidthLegend",
|
|
@@ -93,11 +90,7 @@ export const rematerializeStrokeWidthLegend = action(
|
|
|
93
90
|
if (plot === undefined) {
|
|
94
91
|
throw new Error("Stroke-width legend requires resolved plot bounds.");
|
|
95
92
|
}
|
|
96
|
-
const values =
|
|
97
|
-
{ length: config.count },
|
|
98
|
-
(_, index) => scale.domain[0] +
|
|
99
|
-
index / (config.count - 1) * (scale.domain[1] - scale.domain[0])
|
|
100
|
-
);
|
|
93
|
+
const values = sampleContinuousValues(scale.domain, config.count);
|
|
101
94
|
const widths = mapContinuousScaleValues(values, scale);
|
|
102
95
|
const originX = plot.x + plot.width + 30;
|
|
103
96
|
const titleY = plot.y + 28;
|
|
@@ -129,15 +122,15 @@ export const rematerializeStrokeWidthLegend = action(
|
|
|
129
122
|
.editGraphics({
|
|
130
123
|
target: "strokeWidthLegendLabels",
|
|
131
124
|
property: "text",
|
|
132
|
-
value: values.
|
|
125
|
+
value: formatContinuousValues(values, scale.domain, "quantitative")
|
|
133
126
|
});
|
|
134
|
-
next =
|
|
127
|
+
next = styleContinuousText(next, "strokeWidthLegendLabels", config.labels);
|
|
135
128
|
if (config.titleVisible === false) return next;
|
|
136
129
|
next = next
|
|
137
130
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: originX })
|
|
138
131
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: titleY })
|
|
139
132
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
|
|
140
|
-
return
|
|
133
|
+
return styleContinuousText(next, "strokeWidthLegendTitle", config.titleStyle);
|
|
141
134
|
}
|
|
142
135
|
);
|
|
143
136
|
|
|
@@ -157,6 +150,7 @@ export const createStrokeWidthLegend = action(
|
|
|
157
150
|
"Stroke-width legend count must be an integer of at least 2."
|
|
158
151
|
);
|
|
159
152
|
}
|
|
153
|
+
validateGeneratedItemLimit(count, "Stroke-width legend count");
|
|
160
154
|
return this
|
|
161
155
|
.editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
|
|
162
156
|
.editSemantic({ property: "guide.legend.strokeWidth.title", value: encoding.field })
|