ggaction 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +12 -1
- package/package.json +6 -1
- package/src/actions/boxPlots/components.js +3 -3
- package/src/actions/boxPlots/edit.js +277 -0
- package/src/actions/boxPlots/index.js +2 -0
- package/src/actions/boxPlots/materialize.js +12 -1
- package/src/actions/boxPlots/options.js +25 -14
- package/src/actions/canvas/actions.js +21 -17
- package/src/actions/composition/actions.js +247 -0
- package/src/actions/composition/index.js +1 -0
- package/src/actions/coordinates/actions.js +2 -5
- package/src/actions/data/density.js +7 -1
- package/src/actions/data/filter.js +3 -1
- package/src/actions/encodings/appearance.js +11 -0
- package/src/actions/encodings/barWidth.js +5 -3
- package/src/actions/encodings/color.js +120 -25
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +90 -72
- package/src/actions/encodings/position/apply.js +66 -0
- package/src/actions/encodings/position/policies/arc.js +26 -0
- package/src/actions/encodings/position/policies/bar.js +9 -5
- package/src/actions/encodings/position/policies/index.js +5 -1
- package/src/actions/encodings/position/policies/line.js +20 -3
- package/src/actions/encodings/position/resolve.js +57 -8
- package/src/actions/encodings/position.js +44 -90
- package/src/actions/encodings/ranged.js +28 -6
- package/src/actions/encodings/shared.js +17 -21
- package/src/actions/encodings/text.js +73 -0
- package/src/actions/errorBands/create.js +37 -29
- package/src/actions/errorBands/edit.js +206 -0
- package/src/actions/errorBands/index.js +9 -0
- package/src/actions/errorBands/options.js +39 -0
- package/src/actions/errorBars/create.js +50 -43
- package/src/actions/errorBars/edit.js +130 -0
- package/src/actions/errorBars/index.js +3 -0
- package/src/actions/errorBars/options.js +52 -0
- package/src/actions/facets/actions.js +162 -0
- package/src/actions/facets/derive.js +200 -0
- package/src/actions/facets/guides.js +16 -0
- package/src/actions/facets/index.js +1 -0
- package/src/actions/facets/replay.js +69 -0
- package/src/actions/guides/applicability.js +113 -0
- package/src/actions/guides/axes/axes.js +70 -16
- package/src/actions/guides/axes/edit.js +126 -0
- package/src/actions/guides/axes/index.js +12 -0
- package/src/actions/guides/axes/labels.js +21 -18
- package/src/actions/guides/axes/remove.js +60 -0
- package/src/actions/guides/axes/tickGroups.js +7 -5
- package/src/actions/guides/axes/ticks.js +9 -4
- package/src/actions/guides/axes/titles.js +7 -5
- package/src/actions/guides/grids/grid.js +144 -15
- package/src/actions/guides/grids/resolve.js +5 -13
- package/src/actions/guides/guides.js +17 -75
- package/src/actions/guides/index.js +6 -0
- package/src/actions/guides/legends/categorical/actions.js +48 -9
- package/src/actions/guides/legends/categorical/components.js +16 -16
- package/src/actions/guides/legends/categorical/index.js +14 -14
- package/src/actions/guides/legends/categorical/layout.js +3 -1
- package/src/actions/guides/legends/categorical/options.js +2 -5
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +14 -14
- package/src/actions/guides/legends/continuous/common.js +7 -7
- package/src/actions/guides/legends/continuous/interval.js +5 -5
- package/src/actions/guides/legends/edit.js +2 -6
- package/src/actions/guides/legends/focused.js +63 -0
- package/src/actions/guides/legends/index.js +4 -0
- package/src/actions/guides/legends/remove.js +64 -0
- package/src/actions/guides/legends/size.js +12 -14
- package/src/actions/guides/polar/axes/facade.js +219 -0
- package/src/actions/guides/polar/axes/labels.js +188 -0
- package/src/actions/guides/polar/axes/lines.js +113 -0
- package/src/actions/guides/polar/axes/shared.js +112 -0
- package/src/actions/guides/polar/axes/ticks.js +164 -0
- package/src/actions/guides/polar/axes/titles.js +165 -0
- package/src/actions/guides/polar/axes.js +55 -0
- package/src/actions/guides/polar/grids.js +252 -0
- package/src/actions/guides/polar/index.js +2 -0
- package/src/actions/guides/polar/resolve.js +269 -0
- package/src/actions/index.js +4 -0
- package/src/actions/marks/arc/actions.js +239 -0
- package/src/actions/marks/arc/index.js +1 -0
- package/src/actions/marks/{area.js → area/actions.js} +51 -38
- package/src/actions/marks/area/index.js +4 -0
- package/src/actions/marks/bar/create.js +26 -5
- package/src/actions/marks/bar/materialize.js +25 -31
- package/src/actions/marks/index.js +12 -4
- package/src/actions/marks/lifecycle.js +27 -0
- package/src/actions/marks/line/actions.js +373 -0
- package/src/actions/marks/line/index.js +1 -0
- package/src/actions/marks/{point.js → point/actions.js} +67 -108
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/rect/actions.js +165 -0
- package/src/actions/marks/rect/index.js +1 -0
- package/src/actions/marks/remove.js +214 -0
- package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
- package/src/actions/marks/rule/index.js +1 -0
- package/src/actions/marks/shared.js +156 -25
- package/src/actions/marks/text/actions.js +192 -0
- package/src/actions/marks/text/index.js +1 -0
- package/src/actions/primitives/createGraphics.js +13 -6
- package/src/actions/primitives/editGraphics.js +10 -3
- package/src/actions/primitives/semantic.js +26 -3
- package/src/actions/primitives/semanticValue.js +25 -3
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +18 -1
- package/src/actions/regression/edit.js +247 -0
- package/src/actions/regression/index.js +3 -0
- package/src/actions/regression/resolve.js +3 -1
- package/src/actions/scales/consumers.js +15 -6
- package/src/actions/scales/definitions.js +13 -4
- package/src/actions/scales/edit.js +42 -19
- package/src/actions/scales/materialize.js +57 -408
- package/src/actions/selection/actions.js +38 -24
- package/src/actions/selection/index.js +2 -0
- package/src/actions/titles/actions.js +63 -2
- package/src/actions/titles/index.js +2 -0
- package/src/actions/titles/resolve.js +12 -17
- package/src/composition.js +10 -0
- package/src/core/ChartProgram.js +200 -2
- package/src/core/action.js +10 -0
- package/src/core/validation.js +16 -0
- package/src/core/vocabulary.js +154 -4
- package/src/grammar/arcs.js +154 -0
- package/src/grammar/bars/aggregate.js +2 -2
- package/src/grammar/bars/geometry.js +3 -3
- package/src/grammar/bars/policy.js +7 -1
- package/src/grammar/coordinates.js +3 -7
- package/src/grammar/density.js +20 -1
- package/src/grammar/facets/dependencies.js +185 -0
- package/src/grammar/facets/guides.js +257 -0
- package/src/grammar/facets/scales.js +254 -0
- package/src/grammar/facets.js +122 -0
- package/src/grammar/lineSeries.js +111 -3
- package/src/grammar/polar.js +102 -0
- package/src/grammar/polarGuides.js +243 -0
- package/src/grammar/polarLineCommands.js +43 -0
- package/src/grammar/polarPaths.js +161 -0
- package/src/grammar/positionCompatibility.js +18 -2
- package/src/grammar/rects.js +26 -0
- package/src/grammar/scales/continuous.js +6 -1
- package/src/grammar/scales/fields.js +7 -0
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +1 -1
- package/src/grammar/schemas/graphic.js +1 -1
- package/src/grammar/schemas/graphicBounds.js +64 -9
- package/src/grammar/schemas/graphicTree.js +44 -10
- package/src/grammar/schemas/semanticPath.js +31 -9
- package/src/grammar/seriesLayout.js +13 -2
- package/src/grammar/text.js +71 -0
- package/src/grammar/ticks.js +50 -13
- package/src/index.js +1 -0
- package/src/layout/composition.js +310 -0
- package/src/layout/facets.js +131 -0
- package/src/layout/title.js +6 -0
- package/src/materialization/bars/aggregate.js +5 -2
- package/src/materialization/bars/grouped.js +75 -37
- package/src/materialization/composition.js +152 -0
- package/src/materialization/compositionSnapshot.js +114 -0
- package/src/materialization/dependencies.js +71 -16
- package/src/materialization/encodings.js +14 -28
- package/src/materialization/facetGuides.js +426 -0
- package/src/materialization/facets.js +214 -0
- package/src/materialization/graphicHierarchy.js +15 -5
- package/src/materialization/guides/resources.js +46 -0
- package/src/materialization/marks.js +220 -14
- package/src/materialization/planner.js +105 -12
- package/src/materialization/rect.js +138 -0
- package/src/materialization/rectConfig.js +38 -0
- package/src/materialization/rowEncoding.js +33 -0
- package/src/materialization/scales/map.js +40 -0
- package/src/materialization/scales/policies/arc.js +37 -0
- package/src/materialization/scales/policies/bar.js +132 -0
- package/src/materialization/scales/policies/series.js +76 -0
- package/src/materialization/scales/resolve.js +326 -0
- package/src/materialization/selection/items/arc.js +29 -0
- package/src/materialization/selection/items/bar.js +255 -0
- package/src/materialization/selection/items/common.js +131 -0
- package/src/materialization/selection/items/index.js +6 -0
- package/src/materialization/selection/items/path.js +59 -0
- package/src/materialization/selection/items/point.js +55 -0
- package/src/materialization/selection/items/rect.js +23 -0
- package/src/materialization/selection/items/rule.js +25 -0
- package/src/materialization/selection/policies/arc.js +10 -0
- package/src/materialization/selection/policies/area.js +1 -1
- package/src/materialization/selection/policies/bar.js +1 -1
- package/src/materialization/selection/policies/index.js +5 -1
- package/src/materialization/selection/policies/line.js +1 -1
- package/src/materialization/selection/policies/point.js +1 -1
- package/src/materialization/selection/policies/rect.js +10 -0
- package/src/materialization/selection/policies/rule.js +1 -1
- package/src/materialization/selection/styles.js +24 -16
- package/src/materialization/text.js +156 -0
- package/src/renderers/canvas/index.js +68 -22
- package/types/extension.d.ts +1 -0
- package/types/index.d.ts +56 -1
- package/types/program.d.ts +664 -27
- package/src/actions/marks/line.js +0 -248
- package/src/materialization/selection/items.js +0 -503
|
@@ -8,19 +8,19 @@ import {
|
|
|
8
8
|
createLegendBackground,
|
|
9
9
|
createLegendLabels,
|
|
10
10
|
createLegendTitle,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
rematerializeLegendBackground,
|
|
12
|
+
rematerializeLegendLabels,
|
|
13
|
+
rematerializeLegendTitle
|
|
14
14
|
} from "./components.js";
|
|
15
15
|
import {
|
|
16
16
|
createLegendSymbolLines,
|
|
17
17
|
createLegendSymbolPoints,
|
|
18
18
|
createLegendSymbolSwatches,
|
|
19
19
|
createLegendSymbols,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
rematerializeLegendSymbolLines,
|
|
21
|
+
rematerializeLegendSymbolPoints,
|
|
22
|
+
rematerializeLegendSymbolSwatches,
|
|
23
|
+
rematerializeLegendSymbols,
|
|
24
24
|
rematerializeLegendHighlights
|
|
25
25
|
} from "./symbols.js";
|
|
26
26
|
import { editLegend } from "../edit.js";
|
|
@@ -31,19 +31,19 @@ export function registerCategoricalLegendActions(ProgramClass) {
|
|
|
31
31
|
ProgramClass.prototype.createCategoricalLegend = createCategoricalLegend;
|
|
32
32
|
ProgramClass.prototype.removeCategoricalLegend = removeCategoricalLegend;
|
|
33
33
|
ProgramClass.prototype.createLegendBackground = createLegendBackground;
|
|
34
|
-
ProgramClass.prototype.
|
|
34
|
+
ProgramClass.prototype.rematerializeLegendBackground = rematerializeLegendBackground;
|
|
35
35
|
ProgramClass.prototype.createLegendSymbols = createLegendSymbols;
|
|
36
|
-
ProgramClass.prototype.
|
|
36
|
+
ProgramClass.prototype.rematerializeLegendSymbols = rematerializeLegendSymbols;
|
|
37
37
|
ProgramClass.prototype.rematerializeLegendHighlights = rematerializeLegendHighlights;
|
|
38
38
|
ProgramClass.prototype.createLegendSymbolLines = createLegendSymbolLines;
|
|
39
|
-
ProgramClass.prototype.
|
|
39
|
+
ProgramClass.prototype.rematerializeLegendSymbolLines = rematerializeLegendSymbolLines;
|
|
40
40
|
ProgramClass.prototype.createLegendSymbolPoints = createLegendSymbolPoints;
|
|
41
|
-
ProgramClass.prototype.
|
|
41
|
+
ProgramClass.prototype.rematerializeLegendSymbolPoints = rematerializeLegendSymbolPoints;
|
|
42
42
|
ProgramClass.prototype.createLegendSymbolSwatches = createLegendSymbolSwatches;
|
|
43
|
-
ProgramClass.prototype.
|
|
43
|
+
ProgramClass.prototype.rematerializeLegendSymbolSwatches = rematerializeLegendSymbolSwatches;
|
|
44
44
|
ProgramClass.prototype.createLegendLabels = createLegendLabels;
|
|
45
|
-
ProgramClass.prototype.
|
|
45
|
+
ProgramClass.prototype.rematerializeLegendLabels = rematerializeLegendLabels;
|
|
46
46
|
ProgramClass.prototype.createLegendTitle = createLegendTitle;
|
|
47
|
-
ProgramClass.prototype.
|
|
47
|
+
ProgramClass.prototype.rematerializeLegendTitle = rematerializeLegendTitle;
|
|
48
48
|
ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
|
|
49
49
|
}
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
measureLegendTextWidth,
|
|
11
11
|
resolveLegendGrid
|
|
12
12
|
} from "../../../../layout/legend.js";
|
|
13
|
+
import { findCanvasGraphic } from
|
|
14
|
+
"../../../../materialization/graphicHierarchy.js";
|
|
13
15
|
|
|
14
16
|
export function activeConfig(program) {
|
|
15
17
|
const entries = ["series", "color"]
|
|
@@ -366,7 +368,7 @@ function resolveCompactBottomLayout(bounds, canvas, config, width, count) {
|
|
|
366
368
|
|
|
367
369
|
export function resolveLayout(program, config) {
|
|
368
370
|
const bounds = resolveGraphicBounds(program);
|
|
369
|
-
const canvas = program
|
|
371
|
+
const canvas = findCanvasGraphic(program);
|
|
370
372
|
if (
|
|
371
373
|
bounds === undefined ||
|
|
372
374
|
![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) ||
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateOptionObject } from "../../../../core/validation.js";
|
|
2
2
|
import { CATEGORICAL_LEGEND_CHANNELS } from "../../../../core/vocabulary.js";
|
|
3
3
|
import { normalizeRecipe } from "./recipes.js";
|
|
4
4
|
import {
|
|
@@ -86,10 +86,7 @@ function normalizeBorder(border) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export function normalizeOptions(args, kind) {
|
|
89
|
-
|
|
90
|
-
throw new TypeError("createLegend options must be a plain object.");
|
|
91
|
-
}
|
|
92
|
-
validateKeys(args, OPTIONS, "createLegend");
|
|
89
|
+
validateOptionObject(args, OPTIONS, "createLegend");
|
|
93
90
|
if (Object.hasOwn(args, "labels")) {
|
|
94
91
|
validateObject(args.labels, TEXT_OPTIONS, "createLegend.labels");
|
|
95
92
|
}
|
|
@@ -11,7 +11,7 @@ function isCategoricalTarget(layer) {
|
|
|
11
11
|
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
12
12
|
);
|
|
13
13
|
}
|
|
14
|
-
return ["bar", "area"].includes(layer?.mark?.type) &&
|
|
14
|
+
return ["bar", "area", "arc", "rect"].includes(layer?.mark?.type) &&
|
|
15
15
|
layer.encoding?.color?.scale !== undefined;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -39,7 +39,7 @@ export function resolveTarget(program, requested) {
|
|
|
39
39
|
export const sameValues = sameOrderedValues;
|
|
40
40
|
|
|
41
41
|
export function resolveLegendKind(layer, requestedChannels) {
|
|
42
|
-
if (["bar", "area"].includes(layer.mark.type)) return "color";
|
|
42
|
+
if (["bar", "area", "arc", "rect"].includes(layer.mark.type)) return "color";
|
|
43
43
|
if (
|
|
44
44
|
layer.mark.type === "point" &&
|
|
45
45
|
sameValues(requestedChannels, ["color"])
|
|
@@ -53,7 +53,7 @@ function legendLayerStyle(layer, style) {
|
|
|
53
53
|
|
|
54
54
|
function makeEditSymbol(type) {
|
|
55
55
|
const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[type];
|
|
56
|
-
const op = `
|
|
56
|
+
const op = `rematerializeLegendSymbol${suffix}`;
|
|
57
57
|
return action(
|
|
58
58
|
{ op, description: `Rematerialize categorical legend ${type} symbols.` },
|
|
59
59
|
function (args = {}) {
|
|
@@ -180,20 +180,20 @@ function makeCreateSymbol(type, edit) {
|
|
|
180
180
|
);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
export const
|
|
184
|
-
export const
|
|
185
|
-
export const
|
|
183
|
+
export const rematerializeLegendSymbolLines = makeEditSymbol("line");
|
|
184
|
+
export const rematerializeLegendSymbolPoints = makeEditSymbol("point");
|
|
185
|
+
export const rematerializeLegendSymbolSwatches = makeEditSymbol("swatch");
|
|
186
186
|
export const createLegendSymbolLines = makeCreateSymbol(
|
|
187
187
|
"line",
|
|
188
|
-
"
|
|
188
|
+
"rematerializeLegendSymbolLines"
|
|
189
189
|
);
|
|
190
190
|
export const createLegendSymbolPoints = makeCreateSymbol(
|
|
191
191
|
"point",
|
|
192
|
-
"
|
|
192
|
+
"rematerializeLegendSymbolPoints"
|
|
193
193
|
);
|
|
194
194
|
export const createLegendSymbolSwatches = makeCreateSymbol(
|
|
195
195
|
"swatch",
|
|
196
|
-
"
|
|
196
|
+
"rematerializeLegendSymbolSwatches"
|
|
197
197
|
);
|
|
198
198
|
|
|
199
199
|
export const createLegendSymbols = action(
|
|
@@ -214,17 +214,17 @@ export const createLegendSymbols = action(
|
|
|
214
214
|
}
|
|
215
215
|
);
|
|
216
216
|
|
|
217
|
-
export const
|
|
218
|
-
{ op: "
|
|
217
|
+
export const rematerializeLegendSymbols = action(
|
|
218
|
+
{ op: "rematerializeLegendSymbols", description: "Rematerialize layered legend symbols." },
|
|
219
219
|
function (args = {}) {
|
|
220
|
-
noOptions(args, "
|
|
220
|
+
noOptions(args, "rematerializeLegendSymbols");
|
|
221
221
|
const { config } = activeConfig(this);
|
|
222
222
|
let next = this;
|
|
223
223
|
for (const layer of config.symbol.layers) {
|
|
224
224
|
const operation = {
|
|
225
|
-
line: "
|
|
226
|
-
point: "
|
|
227
|
-
swatch: "
|
|
225
|
+
line: "rematerializeLegendSymbolLines",
|
|
226
|
+
point: "rematerializeLegendSymbolPoints",
|
|
227
|
+
swatch: "rematerializeLegendSymbolSwatches"
|
|
228
228
|
}[layer.type];
|
|
229
229
|
next = next[operation]();
|
|
230
230
|
}
|
|
@@ -252,7 +252,7 @@ export const rematerializeLegendHighlights = action(
|
|
|
252
252
|
})).filter(entry => entry.states !== undefined);
|
|
253
253
|
if (highlights.length === 0) return this;
|
|
254
254
|
|
|
255
|
-
let next = this.
|
|
255
|
+
let next = this.rematerializeLegendSymbols();
|
|
256
256
|
for (const { highlight, states } of highlights) {
|
|
257
257
|
for (const layer of config.symbol.layers) {
|
|
258
258
|
const id = symbolGraphic(config, layer.type);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from "../../../../core/immutable.js";
|
|
2
2
|
import {
|
|
3
3
|
validateKeys,
|
|
4
|
+
validateOptionObject,
|
|
4
5
|
validateNonEmptyString,
|
|
5
6
|
validateNonNegativeFinite,
|
|
6
7
|
validatePositiveFinite
|
|
@@ -10,6 +11,8 @@ import { resolveGraphicBounds } from "../../../../layout/canvas.js";
|
|
|
10
11
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
11
12
|
"../../../../theme/defaults.js";
|
|
12
13
|
import { findLayer } from "../../../../selectors/layers.js";
|
|
14
|
+
import { findCanvasGraphic } from
|
|
15
|
+
"../../../../materialization/graphicHierarchy.js";
|
|
13
16
|
|
|
14
17
|
const OPTIONS = Object.freeze([
|
|
15
18
|
"target", "channels", "position", "align", "offset", "title", "count",
|
|
@@ -85,10 +88,7 @@ function normalizeBorder(value) {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
export function normalizeContinuousLegend(args, kind) {
|
|
88
|
-
|
|
89
|
-
throw new TypeError("createLegend options must be a plain object.");
|
|
90
|
-
}
|
|
91
|
-
validateKeys(args, OPTIONS, "createLegend");
|
|
91
|
+
validateOptionObject(args, OPTIONS, "createLegend");
|
|
92
92
|
const position = args.position ?? "right";
|
|
93
93
|
if (!POSITIONS.includes(position)) {
|
|
94
94
|
throw new Error(`Unsupported legend position "${position}".`);
|
|
@@ -170,7 +170,7 @@ export function resolveContinuousPoint(program, requested, channel) {
|
|
|
170
170
|
|
|
171
171
|
export function resolveContinuousColorLayer(program, requested) {
|
|
172
172
|
const candidates = program.semanticSpec.layers.filter(layer =>
|
|
173
|
-
["point", "bar"].includes(layer.mark?.type) &&
|
|
173
|
+
["point", "bar", "rect"].includes(layer.mark?.type) &&
|
|
174
174
|
layer.encoding?.color?.scale !== undefined
|
|
175
175
|
);
|
|
176
176
|
const layer = requested === undefined
|
|
@@ -182,7 +182,7 @@ export function resolveContinuousColorLayer(program, requested) {
|
|
|
182
182
|
if (layer === undefined) {
|
|
183
183
|
throw new Error(
|
|
184
184
|
requested === undefined
|
|
185
|
-
? "color legend requires one eligible point or
|
|
185
|
+
? "color legend requires one eligible point, bar, or rect mark."
|
|
186
186
|
: `Unknown color legend target "${requested}".`
|
|
187
187
|
);
|
|
188
188
|
}
|
|
@@ -199,7 +199,7 @@ export function requireResolvedLegendScale(program, id, type) {
|
|
|
199
199
|
|
|
200
200
|
export function resolveContinuousBounds(program) {
|
|
201
201
|
const plot = resolveGraphicBounds(program);
|
|
202
|
-
const canvas = program
|
|
202
|
+
const canvas = findCanvasGraphic(program);
|
|
203
203
|
if (
|
|
204
204
|
plot === undefined ||
|
|
205
205
|
![plot.x, plot.y, plot.width, plot.height].every(Number.isFinite) ||
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { action } from "../../../../core/action.js";
|
|
2
2
|
import { isPlainObject } from "../../../../core/immutable.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
validateKeys,
|
|
5
|
+
validateOptionObject
|
|
6
|
+
} from "../../../../core/validation.js";
|
|
4
7
|
import { formatDiscretizedIntervals } from "../../../../grammar/scales.js";
|
|
5
8
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
6
9
|
"../../../../theme/defaults.js";
|
|
@@ -38,10 +41,7 @@ function textOptions(value, label, defaults) {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export function normalizeIntervalLegend(args) {
|
|
41
|
-
|
|
42
|
-
throw new TypeError("createLegend options must be a plain object.");
|
|
43
|
-
}
|
|
44
|
-
validateKeys(args, OPTIONS, "createLegend");
|
|
44
|
+
validateOptionObject(args, OPTIONS, "createLegend");
|
|
45
45
|
if (args.channels !== undefined && (
|
|
46
46
|
!Array.isArray(args.channels) ||
|
|
47
47
|
args.channels.length !== 1 ||
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
-
import {
|
|
4
|
-
import { validateKeys } from "../../../core/validation.js";
|
|
3
|
+
import { validateOptionObject } from "../../../core/validation.js";
|
|
5
4
|
import { normalizeOptions } from "./categorical/options.js";
|
|
6
5
|
import { symbolGraphic } from "./categorical/layout.js";
|
|
7
6
|
import {
|
|
@@ -276,10 +275,7 @@ function editCategorical(program, kind, previous, size, args) {
|
|
|
276
275
|
export const editLegend = action(
|
|
277
276
|
{ op: "editLegend", description: "Edit one stable legend layout or appearance." },
|
|
278
277
|
function (args = {}) {
|
|
279
|
-
|
|
280
|
-
throw new TypeError("editLegend options must be a plain object.");
|
|
281
|
-
}
|
|
282
|
-
validateKeys(args, OPTIONS, "editLegend");
|
|
278
|
+
validateOptionObject(args, OPTIONS, "editLegend");
|
|
283
279
|
const changes = Object.keys(args).filter(key => key !== "target");
|
|
284
280
|
if (changes.length === 0) {
|
|
285
281
|
throw new Error("editLegend requires at least one change.");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateOptionObject } from "../../../core/validation.js";
|
|
3
|
+
|
|
4
|
+
function makeFocusedLegendEdit({ name, options, map }) {
|
|
5
|
+
return action(
|
|
6
|
+
{
|
|
7
|
+
op: name,
|
|
8
|
+
description: `Edit one focused legend component through editLegend.`
|
|
9
|
+
},
|
|
10
|
+
function (args = {}) {
|
|
11
|
+
validateOptionObject(args, ["target", ...options], name);
|
|
12
|
+
if (!options.some(option => Object.hasOwn(args, option))) {
|
|
13
|
+
throw new Error(`${name} requires at least one component change.`);
|
|
14
|
+
}
|
|
15
|
+
return this.editLegend(map(args));
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const editLegendLayout = makeFocusedLegendEdit({
|
|
21
|
+
name: "editLegendLayout",
|
|
22
|
+
options: [
|
|
23
|
+
"position", "align", "direction", "columns", "offset",
|
|
24
|
+
"titlePosition", "itemGap"
|
|
25
|
+
],
|
|
26
|
+
map: args => args
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const editLegendLabels = makeFocusedLegendEdit({
|
|
30
|
+
name: "editLegendLabels",
|
|
31
|
+
options: ["color", "fontSize", "fontFamily", "fontWeight"],
|
|
32
|
+
map: ({ target, ...labels }) => ({ target, labels })
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const editLegendTitle = makeFocusedLegendEdit({
|
|
36
|
+
name: "editLegendTitle",
|
|
37
|
+
options: ["title", "color", "fontSize", "fontFamily", "fontWeight"],
|
|
38
|
+
map: ({ target, title, ...titleStyle }) => ({
|
|
39
|
+
target,
|
|
40
|
+
...(title === undefined ? {} : { title }),
|
|
41
|
+
...(Object.keys(titleStyle).length === 0 ? {} : { titleStyle })
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const editLegendSymbols = makeFocusedLegendEdit({
|
|
46
|
+
name: "editLegendSymbols",
|
|
47
|
+
options: ["symbol", "count", "gradient"],
|
|
48
|
+
map: args => args
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const editLegendBorder = makeFocusedLegendEdit({
|
|
52
|
+
name: "editLegendBorder",
|
|
53
|
+
options: ["border"],
|
|
54
|
+
map: args => args
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export function registerFocusedLegendActions(ProgramClass) {
|
|
58
|
+
ProgramClass.prototype.editLegendLayout = editLegendLayout;
|
|
59
|
+
ProgramClass.prototype.editLegendLabels = editLegendLabels;
|
|
60
|
+
ProgramClass.prototype.editLegendTitle = editLegendTitle;
|
|
61
|
+
ProgramClass.prototype.editLegendSymbols = editLegendSymbols;
|
|
62
|
+
ProgramClass.prototype.editLegendBorder = editLegendBorder;
|
|
63
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { registerCategoricalLegendActions } from "./categorical/index.js";
|
|
2
2
|
import { registerContinuousLegendActions } from "./continuous/index.js";
|
|
3
3
|
import { registerSizeLegendActions } from "./size.js";
|
|
4
|
+
import { registerFocusedLegendActions } from "./focused.js";
|
|
5
|
+
import { removeLegend } from "./remove.js";
|
|
4
6
|
|
|
5
7
|
export function registerLegendActions(ProgramClass) {
|
|
6
8
|
registerCategoricalLegendActions(ProgramClass);
|
|
7
9
|
registerContinuousLegendActions(ProgramClass);
|
|
8
10
|
registerSizeLegendActions(ProgramClass);
|
|
11
|
+
registerFocusedLegendActions(ProgramClass);
|
|
12
|
+
ProgramClass.prototype.removeLegend = removeLegend;
|
|
9
13
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../../core/validation.js";
|
|
4
|
+
import { legendGraphicIds } from
|
|
5
|
+
"../../../materialization/guides/resources.js";
|
|
6
|
+
|
|
7
|
+
const OPTIONS = Object.freeze(["target"]);
|
|
8
|
+
|
|
9
|
+
function resolveTarget(program, requested) {
|
|
10
|
+
const targets = [...new Set(Object.values(program.guideConfigs.legend ?? {})
|
|
11
|
+
.map(config => config?.target)
|
|
12
|
+
.filter(Boolean))];
|
|
13
|
+
if (requested !== undefined) {
|
|
14
|
+
const target = validateUserId(requested, "Legend target id");
|
|
15
|
+
if (!targets.includes(target)) {
|
|
16
|
+
throw new Error(`Unknown legend target "${target}".`);
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
}
|
|
20
|
+
if (targets.length === 0) throw new Error("removeLegend requires an existing legend.");
|
|
21
|
+
if (targets.length > 1) {
|
|
22
|
+
throw new Error("removeLegend requires target when the legend is ambiguous.");
|
|
23
|
+
}
|
|
24
|
+
return targets[0];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SEMANTIC_KIND = Object.freeze({
|
|
28
|
+
series: "series",
|
|
29
|
+
color: "color",
|
|
30
|
+
size: "size",
|
|
31
|
+
gradient: "color",
|
|
32
|
+
interval: "color",
|
|
33
|
+
opacity: "opacity"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const removeLegend = action(
|
|
37
|
+
{ op: "removeLegend", description: "Remove every legend block owned by one mark." },
|
|
38
|
+
function (args = {}) {
|
|
39
|
+
validateKeys(args, OPTIONS, "removeLegend");
|
|
40
|
+
const target = resolveTarget(this, args.target);
|
|
41
|
+
const kinds = Object.entries(this.guideConfigs.legend ?? {})
|
|
42
|
+
.filter(([, config]) => config?.target === target)
|
|
43
|
+
.map(([kind]) => kind);
|
|
44
|
+
const semanticKinds = new Set(kinds.map(kind => SEMANTIC_KIND[kind]));
|
|
45
|
+
let next = this;
|
|
46
|
+
for (const kind of semanticKinds) {
|
|
47
|
+
if (next.semanticSpec.guides.legend?.[kind] !== undefined) {
|
|
48
|
+
next = next.editSemantic({
|
|
49
|
+
property: `guide.legend.${kind}`,
|
|
50
|
+
remove: true
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const id of new Set(kinds.flatMap(legendGraphicIds))) {
|
|
55
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
56
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const kind of kinds) {
|
|
60
|
+
next = next._withoutMaterializationConfig(["guides", "legend", kind]);
|
|
61
|
+
}
|
|
62
|
+
return next;
|
|
63
|
+
}
|
|
64
|
+
);
|
|
@@ -7,17 +7,18 @@ import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
|
7
7
|
import { resolveLayout as resolveCategoricalLayout } from
|
|
8
8
|
"./categorical/layout.js";
|
|
9
9
|
import { findLayer } from "../../../selectors/layers.js";
|
|
10
|
-
import { resolveLegendGraphicPlacement } from
|
|
10
|
+
import { findCanvasGraphic, resolveLegendGraphicPlacement } from
|
|
11
11
|
"../../../materialization/graphicHierarchy.js";
|
|
12
12
|
|
|
13
13
|
const SIZE_OPTIONS = Object.freeze(["target", "count"]);
|
|
14
14
|
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
layer.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
export function isSizeLegendPoint(layer) {
|
|
16
|
+
return layer?.mark?.type === "point" &&
|
|
17
|
+
layer.encoding?.size?.scale !== undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveSizeLegendPoint(program, requested) {
|
|
21
|
+
const candidates = program.semanticSpec.layers.filter(isSizeLegendPoint);
|
|
21
22
|
const layer = requested === undefined
|
|
22
23
|
? candidates.length === 1 ? candidates[0] : undefined
|
|
23
24
|
: (() => {
|
|
@@ -27,13 +28,10 @@ function resolvePoint(program, requested) {
|
|
|
27
28
|
if (layer === undefined) {
|
|
28
29
|
throw new Error(
|
|
29
30
|
requested === undefined
|
|
30
|
-
? "
|
|
31
|
-
: `Unknown
|
|
31
|
+
? "Size legend requires one eligible point mark or an explicit target."
|
|
32
|
+
: `Unknown size legend target "${requested}".`
|
|
32
33
|
);
|
|
33
34
|
}
|
|
34
|
-
if (layer.encoding.color.field !== layer.encoding.shape.field) {
|
|
35
|
-
throw new Error("Point color and shape legends require the same field.");
|
|
36
|
-
}
|
|
37
35
|
return layer;
|
|
38
36
|
}
|
|
39
37
|
|
|
@@ -47,7 +45,7 @@ function requireScale(program, id, type) {
|
|
|
47
45
|
|
|
48
46
|
function bounds(program) {
|
|
49
47
|
const value = resolveGraphicBounds(program);
|
|
50
|
-
const canvas = program
|
|
48
|
+
const canvas = findCanvasGraphic(program);
|
|
51
49
|
if (
|
|
52
50
|
value === undefined ||
|
|
53
51
|
![value.x, value.y, value.width, value.height].every(Number.isFinite) ||
|
|
@@ -171,7 +169,7 @@ export const createSizeLegend = action(
|
|
|
171
169
|
},
|
|
172
170
|
function (args = {}) {
|
|
173
171
|
validateKeys(args, [...SIZE_OPTIONS, "inheritAppearance"], "createSizeLegend");
|
|
174
|
-
const layer =
|
|
172
|
+
const layer = resolveSizeLegendPoint(this, args.target);
|
|
175
173
|
const encoding = layer.encoding?.size;
|
|
176
174
|
if (encoding?.scale === undefined) {
|
|
177
175
|
throw new Error(`Point mark "${layer.id}" requires a size encoding.`);
|