ggaction 0.0.13 → 0.0.15
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 +78 -0
- package/README.md +20 -11
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +8 -3
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +11 -3
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/categoryOrder/index.js +15 -4
- package/src/actions/charts/area.js +5 -1
- package/src/actions/charts/bar.js +2 -1
- package/src/actions/charts/density.js +6 -2
- package/src/actions/charts/ecdf.js +4 -1
- package/src/actions/charts/endpoints.js +25 -3
- package/src/actions/charts/heatmap.js +2 -1
- package/src/actions/charts/histogram.js +4 -3
- package/src/actions/charts/horizon.js +3 -1
- package/src/actions/charts/interval-regression.js +5 -2
- package/src/actions/charts/line.js +3 -1
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +10 -2
- package/src/actions/charts/polar.js +15 -4
- package/src/actions/charts/radar.js +8 -2
- package/src/actions/charts/radial.js +10 -2
- package/src/actions/charts/rug-strip.js +6 -2
- package/src/actions/charts/scatter.js +3 -1
- package/src/actions/composition/actions.js +13 -4
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +2 -2
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +47 -17
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +14 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +14 -0
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +16 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/summary.js +1 -1
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +2 -1
- package/src/actions/encodings/density.js +5 -2
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +20 -13
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +10 -2
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/remove.js +9 -4
- package/src/actions/encodings/ruleAppearance.js +1 -23
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/errorBands/create.js +23 -3
- package/src/actions/errorBands/edit.js +21 -6
- package/src/actions/errorBars/create.js +35 -7
- package/src/actions/errorBars/edit.js +19 -4
- package/src/actions/facets/actions.js +237 -57
- package/src/actions/facets/derive.js +91 -26
- package/src/actions/gradientPlots/components.js +10 -2
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +3 -1
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/labels.js +58 -12
- package/src/actions/guides/axes/lines.js +6 -2
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/tickGroups.js +2 -1
- package/src/actions/guides/axes/ticks.js +6 -2
- package/src/actions/guides/axes/titles.js +11 -3
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +19 -3
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +134 -48
- package/src/actions/guides/legends/categorical/components.js +17 -12
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +97 -15
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +85 -30
- package/src/actions/guides/legends/continuous/common.js +30 -12
- package/src/actions/guides/legends/continuous/gradient.js +10 -7
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +8 -3
- package/src/actions/guides/legends/continuous/opacity.js +8 -5
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +158 -59
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +54 -19
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +13 -3
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +93 -40
- package/src/actions/guides/legends/strokeWidth.js +34 -28
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +4 -4
- package/src/actions/guides/polar/axes/labels.js +24 -5
- package/src/actions/guides/polar/axes/lines.js +10 -4
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +1 -1
- package/src/actions/guides/polar/axes/titles.js +1 -1
- package/src/actions/guides/polar/grids.js +1 -1
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +14 -3
- package/src/actions/marks/arc/actions.js +77 -19
- package/src/actions/marks/area/actions.js +34 -9
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +22 -2
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +37 -14
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +18 -6
- package/src/actions/marks/point/materialize.js +49 -15
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +601 -30
- package/src/actions/marks/remove.js +71 -1
- package/src/actions/marks/rule/actions.js +47 -10
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +74 -9
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +154 -6
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +2 -1
- package/src/actions/primitives/semanticAction.js +83 -10
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +10 -2
- package/src/actions/regression/edit.js +21 -7
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +54 -22
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +66 -11
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +4 -3
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +68 -13
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +315 -67
- package/src/actions/theme/state.js +160 -0
- package/src/actions/violinPlots/create.js +5 -2
- package/src/actions/violinPlots/edit.js +1 -0
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +6 -1
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +340 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +343 -0
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +19 -4
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +32 -1
- package/src/selectors/layers.js +4 -0
- package/src/theme/defaults.js +101 -0
- package/types/basic.d.ts +6 -0
- package/types/index.d.ts +85 -0
- package/types/program.d.ts +910 -117
|
@@ -26,6 +26,11 @@ import {
|
|
|
26
26
|
resolveTarget,
|
|
27
27
|
sameValues
|
|
28
28
|
} from "./resolve.js";
|
|
29
|
+
import { readLegendSampling } from "../sampling.js";
|
|
30
|
+
import {
|
|
31
|
+
reconcileLegendBlockTitleGraphic,
|
|
32
|
+
resolveEffectiveLegendBlockConfig
|
|
33
|
+
} from "../blocks.js";
|
|
29
34
|
|
|
30
35
|
function finishLegend(program) {
|
|
31
36
|
return hasLegendLane(program) ? program.rematerializeLegend() : program;
|
|
@@ -50,25 +55,35 @@ export const rematerializeLegend = action(
|
|
|
50
55
|
withGuideLayoutValidation(function (args = {}) {
|
|
51
56
|
noOptions(args, "rematerializeLegend");
|
|
52
57
|
let next = this;
|
|
53
|
-
const
|
|
54
|
-
this.guideConfigs.legend?.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const definition = resolveCurrentDefinition(
|
|
59
|
-
const symbol =
|
|
60
|
-
? normalizeRecipe(
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
const categoricalKinds = ["series", "color", "stroke"].filter(
|
|
59
|
+
kind => this.guideConfigs.legend?.[kind] !== undefined
|
|
60
|
+
);
|
|
61
|
+
for (const kind of categoricalKinds) {
|
|
62
|
+
const stored = next.guideConfigs.legend[kind];
|
|
63
|
+
const definition = resolveCurrentDefinition(next, stored);
|
|
64
|
+
const symbol = stored.inferredSymbol
|
|
65
|
+
? normalizeRecipe(
|
|
66
|
+
resolveLegendSymbol(
|
|
67
|
+
next,
|
|
68
|
+
findLayer(next, stored.target),
|
|
69
|
+
definition.channels,
|
|
70
|
+
undefined,
|
|
71
|
+
kind
|
|
72
|
+
),
|
|
73
|
+
kind,
|
|
74
|
+
{ internal: true }
|
|
75
|
+
)
|
|
76
|
+
: stored.symbol;
|
|
77
|
+
const symbolChanged = !sameGuideValue(symbol, stored.symbol);
|
|
63
78
|
const changed = symbolChanged ||
|
|
64
|
-
!sameValues(
|
|
65
|
-
!sameValues(
|
|
66
|
-
!sameValues(
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
!sameValues(stored.channels, definition.channels) ||
|
|
80
|
+
!sameValues(stored.domain, definition.domain) ||
|
|
81
|
+
!sameValues(stored.scales, definition.scales) ||
|
|
82
|
+
stored.field !== definition.field ||
|
|
83
|
+
stored.title !== definition.title;
|
|
69
84
|
next = changed
|
|
70
|
-
?
|
|
71
|
-
...
|
|
85
|
+
? next._withLegendConfig(kind, {
|
|
86
|
+
...stored,
|
|
72
87
|
symbol,
|
|
73
88
|
channels: definition.channels,
|
|
74
89
|
scales: definition.scales,
|
|
@@ -76,10 +91,17 @@ export const rematerializeLegend = action(
|
|
|
76
91
|
title: definition.title,
|
|
77
92
|
domain: definition.domain
|
|
78
93
|
})
|
|
79
|
-
:
|
|
94
|
+
: next;
|
|
95
|
+
const config = resolveEffectiveLegendBlockConfig(
|
|
96
|
+
next,
|
|
97
|
+
kind,
|
|
98
|
+
next.guideConfigs.legend[kind]
|
|
99
|
+
);
|
|
100
|
+
next = reconcileLegendBlockTitleGraphic(next, kind,
|
|
101
|
+
config.titleVisible !== false);
|
|
80
102
|
if (kind === "series") {
|
|
81
103
|
if (!sameValues(
|
|
82
|
-
|
|
104
|
+
next.semanticSpec.guides.legend.series.scales,
|
|
83
105
|
definition.scales
|
|
84
106
|
)) {
|
|
85
107
|
next = next.editSemantic({
|
|
@@ -87,45 +109,56 @@ export const rematerializeLegend = action(
|
|
|
87
109
|
value: definition.scales
|
|
88
110
|
});
|
|
89
111
|
}
|
|
90
|
-
if (
|
|
112
|
+
if (next.semanticSpec.guides.legend.series.title !== definition.title) {
|
|
91
113
|
next = next.editSemantic({
|
|
92
114
|
property: "guide.legend.series.title",
|
|
93
115
|
value: definition.title
|
|
94
116
|
});
|
|
95
117
|
}
|
|
96
118
|
} else {
|
|
97
|
-
if (
|
|
119
|
+
if (next.semanticSpec.guides.legend[kind].scale !== definition.scales[0]) {
|
|
98
120
|
next = next.editSemantic({
|
|
99
|
-
property:
|
|
121
|
+
property: `guide.legend.${kind}.scale`,
|
|
100
122
|
value: definition.scales[0]
|
|
101
123
|
});
|
|
102
124
|
}
|
|
103
|
-
if (
|
|
125
|
+
if (next.semanticSpec.guides.legend[kind].title !== definition.title) {
|
|
104
126
|
next = next.editSemantic({
|
|
105
|
-
property:
|
|
127
|
+
property: `guide.legend.${kind}.title`,
|
|
106
128
|
value: definition.title
|
|
107
129
|
});
|
|
108
130
|
}
|
|
109
131
|
}
|
|
110
132
|
if (symbolChanged) {
|
|
111
|
-
resolveLayout(next,
|
|
112
|
-
next = reconcileCategoricalSymbols(next,
|
|
133
|
+
resolveLayout(next, config);
|
|
134
|
+
next = reconcileCategoricalSymbols(next, stored, config);
|
|
135
|
+
}
|
|
136
|
+
if (config.border !== false) {
|
|
137
|
+
next = next.rematerializeLegendBackground({ kind });
|
|
113
138
|
}
|
|
114
|
-
if (config.border !== false) next = next.rematerializeLegendBackground();
|
|
115
139
|
next = next
|
|
116
|
-
.rematerializeLegendSymbols()
|
|
117
|
-
.rematerializeLegendLabels();
|
|
118
|
-
if (config.titleVisible !== false)
|
|
140
|
+
.rematerializeLegendSymbols({ kind })
|
|
141
|
+
.rematerializeLegendLabels({ kind });
|
|
142
|
+
if (config.titleVisible !== false) {
|
|
143
|
+
next = next.rematerializeLegendTitle({ kind });
|
|
144
|
+
}
|
|
119
145
|
const hasHighlight = Object.values(
|
|
120
146
|
next.materializationConfigs.highlights ?? {}
|
|
121
147
|
).some(highlight => highlight.target === config.target);
|
|
122
|
-
if (hasHighlight) next = next.rematerializeLegendHighlights();
|
|
148
|
+
if (hasHighlight) next = next.rematerializeLegendHighlights({ kind });
|
|
123
149
|
}
|
|
124
150
|
for (const policy of legendResourcePolicies()) {
|
|
125
151
|
if (
|
|
126
152
|
policy.rematerializeOp !== undefined &&
|
|
127
153
|
this.guideConfigs.legend?.[policy.kind] !== undefined
|
|
128
154
|
) {
|
|
155
|
+
const config = resolveEffectiveLegendBlockConfig(
|
|
156
|
+
next,
|
|
157
|
+
policy.kind,
|
|
158
|
+
next.guideConfigs.legend[policy.kind]
|
|
159
|
+
);
|
|
160
|
+
next = reconcileLegendBlockTitleGraphic(next, policy.kind,
|
|
161
|
+
config.titleVisible !== false);
|
|
129
162
|
next = next[policy.rematerializeOp]();
|
|
130
163
|
}
|
|
131
164
|
}
|
|
@@ -148,14 +181,21 @@ export function resolveCategoricalLegendConfig(program, args = {}) {
|
|
|
148
181
|
args.title,
|
|
149
182
|
args.order === undefined ? undefined : normalizeLegendOrder(args.order)
|
|
150
183
|
);
|
|
184
|
+
const inferredSymbol = args.symbol === undefined || args.symbol === "auto";
|
|
151
185
|
const options = normalizeOptions({ ...args,
|
|
152
|
-
symbol: resolveLegendSymbol(
|
|
153
|
-
|
|
186
|
+
symbol: resolveLegendSymbol(
|
|
187
|
+
program,
|
|
188
|
+
layer,
|
|
189
|
+
definition.channels,
|
|
190
|
+
args.symbol,
|
|
191
|
+
definition.kind
|
|
192
|
+
)
|
|
193
|
+
}, definition.kind, { internalSymbol: inferredSymbol });
|
|
154
194
|
const config = {
|
|
155
195
|
target: layer.id,
|
|
156
196
|
...definition,
|
|
157
197
|
inferredTitle: !Object.hasOwn(args, "title"),
|
|
158
|
-
inferredSymbol
|
|
198
|
+
inferredSymbol,
|
|
159
199
|
position: options.position,
|
|
160
200
|
align: options.align,
|
|
161
201
|
direction: options.direction,
|
|
@@ -177,11 +217,10 @@ export const createCategoricalLegend = action(
|
|
|
177
217
|
{ op: "createCategoricalLegend", description: "Create one categorical legend block." },
|
|
178
218
|
withGuideLayoutValidation(function (args = {}) {
|
|
179
219
|
const config = resolveCategoricalLegendConfig(this, args);
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
throw new Error("createCategoricalLegend requires a missing legend.");
|
|
220
|
+
if (this.semanticSpec.guides.legend?.[config.kind] !== undefined) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`createCategoricalLegend requires a missing legend for ${config.kind}.`
|
|
223
|
+
);
|
|
185
224
|
}
|
|
186
225
|
resolveLayout(this, config);
|
|
187
226
|
return createCategoricalLegendFromConfig(this, config,
|
|
@@ -195,12 +234,12 @@ export function resolveLegendCreationPlan(program, args = {}, layers = program.s
|
|
|
195
234
|
const channels = args.channels;
|
|
196
235
|
if (channels !== undefined) validateLegendChannels(channels, "createLegend");
|
|
197
236
|
const standalone = [
|
|
198
|
-
["size", isSizeLegendPoint, ["color", "shape", "strokeDash", "opacity"]],
|
|
237
|
+
["size", isSizeLegendPoint, ["color", "stroke", "shape", "strokeDash", "opacity"]],
|
|
199
238
|
["strokeWidth", isStrokeWidthLegendLayer, args.target === undefined
|
|
200
|
-
? ["color", "shape", "strokeDash", "size", "opacity"]
|
|
201
|
-
: ["color", "shape", "strokeDash", "opacity"]],
|
|
239
|
+
? ["color", "stroke", "shape", "strokeDash", "size", "opacity"]
|
|
240
|
+
: ["color", "stroke", "shape", "strokeDash", "opacity"]],
|
|
202
241
|
["opacity", isOpacityLegendLayer,
|
|
203
|
-
["color", "shape", "strokeDash", "size"]]
|
|
242
|
+
["color", "stroke", "shape", "strokeDash", "size"]]
|
|
204
243
|
];
|
|
205
244
|
for (const [kind, eligible, otherChannels] of standalone) {
|
|
206
245
|
const explicit = channels?.length === 1 && channels[0] === kind;
|
|
@@ -240,6 +279,47 @@ export function resolveLegendCreationPlan(program, args = {}, layers = program.s
|
|
|
240
279
|
) {
|
|
241
280
|
return { steps: [{ op: "createIntervalLegend", args }], finish: "auto" };
|
|
242
281
|
}
|
|
282
|
+
const continuousStrokeCandidates = candidates.filter(layer => {
|
|
283
|
+
const scale = findSemanticScale(program, layer.encoding?.stroke?.scale);
|
|
284
|
+
return scale?.type === "sequential";
|
|
285
|
+
});
|
|
286
|
+
const continuousStroke = requestedCandidate(
|
|
287
|
+
program,
|
|
288
|
+
args.target,
|
|
289
|
+
continuousStrokeCandidates
|
|
290
|
+
);
|
|
291
|
+
if (
|
|
292
|
+
(channels?.length === 1 && channels[0] === "stroke" && continuousStroke) ||
|
|
293
|
+
(channels === undefined && continuousStroke &&
|
|
294
|
+
!["color", "shape", "strokeDash", "size", "opacity", "strokeWidth"].some(
|
|
295
|
+
channel => continuousStroke.encoding?.[channel]?.scale !== undefined
|
|
296
|
+
))
|
|
297
|
+
) {
|
|
298
|
+
return {
|
|
299
|
+
steps: [{ op: "createStrokeGradientLegend", args: {
|
|
300
|
+
...args,
|
|
301
|
+
channels: ["stroke"]
|
|
302
|
+
} }],
|
|
303
|
+
finish: "auto"
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const intervalStrokeCandidates = candidates.filter(layer => {
|
|
307
|
+
const scale = findSemanticScale(program, layer.encoding?.stroke?.scale);
|
|
308
|
+
return ["quantize", "quantile", "threshold"].includes(scale?.type);
|
|
309
|
+
});
|
|
310
|
+
if (
|
|
311
|
+
(channels?.length === 1 && channels[0] === "stroke" &&
|
|
312
|
+
intervalStrokeCandidates.length > 0) ||
|
|
313
|
+
(channels === undefined && intervalStrokeCandidates.length === 1)
|
|
314
|
+
) {
|
|
315
|
+
return {
|
|
316
|
+
steps: [{ op: "createStrokeIntervalLegend", args: {
|
|
317
|
+
...args,
|
|
318
|
+
channels: ["stroke"]
|
|
319
|
+
} }],
|
|
320
|
+
finish: "auto"
|
|
321
|
+
};
|
|
322
|
+
}
|
|
243
323
|
const wantsShape = channels?.includes("shape") === true;
|
|
244
324
|
const wantsSize = channels?.includes("size") === true;
|
|
245
325
|
const pointCandidates = candidates.filter(layer =>
|
|
@@ -247,7 +327,7 @@ export function resolveLegendCreationPlan(program, args = {}, layers = program.s
|
|
|
247
327
|
(channels === undefined
|
|
248
328
|
? ["color", "shape"].some(channel => layer.encoding?.[channel]?.scale !== undefined)
|
|
249
329
|
: (wantsShape || wantsSize) && channels.every(channel =>
|
|
250
|
-
["color", "shape", "size"].includes(channel) && layer.encoding?.[channel]?.scale !== undefined))
|
|
330
|
+
["color", "stroke", "shape", "size"].includes(channel) && layer.encoding?.[channel]?.scale !== undefined))
|
|
251
331
|
);
|
|
252
332
|
const requestedPoint = requestedCandidate(program, args.target, pointCandidates);
|
|
253
333
|
const inferredSize = channels === undefined && pointCandidates.some(isSizeLegendPoint);
|
|
@@ -255,6 +335,11 @@ export function resolveLegendCreationPlan(program, args = {}, layers = program.s
|
|
|
255
335
|
throw new Error("Combined size legend requires one eligible point mark or an explicit target.");
|
|
256
336
|
}
|
|
257
337
|
if (requestedPoint !== undefined) {
|
|
338
|
+
if (Object.hasOwn(args, "values")) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
"Combined and categorical legends require a channel block selector for exact values."
|
|
341
|
+
);
|
|
342
|
+
}
|
|
258
343
|
const { count, ...categoricalArgs } = args;
|
|
259
344
|
const combined = requestedPoint.encoding?.size?.scale !== undefined &&
|
|
260
345
|
(channels === undefined || wantsSize);
|
|
@@ -264,7 +349,7 @@ export function resolveLegendCreationPlan(program, args = {}, layers = program.s
|
|
|
264
349
|
if (combined && categoricalArgs.layout === "legacy-bottom") {
|
|
265
350
|
throw new Error('Combined size legends require layout "edge".');
|
|
266
351
|
}
|
|
267
|
-
const inferredChannels = ["color", "shape"].filter(
|
|
352
|
+
const inferredChannels = ["color", "stroke", "shape"].filter(
|
|
268
353
|
channel => requestedPoint.encoding?.[channel]?.scale !== undefined
|
|
269
354
|
);
|
|
270
355
|
const { format: sizeFormat, ...categoricalLabels } = categoricalArgs.labels ?? {};
|
|
@@ -294,7 +379,8 @@ export function resolveLegendCreationPlan(program, args = {}, layers = program.s
|
|
|
294
379
|
if (existing !== undefined && existing.target !== size.target) {
|
|
295
380
|
throw new Error("Combined point series legend requires the active size legend to share its target.");
|
|
296
381
|
}
|
|
297
|
-
if (existing !== undefined && count !== undefined &&
|
|
382
|
+
if (existing !== undefined && count !== undefined &&
|
|
383
|
+
readLegendSampling(existing).count !== count) {
|
|
298
384
|
throw new Error("Existing size legend count must be edited before recreating the categorical block.");
|
|
299
385
|
}
|
|
300
386
|
}
|
|
@@ -331,14 +417,14 @@ export const removeCategoricalLegend = action(
|
|
|
331
417
|
},
|
|
332
418
|
withGuideLayoutValidation(function (args = {}) {
|
|
333
419
|
noOptions(args, "removeCategoricalLegend");
|
|
334
|
-
const entries = ["series", "color"]
|
|
420
|
+
const entries = ["series", "color", "stroke"]
|
|
335
421
|
.filter(kind => this.guideConfigs.legend?.[kind] !== undefined);
|
|
336
422
|
if (entries.length === 0) return this;
|
|
337
423
|
if (entries.length !== 1) {
|
|
338
424
|
throw new Error("removeCategoricalLegend requires one active categorical legend.");
|
|
339
425
|
}
|
|
340
426
|
const kind = entries[0];
|
|
341
|
-
const prefix = kind
|
|
427
|
+
const prefix = `${kind}Legend`;
|
|
342
428
|
const targets = Object.keys(this.graphicSpec.objects)
|
|
343
429
|
.filter(id => id.startsWith(prefix));
|
|
344
430
|
let next = this.editSemantic({
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { action } from "../../../../core/action.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
activeConfig,
|
|
4
|
+
categoricalLegendLabels,
|
|
5
|
+
graphic,
|
|
6
|
+
noOptions,
|
|
7
|
+
resolveLayout
|
|
8
|
+
} from "./layout.js";
|
|
4
9
|
import { resolveCategoricalLegendPlacement } from "../lifecycle.js";
|
|
5
10
|
|
|
6
11
|
export const rematerializeLegendLabels = action(
|
|
7
12
|
{ op: "rematerializeLegendLabels", description: "Rematerialize categorical legend labels." },
|
|
8
13
|
function (args = {}) {
|
|
9
14
|
noOptions(args, "rematerializeLegendLabels");
|
|
10
|
-
const { config } = activeConfig(this);
|
|
15
|
+
const { config } = activeConfig(this, args.kind);
|
|
11
16
|
const id = graphic(config, "Labels");
|
|
12
17
|
if (this.graphicSpec.objects[id]?.type !== "text") {
|
|
13
18
|
throw new Error("rematerializeLegendLabels requires existing legend labels.");
|
|
@@ -20,7 +25,7 @@ export const rematerializeLegendLabels = action(
|
|
|
20
25
|
.editGraphics({
|
|
21
26
|
target: id,
|
|
22
27
|
property: "text",
|
|
23
|
-
value: config
|
|
28
|
+
value: categoricalLegendLabels(config)
|
|
24
29
|
})
|
|
25
30
|
.editGraphics({ target: id, property: "fill", value: config.labels.color })
|
|
26
31
|
.editGraphics({ target: id, property: "fontSize", value: config.labels.fontSize })
|
|
@@ -35,7 +40,7 @@ export const createLegendLabels = action(
|
|
|
35
40
|
{ op: "createLegendLabels", description: "Create categorical legend labels." },
|
|
36
41
|
function (args = {}) {
|
|
37
42
|
noOptions(args, "createLegendLabels");
|
|
38
|
-
const { config } = activeConfig(this);
|
|
43
|
+
const { config } = activeConfig(this, args.kind);
|
|
39
44
|
const id = graphic(config, "Labels");
|
|
40
45
|
if (this.graphicSpec.objects[id] !== undefined) {
|
|
41
46
|
throw new Error("createLegendLabels requires missing legend labels.");
|
|
@@ -47,7 +52,7 @@ export const createLegendLabels = action(
|
|
|
47
52
|
length: config.domain.length,
|
|
48
53
|
...resolveCategoricalLegendPlacement(this)
|
|
49
54
|
})
|
|
50
|
-
.rematerializeLegendLabels();
|
|
55
|
+
.rematerializeLegendLabels({ kind: config.kind });
|
|
51
56
|
}
|
|
52
57
|
);
|
|
53
58
|
|
|
@@ -55,7 +60,7 @@ export const rematerializeLegendTitle = action(
|
|
|
55
60
|
{ op: "rematerializeLegendTitle", description: "Rematerialize the categorical legend title." },
|
|
56
61
|
function (args = {}) {
|
|
57
62
|
noOptions(args, "rematerializeLegendTitle");
|
|
58
|
-
const { config } = activeConfig(this);
|
|
63
|
+
const { config } = activeConfig(this, args.kind);
|
|
59
64
|
const id = graphic(config, "Title");
|
|
60
65
|
if (this.graphicSpec.objects[id]?.type !== "text") {
|
|
61
66
|
throw new Error("rematerializeLegendTitle requires an existing legend title.");
|
|
@@ -85,7 +90,7 @@ export const createLegendTitle = action(
|
|
|
85
90
|
{ op: "createLegendTitle", description: "Create the categorical legend title." },
|
|
86
91
|
function (args = {}) {
|
|
87
92
|
noOptions(args, "createLegendTitle");
|
|
88
|
-
const { config } = activeConfig(this);
|
|
93
|
+
const { config } = activeConfig(this, args.kind);
|
|
89
94
|
const id = graphic(config, "Title");
|
|
90
95
|
if (this.graphicSpec.objects[id] !== undefined) {
|
|
91
96
|
throw new Error("createLegendTitle requires a missing legend title.");
|
|
@@ -96,7 +101,7 @@ export const createLegendTitle = action(
|
|
|
96
101
|
type: "text",
|
|
97
102
|
...resolveCategoricalLegendPlacement(this)
|
|
98
103
|
})
|
|
99
|
-
.rematerializeLegendTitle();
|
|
104
|
+
.rematerializeLegendTitle({ kind: config.kind });
|
|
100
105
|
}
|
|
101
106
|
);
|
|
102
107
|
|
|
@@ -104,7 +109,7 @@ export const rematerializeLegendBackground = action(
|
|
|
104
109
|
{ op: "rematerializeLegendBackground", description: "Rematerialize the legend background." },
|
|
105
110
|
function (args = {}) {
|
|
106
111
|
noOptions(args, "rematerializeLegendBackground");
|
|
107
|
-
const { config } = activeConfig(this);
|
|
112
|
+
const { config } = activeConfig(this, args.kind);
|
|
108
113
|
if (config.border === false) {
|
|
109
114
|
throw new Error("rematerializeLegendBackground requires border configuration.");
|
|
110
115
|
}
|
|
@@ -128,7 +133,7 @@ export const createLegendBackground = action(
|
|
|
128
133
|
{ op: "createLegendBackground", description: "Create the legend background rect." },
|
|
129
134
|
function (args = {}) {
|
|
130
135
|
noOptions(args, "createLegendBackground");
|
|
131
|
-
const { config } = activeConfig(this);
|
|
136
|
+
const { config } = activeConfig(this, args.kind);
|
|
132
137
|
const id = graphic(config, "Background");
|
|
133
138
|
if (this.graphicSpec.objects[id] !== undefined) {
|
|
134
139
|
throw new Error("createLegendBackground requires a missing background.");
|
|
@@ -139,6 +144,6 @@ export const createLegendBackground = action(
|
|
|
139
144
|
type: "rect",
|
|
140
145
|
...resolveCategoricalLegendPlacement(this)
|
|
141
146
|
})
|
|
142
|
-
.rematerializeLegendBackground();
|
|
147
|
+
.rematerializeLegendBackground({ kind: config.kind });
|
|
143
148
|
}
|
|
144
149
|
);
|
|
@@ -68,40 +68,44 @@ function createLegendHighlightAction() {
|
|
|
68
68
|
},
|
|
69
69
|
function (args = {}) {
|
|
70
70
|
noOptions(args, "rematerializeLegendHighlights");
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
const kinds = args.kind === undefined
|
|
72
|
+
? ["series", "color", "stroke"].filter(
|
|
73
|
+
kind => this.guideConfigs.legend?.[kind] !== undefined
|
|
74
|
+
)
|
|
75
|
+
: [args.kind];
|
|
76
|
+
let next = this;
|
|
77
|
+
for (const kind of kinds) {
|
|
78
|
+
const { config } = activeConfig(next, kind);
|
|
79
|
+
const highlights = Object.values(
|
|
80
|
+
next.materializationConfigs.highlights ?? {}
|
|
81
|
+
).map(highlight => ({
|
|
82
|
+
highlight,
|
|
83
|
+
states: exactLegendSelection(next, config, highlight)
|
|
84
|
+
})).filter(entry => entry.states !== undefined);
|
|
85
|
+
if (highlights.length === 0) continue;
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
next = next.rematerializeLegendSymbols({ kind: config.kind });
|
|
88
|
+
for (const { highlight, states } of highlights) {
|
|
89
|
+
for (const layer of config.symbol.layers) {
|
|
90
|
+
const id = symbolGraphic(config, layer.type);
|
|
91
|
+
const graphic = next.graphicSpec.objects[id];
|
|
92
|
+
const selectedStyle = legendLayerStyle(layer, highlight.style);
|
|
93
|
+
const dimOpacity = highlight.dimOthers === false
|
|
94
|
+
? undefined
|
|
95
|
+
: highlight.dimOthers.opacity;
|
|
96
|
+
next = next.editGraphics({
|
|
97
|
+
target: id,
|
|
98
|
+
property: "items",
|
|
99
|
+
value: graphic.items.map((child, index) => ({
|
|
100
|
+
type: child.type ?? graphic.type,
|
|
101
|
+
properties: states[index]
|
|
102
|
+
? { ...child.properties, ...selectedStyle }
|
|
103
|
+
: dimOpacity === undefined
|
|
104
|
+
? child.properties
|
|
105
|
+
: { ...child.properties, opacity: dimOpacity }
|
|
106
|
+
}))
|
|
107
|
+
});
|
|
108
|
+
}
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
return next;
|