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,42 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { noOptions } from "../../../../core/validation.js";
|
|
1
|
+
import { validateOptionObject } from "../../../../core/validation.js";
|
|
3
2
|
import { mapOrdinalValues } from "../../../../grammar/scales/index.js";
|
|
4
|
-
import { unionConcreteGraphicBounds } from "../../../../grammar/schemas/graphicBounds.js";
|
|
5
3
|
import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
|
|
6
4
|
import { formatVisibleText } from "../../../../core/textMetrics.js";
|
|
7
5
|
import {
|
|
8
6
|
assertLegendBoundsInsideCanvas,
|
|
9
|
-
formatContinuousValues,
|
|
10
7
|
resolveContinuousBounds,
|
|
11
|
-
resolveLegendBackgroundFromBounds
|
|
12
|
-
resolveLegendTextBounds,
|
|
13
|
-
sampleContinuousValues
|
|
8
|
+
resolveLegendBackgroundFromBounds
|
|
14
9
|
} from "../continuous/common.js";
|
|
10
|
+
import { resolveLegendItemLayout } from "../../../../layout/legendItems.js";
|
|
11
|
+
import { createPointShapeGraphic } from "../../../../grammar/pointShapes.js";
|
|
12
|
+
import { resolveConcreteGraphicBounds } from "../../../../grammar/schemas/graphicBounds.js";
|
|
13
|
+
import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
|
|
14
|
+
import { resolveDisplayLabel } from "../../../../grammar/displayLabels.js";
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "../../../../layout/legend.js";
|
|
16
|
+
materializeRectItem,
|
|
17
|
+
requestedRectStyleDetails
|
|
18
|
+
} from "../../../../grammar/roundedRect.js";
|
|
19
|
+
import { requestedStrokeDetails } from "../../../../grammar/strokeStyle.js";
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const CATEGORICAL_KINDS = Object.freeze(["series", "color", "stroke"]);
|
|
22
|
+
|
|
23
|
+
export function activeConfig(program, requested) {
|
|
24
|
+
if (requested !== undefined && !CATEGORICAL_KINDS.includes(requested)) {
|
|
25
|
+
throw new Error(`Unknown categorical legend kind "${requested}".`);
|
|
26
|
+
}
|
|
27
|
+
const kinds = CATEGORICAL_KINDS
|
|
24
28
|
.filter(kind => program.guideConfigs.legend?.[kind] !== undefined);
|
|
29
|
+
if (requested !== undefined) {
|
|
30
|
+
if (!kinds.includes(requested)) {
|
|
31
|
+
throw new Error(`Missing categorical legend config "${requested}".`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
kind: requested,
|
|
35
|
+
config: resolveEffectiveLegendBlockConfig(
|
|
36
|
+
program,
|
|
37
|
+
requested,
|
|
38
|
+
program.guideConfigs.legend[requested]
|
|
39
|
+
)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
25
42
|
if (kinds.length !== 1) {
|
|
26
43
|
throw new Error("Legend component requires one categorical legend config.");
|
|
27
44
|
}
|
|
28
45
|
const kind = kinds[0];
|
|
29
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
kind,
|
|
48
|
+
config: resolveEffectiveLegendBlockConfig(
|
|
49
|
+
program,
|
|
50
|
+
kind,
|
|
51
|
+
program.guideConfigs.legend[kind]
|
|
52
|
+
)
|
|
53
|
+
};
|
|
30
54
|
}
|
|
31
55
|
|
|
32
56
|
function prefix(config) {
|
|
33
|
-
return config.kind
|
|
57
|
+
return `${config.kind}Legend`;
|
|
34
58
|
}
|
|
35
59
|
|
|
36
60
|
export function symbolGraphic(config, type) {
|
|
37
61
|
const onlyDefault = config.symbol.layers.length === 1 &&
|
|
38
62
|
((config.kind === "series" && type === "line") ||
|
|
39
|
-
(config.kind
|
|
63
|
+
(["color", "stroke"].includes(config.kind) && type === "swatch"));
|
|
40
64
|
if (onlyDefault) return `${prefix(config)}Symbols`;
|
|
41
65
|
const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[type];
|
|
42
66
|
return `${prefix(config)}Symbol${suffix}`;
|
|
@@ -54,364 +78,109 @@ export function symbolWidth(config) {
|
|
|
54
78
|
}));
|
|
55
79
|
}
|
|
56
80
|
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
export function categoricalLegendLabels(config) {
|
|
82
|
+
return config.domain.map(value => resolveDisplayLabel(
|
|
83
|
+
value,
|
|
84
|
+
config.labelMap,
|
|
85
|
+
formatVisibleText
|
|
86
|
+
));
|
|
62
87
|
}
|
|
63
88
|
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const sizeLabelX = size?.values.map(() => start + 44);
|
|
123
|
-
const categoricalTop = config.titleVisible === false
|
|
124
|
-
? itemY[0] - Math.max(
|
|
125
|
-
config.labels.fontSize,
|
|
126
|
-
measureLegendSymbolHeight(config)
|
|
127
|
-
) / 2
|
|
128
|
-
: titleY - config.titleStyle.fontSize / 2;
|
|
129
|
-
const sizeBottom = size === undefined
|
|
130
|
-
? -Infinity
|
|
131
|
-
: sizeItemY.at(-1) + Math.max(size.maximumRadius, config.labels.fontSize / 2);
|
|
132
|
-
const categoricalBottom = itemY.at(-1) +
|
|
133
|
-
Math.max(config.labels.fontSize, measureLegendSymbolHeight(config)) / 2;
|
|
134
|
-
const blockTop = Math.min(categoricalTop, sizeTitleY - config.titleStyle.fontSize / 2);
|
|
135
|
-
const blockBottom = Math.max(categoricalBottom, sizeBottom);
|
|
136
|
-
if (blockTop < 0 || blockBottom > canvas.height) {
|
|
137
|
-
throw new Error("Legend layout requires more vertical Canvas space.");
|
|
138
|
-
}
|
|
139
|
-
let background;
|
|
140
|
-
if (config.border !== false) {
|
|
141
|
-
const backgroundTop = Math.floor(
|
|
142
|
-
blockTop - config.border.padding - config.border.lineWidth
|
|
143
|
-
);
|
|
144
|
-
const backgroundBottom = Math.ceil(blockBottom + config.border.padding);
|
|
145
|
-
background = {
|
|
146
|
-
x: start - config.border.padding,
|
|
147
|
-
y: backgroundTop,
|
|
148
|
-
width: occupiedRight - (start - config.border.padding),
|
|
149
|
-
height: backgroundBottom - backgroundTop
|
|
150
|
-
};
|
|
151
|
-
if (
|
|
152
|
-
background.x < 0 || background.y < 0 ||
|
|
153
|
-
background.x + background.width > canvas.width ||
|
|
154
|
-
background.y + background.height > canvas.height
|
|
155
|
-
) {
|
|
156
|
-
throw new Error("Legend background requires more left-margin space.");
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
symbolX,
|
|
161
|
-
itemY,
|
|
162
|
-
labelX,
|
|
163
|
-
titleX,
|
|
164
|
-
titleY,
|
|
165
|
-
background,
|
|
166
|
-
blockTop,
|
|
167
|
-
blockBottom,
|
|
168
|
-
size: size === undefined ? undefined : {
|
|
169
|
-
titleX: start,
|
|
170
|
-
titleY: sizeTitleY,
|
|
171
|
-
itemY: sizeItemY,
|
|
172
|
-
symbolX: sizeSymbolX,
|
|
173
|
-
labelX: sizeLabelX
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function resolveGridLayout(program, bounds, canvas, config, width, count, top) {
|
|
179
|
-
const { cells, columnWidths, gridWidth, gridHeight, rowHeight } =
|
|
180
|
-
resolveLegendGrid(config, width, count);
|
|
181
|
-
const inlineTitle = config.titlePosition === "left";
|
|
182
|
-
const titleWidth = config.titleVisible === false
|
|
183
|
-
? 0
|
|
184
|
-
: measureLegendTextWidth(config.title, config.titleStyle);
|
|
185
|
-
const titleGap = inlineTitle ? 20 : 12;
|
|
186
|
-
const totalWidth = inlineTitle
|
|
187
|
-
? titleWidth + titleGap + gridWidth
|
|
188
|
-
: Math.max(titleWidth, gridWidth);
|
|
189
|
-
const start = alignLegendStart(bounds, totalWidth, config.align);
|
|
190
|
-
if (start < 0 || start + totalWidth > canvas.width) {
|
|
191
|
-
throw new Error("Legend layout requires more horizontal Canvas space.");
|
|
192
|
-
}
|
|
193
|
-
const edge = top
|
|
194
|
-
? bounds.y - config.offset
|
|
195
|
-
: bounds.y + bounds.height + config.offset;
|
|
196
|
-
const gridStart = inlineTitle
|
|
197
|
-
? start + titleWidth + titleGap
|
|
198
|
-
: start + (totalWidth - gridWidth) / 2;
|
|
199
|
-
const gridTop = top
|
|
200
|
-
? edge - gridHeight
|
|
201
|
-
: inlineTitle ? edge : edge + config.titleStyle.fontSize + titleGap;
|
|
202
|
-
const blockTop = top
|
|
203
|
-
? inlineTitle
|
|
204
|
-
? Math.min(
|
|
205
|
-
gridTop,
|
|
206
|
-
edge / 2 + gridTop / 2 - config.titleStyle.fontSize / 2
|
|
207
|
-
)
|
|
208
|
-
: gridTop - titleGap - config.titleStyle.fontSize
|
|
209
|
-
: edge;
|
|
210
|
-
const blockBottom = top ? edge : gridTop + gridHeight;
|
|
211
|
-
if (top) {
|
|
212
|
-
if (blockTop < 0) {
|
|
213
|
-
throw new Error("Legend layout requires more top-margin space.");
|
|
214
|
-
}
|
|
215
|
-
const titleGraphic = program.graphicSpec.objects.chartSubtitle ??
|
|
216
|
-
program.graphicSpec.objects.chartTitle;
|
|
217
|
-
if (titleGraphic?.type === "text" &&
|
|
218
|
-
titleGraphic.properties.y + titleGraphic.properties.fontSize / 2 >= blockTop) {
|
|
219
|
-
throw new Error("Top legend and chart title require more top-margin space.");
|
|
220
|
-
}
|
|
221
|
-
} else {
|
|
222
|
-
const occupiedTop = config.border === false
|
|
223
|
-
? blockTop
|
|
224
|
-
: blockTop - config.border.padding;
|
|
225
|
-
const axisTitle = program.graphicSpec.objects.xAxisTitle;
|
|
226
|
-
if (axisTitle?.type === "text" &&
|
|
227
|
-
axisTitle.properties.y + axisTitle.properties.fontSize / 2 >= occupiedTop) {
|
|
228
|
-
throw new Error("Bottom legend and x-axis title require more bottom-margin space.");
|
|
229
|
-
}
|
|
230
|
-
const occupiedBottom = config.border === false
|
|
231
|
-
? blockBottom
|
|
232
|
-
: blockBottom + config.border.padding;
|
|
233
|
-
if (occupiedTop < 0 || occupiedBottom > canvas.height) {
|
|
234
|
-
throw new Error("Legend layout requires more bottom-margin space.");
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const columnX = [];
|
|
238
|
-
let cursor = gridStart;
|
|
239
|
-
for (const columnWidth of columnWidths) {
|
|
240
|
-
columnX.push(cursor);
|
|
241
|
-
cursor += columnWidth + config.itemGap;
|
|
242
|
-
}
|
|
243
|
-
const symbolX = cells.map(cell => columnX[cell.column]);
|
|
244
|
-
const labelX = symbolX.map(value => value + width + config.labels.offset);
|
|
245
|
-
const itemY = cells.map(cell =>
|
|
246
|
-
gridTop + rowHeight / 2 + cell.row * (rowHeight + config.itemGap)
|
|
247
|
-
);
|
|
248
|
-
const titleX = inlineTitle ? start : start + totalWidth / 2;
|
|
249
|
-
const titleY = inlineTitle
|
|
250
|
-
? gridTop + gridHeight / 2
|
|
251
|
-
: top
|
|
252
|
-
? gridTop - titleGap - config.titleStyle.fontSize / 2
|
|
253
|
-
: blockTop + config.titleStyle.fontSize / 2;
|
|
254
|
-
let background;
|
|
255
|
-
if (config.border !== false) {
|
|
256
|
-
background = {
|
|
257
|
-
x: start - config.border.padding,
|
|
258
|
-
y: blockTop - config.border.padding,
|
|
259
|
-
width: totalWidth + config.border.padding * 2,
|
|
260
|
-
height: blockBottom - blockTop + config.border.padding * 2
|
|
261
|
-
};
|
|
262
|
-
if (background.x < 0 || background.y < 0 ||
|
|
263
|
-
background.x + background.width > canvas.width ||
|
|
264
|
-
(!top && background.y + background.height > canvas.height)) {
|
|
265
|
-
throw new Error(`Legend background requires more ${top ? "top" : "bottom"} or horizontal space.`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
symbolX,
|
|
270
|
-
itemY,
|
|
271
|
-
labelX,
|
|
272
|
-
titleX,
|
|
273
|
-
titleY,
|
|
274
|
-
background,
|
|
275
|
-
blockTop,
|
|
276
|
-
blockBottom
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function resolveCompactBottomLayout(bounds, canvas, config, width, count) {
|
|
281
|
-
const labels = config.domain.map(formatVisibleText);
|
|
282
|
-
const itemWidths = labels.map(
|
|
283
|
-
label => width + config.labels.offset +
|
|
284
|
-
measureLegendTextWidth(label, config.labels)
|
|
285
|
-
);
|
|
286
|
-
const totalWidth = itemWidths.reduce((sum, value) => sum + value, 0) +
|
|
287
|
-
config.itemGap * Math.max(0, count - 1);
|
|
288
|
-
const start = config.align === "center"
|
|
289
|
-
? (canvas.width - totalWidth) / 2
|
|
290
|
-
: alignLegendStart(bounds, totalWidth, config.align);
|
|
291
|
-
if (start < 0 || start + totalWidth > canvas.width) {
|
|
292
|
-
throw new Error("Legend layout requires more horizontal Canvas space.");
|
|
293
|
-
}
|
|
294
|
-
const symbolX = [];
|
|
295
|
-
const labelX = [];
|
|
296
|
-
let cursor = start;
|
|
297
|
-
for (let index = 0; index < count; index += 1) {
|
|
298
|
-
symbolX.push(cursor);
|
|
299
|
-
labelX.push(cursor + width + config.labels.offset);
|
|
300
|
-
cursor += itemWidths[index] + config.itemGap;
|
|
301
|
-
}
|
|
302
|
-
const itemY = Array(count).fill(canvas.height - 28);
|
|
303
|
-
const titleX = start + totalWidth / 2;
|
|
304
|
-
const titleY = canvas.height - 52;
|
|
305
|
-
if (titleY <= bounds.y + bounds.height) {
|
|
306
|
-
throw new Error("Legend layout requires more bottom-margin space.");
|
|
307
|
-
}
|
|
308
|
-
let background;
|
|
309
|
-
if (config.border !== false) {
|
|
310
|
-
const maxHeight = Math.max(
|
|
311
|
-
config.labels.fontSize,
|
|
312
|
-
measureLegendSymbolHeight(config)
|
|
313
|
-
);
|
|
314
|
-
const x = start - config.border.padding;
|
|
315
|
-
const y = titleY - config.titleStyle.fontSize / 2 - config.border.padding;
|
|
316
|
-
background = {
|
|
317
|
-
x,
|
|
318
|
-
y,
|
|
319
|
-
width: totalWidth + config.border.padding * 2,
|
|
320
|
-
height: itemY[0] + maxHeight / 2 + config.border.padding - y
|
|
321
|
-
};
|
|
322
|
-
if (
|
|
323
|
-
background.x < 0 || background.y < 0 ||
|
|
324
|
-
background.x + background.width > canvas.width ||
|
|
325
|
-
background.y + background.height > canvas.height
|
|
326
|
-
) {
|
|
327
|
-
throw new Error("Legend background requires more bottom or horizontal space.");
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return { symbolX, itemY, labelX, titleX, titleY, background };
|
|
89
|
+
function resolveSampleBounds(program, config, width) {
|
|
90
|
+
const appearance = resolveAppearance(program, config);
|
|
91
|
+
return config.domain.map((_, index) => {
|
|
92
|
+
const items = config.symbol.layers.map(layer => {
|
|
93
|
+
if (layer.type === "line") {
|
|
94
|
+
const x = (width - layer.length) / 2;
|
|
95
|
+
return { type: "line", properties: { x1: x, x2: x + layer.length, y1: 0, y2: 0,
|
|
96
|
+
stroke: layer.stroke ?? (config.channels.includes("stroke")
|
|
97
|
+
? appearance.strokes[index]
|
|
98
|
+
: appearance.colors[index]),
|
|
99
|
+
strokeWidth: layer.lineWidth, opacity: layer.opacity,
|
|
100
|
+
...requestedStrokeDetails(layer, "Legend line symbol") } };
|
|
101
|
+
}
|
|
102
|
+
if (layer.type === "swatch") {
|
|
103
|
+
const details = requestedRectStyleDetails(layer, "Legend swatch symbol");
|
|
104
|
+
return materializeRectItem({ x: (width - layer.width) / 2, y: -layer.height / 2,
|
|
105
|
+
width: layer.width, height: layer.height, strokeWidth: layer.strokeWidth,
|
|
106
|
+
stroke: config.channels.includes("stroke")
|
|
107
|
+
? appearance.strokes[index]
|
|
108
|
+
: layer.stroke,
|
|
109
|
+
opacity: layer.opacity,
|
|
110
|
+
...requestedStrokeDetails(details, "Legend swatch symbol")
|
|
111
|
+
}, details.cornerRadius ?? 0);
|
|
112
|
+
}
|
|
113
|
+
if (config.channels.includes("shape")) {
|
|
114
|
+
return createPointShapeGraphic({ shape: appearance.shapes[index], x: width / 2, y: 0,
|
|
115
|
+
area: Math.PI * layer.size ** 2, fill: layer.fill ?? appearance.colors[index],
|
|
116
|
+
stroke: layer.stroke, strokeWidth: layer.strokeWidth, opacity: layer.opacity,
|
|
117
|
+
...requestedStrokeDetails(layer, "Legend point symbol") });
|
|
118
|
+
}
|
|
119
|
+
return { type: "circle", properties: { x: width / 2, y: 0, radius: layer.size,
|
|
120
|
+
stroke: config.channels.includes("stroke")
|
|
121
|
+
? appearance.strokes[index]
|
|
122
|
+
: layer.stroke,
|
|
123
|
+
strokeWidth: layer.strokeWidth, opacity: layer.opacity,
|
|
124
|
+
...requestedStrokeDetails(layer, "Legend point symbol") } };
|
|
125
|
+
});
|
|
126
|
+
return items.map((item, itemIndex) => {
|
|
127
|
+
const bounds = resolveConcreteGraphicBounds({
|
|
128
|
+
objects: { sample: item },
|
|
129
|
+
order: ["sample"]
|
|
130
|
+
}, "sample");
|
|
131
|
+
if (config.symbol.layers[itemIndex].type !== "line") return bounds;
|
|
132
|
+
const extent = config.symbol.layers[itemIndex].lineWidth / 2;
|
|
133
|
+
return {
|
|
134
|
+
...bounds,
|
|
135
|
+
left: bounds.left - extent,
|
|
136
|
+
right: bounds.right + extent
|
|
137
|
+
};
|
|
138
|
+
}).reduce((result, bounds) => result === undefined
|
|
139
|
+
? bounds
|
|
140
|
+
: {
|
|
141
|
+
left: Math.min(result.left, bounds.left),
|
|
142
|
+
right: Math.max(result.right, bounds.right),
|
|
143
|
+
top: Math.min(result.top, bounds.top),
|
|
144
|
+
bottom: Math.max(result.bottom, bounds.bottom)
|
|
145
|
+
}, undefined);
|
|
146
|
+
});
|
|
331
147
|
}
|
|
332
148
|
|
|
333
149
|
export function resolveLayout(program, config) {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
"Legend layout requires Canvas bounds, width, and height."
|
|
337
|
-
);
|
|
338
|
-
const bounds = resolved.plot;
|
|
339
|
-
const canvas = resolved.canvas;
|
|
340
|
-
|
|
341
|
-
const count = config.domain.length;
|
|
150
|
+
const { plot, canvas } = resolveContinuousBounds(program,
|
|
151
|
+
"Legend layout requires Canvas bounds, width, and height.");
|
|
342
152
|
const width = symbolWidth(config);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
);
|
|
351
|
-
const labelX = symbolX.map(value => value + width + config.labels.offset);
|
|
352
|
-
const titleX = symbolX[0];
|
|
353
|
-
const titleY = bounds.y + 20;
|
|
354
|
-
const labelBounds = config.domain.map((value, index) => textBounds(
|
|
355
|
-
labelX[index], itemY[index], value, config.labels
|
|
356
|
-
));
|
|
357
|
-
const titleBounds = config.titleVisible === false
|
|
358
|
-
? undefined
|
|
359
|
-
: textBounds(titleX, titleY, config.title, config.titleStyle);
|
|
360
|
-
const symbolHeight = measureLegendSymbolHeight(config);
|
|
361
|
-
const symbolBounds = itemY.map(y => ({
|
|
362
|
-
left: symbolX[0],
|
|
363
|
-
right: symbolX[0] + width,
|
|
364
|
-
top: y - symbolHeight / 2,
|
|
365
|
-
bottom: y + symbolHeight / 2
|
|
366
|
-
}));
|
|
367
|
-
const contentBounds = [
|
|
368
|
-
...labelBounds,
|
|
369
|
-
...symbolBounds,
|
|
370
|
-
...(titleBounds === undefined ? [] : [titleBounds])
|
|
371
|
-
];
|
|
372
|
-
assertLegendBoundsInsideCanvas(
|
|
373
|
-
contentBounds,
|
|
374
|
-
canvas,
|
|
375
|
-
"Right legend layout"
|
|
376
|
-
);
|
|
377
|
-
const background = resolveLegendBackgroundFromBounds(
|
|
378
|
-
contentBounds,
|
|
379
|
-
config.border,
|
|
380
|
-
canvas,
|
|
381
|
-
"Right legend"
|
|
382
|
-
);
|
|
383
|
-
return { symbolX, itemY, labelX, titleX, titleY, background };
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (config.position === "left") {
|
|
387
|
-
return resolveLeftLayout(program, bounds, canvas, config, width, count);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (config.position === "top") {
|
|
391
|
-
return resolveGridLayout(program, bounds, canvas, config, width, count, true);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (config.bottomGrid !== true) {
|
|
395
|
-
return resolveCompactBottomLayout(bounds, canvas, config, width, count);
|
|
396
|
-
}
|
|
397
|
-
return resolveGridLayout(program, bounds, canvas, config, width, count, false);
|
|
153
|
+
const layout = resolveLegendItemLayout(plot, config, categoricalLegendLabels(config), {
|
|
154
|
+
width, height: 0, itemBounds: resolveSampleBounds(program, config, width)
|
|
155
|
+
}, canvas);
|
|
156
|
+
assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Categorical legend layout", config);
|
|
157
|
+
const background = resolveLegendBackgroundFromBounds(layout.bounds, config.border, canvas,
|
|
158
|
+
"Categorical legend", config);
|
|
159
|
+
return { ...layout, titleX: layout.title.x, titleY: layout.title.y, background };
|
|
398
160
|
}
|
|
399
161
|
|
|
400
162
|
export function resolveAppearance(program, config) {
|
|
401
163
|
let colors = config.domain.map(() => DEFAULT_COLORS.mark);
|
|
164
|
+
let strokes = config.domain.map(() => "white");
|
|
402
165
|
let dashes = config.domain.map(() => []);
|
|
403
166
|
let shapes = config.domain.map(() => "circle");
|
|
404
167
|
for (let index = 0; index < config.channels.length; index += 1) {
|
|
405
168
|
const scale = program.resolvedScales[config.scales[index]];
|
|
406
169
|
const values = mapOrdinalValues(config.domain, scale.domain, scale.range);
|
|
407
170
|
if (config.channels[index] === "color") colors = values;
|
|
171
|
+
if (config.channels[index] === "stroke") strokes = values;
|
|
408
172
|
if (config.channels[index] === "strokeDash") dashes = values;
|
|
409
173
|
if (config.channels[index] === "shape") shapes = values;
|
|
410
174
|
}
|
|
411
|
-
return { colors, dashes, shapes };
|
|
175
|
+
return { colors, strokes, dashes, shapes };
|
|
412
176
|
}
|
|
413
177
|
|
|
414
|
-
export
|
|
178
|
+
export function noOptions(args, operation) {
|
|
179
|
+
validateOptionObject(args, ["kind"], operation);
|
|
180
|
+
if (args.kind !== undefined && !CATEGORICAL_KINDS.includes(args.kind)) {
|
|
181
|
+
throw new Error(`Unknown categorical legend kind "${args.kind}".`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
415
184
|
|
|
416
185
|
export function layerFor(config, type) {
|
|
417
186
|
const layer = config.symbol.layers.find(item => item.type === type);
|
|
@@ -16,7 +16,9 @@ export const CHANNELS = CATEGORICAL_LEGEND_CHANNELS;
|
|
|
16
16
|
const OPTIONS = Object.freeze([
|
|
17
17
|
"target",
|
|
18
18
|
"channels",
|
|
19
|
+
"order",
|
|
19
20
|
"position",
|
|
21
|
+
"layout",
|
|
20
22
|
"align",
|
|
21
23
|
"direction",
|
|
22
24
|
"columns",
|
|
@@ -34,7 +36,8 @@ const TEXT_OPTIONS = Object.freeze([
|
|
|
34
36
|
"color",
|
|
35
37
|
"fontSize",
|
|
36
38
|
"fontFamily",
|
|
37
|
-
"fontWeight"
|
|
39
|
+
"fontWeight",
|
|
40
|
+
"format"
|
|
38
41
|
]);
|
|
39
42
|
const TITLE_OPTIONS = Object.freeze([
|
|
40
43
|
"color",
|
|
@@ -85,10 +88,13 @@ function normalizeBorder(border) {
|
|
|
85
88
|
return normalized;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
export function normalizeOptions(args, kind) {
|
|
91
|
+
export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
89
92
|
validateOptionObject(args, OPTIONS, "createLegend");
|
|
90
93
|
if (Object.hasOwn(args, "labels")) {
|
|
91
94
|
validateObject(args.labels, TEXT_OPTIONS, "createLegend.labels");
|
|
95
|
+
if (args.labels.format !== undefined && args.labels.format !== "auto") {
|
|
96
|
+
throw new Error("Categorical legend labels do not accept format.");
|
|
97
|
+
}
|
|
92
98
|
}
|
|
93
99
|
if (Object.hasOwn(args, "titleStyle")) {
|
|
94
100
|
validateObject(
|
|
@@ -108,26 +114,33 @@ export function normalizeOptions(args, kind) {
|
|
|
108
114
|
offset: defaults.offset,
|
|
109
115
|
...(args.labels ?? {})
|
|
110
116
|
};
|
|
117
|
+
delete labels.format;
|
|
111
118
|
const titleStyle = {
|
|
112
119
|
...COMMON_DEFAULTS.titleStyle,
|
|
113
120
|
...(args.titleStyle ?? {})
|
|
114
121
|
};
|
|
115
122
|
const position = args.position ?? defaults.position;
|
|
116
123
|
const align = args.align ?? defaults.align;
|
|
117
|
-
const
|
|
124
|
+
const side = ["left", "right"].includes(position);
|
|
125
|
+
const direction = args.direction ?? (side ? "vertical" : "horizontal");
|
|
118
126
|
const columns = args.columns;
|
|
119
127
|
const offset = args.offset ?? 8;
|
|
120
128
|
const titlePosition = args.titlePosition ?? "top";
|
|
121
129
|
const itemGap = args.itemGap ?? (["right", "left"].includes(position)
|
|
122
130
|
? 28
|
|
123
131
|
: position === "top" ? 24 : 20);
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
|
|
132
|
+
const layout = args.layout === undefined ? "edge" : args.layout;
|
|
133
|
+
if (!["edge", "legacy-bottom"].includes(layout)) {
|
|
134
|
+
throw new Error(`Unsupported legend layout "${layout}".`);
|
|
135
|
+
}
|
|
136
|
+
if (layout === "legacy-bottom" && position !== "bottom") {
|
|
137
|
+
throw new Error('Legend layout "legacy-bottom" requires position "bottom".');
|
|
138
|
+
}
|
|
139
|
+
if (layout === "legacy-bottom" && (
|
|
140
|
+
columns !== undefined || direction !== "horizontal" || titlePosition !== "top" || offset !== 8
|
|
141
|
+
)) {
|
|
142
|
+
throw new Error('Legend columns, vertical direction, left title and custom offset require layout "edge".');
|
|
143
|
+
}
|
|
131
144
|
|
|
132
145
|
if (!["right", "left", "bottom", "top"].includes(position)) {
|
|
133
146
|
throw new Error(`Unsupported legend position "${position}".`);
|
|
@@ -141,11 +154,14 @@ export function normalizeOptions(args, kind) {
|
|
|
141
154
|
if (!["horizontal", "vertical"].includes(direction)) {
|
|
142
155
|
throw new Error(`Unsupported legend direction "${direction}".`);
|
|
143
156
|
}
|
|
144
|
-
if (
|
|
145
|
-
throw new Error("
|
|
157
|
+
if (side && direction !== "vertical") {
|
|
158
|
+
throw new Error("Side legends require vertical direction.");
|
|
159
|
+
}
|
|
160
|
+
if (side && columns !== undefined && columns !== 1) {
|
|
161
|
+
throw new Error("Side legends require one column.");
|
|
146
162
|
}
|
|
147
|
-
if (
|
|
148
|
-
throw new Error("
|
|
163
|
+
if (side && titlePosition !== "top") {
|
|
164
|
+
throw new Error("Side legends require a top title.");
|
|
149
165
|
}
|
|
150
166
|
if (columns !== undefined && (!Number.isInteger(columns) || columns <= 0)) {
|
|
151
167
|
throw new RangeError("Legend columns must be a positive integer.");
|
|
@@ -175,11 +191,11 @@ export function normalizeOptions(args, kind) {
|
|
|
175
191
|
offset,
|
|
176
192
|
titlePosition,
|
|
177
193
|
title: args.title,
|
|
178
|
-
symbol: normalizeRecipe(args.symbol, kind),
|
|
194
|
+
symbol: normalizeRecipe(args.symbol, kind, { internal: internalSymbol }),
|
|
179
195
|
labels,
|
|
180
196
|
titleStyle,
|
|
181
197
|
itemGap,
|
|
182
|
-
|
|
198
|
+
layout,
|
|
183
199
|
border: normalizeBorder(args.border)
|
|
184
200
|
};
|
|
185
201
|
}
|