ggaction 0.0.12 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/README.md +20 -11
- 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 +10 -3
- package/src/actions/basic.js +4 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +13 -4
- package/src/actions/boxPlots/options.js +48 -16
- 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 +18 -6
- package/src/actions/charts/area.js +51 -0
- package/src/actions/charts/bar.js +13 -6
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +48 -0
- package/src/actions/charts/ecdf.js +186 -0
- package/src/actions/charts/endpoints.js +473 -0
- package/src/actions/charts/heatmap.js +4 -3
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +73 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +172 -0
- package/src/actions/charts/line.js +8 -6
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +52 -0
- package/src/actions/charts/polar.js +134 -0
- package/src/actions/charts/radar.js +305 -0
- package/src/actions/charts/radial.js +50 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +283 -0
- package/src/actions/charts/scatter.js +11 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +129 -3
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +111 -15
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +226 -30
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +38 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +19 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.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/channels.js +415 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +30 -23
- 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 +124 -34
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +6 -0
- package/src/actions/encodings/offset.js +30 -29
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +63 -11
- package/src/actions/encodings/position/index.js +52 -6
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +57 -13
- package/src/actions/encodings/position/resolve.js +88 -26
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +44 -34
- package/src/actions/encodings/ruleAppearance.js +24 -32
- package/src/actions/encodings/seriesLayout.js +105 -0
- package/src/actions/encodings/shared.js +107 -37
- package/src/actions/encodings/stroke.js +226 -0
- 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 +41 -8
- package/src/actions/errorBands/edit.js +360 -25
- package/src/actions/errorBars/create.js +56 -17
- package/src/actions/errorBars/edit.js +372 -21
- package/src/actions/facets/actions.js +619 -41
- package/src/actions/facets/derive.js +139 -25
- package/src/actions/gradientPlots/components.js +43 -13
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +8 -8
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +28 -19
- 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 +184 -28
- package/src/actions/guides/axes/lines.js +13 -7
- 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 +50 -25
- 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 +15 -8
- package/src/actions/guides/axes/ticks.js +11 -6
- package/src/actions/guides/axes/titles.js +32 -11
- 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 +17 -4
- package/src/actions/guides/guides.js +54 -31
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +309 -279
- package/src/actions/guides/legends/categorical/components.js +21 -17
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +123 -354
- package/src/actions/guides/legends/categorical/options.js +32 -16
- package/src/actions/guides/legends/categorical/recipes.js +114 -9
- package/src/actions/guides/legends/categorical/resolve.js +30 -15
- package/src/actions/guides/legends/categorical/symbols.js +87 -33
- package/src/actions/guides/legends/continuous/common.js +99 -26
- package/src/actions/guides/legends/continuous/gradient.js +120 -89
- package/src/actions/guides/legends/continuous/index.js +8 -2
- package/src/actions/guides/legends/continuous/interval.js +79 -95
- package/src/actions/guides/legends/continuous/opacity.js +113 -89
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +40 -0
- package/src/actions/guides/legends/edit.js +321 -90
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +149 -102
- package/src/actions/guides/legends/lifecycle.js +143 -0
- package/src/actions/guides/legends/remove.js +43 -78
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +213 -116
- package/src/actions/guides/legends/strokeWidth.js +121 -73
- package/src/actions/guides/legends/target.js +123 -0
- package/src/actions/guides/legends/transition.js +192 -0
- package/src/actions/guides/polar/axes/facade.js +81 -66
- package/src/actions/guides/polar/axes/labels.js +28 -8
- package/src/actions/guides/polar/axes/lines.js +12 -5
- package/src/actions/guides/polar/axes/shared.js +22 -0
- package/src/actions/guides/polar/axes/ticks.js +6 -5
- package/src/actions/guides/polar/axes/titles.js +6 -5
- package/src/actions/guides/polar/grids.js +8 -8
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +15 -2
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +78 -20
- package/src/actions/marks/area/actions.js +51 -12
- package/src/actions/marks/area/materialize.js +22 -3
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +33 -9
- package/src/actions/marks/index.js +14 -0
- package/src/actions/marks/line/actions.js +71 -113
- package/src/actions/marks/line/materialize.js +57 -56
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +29 -21
- 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 +154 -16
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +658 -0
- package/src/actions/marks/remove.js +87 -4
- package/src/actions/marks/rule/actions.js +77 -9
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +13 -13
- package/src/actions/marks/text/actions.js +296 -13
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +156 -8
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +3 -2
- package/src/actions/primitives/semanticAction.js +120 -10
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +23 -1
- package/src/actions/primitives/semanticValidation/scale.js +38 -8
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +11 -3
- package/src/actions/regression/edit.js +40 -13
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +21 -54
- package/src/actions/scales/consumers/families.js +28 -14
- package/src/actions/scales/consumers/index.js +42 -4
- package/src/actions/scales/consumers/seriesLayout.js +57 -12
- package/src/actions/scales/create.js +58 -26
- package/src/actions/scales/definitions.js +109 -39
- package/src/actions/scales/edit.js +101 -34
- package/src/actions/scales/editPolicy.js +95 -35
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +14 -49
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +140 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +98 -0
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +16 -0
- package/src/actions/theme/reconcile.js +1130 -0
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +90 -43
- package/src/actions/violinPlots/edit.js +192 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +32 -2
- package/src/core/compositionState.js +141 -8
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +37 -13
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +226 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +351 -0
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +123 -52
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/impute.js +340 -0
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +110 -0
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +109 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- 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 +8 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +63 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +8 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +57 -0
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +42 -5
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +23 -4
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +245 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +234 -13
- package/src/grammar/transformTopology.js +10 -0
- package/src/grammar/transforms.js +418 -16
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +232 -21
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +100 -12
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/labels.js +343 -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 +56 -147
- package/src/materialization/bars/histogram.js +104 -20
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/composition.js +3 -1
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +93 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +23 -66
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +28 -2
- package/src/materialization/legends.js +11 -2
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +68 -27
- package/src/materialization/marks/policies.js +4 -1
- package/src/materialization/rect.js +55 -42
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +6 -3
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +5 -1
- package/src/materialization/scales/resolve.js +65 -17
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +42 -71
- package/src/materialization/selection/items/common.js +27 -6
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +35 -8
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +324 -14
- package/src/mcp/adapter.js +2 -2
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +44 -0
- package/src/selectors/layers.js +7 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +140 -0
- package/types/basic.d.ts +17 -2
- package/types/index.d.ts +211 -0
- package/types/program.d.ts +2182 -204
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,30 +1,52 @@
|
|
|
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
|
-
validateGeneratedItemLimit,
|
|
4
7
|
validateOptionObject
|
|
5
8
|
} from "../../../core/validation.js";
|
|
6
9
|
import { normalizeOptions } from "./categorical/options.js";
|
|
7
|
-
import {
|
|
10
|
+
import { resolveLegendSymbol } from "./categorical/recipes.js";
|
|
11
|
+
import { resolveLayout } from "./categorical/layout.js";
|
|
12
|
+
import { resolveLegendCreationPlan } from "./categorical/actions.js";
|
|
13
|
+
import { categoricalSymbolIds, reconcileCategoricalSymbols, createCategoricalLegendFromConfig, resolveCategoricalLegendRevision, removeLegendKinds } from "./lifecycle.js";
|
|
14
|
+
import { createGradientLegendFromConfig } from "./continuous/gradient.js";
|
|
8
15
|
import {
|
|
16
|
+
normalizeItemLegendLayout,
|
|
17
|
+
normalizeLegendBorder,
|
|
9
18
|
normalizeLegendTextOptions,
|
|
19
|
+
normalizeLegendTitleOptions,
|
|
10
20
|
normalizeContinuousLegend,
|
|
11
21
|
validatePositive
|
|
12
22
|
} from "./continuous/common.js";
|
|
13
|
-
import { normalizeOpacitySymbol } from "./continuous/opacity.js";
|
|
14
|
-
import { normalizeIntervalLegend } from "./continuous/interval.js";
|
|
23
|
+
import { normalizeOpacitySymbol, createOpacityLegendFromConfig } from "./continuous/opacity.js";
|
|
24
|
+
import { normalizeIntervalLegend, resolveIntervalConfig, resolveIntervalLayout, createIntervalLegendFromConfig } from "./continuous/interval.js";
|
|
25
|
+
import {
|
|
26
|
+
createStrokeGradientLegendFromConfig,
|
|
27
|
+
createStrokeIntervalLegendFromConfig,
|
|
28
|
+
resolveStrokeIntervalConfig,
|
|
29
|
+
resolveStrokeIntervalLayout
|
|
30
|
+
} from "./continuous/stroke.js";
|
|
15
31
|
import { findLayer } from "../../../selectors/layers.js";
|
|
16
32
|
import { resolveLegendGraphicPlacement } from
|
|
17
33
|
"../../../materialization/graphicHierarchy.js";
|
|
18
|
-
import { resolveLegendTarget } from "./target.js";
|
|
34
|
+
import { resolveLegendTarget, validateLegendChannels } from "./target.js";
|
|
35
|
+
import { isDiscreteSizeScaleType } from "../../../grammar/scales/index.js";
|
|
36
|
+
import { SIZE_LEGEND_LABELS, SIZE_LEGEND_TITLE_STYLE, resolveSizeLegendLayout, createSizeLegendFromConfig } from "./size.js";
|
|
19
37
|
import {
|
|
20
38
|
STROKE_WIDTH_LEGEND_LABELS,
|
|
21
|
-
STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
39
|
+
STROKE_WIDTH_LEGEND_TITLE_STYLE,
|
|
40
|
+
resolveStrokeWidthLegendLayout,
|
|
41
|
+
createStrokeWidthLegendFromConfig
|
|
22
42
|
} from "./strokeWidth.js";
|
|
43
|
+
import { normalizeLegendSampling } from "./sampling.js";
|
|
44
|
+
import { planLegendBlockTransitions } from "./transition.js";
|
|
23
45
|
|
|
24
46
|
const OPTIONS = Object.freeze([
|
|
25
|
-
"target", "position", "align", "direction", "columns", "offset",
|
|
47
|
+
"target", "channels", "position", "layout", "align", "direction", "columns", "offset",
|
|
26
48
|
"titlePosition", "title", "symbol", "labels", "titleStyle", "itemGap",
|
|
27
|
-
"border", "count", "gradient"
|
|
49
|
+
"border", "count", "values", "gradient", "order"
|
|
28
50
|
]);
|
|
29
51
|
|
|
30
52
|
function mergeObject(previous, patch) {
|
|
@@ -46,18 +68,22 @@ function reconcileGraphic(program, id, shouldExist, definition) {
|
|
|
46
68
|
return program.createGraphics({
|
|
47
69
|
id,
|
|
48
70
|
...definition,
|
|
49
|
-
...resolveLegendGraphicPlacement(program
|
|
71
|
+
...resolveLegendGraphicPlacement(program, {
|
|
72
|
+
...(definition.before === undefined ? {} : { before: definition.before }),
|
|
73
|
+
...(definition.after === undefined ? {} : { after: definition.after })
|
|
74
|
+
})
|
|
50
75
|
});
|
|
51
76
|
}
|
|
52
77
|
return program;
|
|
53
78
|
}
|
|
54
79
|
|
|
55
|
-
function
|
|
56
|
-
const
|
|
80
|
+
function resolveContinuousEdit(program, kind, previous, args) {
|
|
81
|
+
const gradient = ["gradient", "strokeGradient"].includes(kind);
|
|
82
|
+
const allowed = gradient
|
|
57
83
|
? ["target", "position", "align", "offset", "title", "labels",
|
|
58
|
-
"titleStyle", "border", "count", "gradient"]
|
|
84
|
+
"titleStyle", "titlePosition", "border", "count", "gradient"]
|
|
59
85
|
: ["target", "position", "align", "offset", "title", "symbol", "labels",
|
|
60
|
-
"titleStyle", "titlePosition", "itemGap", "border", "count"];
|
|
86
|
+
"titleStyle", "titlePosition", "itemGap", "border", "count", "values"];
|
|
61
87
|
for (const key of Object.keys(args)) {
|
|
62
88
|
if (!allowed.includes(key)) throw new Error(`${kind} legend does not accept ${key}.`);
|
|
63
89
|
}
|
|
@@ -68,7 +94,9 @@ function editContinuous(program, kind, previous, args) {
|
|
|
68
94
|
const titleVisible = titleMode === false ? false
|
|
69
95
|
: titleMode === undefined ? previous.titleVisible !== false : true;
|
|
70
96
|
const layer = findLayer(program, previous.target);
|
|
71
|
-
const
|
|
97
|
+
const channel = kind === "gradient" ? "color"
|
|
98
|
+
: kind === "strokeGradient" ? "stroke" : "opacity";
|
|
99
|
+
const inferred = layer?.encoding?.[channel]?.field;
|
|
72
100
|
const title = titleMode === "auto"
|
|
73
101
|
? inferred
|
|
74
102
|
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
@@ -76,12 +104,17 @@ function editContinuous(program, kind, previous, args) {
|
|
|
76
104
|
const enteringInline = kind === "opacity" &&
|
|
77
105
|
titlePosition === "left" && previous.titlePosition !== "left";
|
|
78
106
|
const labels = mergeObject(previous.labels, args.labels);
|
|
107
|
+
const sampling = gradient ? undefined : normalizeLegendSampling(args, {
|
|
108
|
+
previous,
|
|
109
|
+
operation: "edit",
|
|
110
|
+
label: "Opacity legend"
|
|
111
|
+
});
|
|
79
112
|
const normalized = normalizeContinuousLegend({
|
|
80
113
|
target: previous.target,
|
|
81
114
|
position: args.position ?? previous.position,
|
|
82
115
|
align: args.align ?? previous.align,
|
|
83
116
|
offset: args.offset ?? previous.offset,
|
|
84
|
-
count: args.count ?? previous.count,
|
|
117
|
+
...(gradient ? { count: args.count ?? previous.count } : {}),
|
|
85
118
|
titlePosition,
|
|
86
119
|
title,
|
|
87
120
|
labels: enteringInline && args.labels?.offset === undefined
|
|
@@ -93,14 +126,16 @@ function editContinuous(program, kind, previous, args) {
|
|
|
93
126
|
itemGap: args.itemGap ?? (enteringInline ? 20 : previous.itemGap),
|
|
94
127
|
symbol: args.symbol ?? previous.symbol
|
|
95
128
|
} : {})
|
|
96
|
-
}, kind);
|
|
129
|
+
}, gradient ? "gradient" : kind);
|
|
130
|
+
const { count: _legacyCount, ...canonicalPrevious } = previous;
|
|
97
131
|
const config = {
|
|
98
|
-
...previous,
|
|
132
|
+
...(gradient ? previous : canonicalPrevious),
|
|
99
133
|
...normalized,
|
|
134
|
+
...(gradient ? {} : { sampling }),
|
|
100
135
|
inferredTitle,
|
|
101
136
|
titleVisible
|
|
102
137
|
};
|
|
103
|
-
if (
|
|
138
|
+
if (gradient) {
|
|
104
139
|
const gradient = mergeObject(previous.gradient, args.gradient);
|
|
105
140
|
validatePositive(gradient.length, "Gradient length");
|
|
106
141
|
validatePositive(gradient.thickness, "Gradient thickness");
|
|
@@ -108,28 +143,38 @@ function editContinuous(program, kind, previous, args) {
|
|
|
108
143
|
} else {
|
|
109
144
|
config.symbol = normalizeOpacitySymbol(args.symbol ?? previous.symbol);
|
|
110
145
|
}
|
|
111
|
-
|
|
146
|
+
return { config, titleMode, title, titleVisible };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function editContinuous(program, kind, previous, args) {
|
|
150
|
+
const { config, titleMode, title, titleVisible } = resolveContinuousEdit(program, kind, previous, args);
|
|
151
|
+
const prefix = kind === "gradient" ? "colorGradient"
|
|
152
|
+
: kind === "strokeGradient" ? "strokeGradient" : "opacityLegend";
|
|
153
|
+
const semanticKind = kind === "gradient" ? "color"
|
|
154
|
+
: kind === "strokeGradient" ? "stroke" : "opacity";
|
|
112
155
|
let next = program;
|
|
113
156
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
114
157
|
next = next.editSemantic({
|
|
115
|
-
property: `guide.legend.${
|
|
158
|
+
property: `guide.legend.${semanticKind}.title`,
|
|
116
159
|
value: title
|
|
117
160
|
});
|
|
118
161
|
}
|
|
119
162
|
next = next._withLegendConfig(kind, config);
|
|
120
163
|
next = reconcileGraphic(next, `${prefix}Background`, config.border !== false, {
|
|
121
164
|
type: "rect",
|
|
122
|
-
before:
|
|
165
|
+
before: ["gradient", "strokeGradient"].includes(kind)
|
|
166
|
+
? `${prefix}Strips`
|
|
167
|
+
: "opacityLegendSymbols"
|
|
123
168
|
});
|
|
124
169
|
next = reconcileGraphic(next, `${prefix}Title`, titleVisible, { type: "text" });
|
|
125
170
|
return next.rematerializeLegend();
|
|
126
171
|
}
|
|
127
172
|
|
|
128
|
-
function
|
|
173
|
+
function resolveIntervalEdit(program, previous, args, channel = "color") {
|
|
129
174
|
for (const key of Object.keys(args)) {
|
|
130
175
|
if (![
|
|
131
176
|
"target", "position", "align", "direction", "offset", "title",
|
|
132
|
-
"symbol", "labels", "titleStyle", "itemGap", "border"
|
|
177
|
+
"symbol", "labels", "titleStyle", "itemGap", "border", "layout", "columns", "titlePosition"
|
|
133
178
|
].includes(key)) {
|
|
134
179
|
throw new Error(`interval legend does not accept ${key}.`);
|
|
135
180
|
}
|
|
@@ -143,13 +188,17 @@ function editInterval(program, previous, args) {
|
|
|
143
188
|
? false
|
|
144
189
|
: titleMode === undefined ? previous.titleVisible !== false : true;
|
|
145
190
|
const title = titleMode === "auto"
|
|
146
|
-
? layer?.encoding?.
|
|
191
|
+
? layer?.encoding?.[channel]?.field
|
|
147
192
|
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
148
193
|
const normalized = normalizeIntervalLegend({
|
|
149
194
|
target: previous.target,
|
|
150
195
|
position: args.position ?? previous.position,
|
|
151
196
|
align: args.align ?? previous.align,
|
|
152
|
-
direction: args.direction ?? previous.
|
|
197
|
+
direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
|
|
198
|
+
? undefined : previous.direction),
|
|
199
|
+
layout: args.layout === undefined ? previous.layout : args.layout,
|
|
200
|
+
columns: args.columns ?? previous.columns,
|
|
201
|
+
titlePosition: args.titlePosition ?? previous.titlePosition,
|
|
153
202
|
offset: args.offset ?? previous.offset,
|
|
154
203
|
title,
|
|
155
204
|
symbol: mergeObject(previous.symbol, args.symbol),
|
|
@@ -158,44 +207,69 @@ function editInterval(program, previous, args) {
|
|
|
158
207
|
itemGap: args.itemGap ?? previous.itemGap,
|
|
159
208
|
border: mergeBorder(previous.border, args.border)
|
|
160
209
|
});
|
|
161
|
-
|
|
210
|
+
const config = {
|
|
162
211
|
...previous,
|
|
163
212
|
...normalized,
|
|
164
213
|
inferredTitle,
|
|
165
|
-
titleVisible
|
|
166
|
-
|
|
214
|
+
titleVisible,
|
|
215
|
+
...(channel === "stroke" && args.symbol?.strokeWidth !== undefined
|
|
216
|
+
? { inferredStrokeWidth: false }
|
|
217
|
+
: {})
|
|
218
|
+
};
|
|
219
|
+
const resolved = channel === "stroke"
|
|
220
|
+
? resolveStrokeIntervalConfig(program, config)
|
|
221
|
+
: resolveIntervalConfig(program, config);
|
|
222
|
+
(channel === "stroke" ? resolveStrokeIntervalLayout : resolveIntervalLayout)(
|
|
223
|
+
program,
|
|
224
|
+
resolved.config,
|
|
225
|
+
resolved.scale
|
|
226
|
+
);
|
|
227
|
+
return { normalized, config, titleMode, title, titleVisible };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function editInterval(program, previous, args, channel = "color") {
|
|
231
|
+
const kind = channel === "stroke" ? "strokeInterval" : "interval";
|
|
232
|
+
const prefix = channel === "stroke" ? "strokeInterval" : "colorLegend";
|
|
233
|
+
const { normalized, config, titleMode, title, titleVisible } = resolveIntervalEdit(program, previous, args, channel);
|
|
234
|
+
let next = program._withLegendConfig(kind, config);
|
|
167
235
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
168
236
|
next = next.editSemantic({
|
|
169
|
-
property:
|
|
237
|
+
property: `guide.legend.${channel}.title`,
|
|
170
238
|
value: title
|
|
171
239
|
});
|
|
172
240
|
}
|
|
173
|
-
next = reconcileGraphic(next,
|
|
241
|
+
next = reconcileGraphic(next, `${prefix}Title`, titleVisible, {
|
|
174
242
|
type: "text"
|
|
175
243
|
});
|
|
176
244
|
next = reconcileGraphic(
|
|
177
245
|
next,
|
|
178
|
-
|
|
246
|
+
`${prefix}Background`,
|
|
179
247
|
normalized.border !== false,
|
|
180
|
-
{ type: "rect", before:
|
|
248
|
+
{ type: "rect", before: `${prefix}Symbols` }
|
|
181
249
|
);
|
|
182
250
|
return next.rematerializeLegend();
|
|
183
251
|
}
|
|
184
252
|
|
|
185
|
-
function
|
|
186
|
-
const
|
|
253
|
+
function resolveSampledLegendEdit(program, kind, previous, args) {
|
|
254
|
+
const size = kind === "size";
|
|
255
|
+
const label = size ? "size" : "stroke-width";
|
|
256
|
+
const allowed = ["target", "title", "count", "values", "labels", "titleStyle",
|
|
257
|
+
"position", "layout", "align", "direction", "columns", "titlePosition", "offset", "itemGap", "border"];
|
|
187
258
|
for (const key of Object.keys(args)) {
|
|
188
259
|
if (!allowed.includes(key)) {
|
|
189
|
-
throw new Error(
|
|
260
|
+
throw new Error(`${label} legend does not accept ${key}.`);
|
|
190
261
|
}
|
|
191
262
|
}
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
263
|
+
const discrete = size &&
|
|
264
|
+
isDiscreteSizeScaleType(program.resolvedScales[previous.scale]?.type);
|
|
265
|
+
if (discrete && (args.count !== undefined || args.values !== undefined)) {
|
|
266
|
+
throw new Error("Discrete size legends do not support count or exact values.");
|
|
197
267
|
}
|
|
198
|
-
|
|
268
|
+
const sampling = discrete ? undefined : normalizeLegendSampling(args, {
|
|
269
|
+
previous,
|
|
270
|
+
operation: "edit",
|
|
271
|
+
label: `${size ? "Size" : "Stroke-width"} legend`
|
|
272
|
+
});
|
|
199
273
|
const layer = findLayer(program, previous.target);
|
|
200
274
|
const titleMode = args.title;
|
|
201
275
|
const inferredTitle = titleMode === "auto"
|
|
@@ -205,44 +279,91 @@ function editStrokeWidth(program, previous, args) {
|
|
|
205
279
|
? false
|
|
206
280
|
: titleMode === undefined ? previous.titleVisible !== false : true;
|
|
207
281
|
const title = titleMode === "auto"
|
|
208
|
-
? layer?.encoding?.
|
|
282
|
+
? layer?.encoding?.[kind]?.field
|
|
209
283
|
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
284
|
+
const { count: _legacyCount, ...continuousPrevious } = previous;
|
|
285
|
+
const canonicalPrevious = discrete ? previous : continuousPrevious;
|
|
210
286
|
const config = {
|
|
211
|
-
...
|
|
287
|
+
...canonicalPrevious,
|
|
212
288
|
title,
|
|
213
289
|
inferredTitle,
|
|
214
290
|
titleVisible,
|
|
215
|
-
|
|
291
|
+
...(discrete ? {} : { sampling }),
|
|
292
|
+
...(size ? { inheritAppearance: false } : {}),
|
|
216
293
|
labels: normalizeLegendTextOptions(
|
|
217
294
|
args.labels,
|
|
218
295
|
"editLegend.labels",
|
|
219
|
-
previous.labels ?? STROKE_WIDTH_LEGEND_LABELS
|
|
296
|
+
previous.labels ?? (size ? SIZE_LEGEND_LABELS : STROKE_WIDTH_LEGEND_LABELS)
|
|
220
297
|
),
|
|
221
|
-
titleStyle:
|
|
298
|
+
titleStyle: normalizeLegendTitleOptions(
|
|
222
299
|
args.titleStyle,
|
|
223
300
|
"editLegend.titleStyle",
|
|
224
|
-
previous.titleStyle ?? STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
301
|
+
previous.titleStyle ?? (size ? SIZE_LEGEND_TITLE_STYLE : STROKE_WIDTH_LEGEND_TITLE_STYLE)
|
|
225
302
|
)
|
|
226
303
|
};
|
|
304
|
+
Object.assign(config, normalizeItemLegendLayout({ ...previous, ...args,
|
|
305
|
+
direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
|
|
306
|
+
? undefined : previous.direction) }));
|
|
307
|
+
config.border = normalizeLegendBorder(mergeBorder(previous.border, args.border));
|
|
308
|
+
(size ? resolveSizeLegendLayout : resolveStrokeWidthLegendLayout)(program, config);
|
|
309
|
+
return { config, titleMode, title, titleVisible };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function editSampledLegend(program, kind, previous, args) {
|
|
313
|
+
const { config, titleMode, title, titleVisible } = resolveSampledLegendEdit(program, kind, previous, args);
|
|
314
|
+
const prefix = kind === "size" ? "sizeLegend" : "strokeWidthLegend";
|
|
227
315
|
let next = program;
|
|
228
316
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
229
317
|
next = next.editSemantic({
|
|
230
|
-
property:
|
|
318
|
+
property: `guide.legend.${kind}.title`,
|
|
231
319
|
value: title
|
|
232
320
|
});
|
|
233
321
|
}
|
|
234
|
-
next = next._withLegendConfig(
|
|
235
|
-
next = reconcileGraphic(next,
|
|
236
|
-
type: "
|
|
322
|
+
next = next._withLegendConfig(kind, config);
|
|
323
|
+
next = reconcileGraphic(next, `${prefix}Background`, config.border !== false, {
|
|
324
|
+
type: "rect", before: `${prefix}Symbols`
|
|
325
|
+
});
|
|
326
|
+
next = reconcileGraphic(next, `${prefix}Title`, titleVisible, {
|
|
327
|
+
type: "text", after: `${prefix}Labels`
|
|
237
328
|
});
|
|
238
329
|
return next.rematerializeLegend();
|
|
239
330
|
}
|
|
240
331
|
|
|
241
|
-
function
|
|
242
|
-
|
|
332
|
+
function resolveCompanionSizeEdit(program, previous, size, args) {
|
|
333
|
+
if (size === undefined) return undefined;
|
|
334
|
+
const discrete = isDiscreteSizeScaleType(
|
|
335
|
+
program.resolvedScales[size.scale]?.type
|
|
336
|
+
);
|
|
337
|
+
if (discrete && (args.count !== undefined || args.values !== undefined)) {
|
|
338
|
+
throw new Error("Discrete size legends do not support count or exact values.");
|
|
339
|
+
}
|
|
340
|
+
const sampling = discrete ? undefined : normalizeLegendSampling(args, {
|
|
341
|
+
previous: size,
|
|
342
|
+
operation: "edit",
|
|
343
|
+
label: "Size legend"
|
|
344
|
+
});
|
|
345
|
+
const { count: _legacyCount, ...continuousSize } = size;
|
|
346
|
+
const canonicalSize = discrete ? size : continuousSize;
|
|
347
|
+
const config = {
|
|
348
|
+
...canonicalSize,
|
|
349
|
+
...(discrete ? {} : { sampling })
|
|
350
|
+
};
|
|
351
|
+
if (args.labels === undefined && args.titleStyle === undefined) return config;
|
|
352
|
+
const labels = size.inheritAppearance
|
|
353
|
+
? { ...previous.labels, offset: size.labels.offset } : size.labels ?? SIZE_LEGEND_LABELS;
|
|
354
|
+
const titleStyle = size.inheritAppearance
|
|
355
|
+
? previous.titleStyle : size.titleStyle ?? SIZE_LEGEND_TITLE_STYLE;
|
|
356
|
+
return { ...config, inheritAppearance: false,
|
|
357
|
+
labels: normalizeLegendTextOptions(args.labels, "editLegend.labels", labels),
|
|
358
|
+
titleStyle: normalizeLegendTitleOptions(args.titleStyle, "editLegend.titleStyle", titleStyle) };
|
|
243
359
|
}
|
|
244
360
|
|
|
245
|
-
function
|
|
361
|
+
function resolveCategoricalEdit(program, kind, previous, size, args, storedOrder = program.semanticSpec.guides.legend?.[kind]?.order) {
|
|
362
|
+
if (args.values !== undefined) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
"Combined and categorical legends require a channel block selector for exact values."
|
|
365
|
+
);
|
|
366
|
+
}
|
|
246
367
|
if (args.gradient !== undefined) {
|
|
247
368
|
throw new Error("Categorical legends do not accept gradient.");
|
|
248
369
|
}
|
|
@@ -258,32 +379,57 @@ function editCategorical(program, kind, previous, size, args) {
|
|
|
258
379
|
const title = titleMode === "auto"
|
|
259
380
|
? previous.field
|
|
260
381
|
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
382
|
+
const mergedLabels = mergeObject(previous.labels, args.labels);
|
|
383
|
+
if (mergedLabels.format !== undefined && mergedLabels.format !== "auto" && size === undefined) {
|
|
384
|
+
throw new Error("Categorical legend labels do not accept format.");
|
|
385
|
+
}
|
|
386
|
+
const { format: _sizeFormat, ...categoricalLabels } = mergedLabels;
|
|
387
|
+
const inferredSymbol = args.symbol === undefined
|
|
388
|
+
? previous.inferredSymbol
|
|
389
|
+
: args.symbol === "auto";
|
|
261
390
|
const normalized = normalizeOptions({
|
|
262
391
|
target: previous.target,
|
|
263
392
|
channels: previous.channels,
|
|
393
|
+
layout: args.layout === undefined ? previous.layout : args.layout,
|
|
264
394
|
position: args.position ?? previous.position,
|
|
265
395
|
align: args.align ?? previous.align,
|
|
266
|
-
direction: args.direction ?? (args.position
|
|
267
|
-
?
|
|
268
|
-
: previous.direction),
|
|
396
|
+
direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
|
|
397
|
+
? undefined : previous.direction),
|
|
269
398
|
...(args.columns === undefined && previous.columns === undefined
|
|
270
399
|
? {} : { columns: args.columns ?? previous.columns }),
|
|
271
400
|
offset: args.offset ?? previous.offset,
|
|
272
401
|
titlePosition: args.titlePosition ?? previous.titlePosition,
|
|
273
402
|
title,
|
|
274
|
-
symbol: args.symbol
|
|
275
|
-
|
|
403
|
+
symbol: args.symbol === undefined ? previous.symbol
|
|
404
|
+
: resolveLegendSymbol(
|
|
405
|
+
program,
|
|
406
|
+
findLayer(program, previous.target),
|
|
407
|
+
previous.channels,
|
|
408
|
+
args.symbol,
|
|
409
|
+
kind
|
|
410
|
+
),
|
|
411
|
+
labels: categoricalLabels,
|
|
276
412
|
titleStyle: mergeObject(previous.titleStyle, args.titleStyle),
|
|
277
413
|
itemGap: args.itemGap ?? previous.itemGap,
|
|
278
414
|
border: mergeBorder(previous.border, args.border)
|
|
279
|
-
}, kind);
|
|
415
|
+
}, kind, { internalSymbol: inferredSymbol });
|
|
280
416
|
const config = {
|
|
281
417
|
...previous,
|
|
282
418
|
...normalized,
|
|
283
419
|
inferredTitle,
|
|
420
|
+
inferredSymbol,
|
|
284
421
|
titleVisible
|
|
285
422
|
};
|
|
286
|
-
const
|
|
423
|
+
const order = args.order === undefined
|
|
424
|
+
? storedOrder
|
|
425
|
+
: normalizeLegendOrder(args.order);
|
|
426
|
+
// Resolve the final domain before changing semantic/config/graphic state.
|
|
427
|
+
resolveDefinition(program, findLayer(program, previous.target), previous.channels, title, order);
|
|
428
|
+
return { config, order, titleMode, title, titleVisible, sizeConfig: resolveCompanionSizeEdit(program, previous, size, args) };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function editCategorical(program, kind, previous, size, args) {
|
|
432
|
+
const { config, order, titleMode, title, titleVisible, sizeConfig } = resolveCategoricalEdit(program, kind, previous, size, args);
|
|
287
433
|
const newSymbols = categoricalSymbolIds(config);
|
|
288
434
|
let next = program;
|
|
289
435
|
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
@@ -292,49 +438,113 @@ function editCategorical(program, kind, previous, size, args) {
|
|
|
292
438
|
value: title
|
|
293
439
|
});
|
|
294
440
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
441
|
+
if (args.order !== undefined) {
|
|
442
|
+
if (order === "scale") {
|
|
443
|
+
if (program.semanticSpec.guides.legend?.[kind]?.order !== undefined) {
|
|
444
|
+
next = next.editSemantic({ property: `guide.legend.${kind}.order`, remove: true });
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
next = next.editSemantic({ property: `guide.legend.${kind}.order`, value: order });
|
|
299
448
|
}
|
|
300
449
|
}
|
|
301
|
-
next =
|
|
450
|
+
next = next._withLegendConfig(kind, config);
|
|
451
|
+
next = reconcileCategoricalSymbols(next, previous, config);
|
|
452
|
+
next = reconcileGraphic(next, `${kind}LegendBackground`,
|
|
302
453
|
config.border !== false, {
|
|
303
454
|
type: "rect",
|
|
304
455
|
before: [...newSymbols][0]
|
|
305
456
|
});
|
|
306
|
-
|
|
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
|
-
const titleId = `${kind === "series" ? "series" : "color"}LegendTitle`;
|
|
457
|
+
const titleId = `${kind}LegendTitle`;
|
|
314
458
|
next = reconcileGraphic(next, titleId, titleVisible, {
|
|
315
459
|
type: "text",
|
|
316
460
|
...(next.graphicSpec.objects.sizeLegendSymbols === undefined
|
|
317
|
-
? {} : { before:
|
|
461
|
+
? {} : { before: next.graphicSpec.objects.sizeLegendBackground === undefined
|
|
462
|
+
? "sizeLegendSymbols" : "sizeLegendBackground" })
|
|
318
463
|
});
|
|
319
464
|
if (size !== undefined) {
|
|
320
|
-
|
|
321
|
-
|
|
465
|
+
next = next._withLegendConfig("size", sizeConfig);
|
|
466
|
+
}
|
|
467
|
+
return next.rematerializeLegend();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
function editLegendContent(program, target, args) {
|
|
472
|
+
validateLegendChannels(args.channels, "editLegend");
|
|
473
|
+
const { channels, target: _target, ...patch } = args;
|
|
474
|
+
const previous = program.guideConfigs.legend ?? {};
|
|
475
|
+
const removed = Object.keys(previous).filter(kind => previous[kind].target === target);
|
|
476
|
+
let view = removed.reduce((next, kind) => next._withoutMaterializationConfig(["guides", "legend", kind]), program);
|
|
477
|
+
const creation = resolveLegendCreationPlan(view, { target, channels });
|
|
478
|
+
const oldCategorical = removed.find(kind =>
|
|
479
|
+
["series", "color", "stroke"].includes(kind)
|
|
480
|
+
);
|
|
481
|
+
let plans = creation.steps.map(step => {
|
|
482
|
+
const descriptor = resolveLegendStepConfig(view, step);
|
|
483
|
+
const { kind } = descriptor;
|
|
484
|
+
if (previous[kind] !== undefined && previous[kind].target !== target) {
|
|
485
|
+
throw new Error(`Legend ${kind} content already belongs to another target.`);
|
|
322
486
|
}
|
|
323
|
-
if (
|
|
324
|
-
|
|
487
|
+
if (["series", "color", "stroke"].includes(kind) && oldCategorical !== undefined) {
|
|
488
|
+
const order = patch.order === undefined ? program.semanticSpec.guides.legend?.[oldCategorical]?.order
|
|
489
|
+
: normalizeLegendOrder(patch.order);
|
|
490
|
+
return { ...resolveCategoricalLegendRevision(view, oldCategorical, previous[oldCategorical],
|
|
491
|
+
descriptor.config.channels, { order, validateLayout: false }), kind };
|
|
325
492
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
493
|
+
return { ...descriptor, config: previous[kind]?.target === target ? previous[kind] : descriptor.config };
|
|
494
|
+
});
|
|
495
|
+
const categorical = plans.find(plan =>
|
|
496
|
+
["series", "color", "stroke"].includes(plan.kind)
|
|
497
|
+
);
|
|
498
|
+
const size = plans.find(plan => plan.kind === "size");
|
|
499
|
+
if (categorical !== undefined) {
|
|
500
|
+
if (size !== undefined && previous.size?.target !== target) {
|
|
501
|
+
size.config = { ...size.config, inheritAppearance: categorical.config.position === "left" ||
|
|
502
|
+
patch.position === "left" || patch.labels !== undefined || patch.titleStyle !== undefined };
|
|
503
|
+
}
|
|
504
|
+
const edited = resolveCategoricalEdit(view, categorical.kind, categorical.config, size?.config, patch, categorical.order);
|
|
505
|
+
categorical.config = edited.config;
|
|
506
|
+
categorical.order = edited.order;
|
|
507
|
+
if (size !== undefined) {
|
|
508
|
+
size.config = edited.sizeConfig;
|
|
509
|
+
if (categorical.config.layout === "legacy-bottom") {
|
|
510
|
+
throw new Error('Combined size legends require layout "edge".');
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
const plan = plans[0];
|
|
515
|
+
if (plan.kind === "size" || plan.kind === "strokeWidth") {
|
|
516
|
+
plan.config = resolveSampledLegendEdit(view, plan.kind, plan.config, patch).config;
|
|
517
|
+
} else if (["interval", "strokeInterval"].includes(plan.kind)) {
|
|
518
|
+
plan.config = resolveIntervalEdit(
|
|
519
|
+
view,
|
|
520
|
+
plan.config,
|
|
521
|
+
patch,
|
|
522
|
+
plan.kind === "strokeInterval" ? "stroke" : "color"
|
|
523
|
+
).config;
|
|
524
|
+
}
|
|
525
|
+
else plan.config = resolveContinuousEdit(view, plan.kind, plan.config, patch).config;
|
|
526
|
+
}
|
|
527
|
+
plans = planLegendBlockTransitions(program, target, plans);
|
|
528
|
+
// The complete final content and styles are validated before removing resources.
|
|
529
|
+
for (const { kind, config } of plans) view = view._withLegendConfig(kind, config);
|
|
530
|
+
if (categorical !== undefined) resolveLayout(view, categorical.config);
|
|
531
|
+
let next = removeLegendKinds(program, removed);
|
|
532
|
+
for (const { kind, config, order } of plans) {
|
|
533
|
+
if (["series", "color", "stroke"].includes(kind)) {
|
|
534
|
+
next = createCategoricalLegendFromConfig(next, config, order);
|
|
535
|
+
}
|
|
536
|
+
else next = ({ size: createSizeLegendFromConfig, strokeWidth: createStrokeWidthLegendFromConfig,
|
|
537
|
+
gradient: createGradientLegendFromConfig, opacity: createOpacityLegendFromConfig,
|
|
538
|
+
interval: createIntervalLegendFromConfig,
|
|
539
|
+
strokeGradient: createStrokeGradientLegendFromConfig,
|
|
540
|
+
strokeInterval: createStrokeIntervalLegendFromConfig })[kind](next, config);
|
|
331
541
|
}
|
|
332
542
|
return next.rematerializeLegend();
|
|
333
543
|
}
|
|
334
544
|
|
|
335
545
|
export const editLegend = action(
|
|
336
|
-
{ op: "editLegend", description: "Edit one stable legend layout or appearance." },
|
|
337
|
-
function (args = {}) {
|
|
546
|
+
{ op: "editLegend", description: "Edit one stable legend content, layout or appearance." },
|
|
547
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
338
548
|
validateOptionObject(args, OPTIONS, "editLegend");
|
|
339
549
|
const changes = Object.keys(args).filter(key => key !== "target");
|
|
340
550
|
if (changes.length === 0) {
|
|
@@ -346,10 +556,25 @@ export const editLegend = action(
|
|
|
346
556
|
throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');
|
|
347
557
|
}
|
|
348
558
|
const target = resolveLegendTarget(this, args.target, "editLegend");
|
|
559
|
+
if (args.channels !== undefined) return editLegendContent(this, target, args);
|
|
349
560
|
const configs = this.guideConfigs.legend ?? {};
|
|
350
|
-
const
|
|
561
|
+
const ownedKinds = Object.keys(configs).filter(
|
|
562
|
+
kind => configs[kind]?.target === target
|
|
563
|
+
);
|
|
564
|
+
if (args.values !== undefined && ownedKinds.length > 1) {
|
|
565
|
+
throw new Error(
|
|
566
|
+
"A target with multiple legend blocks requires a channel block selector for exact values."
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
const categoricalKinds = ["series", "color", "stroke"].filter(
|
|
351
570
|
kind => configs[kind]?.target === target
|
|
352
571
|
);
|
|
572
|
+
if (categoricalKinds.length > 1) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
"editLegend requires channels when a target owns multiple categorical legend blocks."
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
const categoricalKind = categoricalKinds[0];
|
|
353
578
|
if (categoricalKind !== undefined) {
|
|
354
579
|
return editCategorical(
|
|
355
580
|
this,
|
|
@@ -362,12 +587,18 @@ export const editLegend = action(
|
|
|
362
587
|
if (configs.interval?.target === target) {
|
|
363
588
|
return editInterval(this, configs.interval, args);
|
|
364
589
|
}
|
|
590
|
+
if (configs.strokeInterval?.target === target) {
|
|
591
|
+
return editInterval(this, configs.strokeInterval, args, "stroke");
|
|
592
|
+
}
|
|
365
593
|
if (configs.strokeWidth?.target === target) {
|
|
366
|
-
return
|
|
594
|
+
return editSampledLegend(this, "strokeWidth", configs.strokeWidth, args);
|
|
367
595
|
}
|
|
368
|
-
|
|
596
|
+
if (configs.size?.target === target) {
|
|
597
|
+
return editSampledLegend(this, "size", configs.size, args);
|
|
598
|
+
}
|
|
599
|
+
const continuousKind = ["gradient", "strokeGradient", "opacity"].find(
|
|
369
600
|
kind => configs[kind]?.target === target
|
|
370
601
|
);
|
|
371
602
|
return editContinuous(this, continuousKind, configs[continuousKind], args);
|
|
372
|
-
}
|
|
603
|
+
})
|
|
373
604
|
);
|
|
@@ -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
|
|