ggaction 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/README.md +3 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +33 -27
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +40 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +37 -2
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +79 -24
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +153 -11
- package/src/actions/errorBars/edit.js +365 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +44 -23
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +138 -14
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +60 -4
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +213 -65
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionCompatibility.js +1 -1
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +9 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +23 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +86 -23
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +7 -1
- package/src/materialization/facetGuides/placement.js +16 -1
- package/src/materialization/facetGuides/preparation.js +26 -1
- package/src/materialization/facets.js +11 -2
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +30 -7
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +63 -25
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +70 -14
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/bar.js +0 -43
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +48 -18
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1327 -124
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
import { isPlainObject } from "../../../../core/immutable.js";
|
|
2
1
|
import { noOptions } 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";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
measureLegendTextWidth,
|
|
19
|
-
resolveLegendGrid
|
|
20
|
-
} from "../../../../layout/legend.js";
|
|
10
|
+
import { resolveLegendItemLayout } from "../../../../layout/legendItems.js";
|
|
11
|
+
import { createPointShapeGraphic } from "../../../../grammar/pointShapes.js";
|
|
12
|
+
import { resolveConcreteGraphicBounds } from "../../../../grammar/schemas/graphicBounds.js";
|
|
21
13
|
|
|
22
14
|
export function activeConfig(program) {
|
|
23
15
|
const kinds = ["series", "color"]
|
|
@@ -54,347 +46,42 @@ export function symbolWidth(config) {
|
|
|
54
46
|
}));
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
function
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const maximumRadius = Math.sqrt(Math.max(...scale.range) / Math.PI);
|
|
81
|
-
return { values, labels, maximumRadius };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function resolveLeftLayout(program, bounds, canvas, config, width, count) {
|
|
85
|
-
const sizeConfig = program.guideConfigs.legend?.size;
|
|
86
|
-
const size = resolveLeftSizeMetrics(program, sizeConfig);
|
|
87
|
-
const padding = config.border === false ? 0 : config.border.padding;
|
|
88
|
-
const categoricalWidth = config.domain.reduce((maximum, value) => Math.max(
|
|
89
|
-
maximum,
|
|
90
|
-
width + config.labels.offset +
|
|
91
|
-
measureLegendTextWidth(value, config.labels)
|
|
92
|
-
), config.titleVisible === false
|
|
93
|
-
? 0
|
|
94
|
-
: measureLegendTextWidth(config.title, config.titleStyle));
|
|
95
|
-
const sizeWidth = size === undefined
|
|
96
|
-
? 0
|
|
97
|
-
: size.labels.reduce((maximum, label) => Math.max(
|
|
98
|
-
maximum,
|
|
99
|
-
44 + measureLegendTextWidth(label, config.labels)
|
|
100
|
-
), measureLegendTextWidth(sizeConfig.title, config.titleStyle));
|
|
101
|
-
const contentWidth = Math.max(categoricalWidth, sizeWidth);
|
|
102
|
-
const occupiedRight = bounds.x - config.offset;
|
|
103
|
-
const contentRight = occupiedRight - padding;
|
|
104
|
-
const start = contentRight - contentWidth;
|
|
105
|
-
if (start < padding) {
|
|
106
|
-
throw new Error("Legend layout requires more left-margin space.");
|
|
107
|
-
}
|
|
108
|
-
if (occupiedRight >= leftGuideBoundary(program, bounds)) {
|
|
109
|
-
throw new Error("Left legend and y-axis guides require more left-margin space.");
|
|
110
|
-
}
|
|
111
|
-
const symbolX = Array(count).fill(start);
|
|
112
|
-
const itemY = Array.from(
|
|
113
|
-
{ length: count },
|
|
114
|
-
(_, index) => bounds.y + 52 + index * config.itemGap
|
|
115
|
-
);
|
|
116
|
-
const labelX = symbolX.map(value => value + width + config.labels.offset);
|
|
117
|
-
const titleX = start;
|
|
118
|
-
const titleY = bounds.y + 20;
|
|
119
|
-
const sizeTitleY = bounds.y + 56 + count * 34 + 22;
|
|
120
|
-
const sizeItemY = size?.values.map((_, index) => sizeTitleY + 34 + index * 40);
|
|
121
|
-
const sizeSymbolX = size?.values.map(() => start + 16);
|
|
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 };
|
|
49
|
+
function resolveSampleBounds(program, config, width) {
|
|
50
|
+
const appearance = resolveAppearance(program, config);
|
|
51
|
+
return config.domain.map((_, index) => {
|
|
52
|
+
const items = config.symbol.layers.map(layer => {
|
|
53
|
+
if (layer.type === "line") {
|
|
54
|
+
const x = (width - layer.length) / 2;
|
|
55
|
+
return { type: "line", properties: { x1: x, x2: x + layer.length, y1: 0, y2: 0,
|
|
56
|
+
strokeWidth: layer.lineWidth } };
|
|
57
|
+
}
|
|
58
|
+
if (layer.type === "swatch") {
|
|
59
|
+
return { type: "rect", properties: { x: (width - layer.width) / 2, y: -layer.height / 2,
|
|
60
|
+
width: layer.width, height: layer.height, strokeWidth: layer.strokeWidth } };
|
|
61
|
+
}
|
|
62
|
+
if (config.channels.includes("shape")) {
|
|
63
|
+
return createPointShapeGraphic({ shape: appearance.shapes[index], x: width / 2, y: 0,
|
|
64
|
+
area: Math.PI * layer.size ** 2, fill: layer.fill ?? appearance.colors[index],
|
|
65
|
+
stroke: layer.stroke, strokeWidth: layer.strokeWidth });
|
|
66
|
+
}
|
|
67
|
+
return { type: "circle", properties: { x: width / 2, y: 0, radius: layer.size,
|
|
68
|
+
strokeWidth: layer.strokeWidth } };
|
|
69
|
+
});
|
|
70
|
+
return resolveConcreteGraphicBounds({ objects: { sample: { type: "collection", items } }, order: ["sample"] }, "sample");
|
|
71
|
+
});
|
|
331
72
|
}
|
|
332
73
|
|
|
333
74
|
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;
|
|
75
|
+
const { plot, canvas } = resolveContinuousBounds(program,
|
|
76
|
+
"Legend layout requires Canvas bounds, width, and height.");
|
|
342
77
|
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);
|
|
78
|
+
const layout = resolveLegendItemLayout(plot, config, config.domain.map(formatVisibleText), {
|
|
79
|
+
width, height: 0, itemBounds: resolveSampleBounds(program, config, width)
|
|
80
|
+
}, canvas);
|
|
81
|
+
assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Categorical legend layout", config);
|
|
82
|
+
const background = resolveLegendBackgroundFromBounds(layout.bounds, config.border, canvas,
|
|
83
|
+
"Categorical legend", config);
|
|
84
|
+
return { ...layout, titleX: layout.title.x, titleY: layout.title.y, background };
|
|
398
85
|
}
|
|
399
86
|
|
|
400
87
|
export function resolveAppearance(program, config) {
|
|
@@ -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",
|
|
@@ -89,6 +92,9 @@ export function normalizeOptions(args, kind) {
|
|
|
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.");
|
|
@@ -179,7 +195,7 @@ export function normalizeOptions(args, kind) {
|
|
|
179
195
|
labels,
|
|
180
196
|
titleStyle,
|
|
181
197
|
itemGap,
|
|
182
|
-
|
|
198
|
+
layout,
|
|
183
199
|
border: normalizeBorder(args.border)
|
|
184
200
|
};
|
|
185
201
|
}
|
|
@@ -25,6 +25,20 @@ const LAYER_OPTIONS = Object.freeze({
|
|
|
25
25
|
])
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
export function resolveLegendSymbol(program, layer, channels, requested) {
|
|
29
|
+
if (requested !== undefined && requested !== "auto") return requested;
|
|
30
|
+
if (layer.mark?.type !== "point" || !channels?.includes("shape")) return undefined;
|
|
31
|
+
const color = channels.includes("color") ? layer.encoding?.color : undefined;
|
|
32
|
+
const hasMatchingLine = color?.scale !== undefined && program.semanticSpec.layers.some(candidate =>
|
|
33
|
+
candidate.mark?.type === "line" && candidate.encoding?.color?.field === color.field &&
|
|
34
|
+
candidate.encoding?.color?.scale === color.scale
|
|
35
|
+
);
|
|
36
|
+
return { layers: [
|
|
37
|
+
...(hasMatchingLine ? [{ type: "line", length: 32, lineWidth: 3 }] : []),
|
|
38
|
+
{ type: "point", size: Math.sqrt(64 / Math.PI), stroke: "white", strokeWidth: 0 }
|
|
39
|
+
] };
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
function defaultRecipe(kind) {
|
|
29
43
|
return kind === "series"
|
|
30
44
|
? { layers: [{ type: "line", length: 32, lineWidth: 2 }] }
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { requireDataset } from "../../../../selectors/datasets.js";
|
|
2
|
+
import { readNominalField } from "../../../../grammar/scales/fields.js";
|
|
3
|
+
import { resolveLegendOrderDomain } from "../../../../grammar/categoryOrder.js";
|
|
1
4
|
import { validateUserId } from "../../../../core/identifiers.js";
|
|
2
5
|
import {
|
|
3
6
|
sameOrderedValues,
|
|
@@ -41,7 +44,7 @@ export function resolveTarget(program, requested) {
|
|
|
41
44
|
|
|
42
45
|
export const sameValues = sameOrderedValues;
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
function resolveLegendKind(layer, requestedChannels) {
|
|
45
48
|
if (["bar", "area", "arc", "rect"].includes(layer.mark.type)) return "color";
|
|
46
49
|
if (
|
|
47
50
|
layer.mark.type === "point" &&
|
|
@@ -74,13 +77,11 @@ function resolveOrdinalScales(program, scaleIds) {
|
|
|
74
77
|
return { scales, domain: scales[0].domain };
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
export function resolveDefinition(program, layer, requestedChannels, requestedTitle) {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
83
|
-
));
|
|
80
|
+
export function resolveDefinition(program, layer, requestedChannels, requestedTitle, order) {
|
|
81
|
+
const channels = requestedChannels ?? (["line", "point"].includes(layer.mark.type)
|
|
82
|
+
? CHANNELS.filter(channel => layer.encoding?.[channel]?.scale !== undefined)
|
|
83
|
+
: ["color"]);
|
|
84
|
+
const kind = resolveLegendKind(layer, channels);
|
|
84
85
|
if (
|
|
85
86
|
!Array.isArray(channels) ||
|
|
86
87
|
channels.length === 0 ||
|
|
@@ -109,13 +110,22 @@ export function resolveDefinition(program, layer, requestedChannels, requestedTi
|
|
|
109
110
|
const scales = encodings.map(item => item.encoding.scale);
|
|
110
111
|
const resolved = resolveOrdinalScales(program, scales);
|
|
111
112
|
const field = [...fields][0];
|
|
113
|
+
let linked;
|
|
114
|
+
if (order?.channel !== undefined) {
|
|
115
|
+
const position = layer.encoding?.[order.channel];
|
|
116
|
+
if (!["nominal", "ordinal"].includes(position?.fieldType) || position.field !== field) {
|
|
117
|
+
throw new Error("Linked legend order requires the same categorical field on its target.");
|
|
118
|
+
}
|
|
119
|
+
linked = program.resolvedScales[position.scale]?.domain;
|
|
120
|
+
}
|
|
112
121
|
return {
|
|
113
122
|
kind,
|
|
114
123
|
channels,
|
|
115
124
|
scales,
|
|
116
125
|
field,
|
|
117
126
|
title: nonEmptyString(requestedTitle ?? field, "Legend title"),
|
|
118
|
-
domain: resolved.domain
|
|
127
|
+
domain: resolveLegendOrderDomain(resolved.domain, order, linked,
|
|
128
|
+
order?.values === undefined ? undefined : readNominalField(requireDataset(program, layer.data).values, field))
|
|
119
129
|
};
|
|
120
130
|
}
|
|
121
131
|
|
|
@@ -133,6 +143,7 @@ export function resolveCurrentDefinition(program, config) {
|
|
|
133
143
|
program,
|
|
134
144
|
layer,
|
|
135
145
|
channels,
|
|
136
|
-
config.inferredTitle === true ? undefined : guide.title
|
|
146
|
+
config.inferredTitle === true ? undefined : guide.title,
|
|
147
|
+
guide.order
|
|
137
148
|
);
|
|
138
149
|
}
|
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
symbolGraphic,
|
|
10
10
|
symbolWidth
|
|
11
11
|
} from "./layout.js";
|
|
12
|
-
import {
|
|
13
|
-
"../../../../materialization/graphicHierarchy.js";
|
|
12
|
+
import { resolveCategoricalLegendPlacement } from "../lifecycle.js";
|
|
14
13
|
import { createPointShapeGraphic } from "../../../../grammar/pointShapes.js";
|
|
15
14
|
|
|
16
15
|
function makeEditSymbol(type) {
|
|
@@ -129,7 +128,7 @@ function makeCreateSymbol(type, edit) {
|
|
|
129
128
|
.createGraphics({
|
|
130
129
|
id,
|
|
131
130
|
type: graphicType,
|
|
132
|
-
...
|
|
131
|
+
...resolveCategoricalLegendPlacement(this),
|
|
133
132
|
...(this.graphicSpec.objects[graphic(config, "Labels")] === undefined
|
|
134
133
|
? {}
|
|
135
134
|
: { before: graphic(config, "Labels") }),
|