ggaction 0.0.21 → 0.0.22
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 +73 -0
- package/README.md +3 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +6 -2
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +1 -1
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +21 -35
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +21 -4
- package/src/core/datasetReferences.js +17 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +31 -1
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +23 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +5 -0
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +108 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +270 -60
- package/types/scene.d.ts +35 -0
|
@@ -1,515 +1 @@
|
|
|
1
|
-
import {resolveColorSizeConfig,createColorSizeLegendFromConfig,materializeColorSizeLegend} from "../colorSize.js";
|
|
2
|
-
import {resetLegendClipGraphics,applyLegendClipGraphics,restoreLegendClipContext}from"../../../../materialization/legendClip.js";
|
|
3
|
-
import {validateLegendCombine,resolveSizeShapeCreation} from "../combine.js";
|
|
4
|
-
import { recordLegendOffsetIntent } from "../group.js";
|
|
5
|
-
import { prepareLegendGroup, creationLegendTarget, validateLegendGroupOptions } from "../group.js";
|
|
6
|
-
import { PLOT_LEGEND_POSITIONS } from "../../../../layout/legend.js";
|
|
7
|
-
import { normalizeInitialLegendTitle } from "../continuous/common.js";
|
|
8
|
-
import { isContinuousColorScaleType } from "../../../../grammar/scales/types.js";
|
|
9
|
-
import { withGuideLayoutValidation } from "../../../../materialization/guides/layout.js";
|
|
10
|
-
import { sameGuideValue } from "../../reuse.js";
|
|
11
|
-
import { normalizeLegendOrder } from "../../../../grammar/categoryOrder.js";
|
|
12
|
-
import { action } from "../../../../core/action.js";
|
|
13
|
-
import { validateOptionObject } from "../../../../core/validation.js";
|
|
14
|
-
import { noOptions, resolveLayout, activeConfig } from "./layout.js";
|
|
15
|
-
import { normalizeOptions } from "./options.js";
|
|
16
|
-
import { normalizeRecipe, resolveLegendSymbol } from "./recipes.js";
|
|
17
|
-
import { validateLegendChannels } from "../target.js";
|
|
18
|
-
import { createCategoricalLegendFromConfig, reconcileCategoricalSymbols } from "../lifecycle.js";
|
|
19
|
-
import { findLayer } from "../../../../selectors/layers.js";
|
|
20
|
-
import { findSemanticScale } from "../../../../selectors/scales.js";
|
|
21
|
-
import { isSizeLegendPoint, resolveSizeLegendConfig } from "../size.js";
|
|
22
|
-
import { isStrokeWidthLegendLayer } from "../strokeWidth.js";
|
|
23
|
-
import { isOpacityLegendLayer } from "../../../../materialization/legends.js";
|
|
24
|
-
import { legendResourcePolicies } from
|
|
25
|
-
"../../../../materialization/guides/resources.js";
|
|
26
|
-
import {
|
|
27
|
-
hasHorizontalLegendLane,
|
|
28
|
-
hasLegendLane
|
|
29
|
-
} from "../lane.js";
|
|
30
|
-
import {
|
|
31
|
-
resolveCurrentDefinition,
|
|
32
|
-
resolveDefinition,
|
|
33
|
-
resolveTarget,
|
|
34
|
-
sameValues
|
|
35
|
-
} from "./resolve.js";
|
|
36
|
-
import { readLegendSampling } from "../sampling.js";
|
|
37
|
-
import {
|
|
38
|
-
reconcileLegendBlockTitleGraphic,
|
|
39
|
-
resolveEffectiveLegendBlockConfig
|
|
40
|
-
} from "../blocks.js";
|
|
41
|
-
|
|
42
|
-
function finishLegend(program) {
|
|
43
|
-
return Object.values(program.guideConfigs.legend??{}).some(config=>config.clipHeight!==undefined)||hasLegendLane(program) || Object.keys(program.guideConfigs.legendGroups ?? {}).length > 0
|
|
44
|
-
? program.rematerializeLegend() : program;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function requestedCandidate(program, target, candidates) {
|
|
48
|
-
if (target === undefined) {
|
|
49
|
-
return candidates.length === 1 ? candidates[0] : undefined;
|
|
50
|
-
}
|
|
51
|
-
const layer = findLayer(program, target);
|
|
52
|
-
return candidates.includes(layer) ? layer : undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function resolveStandaloneLegendStep(args, kind) {
|
|
56
|
-
if (Object.hasOwn(args, "inheritAppearance")) throw new Error("createLegend does not accept inheritAppearance.");
|
|
57
|
-
const { channels: _channels, ...options } = args;
|
|
58
|
-
return { op: kind === "size" ? "createSizeLegend" : "createStrokeWidthLegend", args: options };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const rematerializeLegend = /* @__PURE__ */ action(
|
|
62
|
-
{ op: "rematerializeLegend", description: "Rematerialize every existing legend component." },
|
|
63
|
-
withGuideLayoutValidation(function (args = {}) {
|
|
64
|
-
noOptions(args, "rematerializeLegend");
|
|
65
|
-
const oldClipContext=this.context.deferLegendClip;
|
|
66
|
-
let next = resetLegendClipGraphics(this)._withContext({deferLegendClip:true});
|
|
67
|
-
const categoricalKinds = ["series", "color", "stroke"].filter(
|
|
68
|
-
kind => this.guideConfigs.legend?.[kind] !== undefined
|
|
69
|
-
);
|
|
70
|
-
for (const kind of categoricalKinds) {
|
|
71
|
-
const stored = next.guideConfigs.legend[kind];
|
|
72
|
-
const definition = resolveCurrentDefinition(next, stored);
|
|
73
|
-
const symbol = stored.inferredSymbol
|
|
74
|
-
? normalizeRecipe(
|
|
75
|
-
resolveLegendSymbol(
|
|
76
|
-
next,
|
|
77
|
-
findLayer(next, stored.target),
|
|
78
|
-
definition.channels,
|
|
79
|
-
undefined,
|
|
80
|
-
kind
|
|
81
|
-
),
|
|
82
|
-
kind,
|
|
83
|
-
{ internal: true }
|
|
84
|
-
)
|
|
85
|
-
: stored.symbol;
|
|
86
|
-
const symbolChanged = !sameGuideValue(symbol, stored.symbol);
|
|
87
|
-
const changed = symbolChanged ||
|
|
88
|
-
!sameValues(stored.channels, definition.channels) ||
|
|
89
|
-
!sameValues(stored.domain, definition.domain) ||
|
|
90
|
-
!sameValues(stored.scales, definition.scales) ||
|
|
91
|
-
stored.field !== definition.field ||
|
|
92
|
-
!sameValues(stored.sizeAreas ?? [], definition.sizeAreas ?? []) ||
|
|
93
|
-
stored.title !== definition.title;
|
|
94
|
-
next = changed
|
|
95
|
-
? next._withLegendConfig(kind, {
|
|
96
|
-
...stored,
|
|
97
|
-
symbol,
|
|
98
|
-
channels: definition.channels,
|
|
99
|
-
scales: definition.scales,
|
|
100
|
-
field: definition.field,
|
|
101
|
-
...(definition.combine === undefined ? {} : {combine: definition.combine, sizeAreas: definition.sizeAreas}),
|
|
102
|
-
title: definition.title,
|
|
103
|
-
domain: definition.domain
|
|
104
|
-
})
|
|
105
|
-
: next;
|
|
106
|
-
const config = resolveEffectiveLegendBlockConfig(
|
|
107
|
-
next,
|
|
108
|
-
kind,
|
|
109
|
-
next.guideConfigs.legend[kind]
|
|
110
|
-
);
|
|
111
|
-
next = reconcileLegendBlockTitleGraphic(next, kind,
|
|
112
|
-
config.titleVisible !== false);
|
|
113
|
-
if (kind === "series") {
|
|
114
|
-
if (!sameValues(
|
|
115
|
-
next.semanticSpec.guides.legend.series.scales,
|
|
116
|
-
definition.scales
|
|
117
|
-
)) {
|
|
118
|
-
next = next.editSemantic({
|
|
119
|
-
property: "guide.legend.series.scales",
|
|
120
|
-
value: definition.scales
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
if (next.semanticSpec.guides.legend.series.title !== definition.title) {
|
|
124
|
-
next = next.editSemantic({
|
|
125
|
-
property: "guide.legend.series.title",
|
|
126
|
-
value: definition.title
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
if (next.semanticSpec.guides.legend[kind].scale !== definition.scales[0]) {
|
|
131
|
-
next = next.editSemantic({
|
|
132
|
-
property: `guide.legend.${kind}.scale`,
|
|
133
|
-
value: definition.scales[0]
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
if (next.semanticSpec.guides.legend[kind].title !== definition.title) {
|
|
137
|
-
next = next.editSemantic({
|
|
138
|
-
property: `guide.legend.${kind}.title`,
|
|
139
|
-
value: definition.title
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (symbolChanged) {
|
|
144
|
-
resolveLayout(next, config);
|
|
145
|
-
next = reconcileCategoricalSymbols(next, stored, config);
|
|
146
|
-
}
|
|
147
|
-
if (config.border !== false) {
|
|
148
|
-
next = next.rematerializeLegendBackground({ kind });
|
|
149
|
-
}
|
|
150
|
-
next = next
|
|
151
|
-
.rematerializeLegendSymbols({ kind })
|
|
152
|
-
.rematerializeLegendLabels({ kind });
|
|
153
|
-
if (config.titleVisible !== false) {
|
|
154
|
-
next = next.rematerializeLegendTitle({ kind });
|
|
155
|
-
}
|
|
156
|
-
const hasHighlight = Object.values(
|
|
157
|
-
next.materializationConfigs.highlights ?? {}
|
|
158
|
-
).some(highlight => highlight.target === config.target);
|
|
159
|
-
if (hasHighlight) next = next.rematerializeLegendHighlights({ kind });
|
|
160
|
-
}
|
|
161
|
-
if(next.guideConfigs.legend?.colorSize !== undefined) next=materializeColorSizeLegend(next,next.guideConfigs.legend.colorSize);
|
|
162
|
-
for (const policy of legendResourcePolicies()) {
|
|
163
|
-
if (
|
|
164
|
-
policy.rematerializeOp !== undefined &&
|
|
165
|
-
this.guideConfigs.legend?.[policy.kind] !== undefined
|
|
166
|
-
) {
|
|
167
|
-
const config = resolveEffectiveLegendBlockConfig(
|
|
168
|
-
next,
|
|
169
|
-
policy.kind,
|
|
170
|
-
next.guideConfigs.legend[policy.kind]
|
|
171
|
-
);
|
|
172
|
-
next = reconcileLegendBlockTitleGraphic(next, policy.kind,
|
|
173
|
-
config.titleVisible !== false);
|
|
174
|
-
next = next[policy.rematerializeOp]();
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const clipAnchors = Object.fromEntries(categoricalKinds.filter(kind => next.guideConfigs.legend[kind].clipHeight !== undefined)
|
|
178
|
-
.map(kind => [kind, resolveLayout(next, resolveEffectiveLegendBlockConfig(next, kind, next.guideConfigs.legend[kind])).itemY]));
|
|
179
|
-
next=applyLegendClipGraphics(next,{force:true,anchors:clipAnchors});
|
|
180
|
-
next=restoreLegendClipContext(next,oldClipContext);
|
|
181
|
-
if (hasHorizontalLegendLane(next)) {
|
|
182
|
-
next = next.rematerializeHorizontalLegendLane();
|
|
183
|
-
}
|
|
184
|
-
return next;
|
|
185
|
-
})
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
export function resolveCategoricalLegendConfig(program, args = {}) {
|
|
189
|
-
const layer = resolveTarget(program, args.target);
|
|
190
|
-
const definition = resolveDefinition(
|
|
191
|
-
program,
|
|
192
|
-
layer,
|
|
193
|
-
args.channels,
|
|
194
|
-
args.title === false ? undefined : args.title,
|
|
195
|
-
args.order === undefined ? undefined : normalizeLegendOrder(args.order)
|
|
196
|
-
);
|
|
197
|
-
const inferredSymbol = args.symbol === undefined || args.symbol === "auto";
|
|
198
|
-
const options = normalizeOptions({ ...args,
|
|
199
|
-
symbol: resolveLegendSymbol(
|
|
200
|
-
program,
|
|
201
|
-
layer,
|
|
202
|
-
definition.channels,
|
|
203
|
-
args.symbol,
|
|
204
|
-
definition.kind
|
|
205
|
-
)
|
|
206
|
-
}, definition.kind, { internalSymbol: inferredSymbol });
|
|
207
|
-
const config = {
|
|
208
|
-
target: layer.id,
|
|
209
|
-
...definition,
|
|
210
|
-
...normalizeInitialLegendTitle(args.title, definition.title),
|
|
211
|
-
inferredTitle: args.title === false || !Object.hasOwn(args, "title"),
|
|
212
|
-
inferredSymbol,
|
|
213
|
-
position: options.position,
|
|
214
|
-
align: options.align,
|
|
215
|
-
direction: options.direction,
|
|
216
|
-
columns: options.columns,
|
|
217
|
-
offset: options.offset,
|
|
218
|
-
titlePosition: options.titlePosition,
|
|
219
|
-
symbol: options.symbol,
|
|
220
|
-
labels: options.labels,
|
|
221
|
-
titleStyle: options.titleStyle,
|
|
222
|
-
itemGap: options.itemGap,
|
|
223
|
-
layout: options.layout,
|
|
224
|
-
...(options.clipHeight === undefined ? {} : {clipHeight:options.clipHeight}),
|
|
225
|
-
...(options.overflow === undefined ? {} : { overflow: options.overflow }),
|
|
226
|
-
border: options.border
|
|
227
|
-
};
|
|
228
|
-
return config;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export const createCategoricalLegend = /* @__PURE__ */ action(
|
|
232
|
-
{ op: "createCategoricalLegend", description: "Create one categorical legend block." },
|
|
233
|
-
withGuideLayoutValidation(function (args = {}) {
|
|
234
|
-
const config = resolveCategoricalLegendConfig(this, args);
|
|
235
|
-
if (this.semanticSpec.guides.legend?.[config.kind] !== undefined) {
|
|
236
|
-
throw new Error(
|
|
237
|
-
`createCategoricalLegend requires a missing legend for ${config.kind}.`
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
resolveLayout(this, config);
|
|
241
|
-
return createCategoricalLegendFromConfig(this, config,
|
|
242
|
-
args.order === undefined ? undefined : normalizeLegendOrder(args.order));
|
|
243
|
-
})
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
function resolveRawLegendCreationPlan(program, args = {}, layers = program.semanticSpec.layers, internalChannels) {
|
|
247
|
-
if (Object.hasOwn(args, "combine")) {
|
|
248
|
-
validateLegendCombine(args.combine);
|
|
249
|
-
if (args.combine === "color-size") {
|
|
250
|
-
const config=resolveColorSizeConfig(program,args);
|
|
251
|
-
const prepared=prepareLegendGroup(program,{...args,blockLayout:args.blockLayout??(["left","right"].includes(args.position??"right")?"column":"row")},config.target,{creation:true,nativeOffset:30});
|
|
252
|
-
return {steps:[{op:"createColorSizeLegend",args}],finish:"always",legendGroup:{target:config.target,config:prepared.program.guideConfigs.legendGroups[config.target]}};
|
|
253
|
-
}
|
|
254
|
-
if (args.combine === "size-shape") return resolveSizeShapeCreation(program, args, layers, resolveRawLegendCreationPlan, internalChannels);
|
|
255
|
-
const {combine: _combine, ...separate} = args;
|
|
256
|
-
return resolveRawLegendCreationPlan(program, separate, layers);
|
|
257
|
-
}
|
|
258
|
-
validateOptionObject(args, undefined, "createLegend");
|
|
259
|
-
const candidates = args.target === undefined ? layers : layers.filter(layer => layer.id === args.target);
|
|
260
|
-
const channels = args.channels;
|
|
261
|
-
if (channels !== undefined) validateLegendChannels(channels, "createLegend");
|
|
262
|
-
const standalone = [
|
|
263
|
-
["size", isSizeLegendPoint, ["color", "stroke", "shape", "strokeDash", "opacity"]],
|
|
264
|
-
["strokeWidth", isStrokeWidthLegendLayer, args.target === undefined
|
|
265
|
-
? ["color", "stroke", "shape", "strokeDash", "size", "opacity"]
|
|
266
|
-
: ["color", "stroke", "shape", "strokeDash", "opacity"]],
|
|
267
|
-
["opacity", isOpacityLegendLayer,
|
|
268
|
-
["color", "stroke", "shape", "strokeDash", "size"]]
|
|
269
|
-
];
|
|
270
|
-
for (const [kind, eligible, otherChannels] of standalone) {
|
|
271
|
-
const explicit = channels?.length === 1 && channels[0] === kind;
|
|
272
|
-
const inferred = channels === undefined && candidates.filter(eligible).length === 1 &&
|
|
273
|
-
!candidates.some(layer => otherChannels.some(channel => layer.encoding?.[channel]?.scale !== undefined));
|
|
274
|
-
if (explicit || inferred) {
|
|
275
|
-
const step = kind === "opacity" ? { op: "createOpacityLegend", args }
|
|
276
|
-
: resolveStandaloneLegendStep(args, kind);
|
|
277
|
-
return { steps: [step], finish: "auto" };
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const continuousColorCandidates = candidates.filter(layer => {
|
|
281
|
-
const encoding = ["point", "bar", "rect", "text", "area"].includes(layer.mark?.type)
|
|
282
|
-
? layer.encoding?.color
|
|
283
|
-
: undefined;
|
|
284
|
-
const scale = findSemanticScale(program, encoding?.scale);
|
|
285
|
-
return isContinuousColorScaleType(scale?.type);
|
|
286
|
-
});
|
|
287
|
-
const continuousColor = requestedCandidate(
|
|
288
|
-
program, args.target, continuousColorCandidates
|
|
289
|
-
);
|
|
290
|
-
if (args.representation !== undefined && channels !== undefined && channels.length > 1) {
|
|
291
|
-
if (!continuousColor || !channels.includes("color")) throw new Error("Legend representation requires selected continuous color.");
|
|
292
|
-
const { representation, values, count, symbol, gradient, ...common } = args;
|
|
293
|
-
const steps = channels.flatMap(channel => resolveRawLegendCreationPlan(program,
|
|
294
|
-
channel === "color" ? { ...args, channels: [channel] }
|
|
295
|
-
: { ...common, channels: [channel] }, layers).steps);
|
|
296
|
-
return { steps, finish: "always" };
|
|
297
|
-
}
|
|
298
|
-
if (
|
|
299
|
-
(channels?.length === 1 && channels[0] === "color" && continuousColor) ||
|
|
300
|
-
(channels === undefined && continuousColor)
|
|
301
|
-
) {
|
|
302
|
-
return { steps: [{ op: "createGradientLegend", args }], finish: "auto" };
|
|
303
|
-
}
|
|
304
|
-
const intervalColorCandidates = candidates.filter(layer => {
|
|
305
|
-
const encoding = ["point", "bar", "rect", "text", "area"].includes(layer.mark?.type) ? layer.encoding?.color : undefined;
|
|
306
|
-
const scale = findSemanticScale(program, encoding?.scale);
|
|
307
|
-
return ["quantize", "quantile", "threshold"].includes(scale?.type);
|
|
308
|
-
});
|
|
309
|
-
if (
|
|
310
|
-
(channels?.length === 1 && channels[0] === "color" &&
|
|
311
|
-
intervalColorCandidates.length > 0) ||
|
|
312
|
-
(channels === undefined && intervalColorCandidates.length > 0)
|
|
313
|
-
) {
|
|
314
|
-
return { steps: [{ op: "createIntervalLegend", args }], finish: "auto" };
|
|
315
|
-
}
|
|
316
|
-
const continuousStrokeCandidates = candidates.filter(layer => {
|
|
317
|
-
const scale = findSemanticScale(program, layer.encoding?.stroke?.scale);
|
|
318
|
-
return isContinuousColorScaleType(scale?.type);
|
|
319
|
-
});
|
|
320
|
-
const continuousStroke = requestedCandidate(
|
|
321
|
-
program,
|
|
322
|
-
args.target,
|
|
323
|
-
continuousStrokeCandidates
|
|
324
|
-
);
|
|
325
|
-
if (
|
|
326
|
-
(channels?.length === 1 && channels[0] === "stroke" && continuousStroke) ||
|
|
327
|
-
(channels === undefined && continuousStroke &&
|
|
328
|
-
!["color", "shape", "strokeDash", "size", "opacity", "strokeWidth"].some(
|
|
329
|
-
channel => continuousStroke.encoding?.[channel]?.scale !== undefined
|
|
330
|
-
))
|
|
331
|
-
) {
|
|
332
|
-
return {
|
|
333
|
-
steps: [{ op: "createStrokeGradientLegend", args: {
|
|
334
|
-
...args,
|
|
335
|
-
channels: ["stroke"]
|
|
336
|
-
} }],
|
|
337
|
-
finish: "auto"
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
const intervalStrokeCandidates = candidates.filter(layer => {
|
|
341
|
-
const scale = findSemanticScale(program, layer.encoding?.stroke?.scale);
|
|
342
|
-
return ["quantize", "quantile", "threshold"].includes(scale?.type);
|
|
343
|
-
});
|
|
344
|
-
if (
|
|
345
|
-
(channels?.length === 1 && channels[0] === "stroke" &&
|
|
346
|
-
intervalStrokeCandidates.length > 0) ||
|
|
347
|
-
(channels === undefined && intervalStrokeCandidates.length === 1)
|
|
348
|
-
) {
|
|
349
|
-
return {
|
|
350
|
-
steps: [{ op: "createStrokeIntervalLegend", args: {
|
|
351
|
-
...args,
|
|
352
|
-
channels: ["stroke"]
|
|
353
|
-
} }],
|
|
354
|
-
finish: "auto"
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
const wantsShape = channels?.includes("shape") === true;
|
|
358
|
-
const wantsSize = channels?.includes("size") === true;
|
|
359
|
-
const pointCandidates = candidates.filter(layer =>
|
|
360
|
-
layer.mark?.type === "point" &&
|
|
361
|
-
(channels === undefined
|
|
362
|
-
? ["color", "shape"].some(channel => layer.encoding?.[channel]?.scale !== undefined)
|
|
363
|
-
: (wantsShape || wantsSize) && channels.every(channel =>
|
|
364
|
-
["color", "stroke", "shape", "size"].includes(channel) && layer.encoding?.[channel]?.scale !== undefined))
|
|
365
|
-
);
|
|
366
|
-
const requestedPoint = requestedCandidate(program, args.target, pointCandidates);
|
|
367
|
-
const inferredSize = channels === undefined && pointCandidates.some(isSizeLegendPoint);
|
|
368
|
-
if ((wantsSize || inferredSize) && requestedPoint === undefined) {
|
|
369
|
-
throw new Error("Combined size legend requires one eligible point mark or an explicit target.");
|
|
370
|
-
}
|
|
371
|
-
if (requestedPoint !== undefined) {
|
|
372
|
-
if (Object.hasOwn(args, "values")) {
|
|
373
|
-
throw new Error(
|
|
374
|
-
"Combined and categorical legends require a channel block selector for exact values."
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
const { count, ...categoricalArgs } = args;
|
|
378
|
-
const combined = requestedPoint.encoding?.size?.scale !== undefined &&
|
|
379
|
-
(channels === undefined || wantsSize);
|
|
380
|
-
if (count !== undefined && !combined) {
|
|
381
|
-
throw new Error("Legend count requires a selected size legend.");
|
|
382
|
-
}
|
|
383
|
-
if (combined && categoricalArgs.layout === "legacy-bottom") {
|
|
384
|
-
throw new Error('Combined size legends require layout "edge".');
|
|
385
|
-
}
|
|
386
|
-
const inferredChannels = ["color", "stroke", "shape"].filter(
|
|
387
|
-
channel => requestedPoint.encoding?.[channel]?.scale !== undefined
|
|
388
|
-
);
|
|
389
|
-
const { format: sizeFormat, ...categoricalLabels } = categoricalArgs.labels ?? {};
|
|
390
|
-
if (!combined && sizeFormat !== undefined && sizeFormat !== "auto") {
|
|
391
|
-
throw new Error("Categorical legend labels do not accept format.");
|
|
392
|
-
}
|
|
393
|
-
const steps = [{ op: "createCategoricalLegend", args: {
|
|
394
|
-
...categoricalArgs,
|
|
395
|
-
...(categoricalArgs.labels === undefined ? {} : { labels: categoricalLabels }),
|
|
396
|
-
target: requestedPoint.id,
|
|
397
|
-
channels: channels?.filter(channel => channel !== "size") ?? inferredChannels
|
|
398
|
-
} }];
|
|
399
|
-
if (combined) steps.push({ op: "createSizeLegend", args: {
|
|
400
|
-
target: requestedPoint.id,
|
|
401
|
-
...(count === undefined ? {} : { count }),
|
|
402
|
-
...(categoricalArgs.overflow !== undefined &&
|
|
403
|
-
findSemanticScale(program, requestedPoint.encoding.size.scale)?.type === "ordinal"
|
|
404
|
-
? { overflow: categoricalArgs.overflow } : {}),
|
|
405
|
-
...(categoricalArgs.labels === undefined ? {} : { labels: {
|
|
406
|
-
...(categoricalArgs.labels.offset === undefined ? {} : { offset: categoricalArgs.labels.offset }),
|
|
407
|
-
...(sizeFormat === undefined ? {} : { format: sizeFormat })
|
|
408
|
-
} }),
|
|
409
|
-
inheritAppearance: true
|
|
410
|
-
} });
|
|
411
|
-
// Validate both content owners before any component action starts.
|
|
412
|
-
resolveCategoricalLegendConfig(program, steps[0].args);
|
|
413
|
-
if (combined) {
|
|
414
|
-
const size = resolveSizeLegendConfig(program, steps[1].args);
|
|
415
|
-
const existing = program.guideConfigs.legend?.size;
|
|
416
|
-
if (existing !== undefined && existing.target !== size.target) {
|
|
417
|
-
throw new Error("Combined point series legend requires the active size legend to share its target.");
|
|
418
|
-
}
|
|
419
|
-
if (existing !== undefined && count !== undefined &&
|
|
420
|
-
readLegendSampling(existing).count !== count) {
|
|
421
|
-
throw new Error("Existing size legend count must be edited before recreating the categorical block.");
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
return { steps, combined, finish: combined ? "always" : "auto" };
|
|
425
|
-
}
|
|
426
|
-
return { steps: [{ op: "createCategoricalLegend", args }], finish: "auto" };
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
export function resolveLegendCreationPlan(program, args = {}, layers = program.semanticSpec.layers, internalChannels) {
|
|
430
|
-
validateLegendGroupOptions(args);
|
|
431
|
-
const { blockLayout: _layout, at: _at, ...raw } = args;
|
|
432
|
-
const groups = program.guideConfigs.legendGroups ?? {};
|
|
433
|
-
if (Object.keys(groups).length > 0 && raw.position === undefined) {
|
|
434
|
-
const target = creationLegendTarget(program, args);
|
|
435
|
-
const placement = groups[target]?.placement;
|
|
436
|
-
if (placement?.position !== undefined) {
|
|
437
|
-
raw.position = placement.position;
|
|
438
|
-
raw.align = args.align ?? placement.align;
|
|
439
|
-
raw.offset = args.offset ?? placement.offset;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
if (raw.position === undefined && args.direction === "horizontal" &&
|
|
443
|
-
(args.at !== undefined || Object.keys(groups).length > 0)) raw.position = "top";
|
|
444
|
-
const initial = resolveRawLegendCreationPlan(program, raw, layers, internalChannels);
|
|
445
|
-
const inherited = Object.keys(program.guideConfigs.legendGroups ?? {}).length > 0;
|
|
446
|
-
const corner = PLOT_LEGEND_POSITIONS.includes(args.position) &&
|
|
447
|
-
(initial.steps.length > 1 || initial.steps[0]?.op !== "createCategoricalLegend" ||
|
|
448
|
-
Object.keys(program.guideConfigs.legend ?? {}).length > 0);
|
|
449
|
-
if (args.blockLayout === undefined && args.at === undefined && !inherited && !corner) return initial;
|
|
450
|
-
const target = creationLegendTarget(program, args);
|
|
451
|
-
const first = initial.steps[0];
|
|
452
|
-
const nativeOffset = program.guideConfigs.legendGroups?.[target] !== undefined ? 30 : first?.op === "createCategoricalLegend"
|
|
453
|
-
? resolveCategoricalLegendConfig(program, first.args).offset
|
|
454
|
-
: first?.op === "createSizeLegend" ? resolveSizeLegendConfig(program, first.args).offset : 30;
|
|
455
|
-
const prepared = prepareLegendGroup(program, args, target, { creation: true, corner, nativeOffset });
|
|
456
|
-
if (!prepared.grouped) return initial;
|
|
457
|
-
if (prepared.options.position === undefined && raw.position !== undefined) {
|
|
458
|
-
prepared.options.position = raw.position;
|
|
459
|
-
}
|
|
460
|
-
return { ...resolveRawLegendCreationPlan(prepared.program, prepared.options, layers, internalChannels),
|
|
461
|
-
legendGroup: { target, config: prepared.program.guideConfigs.legendGroups[target] } };
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
export function applyLegendCreationPlan(program, plan) {
|
|
465
|
-
if (plan.steps.length === 0) return program;
|
|
466
|
-
let next = plan.legendGroup === undefined ? program : program._withMaterializationConfig(
|
|
467
|
-
["guides", "legendGroups", plan.legendGroup.target], plan.legendGroup.config);
|
|
468
|
-
for (const step of plan.steps) {
|
|
469
|
-
const existingSize = next.guideConfigs.legend?.size;
|
|
470
|
-
if (plan.combined && existingSize !== undefined && step.op === "createCategoricalLegend") {
|
|
471
|
-
next = next._withLegendConfig("size", { ...existingSize, inheritPosition: true });
|
|
472
|
-
}
|
|
473
|
-
// The plan already validated that a retained size block is compatible.
|
|
474
|
-
if (!(plan.combined && step.op === "createSizeLegend" && existingSize !== undefined)) {
|
|
475
|
-
next = step.op === "createColorSizeLegend" ? createColorSizeLegendFromConfig(next,resolveColorSizeConfig(next,step.args)) : next[step.op](step.args);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return plan.finish === "always" ? next.rematerializeLegend()
|
|
479
|
-
: plan.finish === "auto" ? finishLegend(next) : next;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export const createLegend = /* @__PURE__ */ action(
|
|
483
|
-
{ op: "createLegend", description: "Create an inferred legend for selected channels." },
|
|
484
|
-
withGuideLayoutValidation(function (args = {}) {
|
|
485
|
-
return recordLegendOffsetIntent(this, applyLegendCreationPlan(this, resolveLegendCreationPlan(this, args)), args, { creation: true });
|
|
486
|
-
})
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
export const removeCategoricalLegend = /* @__PURE__ */ action(
|
|
490
|
-
{
|
|
491
|
-
op: "removeCategoricalLegend",
|
|
492
|
-
description: "Remove the active categorical legend and its concrete components."
|
|
493
|
-
},
|
|
494
|
-
withGuideLayoutValidation(function (args = {}) {
|
|
495
|
-
noOptions(args, "removeCategoricalLegend");
|
|
496
|
-
const entries = ["series", "color", "stroke"]
|
|
497
|
-
.filter(kind => this.guideConfigs.legend?.[kind] !== undefined);
|
|
498
|
-
if (entries.length === 0) return this;
|
|
499
|
-
if (entries.length !== 1) {
|
|
500
|
-
throw new Error("removeCategoricalLegend requires one active categorical legend.");
|
|
501
|
-
}
|
|
502
|
-
const kind = entries[0];
|
|
503
|
-
const prefix = `${kind}Legend`;
|
|
504
|
-
const targets = Object.keys(this.graphicSpec.objects)
|
|
505
|
-
.filter(id => id.startsWith(prefix));
|
|
506
|
-
let next = this.editSemantic({
|
|
507
|
-
property: `guide.legend.${kind}`,
|
|
508
|
-
remove: true
|
|
509
|
-
});
|
|
510
|
-
for (const target of targets) {
|
|
511
|
-
next = next.editGraphics({ target, remove: true });
|
|
512
|
-
}
|
|
513
|
-
return next._withoutMaterializationConfig(["guides", "legend", kind]);
|
|
514
|
-
})
|
|
515
|
-
);
|
|
1
|
+
import{resolveColorSizeConfig as B,createColorSizeLegendFromConfig as se,materializeColorSizeLegend as de}from"../colorSize.js";import{resetLegendClipGraphics as re,applyLegendClipGraphics as le,restoreLegendClipContext as ce}from"../../../../materialization/legendClip.js";import{validateLegendCombine as ae,resolveSizeShapeCreation as fe}from"../combine.js";import{recordLegendOffsetIntent as ue}from"../group.js";import{prepareLegendGroup as F,creationLegendTarget as N,validateLegendGroupOptions as ge}from"../group.js";import{PLOT_LEGEND_POSITIONS as pe}from"../../../../layout/legend.js";import{normalizeInitialLegendTitle as he}from"../continuous/common.js";import{isContinuousColorScaleType as K}from"../../../../grammar/scales/types.js";import{withGuideLayoutValidation as M}from"../../../../materialization/guides/layout.js";import{sameGuideValue as Y}from"../../reuse.js";import{normalizeLegendOrder as J}from"../../../../grammar/categoryOrder.js";import{action as W}from"../../../../core/action.js";import{validateOptionObject as me}from"../../../../core/validation.js";import{noOptions as Q,resolveLayout as A}from"./layout.js";import{normalizeOptions as Le}from"./options.js";import{normalizeRecipe as Ce,resolveLegendSymbol as U}from"./recipes.js";import{validateLegendChannels as be}from"../target.js";import{createCategoricalLegendFromConfig as ze,reconcileCategoricalSymbols as ye}from"../lifecycle.js";import{findLayer as X}from"../../../../selectors/layers.js";import{findSemanticScale as G}from"../../../../selectors/scales.js";import{isSizeLegendPoint as D,resolveSizeLegendConfig as Z}from"../size.js";import{isStrokeWidthLegendLayer as ke}from"../strokeWidth.js";import{isOpacityLegendLayer as ee}from"../../../../materialization/legends.js";import{legendResourcePolicies as Se}from"../../../../materialization/guides/resources.js";import{hasHorizontalLegendLane as ve,hasLegendLane as we}from"../lane.js";import{resolveCurrentDefinition as Oe,resolveDefinition as xe,resolveTarget as Ge,sameValues as j}from"./resolve.js";import{readLegendSampling as Pe}from"../sampling.js";import{reconcileLegendBlockTitleGraphic as ne,resolveEffectiveLegendBlockConfig as T}from"../blocks.js";function Ee(i){return Object.values(i.guideConfigs.legend??{}).some(e=>e.clipHeight!==void 0)||we(i)||Object.keys(i.guideConfigs.legendGroups??{}).length>0?i.rematerializeLegend():i}function $(i,e,n){if(e===void 0)return n.length===1?n[0]:void 0;const a=X(i,e);return n.includes(a)?a:void 0}function ie(i,e){if(Object.hasOwn(i,"inheritAppearance"))throw new Error("createLegend does not accept inheritAppearance.");const{channels:n,...a}=i;return{op:e==="size"?"createSizeLegend":"createStrokeWidthLegend",args:a}}const fn=W({op:"rematerializeLegend",description:"Rematerialize every existing legend component."},M(function(i={}){Q(i,"rematerializeLegend");const e=this.context.deferLegendClip;let n=re(this)._withContext({deferLegendClip:!0});const a=["series","color","stroke"].filter(t=>this.guideConfigs.legend?.[t]!==void 0);for(const t of a){const d=n.guideConfigs.legend[t],{defaultLabelMap:s,...L}=d,l=Oe(n,d),S=d.inferredSymbol?Ce(U(n,X(n,d.target),l.channels,void 0,t),t,{internal:!0}):d.symbol,b=!Y(S,d.symbol);n=b||!Y(s,l.defaultLabelMap)||!j(d.channels,l.channels)||!j(d.domain,l.domain)||!j(d.scales,l.scales)||d.field!==l.field||!j(d.sizeAreas??[],l.sizeAreas??[])||d.title!==l.title?n._withLegendConfig(t,{...L,symbol:S,channels:l.channels,scales:l.scales,field:l.field,...l.defaultLabelMap===void 0?{}:{defaultLabelMap:l.defaultLabelMap},...l.combine===void 0?{}:{combine:l.combine,sizeAreas:l.sizeAreas},title:l.title,domain:l.domain}):n;const g=T(n,t,n.guideConfigs.legend[t]);n=ne(n,t,g.titleVisible!==!1),t==="series"?(j(n.semanticSpec.guides.legend.series.scales,l.scales)||(n=n.editSemantic({property:"guide.legend.series.scales",value:l.scales})),n.semanticSpec.guides.legend.series.title!==l.title&&(n=n.editSemantic({property:"guide.legend.series.title",value:l.title}))):(n.semanticSpec.guides.legend[t].scale!==l.scales[0]&&(n=n.editSemantic({property:`guide.legend.${t}.scale`,value:l.scales[0]})),n.semanticSpec.guides.legend[t].title!==l.title&&(n=n.editSemantic({property:`guide.legend.${t}.title`,value:l.title}))),b&&(A(n,g),n=ye(n,d,g)),g.border!==!1&&(n=n.rematerializeLegendBackground({kind:t})),n=n.rematerializeLegendSymbols({kind:t}).rematerializeLegendLabels({kind:t}),g.titleVisible!==!1&&(n=n.rematerializeLegendTitle({kind:t})),Object.values(n.materializationConfigs.highlights??{}).some(E=>E.target===g.target)&&(n=n.rematerializeLegendHighlights({kind:t}))}n.guideConfigs.legend?.colorSize!==void 0&&(n=de(n,n.guideConfigs.legend.colorSize));for(const t of Se())if(t.rematerializeOp!==void 0&&this.guideConfigs.legend?.[t.kind]!==void 0){const d=T(n,t.kind,n.guideConfigs.legend[t.kind]);n=ne(n,t.kind,d.titleVisible!==!1),n=n[t.rematerializeOp]()}const r=Object.fromEntries(a.filter(t=>n.guideConfigs.legend[t].clipHeight!==void 0).map(t=>[t,A(n,T(n,t,n.guideConfigs.legend[t])).itemY]));return n=le(n,{force:!0,anchors:r}),n=ce(n,e),ve(n)&&(n=n.rematerializeHorizontalLegendLane()),n}));function I(i,e={}){const n=Ge(i,e.target),a=xe(i,n,e.channels,e.title===!1?void 0:e.title,e.order===void 0?void 0:J(e.order)),r=e.symbol===void 0||e.symbol==="auto",t=Le({...e,symbol:U(i,n,a.channels,e.symbol,a.kind)},a.kind,{internalSymbol:r});return{target:n.id,...a,...he(e.title,a.title),inferredTitle:e.title===!1||!Object.hasOwn(e,"title"),inferredSymbol:r,position:t.position,align:t.align,direction:t.direction,columns:t.columns,offset:t.offset,titlePosition:t.titlePosition,symbol:t.symbol,labels:t.labels,titleStyle:t.titleStyle,itemGap:t.itemGap,...t.rowPadding===void 0?{}:{rowPadding:t.rowPadding},...t.titlePadding===void 0?{}:{titlePadding:t.titlePadding},layout:t.layout,...t.clipHeight===void 0?{}:{clipHeight:t.clipHeight},...t.overflow===void 0?{}:{overflow:t.overflow},border:t.border}}const un=W({op:"createCategoricalLegend",description:"Create one categorical legend block."},M(function(i={}){const e=I(this,i);if(this.semanticSpec.guides.legend?.[e.kind]!==void 0)throw new Error(`createCategoricalLegend requires a missing legend for ${e.kind}.`);return A(this,e),ze(this,e,i.order===void 0?void 0:J(i.order))}));function P(i,e={},n=i.semanticSpec.layers,a){if(Object.hasOwn(e,"combine")){if(ae(e.combine),e.combine==="color-size"){const u=B(i,e),O=F(i,{...e,blockLayout:e.blockLayout??(["left","right"].includes(e.position??"right")?"column":"row")},u.target,{creation:!0,nativeOffset:30});return{steps:[{op:"createColorSizeLegend",args:e}],finish:"always",legendGroup:{target:u.target,config:O.program.guideConfigs.legendGroups[u.target]}}}if(e.combine==="size-shape")return fe(i,e,n,P,a);const{combine:o,...c}=e;return P(i,c,n)}me(e,void 0,"createLegend");const r=e.target===void 0?n:n.filter(o=>o.id===e.target),t=r.length===1&&["area","rect","bar","arc","text","rule","tick"].includes(r[0].mark?.type),d=t?["color","stroke"].filter(o=>r[0].encoding?.[o]?.scale!==void 0):[],s=e.channels!==void 0?e.channels:d.length>0?d:void 0;if(e.channels===void 0&&s?.length>0)return P(i,{...e,channels:s},n);s!==void 0&&be(s,"createLegend");const L=r.length===1&&r[0].mark?.type==="point",l=o=>["color","stroke"].includes(o)&&!["ordinal","band","point"].includes(G(i,r[0]?.encoding?.[o]?.scale)?.type),S=L&&((s??[]).some(l)||new Set((s??[]).filter(o=>!["size","opacity","strokeWidth"].includes(o)).map(o=>r[0].encoding?.[o]?.field)).size>1);if(s?.length>1&&(S||s.some(o=>["opacity","strokeWidth"].includes(o))||t)){const{representation:o,values:c,count:u,symbol:O,gradient:m,overflow:z,...y}=e,p=s.filter(f=>["opacity","strokeWidth"].includes(f)),x=s.filter(f=>!p.includes(f)),q=new Map;for(const f of x.filter(h=>h!=="size")){const h=l(f)?`channel:${f}`:`field:${r[0]?.encoding?.[f]?.field}`;q.has(h)||q.set(h,[]),q.get(h).push(f)}const H=[...q.values()];if(x.includes("size")){const f=H.find(h=>!h.some(l));f?f.push("size"):H.push(["size"])}return{steps:(L?[...H,...p.map(f=>[f])]:s.map(f=>[f])).flatMap(f=>P(i,f.includes("color")&&(o!==void 0||m!==void 0)?{...e,channels:f}:{...y,channels:f,...f.some(l)&&m!==void 0?{gradient:m}:{},...f.some(h=>!["size","opacity","strokeWidth"].includes(h)&&["ordinal","band","point"].includes(G(i,r[0]?.encoding?.[h]?.scale)?.type))&&z!==void 0?{overflow:z}:{},...f.some(h=>["size","opacity","strokeWidth"].includes(h))&&u!==void 0?{count:u}:{}},n).steps),finish:"always"}}if(s===void 0&&r.length===1&&D(r[0])&&ee(r[0])&&!["color","stroke","shape","strokeDash","strokeWidth"].some(o=>r[0].encoding?.[o]?.scale!==void 0)){const o=r[0].id;return{steps:[ie({...e,target:o},"size"),{op:"createOpacityLegend",args:{...e,target:o}}],finish:"auto"}}const b=[["size",D,["color","stroke","shape","strokeDash","opacity"]],["strokeWidth",ke,e.target===void 0?["color","stroke","shape","strokeDash","size","opacity"]:["color","stroke","shape","strokeDash","opacity"]],["opacity",ee,["color","stroke","shape","strokeDash","size"]]];for(const[o,c,u]of b){const O=s?.length===1&&s[0]===o,m=s===void 0&&r.filter(c).length===1&&!r.some(z=>u.some(y=>z.encoding?.[y]?.scale!==void 0));if(O||m)return{steps:[o==="opacity"?{op:"createOpacityLegend",args:e}:ie(e,o)],finish:"auto"}}const v=r.filter(o=>{const c=["point","line","bar","rect","text","area","arc","tick"].includes(o.mark?.type)?o.encoding?.color:void 0,u=G(i,c?.scale);return K(u?.type)}),g=$(i,e.target,v);if(e.representation!==void 0&&s!==void 0&&s.length>1){if(!g||!s.includes("color"))throw new Error("Legend representation requires selected continuous color.");const{representation:o,values:c,count:u,symbol:O,gradient:m,...z}=e;return{steps:s.flatMap(p=>P(i,p==="color"?{...e,channels:[p]}:{...z,channels:[p]},n).steps),finish:"always"}}if(s?.length===1&&s[0]==="color"&&g||s===void 0&&g)return{steps:[{op:"createGradientLegend",args:e}],finish:"auto"};const C=r.filter(o=>{const c=["point","line","bar","rect","text","area","arc","tick"].includes(o.mark?.type)?o.encoding?.color:void 0,u=G(i,c?.scale);return["quantize","quantile","threshold"].includes(u?.type)});if(s?.length===1&&s[0]==="color"&&C.length>0||s===void 0&&C.length>0)return{steps:[{op:"createIntervalLegend",args:e}],finish:"auto"};const E=r.filter(o=>{const c=G(i,o.encoding?.stroke?.scale);return K(c?.type)}),k=$(i,e.target,E);if(s?.length===1&&s[0]==="stroke"&&k||s===void 0&&k&&!["color","shape","strokeDash","size","opacity","strokeWidth"].some(o=>k.encoding?.[o]?.scale!==void 0))return{steps:[{op:"createStrokeGradientLegend",args:{...e,channels:["stroke"]}}],finish:"auto"};const V=r.filter(o=>{const c=G(i,o.encoding?.stroke?.scale);return["quantize","quantile","threshold"].includes(c?.type)});if(s?.length===1&&s[0]==="stroke"&&V.length>0||s===void 0&&V.length===1)return{steps:[{op:"createStrokeIntervalLegend",args:{...e,channels:["stroke"]}}],finish:"auto"};const te=s?.includes("shape")===!0,_=s?.includes("size")===!0,R=r.filter(o=>o.mark?.type==="point"&&(s===void 0?["color","shape"].some(c=>o.encoding?.[c]?.scale!==void 0):(te||_)&&s.every(c=>["color","stroke","shape","size"].includes(c)&&o.encoding?.[c]?.scale!==void 0))),w=$(i,e.target,R),oe=s===void 0&&R.some(D);if((_||oe)&&w===void 0)throw new Error("Combined size legend requires one eligible point mark or an explicit target.");if(w!==void 0){if(Object.hasOwn(e,"values"))throw new Error("Combined and categorical legends require a channel block selector for exact values.");const{count:o,...c}=e,u=w.encoding?.size?.scale!==void 0&&(s===void 0||_);if(o!==void 0&&!u)throw new Error("Legend count requires a selected size legend.");if(u&&c.layout==="legacy-bottom")throw new Error('Combined size legends require layout "edge".');const O=["color","stroke","shape"].filter(p=>w.encoding?.[p]?.scale!==void 0),{format:m,...z}=c.labels??{};if(!u&&m!==void 0&&m!=="auto")throw new Error("Categorical legend labels do not accept format.");const y=[{op:"createCategoricalLegend",args:{...c,...c.labels===void 0?{}:{labels:z},target:w.id,channels:s?.filter(p=>p!=="size")??O}}];if(u&&y.push({op:"createSizeLegend",args:{target:w.id,...o===void 0?{}:{count:o},...c.overflow!==void 0&&G(i,w.encoding.size.scale)?.type==="ordinal"?{overflow:c.overflow}:{},...c.labels===void 0?{}:{labels:{...c.labels.offset===void 0?{}:{offset:c.labels.offset},...m===void 0?{}:{format:m}}},inheritAppearance:!0}}),I(i,y[0].args),u){const p=Z(i,y[1].args),x=i.guideConfigs.legend?.size;if(x!==void 0&&x.target!==p.target)throw new Error("Combined point series legend requires the active size legend to share its target.");if(x!==void 0&&o!==void 0&&Pe(x).count!==o)throw new Error("Existing size legend count must be edited before recreating the categorical block.")}return{steps:y,combined:u,finish:u?"always":"auto"}}return{steps:[{op:"createCategoricalLegend",args:e}],finish:"auto"}}function je(i,e={},n=i.semanticSpec.layers,a){ge(e);const{blockLayout:r,at:t,...d}=e,s=i.guideConfigs.legendGroups??{};if(Object.keys(s).length>0&&d.position===void 0){const E=N(i,e),k=s[E]?.placement;k?.position!==void 0&&(d.position=k.position,d.align=e.align??k.align,d.offset=e.offset??k.offset)}d.position===void 0&&e.direction==="horizontal"&&(e.at!==void 0||Object.keys(s).length>0)&&(d.position="top");const L=P(i,d,n,a),l=Object.keys(i.guideConfigs.legendGroups??{}).length>0,S=pe.includes(e.position)&&(L.steps.length>1||L.steps[0]?.op!=="createCategoricalLegend"||Object.keys(i.guideConfigs.legend??{}).length>0);if(e.blockLayout===void 0&&e.at===void 0&&!l&&!S)return L;const b=N(i,e),v=L.steps[0],g=i.guideConfigs.legendGroups?.[b]!==void 0?30:v?.op==="createCategoricalLegend"?I(i,v.args).offset:v?.op==="createSizeLegend"?Z(i,v.args).offset:30,C=F(i,e,b,{creation:!0,corner:S,nativeOffset:g});return C.grouped?(C.options.position===void 0&&d.position!==void 0&&(C.options.position=d.position),{...P(C.program,C.options,n,a),legendGroup:{target:b,config:C.program.guideConfigs.legendGroups[b]}}):L}function qe(i,e){if(e.steps.length===0)return i;let n=e.legendGroup===void 0?i:i._withMaterializationConfig(["guides","legendGroups",e.legendGroup.target],e.legendGroup.config);for(const a of e.steps){const r=n.guideConfigs.legend?.size;e.combined&&r!==void 0&&a.op==="createCategoricalLegend"&&(n=n._withLegendConfig("size",{...r,inheritPosition:!0})),e.combined&&a.op==="createSizeLegend"&&r!==void 0||(n=a.op==="createColorSizeLegend"?se(n,B(n,a.args)):n[a.op](a.args))}return e.finish==="always"?n.rematerializeLegend():e.finish==="auto"?Ee(n):n}const gn=W({op:"createLegend",description:"Create an inferred legend for selected channels."},M(function(i={}){return ue(this,qe(this,je(this,i)),i,{creation:!0})})),pn=W({op:"removeCategoricalLegend",description:"Remove the active categorical legend and its concrete components."},M(function(i={}){Q(i,"removeCategoricalLegend");const e=["series","color","stroke"].filter(d=>this.guideConfigs.legend?.[d]!==void 0);if(e.length===0)return this;if(e.length!==1)throw new Error("removeCategoricalLegend requires one active categorical legend.");const n=e[0],a=`${n}Legend`,r=Object.keys(this.graphicSpec.objects).filter(d=>d.startsWith(a));let t=this.editSemantic({property:`guide.legend.${n}`,remove:!0});for(const d of r)t=t.editGraphics({target:d,remove:!0});return t._withoutMaterializationConfig(["guides","legend",n])}));export{qe as applyLegendCreationPlan,un as createCategoricalLegend,gn as createLegend,fn as rematerializeLegend,pn as removeCategoricalLegend,I as resolveCategoricalLegendConfig,je as resolveLegendCreationPlan};
|
|
@@ -86,7 +86,7 @@ export function categoricalLegendLabels(config) {
|
|
|
86
86
|
config = categoricalLegendView(config);
|
|
87
87
|
const labels = config.domain.map(value => resolveDisplayLabel(
|
|
88
88
|
value,
|
|
89
|
-
config.labelMap,
|
|
89
|
+
[...(config.labelMap ?? []), ...(config.defaultLabelMap ?? [])],
|
|
90
90
|
formatVisibleText
|
|
91
91
|
));
|
|
92
92
|
return config.hiddenCount > 0 ? [...labels, `…${config.hiddenCount} entries`] : labels;
|
|
@@ -174,8 +174,11 @@ export function resolveAppearance(program, config) {
|
|
|
174
174
|
target.encoding.color === undefined &&
|
|
175
175
|
!config.channels.includes("color") &&
|
|
176
176
|
isLinearGradientPaint(program.markConfigs[target.id]?.fill);
|
|
177
|
+
const fixedLineStroke = target?.mark?.type === "line" &&
|
|
178
|
+
config.channels.includes("strokeDash")
|
|
179
|
+
? program.markConfigs[target.id]?.stroke : undefined;
|
|
177
180
|
const fallback = gradientFallback
|
|
178
|
-
? program.markConfigs[target.id].fill : DEFAULT_COLORS.mark;
|
|
181
|
+
? program.markConfigs[target.id].fill : fixedLineStroke ?? DEFAULT_COLORS.mark;
|
|
179
182
|
let colors = config.domain.map(() => fallback);
|
|
180
183
|
let strokes = config.domain.map(() => "white");
|
|
181
184
|
let dashes = config.domain.map(() => []);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import{normalizeLegendClipHeight}from"../../../../grammar/legendClip.js";
|
|
2
|
+
import {normalizeLegendSpacing,validateLegendSpacing} from "../../../../grammar/legendSpacing.js";
|
|
2
3
|
import { normalizeLegendBorderDash } from "../../../../grammar/legendBorder.js";
|
|
3
4
|
import { LEGEND_TITLE_STYLE_KEYS, validateLegendTitleAlignment } from "../../../../grammar/legendTitleStyle.js";
|
|
4
5
|
import { PLOT_LEGEND_POSITIONS, isPlotLegend } from "../../../../layout/legend.js";
|
|
@@ -34,6 +35,7 @@ const OPTIONS = Object.freeze([
|
|
|
34
35
|
"labels",
|
|
35
36
|
"titleStyle",
|
|
36
37
|
"itemGap",
|
|
38
|
+
"rowPadding", "titlePadding",
|
|
37
39
|
"border",
|
|
38
40
|
"overflow"
|
|
39
41
|
]);
|
|
@@ -147,6 +149,7 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
|
147
149
|
? DEFAULT_VERTICAL_LEGEND_ITEM_GAP
|
|
148
150
|
: position === "top" ? 24 : 20);
|
|
149
151
|
const layout = args.layout === undefined ? "edge" : args.layout;
|
|
152
|
+
validateLegendSpacing(kind,{...args,layout});
|
|
150
153
|
if (!["edge", "legacy-bottom"].includes(layout)) {
|
|
151
154
|
throw new Error(`Unsupported legend layout "${layout}".`);
|
|
152
155
|
}
|
|
@@ -198,6 +201,7 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
|
198
201
|
const overflow = normalizeLegendOverflow(args.overflow);
|
|
199
202
|
return {
|
|
200
203
|
...(args.clipHeight===undefined?{}:{clipHeight:normalizeLegendClipHeight(args.clipHeight)}),
|
|
204
|
+
...normalizeLegendSpacing(args),
|
|
201
205
|
...(overflow === undefined ? {} : { overflow }),
|
|
202
206
|
target: args.target,
|
|
203
207
|
channels: args.channels,
|
|
@@ -11,6 +11,7 @@ import { CHANNELS } from "./options.js";
|
|
|
11
11
|
import { nonEmptyString } from "./validation.js";
|
|
12
12
|
import { findLayer } from "../../../../selectors/layers.js";
|
|
13
13
|
import { findSemanticScale } from "../../../../selectors/scales.js";
|
|
14
|
+
import { horizonLegendPresentation } from "../../../../grammar/horizon.js";
|
|
14
15
|
|
|
15
16
|
function isCategoricalTarget(layer) {
|
|
16
17
|
if (["line", "point"].includes(layer?.mark?.type)) {
|
|
@@ -76,8 +77,9 @@ function resolveOrdinalScales(program, scaleIds) {
|
|
|
76
77
|
);
|
|
77
78
|
return concrete;
|
|
78
79
|
});
|
|
79
|
-
if (scales.slice(1).some(scale =>
|
|
80
|
-
|
|
80
|
+
if (scales.slice(1).some(scale => scale.domain.length !== scales[0].domain.length ||
|
|
81
|
+
scale.domain.some(value => !scales[0].domain.some(other => Object.is(value, other))))) {
|
|
82
|
+
throw new Error("Combined legend scales must contain the same domain values.");
|
|
81
83
|
}
|
|
82
84
|
return { scales, domain: scales[0].domain };
|
|
83
85
|
}
|
|
@@ -119,6 +121,8 @@ export function resolveDefinition(program, layer, requestedChannels, requestedTi
|
|
|
119
121
|
const scales = encodings.map(item => item.encoding.scale);
|
|
120
122
|
const resolved = resolveOrdinalScales(program, scales);
|
|
121
123
|
const field = [...fields][0];
|
|
124
|
+
const transform = requireDataset(program, layer.data).transform?.[0];
|
|
125
|
+
const horizon = field === transform?.as?.color ? horizonLegendPresentation(transform) : undefined;
|
|
122
126
|
let linked;
|
|
123
127
|
if (order?.channel !== undefined) {
|
|
124
128
|
const position = layer.encoding?.[order.channel];
|
|
@@ -132,7 +136,8 @@ export function resolveDefinition(program, layer, requestedChannels, requestedTi
|
|
|
132
136
|
channels,
|
|
133
137
|
scales,
|
|
134
138
|
field,
|
|
135
|
-
title: nonEmptyString(requestedTitle ?? field, "Legend title"),
|
|
139
|
+
title: nonEmptyString(requestedTitle ?? horizon?.title ?? field, "Legend title"),
|
|
140
|
+
...(horizon === undefined ? {} : { defaultLabelMap: horizon.labelMap }),
|
|
136
141
|
domain: resolveLegendOrderDomain(resolved.domain, order, linked,
|
|
137
142
|
order?.values === undefined ? undefined : readNominalField(requireDataset(program, layer.data).values, field)),
|
|
138
143
|
...(sizeShape ? {combine:"size-shape",...resolveSizeShapeMapping({layer,
|
|
@@ -55,14 +55,14 @@ function makeEditSymbol(type) {
|
|
|
55
55
|
? "collection"
|
|
56
56
|
: { line: "line", point: "circle", swatch: "rect" }[type];
|
|
57
57
|
const actual = this.graphicSpec.objects[id]?.type;
|
|
58
|
-
const
|
|
59
|
-
if (actual !== expected && !
|
|
58
|
+
const compatibleSwatch = type === "swatch" && ["rect", "collection"].includes(actual);
|
|
59
|
+
if (actual !== expected && !compatibleSwatch) {
|
|
60
60
|
throw new Error(`${op} requires existing ${type} symbols.`);
|
|
61
61
|
}
|
|
62
62
|
const layout = resolveLayout(this, config);
|
|
63
63
|
const appearance = resolveAppearance(this, config);
|
|
64
64
|
let next = this;
|
|
65
|
-
if (!dynamicPoint && !styledSwatch) {
|
|
65
|
+
if (!dynamicPoint && !styledSwatch && actual !== "collection") {
|
|
66
66
|
next = next.editGraphics({
|
|
67
67
|
target: id,
|
|
68
68
|
property: "length",
|