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
|
@@ -49,8 +49,13 @@ export function resolveCategoricalLegendRevision(program, kind, previous, channe
|
|
|
49
49
|
const config = {
|
|
50
50
|
...previous,
|
|
51
51
|
...definition,
|
|
52
|
-
symbol: normalizeRecipe(resolveLegendSymbol(
|
|
53
|
-
|
|
52
|
+
symbol: normalizeRecipe(resolveLegendSymbol(
|
|
53
|
+
program,
|
|
54
|
+
layer,
|
|
55
|
+
channels,
|
|
56
|
+
previous.inferredSymbol ? undefined : previous.symbol,
|
|
57
|
+
definition.kind
|
|
58
|
+
), definition.kind, { internal: previous.inferredSymbol })
|
|
54
59
|
};
|
|
55
60
|
if (validateLayout) resolveLayout(program, config);
|
|
56
61
|
return { kind, config, order };
|
|
@@ -76,11 +81,11 @@ export function createCategoricalLegendFromConfig(program, config, order) {
|
|
|
76
81
|
} else {
|
|
77
82
|
next = next
|
|
78
83
|
.editSemantic({
|
|
79
|
-
property:
|
|
84
|
+
property: `guide.legend.${kind}.scale`,
|
|
80
85
|
value: config.scales[0]
|
|
81
86
|
})
|
|
82
87
|
.editSemantic({
|
|
83
|
-
property:
|
|
88
|
+
property: `guide.legend.${kind}.title`,
|
|
84
89
|
value: config.title
|
|
85
90
|
});
|
|
86
91
|
}
|
|
@@ -91,9 +96,15 @@ export function createCategoricalLegendFromConfig(program, config, order) {
|
|
|
91
96
|
});
|
|
92
97
|
}
|
|
93
98
|
next = next._withLegendConfig(kind, config);
|
|
94
|
-
if (config.border !== false)
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
if (config.border !== false) {
|
|
100
|
+
next = next.createLegendBackground({ kind });
|
|
101
|
+
}
|
|
102
|
+
next = next
|
|
103
|
+
.createLegendSymbols({ kind })
|
|
104
|
+
.createLegendLabels({ kind });
|
|
105
|
+
return config.titleVisible === false
|
|
106
|
+
? next
|
|
107
|
+
: next.createLegendTitle({ kind });
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
// Categorical content precedes its sampled companions in both creation and revision.
|
|
@@ -125,7 +136,7 @@ export function reconcileCategoricalSymbols(program, previous, config) {
|
|
|
125
136
|
for (const layer of config.symbol.layers) {
|
|
126
137
|
if (next.graphicSpec.objects[symbolGraphic(config, layer.type)] === undefined) {
|
|
127
138
|
const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[layer.type];
|
|
128
|
-
next = next[`createLegendSymbol${suffix}`]();
|
|
139
|
+
next = next[`createLegendSymbol${suffix}`]({ kind: config.kind });
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
142
|
return next;
|
|
@@ -6,17 +6,20 @@ import {
|
|
|
6
6
|
createCategoricalLegendFromConfig
|
|
7
7
|
} from "./lifecycle.js";
|
|
8
8
|
import { resolveLegendTarget, validateLegendChannels } from "./target.js";
|
|
9
|
+
import { planLegendBlockTransitions } from "./transition.js";
|
|
9
10
|
|
|
10
11
|
export { removeLegendKinds } from "./lifecycle.js";
|
|
11
12
|
|
|
12
13
|
const OPTIONS = Object.freeze(["target", "channels"]);
|
|
13
14
|
|
|
14
15
|
function legendKindChannels(kind, config) {
|
|
15
|
-
if (["series", "color"].includes(kind)) return config.channels;
|
|
16
|
+
if (["series", "color", "stroke"].includes(kind)) return config.channels;
|
|
16
17
|
return {
|
|
17
18
|
size: ["size"],
|
|
18
19
|
gradient: ["color"],
|
|
19
20
|
interval: ["color"],
|
|
21
|
+
strokeGradient: ["stroke"],
|
|
22
|
+
strokeInterval: ["stroke"],
|
|
20
23
|
opacity: ["opacity"],
|
|
21
24
|
strokeWidth: ["strokeWidth"]
|
|
22
25
|
}[kind];
|
|
@@ -46,8 +49,13 @@ function resolveRequestedRemoval(program, target, channels) {
|
|
|
46
49
|
// Remove departing sibling configs from the immutable layout preflight view.
|
|
47
50
|
const view = kinds.reduce((next, kind) =>
|
|
48
51
|
next._withoutMaterializationConfig(["guides", "legend", kind]), program);
|
|
49
|
-
const
|
|
52
|
+
const resolved = partial === undefined ? undefined
|
|
50
53
|
: resolveCategoricalLegendRevision(view, partial.kind, partial.config, partial.channels);
|
|
54
|
+
const revision = resolved === undefined ? undefined : planLegendBlockTransitions(
|
|
55
|
+
program,
|
|
56
|
+
target,
|
|
57
|
+
[{ ...resolved, kind: resolved.config.kind }]
|
|
58
|
+
)[0];
|
|
51
59
|
return { kinds, revision };
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -81,7 +89,9 @@ export const removeLegend = action(
|
|
|
81
89
|
: next.rematerializeLegend();
|
|
82
90
|
}
|
|
83
91
|
const remainingTargetKinds = targetKinds.filter(kind => !kinds.includes(kind));
|
|
84
|
-
const removedCategorical = kinds.some(kind =>
|
|
92
|
+
const removedCategorical = kinds.some(kind =>
|
|
93
|
+
["series", "color", "stroke"].includes(kind)
|
|
94
|
+
);
|
|
85
95
|
if (
|
|
86
96
|
removedCategorical && revision === undefined &&
|
|
87
97
|
remainingTargetKinds.includes("size") &&
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { sampleNumericRange } from "../../../grammar/numeric.js";
|
|
2
|
+
import {
|
|
3
|
+
validateGeneratedItemLimit
|
|
4
|
+
} from "../../../core/validation.js";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_COUNT = 5;
|
|
7
|
+
const MAX_EXACT_VALUES = 100;
|
|
8
|
+
|
|
9
|
+
function validateCount(value, label) {
|
|
10
|
+
if (!Number.isInteger(value) || value < 2) {
|
|
11
|
+
throw new RangeError(`${label} count must be an integer of at least 2.`);
|
|
12
|
+
}
|
|
13
|
+
validateGeneratedItemLimit(value, `${label} count`);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeExactValues(value, label) {
|
|
18
|
+
if (!Array.isArray(value)) {
|
|
19
|
+
throw new TypeError(`${label} values must be an array or "auto".`);
|
|
20
|
+
}
|
|
21
|
+
if (value.length === 0 || value.length > MAX_EXACT_VALUES) {
|
|
22
|
+
throw new RangeError(
|
|
23
|
+
`${label} values must contain between 1 and ${MAX_EXACT_VALUES} values.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const values = [...value];
|
|
27
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
28
|
+
if (!Number.isFinite(values[index])) {
|
|
29
|
+
throw new TypeError(`${label} values must contain only finite numbers.`);
|
|
30
|
+
}
|
|
31
|
+
if (index > 0 && !(values[index] > values[index - 1])) {
|
|
32
|
+
throw new RangeError(
|
|
33
|
+
`${label} values must be unique and strictly increasing.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze(values);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readLegendSampling(config, defaultCount = DEFAULT_COUNT) {
|
|
41
|
+
if (config?.sampling?.mode === "values") {
|
|
42
|
+
return {
|
|
43
|
+
mode: "values",
|
|
44
|
+
values: config.sampling.values,
|
|
45
|
+
count: config.sampling.count ?? config.count ?? defaultCount
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
mode: "auto",
|
|
50
|
+
count: config?.sampling?.count ?? config?.count ?? defaultCount
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function normalizeLegendSampling(
|
|
55
|
+
patch,
|
|
56
|
+
{ previous, operation = "create", label = "Legend" } = {}
|
|
57
|
+
) {
|
|
58
|
+
const prior = readLegendSampling(previous);
|
|
59
|
+
const hasValues = Object.hasOwn(patch, "values");
|
|
60
|
+
const hasCount = Object.hasOwn(patch, "count");
|
|
61
|
+
const requestedValues = patch.values;
|
|
62
|
+
|
|
63
|
+
if (hasValues && requestedValues !== "auto" && hasCount) {
|
|
64
|
+
throw new Error(`${label} cannot specify both count and exact values.`);
|
|
65
|
+
}
|
|
66
|
+
if (requestedValues === "auto" && operation === "create") {
|
|
67
|
+
throw new Error(`${label} creation uses omission for automatic values.`);
|
|
68
|
+
}
|
|
69
|
+
if (!hasValues && !hasCount) {
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
...prior,
|
|
72
|
+
...(prior.values === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: { values: Object.freeze([...prior.values]) })
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (!hasValues) {
|
|
78
|
+
if (prior.mode === "values") {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`${label} count cannot replace exact values; set values to "auto" first.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return Object.freeze({ mode: "auto", count: validateCount(patch.count, label) });
|
|
84
|
+
}
|
|
85
|
+
if (requestedValues === "auto") {
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
mode: "auto",
|
|
88
|
+
count: hasCount ? validateCount(patch.count, label) : validateCount(prior.count, label)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
mode: "values",
|
|
93
|
+
values: normalizeExactValues(requestedValues, label),
|
|
94
|
+
count: validateCount(prior.count, label)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function validateSamplingAgainstScale(sampling, scale, label = "Legend") {
|
|
99
|
+
if (sampling.mode !== "values") return sampling;
|
|
100
|
+
const domain = scale?.domain;
|
|
101
|
+
if (!Array.isArray(domain) || domain.length < 2 || !domain.every(Number.isFinite)) {
|
|
102
|
+
throw new Error(`${label} exact values require a finite quantitative scale domain.`);
|
|
103
|
+
}
|
|
104
|
+
const lower = Math.min(...domain);
|
|
105
|
+
const upper = Math.max(...domain);
|
|
106
|
+
for (const value of sampling.values) {
|
|
107
|
+
if (scale.type === "log" && value <= 0) {
|
|
108
|
+
throw new RangeError(`${label} exact values must be positive for a log scale.`);
|
|
109
|
+
}
|
|
110
|
+
if (value < lower || value > upper) {
|
|
111
|
+
throw new RangeError(
|
|
112
|
+
`${label} exact value ${value} is outside the scale domain [${lower}, ${upper}].`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return sampling;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function resolveLegendSampleValues(config, scale, label = "Legend") {
|
|
120
|
+
const sampling = validateSamplingAgainstScale(
|
|
121
|
+
readLegendSampling(config),
|
|
122
|
+
scale,
|
|
123
|
+
label
|
|
124
|
+
);
|
|
125
|
+
return sampling.mode === "values"
|
|
126
|
+
? [...sampling.values]
|
|
127
|
+
: sampleNumericRange(
|
|
128
|
+
scale.domain[0],
|
|
129
|
+
scale.domain[1],
|
|
130
|
+
sampling.count,
|
|
131
|
+
`${label} domain`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function hasExactLegendSamplingForScale(program, scaleId) {
|
|
136
|
+
return ["size", "opacity", "strokeWidth"].some(kind => {
|
|
137
|
+
const config = program.guideConfigs.legend?.[kind];
|
|
138
|
+
return config?.scale === scaleId && readLegendSampling(config).mode === "values";
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import {
|
|
3
|
-
validateGeneratedItemLimit,
|
|
4
3
|
validateNonEmptyString,
|
|
5
4
|
validateKeys
|
|
6
5
|
} from "../../../core/validation.js";
|
|
7
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
formatDiscretizedIntervals,
|
|
8
|
+
isDiscreteSizeScaleType,
|
|
9
|
+
isSizeScaleType,
|
|
10
|
+
mapSizeValues
|
|
11
|
+
} from "../../../grammar/scales/index.js";
|
|
8
12
|
import { resolveLegendItemLayout } from "../../../layout/legendItems.js";
|
|
9
13
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
10
14
|
"../../../theme/defaults.js";
|
|
@@ -21,13 +25,18 @@ import {
|
|
|
21
25
|
resolveContinuousBounds,
|
|
22
26
|
resolveLegendBackgroundFromBounds,
|
|
23
27
|
formatContinuousValues,
|
|
24
|
-
sampleContinuousValues,
|
|
25
28
|
selectLegendLayer,
|
|
26
29
|
styleContinuousText
|
|
27
30
|
} from "./continuous/common.js";
|
|
28
31
|
import { legendResourcePolicies } from "../../../materialization/guides/resources.js";
|
|
32
|
+
import {
|
|
33
|
+
normalizeLegendSampling,
|
|
34
|
+
readLegendSampling,
|
|
35
|
+
resolveLegendSampleValues
|
|
36
|
+
} from "./sampling.js";
|
|
37
|
+
import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
|
|
29
38
|
|
|
30
|
-
const SIZE_OPTIONS = Object.freeze(["target", "count", "position", "layout", "align",
|
|
39
|
+
const SIZE_OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
|
|
31
40
|
"direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
|
|
32
41
|
|
|
33
42
|
export const SIZE_LEGEND_LABELS = Object.freeze({
|
|
@@ -61,21 +70,32 @@ export function resolveSizeLegendPoint(program, requested) {
|
|
|
61
70
|
return layer;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
function requireScale(program, id
|
|
73
|
+
function requireScale(program, id) {
|
|
65
74
|
const scale = program.resolvedScales[id];
|
|
66
|
-
if (scale?.type
|
|
67
|
-
throw new Error(`Legend requires resolved
|
|
75
|
+
if (!isSizeScaleType(scale?.type)) {
|
|
76
|
+
throw new Error(`Legend requires resolved size scale "${id}".`);
|
|
68
77
|
}
|
|
69
78
|
return scale;
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
export function resolveSizeLegendLayout(program, config) {
|
|
73
|
-
const scale = requireScale(program, config.scale
|
|
74
|
-
const categorical = [
|
|
82
|
+
const scale = requireScale(program, config.scale);
|
|
83
|
+
const categorical = [
|
|
84
|
+
program.guideConfigs.legend?.series,
|
|
85
|
+
program.guideConfigs.legend?.color,
|
|
86
|
+
program.guideConfigs.legend?.stroke
|
|
87
|
+
]
|
|
75
88
|
.find(candidate => candidate?.target === config.target);
|
|
76
89
|
const inherit = config.inheritAppearance === true && categorical !== undefined;
|
|
77
|
-
const
|
|
78
|
-
const
|
|
90
|
+
const inheritedLabels = inherit ? { ...categorical.labels, offset: config.labels.offset } : config.labels;
|
|
91
|
+
const inheritedTitleStyle = inherit ? categorical.titleStyle : config.titleStyle;
|
|
92
|
+
const effective = resolveEffectiveLegendBlockConfig(program, "size", {
|
|
93
|
+
...config,
|
|
94
|
+
labels: inheritedLabels,
|
|
95
|
+
titleStyle: inheritedTitleStyle
|
|
96
|
+
});
|
|
97
|
+
const labels = effective.labels;
|
|
98
|
+
const titleStyle = effective.titleStyle;
|
|
79
99
|
const position = categorical?.position ?? config.position;
|
|
80
100
|
if (categorical?.layout === "legacy-bottom") {
|
|
81
101
|
throw new Error('Combined size legends require layout "edge".');
|
|
@@ -84,27 +104,46 @@ export function resolveSizeLegendLayout(program, config) {
|
|
|
84
104
|
const geometry = horizontal ? {
|
|
85
105
|
position, align: categorical.align, direction: categorical.direction,
|
|
86
106
|
columns: categorical.columns, titlePosition: categorical.titlePosition,
|
|
87
|
-
offset: categorical.offset, itemGap: categorical.itemGap
|
|
107
|
+
offset: categorical.offset, itemGap: effective.blockGap ?? categorical.itemGap
|
|
88
108
|
} : { position };
|
|
89
|
-
const
|
|
90
|
-
|
|
109
|
+
const discrete = isDiscreteSizeScaleType(scale.type);
|
|
110
|
+
if (discrete && readLegendSampling(config).mode === "values") {
|
|
111
|
+
throw new Error("Discrete size legends do not support exact values.");
|
|
112
|
+
}
|
|
113
|
+
const values = discrete
|
|
114
|
+
? undefined
|
|
115
|
+
: resolveLegendSampleValues(effective, scale, "Size legend");
|
|
116
|
+
const areas = discrete
|
|
117
|
+
? scale.range
|
|
118
|
+
: mapSizeValues(values, scale);
|
|
91
119
|
const radii = areas.map(area => Math.sqrt(area / Math.PI));
|
|
92
120
|
const radius = Math.max(...radii);
|
|
93
121
|
const width = Math.max(32, radius * 2);
|
|
94
122
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
95
|
-
const text =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
const text = discrete
|
|
124
|
+
? formatDiscretizedIntervals(scale.thresholds, config.labels.format)
|
|
125
|
+
: formatContinuousValues(
|
|
126
|
+
values,
|
|
127
|
+
scale.domain,
|
|
128
|
+
"quantitative",
|
|
129
|
+
config.labels.format
|
|
130
|
+
);
|
|
131
|
+
const symbolStroke = effective.blockSymbol?.strokeWidth ?? 0;
|
|
132
|
+
const symbolExtent = symbolStroke / 2;
|
|
133
|
+
const sampleWidth = width + symbolStroke;
|
|
134
|
+
const layout = resolveLegendItemLayout(plot, { ...effective, ...geometry, labels, titleStyle }, text, {
|
|
135
|
+
width: sampleWidth, height: radius * 2 + symbolStroke,
|
|
136
|
+
itemBounds: radii.map(r => ({
|
|
137
|
+
left: sampleWidth / 2 - r - symbolExtent,
|
|
138
|
+
right: sampleWidth / 2 + r + symbolExtent,
|
|
139
|
+
top: -r - symbolExtent,
|
|
140
|
+
bottom: r + symbolExtent
|
|
141
|
+
}))
|
|
104
142
|
});
|
|
105
|
-
assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Size legend layout", { ...
|
|
106
|
-
const background = resolveLegendBackgroundFromBounds(layout.bounds,
|
|
107
|
-
return { ...layout, symbolX: layout.symbolX.map(x => x +
|
|
143
|
+
assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Size legend layout", { ...effective, ...geometry });
|
|
144
|
+
const background = resolveLegendBackgroundFromBounds(layout.bounds, effective.border, canvas, "Size legend", { ...effective, ...geometry });
|
|
145
|
+
return { ...layout, symbolX: layout.symbolX.map(x => x + sampleWidth / 2), radii, text,
|
|
146
|
+
labels, titleStyle, background, config: effective };
|
|
108
147
|
}
|
|
109
148
|
|
|
110
149
|
export const rematerializeSizeLegend = action(
|
|
@@ -121,7 +160,7 @@ export const rematerializeSizeLegend = action(
|
|
|
121
160
|
if (encoding?.scale === undefined) {
|
|
122
161
|
throw new Error("Size legend target requires a size encoding.");
|
|
123
162
|
}
|
|
124
|
-
const scale = requireScale(this, encoding.scale
|
|
163
|
+
const scale = requireScale(this, encoding.scale);
|
|
125
164
|
const title = config.inferredTitle === true ? encoding.field : config.title;
|
|
126
165
|
const currentConfig = {
|
|
127
166
|
...config,
|
|
@@ -130,6 +169,7 @@ export const rematerializeSizeLegend = action(
|
|
|
130
169
|
domain: scale.domain
|
|
131
170
|
};
|
|
132
171
|
const layout = resolveSizeLegendLayout(this, currentConfig);
|
|
172
|
+
const effective = layout.config;
|
|
133
173
|
const { itemY, symbolX, labelX, radii, labels, titleStyle } = layout;
|
|
134
174
|
let next = this
|
|
135
175
|
.editSemantic({ property: "guide.legend.size.scale", value: encoding.scale })
|
|
@@ -146,9 +186,10 @@ export const rematerializeSizeLegend = action(
|
|
|
146
186
|
.editGraphics({
|
|
147
187
|
target: "sizeLegendSymbols",
|
|
148
188
|
property: "fill",
|
|
149
|
-
value: DEFAULT_COLORS.sizeSymbol
|
|
189
|
+
value: effective.blockSymbol?.fill ?? DEFAULT_COLORS.sizeSymbol
|
|
150
190
|
})
|
|
151
|
-
.editGraphics({ target: "sizeLegendSymbols", property: "opacity",
|
|
191
|
+
.editGraphics({ target: "sizeLegendSymbols", property: "opacity",
|
|
192
|
+
value: effective.blockSymbol?.opacity ?? 0.7 })
|
|
152
193
|
.editGraphics({ target: "sizeLegendLabels", property: "length", value: radii.length })
|
|
153
194
|
.editGraphics({ target: "sizeLegendLabels", property: "x", value: labelX })
|
|
154
195
|
.editGraphics({ target: "sizeLegendLabels", property: "y", value: itemY })
|
|
@@ -158,12 +199,20 @@ export const rematerializeSizeLegend = action(
|
|
|
158
199
|
value: layout.text
|
|
159
200
|
});
|
|
160
201
|
next = editLegendBackground(next, "sizeLegendBackground", layout.background, currentConfig.border);
|
|
202
|
+
if (effective.blockSymbol?.stroke !== undefined) {
|
|
203
|
+
next = next.editGraphics({ target: "sizeLegendSymbols", property: "stroke",
|
|
204
|
+
value: effective.blockSymbol.stroke });
|
|
205
|
+
}
|
|
206
|
+
if (effective.blockSymbol?.strokeWidth !== undefined) {
|
|
207
|
+
next = next.editGraphics({ target: "sizeLegendSymbols", property: "strokeWidth",
|
|
208
|
+
value: effective.blockSymbol.strokeWidth });
|
|
209
|
+
}
|
|
161
210
|
next = styleContinuousText(next, "sizeLegendLabels", labels);
|
|
162
|
-
if (
|
|
211
|
+
if (effective.titleVisible === false) return next;
|
|
163
212
|
next = next
|
|
164
213
|
.editGraphics({ target: "sizeLegendTitle", property: "x", value: layout.title.x })
|
|
165
214
|
.editGraphics({ target: "sizeLegendTitle", property: "y", value: layout.title.y })
|
|
166
|
-
.editGraphics({ target: "sizeLegendTitle", property: "text", value: title });
|
|
215
|
+
.editGraphics({ target: "sizeLegendTitle", property: "text", value: effective.title });
|
|
167
216
|
return styleContinuousText(next, "sizeLegendTitle", titleStyle, { align: layout.title.align });
|
|
168
217
|
}
|
|
169
218
|
);
|
|
@@ -177,12 +226,16 @@ export function resolveSizeLegendConfig(program, args = {}) {
|
|
|
177
226
|
if (encoding?.scale === undefined) {
|
|
178
227
|
throw new Error(`Point mark "${layer.id}" requires a size encoding.`);
|
|
179
228
|
}
|
|
180
|
-
const scale = requireScale(program, encoding.scale
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
183
|
-
throw new
|
|
229
|
+
const scale = requireScale(program, encoding.scale);
|
|
230
|
+
const discrete = isDiscreteSizeScaleType(scale.type);
|
|
231
|
+
if (discrete && (Object.hasOwn(args, "count") || Object.hasOwn(args, "values"))) {
|
|
232
|
+
throw new Error("Discrete size legends do not support count or exact values.");
|
|
184
233
|
}
|
|
185
|
-
|
|
234
|
+
const sampling = discrete ? undefined : normalizeLegendSampling(args, {
|
|
235
|
+
operation: "create",
|
|
236
|
+
label: "Size legend"
|
|
237
|
+
});
|
|
238
|
+
const count = discrete ? scale.range.length : undefined;
|
|
186
239
|
return {
|
|
187
240
|
target: layer.id,
|
|
188
241
|
scale: encoding.scale,
|
|
@@ -190,7 +243,7 @@ export function resolveSizeLegendConfig(program, args = {}) {
|
|
|
190
243
|
title: args.title ?? encoding.field,
|
|
191
244
|
inferredTitle: args.title === undefined,
|
|
192
245
|
domain: scale.domain,
|
|
193
|
-
count,
|
|
246
|
+
...(discrete ? { count } : { sampling }),
|
|
194
247
|
inheritAppearance: args.inheritAppearance === true,
|
|
195
248
|
labels: normalizeLegendTextOptions(args.labels, "createLegend.labels", SIZE_LEGEND_LABELS),
|
|
196
249
|
titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", SIZE_LEGEND_TITLE_STYLE),
|
|
@@ -200,8 +253,8 @@ export function resolveSizeLegendConfig(program, args = {}) {
|
|
|
200
253
|
}
|
|
201
254
|
|
|
202
255
|
export function createSizeLegendFromConfig(program, config) {
|
|
203
|
-
resolveSizeLegendLayout(program, config);
|
|
204
|
-
const
|
|
256
|
+
const { radii } = resolveSizeLegendLayout(program, config);
|
|
257
|
+
const count = radii.length;
|
|
205
258
|
const following = new Set(legendResourcePolicies().filter(policy =>
|
|
206
259
|
policy.kind !== "size" && policy.family !== "categorical" &&
|
|
207
260
|
program.guideConfigs.legend?.[policy.kind] !== undefined
|
|
@@ -241,7 +294,7 @@ export function createSizeLegendFromConfig(program, config) {
|
|
|
241
294
|
export const createSizeLegend = action(
|
|
242
295
|
{
|
|
243
296
|
op: "createSizeLegend",
|
|
244
|
-
description: "Create
|
|
297
|
+
description: "Create an equal-area point-size legend."
|
|
245
298
|
},
|
|
246
299
|
function (args = {}) {
|
|
247
300
|
const config = resolveSizeLegendConfig(this, args);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import {
|
|
3
|
-
validateGeneratedItemLimit,
|
|
4
3
|
validateNonEmptyString,
|
|
5
4
|
validateKeys
|
|
6
5
|
} from "../../../core/validation.js";
|
|
@@ -21,12 +20,16 @@ import {
|
|
|
21
20
|
resolveContinuousBounds,
|
|
22
21
|
resolveLegendBackgroundFromBounds,
|
|
23
22
|
formatContinuousValues,
|
|
24
|
-
sampleContinuousValues,
|
|
25
23
|
selectLegendLayer,
|
|
26
24
|
styleContinuousText
|
|
27
25
|
} from "./continuous/common.js";
|
|
26
|
+
import {
|
|
27
|
+
normalizeLegendSampling,
|
|
28
|
+
resolveLegendSampleValues
|
|
29
|
+
} from "./sampling.js";
|
|
30
|
+
import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
|
|
28
31
|
|
|
29
|
-
const OPTIONS = Object.freeze(["target", "count", "position", "layout", "align",
|
|
32
|
+
const OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
|
|
30
33
|
"direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
|
|
31
34
|
|
|
32
35
|
export const STROKE_WIDTH_LEGEND_LABELS = Object.freeze({
|
|
@@ -74,20 +77,21 @@ function requireScale(program, id) {
|
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
export function resolveStrokeWidthLegendLayout(program, config) {
|
|
80
|
+
const effective = resolveEffectiveLegendBlockConfig(program, "strokeWidth", config);
|
|
77
81
|
const scale = requireScale(program, config.scale);
|
|
78
82
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
79
|
-
const values =
|
|
83
|
+
const values = resolveLegendSampleValues(effective, scale, "Stroke-width legend");
|
|
80
84
|
const widths = mapContinuousScaleValues(values, scale);
|
|
81
85
|
const labels = formatContinuousValues(
|
|
82
86
|
values,
|
|
83
87
|
scale.domain,
|
|
84
88
|
"quantitative",
|
|
85
|
-
|
|
89
|
+
effective.labels.format
|
|
86
90
|
);
|
|
87
|
-
const layout = resolveLegendItemLayout(plot,
|
|
88
|
-
assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Stroke-width legend layout",
|
|
89
|
-
const background = resolveLegendBackgroundFromBounds(layout.bounds,
|
|
90
|
-
return { ...layout, widths, labels, background };
|
|
91
|
+
const layout = resolveLegendItemLayout(plot, effective, labels, { width: 32, height: 0, strokeWidth: widths });
|
|
92
|
+
assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Stroke-width legend layout", effective);
|
|
93
|
+
const background = resolveLegendBackgroundFromBounds(layout.bounds, effective.border, canvas, "Stroke-width legend", effective);
|
|
94
|
+
return { ...layout, widths, labels, background, config: effective };
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
export const rematerializeStrokeWidthLegend = /* @__PURE__ */ action(
|
|
@@ -114,15 +118,15 @@ export const rematerializeStrokeWidthLegend = /* @__PURE__ */ action(
|
|
|
114
118
|
}
|
|
115
119
|
const scale = requireScale(this, encoding.scale);
|
|
116
120
|
const title = config.inferredTitle === true ? encoding.field : config.title;
|
|
117
|
-
const
|
|
121
|
+
const currentConfig = { ...config, scale: encoding.scale, title };
|
|
122
|
+
const layout = resolveStrokeWidthLegendLayout(this, currentConfig);
|
|
123
|
+
const effective = layout.config;
|
|
118
124
|
const { itemY: y, widths } = layout;
|
|
119
125
|
let next = this
|
|
120
126
|
.editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
|
|
121
127
|
.editSemantic({ property: "guide.legend.strokeWidth.title", value: title })
|
|
122
128
|
._withLegendConfig("strokeWidth", {
|
|
123
|
-
...
|
|
124
|
-
scale: encoding.scale,
|
|
125
|
-
title,
|
|
129
|
+
...currentConfig,
|
|
126
130
|
domain: scale.domain
|
|
127
131
|
})
|
|
128
132
|
.editGraphics({ target: "strokeWidthLegendSymbols", property: "length", value: widths.length })
|
|
@@ -130,7 +134,8 @@ export const rematerializeStrokeWidthLegend = /* @__PURE__ */ action(
|
|
|
130
134
|
.editGraphics({ target: "strokeWidthLegendSymbols", property: "x2", value: layout.symbolX.map(x => x + 32) })
|
|
131
135
|
.editGraphics({ target: "strokeWidthLegendSymbols", property: "y1", value: y })
|
|
132
136
|
.editGraphics({ target: "strokeWidthLegendSymbols", property: "y2", value: y })
|
|
133
|
-
.editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke",
|
|
137
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke",
|
|
138
|
+
value: effective.blockSymbol?.stroke ?? DEFAULT_COLORS.mark })
|
|
134
139
|
.editGraphics({ target: "strokeWidthLegendSymbols", property: "strokeWidth", value: widths })
|
|
135
140
|
.editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: widths.length })
|
|
136
141
|
.editGraphics({
|
|
@@ -145,13 +150,17 @@ export const rematerializeStrokeWidthLegend = /* @__PURE__ */ action(
|
|
|
145
150
|
value: layout.labels
|
|
146
151
|
});
|
|
147
152
|
next = editLegendBackground(next, "strokeWidthLegendBackground", layout.background, config.border);
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
if (effective.blockSymbol?.opacity !== undefined) {
|
|
154
|
+
next = next.editGraphics({ target: "strokeWidthLegendSymbols", property: "opacity",
|
|
155
|
+
value: effective.blockSymbol.opacity });
|
|
156
|
+
}
|
|
157
|
+
next = styleContinuousText(next, "strokeWidthLegendLabels", effective.labels);
|
|
158
|
+
if (effective.titleVisible === false) return next;
|
|
150
159
|
next = next
|
|
151
160
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: layout.title.x })
|
|
152
161
|
.editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: layout.title.y })
|
|
153
|
-
.editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
|
|
154
|
-
return styleContinuousText(next, "strokeWidthLegendTitle",
|
|
162
|
+
.editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: effective.title });
|
|
163
|
+
return styleContinuousText(next, "strokeWidthLegendTitle", effective.titleStyle, { align: layout.title.align });
|
|
155
164
|
}
|
|
156
165
|
);
|
|
157
166
|
|
|
@@ -162,20 +171,17 @@ export function resolveStrokeWidthLegendConfig(program, args = {}) {
|
|
|
162
171
|
const layer = resolveLayer(program, args.target);
|
|
163
172
|
const encoding = layer.encoding.strokeWidth;
|
|
164
173
|
requireScale(program, encoding.scale);
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
validateGeneratedItemLimit(count, "Stroke-width legend count");
|
|
174
|
+
const sampling = normalizeLegendSampling(args, {
|
|
175
|
+
operation: "create",
|
|
176
|
+
label: "Stroke-width legend"
|
|
177
|
+
});
|
|
172
178
|
return {
|
|
173
179
|
target: layer.id,
|
|
174
180
|
scale: encoding.scale,
|
|
175
181
|
...normalizeItemLegendLayout({ ...args, itemGap: args.itemGap ?? 32 }),
|
|
176
182
|
title: args.title ?? encoding.field,
|
|
177
183
|
inferredTitle: args.title === undefined,
|
|
178
|
-
|
|
184
|
+
sampling,
|
|
179
185
|
labels: normalizeLegendTextOptions(args.labels, "createLegend.labels", STROKE_WIDTH_LEGEND_LABELS),
|
|
180
186
|
titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", STROKE_WIDTH_LEGEND_TITLE_STYLE),
|
|
181
187
|
border: normalizeLegendBorder(args.border),
|
|
@@ -184,8 +190,8 @@ export function resolveStrokeWidthLegendConfig(program, args = {}) {
|
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
export function createStrokeWidthLegendFromConfig(program, config) {
|
|
187
|
-
resolveStrokeWidthLegendLayout(program, config);
|
|
188
|
-
const
|
|
193
|
+
const layout = resolveStrokeWidthLegendLayout(program, config);
|
|
194
|
+
const count = layout.widths.length;
|
|
189
195
|
let next = program
|
|
190
196
|
.editSemantic({ property: "guide.legend.strokeWidth.scale", value: config.scale })
|
|
191
197
|
.editSemantic({ property: "guide.legend.strokeWidth.title", value: config.title })
|