ggaction 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +6 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +20 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +8 -7
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/derived.js +11 -1
- package/src/actions/data/edit.js +3 -1
- package/src/actions/data/index.js +4 -0
- package/src/actions/data/regression.js +25 -16
- package/src/actions/data/regressionStatistics.js +73 -0
- package/src/actions/data/shared.js +8 -4
- package/src/actions/data/stack.js +3 -1
- package/src/actions/distributions/revision.js +24 -18
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +14 -8
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +26 -24
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +176 -247
- package/src/actions/facets/guides.js +2 -1
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axes.js +30 -11
- package/src/actions/guides/axes/axis.js +72 -10
- package/src/actions/guides/axes/components.js +13 -13
- package/src/actions/guides/axes/edit.js +36 -24
- package/src/actions/guides/axes/labels.js +155 -71
- package/src/actions/guides/axes/lines.js +27 -24
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +13 -8
- package/src/actions/guides/axes/tickGroups.js +11 -7
- package/src/actions/guides/axes/ticks.js +36 -24
- package/src/actions/guides/axes/titles.js +107 -69
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeAxes.js +33 -24
- package/src/actions/guides/facadeLegend.js +12 -4
- package/src/actions/guides/grids/grid.js +8 -2
- package/src/actions/guides/grids/resolve.js +102 -58
- package/src/actions/guides/legends/blocks.js +46 -20
- package/src/actions/guides/legends/categorical/actions.js +95 -19
- package/src/actions/guides/legends/categorical/components.js +15 -18
- package/src/actions/guides/legends/categorical/index.js +8 -0
- package/src/actions/guides/legends/categorical/layout.js +45 -20
- package/src/actions/guides/legends/categorical/options.js +39 -22
- package/src/actions/guides/legends/categorical/recipes.js +7 -3
- package/src/actions/guides/legends/categorical/resolve.js +11 -4
- package/src/actions/guides/legends/categorical/symbols.js +38 -18
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/colorSize.js +110 -0
- package/src/actions/guides/legends/combine.js +37 -0
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +73 -46
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +33 -21
- package/src/actions/guides/legends/creation.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +8 -7
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/lifecycle.js +2 -1
- package/src/actions/guides/legends/remove.js +9 -3
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +65 -47
- package/src/actions/guides/legends/strokeWidth.js +13 -29
- package/src/actions/guides/legends/target.js +2 -79
- package/src/actions/guides/legends/transition.js +44 -11
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/guides/tickValues.js +8 -5
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +29 -14
- package/src/actions/marks/bar/materialize.js +15 -7
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/point/materialize.js +5 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +12 -10
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/createGraphics.js +3 -2
- package/src/actions/primitives/editGraphics.js +15 -11
- package/src/actions/primitives/graphicProperties.js +1 -6
- package/src/actions/primitives/semanticAction.js +27 -1
- package/src/actions/primitives/semanticValidation/guide.js +11 -1
- package/src/actions/primitives/semanticValidation/layer.js +8 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +18 -6
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -5
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +12 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +8 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +126 -45
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +6 -3
- package/src/core/font.js +9 -2
- package/src/core/graphicProperties.js +1 -0
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +4 -0
- package/src/core/resourceReferences.js +5 -2
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +35 -0
- package/src/core/vocabulary.js +7 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/axisTextPlacement.js +1 -0
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/colorSizeLegend.js +39 -0
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +115 -26
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +13 -9
- package/src/grammar/displayLabels.js +36 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +58 -30
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/facets/legendIntent.js +65 -0
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/guideDash.js +1 -0
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendBlocks.js +1 -0
- package/src/grammar/legendBorder.js +9 -0
- package/src/grammar/legendClip.js +19 -0
- package/src/grammar/legendCombinations.js +45 -0
- package/src/grammar/legendGradient.js +27 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendOpacity.js +6 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/pointShapes.js +16 -2
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +16 -2
- package/src/grammar/regression/models.js +142 -54
- package/src/grammar/regression/parameters.js +23 -4
- package/src/grammar/regression/statistics.js +64 -0
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +14 -0
- package/src/grammar/schemas/graphic.js +6 -0
- package/src/grammar/schemas/graphicBounds.js +31 -7
- package/src/grammar/schemas/semanticPath.js +19 -3
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +4 -27
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +22 -11
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +15 -2
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/guideCollisions.js +9 -1
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/compositionSnapshot.js +2 -7
- package/src/materialization/coordinateBounds.js +21 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/edit.js +30 -0
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/owner.js +125 -0
- package/src/materialization/facetGuides/ownerLayout.js +71 -0
- package/src/materialization/facetGuides/placement.js +71 -14
- package/src/materialization/facetGuides/preparation.js +22 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +5 -1
- package/src/materialization/graphicTranslation.js +73 -0
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendBorder.js +28 -0
- package/src/materialization/legendClip.js +76 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +2 -2
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +14 -15
- package/src/materialization/scales/map.js +6 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +7 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/materialization/typography.js +10 -2
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +220 -0
- package/src/persistence.js +19 -6
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +22 -11
- package/src/renderers/canvas/isolated.js +166 -0
- package/src/renderers/canvas/limits.js +3 -0
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/pdf.js +2 -2
- package/src/renderers/png.js +2 -2
- package/src/renderers/svg.js +14 -4
- package/src/selectors/axes.js +1 -0
- package/src/selectors/graphicCapacity.js +38 -0
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +3 -0
- package/types/index.d.ts +12 -0
- package/types/program.d.ts +536 -129
|
@@ -1,28 +1,33 @@
|
|
|
1
|
+
import{validateLegendClip,clippedSampleBounds}from"../../../grammar/legendClip.js";
|
|
2
|
+
import{hasLegendClipGraphics}from"../../../materialization/legendClip.js";
|
|
3
|
+
import { resolveDisplayLabel } from "../../../grammar/displayLabels.js";
|
|
4
|
+
import { intrinsicLegendConfig, legendGroup } from "../../../materialization/legendGroups.js";
|
|
5
|
+
import { formatVisibleText } from "../../../core/textMetrics.js";
|
|
1
6
|
import { action, closedAction } from "../../../core/action.js";
|
|
2
7
|
import {
|
|
3
|
-
validateNonEmptyString,
|
|
4
8
|
validateKeys
|
|
5
9
|
} from "../../../core/validation.js";
|
|
6
10
|
import {
|
|
7
11
|
formatDiscretizedIntervals,
|
|
8
12
|
isDiscreteSizeScaleType,
|
|
13
|
+
isEnumeratedSizeScaleType,
|
|
9
14
|
isSizeScaleType,
|
|
10
15
|
mapSizeValues
|
|
11
16
|
} from "../../../grammar/scales/index.js";
|
|
12
17
|
import { resolveLegendItemLayout } from "../../../layout/legendItems.js";
|
|
13
|
-
import { DEFAULT_COLORS
|
|
18
|
+
import { DEFAULT_COLORS } from
|
|
14
19
|
"../../../theme/defaults.js";
|
|
15
20
|
import { findLayer } from "../../../selectors/layers.js";
|
|
16
21
|
import { resolveLegendGraphicPlacement } from
|
|
17
22
|
"../../../materialization/graphicHierarchy.js";
|
|
18
23
|
import {
|
|
24
|
+
ITEM_LEGEND_LABELS as SIZE_LEGEND_LABELS,
|
|
25
|
+
ITEM_LEGEND_TITLE_STYLE as SIZE_LEGEND_TITLE_STYLE,
|
|
26
|
+
normalizeItemLegendConfig,
|
|
27
|
+
ITEM_LEGEND_OPTIONS as SIZE_OPTIONS,
|
|
19
28
|
assertLegendBoundsInsideCanvas,
|
|
20
29
|
materializeItemLegend,
|
|
21
30
|
createItemLegendGraphics,
|
|
22
|
-
normalizeItemLegendLayout,
|
|
23
|
-
normalizeLegendBorder,
|
|
24
|
-
normalizeLegendTextOptions,
|
|
25
|
-
normalizeLegendTitleOptions,
|
|
26
31
|
resolveContinuousBounds,
|
|
27
32
|
resolveLegendBackgroundFromBounds,
|
|
28
33
|
formatContinuousValues,
|
|
@@ -35,23 +40,11 @@ import {
|
|
|
35
40
|
resolveLegendSampleValues
|
|
36
41
|
} from "./sampling.js";
|
|
37
42
|
import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
|
|
43
|
+
import { normalizeLegendOverflow } from "./categorical/options.js";
|
|
38
44
|
|
|
39
|
-
const SIZE_OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
|
|
40
|
-
"direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
color: DEFAULT_COLORS.text,
|
|
45
|
-
fontSize: 12,
|
|
46
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
47
|
-
fontWeight: "normal"
|
|
48
|
-
});
|
|
49
|
-
export const SIZE_LEGEND_TITLE_STYLE = Object.freeze({
|
|
50
|
-
color: DEFAULT_COLORS.strongText,
|
|
51
|
-
fontSize: 13,
|
|
52
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
53
|
-
fontWeight: 600
|
|
54
|
-
});
|
|
46
|
+
|
|
47
|
+
export { SIZE_LEGEND_LABELS, SIZE_LEGEND_TITLE_STYLE };
|
|
55
48
|
|
|
56
49
|
export function isSizeLegendPoint(layer) {
|
|
57
50
|
return layer?.mark?.type === "point" &&
|
|
@@ -79,6 +72,8 @@ function requireScale(program, id) {
|
|
|
79
72
|
}
|
|
80
73
|
|
|
81
74
|
export function resolveSizeLegendLayout(program, config) {
|
|
75
|
+
validateLegendClip("size",config);
|
|
76
|
+
config = intrinsicLegendConfig(program, config);
|
|
82
77
|
const scale = requireScale(program, config.scale);
|
|
83
78
|
const categorical = [
|
|
84
79
|
program.guideConfigs.legend?.series,
|
|
@@ -96,23 +91,38 @@ export function resolveSizeLegendLayout(program, config) {
|
|
|
96
91
|
});
|
|
97
92
|
const labels = effective.labels;
|
|
98
93
|
const titleStyle = effective.titleStyle;
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
// Group ownership replaces placement inheritance, never shared typography.
|
|
95
|
+
const placementCategorical = config.blockLocalPosition === undefined && legendGroup(program, config.target) === undefined &&
|
|
96
|
+
(config.inheritPosition !== false || categorical?.position === config.position)
|
|
97
|
+
? categorical : undefined;
|
|
98
|
+
const position = placementCategorical?.position ?? config.position;
|
|
99
|
+
if (placementCategorical?.layout === "legacy-bottom") {
|
|
101
100
|
throw new Error('Combined size legends require layout "edge".');
|
|
102
101
|
}
|
|
103
|
-
const horizontal =
|
|
102
|
+
const horizontal = placementCategorical !== undefined && ["top", "bottom"].includes(position);
|
|
104
103
|
const geometry = horizontal ? {
|
|
105
104
|
position, align: categorical.align, direction: categorical.direction,
|
|
106
105
|
columns: categorical.columns, titlePosition: categorical.titlePosition,
|
|
107
106
|
offset: categorical.offset, itemGap: effective.blockGap ?? categorical.itemGap
|
|
108
107
|
} : { position };
|
|
108
|
+
const ordinalSize = scale.type === "ordinal";
|
|
109
|
+
if (ordinalSize && config.labels.format !== undefined && config.labels.format !== "auto") {
|
|
110
|
+
throw new Error("Categorical size legends retain original category labels.");
|
|
111
|
+
}
|
|
109
112
|
const discrete = isDiscreteSizeScaleType(scale.type);
|
|
110
|
-
if (discrete && readLegendSampling(config).mode === "values") {
|
|
113
|
+
if ((discrete || ordinalSize) && readLegendSampling(config).mode === "values") {
|
|
111
114
|
throw new Error("Discrete size legends do not support exact values.");
|
|
112
115
|
}
|
|
113
|
-
const
|
|
116
|
+
const allValues = discrete
|
|
114
117
|
? undefined
|
|
115
|
-
: resolveLegendSampleValues(effective, scale, "Size legend");
|
|
118
|
+
: ordinalSize ? scale.domain : resolveLegendSampleValues(effective, scale, "Size legend");
|
|
119
|
+
const hiddenCount = ordinalSize && effective.overflow &&
|
|
120
|
+
allValues.length > effective.overflow.maxItems
|
|
121
|
+
? allValues.length - effective.overflow.maxItems
|
|
122
|
+
: 0;
|
|
123
|
+
const values = hiddenCount > 0
|
|
124
|
+
? allValues.slice(0, effective.overflow.maxItems)
|
|
125
|
+
: allValues;
|
|
116
126
|
const areas = discrete
|
|
117
127
|
? scale.range
|
|
118
128
|
: mapSizeValues(values, scale);
|
|
@@ -120,38 +130,46 @@ export function resolveSizeLegendLayout(program, config) {
|
|
|
120
130
|
const radius = Math.max(...radii);
|
|
121
131
|
const width = Math.max(32, radius * 2);
|
|
122
132
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
123
|
-
const
|
|
133
|
+
const itemText = discrete
|
|
124
134
|
? formatDiscretizedIntervals(scale.thresholds, config.labels.format)
|
|
125
|
-
: formatContinuousValues(
|
|
135
|
+
: ordinalSize ? values.map(formatVisibleText) : formatContinuousValues(
|
|
126
136
|
values,
|
|
127
137
|
scale.domain,
|
|
128
138
|
"quantitative",
|
|
129
139
|
config.labels.format
|
|
130
140
|
);
|
|
141
|
+
const mappedText = !discrete && !ordinalSize && effective.labelMap !== undefined
|
|
142
|
+
? itemText.map((label, index) => resolveDisplayLabel(values[index], effective.labelMap, () => label))
|
|
143
|
+
: itemText;
|
|
144
|
+
const text = hiddenCount > 0 ? [...mappedText, `…${hiddenCount} entries`] : mappedText;
|
|
131
145
|
const symbolStroke = effective.blockSymbol?.strokeWidth ?? 0;
|
|
132
146
|
const symbolExtent = symbolStroke / 2;
|
|
133
147
|
const sampleWidth = width + symbolStroke;
|
|
134
148
|
const layout = resolveLegendItemLayout(plot, { ...effective, ...geometry, labels, titleStyle }, text, {
|
|
135
|
-
width: sampleWidth, height: radius * 2 + symbolStroke,
|
|
136
|
-
itemBounds: radii.map(r => ({
|
|
149
|
+
width: sampleWidth, height: Math.min(radius * 2 + symbolStroke, effective.clipHeight??Infinity),
|
|
150
|
+
itemBounds: radii.map(r => clippedSampleBounds({
|
|
137
151
|
left: sampleWidth / 2 - r - symbolExtent,
|
|
138
152
|
right: sampleWidth / 2 + r + symbolExtent,
|
|
139
153
|
top: -r - symbolExtent,
|
|
140
154
|
bottom: r + symbolExtent
|
|
141
|
-
}))
|
|
155
|
+
},effective.clipHeight))
|
|
142
156
|
}, undefined, program.materializationConfigs.textMetrics);
|
|
143
157
|
assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Size legend layout", { ...effective, ...geometry });
|
|
144
158
|
const background = resolveLegendBackgroundFromBounds(layout.bounds, effective.border, canvas, "Size legend", { ...effective, ...geometry });
|
|
145
|
-
return { ...layout, symbolX: layout.symbolX.map(x => x + sampleWidth / 2),
|
|
146
|
-
|
|
159
|
+
return { ...layout, symbolX: layout.symbolX.slice(0, radii.length).map(x => x + sampleWidth / 2),
|
|
160
|
+
symbolY: layout.itemY.slice(0, radii.length), radii, text,
|
|
161
|
+
...(effective.labelMap === undefined ? {} : {
|
|
162
|
+
labelItemIndices: text.flatMap((label, index) => label === "" ? [] : [index])
|
|
163
|
+
}), labels, titleStyle, background, config: effective };
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
|
|
150
167
|
{
|
|
151
168
|
op: "rematerializeSizeLegend",
|
|
152
|
-
description: "Rematerialize a
|
|
169
|
+
description: "Rematerialize a point-size legend."
|
|
153
170
|
}, [],
|
|
154
171
|
function (args = {}) {
|
|
172
|
+
if(hasLegendClipGraphics(this))return this.rematerializeLegend();
|
|
155
173
|
const config = this.guideConfigs.legend?.size;
|
|
156
174
|
if (config === undefined) throw new Error("Size legend requires stored configuration.");
|
|
157
175
|
const layer = findLayer(this, config.target);
|
|
@@ -171,13 +189,14 @@ export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
|
|
|
171
189
|
return materializeItemLegend(this, "size", currentConfig, layout, {
|
|
172
190
|
text: layout.text, labels: layout.labels, titleStyle: layout.titleStyle,
|
|
173
191
|
symbols: {
|
|
174
|
-
x: layout.symbolX, y: layout.
|
|
192
|
+
x: layout.symbolX, y: layout.symbolY, radius: layout.radii,
|
|
175
193
|
fill: layout.config.blockSymbol?.fill ?? DEFAULT_COLORS.sizeSymbol,
|
|
176
194
|
opacity: layout.config.blockSymbol?.opacity ?? 0.7
|
|
177
195
|
},
|
|
178
196
|
optional: {
|
|
179
197
|
stroke: layout.config.blockSymbol?.stroke,
|
|
180
|
-
strokeWidth: layout.config.blockSymbol?.strokeWidth
|
|
198
|
+
strokeWidth: layout.config.blockSymbol?.strokeWidth ??
|
|
199
|
+
(layout.config.blockSymbol?.stroke === undefined ? undefined : 0)
|
|
181
200
|
}
|
|
182
201
|
});
|
|
183
202
|
}
|
|
@@ -186,14 +205,16 @@ export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
|
|
|
186
205
|
|
|
187
206
|
export function resolveSizeLegendConfig(program, args = {}) {
|
|
188
207
|
validateKeys(args, [...SIZE_OPTIONS, "inheritAppearance"], "createSizeLegend");
|
|
189
|
-
if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
|
|
190
208
|
const layer = resolveSizeLegendPoint(program, args.target);
|
|
191
209
|
const encoding = layer.encoding?.size;
|
|
192
210
|
if (encoding?.scale === undefined) {
|
|
193
211
|
throw new Error(`Point mark "${layer.id}" requires a size encoding.`);
|
|
194
212
|
}
|
|
195
213
|
const scale = requireScale(program, encoding.scale);
|
|
196
|
-
const discrete =
|
|
214
|
+
const discrete = isEnumeratedSizeScaleType(scale.type);
|
|
215
|
+
if (args.overflow !== undefined && scale.type !== "ordinal") {
|
|
216
|
+
throw new Error("Size legend overflow requires an ordinal scale.");
|
|
217
|
+
}
|
|
197
218
|
if (discrete && (Object.hasOwn(args, "count") || Object.hasOwn(args, "values"))) {
|
|
198
219
|
throw new Error("Discrete size legends do not support count or exact values.");
|
|
199
220
|
}
|
|
@@ -201,20 +222,17 @@ export function resolveSizeLegendConfig(program, args = {}) {
|
|
|
201
222
|
operation: "create",
|
|
202
223
|
label: "Size legend"
|
|
203
224
|
});
|
|
204
|
-
const count = discrete ? scale.range.length : undefined;
|
|
225
|
+
const count = discrete ? (scale.type === "ordinal" ? scale.domain.length : scale.range.length) : undefined;
|
|
205
226
|
return {
|
|
206
227
|
target: layer.id,
|
|
207
228
|
scale: encoding.scale,
|
|
208
|
-
...
|
|
209
|
-
|
|
210
|
-
inferredTitle: args.title === undefined,
|
|
229
|
+
...normalizeItemLegendConfig(args, encoding, 40),
|
|
230
|
+
...(args.overflow === undefined ? {} : { overflow: normalizeLegendOverflow(args.overflow) }),
|
|
211
231
|
domain: scale.domain,
|
|
212
232
|
...(discrete ? { count } : { sampling }),
|
|
213
233
|
inheritAppearance: args.inheritAppearance === true,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
border: normalizeLegendBorder(args.border),
|
|
217
|
-
titleVisible: true
|
|
234
|
+
inheritPosition: args.position === undefined,
|
|
235
|
+
|
|
218
236
|
};
|
|
219
237
|
}
|
|
220
238
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import {validateLegendClip} from "../../../grammar/legendClip.js";
|
|
2
|
+
import { intrinsicLegendConfig } from "../../../materialization/legendGroups.js";
|
|
1
3
|
import { action, closedAction } from "../../../core/action.js";
|
|
2
4
|
import {
|
|
3
|
-
validateNonEmptyString,
|
|
4
5
|
validateKeys
|
|
5
6
|
} from "../../../core/validation.js";
|
|
6
7
|
import { mapContinuousScaleValues } from "../../../grammar/scales/index.js";
|
|
@@ -8,16 +9,16 @@ import { resolveLegendItemLayout } from "../../../layout/legendItems.js";
|
|
|
8
9
|
import { resolveLegendGraphicPlacement } from
|
|
9
10
|
"../../../materialization/graphicHierarchy.js";
|
|
10
11
|
import { findLayer } from "../../../selectors/layers.js";
|
|
11
|
-
import { DEFAULT_COLORS
|
|
12
|
+
import { DEFAULT_COLORS } from
|
|
12
13
|
"../../../theme/defaults.js";
|
|
13
14
|
import {
|
|
15
|
+
ITEM_LEGEND_LABELS as STROKE_WIDTH_LEGEND_LABELS,
|
|
16
|
+
ITEM_LEGEND_TITLE_STYLE as STROKE_WIDTH_LEGEND_TITLE_STYLE,
|
|
17
|
+
normalizeItemLegendConfig,
|
|
18
|
+
ITEM_LEGEND_OPTIONS as OPTIONS,
|
|
14
19
|
assertLegendBoundsInsideCanvas,
|
|
15
20
|
materializeItemLegend,
|
|
16
21
|
createItemLegendGraphics,
|
|
17
|
-
normalizeItemLegendLayout,
|
|
18
|
-
normalizeLegendBorder,
|
|
19
|
-
normalizeLegendTextOptions,
|
|
20
|
-
normalizeLegendTitleOptions,
|
|
21
22
|
resolveContinuousBounds,
|
|
22
23
|
resolveLegendBackgroundFromBounds,
|
|
23
24
|
formatContinuousValues,
|
|
@@ -29,29 +30,16 @@ import {
|
|
|
29
30
|
} from "./sampling.js";
|
|
30
31
|
import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
|
|
31
32
|
|
|
32
|
-
const OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
|
|
33
|
-
"direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
|
|
34
33
|
|
|
35
|
-
export const STROKE_WIDTH_LEGEND_LABELS = Object.freeze({
|
|
36
|
-
offset: 12,
|
|
37
|
-
color: DEFAULT_COLORS.text,
|
|
38
|
-
fontSize: 12,
|
|
39
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
40
|
-
fontWeight: "normal"
|
|
41
|
-
});
|
|
42
34
|
|
|
43
|
-
export
|
|
44
|
-
color: DEFAULT_COLORS.strongText,
|
|
45
|
-
fontSize: 13,
|
|
46
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
|
47
|
-
fontWeight: 600
|
|
48
|
-
});
|
|
35
|
+
export { STROKE_WIDTH_LEGEND_LABELS, STROKE_WIDTH_LEGEND_TITLE_STYLE };
|
|
49
36
|
|
|
50
37
|
export function isStrokeWidthLegendLayer(layer) {
|
|
51
38
|
return ["line", "rule"].includes(layer?.mark?.type) &&
|
|
52
39
|
layer.encoding?.strokeWidth?.scale !== undefined;
|
|
53
40
|
}
|
|
54
41
|
|
|
42
|
+
|
|
55
43
|
function resolveLayer(program, requested) {
|
|
56
44
|
const layer = selectLegendLayer(
|
|
57
45
|
program,
|
|
@@ -77,6 +65,8 @@ function requireScale(program, id) {
|
|
|
77
65
|
}
|
|
78
66
|
|
|
79
67
|
export function resolveStrokeWidthLegendLayout(program, config) {
|
|
68
|
+
validateLegendClip("strokeWidth", config);
|
|
69
|
+
config = intrinsicLegendConfig(program, config);
|
|
80
70
|
const effective = resolveEffectiveLegendBlockConfig(program, "strokeWidth", config);
|
|
81
71
|
const scale = requireScale(program, config.scale);
|
|
82
72
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
@@ -134,7 +124,6 @@ export const rematerializeStrokeWidthLegend = /* @__PURE__ */ closedAction(
|
|
|
134
124
|
|
|
135
125
|
export function resolveStrokeWidthLegendConfig(program, args = {}) {
|
|
136
126
|
validateKeys(args, OPTIONS, "createStrokeWidthLegend");
|
|
137
|
-
if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
|
|
138
127
|
const layer = resolveLayer(program, args.target);
|
|
139
128
|
const encoding = layer.encoding.strokeWidth;
|
|
140
129
|
requireScale(program, encoding.scale);
|
|
@@ -145,14 +134,9 @@ export function resolveStrokeWidthLegendConfig(program, args = {}) {
|
|
|
145
134
|
return {
|
|
146
135
|
target: layer.id,
|
|
147
136
|
scale: encoding.scale,
|
|
148
|
-
...
|
|
149
|
-
title: args.title ?? encoding.field,
|
|
150
|
-
inferredTitle: args.title === undefined,
|
|
137
|
+
...normalizeItemLegendConfig(args, encoding, 32),
|
|
151
138
|
sampling,
|
|
152
|
-
|
|
153
|
-
titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", STROKE_WIDTH_LEGEND_TITLE_STYLE),
|
|
154
|
-
border: normalizeLegendBorder(args.border),
|
|
155
|
-
titleVisible: true
|
|
139
|
+
|
|
156
140
|
};
|
|
157
141
|
}
|
|
158
142
|
|
|
@@ -3,18 +3,8 @@ import {
|
|
|
3
3
|
LEGEND_CHANNELS,
|
|
4
4
|
LEGEND_CONFIG_KINDS
|
|
5
5
|
} from "../../../core/vocabulary.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
export function validateLegendChannels(channels, operation) {
|
|
9
|
-
if (!Array.isArray(channels)) throw new TypeError(`${operation} channels must be an array.`);
|
|
10
|
-
if (channels.length === 0) throw new Error(`${operation} channels must select at least one channel.`);
|
|
11
|
-
const seen = new Set();
|
|
12
|
-
for (const channel of channels) {
|
|
13
|
-
if (!LEGEND_CHANNELS.includes(channel)) throw new Error(`Unsupported legend channel "${channel}".`);
|
|
14
|
-
if (seen.has(channel)) throw new Error(`${operation} channels contains duplicate "${channel}".`);
|
|
15
|
-
seen.add(channel);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
6
|
+
import { validateLegendChannels, describeLegendBlock } from "../../../grammar/legendBlocks.js";
|
|
7
|
+
export { validateLegendChannels, describeLegendBlock, legendBlockKey, legendBlockChannels } from "../../../grammar/legendBlocks.js";
|
|
18
8
|
|
|
19
9
|
export function legendTargets(program) {
|
|
20
10
|
return [...new Set(Object.values(program.guideConfigs.legend ?? {})
|
|
@@ -40,73 +30,6 @@ export function resolveLegendTarget(program, requested, operation) {
|
|
|
40
30
|
return targets[0];
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
const CATEGORICAL_KINDS = Object.freeze(["series", "color", "stroke"]);
|
|
44
|
-
const SINGLE_CHANNELS = Object.freeze({
|
|
45
|
-
size: "size",
|
|
46
|
-
opacity: "opacity",
|
|
47
|
-
strokeWidth: "strokeWidth",
|
|
48
|
-
gradient: "color",
|
|
49
|
-
interval: "color",
|
|
50
|
-
strokeGradient: "stroke",
|
|
51
|
-
strokeInterval: "stroke"
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const asciiCompare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
55
|
-
|
|
56
|
-
export function legendBlockChannels(kind, config) {
|
|
57
|
-
const channels = CATEGORICAL_KINDS.includes(kind)
|
|
58
|
-
? config?.channels
|
|
59
|
-
: SINGLE_CHANNELS[kind] === undefined
|
|
60
|
-
? undefined
|
|
61
|
-
: [SINGLE_CHANNELS[kind]];
|
|
62
|
-
if (!Array.isArray(channels) || channels.length === 0) {
|
|
63
|
-
throw new Error(`Legend block ${kind} requires at least one channel.`);
|
|
64
|
-
}
|
|
65
|
-
validateLegendChannels(channels, `Legend block ${kind}`);
|
|
66
|
-
return Object.freeze([...channels].sort(asciiCompare));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function legendBlockKey(channels) {
|
|
70
|
-
if (!Array.isArray(channels) || channels.length === 0) {
|
|
71
|
-
throw new TypeError("Legend block key requires a non-empty channel array.");
|
|
72
|
-
}
|
|
73
|
-
return JSON.stringify([...channels].sort(asciiCompare));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function describeLegendBlock(program, kind, config) {
|
|
77
|
-
if (!LEGEND_CONFIG_KINDS.includes(kind)) {
|
|
78
|
-
throw new Error(`Unknown legend block kind "${kind}".`);
|
|
79
|
-
}
|
|
80
|
-
const channels = legendBlockChannels(kind, config);
|
|
81
|
-
const scaleIds = CATEGORICAL_KINDS.includes(kind)
|
|
82
|
-
? config?.scales
|
|
83
|
-
: [config?.scale];
|
|
84
|
-
if (!Array.isArray(scaleIds) || scaleIds.length === 0 ||
|
|
85
|
-
scaleIds.some(id => typeof id !== "string" || program.resolvedScales[id] === undefined)) {
|
|
86
|
-
throw new Error(`Legend block ${kind} requires resolved scales.`);
|
|
87
|
-
}
|
|
88
|
-
const family = CATEGORICAL_KINDS.includes(kind)
|
|
89
|
-
? "categorical"
|
|
90
|
-
: kind === "size"
|
|
91
|
-
? isDiscreteSizeScaleType(program.resolvedScales[scaleIds[0]].type)
|
|
92
|
-
? "discrete-size"
|
|
93
|
-
: "sampled"
|
|
94
|
-
: ["opacity", "strokeWidth"].includes(kind)
|
|
95
|
-
? "sampled"
|
|
96
|
-
: ["gradient", "strokeGradient"].includes(kind)
|
|
97
|
-
? "gradient"
|
|
98
|
-
: "interval";
|
|
99
|
-
return Object.freeze({
|
|
100
|
-
target: config.target,
|
|
101
|
-
kind,
|
|
102
|
-
family,
|
|
103
|
-
key: legendBlockKey(channels),
|
|
104
|
-
channels,
|
|
105
|
-
scaleIds: Object.freeze([...scaleIds]),
|
|
106
|
-
config
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
33
|
export function legendBlockDescriptors(program, target) {
|
|
111
34
|
const requested = validateUserId(target, "Legend target id");
|
|
112
35
|
const descriptors = LEGEND_CONFIG_KINDS.flatMap(kind => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { legendResourcePolicy } from "../../../materialization/guides/resources.js";
|
|
2
|
+
import { isContinuousColorScaleType } from "../../../grammar/scales/types.js";
|
|
1
3
|
import { isDiscretizedColorScaleType } from "../../../grammar/scales/types.js";
|
|
2
4
|
import { normalizeContinuousLegend } from "./continuous/common.js";
|
|
3
5
|
import { DEFAULT_GRADIENT_SIZE } from "./continuous/gradient.js";
|
|
@@ -8,8 +10,9 @@ import {
|
|
|
8
10
|
resolveLegendBlock
|
|
9
11
|
} from "./target.js";
|
|
10
12
|
import { validateLegendBlockOverride } from "./blocks.js";
|
|
13
|
+
import { DEFAULT_VERTICAL_LEGEND_ITEM_GAP } from "../../../theme/defaults.js";
|
|
11
14
|
|
|
12
|
-
const family = (channel, type) => type
|
|
15
|
+
const family = (channel, type) => isContinuousColorScaleType(type)
|
|
13
16
|
? channel === "stroke" ? "strokeGradient" : "gradient"
|
|
14
17
|
: isDiscretizedColorScaleType(type)
|
|
15
18
|
? channel === "stroke" ? "strokeInterval" : "interval"
|
|
@@ -21,7 +24,7 @@ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
|
21
24
|
function categoricalFamilyCustom(program, kind, stored) {
|
|
22
25
|
const side = ["left", "right"].includes(stored.position);
|
|
23
26
|
const direction = side ? "vertical" : "horizontal";
|
|
24
|
-
const itemGap = side ?
|
|
27
|
+
const itemGap = side ? DEFAULT_VERTICAL_LEGEND_ITEM_GAP : stored.position === "top" ? 24 : 20;
|
|
25
28
|
const order = program.semanticSpec.guides.legend?.[kind]?.order;
|
|
26
29
|
return stored.inferredSymbol !== true ||
|
|
27
30
|
stored.direction !== direction ||
|
|
@@ -40,7 +43,9 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
40
43
|
if (overrideKeys.some(key => key !== oldDescriptor.key)) {
|
|
41
44
|
throw new Error(`${channel} legend transition found stale legend block overrides.`);
|
|
42
45
|
}
|
|
43
|
-
const
|
|
46
|
+
const storedOverride = stored.blockOverrides?.[oldDescriptor.key];
|
|
47
|
+
const { symbol: _sampleSymbol, labelMap: _sampleMap, gap: _sampleGap, ...commonOverride } = storedOverride ?? {};
|
|
48
|
+
const blockOverride = stored.representation === "symbols" && storedOverride !== undefined ? commonOverride : storedOverride;
|
|
44
49
|
const side = ["left", "right"].includes(stored.position);
|
|
45
50
|
const categorical = ["color", "stroke"].includes(from);
|
|
46
51
|
const gradientFamily = from.toLowerCase().endsWith("gradient");
|
|
@@ -49,10 +54,11 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
49
54
|
const comparableSymbol = channel === "stroke" && stored.inferredStrokeWidth === true
|
|
50
55
|
? { ...stored.symbol, strokeWidth: defaults.symbol?.strokeWidth }
|
|
51
56
|
: stored.symbol;
|
|
52
|
-
const
|
|
57
|
+
const sampledColor = from === "gradient" && stored.representation === "symbols";
|
|
58
|
+
const custom = sampledColor ? false : categorical
|
|
53
59
|
? categoricalFamilyCustom(program, from, stored)
|
|
54
60
|
: gradientFamily
|
|
55
|
-
? stored.count !== defaults.count || !same(stored.gradient, DEFAULT_GRADIENT_SIZE) || stored.itemGap !== defaults.itemGap
|
|
61
|
+
? stored.gradientDirection !== undefined || stored.count !== defaults.count || !same({ ...stored.gradient, opacity: stored.gradient?.opacity ?? 1 }, { ...DEFAULT_GRADIENT_SIZE, opacity: 1 }) || stored.itemGap !== defaults.itemGap
|
|
56
62
|
: !same(comparableSymbol, defaults.symbol) || stored.itemGap !== defaults.itemGap ||
|
|
57
63
|
stored.direction !== defaults.direction || (stored.columns !== undefined && !(side && stored.columns === 1));
|
|
58
64
|
if (custom) {
|
|
@@ -70,7 +76,7 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
70
76
|
const args = {
|
|
71
77
|
target: stored.target, channels: [channel], position: stored.position,
|
|
72
78
|
labels: stored.labels, titleStyle: stored.titleStyle, border: stored.border,
|
|
73
|
-
align: stored.align, offset: stored.offset, titlePosition: stored.titlePosition,
|
|
79
|
+
align: stored.align, offset: stored.offset, titlePosition: sampledColor ? "top" : stored.titlePosition,
|
|
74
80
|
...(stored.inferredTitle ? {} : { title: stored.title })
|
|
75
81
|
};
|
|
76
82
|
try {
|
|
@@ -83,7 +89,7 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
|
|
|
83
89
|
} catch (error) {
|
|
84
90
|
throw new Error(`${channel} legend transition has incompatible layout or style: ${error.message}`);
|
|
85
91
|
}
|
|
86
|
-
return { args, channel, titleVisible: stored.titleVisible !== false, blockOverride };
|
|
92
|
+
return { args, channel, titleVisible: stored.titleVisible !== false, inferredOffset: stored.inferredOffset, blockOverride };
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
export function planColorLegendTransitions(program, scale, nextType) {
|
|
@@ -95,6 +101,11 @@ export function planColorLegendTransitions(program, scale, nextType) {
|
|
|
95
101
|
|
|
96
102
|
export function applyColorLegendTransition(program, plan) {
|
|
97
103
|
let next = program.createLegend(plan.args);
|
|
104
|
+
for (const [kind, config] of Object.entries(next.guideConfigs.legend ?? {})) {
|
|
105
|
+
if (config.target === plan.args.target && legendResourcePolicy(kind).semanticKind === plan.channel) {
|
|
106
|
+
next = next._withLegendConfig(kind, { ...config, inferredOffset: plan.inferredOffset === true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
98
109
|
if (!plan.titleVisible) {
|
|
99
110
|
next = next.editLegend({ target: plan.args.target, title: false });
|
|
100
111
|
}
|
|
@@ -150,7 +161,7 @@ function withoutBlockOverrides(config) {
|
|
|
150
161
|
return rest;
|
|
151
162
|
}
|
|
152
163
|
|
|
153
|
-
export function planLegendBlockTransitions(program, target, plans) {
|
|
164
|
+
export function planLegendBlockTransitions(program, target, plans, position) {
|
|
154
165
|
const oldDescriptors = legendBlockDescriptors(program, target);
|
|
155
166
|
const nextDescriptors = plans.map(plan =>
|
|
156
167
|
describeLegendBlock(program, plan.kind, plan.config));
|
|
@@ -158,19 +169,41 @@ export function planLegendBlockTransitions(program, target, plans) {
|
|
|
158
169
|
const descriptor = nextDescriptors[index];
|
|
159
170
|
const incoming = oldDescriptors.filter(old => intersects(old, descriptor));
|
|
160
171
|
let override;
|
|
161
|
-
if (
|
|
172
|
+
if (["size-shape","color-size"].includes(descriptor.config.combine) && incoming.length > 1) {
|
|
173
|
+
const positions = incoming.map(old=>old.config.blockOverrides?.[old.key]?.position);
|
|
174
|
+
if (position === undefined && !positions.every(value=>value===positions[0])) transitionError(target, incoming, descriptor, "position");
|
|
175
|
+
override = position === undefined && positions[0] !== undefined ? {position: positions[0]} : undefined;
|
|
176
|
+
} else if (incoming.length === 1 && incoming[0].key === descriptor.key) {
|
|
162
177
|
override = incoming[0].config.blockOverrides?.[incoming[0].key];
|
|
178
|
+
if (incoming[0].config.representation === "symbols" && descriptor.kind === "gradient" && descriptor.config.representation !== "symbols" && override !== undefined) {
|
|
179
|
+
const { symbol: _symbol, labelMap: _labelMap, ...common } = override; override = common;
|
|
180
|
+
}
|
|
163
181
|
} else if (incoming.length === 1) {
|
|
164
182
|
const old = incoming[0];
|
|
165
183
|
override = old.config.blockOverrides?.[old.key];
|
|
184
|
+
if (["size-shape","color-size"].includes(incoming[0].config.combine) && override !== undefined) {
|
|
185
|
+
const {title: _title, ...rest} = override; override = Object.keys(rest).length ? rest : undefined;
|
|
186
|
+
}
|
|
187
|
+
if(old.config.combine==='color-size'&&descriptor.kind==='gradient'&&override!==undefined){
|
|
188
|
+
const {symbol:_symbol,labelMap:_map,gap:_gap,...common}=override;override=Object.keys(common).length?common:undefined;
|
|
189
|
+
}
|
|
190
|
+
if(old.config.combine==='color-size'&&descriptor.kind==='size'){
|
|
191
|
+
const {type:_type,...style}=old.config.symbol;
|
|
192
|
+
override={...override,symbol:{...style,...override?.symbol}};
|
|
193
|
+
}
|
|
166
194
|
if (override?.title !== undefined) {
|
|
167
195
|
transitionError(target, incoming, descriptor, "title");
|
|
168
196
|
}
|
|
169
|
-
if (override?.labelMap !== undefined) {
|
|
197
|
+
if (override?.labelMap !== undefined && !(old.config.combine==='color-size'&&descriptor.kind==='size')) {
|
|
170
198
|
transitionError(target, incoming, descriptor, "labelMap");
|
|
171
199
|
}
|
|
172
200
|
} else if (incoming.length > 1) {
|
|
173
|
-
const overrides = incoming.map(old =>
|
|
201
|
+
const overrides = incoming.map(old => {
|
|
202
|
+
const override = old.config.blockOverrides?.[old.key];
|
|
203
|
+
if (position === undefined || override === undefined) return override;
|
|
204
|
+
const {position: _local, ...rest} = override;
|
|
205
|
+
return Object.keys(rest).length ? rest : undefined;
|
|
206
|
+
});
|
|
174
207
|
if (!overrides.every(value => sameBlockValue(value, overrides[0]))) {
|
|
175
208
|
transitionError(target, incoming, descriptor, "block override");
|
|
176
209
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { axisThemeTypography } from "../../../theme/state.js";
|
|
1
2
|
import { action } from "../../../../core/action.js";
|
|
2
3
|
import { validateNonNegativeFinite } from "../../../../core/validation.js";
|
|
3
4
|
import {
|
|
@@ -99,6 +100,7 @@ function resolveLabelConfig(program, kind, args, resources, previous) {
|
|
|
99
100
|
} else {
|
|
100
101
|
mode = normalizePolarTickMode(program, resources.scale, {}, count);
|
|
101
102
|
}
|
|
103
|
+
const typography = axisThemeTypography(program.materializationConfigs.theme, "labels");
|
|
102
104
|
const config = {
|
|
103
105
|
...(previous ?? {}),
|
|
104
106
|
scale: resources.scale,
|
|
@@ -110,9 +112,9 @@ function resolveLabelConfig(program, kind, args, resources, previous) {
|
|
|
110
112
|
format: args.format ?? previous?.format ??
|
|
111
113
|
POLAR_AXIS_DEFAULTS.labels.format,
|
|
112
114
|
color: args.color ?? previous?.color ?? POLAR_AXIS_DEFAULTS.labels.color,
|
|
113
|
-
fontSize: args.fontSize ?? previous?.fontSize ??
|
|
115
|
+
fontSize: args.fontSize ?? previous?.fontSize ?? typography.fontSize ??
|
|
114
116
|
POLAR_AXIS_DEFAULTS.labels.fontSize,
|
|
115
|
-
fontFamily: args.fontFamily ?? previous?.fontFamily ??
|
|
117
|
+
fontFamily: args.fontFamily ?? previous?.fontFamily ?? typography.fontFamily ??
|
|
116
118
|
POLAR_AXIS_DEFAULTS.labels.fontFamily,
|
|
117
119
|
fontWeight: args.fontWeight ?? previous?.fontWeight ??
|
|
118
120
|
POLAR_AXIS_DEFAULTS.labels.fontWeight
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { axisThemeTypography } from "../../../theme/state.js";
|
|
1
2
|
import { action } from "../../../../core/action.js";
|
|
2
3
|
import {
|
|
3
4
|
validateNonEmptyString,
|
|
@@ -55,7 +56,8 @@ function validateTitleGeometry(program, kind, config, geometry, text) {
|
|
|
55
56
|
}, program.materializationConfigs.textMetrics);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
function resolveTitleConfig(kind, args, resources, previous) {
|
|
59
|
+
function resolveTitleConfig(program, kind, args, resources, previous) {
|
|
60
|
+
const typography = axisThemeTypography(program.materializationConfigs.theme, "title");
|
|
59
61
|
const config = {
|
|
60
62
|
...(previous ?? {}),
|
|
61
63
|
scale: resources.scale,
|
|
@@ -65,9 +67,9 @@ function resolveTitleConfig(kind, args, resources, previous) {
|
|
|
65
67
|
? POLAR_AXIS_DEFAULTS.title.thetaOffset
|
|
66
68
|
: POLAR_AXIS_DEFAULTS.title.radiusOffset),
|
|
67
69
|
color: args.color ?? previous?.color ?? POLAR_AXIS_DEFAULTS.title.color,
|
|
68
|
-
fontSize: args.fontSize ?? previous?.fontSize ??
|
|
70
|
+
fontSize: args.fontSize ?? previous?.fontSize ?? typography.fontSize ??
|
|
69
71
|
POLAR_AXIS_DEFAULTS.title.fontSize,
|
|
70
|
-
fontFamily: args.fontFamily ?? previous?.fontFamily ??
|
|
72
|
+
fontFamily: args.fontFamily ?? previous?.fontFamily ?? typography.fontFamily ??
|
|
71
73
|
POLAR_AXIS_DEFAULTS.title.fontFamily,
|
|
72
74
|
fontWeight: args.fontWeight ?? previous?.fontWeight ??
|
|
73
75
|
POLAR_AXIS_DEFAULTS.title.fontWeight
|
|
@@ -106,7 +108,7 @@ function makeEditTitle(kind) {
|
|
|
106
108
|
scale: previous.scale,
|
|
107
109
|
coordinate: previous.coordinate
|
|
108
110
|
};
|
|
109
|
-
const config = resolveTitleConfig(kind, args, resources, previous);
|
|
111
|
+
const config = resolveTitleConfig(this, kind, args, resources, previous);
|
|
110
112
|
const text = Object.hasOwn(args, "text")
|
|
111
113
|
? validateNonEmptyString(args.text, "Polar axis title text")
|
|
112
114
|
: config.inferredText
|
|
@@ -150,7 +152,7 @@ function makeCreateTitle(kind) {
|
|
|
150
152
|
}
|
|
151
153
|
const resources = componentResources(this, kind, args, operation.create);
|
|
152
154
|
const angle = resolveAngle(this, kind, args);
|
|
153
|
-
const config = resolveTitleConfig(kind, args, resources);
|
|
155
|
+
const config = resolveTitleConfig(this, kind, args, resources);
|
|
154
156
|
const text = validateNonEmptyString(
|
|
155
157
|
args.text ?? inferAxisTitleText(this, names.channel, resources.scale),
|
|
156
158
|
"Polar axis title text"
|