ggaction 0.0.17 → 0.0.19
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 +96 -0
- package/README.md +6 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -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 +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +20 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +8 -7
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/derived.js +11 -1
- package/src/actions/data/edit.js +3 -1
- package/src/actions/data/index.js +4 -0
- package/src/actions/data/regression.js +25 -16
- package/src/actions/data/regressionStatistics.js +73 -0
- package/src/actions/data/shared.js +8 -4
- package/src/actions/data/stack.js +3 -1
- package/src/actions/distributions/revision.js +24 -18
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +14 -8
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +26 -24
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +176 -247
- package/src/actions/facets/guides.js +2 -1
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axes.js +30 -11
- package/src/actions/guides/axes/axis.js +72 -10
- package/src/actions/guides/axes/components.js +13 -13
- package/src/actions/guides/axes/edit.js +36 -24
- package/src/actions/guides/axes/labels.js +155 -71
- package/src/actions/guides/axes/lines.js +27 -24
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +13 -8
- package/src/actions/guides/axes/tickGroups.js +11 -7
- package/src/actions/guides/axes/ticks.js +36 -24
- package/src/actions/guides/axes/titles.js +107 -69
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeAxes.js +33 -24
- package/src/actions/guides/facadeLegend.js +12 -4
- package/src/actions/guides/grids/grid.js +8 -2
- package/src/actions/guides/grids/resolve.js +102 -58
- package/src/actions/guides/legends/blocks.js +46 -20
- package/src/actions/guides/legends/categorical/actions.js +95 -19
- package/src/actions/guides/legends/categorical/components.js +15 -18
- package/src/actions/guides/legends/categorical/index.js +8 -0
- package/src/actions/guides/legends/categorical/layout.js +45 -20
- package/src/actions/guides/legends/categorical/options.js +39 -22
- package/src/actions/guides/legends/categorical/recipes.js +7 -3
- package/src/actions/guides/legends/categorical/resolve.js +11 -4
- package/src/actions/guides/legends/categorical/symbols.js +38 -18
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/colorSize.js +110 -0
- package/src/actions/guides/legends/combine.js +37 -0
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +73 -46
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +33 -21
- package/src/actions/guides/legends/creation.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +8 -7
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/lifecycle.js +2 -1
- package/src/actions/guides/legends/remove.js +9 -3
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +65 -47
- package/src/actions/guides/legends/strokeWidth.js +13 -29
- package/src/actions/guides/legends/target.js +2 -79
- package/src/actions/guides/legends/transition.js +44 -11
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/guides/tickValues.js +8 -5
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +29 -14
- package/src/actions/marks/bar/materialize.js +15 -7
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/point/materialize.js +5 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +12 -10
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/createGraphics.js +3 -2
- package/src/actions/primitives/editGraphics.js +15 -11
- package/src/actions/primitives/graphicProperties.js +1 -6
- package/src/actions/primitives/semanticAction.js +27 -1
- package/src/actions/primitives/semanticValidation/guide.js +11 -1
- package/src/actions/primitives/semanticValidation/layer.js +8 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +18 -6
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -5
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +12 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +8 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +126 -45
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +6 -3
- package/src/core/font.js +9 -2
- package/src/core/graphicProperties.js +1 -0
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +4 -0
- package/src/core/resourceReferences.js +5 -2
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +35 -0
- package/src/core/vocabulary.js +7 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/axisTextPlacement.js +1 -0
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/colorSizeLegend.js +39 -0
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +115 -26
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +13 -9
- package/src/grammar/displayLabels.js +36 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +58 -30
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/facets/legendIntent.js +65 -0
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/guideDash.js +1 -0
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendBlocks.js +1 -0
- package/src/grammar/legendBorder.js +9 -0
- package/src/grammar/legendClip.js +19 -0
- package/src/grammar/legendCombinations.js +45 -0
- package/src/grammar/legendGradient.js +27 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendOpacity.js +6 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/pointShapes.js +16 -2
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +16 -2
- package/src/grammar/regression/models.js +142 -54
- package/src/grammar/regression/parameters.js +23 -4
- package/src/grammar/regression/statistics.js +64 -0
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +14 -0
- package/src/grammar/schemas/graphic.js +6 -0
- package/src/grammar/schemas/graphicBounds.js +31 -7
- package/src/grammar/schemas/semanticPath.js +19 -3
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +4 -27
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +22 -11
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +15 -2
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/guideCollisions.js +9 -1
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/compositionSnapshot.js +2 -7
- package/src/materialization/coordinateBounds.js +21 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/edit.js +30 -0
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/owner.js +125 -0
- package/src/materialization/facetGuides/ownerLayout.js +71 -0
- package/src/materialization/facetGuides/placement.js +71 -14
- package/src/materialization/facetGuides/preparation.js +22 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +5 -1
- package/src/materialization/graphicTranslation.js +73 -0
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendBorder.js +28 -0
- package/src/materialization/legendClip.js +76 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +2 -2
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +14 -15
- package/src/materialization/scales/map.js +6 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +7 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/materialization/typography.js +10 -2
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +220 -0
- package/src/persistence.js +19 -6
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +22 -11
- package/src/renderers/canvas/isolated.js +166 -0
- package/src/renderers/canvas/limits.js +3 -0
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/pdf.js +2 -2
- package/src/renderers/png.js +2 -2
- package/src/renderers/svg.js +14 -4
- package/src/selectors/axes.js +1 -0
- package/src/selectors/graphicCapacity.js +38 -0
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +3 -0
- package/types/index.d.ts +12 -0
- package/types/program.d.ts +536 -129
|
@@ -1,447 +1 @@
|
|
|
1
|
-
import { isPlainObject } from "../../../../core/immutable.js";
|
|
2
|
-
import { resolveTextBounds } from "../../../../core/textMetrics.js";
|
|
3
|
-
import {
|
|
4
|
-
validateKeys,
|
|
5
|
-
validateOptionObject,
|
|
6
|
-
validateNonEmptyString,
|
|
7
|
-
validateNonNegativeFinite,
|
|
8
|
-
validatePositiveFinite,
|
|
9
|
-
validateGeneratedItemLimit
|
|
10
|
-
} from "../../../../core/validation.js";
|
|
11
|
-
import { formatTimeTick } from "../../../../grammar/ticks.js";
|
|
12
|
-
import {
|
|
13
|
-
formatValue,
|
|
14
|
-
validateValueFormat
|
|
15
|
-
} from "../../../../grammar/valueFormat.js";
|
|
16
|
-
import {
|
|
17
|
-
formatDistinctNumericSamples,
|
|
18
|
-
sampleNumericRange
|
|
19
|
-
} from "../../../../grammar/numeric.js";
|
|
20
|
-
import { resolveGraphicBounds } from "../../../../layout/canvas.js";
|
|
21
|
-
import { isHorizontalEdgeLegend } from "../../../../layout/legendLane.js";
|
|
22
|
-
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
23
|
-
"../../../../theme/defaults.js";
|
|
24
|
-
import { findLayer } from "../../../../selectors/layers.js";
|
|
25
|
-
import { isOpacityLegendLayer } from "../../../../materialization/legends.js";
|
|
26
|
-
import { findCanvasGraphic } from
|
|
27
|
-
"../../../../materialization/graphicHierarchy.js";
|
|
28
|
-
import { normalizeLegendSampling } from "../sampling.js";
|
|
29
|
-
import { editGraphicProperties } from "../../../primitives/graphicProperties.js";
|
|
30
|
-
|
|
31
|
-
const OPTIONS = [
|
|
32
|
-
"target", "channels", "position", "align", "offset", "title", "count",
|
|
33
|
-
"values",
|
|
34
|
-
"gradient", "symbol", "labels", "titleStyle", "itemGap", "border",
|
|
35
|
-
"direction", "columns", "titlePosition"
|
|
36
|
-
];
|
|
37
|
-
const TEXT_OPTIONS = [
|
|
38
|
-
"offset", "color", "fontSize", "fontFamily", "fontWeight", "format"
|
|
39
|
-
];
|
|
40
|
-
const BORDER_OPTIONS = [
|
|
41
|
-
"color", "lineWidth", "padding", "background"
|
|
42
|
-
];
|
|
43
|
-
const POSITIONS = ["right", "left", "top", "bottom"];
|
|
44
|
-
const DEFAULT_LABELS = {
|
|
45
|
-
offset: 12,
|
|
46
|
-
color: DEFAULT_COLORS.text,
|
|
47
|
-
fontSize: 12,
|
|
48
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
49
|
-
fontWeight: "normal"
|
|
50
|
-
};
|
|
51
|
-
const DEFAULT_TITLE = {
|
|
52
|
-
color: DEFAULT_COLORS.text,
|
|
53
|
-
fontSize: 13,
|
|
54
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
55
|
-
fontWeight: 600
|
|
56
|
-
};
|
|
57
|
-
const DEFAULT_BORDER = {
|
|
58
|
-
color: DEFAULT_COLORS.border,
|
|
59
|
-
lineWidth: 1,
|
|
60
|
-
padding: 12,
|
|
61
|
-
background: "transparent"
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const validatePositive = validatePositiveFinite;
|
|
65
|
-
export const validateNonNegative = validateNonNegativeFinite;
|
|
66
|
-
|
|
67
|
-
export { editGraphicProperties };
|
|
68
|
-
|
|
69
|
-
export function normalizeLegendTextOptions(value, label, defaults) {
|
|
70
|
-
if (value === undefined) return { ...defaults };
|
|
71
|
-
if (!isPlainObject(value)) {
|
|
72
|
-
throw new TypeError(`${label} must be a plain object.`);
|
|
73
|
-
}
|
|
74
|
-
validateKeys(value, TEXT_OPTIONS, label);
|
|
75
|
-
const result = { ...defaults, ...value };
|
|
76
|
-
if (Object.hasOwn(result, "offset")) {
|
|
77
|
-
validateNonNegative(result.offset, `${label} offset`);
|
|
78
|
-
}
|
|
79
|
-
validatePositive(result.fontSize, `${label} fontSize`);
|
|
80
|
-
for (const key of ["color", "fontFamily"]) {
|
|
81
|
-
validateNonEmptyString(result[key], `${label} ${key}`);
|
|
82
|
-
}
|
|
83
|
-
if (
|
|
84
|
-
typeof result.fontWeight !== "string" &&
|
|
85
|
-
!Number.isFinite(result.fontWeight)
|
|
86
|
-
) {
|
|
87
|
-
throw new TypeError(`${label} fontWeight must be a string or finite number.`);
|
|
88
|
-
}
|
|
89
|
-
if (Object.hasOwn(result, "format")) {
|
|
90
|
-
result.format = validateValueFormat(result.format, `${label} format`);
|
|
91
|
-
}
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function normalizeLegendTitleOptions(value, label, defaults) {
|
|
96
|
-
if (value !== undefined) {
|
|
97
|
-
validateOptionObject(value, ["color", "fontSize", "fontFamily", "fontWeight"], label);
|
|
98
|
-
}
|
|
99
|
-
return normalizeLegendTextOptions(value, label, defaults);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function normalizeLegendBorder(value) {
|
|
103
|
-
if (value === undefined || value === false) return false;
|
|
104
|
-
if (value !== true && !isPlainObject(value)) {
|
|
105
|
-
throw new TypeError("createLegend.border must be a boolean or plain object.");
|
|
106
|
-
}
|
|
107
|
-
if (value !== true) validateKeys(value, BORDER_OPTIONS, "createLegend.border");
|
|
108
|
-
const border = { ...DEFAULT_BORDER, ...(value === true ? {} : value) };
|
|
109
|
-
for (const key of ["color", "background"]) {
|
|
110
|
-
validateNonEmptyString(border[key], `Legend border ${key}`);
|
|
111
|
-
}
|
|
112
|
-
validateNonNegative(border.lineWidth, "Legend border lineWidth");
|
|
113
|
-
validateNonNegative(border.padding, "Legend border padding");
|
|
114
|
-
return border;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function normalizeContinuousLegend(args, kind) {
|
|
118
|
-
validateOptionObject(args, OPTIONS, "createLegend");
|
|
119
|
-
const position = args.position ?? "right";
|
|
120
|
-
if (!POSITIONS.includes(position)) {
|
|
121
|
-
throw new Error(`Unsupported legend position "${position}".`);
|
|
122
|
-
}
|
|
123
|
-
const align = args.align ?? "center";
|
|
124
|
-
if (!["left", "center", "right"].includes(align)) {
|
|
125
|
-
throw new Error(`Unsupported legend alignment "${align}".`);
|
|
126
|
-
}
|
|
127
|
-
if (["left", "right"].includes(position) && align !== "center") {
|
|
128
|
-
throw new Error("Side continuous legends require center alignment.");
|
|
129
|
-
}
|
|
130
|
-
if (kind === "gradient" && Object.hasOwn(args, "values")) {
|
|
131
|
-
throw new Error("Gradient legends do not support exact sample values.");
|
|
132
|
-
}
|
|
133
|
-
const sampling = kind === "opacity"
|
|
134
|
-
? normalizeLegendSampling(args, {
|
|
135
|
-
operation: "create",
|
|
136
|
-
label: "Opacity legend"
|
|
137
|
-
})
|
|
138
|
-
: undefined;
|
|
139
|
-
const count = kind === "gradient" ? args.count ?? 5 : undefined;
|
|
140
|
-
if (kind === "gradient") {
|
|
141
|
-
if (!Number.isInteger(count) || count < 2) {
|
|
142
|
-
throw new RangeError(
|
|
143
|
-
"Continuous legend count must be an integer of at least 2."
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
validateGeneratedItemLimit(count, "Continuous legend count");
|
|
147
|
-
}
|
|
148
|
-
const offset = args.offset ?? 30;
|
|
149
|
-
validateNonNegative(offset, "Legend offset");
|
|
150
|
-
const titlePosition = args.titlePosition ?? "top";
|
|
151
|
-
if (!["top", "left"].includes(titlePosition)) {
|
|
152
|
-
throw new Error(`Unsupported legend titlePosition "${titlePosition}".`);
|
|
153
|
-
}
|
|
154
|
-
if (
|
|
155
|
-
titlePosition === "left" &&
|
|
156
|
-
(!["top", "bottom"].includes(position) || kind !== "opacity")
|
|
157
|
-
) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
"Only horizontal opacity legends support left titlePosition."
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
const itemGap = args.itemGap ?? (titlePosition === "left" ? 20 : 28);
|
|
163
|
-
validatePositive(itemGap, "Legend itemGap");
|
|
164
|
-
if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
|
|
165
|
-
if (kind === "gradient") {
|
|
166
|
-
for (const key of ["symbol", "columns", "direction", "itemGap"]) {
|
|
167
|
-
if (Object.hasOwn(args, key)) {
|
|
168
|
-
throw new Error(`Gradient legend does not accept ${key}.`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
for (const key of ["columns", "direction", "gradient"]) {
|
|
173
|
-
if (Object.hasOwn(args, key)) {
|
|
174
|
-
throw new Error(`Opacity legend does not accept ${key}.`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return {
|
|
179
|
-
target: args.target,
|
|
180
|
-
position,
|
|
181
|
-
align,
|
|
182
|
-
offset,
|
|
183
|
-
...(kind === "gradient" ? { count } : { sampling }),
|
|
184
|
-
title: args.title,
|
|
185
|
-
inferredTitle: args.title === undefined,
|
|
186
|
-
labels: normalizeLegendTextOptions(
|
|
187
|
-
args.labels,
|
|
188
|
-
"createLegend.labels",
|
|
189
|
-
titlePosition === "left"
|
|
190
|
-
? { ...DEFAULT_LABELS, offset: 8 }
|
|
191
|
-
: DEFAULT_LABELS
|
|
192
|
-
),
|
|
193
|
-
titleStyle: normalizeLegendTitleOptions(
|
|
194
|
-
args.titleStyle,
|
|
195
|
-
"createLegend.titleStyle",
|
|
196
|
-
DEFAULT_TITLE
|
|
197
|
-
),
|
|
198
|
-
itemGap,
|
|
199
|
-
titlePosition,
|
|
200
|
-
border: normalizeLegendBorder(args.border)
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function selectLegendLayer(program, requested, predicate) {
|
|
205
|
-
const candidates = program.semanticSpec.layers.filter(predicate);
|
|
206
|
-
if (requested === undefined) return candidates.length === 1
|
|
207
|
-
? candidates[0]
|
|
208
|
-
: undefined;
|
|
209
|
-
const candidate = findLayer(program, requested);
|
|
210
|
-
return candidates.includes(candidate) ? candidate : undefined;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export function resolveContinuousLegendLayer(program, requested, channel) {
|
|
214
|
-
const layer = selectLegendLayer(
|
|
215
|
-
program,
|
|
216
|
-
requested,
|
|
217
|
-
channel === "opacity" ? isOpacityLegendLayer : candidate =>
|
|
218
|
-
candidate.mark?.type === "point" && candidate.encoding?.[channel]?.scale !== undefined
|
|
219
|
-
);
|
|
220
|
-
if (layer === undefined) {
|
|
221
|
-
throw new Error(
|
|
222
|
-
requested === undefined
|
|
223
|
-
? `${channel} legend requires one eligible ${channel === "opacity" ? "point or line" : "point"} mark.`
|
|
224
|
-
: `Unknown ${channel} legend target "${requested}".`
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
return layer;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export function resolveContinuousColorLayer(program, requested, channel = "color") {
|
|
231
|
-
const layer = selectLegendLayer(
|
|
232
|
-
program,
|
|
233
|
-
requested,
|
|
234
|
-
candidate => (channel === "stroke"
|
|
235
|
-
? ["point", "line", "area", "bar", "rect", "arc", "rule", "tick"]
|
|
236
|
-
: ["point", "bar", "rect"]
|
|
237
|
-
).includes(candidate.mark?.type) &&
|
|
238
|
-
candidate.encoding?.[channel]?.scale !== undefined
|
|
239
|
-
);
|
|
240
|
-
if (layer === undefined) {
|
|
241
|
-
throw new Error(
|
|
242
|
-
requested === undefined
|
|
243
|
-
? channel === "color"
|
|
244
|
-
? "color legend requires one eligible point, bar, or rect mark."
|
|
245
|
-
: "stroke legend requires one eligible mark."
|
|
246
|
-
: `Unknown ${channel} legend target "${requested}".`
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
return layer;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function requireResolvedLegendScale(program, id, type) {
|
|
253
|
-
const scale = program.resolvedScales[id];
|
|
254
|
-
if (scale?.type !== type) {
|
|
255
|
-
throw new Error(`Legend requires resolved ${type} scale "${id}".`);
|
|
256
|
-
}
|
|
257
|
-
return scale;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export function resolveContinuousBounds(
|
|
261
|
-
program,
|
|
262
|
-
message = "Continuous legend layout requires Canvas bounds."
|
|
263
|
-
) {
|
|
264
|
-
const plot = resolveGraphicBounds(program);
|
|
265
|
-
const canvas = findCanvasGraphic(program);
|
|
266
|
-
if (
|
|
267
|
-
plot === undefined ||
|
|
268
|
-
![plot.x, plot.y, plot.width, plot.height].every(Number.isFinite) ||
|
|
269
|
-
canvas?.type !== "canvas" ||
|
|
270
|
-
!Number.isFinite(canvas.properties.width) ||
|
|
271
|
-
!Number.isFinite(canvas.properties.height)
|
|
272
|
-
) {
|
|
273
|
-
throw new Error(message);
|
|
274
|
-
}
|
|
275
|
-
return { plot, canvas: canvas.properties };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function sampleContinuousValues(domain, count) {
|
|
279
|
-
return sampleNumericRange(
|
|
280
|
-
domain[0],
|
|
281
|
-
domain[1],
|
|
282
|
-
count,
|
|
283
|
-
"Continuous legend domain"
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export function formatContinuousValues(values, domain, fieldType, format = "auto") {
|
|
288
|
-
const resolved = validateValueFormat(format, "Legend label format");
|
|
289
|
-
if (resolved === "auto") {
|
|
290
|
-
return fieldType === "temporal"
|
|
291
|
-
? values.map(value => formatTimeTick(value, domain))
|
|
292
|
-
: formatDistinctNumericSamples(values);
|
|
293
|
-
}
|
|
294
|
-
return values.map(value => formatValue(value, {
|
|
295
|
-
format: resolved,
|
|
296
|
-
valueType: fieldType === "temporal" ? "temporal" : "quantitative",
|
|
297
|
-
label: "Legend label format"
|
|
298
|
-
}));
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
export function styleContinuousText(
|
|
302
|
-
program,
|
|
303
|
-
id,
|
|
304
|
-
style,
|
|
305
|
-
{ align = "left" } = {}
|
|
306
|
-
) {
|
|
307
|
-
return editGraphicProperties(program, id, {
|
|
308
|
-
fill: style.color,
|
|
309
|
-
fontSize: style.fontSize,
|
|
310
|
-
fontFamily: style.fontFamily,
|
|
311
|
-
fontWeight: style.fontWeight,
|
|
312
|
-
textAlign: align,
|
|
313
|
-
textBaseline: "middle"
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function resolveLegendTextBounds(position, text, style, profile) {
|
|
318
|
-
return resolveTextBounds({
|
|
319
|
-
x: position.x,
|
|
320
|
-
y: position.y,
|
|
321
|
-
text: String(text),
|
|
322
|
-
...style,
|
|
323
|
-
textAlign: position.align ?? "left",
|
|
324
|
-
textBaseline: "middle"
|
|
325
|
-
}, profile);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export function assertLegendBoundsInsideCanvas(bounds, canvas, label, config) {
|
|
329
|
-
// Horizontal content is intrinsic until the shared lane places its actual
|
|
330
|
-
// concrete bounds. Only that final placement can determine Canvas fit.
|
|
331
|
-
if (isHorizontalEdgeLegend(config)) return;
|
|
332
|
-
if (bounds.some(item =>
|
|
333
|
-
item.left < 0 || item.right > canvas.width ||
|
|
334
|
-
item.top < 0 || item.bottom > canvas.height
|
|
335
|
-
)) {
|
|
336
|
-
throw new Error(`${label} requires more Canvas margin space.`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export function resolveLegendBackgroundFromBounds(
|
|
341
|
-
bounds,
|
|
342
|
-
border,
|
|
343
|
-
canvas,
|
|
344
|
-
label,
|
|
345
|
-
config
|
|
346
|
-
) {
|
|
347
|
-
if (border === false) return undefined;
|
|
348
|
-
const strokeExtent = border.lineWidth / 2;
|
|
349
|
-
let x = Infinity;
|
|
350
|
-
let y = Infinity;
|
|
351
|
-
let right = -Infinity;
|
|
352
|
-
let bottom = -Infinity;
|
|
353
|
-
for (const item of bounds) {
|
|
354
|
-
x = Math.min(x, item.left);
|
|
355
|
-
y = Math.min(y, item.top);
|
|
356
|
-
right = Math.max(right, item.right);
|
|
357
|
-
bottom = Math.max(bottom, item.bottom);
|
|
358
|
-
}
|
|
359
|
-
x -= border.padding;
|
|
360
|
-
y -= border.padding;
|
|
361
|
-
right += border.padding;
|
|
362
|
-
bottom += border.padding;
|
|
363
|
-
if (!isHorizontalEdgeLegend(config) && (
|
|
364
|
-
x - strokeExtent < 0 || y - strokeExtent < 0 ||
|
|
365
|
-
right + strokeExtent > canvas.width ||
|
|
366
|
-
bottom + strokeExtent > canvas.height
|
|
367
|
-
)) {
|
|
368
|
-
throw new Error(`${label} background requires more Canvas margin space.`);
|
|
369
|
-
}
|
|
370
|
-
return { x, y, width: right - x, height: bottom - y };
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export function editLegendBackground(program, id, bounds, border) {
|
|
374
|
-
if (bounds === undefined) return program;
|
|
375
|
-
return editGraphicProperties(program, id, {
|
|
376
|
-
...bounds,
|
|
377
|
-
fill: border.background,
|
|
378
|
-
stroke: border.color,
|
|
379
|
-
strokeWidth: border.lineWidth
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
export function normalizeItemLegendLayout(args) {
|
|
384
|
-
const position = args.position ?? "right";
|
|
385
|
-
if (!["right", "left", "top", "bottom"].includes(position)) throw new Error(`Unsupported legend position "${position}".`);
|
|
386
|
-
const side = ["left", "right"].includes(position);
|
|
387
|
-
const layout = args.layout === undefined ? "edge" : args.layout;
|
|
388
|
-
if (layout !== "edge") throw new Error('This legend requires layout "edge".');
|
|
389
|
-
const align = args.align ?? "center";
|
|
390
|
-
if (!["left", "center", "right"].includes(align)) throw new Error(`Unsupported legend alignment "${align}".`);
|
|
391
|
-
const direction = args.direction ?? (side ? "vertical" : "horizontal");
|
|
392
|
-
if (!["horizontal", "vertical"].includes(direction)) throw new Error(`Unsupported legend direction "${direction}".`);
|
|
393
|
-
const columns = args.columns;
|
|
394
|
-
if (columns !== undefined && (!Number.isInteger(columns) || columns < 1)) throw new RangeError("Legend columns must be a positive integer.");
|
|
395
|
-
const titlePosition = args.titlePosition ?? "top";
|
|
396
|
-
if (!["top", "left"].includes(titlePosition)) throw new Error(`Unsupported legend titlePosition "${titlePosition}".`);
|
|
397
|
-
if (side && (direction !== "vertical" || align !== "center" || (columns !== undefined && columns !== 1) || titlePosition !== "top")) {
|
|
398
|
-
throw new Error("Side item legends require vertical direction, center alignment, top title and one column.");
|
|
399
|
-
}
|
|
400
|
-
const offset = args.offset ?? 30;
|
|
401
|
-
const itemGap = args.itemGap ?? 28;
|
|
402
|
-
validateNonNegative(offset, "Legend offset");
|
|
403
|
-
validatePositive(itemGap, "Legend itemGap");
|
|
404
|
-
return { position, layout, align, direction, columns, titlePosition, offset, itemGap };
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
export function materializeItemLegend(program, kind, config, layout, {
|
|
408
|
-
symbols, optional = {}, text = layout.labels,
|
|
409
|
-
labels = layout.config.labels, titleStyle = layout.config.titleStyle
|
|
410
|
-
}) {
|
|
411
|
-
const prefix = `${kind}Legend`;
|
|
412
|
-
let next = program
|
|
413
|
-
.editSemantic({ property: `guide.legend.${kind}.scale`, value: config.scale })
|
|
414
|
-
.editSemantic({ property: `guide.legend.${kind}.title`, value: config.title })
|
|
415
|
-
._withLegendConfig(kind, config);
|
|
416
|
-
next = editGraphicProperties(next, `${prefix}Symbols`, {
|
|
417
|
-
length: text.length,
|
|
418
|
-
...symbols
|
|
419
|
-
});
|
|
420
|
-
next = editGraphicProperties(next, `${prefix}Labels`, {
|
|
421
|
-
length: text.length, x: layout.labelX, y: layout.itemY, text
|
|
422
|
-
});
|
|
423
|
-
next = editLegendBackground(next, `${prefix}Background`, layout.background, config.border);
|
|
424
|
-
next = editGraphicProperties(next, `${prefix}Symbols`, Object.fromEntries(
|
|
425
|
-
Object.entries(optional).filter(([, value]) => value !== undefined)
|
|
426
|
-
));
|
|
427
|
-
next = styleContinuousText(next, `${prefix}Labels`, labels);
|
|
428
|
-
const effective = layout.config;
|
|
429
|
-
if (effective.titleVisible === false) return next;
|
|
430
|
-
next = editGraphicProperties(next, `${prefix}Title`, {
|
|
431
|
-
x: layout.title.x, y: layout.title.y, text: effective.title
|
|
432
|
-
});
|
|
433
|
-
return styleContinuousText(next, `${prefix}Title`, titleStyle, { align: layout.title.align });
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
export function createItemLegendGraphics(program, kind, config, count, type, placement) {
|
|
437
|
-
const prefix = `${kind}Legend`;
|
|
438
|
-
let next = program
|
|
439
|
-
.editSemantic({ property: `guide.legend.${kind}.scale`, value: config.scale })
|
|
440
|
-
.editSemantic({ property: `guide.legend.${kind}.title`, value: config.title })
|
|
441
|
-
._withLegendConfig(kind, config);
|
|
442
|
-
if (config.border !== false) next = next.createGraphics({ id: `${prefix}Background`, type: "rect", ...placement });
|
|
443
|
-
next = next.createGraphics({ id: `${prefix}Symbols`, type, length: count, ...placement })
|
|
444
|
-
.createGraphics({ id: `${prefix}Labels`, type: "text", length: count, ...placement });
|
|
445
|
-
if (config.titleVisible !== false) next = next.createGraphics({ id: `${prefix}Title`, type: "text", ...placement });
|
|
446
|
-
return next;
|
|
447
|
-
}
|
|
1
|
+
import{normalizeLegendClipHeight as O}from"../../../../grammar/legendClip.js";import{applyLegendClipGraphics as $}from"../../../../materialization/legendClip.js";import{normalizeGradientDirection as H}from"../../../../grammar/legendGradient.js";import{normalizeLegendBorderDash as R}from"../../../../grammar/legendBorder.js";import{materializeLegendBorder as M}from"../../../../materialization/legendBorder.js";import{LEGEND_TITLE_STYLE_KEYS as q,mergeLegendTitleStyle as V,validateLegendTitleAlignment as v}from"../../../../grammar/legendTitleStyle.js";import{PLOT_LEGEND_POSITIONS as Y,isPlotLegend as m}from"../../../../layout/legend.js";import{isContinuousColorScaleType as K}from"../../../../grammar/scales/types.js";import{isPlainObject as I}from"../../../../core/immutable.js";import{resolveTextBounds as X}from"../../../../core/textMetrics.js";import{resolveLegendTitleText as J}from"../../../../layout/legendText.js";import{materializeLegendTitleLines as Q}from"../../../../materialization/legendText.js";import{validateKeys as G,validateFontWeight as Z,validateOptionObject as z,validateNonEmptyString as y,validateNonNegativeFinite as ee,validatePositiveFinite as te,validateGeneratedItemLimit as ne}from"../../../../core/validation.js";import{formatTimeTick as ie}from"../../../../grammar/ticks.js";import{formatValue as oe,validateValueFormat as C}from"../../../../grammar/valueFormat.js";import{formatDistinctNumericSamples as re,sampleNumericRange as le}from"../../../../grammar/numeric.js";import{resolveGraphicBounds as de,canvasOverflowError as _}from"../../../../layout/canvas.js";import{isHorizontalEdgeLegend as N}from"../../../../layout/legendLane.js";import{DEFAULT_COLORS as h,DEFAULT_FONT_FAMILY as F,DEFAULT_VERTICAL_LEGEND_ITEM_GAP as w}from"../../../../theme/defaults.js";import{findLayer as se}from"../../../../selectors/layers.js";import{isOpacityLegendLayer as ae}from"../../../../materialization/legends.js";import{findCanvasGraphic as ce}from"../../../../materialization/graphicHierarchy.js";import{normalizeLegendSampling as fe}from"../sampling.js";import{editGraphicProperties as p}from"../../../primitives/graphicProperties.js";const pe=["clipHeight","target","channels","position","align","offset","title","count","values","gradient","symbol","labels","titleStyle","itemGap","border","direction","columns","titlePosition"],Re=Object.freeze(["clipHeight","target","count","values","position","layout","align","direction","columns","titlePosition","offset","itemGap","title","labels","titleStyle","border","overflow"]),ue=["offset","color","fontSize","fontFamily","fontWeight","format"],me=["color","lineWidth","padding","background","strokeDash"],P=["right","left","top","bottom",...Y],E={offset:12,color:h.text,fontSize:12,fontFamily:F,fontWeight:"normal"},D={color:h.text,fontSize:13,fontFamily:F,fontWeight:600},ge=Object.freeze(E),he=Object.freeze({...D,color:h.strongText});function B(e,t){return e!==void 0&&e!==!1&&y(e,"Legend title"),{title:e===!1||e===void 0?t:e,inferredTitle:e===!1||e===void 0,titleVisible:e!==!1}}function Me(e,t,n){const i=["right","left"].includes(e.position??"right")||m(e),r=(e.direction??(i?"vertical":"horizontal"))==="vertical"?w:n;return{...e.clipHeight===void 0?{}:{clipHeight:O(e.clipHeight)},...ye({...e,itemGap:e.itemGap??r}),...B(e.title,t.field),labels:T(e.labels,"createLegend.labels",ge),titleStyle:A(e.titleStyle,"createLegend.titleStyle",he),border:j(e.border)}}const Le={color:h.border,lineWidth:1,padding:12,background:"transparent"},x=te,g=ee;function T(e,t,n){if(e===void 0)return{...n};if(!I(e))throw new TypeError(`${t} must be a plain object.`);G(e,ue,t);const i={...n,...e};Object.hasOwn(i,"offset")&&g(i.offset,`${t} offset`),x(i.fontSize,`${t} fontSize`);for(const o of["color","fontFamily"])y(i[o],`${t} ${o}`);return Z(i.fontWeight,`${t} fontWeight`,"finite number"),Object.hasOwn(i,"format")&&(i.format=C(i.format,`${t} format`)),i}function A(e,t,n,i=!1){e!==void 0&&z(e,q,t);const o=V(n,e,{edit:i}),{align:r,...d}=o;return{...T(d,t,{}),...r===void 0?{}:{align:r}}}function j(e){if(e===void 0||e===!1)return!1;if(e!==!0&&!I(e))throw new TypeError("createLegend.border must be a boolean or plain object.");e!==!0&&G(e,me,"createLegend.border");const t={...Le,...e===!0?{}:e};for(const i of["color","background"])y(t[i],`Legend border ${i}`);g(t.lineWidth,"Legend border lineWidth"),g(t.padding,"Legend border padding");const n=R(t.strokeDash);return{...t,...n===void 0?{}:{strokeDash:n}}}function qe(e,t){z(e,pe,"createLegend");const n=e.position??"right";if(!P.includes(n))throw new Error(`Unsupported legend position "${n}".`);const i=e.align??"center";if(!["left","center","right"].includes(i))throw new Error(`Unsupported legend alignment "${i}".`);if(["left","right"].includes(n)&&i!=="center")throw new Error("Side continuous legends require center alignment.");if(t==="gradient"&&Object.hasOwn(e,"values"))throw new Error("Gradient legends do not support exact sample values.");const o=t==="opacity"?fe(e,{operation:"create",label:"Opacity legend"}):void 0,r=t==="gradient"?e.count??5:void 0;if(t==="gradient"){if(!Number.isInteger(r)||r<2)throw new RangeError("Continuous legend count must be an integer of at least 2.");ne(r,"Continuous legend count")}const d=e.offset??(m({position:n})?8:30);g(d,"Legend offset");const l=e.titlePosition??"top";if(v(e.titleStyle,l),!(t==="gradient"?["top","left","bottom"]:["top","left"]).includes(l))throw new Error(`Unsupported legend titlePosition "${l}".`);if(l==="left"&&(!["top","bottom"].includes(n)||t!=="opacity"))throw new Error("Only horizontal opacity legends support left titlePosition.");const a=e.itemGap??(t==="opacity"&&(["right","left"].includes(n)||m({position:n}))?w:l==="left"?20:28);if(x(a,"Legend itemGap"),t==="gradient"){for(const s of["symbol","columns","itemGap"])if(Object.hasOwn(e,s))throw new Error(`Gradient legend does not accept ${s}.`)}else for(const s of["columns","direction","gradient"])if(Object.hasOwn(e,s))throw new Error(`Opacity legend does not accept ${s}.`);return{...e.clipHeight===void 0?{}:{clipHeight:O(e.clipHeight)},target:e.target,position:n,align:i,offset:d,...t==="gradient"?{count:r,...e.direction===void 0?{}:{gradientDirection:H(e.direction)}}:{sampling:o},...B(e.title),labels:T(e.labels,"createLegend.labels",l==="left"?{...E,offset:8}:E),titleStyle:A(e.titleStyle,"createLegend.titleStyle",D),itemGap:a,titlePosition:l,border:j(e.border)}}function U(e,t,n){const i=e.semanticSpec.layers.filter(n);if(t===void 0)return i.length===1?i[0]:void 0;const o=se(e,t);return i.includes(o)?o:void 0}function Ve(e,t,n){const i=U(e,t,n==="opacity"?ae:o=>o.mark?.type==="point"&&o.encoding?.[n]?.scale!==void 0);if(i===void 0)throw new Error(t===void 0?`${n} legend requires one eligible ${n==="opacity"?"point, line or bar":"point"} mark.`:`Unknown ${n} legend target "${t}".`);return i}function Ye(e,t,n="color"){const i=U(e,t,o=>(n==="stroke"?["point","line","area","bar","rect","arc","rule","tick"]:["point","bar","rect","text"]).includes(o.mark?.type)&&o.encoding?.[n]?.scale!==void 0);if(i===void 0)throw new Error(t===void 0?n==="color"?"color legend requires one eligible point, bar, or rect mark.":"stroke legend requires one eligible mark.":`Unknown ${n} legend target "${t}".`);return i}function Ke(e,t,n){const i=e.resolvedScales[t];if(n==="sequential"?!K(i?.type):i?.type!==n)throw new Error(`Legend requires resolved ${n} scale "${t}".`);return i}function Xe(e,t="Continuous legend layout requires Canvas bounds."){const n=de(e),i=ce(e);if(n===void 0||![n.x,n.y,n.width,n.height].every(Number.isFinite)||i?.type!=="canvas"||!Number.isFinite(i.properties.width)||!Number.isFinite(i.properties.height))throw new Error(t);return{plot:n,canvas:i.properties}}function Je(e,t){return le(e[0],e[1],t,"Continuous legend domain")}function Qe(e,t,n,i="auto"){const o=C(i,"Legend label format");return o==="auto"?n==="temporal"?e.map(r=>ie(r,t)):re(e):e.map(r=>oe(r,{format:o,valueType:n==="temporal"?"temporal":"quantitative",label:"Legend label format"}))}function k(e,t,n,{align:i="left"}={}){const o=p(e,t,{fill:n.color,fontSize:n.fontSize,fontFamily:n.fontFamily,fontWeight:n.fontWeight,textAlign:i,textBaseline:"middle"});return t.endsWith("Title")?Q(o,t,o.graphicSpec.objects[t].properties.text,n):o}function Ze(e,t,n,i,{hardBreaks:o=!1}={}){return X({x:e.x,y:e.y,text:String(t),...n,...o?{lines:J(t,n,i).lines}:{},textAlign:e.align??"left",textBaseline:"middle"},i)}function et(e,t,n,i){if(!(i?.groupIntrinsic||N(i)||["left","right"].includes(i?.position))&&e.some(o=>o.left<0||o.right>t.width||o.top<0||o.bottom>t.height))throw _(`${n} requires more Canvas margin space.`,e,t)}function tt(e,t,n,i,o){if(t===!1)return;const r=t.lineWidth/2;let d=1/0,l=1/0,a=-1/0,s=-1/0;for(const f of e)d=Math.min(d,f.left),l=Math.min(l,f.top),a=Math.max(a,f.right),s=Math.max(s,f.bottom);if(d-=t.padding,l-=t.padding,a+=t.padding,s+=t.padding,!o?.groupIntrinsic&&!N(o)&&!["left","right"].includes(o?.position)&&(d-r<0||l-r<0||a+r>n.width||s+r>n.height))throw _(`${i} background requires more Canvas margin space.`,[{left:d-r,right:a+r,top:l-r,bottom:s+r}],n);return{x:d,y:l,width:a-d,height:s-l}}function be(e,t,n,i){return n===void 0?e:M(e,t,n,i)}function ye(e,{multiColumnSide:t=!1}={}){const n=e.position??"right";if(!P.includes(n))throw new Error(`Unsupported legend position "${n}".`);const i=["left","right"].includes(n)||m({position:n}),o=e.layout===void 0?"edge":e.layout;if(o!=="edge")throw new Error('This legend requires layout "edge".');const r=e.align??"center";if(!["left","center","right"].includes(r))throw new Error(`Unsupported legend alignment "${r}".`);const d=e.direction??(i?"vertical":"horizontal");if(!["horizontal","vertical"].includes(d))throw new Error(`Unsupported legend direction "${d}".`);const l=e.columns;if(l!==void 0&&(!Number.isInteger(l)||l<1))throw new RangeError("Legend columns must be a positive integer.");const a=e.titlePosition??"top";if(v(e.titleStyle,a),!["top","left"].includes(a))throw new Error(`Unsupported legend titlePosition "${a}".`);if(i&&(r!=="center"||a!=="top"||!t&&(d!=="vertical"||l!==void 0&&l!==1)))throw new Error("Side item legends require vertical direction, center alignment, top title and one column.");const s=e.offset??(m({position:n})?8:30),f=e.itemGap??(d==="vertical"?w:28);return g(s,"Legend offset"),x(f,"Legend itemGap"),{position:n,layout:o,align:r,direction:d,columns:l,titlePosition:a,offset:s,itemGap:f}}function nt(e,t,n,i,{symbols:o,optional:r={},text:d=i.labels,labels:l=i.config.labels,titleStyle:a=i.config.titleStyle}){const s=`${t}Legend`,f=Object.values(o).find(Array.isArray)?.length??d.length;let c=e.editSemantic({property:`guide.legend.${t}.scale`,value:n.scale}).editSemantic({property:`guide.legend.${t}.title`,value:n.title})._withLegendConfig(t,n);c=p(c,`${s}Symbols`,{length:f,...o});const L=i.labelItemIndices,b=u=>L===void 0||!Array.isArray(u)?u:L.map(W=>u[W]);c=p(c,`${s}Labels`,{length:L?.length??d.length,x:b(i.labelX),y:b(i.itemY),text:b(d)}),c=be(c,`${s}Background`,i.background,n.border),c=p(c,`${s}Symbols`,Object.fromEntries(Object.entries(r).filter(([,u])=>u!==void 0))),c=k(c,`${s}Labels`,l);const S=i.config;return S.titleVisible===!1?$(c):(c=p(c,`${s}Title`,{x:i.title.x,y:i.title.y,text:S.title}),$(k(c,`${s}Title`,a,{align:i.title.align})))}function it(e,t,n,i,o,r){const d=`${t}Legend`;let l=e.editSemantic({property:`guide.legend.${t}.scale`,value:n.scale}).editSemantic({property:`guide.legend.${t}.title`,value:n.title})._withLegendConfig(t,n);return n.border!==!1&&(l=l.createGraphics({id:`${d}Background`,type:"rect",...r})),l=l.createGraphics({id:`${d}Symbols`,type:o,length:i,...r}).createGraphics({id:`${d}Labels`,type:"text",length:i,...r}),n.titleVisible!==!1&&(l=l.createGraphics({id:`${d}Title`,type:"text",...r})),l}export{ge as ITEM_LEGEND_LABELS,Re as ITEM_LEGEND_OPTIONS,he as ITEM_LEGEND_TITLE_STYLE,et as assertLegendBoundsInsideCanvas,it as createItemLegendGraphics,p as editGraphicProperties,be as editLegendBackground,Qe as formatContinuousValues,nt as materializeItemLegend,qe as normalizeContinuousLegend,B as normalizeInitialLegendTitle,Me as normalizeItemLegendConfig,ye as normalizeItemLegendLayout,j as normalizeLegendBorder,T as normalizeLegendTextOptions,A as normalizeLegendTitleOptions,Ke as requireResolvedLegendScale,Xe as resolveContinuousBounds,Ye as resolveContinuousColorLayer,Ve as resolveContinuousLegendLayer,tt as resolveLegendBackgroundFromBounds,Ze as resolveLegendTextBounds,Je as sampleContinuousValues,U as selectLegendLayer,k as styleContinuousText,g as validateNonNegative,x as validatePositive};
|