ggaction 0.0.17 → 0.0.18
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 +46 -0
- package/README.md +2 -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 +16 -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 +4 -4
- 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/edit.js +1 -1
- package/src/actions/data/regression.js +2 -1
- package/src/actions/data/shared.js +3 -2
- package/src/actions/data/stack.js +3 -1
- 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 +3 -2
- package/src/actions/encodings/seriesLayout.js +1 -0
- 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 +158 -240
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axis.js +16 -4
- package/src/actions/guides/axes/edit.js +4 -4
- package/src/actions/guides/axes/labels.js +120 -46
- package/src/actions/guides/axes/lines.js +6 -5
- 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 +3 -2
- package/src/actions/guides/axes/tickGroups.js +5 -2
- package/src/actions/guides/axes/ticks.js +6 -3
- package/src/actions/guides/axes/titles.js +71 -26
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeLegend.js +10 -2
- package/src/actions/guides/grids/grid.js +7 -1
- package/src/actions/guides/grids/resolve.js +59 -12
- package/src/actions/guides/legends/blocks.js +3 -1
- package/src/actions/guides/legends/categorical/actions.js +70 -17
- package/src/actions/guides/legends/categorical/components.js +12 -8
- package/src/actions/guides/legends/categorical/index.js +6 -0
- package/src/actions/guides/legends/categorical/layout.js +37 -8
- package/src/actions/guides/legends/categorical/options.js +31 -20
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +18 -12
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- 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 +47 -34
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +19 -12
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/remove.js +3 -0
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +51 -43
- package/src/actions/guides/legends/strokeWidth.js +11 -29
- package/src/actions/guides/legends/target.js +3 -3
- package/src/actions/guides/legends/transition.js +21 -7
- 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/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 +8 -4
- package/src/actions/marks/bar/materialize.js +9 -6
- 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/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +2 -0
- 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/semanticValidation/layer.js +4 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +3 -3
- 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 +8 -3
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +2 -2
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +81 -27
- 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 +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/resourceReferences.js +3 -0
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +26 -0
- package/src/core/vocabulary.js +4 -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/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/complete.js +15 -19
- package/src/grammar/computed.js +92 -25
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +3 -7
- package/src/grammar/displayLabels.js +28 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +5 -0
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendGradient.js +18 -0
- package/src/grammar/legendGroups.js +1 -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/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +7 -1
- package/src/grammar/regression/models.js +83 -55
- package/src/grammar/regression/parameters.js +14 -3
- 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 +11 -0
- package/src/grammar/schemas/graphic.js +3 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +3 -26
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transforms.js +4 -1
- 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/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/coordinateBounds.js +8 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/placement.js +19 -8
- package/src/materialization/facetGuides/preparation.js +15 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +1 -1
- 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/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +1 -1
- package/src/materialization/scales/map.js +3 -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 +5 -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/persistence/codec.js +37 -6
- package/src/persistence/validation.js +47 -0
- package/src/persistence.js +18 -5
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +2 -1
- 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/svg.js +4 -1
- 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 +1 -0
- package/types/index.d.ts +7 -0
- package/types/program.d.ts +347 -85
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { editGraphicProperties } from "../../../primitives/graphicProperties.js";
|
|
2
|
+
import { materializeLegendTitleLines } from "../../../../materialization/legendText.js";
|
|
2
3
|
import { action } from "../../../../core/action.js";
|
|
3
4
|
import {
|
|
4
5
|
activeConfig,
|
|
@@ -19,15 +20,17 @@ export const rematerializeLegendLabels = /* @__PURE__ */ action(
|
|
|
19
20
|
throw new Error("rematerializeLegendLabels requires existing legend labels.");
|
|
20
21
|
}
|
|
21
22
|
const layout = resolveLayout(this, config);
|
|
23
|
+
const labels = categoricalLegendLabels(config);
|
|
24
|
+
const visible = labels.flatMap((text, index) => text === "" ? [] : [index]);
|
|
22
25
|
return editGraphicProperties(editGraphicProperties(this, id, {
|
|
23
|
-
length:
|
|
24
|
-
x: layout.labelX,
|
|
25
|
-
y: layout.itemY
|
|
26
|
+
length: visible.length,
|
|
27
|
+
x: visible.map(index => layout.labelX[index]),
|
|
28
|
+
y: visible.map(index => layout.itemY[index])
|
|
26
29
|
})
|
|
27
30
|
.editGraphics({
|
|
28
31
|
target: id,
|
|
29
32
|
property: "text",
|
|
30
|
-
value:
|
|
33
|
+
value: visible.map(index => labels[index])
|
|
31
34
|
}), id, {
|
|
32
35
|
fill: config.labels.color,
|
|
33
36
|
fontSize: config.labels.fontSize,
|
|
@@ -52,7 +55,7 @@ export const createLegendLabels = /* @__PURE__ */ action(
|
|
|
52
55
|
.createGraphics({
|
|
53
56
|
id,
|
|
54
57
|
type: "text",
|
|
55
|
-
length: config.
|
|
58
|
+
length: categoricalLegendLabels(config).length,
|
|
56
59
|
...resolveCategoricalLegendPlacement(this)
|
|
57
60
|
})
|
|
58
61
|
.rematerializeLegendLabels({ kind: config.kind });
|
|
@@ -69,7 +72,7 @@ export const rematerializeLegendTitle = /* @__PURE__ */ action(
|
|
|
69
72
|
throw new Error("rematerializeLegendTitle requires an existing legend title.");
|
|
70
73
|
}
|
|
71
74
|
const layout = resolveLayout(this, config);
|
|
72
|
-
|
|
75
|
+
const next = editGraphicProperties(this, id, {
|
|
73
76
|
x: layout.titleX,
|
|
74
77
|
y: layout.titleY,
|
|
75
78
|
text: config.title,
|
|
@@ -81,12 +84,13 @@ export const rematerializeLegendTitle = /* @__PURE__ */ action(
|
|
|
81
84
|
.editGraphics({
|
|
82
85
|
target: id,
|
|
83
86
|
property: "textAlign",
|
|
84
|
-
value: ["top", "bottom"].includes(config.position) &&
|
|
87
|
+
value: config.titleStyle.align ?? (["top", "bottom"].includes(config.position) &&
|
|
85
88
|
config.titlePosition === "top"
|
|
86
89
|
? "center"
|
|
87
|
-
: "left"
|
|
90
|
+
: "left")
|
|
88
91
|
})
|
|
89
92
|
.editGraphics({ target: id, property: "textBaseline", value: "middle" });
|
|
93
|
+
return materializeLegendTitleLines(next, id, config.title, config.titleStyle);
|
|
90
94
|
}
|
|
91
95
|
);
|
|
92
96
|
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from "./symbols.js";
|
|
30
30
|
import { activeConfig, noOptions, symbolGraphic } from "./layout.js";
|
|
31
31
|
import {
|
|
32
|
+
rematerializeLegendGroups,
|
|
32
33
|
rematerializeHorizontalLegendLane,
|
|
33
34
|
rematerializeSideLegendLane
|
|
34
35
|
} from "../lane.js";
|
|
@@ -68,6 +69,10 @@ function createLegendHighlightAction() {
|
|
|
68
69
|
},
|
|
69
70
|
function (args = {}) {
|
|
70
71
|
noOptions(args, "rematerializeLegendHighlights");
|
|
72
|
+
if (this.context.deferGuideLayoutValidation !== true &&
|
|
73
|
+
Object.values(this.guideConfigs.legend ?? {}).some(config => ["left", "right"].includes(config.position))) {
|
|
74
|
+
return this.rematerializeLegend();
|
|
75
|
+
}
|
|
71
76
|
const kinds = args.kind === undefined
|
|
72
77
|
? ["series", "color", "stroke"].filter(
|
|
73
78
|
kind => this.guideConfigs.legend?.[kind] !== undefined
|
|
@@ -134,6 +139,7 @@ export function registerBasicCategoricalLegendActions(ProgramClass) {
|
|
|
134
139
|
ProgramClass.prototype.createLegendTitle = createLegendTitle;
|
|
135
140
|
ProgramClass.prototype.rematerializeLegendTitle = rematerializeLegendTitle;
|
|
136
141
|
ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
|
|
142
|
+
ProgramClass.prototype.rematerializeLegendGroups = rematerializeLegendGroups;
|
|
137
143
|
ProgramClass.prototype.rematerializeSideLegendLane =
|
|
138
144
|
rematerializeSideLegendLane;
|
|
139
145
|
ProgramClass.prototype.rematerializeHorizontalLegendLane =
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { findLayer } from "../../../../selectors/layers.js";
|
|
2
|
+
import { isLinearGradientPaint } from "../../../../grammar/paint.js";
|
|
3
|
+
import { intrinsicLegendConfig } from "../../../../materialization/legendGroups.js";
|
|
1
4
|
import { validateOptionObject } from "../../../../core/validation.js";
|
|
2
5
|
import { mapOrdinalValues } from "../../../../grammar/scales/index.js";
|
|
3
6
|
import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
|
|
@@ -78,12 +81,20 @@ export function symbolWidth(config) {
|
|
|
78
81
|
}));
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
export function categoricalLegendView(config) {
|
|
85
|
+
if (config.hiddenCount !== undefined || !config.overflow || config.domain.length <= config.overflow.maxItems) return config;
|
|
86
|
+
return { ...config, domain: config.domain.slice(0, config.overflow.maxItems),
|
|
87
|
+
hiddenCount: config.domain.length - config.overflow.maxItems };
|
|
88
|
+
}
|
|
89
|
+
|
|
81
90
|
export function categoricalLegendLabels(config) {
|
|
82
|
-
|
|
91
|
+
config = categoricalLegendView(config);
|
|
92
|
+
const labels = config.domain.map(value => resolveDisplayLabel(
|
|
83
93
|
value,
|
|
84
94
|
config.labelMap,
|
|
85
95
|
formatVisibleText
|
|
86
96
|
));
|
|
97
|
+
return config.hiddenCount > 0 ? [...labels, `…${config.hiddenCount} entries`] : labels;
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
function resolveSampleBounds(program, config, width) {
|
|
@@ -96,7 +107,7 @@ function resolveSampleBounds(program, config, width) {
|
|
|
96
107
|
stroke: layer.stroke ?? (config.channels.includes("stroke")
|
|
97
108
|
? appearance.strokes[index]
|
|
98
109
|
: appearance.colors[index]),
|
|
99
|
-
strokeWidth: layer.lineWidth, opacity: layer.opacity,
|
|
110
|
+
strokeWidth: layer.lineWidth, opacity: layer.opacity ?? appearance.opacity,
|
|
100
111
|
...requestedStrokeDetails(layer, "Legend line symbol") } };
|
|
101
112
|
}
|
|
102
113
|
if (layer.type === "swatch") {
|
|
@@ -106,21 +117,21 @@ function resolveSampleBounds(program, config, width) {
|
|
|
106
117
|
stroke: config.channels.includes("stroke")
|
|
107
118
|
? appearance.strokes[index]
|
|
108
119
|
: layer.stroke,
|
|
109
|
-
opacity: layer.opacity,
|
|
120
|
+
opacity: layer.opacity ?? appearance.opacity,
|
|
110
121
|
...requestedStrokeDetails(details, "Legend swatch symbol")
|
|
111
|
-
}, details
|
|
122
|
+
}, details);
|
|
112
123
|
}
|
|
113
124
|
if (config.channels.includes("shape")) {
|
|
114
125
|
return createPointShapeGraphic({ shape: appearance.shapes[index], x: width / 2, y: 0,
|
|
115
126
|
area: Math.PI * layer.size ** 2, fill: layer.fill ?? appearance.colors[index],
|
|
116
|
-
stroke: layer.stroke, strokeWidth: layer.strokeWidth, opacity: layer.opacity,
|
|
127
|
+
stroke: layer.stroke, strokeWidth: layer.strokeWidth, opacity: layer.opacity ?? appearance.opacity,
|
|
117
128
|
...requestedStrokeDetails(layer, "Legend point symbol") });
|
|
118
129
|
}
|
|
119
130
|
return { type: "circle", properties: { x: width / 2, y: 0, radius: layer.size,
|
|
120
131
|
stroke: config.channels.includes("stroke")
|
|
121
132
|
? appearance.strokes[index]
|
|
122
133
|
: layer.stroke,
|
|
123
|
-
strokeWidth: layer.strokeWidth, opacity: layer.opacity,
|
|
134
|
+
strokeWidth: layer.strokeWidth, opacity: layer.opacity ?? appearance.opacity,
|
|
124
135
|
...requestedStrokeDetails(layer, "Legend point symbol") } };
|
|
125
136
|
});
|
|
126
137
|
return items.map((item, itemIndex) => {
|
|
@@ -147,6 +158,8 @@ function resolveSampleBounds(program, config, width) {
|
|
|
147
158
|
}
|
|
148
159
|
|
|
149
160
|
export function resolveLayout(program, config) {
|
|
161
|
+
config = intrinsicLegendConfig(program, config);
|
|
162
|
+
config = categoricalLegendView(config);
|
|
150
163
|
const { plot, canvas } = resolveContinuousBounds(program,
|
|
151
164
|
"Legend layout requires Canvas bounds, width, and height.");
|
|
152
165
|
const width = symbolWidth(config);
|
|
@@ -160,7 +173,14 @@ export function resolveLayout(program, config) {
|
|
|
160
173
|
}
|
|
161
174
|
|
|
162
175
|
export function resolveAppearance(program, config) {
|
|
163
|
-
|
|
176
|
+
const target = findLayer(program, config.target);
|
|
177
|
+
const gradientFallback = target?.mark?.type === "area" &&
|
|
178
|
+
target.encoding.color === undefined &&
|
|
179
|
+
!config.channels.includes("color") &&
|
|
180
|
+
isLinearGradientPaint(program.markConfigs[target.id]?.fill);
|
|
181
|
+
const fallback = gradientFallback
|
|
182
|
+
? program.markConfigs[target.id].fill : DEFAULT_COLORS.mark;
|
|
183
|
+
let colors = config.domain.map(() => fallback);
|
|
164
184
|
let strokes = config.domain.map(() => "white");
|
|
165
185
|
let dashes = config.domain.map(() => []);
|
|
166
186
|
let shapes = config.domain.map(() => "circle");
|
|
@@ -172,7 +192,8 @@ export function resolveAppearance(program, config) {
|
|
|
172
192
|
if (config.channels[index] === "strokeDash") dashes = values;
|
|
173
193
|
if (config.channels[index] === "shape") shapes = values;
|
|
174
194
|
}
|
|
175
|
-
return { colors, strokes, dashes, shapes
|
|
195
|
+
return { colors, strokes, dashes, shapes,
|
|
196
|
+
...(gradientFallback ? { opacity: program.markConfigs[target.id].opacity } : {}) };
|
|
176
197
|
}
|
|
177
198
|
|
|
178
199
|
export function noOptions(args, operation) {
|
|
@@ -189,3 +210,11 @@ export function layerFor(config, type) {
|
|
|
189
210
|
}
|
|
190
211
|
return layer;
|
|
191
212
|
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
export function resolveSymbolLayout(program, config) {
|
|
216
|
+
const view = categoricalLegendView(config);
|
|
217
|
+
const layout = resolveLayout(program, view);
|
|
218
|
+
return { ...layout, symbolX: layout.symbolX.slice(0, view.domain.length),
|
|
219
|
+
itemY: layout.itemY.slice(0, view.domain.length) };
|
|
220
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LEGEND_TITLE_STYLE_KEYS, validateLegendTitleAlignment } from "../../../../grammar/legendTitleStyle.js";
|
|
2
|
+
import { PLOT_LEGEND_POSITIONS, isPlotLegend } from "../../../../layout/legend.js";
|
|
1
3
|
import { validateOptionObject } from "../../../../core/validation.js";
|
|
2
4
|
import { CATEGORICAL_LEGEND_CHANNELS } from "../../../../core/vocabulary.js";
|
|
3
5
|
import { normalizeRecipe } from "./recipes.js";
|
|
@@ -9,7 +11,7 @@ import {
|
|
|
9
11
|
validateKeys,
|
|
10
12
|
validateObject
|
|
11
13
|
} from "./validation.js";
|
|
12
|
-
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
14
|
+
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY, DEFAULT_VERTICAL_LEGEND_ITEM_GAP } from
|
|
13
15
|
"../../../../theme/defaults.js";
|
|
14
16
|
|
|
15
17
|
export const CHANNELS = CATEGORICAL_LEGEND_CHANNELS;
|
|
@@ -29,7 +31,8 @@ const OPTIONS = Object.freeze([
|
|
|
29
31
|
"labels",
|
|
30
32
|
"titleStyle",
|
|
31
33
|
"itemGap",
|
|
32
|
-
"border"
|
|
34
|
+
"border",
|
|
35
|
+
"overflow"
|
|
33
36
|
]);
|
|
34
37
|
const TEXT_OPTIONS = Object.freeze([
|
|
35
38
|
"offset",
|
|
@@ -39,12 +42,19 @@ const TEXT_OPTIONS = Object.freeze([
|
|
|
39
42
|
"fontWeight",
|
|
40
43
|
"format"
|
|
41
44
|
]);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
|
|
46
|
+
export function normalizeLegendOverflow(value) {
|
|
47
|
+
if (value === undefined || value === false) return value;
|
|
48
|
+
validateObject(value, ["maxItems", "summary"], "Legend overflow");
|
|
49
|
+
if (!Number.isInteger(value.maxItems) || value.maxItems < 1) {
|
|
50
|
+
throw new RangeError("Legend overflow.maxItems must be a positive integer.");
|
|
51
|
+
}
|
|
52
|
+
if (value.summary !== undefined && value.summary !== "ellipsis-count") {
|
|
53
|
+
throw new Error("Legend overflow.summary must be ellipsis-count.");
|
|
54
|
+
}
|
|
55
|
+
return { maxItems: value.maxItems, summary: "ellipsis-count" };
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
const BORDER_OPTIONS = Object.freeze([
|
|
49
59
|
"color",
|
|
50
60
|
"lineWidth",
|
|
@@ -99,7 +109,7 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
|
99
109
|
if (Object.hasOwn(args, "titleStyle")) {
|
|
100
110
|
validateObject(
|
|
101
111
|
args.titleStyle,
|
|
102
|
-
|
|
112
|
+
LEGEND_TITLE_STYLE_KEYS,
|
|
103
113
|
"createLegend.titleStyle"
|
|
104
114
|
);
|
|
105
115
|
}
|
|
@@ -121,13 +131,15 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
|
121
131
|
};
|
|
122
132
|
const position = args.position ?? defaults.position;
|
|
123
133
|
const align = args.align ?? defaults.align;
|
|
134
|
+
const inside = isPlotLegend({ position });
|
|
124
135
|
const side = ["left", "right"].includes(position);
|
|
125
|
-
const direction = args.direction ?? (side ? "vertical" : "horizontal");
|
|
136
|
+
const direction = args.direction ?? (side || inside ? "vertical" : "horizontal");
|
|
126
137
|
const columns = args.columns;
|
|
127
138
|
const offset = args.offset ?? 8;
|
|
128
139
|
const titlePosition = args.titlePosition ?? "top";
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
validateLegendTitleAlignment(titleStyle, titlePosition);
|
|
141
|
+
const itemGap = args.itemGap ?? (direction === "vertical"
|
|
142
|
+
? DEFAULT_VERTICAL_LEGEND_ITEM_GAP
|
|
131
143
|
: position === "top" ? 24 : 20);
|
|
132
144
|
const layout = args.layout === undefined ? "edge" : args.layout;
|
|
133
145
|
if (!["edge", "legacy-bottom"].includes(layout)) {
|
|
@@ -142,14 +154,14 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
|
142
154
|
throw new Error('Legend columns, vertical direction, left title and custom offset require layout "edge".');
|
|
143
155
|
}
|
|
144
156
|
|
|
145
|
-
if (!["right", "left", "bottom", "top"].includes(position)) {
|
|
157
|
+
if (!["right", "left", "bottom", "top", ...PLOT_LEGEND_POSITIONS].includes(position)) {
|
|
146
158
|
throw new Error(`Unsupported legend position "${position}".`);
|
|
147
159
|
}
|
|
148
160
|
if (!["left", "center", "right"].includes(align)) {
|
|
149
161
|
throw new Error(`Unsupported legend alignment "${align}".`);
|
|
150
162
|
}
|
|
151
|
-
if (
|
|
152
|
-
throw new Error("Side legends currently require center alignment.");
|
|
163
|
+
if ((side || inside) && align !== "center") {
|
|
164
|
+
throw new Error(inside ? "In-plot legends require center alignment." : "Side legends currently require center alignment.");
|
|
153
165
|
}
|
|
154
166
|
if (!["horizontal", "vertical"].includes(direction)) {
|
|
155
167
|
throw new Error(`Unsupported legend direction "${direction}".`);
|
|
@@ -157,11 +169,8 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
|
157
169
|
if (side && direction !== "vertical") {
|
|
158
170
|
throw new Error("Side legends require vertical direction.");
|
|
159
171
|
}
|
|
160
|
-
if (side
|
|
161
|
-
throw new Error("Side legends require
|
|
162
|
-
}
|
|
163
|
-
if (side && titlePosition !== "top") {
|
|
164
|
-
throw new Error("Side legends require a top title.");
|
|
172
|
+
if ((side || inside) && titlePosition !== "top") {
|
|
173
|
+
throw new Error(inside ? "In-plot legends require a top title." : "Side legends require a top title.");
|
|
165
174
|
}
|
|
166
175
|
if (columns !== undefined && (!Number.isInteger(columns) || columns <= 0)) {
|
|
167
176
|
throw new RangeError("Legend columns must be a positive integer.");
|
|
@@ -181,7 +190,9 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
|
|
|
181
190
|
validateFontWeight(titleStyle.fontWeight, "Legend title fontWeight");
|
|
182
191
|
positive(itemGap, "Legend itemGap");
|
|
183
192
|
|
|
193
|
+
const overflow = normalizeLegendOverflow(args.overflow);
|
|
184
194
|
return {
|
|
195
|
+
...(overflow === undefined ? {} : { overflow }),
|
|
185
196
|
target: args.target,
|
|
186
197
|
channels: args.channels,
|
|
187
198
|
position,
|
|
@@ -17,7 +17,7 @@ function isCategoricalTarget(layer) {
|
|
|
17
17
|
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
-
return ["bar", "area", "arc", "rect", "rule", "tick"].includes(
|
|
20
|
+
return ["bar", "area", "arc", "rect", "rule", "tick", "text"].includes(
|
|
21
21
|
layer?.mark?.type
|
|
22
22
|
) && ["color", "stroke"].some(
|
|
23
23
|
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
@@ -49,7 +49,7 @@ export const sameValues = sameOrderedValues;
|
|
|
49
49
|
|
|
50
50
|
function resolveLegendKind(layer, requestedChannels) {
|
|
51
51
|
if (sameValues(requestedChannels, ["stroke"])) return "stroke";
|
|
52
|
-
if (["bar", "area", "arc", "rect"].includes(layer.mark.type)) return "color";
|
|
52
|
+
if (["bar", "area", "arc", "rect", "text"].includes(layer.mark.type)) return "color";
|
|
53
53
|
if (
|
|
54
54
|
layer.mark.type === "point" &&
|
|
55
55
|
sameValues(requestedChannels, ["color"])
|
|
@@ -2,17 +2,19 @@ import { editGraphicProperties } from "../../../primitives/graphicProperties.js"
|
|
|
2
2
|
import { action } from "../../../../core/action.js";
|
|
3
3
|
import {
|
|
4
4
|
activeConfig,
|
|
5
|
+
categoricalLegendView,
|
|
5
6
|
graphic,
|
|
6
7
|
layerFor,
|
|
7
8
|
noOptions,
|
|
8
9
|
resolveAppearance,
|
|
9
|
-
resolveLayout,
|
|
10
|
+
resolveSymbolLayout as resolveLayout,
|
|
10
11
|
symbolGraphic,
|
|
11
12
|
symbolWidth
|
|
12
13
|
} from "./layout.js";
|
|
13
14
|
import { resolveCategoricalLegendPlacement } from "../lifecycle.js";
|
|
14
15
|
import { createPointShapeGraphic } from "../../../../grammar/pointShapes.js";
|
|
15
16
|
import {
|
|
17
|
+
hasRectRadius,
|
|
16
18
|
materializeRectItem,
|
|
17
19
|
requestedRectStyleDetails
|
|
18
20
|
} from "../../../../grammar/roundedRect.js";
|
|
@@ -32,11 +34,12 @@ function makeEditSymbol(type) {
|
|
|
32
34
|
{ op, description: `Rematerialize categorical legend ${type} symbols.` },
|
|
33
35
|
function (args = {}) {
|
|
34
36
|
noOptions(args, op);
|
|
35
|
-
const { config } = activeConfig(this, args.kind);
|
|
37
|
+
const { config: stored } = activeConfig(this, args.kind);
|
|
38
|
+
const config = categoricalLegendView(stored);
|
|
36
39
|
const layer = layerFor(config, type);
|
|
37
40
|
const id = symbolGraphic(config, type);
|
|
38
41
|
const dynamicPoint = type === "point" && config.channels.includes("shape");
|
|
39
|
-
const styledSwatch = type === "swatch" &&
|
|
42
|
+
const styledSwatch = type === "swatch" && hasRectRadius(layer);
|
|
40
43
|
const expected = dynamicPoint || styledSwatch
|
|
41
44
|
? "collection"
|
|
42
45
|
: { line: "line", point: "circle", swatch: "rect" }[type];
|
|
@@ -81,7 +84,7 @@ function makeEditSymbol(type) {
|
|
|
81
84
|
)) {
|
|
82
85
|
next = next.editGraphics({ target: id, property, value });
|
|
83
86
|
}
|
|
84
|
-
return applyLayerOpacity(next, id, layer.opacity);
|
|
87
|
+
return applyLayerOpacity(next, id, layer.opacity ?? appearance.opacity);
|
|
85
88
|
}
|
|
86
89
|
if (type === "point") {
|
|
87
90
|
const x = layout.symbolX.map(value => value + symbolWidth(config) / 2);
|
|
@@ -98,7 +101,7 @@ function makeEditSymbol(type) {
|
|
|
98
101
|
? appearance.strokes[index]
|
|
99
102
|
: layer.stroke,
|
|
100
103
|
strokeWidth: layer.strokeWidth,
|
|
101
|
-
opacity: layer.opacity,
|
|
104
|
+
opacity: layer.opacity ?? appearance.opacity,
|
|
102
105
|
...requestedStrokeDetails(layer, "Legend point symbol")
|
|
103
106
|
});
|
|
104
107
|
});
|
|
@@ -127,7 +130,7 @@ function makeEditSymbol(type) {
|
|
|
127
130
|
)) {
|
|
128
131
|
next = next.editGraphics({ target: id, property, value });
|
|
129
132
|
}
|
|
130
|
-
return applyLayerOpacity(next, id, layer.opacity);
|
|
133
|
+
return applyLayerOpacity(next, id, layer.opacity ?? appearance.opacity);
|
|
131
134
|
}
|
|
132
135
|
const x = layout.symbolX.map(
|
|
133
136
|
value => value + (symbolWidth(config) - layer.width) / 2
|
|
@@ -151,9 +154,9 @@ function makeEditSymbol(type) {
|
|
|
151
154
|
fill: fills[index],
|
|
152
155
|
stroke: strokes[index],
|
|
153
156
|
strokeWidth: layer.strokeWidth,
|
|
154
|
-
...(layer.opacity === undefined ? {} : { opacity: layer.opacity }),
|
|
157
|
+
...((layer.opacity ?? appearance.opacity) === undefined ? {} : { opacity: layer.opacity ?? appearance.opacity }),
|
|
155
158
|
...requestedStrokeDetails(details, "Legend swatch symbol")
|
|
156
|
-
}, details
|
|
159
|
+
}, details))
|
|
157
160
|
);
|
|
158
161
|
}
|
|
159
162
|
);
|
|
@@ -166,14 +169,15 @@ function makeCreateSymbol(type, edit) {
|
|
|
166
169
|
{ op, description: `Create categorical legend ${type} symbols.` },
|
|
167
170
|
function (args = {}) {
|
|
168
171
|
noOptions(args, op);
|
|
169
|
-
const { config } = activeConfig(this, args.kind);
|
|
172
|
+
const { config: stored } = activeConfig(this, args.kind);
|
|
173
|
+
const config = categoricalLegendView(stored);
|
|
170
174
|
const layer = layerFor(config, type);
|
|
171
175
|
const id = symbolGraphic(config, type);
|
|
172
176
|
if (this.graphicSpec.objects[id] !== undefined) {
|
|
173
177
|
throw new Error(`${op} requires missing ${type} symbols.`);
|
|
174
178
|
}
|
|
175
179
|
const graphicType = type === "point" && config.channels.includes("shape") ||
|
|
176
|
-
type === "swatch" && (layer
|
|
180
|
+
type === "swatch" && hasRectRadius(layer)
|
|
177
181
|
? "collection"
|
|
178
182
|
: { line: "line", point: "circle", swatch: "rect" }[type];
|
|
179
183
|
return this
|
|
@@ -213,7 +217,8 @@ export const createLegendSymbols = /* @__PURE__ */ action(
|
|
|
213
217
|
{ op: "createLegendSymbols", description: "Create layered legend symbols." },
|
|
214
218
|
function (args = {}) {
|
|
215
219
|
noOptions(args, "createLegendSymbols");
|
|
216
|
-
const { config } = activeConfig(this, args.kind);
|
|
220
|
+
const { config: stored } = activeConfig(this, args.kind);
|
|
221
|
+
const config = categoricalLegendView(stored);
|
|
217
222
|
let next = this;
|
|
218
223
|
for (const layer of config.symbol.layers) {
|
|
219
224
|
const operation = {
|
|
@@ -231,7 +236,8 @@ export const rematerializeLegendSymbols = /* @__PURE__ */ action(
|
|
|
231
236
|
{ op: "rematerializeLegendSymbols", description: "Rematerialize layered legend symbols." },
|
|
232
237
|
function (args = {}) {
|
|
233
238
|
noOptions(args, "rematerializeLegendSymbols");
|
|
234
|
-
const { config } = activeConfig(this, args.kind);
|
|
239
|
+
const { config: stored } = activeConfig(this, args.kind);
|
|
240
|
+
const config = categoricalLegendView(stored);
|
|
235
241
|
let next = this;
|
|
236
242
|
for (const layer of config.symbol.layers) {
|
|
237
243
|
const operation = {
|
|
@@ -19,9 +19,4 @@ export const nonEmptyString = validateNonEmptyString;
|
|
|
19
19
|
export const nonNegative = validateNonNegativeFinite;
|
|
20
20
|
export const positive = validatePositiveFinite;
|
|
21
21
|
|
|
22
|
-
export
|
|
23
|
-
if (typeof value !== "string" && !Number.isFinite(value)) {
|
|
24
|
-
throw new TypeError(`${label} must be a string or number.`);
|
|
25
|
-
}
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
22
|
+
export { validateFontWeight } from "../../../../core/validation.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{validateOptionObject as h,validatePositiveFinite as k,validateNonNegativeFinite as x,validateNonEmptyString as v}from"../../../../core/validation.js";import{intrinsicLegendConfig as L}from"../../../../materialization/legendGroups.js";import{mapScaleConsumerValues as C}from"../../../../materialization/scales/map.js";import{transformedTicks as G}from"../../../../grammar/scales/index.js";import{resolveLegendItemLayout as _}from"../../../../layout/legendItems.js";import{editGraphicProperties as u}from"../../../primitives/graphicProperties.js";import{normalizeLegendSampling as z,readLegendSampling as W,validateSamplingAgainstScale as w,resolveLegendSampleValues as B}from"../sampling.js";import{normalizeContinuousLegend as T,normalizeItemLegendLayout as P,resolveContinuousBounds as V,formatContinuousValues as E,assertLegendBoundsInsideCanvas as F,resolveLegendBackgroundFromBounds as I,editLegendBackground as N,styleContinuousText as y}from"./common.js";function q(t){if(t===void 0||t==="auto")return{type:"point",radius:7,strokeWidth:1};if(h(t,["type","radius","stroke","strokeWidth"],"Sampled color symbol"),t.type!==void 0&&t.type!=="point")throw new Error("Sampled color symbol requires type point.");const e={type:"point",radius:7,strokeWidth:1,...t};return k(e.radius,"Sampled color symbol radius"),x(e.strokeWidth,"Sampled color symbol strokeWidth"),e.stroke!==void 0&&v(e.stroke,"Sampled color symbol stroke"),e}function Q(t,e,i="create"){if(t.gradient!==void 0)throw new Error("Symbol color legends do not accept gradient dimensions.");const{representation:l,values:r,count:s,symbol:o,columns:d,direction:n,itemGap:a,layout:p,titlePosition:c,...m}=t,b=T(m,"gradient"),g=["left","right","top-left","top-right","bottom-left","bottom-right"].includes(t.position??"right"),S=P({...t,itemGap:t.itemGap??(g?18:20)},{multiColumnSide:!0}),{count:X,...f}=b;return{...f,...S,representation:"symbols",sampling:z(t,{previous:e,operation:i,label:"Sampled color legend"}),symbol:q(t.symbol)}}function O(t,e,i){e=L(t,e);const{plot:l,canvas:r}=V(t),s=w(W(e),i,"Sampled color legend");let o=s.mode==="auto"&&["log","sqrt","symlog"].includes(i.type)?G(i.type,i.domain,s.count,i.type==="log"?{base:i.base}:i.type==="symlog"?{constant:i.constant}:{}):B(e,i,"Sampled color legend");s.mode==="auto"&&i.midpoint!==void 0&&!o.includes(i.midpoint)&&(o=[...o,i.midpoint].sort((c,m)=>c-m));let d=E(o,i.domain,e.fieldType,e.labels.format);const n=e.symbol.radius+e.symbol.strokeWidth/2,a=_(l,e,d,{width:n*2,height:n*2},r,t.materializationConfigs.textMetrics);F(a.bounds,r,"Sampled color legend",e);const p=I(a.bounds,e.border,r,"Sampled color legend",e);return{...a,background:p,values:o,texts:d,extent:n,config:e}}function R(t,e,i){const l=i,{scale:r,encoding:s}=e,o=O(t,l,r),d=C(o.values,r,"color");let n=t.editSemantic({property:"guide.legend.color.scale",value:s.scale}).editSemantic({property:"guide.legend.color.title",value:l.title})._withLegendConfig("gradient",e.config);return n=u(n,"colorGradientSymbols",{length:o.values.length,x:o.symbolX.map(a=>a+o.extent),y:o.itemY,radius:l.symbol.radius,fill:d,stroke:l.symbol.stroke??d,strokeWidth:l.symbol.strokeWidth,opacity:l.blockSymbol?.opacity??1}),n=u(n,"colorGradientLabels",{length:o.values.length,x:o.labelX,y:o.itemY,text:o.texts}),n=N(n,"colorGradientBackground",o.background,l.border),n=y(n,"colorGradientLabels",l.labels),l.titleVisible===!1?n:(n=u(n,"colorGradientTitle",{x:o.title.x,y:o.title.y,text:l.title}),y(n,"colorGradientTitle",l.titleStyle,{align:o.title.align}))}export{R as materializeColorSymbols,q as normalizeColorSymbol,Q as normalizeColorSymbols,O as resolveColorSymbolsLayout};
|