ggaction 0.0.11 → 0.0.13
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 +80 -0
- package/README.md +3 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +33 -27
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +40 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +37 -2
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +79 -24
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +153 -11
- package/src/actions/errorBars/edit.js +365 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +44 -23
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +138 -14
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +60 -4
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +213 -65
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionCompatibility.js +1 -1
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +9 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +23 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +86 -23
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +7 -1
- package/src/materialization/facetGuides/placement.js +16 -1
- package/src/materialization/facetGuides/preparation.js +26 -1
- package/src/materialization/facets.js +11 -2
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +30 -7
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +63 -25
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +70 -14
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/bar.js +0 -43
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +48 -18
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1327 -124
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,30 +1,44 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
2
|
+
import { resolveLegendStepConfig } from "./creation.js";
|
|
3
|
+
import { normalizeLegendOrder } from "../../../grammar/categoryOrder.js";
|
|
4
|
+
import { resolveDefinition } from "./categorical/resolve.js";
|
|
1
5
|
import { action } from "../../../core/action.js";
|
|
2
6
|
import {
|
|
3
7
|
validateGeneratedItemLimit,
|
|
4
8
|
validateOptionObject
|
|
5
9
|
} from "../../../core/validation.js";
|
|
6
10
|
import { normalizeOptions } from "./categorical/options.js";
|
|
7
|
-
import {
|
|
11
|
+
import { resolveLegendSymbol } from "./categorical/recipes.js";
|
|
12
|
+
import { resolveLayout } from "./categorical/layout.js";
|
|
13
|
+
import { resolveLegendCreationPlan } from "./categorical/actions.js";
|
|
14
|
+
import { categoricalSymbolIds, reconcileCategoricalSymbols, createCategoricalLegendFromConfig, resolveCategoricalLegendRevision, removeLegendKinds } from "./lifecycle.js";
|
|
15
|
+
import { createGradientLegendFromConfig } from "./continuous/gradient.js";
|
|
8
16
|
import {
|
|
17
|
+
normalizeItemLegendLayout,
|
|
18
|
+
normalizeLegendBorder,
|
|
9
19
|
normalizeLegendTextOptions,
|
|
20
|
+
normalizeLegendTitleOptions,
|
|
10
21
|
normalizeContinuousLegend,
|
|
11
22
|
validatePositive
|
|
12
23
|
} from "./continuous/common.js";
|
|
13
|
-
import { normalizeOpacitySymbol } from "./continuous/opacity.js";
|
|
14
|
-
import { normalizeIntervalLegend } from "./continuous/interval.js";
|
|
24
|
+
import { normalizeOpacitySymbol, createOpacityLegendFromConfig } from "./continuous/opacity.js";
|
|
25
|
+
import { normalizeIntervalLegend, resolveIntervalConfig, resolveIntervalLayout, createIntervalLegendFromConfig } from "./continuous/interval.js";
|
|
15
26
|
import { findLayer } from "../../../selectors/layers.js";
|
|
16
27
|
import { resolveLegendGraphicPlacement } from
|
|
17
28
|
"../../../materialization/graphicHierarchy.js";
|
|
18
|
-
import { resolveLegendTarget } from "./target.js";
|
|
29
|
+
import { resolveLegendTarget, validateLegendChannels } from "./target.js";
|
|
30
|
+
import { SIZE_LEGEND_LABELS, SIZE_LEGEND_TITLE_STYLE, resolveSizeLegendLayout, createSizeLegendFromConfig } from "./size.js";
|
|
19
31
|
import {
|
|
20
32
|
STROKE_WIDTH_LEGEND_LABELS,
|
|
21
|
-
STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
33
|
+
STROKE_WIDTH_LEGEND_TITLE_STYLE,
|
|
34
|
+
resolveStrokeWidthLegendLayout,
|
|
35
|
+
createStrokeWidthLegendFromConfig
|
|
22
36
|
} from "./strokeWidth.js";
|
|
23
37
|
|
|
24
38
|
const OPTIONS = Object.freeze([
|
|
25
|
-
"target", "position", "align", "direction", "columns", "offset",
|
|
39
|
+
"target", "channels", "position", "layout", "align", "direction", "columns", "offset",
|
|
26
40
|
"titlePosition", "title", "symbol", "labels", "titleStyle", "itemGap",
|
|
27
|
-
"border", "count", "gradient"
|
|
41
|
+
"border", "count", "gradient", "order"
|
|
28
42
|
]);
|
|
29
43
|
|
|
30
44
|
function mergeObject(previous, patch) {
|
|
@@ -46,16 +60,19 @@ function reconcileGraphic(program, id, shouldExist, definition) {
|
|
|
46
60
|
return program.createGraphics({
|
|
47
61
|
id,
|
|
48
62
|
...definition,
|
|
49
|
-
...resolveLegendGraphicPlacement(program
|
|
63
|
+
...resolveLegendGraphicPlacement(program, {
|
|
64
|
+
...(definition.before === undefined ? {} : { before: definition.before }),
|
|
65
|
+
...(definition.after === undefined ? {} : { after: definition.after })
|
|
66
|
+
})
|
|
50
67
|
});
|
|
51
68
|
}
|
|
52
69
|
return program;
|
|
53
70
|
}
|
|
54
71
|
|
|
55
|
-
function
|
|
72
|
+
function resolveContinuousEdit(program, kind, previous, args) {
|
|
56
73
|
const allowed = kind === "gradient"
|
|
57
74
|
? ["target", "position", "align", "offset", "title", "labels",
|
|
58
|
-
"titleStyle", "border", "count", "gradient"]
|
|
75
|
+
"titleStyle", "titlePosition", "border", "count", "gradient"]
|
|
59
76
|
: ["target", "position", "align", "offset", "title", "symbol", "labels",
|
|
60
77
|
"titleStyle", "titlePosition", "itemGap", "border", "count"];
|
|
61
78
|
for (const key of Object.keys(args)) {
|
|
@@ -108,6 +125,11 @@ function editContinuous(program, kind, previous, args) {
|
|
|
108
125
|
} else {
|
|
109
126
|
config.symbol = normalizeOpacitySymbol(args.symbol ?? previous.symbol);
|
|
110
127
|
}
|
|
128
|
+
return { config, titleMode, title, titleVisible };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function editContinuous(program, kind, previous, args) {
|
|
132
|
+
const { config, titleMode, title, titleVisible } = resolveContinuousEdit(program, kind, previous, args);
|
|
111
133
|
const prefix = kind === "gradient" ? "colorGradient" : "opacityLegend";
|
|
112
134
|
let next = program;
|
|
113
135
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
@@ -125,11 +147,11 @@ function editContinuous(program, kind, previous, args) {
|
|
|
125
147
|
return next.rematerializeLegend();
|
|
126
148
|
}
|
|
127
149
|
|
|
128
|
-
function
|
|
150
|
+
function resolveIntervalEdit(program, previous, args) {
|
|
129
151
|
for (const key of Object.keys(args)) {
|
|
130
152
|
if (![
|
|
131
153
|
"target", "position", "align", "direction", "offset", "title",
|
|
132
|
-
"symbol", "labels", "titleStyle", "itemGap", "border"
|
|
154
|
+
"symbol", "labels", "titleStyle", "itemGap", "border", "layout", "columns", "titlePosition"
|
|
133
155
|
].includes(key)) {
|
|
134
156
|
throw new Error(`interval legend does not accept ${key}.`);
|
|
135
157
|
}
|
|
@@ -149,7 +171,11 @@ function editInterval(program, previous, args) {
|
|
|
149
171
|
target: previous.target,
|
|
150
172
|
position: args.position ?? previous.position,
|
|
151
173
|
align: args.align ?? previous.align,
|
|
152
|
-
direction: args.direction ?? previous.
|
|
174
|
+
direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
|
|
175
|
+
? undefined : previous.direction),
|
|
176
|
+
layout: args.layout === undefined ? previous.layout : args.layout,
|
|
177
|
+
columns: args.columns ?? previous.columns,
|
|
178
|
+
titlePosition: args.titlePosition ?? previous.titlePosition,
|
|
153
179
|
offset: args.offset ?? previous.offset,
|
|
154
180
|
title,
|
|
155
181
|
symbol: mergeObject(previous.symbol, args.symbol),
|
|
@@ -158,12 +184,15 @@ function editInterval(program, previous, args) {
|
|
|
158
184
|
itemGap: args.itemGap ?? previous.itemGap,
|
|
159
185
|
border: mergeBorder(previous.border, args.border)
|
|
160
186
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
const config = { ...previous, ...normalized, inferredTitle, titleVisible };
|
|
188
|
+
const resolved = resolveIntervalConfig(program, config);
|
|
189
|
+
resolveIntervalLayout(program, resolved.config, resolved.scale);
|
|
190
|
+
return { normalized, config, titleMode, title, titleVisible };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function editInterval(program, previous, args) {
|
|
194
|
+
const { normalized, config, titleMode, title, titleVisible } = resolveIntervalEdit(program, previous, args);
|
|
195
|
+
let next = program._withLegendConfig("interval", config);
|
|
167
196
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
168
197
|
next = next.editSemantic({
|
|
169
198
|
property: "guide.legend.color.title",
|
|
@@ -182,20 +211,23 @@ function editInterval(program, previous, args) {
|
|
|
182
211
|
return next.rematerializeLegend();
|
|
183
212
|
}
|
|
184
213
|
|
|
185
|
-
function
|
|
186
|
-
const
|
|
214
|
+
function resolveSampledLegendEdit(program, kind, previous, args) {
|
|
215
|
+
const size = kind === "size";
|
|
216
|
+
const label = size ? "size" : "stroke-width";
|
|
217
|
+
const allowed = ["target", "title", "count", "labels", "titleStyle",
|
|
218
|
+
"position", "layout", "align", "direction", "columns", "titlePosition", "offset", "itemGap", "border"];
|
|
187
219
|
for (const key of Object.keys(args)) {
|
|
188
220
|
if (!allowed.includes(key)) {
|
|
189
|
-
throw new Error(
|
|
221
|
+
throw new Error(`${label} legend does not accept ${key}.`);
|
|
190
222
|
}
|
|
191
223
|
}
|
|
192
224
|
const count = args.count ?? previous.count;
|
|
193
225
|
if (!Number.isInteger(count) || count < 2) {
|
|
194
226
|
throw new RangeError(
|
|
195
|
-
|
|
227
|
+
`${label} legend count must be an integer of at least 2.`
|
|
196
228
|
);
|
|
197
229
|
}
|
|
198
|
-
validateGeneratedItemLimit(count,
|
|
230
|
+
validateGeneratedItemLimit(count, `${label} legend count`);
|
|
199
231
|
const layer = findLayer(program, previous.target);
|
|
200
232
|
const titleMode = args.title;
|
|
201
233
|
const inferredTitle = titleMode === "auto"
|
|
@@ -205,7 +237,7 @@ function editStrokeWidth(program, previous, args) {
|
|
|
205
237
|
? false
|
|
206
238
|
: titleMode === undefined ? previous.titleVisible !== false : true;
|
|
207
239
|
const title = titleMode === "auto"
|
|
208
|
-
? layer?.encoding?.
|
|
240
|
+
? layer?.encoding?.[kind]?.field
|
|
209
241
|
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
210
242
|
const config = {
|
|
211
243
|
...previous,
|
|
@@ -213,36 +245,60 @@ function editStrokeWidth(program, previous, args) {
|
|
|
213
245
|
inferredTitle,
|
|
214
246
|
titleVisible,
|
|
215
247
|
count,
|
|
248
|
+
...(size ? { inheritAppearance: false } : {}),
|
|
216
249
|
labels: normalizeLegendTextOptions(
|
|
217
250
|
args.labels,
|
|
218
251
|
"editLegend.labels",
|
|
219
|
-
previous.labels ?? STROKE_WIDTH_LEGEND_LABELS
|
|
252
|
+
previous.labels ?? (size ? SIZE_LEGEND_LABELS : STROKE_WIDTH_LEGEND_LABELS)
|
|
220
253
|
),
|
|
221
|
-
titleStyle:
|
|
254
|
+
titleStyle: normalizeLegendTitleOptions(
|
|
222
255
|
args.titleStyle,
|
|
223
256
|
"editLegend.titleStyle",
|
|
224
|
-
previous.titleStyle ?? STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
257
|
+
previous.titleStyle ?? (size ? SIZE_LEGEND_TITLE_STYLE : STROKE_WIDTH_LEGEND_TITLE_STYLE)
|
|
225
258
|
)
|
|
226
259
|
};
|
|
260
|
+
Object.assign(config, normalizeItemLegendLayout({ ...previous, ...args,
|
|
261
|
+
direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
|
|
262
|
+
? undefined : previous.direction) }));
|
|
263
|
+
config.border = normalizeLegendBorder(mergeBorder(previous.border, args.border));
|
|
264
|
+
(size ? resolveSizeLegendLayout : resolveStrokeWidthLegendLayout)(program, config);
|
|
265
|
+
return { config, titleMode, title, titleVisible };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function editSampledLegend(program, kind, previous, args) {
|
|
269
|
+
const { config, titleMode, title, titleVisible } = resolveSampledLegendEdit(program, kind, previous, args);
|
|
270
|
+
const prefix = kind === "size" ? "sizeLegend" : "strokeWidthLegend";
|
|
227
271
|
let next = program;
|
|
228
272
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
229
273
|
next = next.editSemantic({
|
|
230
|
-
property:
|
|
274
|
+
property: `guide.legend.${kind}.title`,
|
|
231
275
|
value: title
|
|
232
276
|
});
|
|
233
277
|
}
|
|
234
|
-
next = next._withLegendConfig(
|
|
235
|
-
next = reconcileGraphic(next,
|
|
236
|
-
type: "
|
|
278
|
+
next = next._withLegendConfig(kind, config);
|
|
279
|
+
next = reconcileGraphic(next, `${prefix}Background`, config.border !== false, {
|
|
280
|
+
type: "rect", before: `${prefix}Symbols`
|
|
281
|
+
});
|
|
282
|
+
next = reconcileGraphic(next, `${prefix}Title`, titleVisible, {
|
|
283
|
+
type: "text", after: `${prefix}Labels`
|
|
237
284
|
});
|
|
238
285
|
return next.rematerializeLegend();
|
|
239
286
|
}
|
|
240
287
|
|
|
241
|
-
function
|
|
242
|
-
|
|
288
|
+
function resolveCompanionSizeEdit(previous, size, args) {
|
|
289
|
+
if (size === undefined) return undefined;
|
|
290
|
+
const config = { ...size, count: args.count ?? size.count };
|
|
291
|
+
if (args.labels === undefined && args.titleStyle === undefined) return config;
|
|
292
|
+
const labels = size.inheritAppearance
|
|
293
|
+
? { ...previous.labels, offset: size.labels.offset } : size.labels ?? SIZE_LEGEND_LABELS;
|
|
294
|
+
const titleStyle = size.inheritAppearance
|
|
295
|
+
? previous.titleStyle : size.titleStyle ?? SIZE_LEGEND_TITLE_STYLE;
|
|
296
|
+
return { ...config, inheritAppearance: false,
|
|
297
|
+
labels: normalizeLegendTextOptions(args.labels, "editLegend.labels", labels),
|
|
298
|
+
titleStyle: normalizeLegendTitleOptions(args.titleStyle, "editLegend.titleStyle", titleStyle) };
|
|
243
299
|
}
|
|
244
300
|
|
|
245
|
-
function
|
|
301
|
+
function resolveCategoricalEdit(program, kind, previous, size, args, storedOrder = program.semanticSpec.guides.legend?.[kind]?.order) {
|
|
246
302
|
if (args.gradient !== undefined) {
|
|
247
303
|
throw new Error("Categorical legends do not accept gradient.");
|
|
248
304
|
}
|
|
@@ -258,21 +314,27 @@ function editCategorical(program, kind, previous, size, args) {
|
|
|
258
314
|
const title = titleMode === "auto"
|
|
259
315
|
? previous.field
|
|
260
316
|
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
317
|
+
const mergedLabels = mergeObject(previous.labels, args.labels);
|
|
318
|
+
if (mergedLabels.format !== undefined && mergedLabels.format !== "auto" && size === undefined) {
|
|
319
|
+
throw new Error("Categorical legend labels do not accept format.");
|
|
320
|
+
}
|
|
321
|
+
const { format: _sizeFormat, ...categoricalLabels } = mergedLabels;
|
|
261
322
|
const normalized = normalizeOptions({
|
|
262
323
|
target: previous.target,
|
|
263
324
|
channels: previous.channels,
|
|
325
|
+
layout: args.layout === undefined ? previous.layout : args.layout,
|
|
264
326
|
position: args.position ?? previous.position,
|
|
265
327
|
align: args.align ?? previous.align,
|
|
266
|
-
direction: args.direction ?? (args.position
|
|
267
|
-
?
|
|
268
|
-
: previous.direction),
|
|
328
|
+
direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
|
|
329
|
+
? undefined : previous.direction),
|
|
269
330
|
...(args.columns === undefined && previous.columns === undefined
|
|
270
331
|
? {} : { columns: args.columns ?? previous.columns }),
|
|
271
332
|
offset: args.offset ?? previous.offset,
|
|
272
333
|
titlePosition: args.titlePosition ?? previous.titlePosition,
|
|
273
334
|
title,
|
|
274
|
-
symbol: args.symbol
|
|
275
|
-
|
|
335
|
+
symbol: args.symbol === undefined ? previous.symbol
|
|
336
|
+
: resolveLegendSymbol(program, findLayer(program, previous.target), previous.channels, args.symbol),
|
|
337
|
+
labels: categoricalLabels,
|
|
276
338
|
titleStyle: mergeObject(previous.titleStyle, args.titleStyle),
|
|
277
339
|
itemGap: args.itemGap ?? previous.itemGap,
|
|
278
340
|
border: mergeBorder(previous.border, args.border)
|
|
@@ -281,9 +343,25 @@ function editCategorical(program, kind, previous, size, args) {
|
|
|
281
343
|
...previous,
|
|
282
344
|
...normalized,
|
|
283
345
|
inferredTitle,
|
|
346
|
+
inferredSymbol: args.symbol === undefined ? previous.inferredSymbol : args.symbol === "auto",
|
|
284
347
|
titleVisible
|
|
285
348
|
};
|
|
286
|
-
const
|
|
349
|
+
const order = args.order === undefined
|
|
350
|
+
? storedOrder
|
|
351
|
+
: normalizeLegendOrder(args.order);
|
|
352
|
+
// Resolve the final domain before changing semantic/config/graphic state.
|
|
353
|
+
resolveDefinition(program, findLayer(program, previous.target), previous.channels, title, order);
|
|
354
|
+
if (args.count !== undefined) {
|
|
355
|
+
if (!Number.isInteger(args.count) || args.count < 2) {
|
|
356
|
+
throw new RangeError("Size legend count must be an integer of at least 2.");
|
|
357
|
+
}
|
|
358
|
+
validateGeneratedItemLimit(args.count, "Size legend count");
|
|
359
|
+
}
|
|
360
|
+
return { config, order, titleMode, title, titleVisible, sizeConfig: resolveCompanionSizeEdit(previous, size, args) };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function editCategorical(program, kind, previous, size, args) {
|
|
364
|
+
const { config, order, titleMode, title, titleVisible, sizeConfig } = resolveCategoricalEdit(program, kind, previous, size, args);
|
|
287
365
|
const newSymbols = categoricalSymbolIds(config);
|
|
288
366
|
let next = program;
|
|
289
367
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
@@ -292,49 +370,99 @@ function editCategorical(program, kind, previous, size, args) {
|
|
|
292
370
|
value: title
|
|
293
371
|
});
|
|
294
372
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
373
|
+
if (args.order !== undefined) {
|
|
374
|
+
if (order === "scale") {
|
|
375
|
+
if (program.semanticSpec.guides.legend?.[kind]?.order !== undefined) {
|
|
376
|
+
next = next.editSemantic({ property: `guide.legend.${kind}.order`, remove: true });
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
next = next.editSemantic({ property: `guide.legend.${kind}.order`, value: order });
|
|
299
380
|
}
|
|
300
381
|
}
|
|
382
|
+
next = next._withLegendConfig(kind, config);
|
|
383
|
+
next = reconcileCategoricalSymbols(next, previous, config);
|
|
301
384
|
next = reconcileGraphic(next, `${kind === "series" ? "series" : "color"}LegendBackground`,
|
|
302
385
|
config.border !== false, {
|
|
303
386
|
type: "rect",
|
|
304
387
|
before: [...newSymbols][0]
|
|
305
388
|
});
|
|
306
|
-
for (const layer of config.symbol.layers) {
|
|
307
|
-
const id = symbolGraphic(config, layer.type);
|
|
308
|
-
if (next.graphicSpec.objects[id] === undefined) {
|
|
309
|
-
const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[layer.type];
|
|
310
|
-
next = next[`createLegendSymbol${suffix}`]();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
389
|
const titleId = `${kind === "series" ? "series" : "color"}LegendTitle`;
|
|
314
390
|
next = reconcileGraphic(next, titleId, titleVisible, {
|
|
315
391
|
type: "text",
|
|
316
392
|
...(next.graphicSpec.objects.sizeLegendSymbols === undefined
|
|
317
|
-
? {} : { before:
|
|
393
|
+
? {} : { before: next.graphicSpec.objects.sizeLegendBackground === undefined
|
|
394
|
+
? "sizeLegendSymbols" : "sizeLegendBackground" })
|
|
318
395
|
});
|
|
319
396
|
if (size !== undefined) {
|
|
320
|
-
|
|
321
|
-
|
|
397
|
+
next = next._withLegendConfig("size", sizeConfig);
|
|
398
|
+
}
|
|
399
|
+
return next.rematerializeLegend();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
function editLegendContent(program, target, args) {
|
|
404
|
+
validateLegendChannels(args.channels, "editLegend");
|
|
405
|
+
const { channels, target: _target, ...patch } = args;
|
|
406
|
+
const previous = program.guideConfigs.legend ?? {};
|
|
407
|
+
const removed = Object.keys(previous).filter(kind => previous[kind].target === target);
|
|
408
|
+
let view = removed.reduce((next, kind) => next._withoutMaterializationConfig(["guides", "legend", kind]), program);
|
|
409
|
+
const creation = resolveLegendCreationPlan(view, { target, channels });
|
|
410
|
+
const oldCategorical = removed.find(kind => ["series", "color"].includes(kind));
|
|
411
|
+
const plans = creation.steps.map(step => {
|
|
412
|
+
const descriptor = resolveLegendStepConfig(view, step);
|
|
413
|
+
const { kind } = descriptor;
|
|
414
|
+
const otherCategorical = ["series", "color"].some(key => previous[key] !== undefined && previous[key].target !== target);
|
|
415
|
+
if (previous[kind] !== undefined && previous[kind].target !== target ||
|
|
416
|
+
["series", "color"].includes(kind) && otherCategorical) {
|
|
417
|
+
throw new Error(`Legend ${kind} content already belongs to another target.`);
|
|
322
418
|
}
|
|
323
|
-
if (
|
|
324
|
-
|
|
419
|
+
if (["series", "color"].includes(kind) && oldCategorical !== undefined) {
|
|
420
|
+
const order = patch.order === undefined ? program.semanticSpec.guides.legend?.[oldCategorical]?.order
|
|
421
|
+
: normalizeLegendOrder(patch.order);
|
|
422
|
+
return { ...resolveCategoricalLegendRevision(view, oldCategorical, previous[oldCategorical],
|
|
423
|
+
descriptor.config.channels, { order, validateLayout: false }), kind };
|
|
325
424
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
425
|
+
return { ...descriptor, config: previous[kind]?.target === target ? previous[kind] : descriptor.config };
|
|
426
|
+
});
|
|
427
|
+
const categorical = plans.find(plan => ["series", "color"].includes(plan.kind));
|
|
428
|
+
const size = plans.find(plan => plan.kind === "size");
|
|
429
|
+
if (categorical !== undefined) {
|
|
430
|
+
if (size !== undefined && previous.size?.target !== target) {
|
|
431
|
+
size.config = { ...size.config, inheritAppearance: categorical.config.position === "left" ||
|
|
432
|
+
patch.position === "left" || patch.labels !== undefined || patch.titleStyle !== undefined };
|
|
433
|
+
}
|
|
434
|
+
const edited = resolveCategoricalEdit(view, categorical.kind, categorical.config, size?.config, patch, categorical.order);
|
|
435
|
+
categorical.config = edited.config;
|
|
436
|
+
categorical.order = edited.order;
|
|
437
|
+
if (size !== undefined) {
|
|
438
|
+
size.config = edited.sizeConfig;
|
|
439
|
+
if (categorical.config.layout === "legacy-bottom") {
|
|
440
|
+
throw new Error('Combined size legends require layout "edge".');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
const plan = plans[0];
|
|
445
|
+
if (plan.kind === "size" || plan.kind === "strokeWidth") {
|
|
446
|
+
plan.config = resolveSampledLegendEdit(view, plan.kind, plan.config, patch).config;
|
|
447
|
+
} else if (plan.kind === "interval") plan.config = resolveIntervalEdit(view, plan.config, patch).config;
|
|
448
|
+
else plan.config = resolveContinuousEdit(view, plan.kind, plan.config, patch).config;
|
|
449
|
+
}
|
|
450
|
+
// The complete final content and styles are validated before removing resources.
|
|
451
|
+
for (const { kind, config } of plans) view = view._withLegendConfig(kind, config);
|
|
452
|
+
if (categorical !== undefined) resolveLayout(view, categorical.config);
|
|
453
|
+
let next = removeLegendKinds(program, removed);
|
|
454
|
+
for (const { kind, config, order } of plans) {
|
|
455
|
+
if (["series", "color"].includes(kind)) next = createCategoricalLegendFromConfig(next, config, order);
|
|
456
|
+
else next = ({ size: createSizeLegendFromConfig, strokeWidth: createStrokeWidthLegendFromConfig,
|
|
457
|
+
gradient: createGradientLegendFromConfig, opacity: createOpacityLegendFromConfig,
|
|
458
|
+
interval: createIntervalLegendFromConfig })[kind](next, config);
|
|
331
459
|
}
|
|
332
460
|
return next.rematerializeLegend();
|
|
333
461
|
}
|
|
334
462
|
|
|
335
463
|
export const editLegend = action(
|
|
336
|
-
{ op: "editLegend", description: "Edit one stable legend layout or appearance." },
|
|
337
|
-
function (args = {}) {
|
|
464
|
+
{ op: "editLegend", description: "Edit one stable legend content, layout or appearance." },
|
|
465
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
338
466
|
validateOptionObject(args, OPTIONS, "editLegend");
|
|
339
467
|
const changes = Object.keys(args).filter(key => key !== "target");
|
|
340
468
|
if (changes.length === 0) {
|
|
@@ -346,6 +474,7 @@ export const editLegend = action(
|
|
|
346
474
|
throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');
|
|
347
475
|
}
|
|
348
476
|
const target = resolveLegendTarget(this, args.target, "editLegend");
|
|
477
|
+
if (args.channels !== undefined) return editLegendContent(this, target, args);
|
|
349
478
|
const configs = this.guideConfigs.legend ?? {};
|
|
350
479
|
const categoricalKind = ["series", "color"].find(
|
|
351
480
|
kind => configs[kind]?.target === target
|
|
@@ -363,11 +492,14 @@ export const editLegend = action(
|
|
|
363
492
|
return editInterval(this, configs.interval, args);
|
|
364
493
|
}
|
|
365
494
|
if (configs.strokeWidth?.target === target) {
|
|
366
|
-
return
|
|
495
|
+
return editSampledLegend(this, "strokeWidth", configs.strokeWidth, args);
|
|
496
|
+
}
|
|
497
|
+
if (configs.size?.target === target) {
|
|
498
|
+
return editSampledLegend(this, "size", configs.size, args);
|
|
367
499
|
}
|
|
368
500
|
const continuousKind = ["gradient", "opacity"].find(
|
|
369
501
|
kind => configs[kind]?.target === target
|
|
370
502
|
);
|
|
371
503
|
return editContinuous(this, continuousKind, configs[continuousKind], args);
|
|
372
|
-
}
|
|
504
|
+
})
|
|
373
505
|
);
|
|
@@ -20,7 +20,7 @@ function makeFocusedLegendEdit({ name, options, map }) {
|
|
|
20
20
|
const editLegendLayout = makeFocusedLegendEdit({
|
|
21
21
|
name: "editLegendLayout",
|
|
22
22
|
options: [
|
|
23
|
-
"position", "align", "direction", "columns", "offset",
|
|
23
|
+
"position", "layout", "align", "direction", "columns", "offset",
|
|
24
24
|
"titlePosition", "itemGap"
|
|
25
25
|
],
|
|
26
26
|
map: args => args
|
|
@@ -28,7 +28,7 @@ const editLegendLayout = makeFocusedLegendEdit({
|
|
|
28
28
|
|
|
29
29
|
const editLegendLabels = makeFocusedLegendEdit({
|
|
30
30
|
name: "editLegendLabels",
|
|
31
|
-
options: ["color", "fontSize", "fontFamily", "fontWeight"],
|
|
31
|
+
options: ["offset", "color", "fontSize", "fontFamily", "fontWeight", "format"],
|
|
32
32
|
map: ({ target, ...labels }) => ({ target, labels })
|
|
33
33
|
});
|
|
34
34
|
|