ggaction 0.0.12 → 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 +49 -0
- package/README.md +2 -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 +10 -16
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +10 -4
- package/src/actions/encodings/position/index.js +43 -5
- 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 +48 -13
- package/src/actions/encodings/position/resolve.js +67 -22
- 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 +21 -10
- package/src/actions/errorBars/edit.js +355 -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 +106 -2
- 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 +34 -3
- 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/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 +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/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 +7 -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/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 +41 -7
- 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/facets.js +8 -1
- 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 +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +58 -23
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +13 -13
- 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/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +46 -14
- 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 +1320 -135
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { resolveLegendItemLayout } from "../../../../layout/legendItems.js";
|
|
1
2
|
import { action } from "../../../../core/action.js";
|
|
2
3
|
import { isPlainObject } from "../../../../core/immutable.js";
|
|
3
4
|
import {
|
|
4
5
|
validateKeys,
|
|
5
|
-
validateOptionObject
|
|
6
|
+
validateOptionObject,
|
|
7
|
+
validateNonEmptyString
|
|
6
8
|
} from "../../../../core/validation.js";
|
|
7
9
|
import { formatDiscretizedIntervals } from "../../../../grammar/scales/index.js";
|
|
8
10
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
@@ -12,11 +14,12 @@ import {
|
|
|
12
14
|
editGraphicProperties,
|
|
13
15
|
editLegendBackground,
|
|
14
16
|
normalizeLegendBorder,
|
|
17
|
+
normalizeItemLegendLayout,
|
|
15
18
|
normalizeLegendTextOptions,
|
|
19
|
+
normalizeLegendTitleOptions,
|
|
16
20
|
resolveContinuousBounds,
|
|
17
|
-
|
|
21
|
+
resolveContinuousColorLayer,
|
|
18
22
|
resolveLegendBackgroundFromBounds,
|
|
19
|
-
resolveLegendTextBounds,
|
|
20
23
|
styleContinuousText,
|
|
21
24
|
validateNonNegative,
|
|
22
25
|
validatePositive
|
|
@@ -26,7 +29,7 @@ import { resolveLegendGraphicPlacement } from
|
|
|
26
29
|
|
|
27
30
|
const OPTIONS = [
|
|
28
31
|
"target", "channels", "position", "align", "offset", "title",
|
|
29
|
-
"symbol", "labels", "titleStyle", "itemGap", "direction", "border"
|
|
32
|
+
"symbol", "labels", "titleStyle", "itemGap", "direction", "border", "layout", "columns", "titlePosition"
|
|
30
33
|
];
|
|
31
34
|
const SYMBOL_OPTIONS = [
|
|
32
35
|
"width", "height", "stroke", "strokeWidth"
|
|
@@ -41,12 +44,7 @@ export function normalizeIntervalLegend(args) {
|
|
|
41
44
|
)) {
|
|
42
45
|
throw new Error('Interval legend requires channels: ["color"].');
|
|
43
46
|
}
|
|
44
|
-
|
|
45
|
-
throw new Error('Interval legends currently support position "right".');
|
|
46
|
-
}
|
|
47
|
-
if ((args.direction ?? "vertical") !== "vertical") {
|
|
48
|
-
throw new Error('Interval legends currently support direction "vertical".');
|
|
49
|
-
}
|
|
47
|
+
const layout = normalizeItemLegendLayout(args);
|
|
50
48
|
if (args.symbol !== undefined && !isPlainObject(args.symbol)) {
|
|
51
49
|
throw new TypeError("createLegend.symbol must be a plain object.");
|
|
52
50
|
}
|
|
@@ -61,17 +59,11 @@ export function normalizeIntervalLegend(args) {
|
|
|
61
59
|
validatePositive(symbol.width, "Legend symbol width");
|
|
62
60
|
validatePositive(symbol.height, "Legend symbol height");
|
|
63
61
|
validateNonNegative(symbol.strokeWidth, "Legend symbol strokeWidth");
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
validateNonNegative(offset, "Legend offset");
|
|
67
|
-
validatePositive(itemGap, "Legend itemGap");
|
|
62
|
+
if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
|
|
63
|
+
validateNonEmptyString(symbol.stroke, "Legend symbol stroke");
|
|
68
64
|
return {
|
|
69
65
|
target: args.target,
|
|
70
|
-
|
|
71
|
-
direction: "vertical",
|
|
72
|
-
align: args.align ?? "center",
|
|
73
|
-
offset,
|
|
74
|
-
itemGap,
|
|
66
|
+
...layout,
|
|
75
67
|
title: args.title,
|
|
76
68
|
inferredTitle: args.title === undefined,
|
|
77
69
|
titleVisible: true,
|
|
@@ -83,7 +75,7 @@ export function normalizeIntervalLegend(args) {
|
|
|
83
75
|
fontFamily: DEFAULT_FONT_FAMILY,
|
|
84
76
|
fontWeight: "normal"
|
|
85
77
|
}),
|
|
86
|
-
titleStyle:
|
|
78
|
+
titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", {
|
|
87
79
|
color: DEFAULT_COLORS.text,
|
|
88
80
|
fontSize: 13,
|
|
89
81
|
fontFamily: DEFAULT_FONT_FAMILY,
|
|
@@ -93,8 +85,8 @@ export function normalizeIntervalLegend(args) {
|
|
|
93
85
|
};
|
|
94
86
|
}
|
|
95
87
|
|
|
96
|
-
function resolveIntervalConfig(program, stored) {
|
|
97
|
-
const layer =
|
|
88
|
+
export function resolveIntervalConfig(program, stored) {
|
|
89
|
+
const layer = resolveContinuousColorLayer(program, stored.target);
|
|
98
90
|
const encoding = layer.encoding.color;
|
|
99
91
|
if (encoding.fieldType !== "quantitative") {
|
|
100
92
|
throw new Error("Interval legend requires quantitative color.");
|
|
@@ -111,47 +103,26 @@ function resolveIntervalConfig(program, stored) {
|
|
|
111
103
|
} };
|
|
112
104
|
}
|
|
113
105
|
|
|
114
|
-
function resolveIntervalLayout(program, config, scale) {
|
|
106
|
+
export function resolveIntervalLayout(program, config, scale) {
|
|
115
107
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
116
|
-
const labels = formatDiscretizedIntervals(scale.thresholds);
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const labelX = symbolX + config.symbol.width + config.labels.offset;
|
|
120
|
-
const title = { x: symbolX, y: plot.y + 20 };
|
|
121
|
-
const strokeExtent = config.symbol.strokeWidth / 2;
|
|
122
|
-
const occupiedBounds = [
|
|
123
|
-
resolveLegendTextBounds(
|
|
124
|
-
{ ...title, align: "left" },
|
|
125
|
-
config.title,
|
|
126
|
-
config.titleStyle
|
|
127
|
-
),
|
|
128
|
-
...labels.map((label, index) => resolveLegendTextBounds(
|
|
129
|
-
{ x: labelX, y: itemY[index], align: "left" },
|
|
130
|
-
label,
|
|
131
|
-
config.labels
|
|
132
|
-
)),
|
|
133
|
-
...itemY.map(y => ({
|
|
134
|
-
left: symbolX - strokeExtent,
|
|
135
|
-
right: symbolX + config.symbol.width + strokeExtent,
|
|
136
|
-
top: y - config.symbol.height / 2 - strokeExtent,
|
|
137
|
-
bottom: y + config.symbol.height / 2 + strokeExtent
|
|
138
|
-
}))
|
|
139
|
-
];
|
|
108
|
+
const labels = formatDiscretizedIntervals(scale.thresholds, config.labels.format);
|
|
109
|
+
const layout = resolveLegendItemLayout(plot, config, labels, config.symbol);
|
|
110
|
+
const occupiedBounds = layout.bounds;
|
|
140
111
|
assertLegendBoundsInsideCanvas(
|
|
141
112
|
occupiedBounds,
|
|
142
113
|
canvas,
|
|
143
|
-
"Interval legend layout"
|
|
114
|
+
"Interval legend layout", config
|
|
144
115
|
);
|
|
145
116
|
const background = resolveLegendBackgroundFromBounds(
|
|
146
117
|
occupiedBounds,
|
|
147
118
|
config.border,
|
|
148
119
|
canvas,
|
|
149
|
-
"Interval legend"
|
|
120
|
+
"Interval legend", config
|
|
150
121
|
);
|
|
151
|
-
return { labels,
|
|
122
|
+
return { labels, ...layout, background };
|
|
152
123
|
}
|
|
153
124
|
|
|
154
|
-
export const rematerializeIntervalLegend = action(
|
|
125
|
+
export const rematerializeIntervalLegend = /* @__PURE__ */ action(
|
|
155
126
|
{
|
|
156
127
|
op: "rematerializeIntervalLegend",
|
|
157
128
|
description: "Rematerialize a discretized color interval legend."
|
|
@@ -196,11 +167,57 @@ export const rematerializeIntervalLegend = action(
|
|
|
196
167
|
y: layout.title.y,
|
|
197
168
|
text: config.title
|
|
198
169
|
});
|
|
199
|
-
return styleContinuousText(next, "colorLegendTitle", config.titleStyle);
|
|
170
|
+
return styleContinuousText(next, "colorLegendTitle", config.titleStyle, { align: layout.title.align });
|
|
200
171
|
}
|
|
201
172
|
);
|
|
202
173
|
|
|
203
|
-
export
|
|
174
|
+
export function createIntervalLegendFromConfig(program, config) {
|
|
175
|
+
const resolved = resolveIntervalConfig(program, config);
|
|
176
|
+
resolveIntervalLayout(program, resolved.config, resolved.scale);
|
|
177
|
+
let next = program
|
|
178
|
+
.editSemantic({
|
|
179
|
+
property: "guide.legend.color.scale",
|
|
180
|
+
value: resolved.encoding.scale
|
|
181
|
+
})
|
|
182
|
+
.editSemantic({
|
|
183
|
+
property: "guide.legend.color.title",
|
|
184
|
+
value: resolved.config.title
|
|
185
|
+
})
|
|
186
|
+
._withLegendConfig("interval", resolved.config);
|
|
187
|
+
const placement = resolveLegendGraphicPlacement(next);
|
|
188
|
+
if (resolved.config.border !== false) {
|
|
189
|
+
next = next.createGraphics({
|
|
190
|
+
id: "colorLegendBackground",
|
|
191
|
+
type: "rect",
|
|
192
|
+
...placement
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
next = next
|
|
196
|
+
.createGraphics({
|
|
197
|
+
id: "colorLegendSymbols",
|
|
198
|
+
type: "rect",
|
|
199
|
+
length: 0,
|
|
200
|
+
...resolveLegendGraphicPlacement(next, resolved.config.border === false
|
|
201
|
+
? {}
|
|
202
|
+
: { after: "colorLegendBackground" })
|
|
203
|
+
})
|
|
204
|
+
.createGraphics({
|
|
205
|
+
id: "colorLegendLabels",
|
|
206
|
+
type: "text",
|
|
207
|
+
length: 0,
|
|
208
|
+
...placement
|
|
209
|
+
});
|
|
210
|
+
if (resolved.config.titleVisible !== false) {
|
|
211
|
+
next = next.createGraphics({
|
|
212
|
+
id: "colorLegendTitle",
|
|
213
|
+
type: "text",
|
|
214
|
+
...placement
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return next.rematerializeIntervalLegend();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const createIntervalLegend = /* @__PURE__ */ action(
|
|
204
221
|
{
|
|
205
222
|
op: "createIntervalLegend",
|
|
206
223
|
description: "Create a discretized color interval legend."
|
|
@@ -212,44 +229,6 @@ export const createIntervalLegend = action(
|
|
|
212
229
|
if (this.graphicSpec.objects.colorLegendSymbols !== undefined) {
|
|
213
230
|
throw new Error("createIntervalLegend requires a missing interval legend.");
|
|
214
231
|
}
|
|
215
|
-
|
|
216
|
-
.editSemantic({
|
|
217
|
-
property: "guide.legend.color.scale",
|
|
218
|
-
value: resolved.encoding.scale
|
|
219
|
-
})
|
|
220
|
-
.editSemantic({
|
|
221
|
-
property: "guide.legend.color.title",
|
|
222
|
-
value: resolved.config.title
|
|
223
|
-
})
|
|
224
|
-
._withLegendConfig("interval", resolved.config);
|
|
225
|
-
const placement = resolveLegendGraphicPlacement(next);
|
|
226
|
-
if (resolved.config.border !== false) {
|
|
227
|
-
next = next.createGraphics({
|
|
228
|
-
id: "colorLegendBackground",
|
|
229
|
-
type: "rect",
|
|
230
|
-
...placement
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
return next
|
|
234
|
-
.createGraphics({
|
|
235
|
-
id: "colorLegendSymbols",
|
|
236
|
-
type: "rect",
|
|
237
|
-
length: 0,
|
|
238
|
-
...resolveLegendGraphicPlacement(next, resolved.config.border === false
|
|
239
|
-
? {}
|
|
240
|
-
: { after: "colorLegendBackground" })
|
|
241
|
-
})
|
|
242
|
-
.createGraphics({
|
|
243
|
-
id: "colorLegendLabels",
|
|
244
|
-
type: "text",
|
|
245
|
-
length: 0,
|
|
246
|
-
...placement
|
|
247
|
-
})
|
|
248
|
-
.createGraphics({
|
|
249
|
-
id: "colorLegendTitle",
|
|
250
|
-
type: "text",
|
|
251
|
-
...placement
|
|
252
|
-
})
|
|
253
|
-
.rematerializeIntervalLegend();
|
|
232
|
+
return createIntervalLegendFromConfig(this, resolved.config);
|
|
254
233
|
}
|
|
255
234
|
);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
normalizeContinuousLegend,
|
|
12
12
|
requireResolvedLegendScale,
|
|
13
13
|
resolveContinuousBounds,
|
|
14
|
-
|
|
14
|
+
resolveContinuousLegendLayer,
|
|
15
15
|
resolveLegendBackgroundFromBounds,
|
|
16
16
|
resolveLegendTextBounds,
|
|
17
17
|
sampleContinuousValues,
|
|
@@ -62,7 +62,7 @@ export function normalizeOpacitySymbol(value) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function resolveOpacityConfig(program, config) {
|
|
65
|
-
const layer =
|
|
65
|
+
const layer = resolveContinuousLegendLayer(program, config.target, "opacity");
|
|
66
66
|
const encoding = layer.encoding.opacity;
|
|
67
67
|
if (encoding.fieldType !== "quantitative") {
|
|
68
68
|
throw new Error("Opacity legend requires quantitative field opacity.");
|
|
@@ -86,8 +86,14 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
86
86
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
87
87
|
const vertical = ["right", "left"].includes(config.position);
|
|
88
88
|
const values = sampleContinuousValues(scale.domain, config.count);
|
|
89
|
-
const texts = formatContinuousValues(
|
|
90
|
-
|
|
89
|
+
const texts = formatContinuousValues(
|
|
90
|
+
values,
|
|
91
|
+
scale.domain,
|
|
92
|
+
"quantitative",
|
|
93
|
+
config.labels.format
|
|
94
|
+
);
|
|
95
|
+
const symbolExtent = config.symbol.radius + (config.symbol.strokeWidth ?? 0) / 2;
|
|
96
|
+
const labelWidths = texts.map(text => measureTextWidth(text, config.labels));
|
|
91
97
|
let symbols;
|
|
92
98
|
let labels;
|
|
93
99
|
let title;
|
|
@@ -95,57 +101,61 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
95
101
|
const baseX = config.position === "right"
|
|
96
102
|
? plot.x + plot.width + config.offset
|
|
97
103
|
: plot.x - config.offset;
|
|
98
|
-
const
|
|
104
|
+
const itemHeight = Math.max(symbolExtent * 2, config.labels.fontSize);
|
|
105
|
+
const pitch = Math.max(config.itemGap, itemHeight);
|
|
106
|
+
const firstY = Math.max(plot.y + 46, config.titleVisible === false ? plot.y + 46
|
|
107
|
+
: plot.y + 20 + config.titleStyle.fontSize / 2 + 12 + itemHeight / 2);
|
|
108
|
+
const y = values.map((_, index) => firstY + index * pitch);
|
|
99
109
|
symbols = y.map(itemY => ({
|
|
100
|
-
x: config.position === "right" ? baseX +
|
|
110
|
+
x: config.position === "right" ? baseX + symbolExtent : baseX - symbolExtent,
|
|
101
111
|
y: itemY
|
|
102
112
|
}));
|
|
103
113
|
labels = y.map(itemY => ({
|
|
104
114
|
x: config.position === "right"
|
|
105
|
-
? baseX +
|
|
106
|
-
: baseX -
|
|
115
|
+
? baseX + symbolExtent * 2 + config.labels.offset
|
|
116
|
+
: baseX - symbolExtent * 2 - config.labels.offset,
|
|
107
117
|
y: itemY,
|
|
108
118
|
align: config.position === "right" ? "left" : "right"
|
|
109
119
|
}));
|
|
110
120
|
title = {
|
|
111
|
-
x: config.position === "right" ? baseX : baseX -
|
|
121
|
+
x: config.position === "right" ? baseX : baseX - symbolExtent * 2,
|
|
112
122
|
y: plot.y + 20,
|
|
113
123
|
align: config.position === "right" ? "left" : "right"
|
|
114
124
|
};
|
|
115
125
|
} else if (config.titlePosition === "left") {
|
|
116
|
-
const titleWidth = measureTextWidth(config.title, config.titleStyle);
|
|
117
|
-
const
|
|
118
|
-
measureTextWidth(text, config.labels)
|
|
119
|
-
);
|
|
126
|
+
const titleWidth = config.titleVisible === false ? 0 : measureTextWidth(config.title, config.titleStyle);
|
|
127
|
+
const titlePrefix = config.titleVisible === false ? 0 : titleWidth + 20;
|
|
120
128
|
const samplesWidth = labelWidths.reduce(
|
|
121
|
-
(sum, width) => sum +
|
|
129
|
+
(sum, width) => sum + symbolExtent * 2 + config.labels.offset + width,
|
|
122
130
|
0
|
|
123
131
|
) + config.itemGap * (values.length - 1);
|
|
124
|
-
const width =
|
|
132
|
+
const width = titlePrefix + samplesWidth;
|
|
125
133
|
const startX = config.align === "left" ? plot.x
|
|
126
134
|
: config.align === "right" ? plot.x + plot.width - width
|
|
127
135
|
: plot.x + (plot.width - width) / 2;
|
|
128
136
|
const height = Math.max(
|
|
129
|
-
|
|
137
|
+
symbolExtent * 2,
|
|
130
138
|
config.labels.fontSize,
|
|
131
|
-
config.titleStyle.fontSize
|
|
139
|
+
config.titleVisible === false ? 0 : config.titleStyle.fontSize
|
|
132
140
|
);
|
|
133
141
|
const centerY = config.position === "top"
|
|
134
142
|
? plot.y - config.offset - height / 2
|
|
135
143
|
: plot.y + plot.height + config.offset + height / 2;
|
|
136
144
|
title = { x: startX, y: centerY, align: "left" };
|
|
137
|
-
let cursor = startX +
|
|
145
|
+
let cursor = startX + titlePrefix;
|
|
138
146
|
symbols = [];
|
|
139
147
|
labels = [];
|
|
140
148
|
for (let index = 0; index < values.length; index += 1) {
|
|
141
|
-
const symbolX = cursor +
|
|
149
|
+
const symbolX = cursor + symbolExtent;
|
|
142
150
|
symbols.push({ x: symbolX, y: centerY });
|
|
143
|
-
const labelX = symbolX +
|
|
151
|
+
const labelX = symbolX + symbolExtent + config.labels.offset;
|
|
144
152
|
labels.push({ x: labelX, y: centerY, align: "left" });
|
|
145
153
|
cursor = labelX + labelWidths[index] + config.itemGap;
|
|
146
154
|
}
|
|
147
155
|
} else {
|
|
148
|
-
const
|
|
156
|
+
const pitch = Math.max(56, config.itemGap * 2,
|
|
157
|
+
symbolExtent * 2 + config.itemGap, Math.max(...labelWidths) + config.itemGap);
|
|
158
|
+
const width = (values.length - 1) * pitch;
|
|
149
159
|
const startX = config.align === "left" ? plot.x
|
|
150
160
|
: config.align === "right" ? plot.x + plot.width - width
|
|
151
161
|
: plot.x + (plot.width - width) / 2;
|
|
@@ -153,27 +163,25 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
153
163
|
const labelHalf = config.labels.fontSize / 2;
|
|
154
164
|
const y = config.position === "top"
|
|
155
165
|
? plot.y - config.offset - config.labels.fontSize -
|
|
156
|
-
config.labels.offset -
|
|
166
|
+
config.labels.offset - symbolExtent
|
|
157
167
|
: plot.y + plot.height + config.offset +
|
|
158
|
-
config.titleStyle.fontSize + 12 +
|
|
168
|
+
(config.titleVisible === false ? 0 : config.titleStyle.fontSize + 12) + symbolExtent;
|
|
159
169
|
symbols = values.map((_, index) => ({
|
|
160
170
|
x: startX + index * width / (values.length - 1),
|
|
161
171
|
y
|
|
162
172
|
}));
|
|
163
173
|
labels = symbols.map(symbol => ({
|
|
164
174
|
x: symbol.x,
|
|
165
|
-
y: symbol.y +
|
|
175
|
+
y: symbol.y + symbolExtent + config.labels.offset + labelHalf,
|
|
166
176
|
align: "center"
|
|
167
177
|
}));
|
|
168
178
|
title = {
|
|
169
179
|
x: startX + width / 2,
|
|
170
|
-
y: y -
|
|
180
|
+
y: y - symbolExtent - 12 - titleHalf,
|
|
171
181
|
align: "center"
|
|
172
182
|
};
|
|
173
183
|
}
|
|
174
|
-
const
|
|
175
|
-
const symbolExtent = radius + strokeExtent;
|
|
176
|
-
const titleBounds = resolveLegendTextBounds(
|
|
184
|
+
const titleBounds = config.titleVisible === false ? undefined : resolveLegendTextBounds(
|
|
177
185
|
title,
|
|
178
186
|
config.title,
|
|
179
187
|
config.titleStyle
|
|
@@ -187,22 +195,22 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
187
195
|
top: symbol.y - symbolExtent,
|
|
188
196
|
bottom: symbol.y + symbolExtent
|
|
189
197
|
}));
|
|
190
|
-
const occupiedBounds = [titleBounds, ...labelBounds, ...symbolBounds];
|
|
198
|
+
const occupiedBounds = [...(config.titleVisible === false ? [] : [titleBounds]), ...labelBounds, ...symbolBounds];
|
|
191
199
|
assertLegendBoundsInsideCanvas(
|
|
192
200
|
occupiedBounds,
|
|
193
201
|
canvas,
|
|
194
|
-
"Opacity legend layout"
|
|
202
|
+
"Opacity legend layout", config
|
|
195
203
|
);
|
|
196
204
|
const background = resolveLegendBackgroundFromBounds(
|
|
197
205
|
occupiedBounds,
|
|
198
206
|
config.border,
|
|
199
207
|
canvas,
|
|
200
|
-
"Opacity legend"
|
|
208
|
+
"Opacity legend", config
|
|
201
209
|
);
|
|
202
210
|
return { values, texts, symbols, labels, title, background };
|
|
203
211
|
}
|
|
204
212
|
|
|
205
|
-
export const rematerializeOpacityLegend = action(
|
|
213
|
+
export const rematerializeOpacityLegend = /* @__PURE__ */ action(
|
|
206
214
|
{
|
|
207
215
|
op: "rematerializeOpacityLegend",
|
|
208
216
|
description: "Rematerialize a field-opacity sample legend."
|
|
@@ -330,72 +338,85 @@ export const rematerializeOpacityLegend = action(
|
|
|
330
338
|
}
|
|
331
339
|
);
|
|
332
340
|
|
|
333
|
-
|
|
341
|
+
|
|
342
|
+
export function resolveOpacityLegendCreation(program, args = {}) {
|
|
343
|
+
const config = normalizeContinuousLegend(args, "opacity");
|
|
344
|
+
if (args.channels !== undefined && (
|
|
345
|
+
!Array.isArray(args.channels) ||
|
|
346
|
+
args.channels.length !== 1 ||
|
|
347
|
+
args.channels[0] !== "opacity"
|
|
348
|
+
)) {
|
|
349
|
+
throw new Error('Opacity legend requires channels: ["opacity"].');
|
|
350
|
+
}
|
|
351
|
+
config.symbol = normalizeOpacitySymbol(args.symbol);
|
|
352
|
+
config.titleVisible = true;
|
|
353
|
+
const resolved = resolveOpacityConfig(program, config);
|
|
354
|
+
return resolved;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function createOpacityLegendFromConfig(program, config) {
|
|
358
|
+
const resolved = resolveOpacityConfig(program, config);
|
|
359
|
+
resolveOpacityLayout(program, resolved.config, resolved.scale);
|
|
360
|
+
let next = program
|
|
361
|
+
.editSemantic({
|
|
362
|
+
property: "guide.legend.opacity.scale",
|
|
363
|
+
value: resolved.encoding.scale
|
|
364
|
+
})
|
|
365
|
+
.editSemantic({
|
|
366
|
+
property: "guide.legend.opacity.title",
|
|
367
|
+
value: resolved.config.title
|
|
368
|
+
})
|
|
369
|
+
._withLegendConfig("opacity", resolved.config);
|
|
370
|
+
if (resolved.config.border !== false) {
|
|
371
|
+
next = next.createGraphics({
|
|
372
|
+
id: "opacityLegendBackground",
|
|
373
|
+
type: "rect",
|
|
374
|
+
...resolveLegendGraphicPlacement(next)
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
next = next
|
|
378
|
+
.createGraphics({
|
|
379
|
+
id: "opacityLegendSymbols",
|
|
380
|
+
type: "circle",
|
|
381
|
+
length: 0,
|
|
382
|
+
...resolveLegendGraphicPlacement(next, resolved.config.border === false
|
|
383
|
+
? {}
|
|
384
|
+
: { after: "opacityLegendBackground" })
|
|
385
|
+
})
|
|
386
|
+
.createGraphics({
|
|
387
|
+
id: "opacityLegendLabels",
|
|
388
|
+
type: "text",
|
|
389
|
+
length: 0,
|
|
390
|
+
...resolveLegendGraphicPlacement(next)
|
|
391
|
+
});
|
|
392
|
+
if (resolved.config.titleVisible !== false) {
|
|
393
|
+
next = next.createGraphics({
|
|
394
|
+
id: "opacityLegendTitle",
|
|
395
|
+
type: "text",
|
|
396
|
+
...resolveLegendGraphicPlacement(next)
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return next.rematerializeOpacityLegend();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export const createOpacityLegend = /* @__PURE__ */ action(
|
|
334
403
|
{
|
|
335
404
|
op: "createOpacityLegend",
|
|
336
405
|
description: "Create a field-opacity sample legend."
|
|
337
406
|
},
|
|
338
407
|
function (args = {}) {
|
|
339
|
-
const
|
|
340
|
-
if (args.channels !== undefined && (
|
|
341
|
-
!Array.isArray(args.channels) ||
|
|
342
|
-
args.channels.length !== 1 ||
|
|
343
|
-
args.channels[0] !== "opacity"
|
|
344
|
-
)) {
|
|
345
|
-
throw new Error('Opacity legend requires channels: ["opacity"].');
|
|
346
|
-
}
|
|
347
|
-
config.symbol = normalizeOpacitySymbol(args.symbol);
|
|
348
|
-
config.titleVisible = true;
|
|
349
|
-
const resolved = resolveOpacityConfig(this, config);
|
|
408
|
+
const resolved = resolveOpacityLegendCreation(this, args);
|
|
350
409
|
resolveOpacityLayout(this, resolved.config, resolved.scale);
|
|
351
410
|
if (this.graphicSpec.objects.opacityLegendSymbols !== undefined) {
|
|
352
411
|
throw new Error(
|
|
353
412
|
"createOpacityLegend requires a missing opacity legend."
|
|
354
413
|
);
|
|
355
414
|
}
|
|
356
|
-
|
|
357
|
-
.editSemantic({
|
|
358
|
-
property: "guide.legend.opacity.scale",
|
|
359
|
-
value: resolved.encoding.scale
|
|
360
|
-
})
|
|
361
|
-
.editSemantic({
|
|
362
|
-
property: "guide.legend.opacity.title",
|
|
363
|
-
value: resolved.config.title
|
|
364
|
-
})
|
|
365
|
-
._withLegendConfig("opacity", resolved.config);
|
|
366
|
-
if (resolved.config.border !== false) {
|
|
367
|
-
next = next.createGraphics({
|
|
368
|
-
id: "opacityLegendBackground",
|
|
369
|
-
type: "rect",
|
|
370
|
-
...resolveLegendGraphicPlacement(next)
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
return next
|
|
374
|
-
.createGraphics({
|
|
375
|
-
id: "opacityLegendSymbols",
|
|
376
|
-
type: "circle",
|
|
377
|
-
length: 0,
|
|
378
|
-
...resolveLegendGraphicPlacement(next),
|
|
379
|
-
...(resolved.config.border === false
|
|
380
|
-
? {}
|
|
381
|
-
: { after: "opacityLegendBackground" })
|
|
382
|
-
})
|
|
383
|
-
.createGraphics({
|
|
384
|
-
id: "opacityLegendLabels",
|
|
385
|
-
type: "text",
|
|
386
|
-
length: 0,
|
|
387
|
-
...resolveLegendGraphicPlacement(next)
|
|
388
|
-
})
|
|
389
|
-
.createGraphics({
|
|
390
|
-
id: "opacityLegendTitle",
|
|
391
|
-
type: "text",
|
|
392
|
-
...resolveLegendGraphicPlacement(next)
|
|
393
|
-
})
|
|
394
|
-
.rematerializeOpacityLegend();
|
|
415
|
+
return createOpacityLegendFromConfig(this, resolved.config);
|
|
395
416
|
}
|
|
396
417
|
);
|
|
397
418
|
|
|
398
|
-
export const removeOpacityLegend = action(
|
|
419
|
+
export const removeOpacityLegend = /* @__PURE__ */ action(
|
|
399
420
|
{
|
|
400
421
|
op: "removeOpacityLegend",
|
|
401
422
|
description: "Remove a field-opacity legend after switching to constant opacity."
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveCategoricalLegendConfig } from "./categorical/actions.js";
|
|
2
|
+
import { resolveGradientLegendCreation } from "./continuous/gradient.js";
|
|
3
|
+
import { resolveOpacityLegendCreation } from "./continuous/opacity.js";
|
|
4
|
+
import { normalizeIntervalLegend, resolveIntervalConfig } from "./continuous/interval.js";
|
|
5
|
+
import { resolveSizeLegendConfig } from "./size.js";
|
|
6
|
+
import { resolveStrokeWidthLegendConfig } from "./strokeWidth.js";
|
|
7
|
+
|
|
8
|
+
export function resolveLegendStepConfig(program, step) {
|
|
9
|
+
switch (step.op) {
|
|
10
|
+
case "createCategoricalLegend": {
|
|
11
|
+
const config = resolveCategoricalLegendConfig(program, step.args);
|
|
12
|
+
return { kind: config.kind, config };
|
|
13
|
+
}
|
|
14
|
+
case "createGradientLegend":
|
|
15
|
+
return { kind: "gradient", config: resolveGradientLegendCreation(program, step.args).config };
|
|
16
|
+
case "createOpacityLegend":
|
|
17
|
+
return { kind: "opacity", config: resolveOpacityLegendCreation(program, step.args).config };
|
|
18
|
+
case "createIntervalLegend":
|
|
19
|
+
return { kind: "interval", config: resolveIntervalConfig(program, normalizeIntervalLegend(step.args)).config };
|
|
20
|
+
case "createSizeLegend":
|
|
21
|
+
return { kind: "size", config: resolveSizeLegendConfig(program, step.args) };
|
|
22
|
+
case "createStrokeWidthLegend":
|
|
23
|
+
return { kind: "strokeWidth", config: resolveStrokeWidthLegendConfig(program, step.args) };
|
|
24
|
+
default: throw new Error(`Unsupported legend creation owner "${step.op}".`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|