ggaction 0.0.10 → 0.0.12
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 +97 -0
- package/README.md +5 -3
- package/knowledge/action-card.schema.json +1 -0
- package/knowledge/action-cards.json +1 -10941
- package/knowledge/action-cards.schema.json +1 -0
- package/knowledge/intent-taxonomy.json +1 -183
- package/knowledge/intent-taxonomy.schema.json +1 -0
- package/knowledge/mcp-resources.json +1 -95
- package/knowledge/mcp-resources.schema.json +1 -0
- package/knowledge/task-packet.schema.json +1 -159
- package/knowledge/task-resolver.js +1 -1230
- package/package.json +18 -2
- package/src/actions/boxPlots/edit.js +45 -298
- package/src/actions/boxPlots/options.js +46 -77
- package/src/actions/boxPlots/resolve.js +1 -1
- package/src/actions/charts/bar.js +3 -0
- package/src/actions/charts/heatmap.js +73 -83
- package/src/actions/charts/index.js +1 -1
- package/src/actions/charts/parallel.js +35 -1
- package/src/actions/charts/shared.js +20 -0
- package/src/actions/composition/actions.js +46 -49
- package/src/actions/data/bin2d.js +105 -2
- package/src/actions/data/box.js +17 -17
- package/src/actions/data/density.js +42 -78
- package/src/actions/data/gradientProfile.js +22 -52
- package/src/actions/data/horizon.js +16 -41
- package/src/actions/data/index.js +10 -10
- package/src/actions/data/interval.js +16 -38
- package/src/actions/data/intervalEdit.js +55 -0
- package/src/actions/data/regression.js +7 -17
- package/src/actions/data/shared.js +47 -0
- package/src/actions/data/timeUnit.js +14 -38
- package/src/actions/data/window.js +14 -38
- package/src/actions/distributions/revision.js +154 -34
- package/src/actions/encodings/angle.js +9 -10
- package/src/actions/encodings/appearance.js +17 -51
- package/src/actions/encodings/density/resolve.js +3 -1
- package/src/actions/encodings/density.js +1 -15
- package/src/actions/encodings/horizon/resolve.js +13 -3
- package/src/actions/encodings/horizon.js +8 -7
- package/src/actions/encodings/index.js +9 -9
- package/src/actions/encodings/offset.js +41 -45
- package/src/actions/encodings/parallel.js +9 -10
- package/src/actions/encodings/pathOrder.js +6 -13
- package/src/actions/encodings/position/apply.js +30 -0
- package/src/actions/encodings/position/policies/arc.js +23 -2
- package/src/actions/encodings/position/policies/index.js +4 -4
- package/src/actions/encodings/position/policies/line.js +47 -17
- package/src/actions/encodings/position/resolve.js +36 -7
- package/src/actions/encodings/ranged.js +18 -23
- package/src/actions/encodings/remove.js +15 -25
- package/src/actions/encodings/ruleAppearance.js +9 -24
- package/src/actions/encodings/shared.js +37 -0
- package/src/actions/encodings/strokeDash.js +8 -23
- package/src/actions/encodings/text.js +9 -10
- package/src/actions/errorBands/create.js +105 -39
- package/src/actions/errorBands/edit.js +89 -93
- package/src/actions/errorBands/index.js +1 -2
- package/src/actions/errorBars/create.js +212 -30
- package/src/actions/errorBars/edit.js +91 -48
- package/src/actions/errorBars/index.js +1 -2
- package/src/actions/facets/actions.js +45 -69
- package/src/actions/facets/derive.js +38 -53
- package/src/actions/facets/replay.js +0 -1
- package/src/actions/gradientPlots/components.js +59 -48
- package/src/actions/gradientPlots/edit.js +35 -216
- package/src/actions/gradientPlots/index.js +11 -10
- package/src/actions/gradientPlots/materialize.js +21 -35
- package/src/actions/gradientPlots/options.js +5 -7
- package/src/actions/gradientPlots/paint.js +9 -6
- package/src/actions/gradientPlots/resolve.js +1 -1
- package/src/actions/guides/axes/index.js +3 -3
- package/src/actions/guides/axes/labels.js +68 -26
- package/src/actions/guides/axes/lines.js +22 -2
- package/src/actions/guides/axes/policy.js +28 -8
- package/src/actions/guides/axes/ticks.js +27 -9
- package/src/actions/guides/axes/titles.js +113 -65
- package/src/actions/guides/grids/resolve.js +14 -0
- package/src/actions/guides/guides.js +2 -4
- package/src/actions/guides/index.js +2 -2
- package/src/actions/guides/legends/categorical/actions.js +44 -68
- package/src/actions/guides/legends/categorical/components.js +6 -1
- package/src/actions/guides/legends/categorical/index.js +2 -2
- package/src/actions/guides/legends/categorical/layout.js +148 -175
- package/src/actions/guides/legends/categorical/recipes.js +5 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -1
- package/src/actions/guides/legends/continuous/common.js +117 -66
- package/src/actions/guides/legends/continuous/gradient.js +38 -37
- package/src/actions/guides/legends/continuous/interval.js +97 -76
- package/src/actions/guides/legends/continuous/opacity.js +28 -38
- package/src/actions/guides/legends/edit.js +14 -1
- package/src/actions/guides/legends/index.js +1 -1
- package/src/actions/guides/legends/lane.js +2 -1
- package/src/actions/guides/legends/remove.js +8 -0
- package/src/actions/guides/legends/size.js +36 -43
- package/src/actions/guides/legends/strokeWidth.js +20 -26
- package/src/actions/guides/polar/axes/labels.js +17 -1
- package/src/actions/guides/polar/axes/shared.js +7 -1
- package/src/actions/guides/polar/axes/titles.js +19 -2
- package/src/actions/guides/polar/grids.js +4 -0
- package/src/actions/guides/polar/resolve.js +6 -0
- package/src/actions/index.js +13 -13
- package/src/actions/intervals/resolve.js +87 -117
- package/src/actions/marks/arc/actions.js +31 -36
- package/src/actions/marks/area/actions.js +23 -31
- package/src/actions/marks/index.js +6 -6
- package/src/actions/marks/line/actions.js +33 -52
- package/src/actions/marks/line/materialize.js +7 -5
- package/src/actions/marks/point/materialize.js +38 -16
- package/src/actions/marks/remove.js +21 -0
- package/src/actions/marks/rule/actions.js +79 -54
- package/src/actions/marks/shared.js +8 -4
- package/src/actions/marks/text/actions.js +14 -6
- package/src/actions/marks/text/layout.js +21 -45
- package/src/actions/marks/tick/actions.js +22 -21
- package/src/actions/primitives/createGraphics.js +4 -0
- package/src/actions/primitives/editGraphics.js +4 -0
- package/src/actions/primitives/index.js +3 -3
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +2 -2
- package/src/actions/primitives/semanticValidation/layer.js +1 -1
- package/src/actions/regression/edit.js +3 -43
- package/src/actions/scales/consumers/families.js +24 -4
- package/src/actions/scales/consumers/index.js +1 -1
- package/src/actions/scales/consumers/seriesLayout.js +17 -0
- package/src/actions/scales/definitions.js +100 -103
- package/src/actions/scales/materialize.js +5 -0
- package/src/actions/selection/actions.js +79 -121
- package/src/actions/titles/actions.js +48 -94
- package/src/actions/titles/resolve.js +14 -27
- package/src/actions/violinPlots/create.js +37 -44
- package/src/core/textMetrics.js +74 -37
- package/src/core/validation.js +18 -0
- package/src/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +48 -28
- package/src/grammar/arcs.js +170 -32
- package/src/grammar/areaSeries.js +14 -16
- package/src/grammar/bin2d.js +37 -23
- package/src/grammar/boxPlot.js +14 -13
- package/src/grammar/categoricalDensity.js +14 -14
- package/src/grammar/categoryOrder.js +8 -14
- package/src/grammar/curveCommands.js +87 -20
- package/src/grammar/density.js +162 -141
- package/src/grammar/facets/index.js +14 -0
- package/src/grammar/facets/scales.js +7 -4
- package/src/grammar/gradientProfile.js +12 -15
- package/src/grammar/histogram.js +145 -83
- package/src/grammar/horizon.js +60 -23
- package/src/grammar/interval.js +14 -13
- package/src/grammar/lineSeries.js +91 -61
- package/src/grammar/numeric.js +290 -0
- package/src/grammar/paint.js +3 -4
- package/src/grammar/palettes.js +21 -32
- package/src/grammar/pathCommands.js +6 -0
- package/src/grammar/polarPaths.js +5 -5
- package/src/grammar/positionCompatibility.js +1 -1
- package/src/grammar/regression/derive.js +48 -9
- package/src/grammar/regression/models.js +248 -69
- package/src/grammar/regression/parameters.js +8 -0
- package/src/grammar/rules.js +2 -0
- package/src/grammar/scales/color.js +35 -26
- package/src/grammar/scales/continuous.js +71 -24
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/discretized.js +42 -12
- package/src/grammar/scales/fields.js +21 -4
- package/src/grammar/scales/ordinal.js +139 -60
- package/src/grammar/scales/temporal.js +40 -11
- package/src/grammar/scales/transformed.js +199 -52
- package/src/grammar/schemas/graphicBounds.js +270 -79
- package/src/grammar/seriesLayout.js +50 -18
- package/src/grammar/ticks.js +123 -53
- package/src/grammar/timeUnit.js +18 -8
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/window.js +70 -57
- package/src/index.js +1 -1
- package/src/layout/canvas.js +4 -8
- package/src/layout/composition.js +39 -40
- package/src/layout/facets.js +55 -30
- package/src/layout/labels.js +91 -15
- package/src/layout/legend.js +22 -20
- package/src/layout/legendLane.js +54 -40
- package/src/layout/title.js +7 -7
- package/src/materialization/bars/aggregate.js +14 -40
- package/src/materialization/bars/grouped.js +7 -18
- package/src/materialization/bars/histogram.js +2 -19
- package/src/materialization/bars/ranged.js +20 -15
- package/src/materialization/bars/resolve.js +21 -0
- package/src/materialization/facetGuides/legacyCategorical.js +9 -2
- package/src/materialization/facetGuides/placement.js +49 -19
- package/src/materialization/facetGuides/preparation.js +35 -6
- package/src/materialization/facets.js +42 -28
- package/src/materialization/guides/resources.js +2 -1
- package/src/materialization/marks/capabilities.js +15 -4
- package/src/materialization/marks/index.js +29 -6
- package/src/materialization/rowEncoding.js +57 -1
- package/src/materialization/scales/policies/bar.js +61 -102
- package/src/materialization/scales/policies/binnedPosition.js +34 -0
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +13 -7
- package/src/materialization/scales/resolve.js +6 -9
- package/src/materialization/selection/items/arc.js +16 -2
- package/src/materialization/selection/items/bar.js +4 -2
- package/src/materialization/selection/items/common.js +21 -1
- package/src/materialization/selection/items/path.js +19 -2
- package/src/materialization/selection/items/point.js +4 -16
- package/src/materialization/selection/items/tick.js +13 -21
- package/src/materialization/selection/point.js +11 -4
- package/src/materialization/selection/styles.js +19 -27
- package/src/materialization/text.js +4 -1
- package/src/mcp/adapter.js +8 -3
- package/src/renderers/canvas/fill.js +2 -2
- package/src/renderers/canvas/index.js +35 -2
- package/src/renderers/canvas/native.js +160 -0
- package/src/renderers/pdf.js +14 -0
- package/src/renderers/svg.js +37 -4
- package/types/index.d.ts +3 -0
- package/types/program.d.ts +523 -131
- package/types/svg.d.ts +1 -0
- package/src/actions/errorBands/components.js +0 -81
- package/src/actions/errorBands/options.js +0 -39
- package/src/actions/errorBands/resolve.js +0 -28
- package/src/actions/errorBars/components.js +0 -62
- package/src/actions/errorBars/options.js +0 -58
- package/src/actions/errorBars/resolve.js +0 -25
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
} from "../../grammar/scales/index.js";
|
|
6
6
|
import { sameOrderedValues } from "../../core/validation.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
DEFAULT_BAR_STROKE_WIDTH
|
|
8
|
+
resolveBarAppearance
|
|
10
9
|
} from "./resolve.js";
|
|
11
10
|
import { resolveBarWidth } from "../../grammar/bars/geometry.js";
|
|
12
11
|
import { DEFAULT_SERIES_BASELINE } from "../../grammar/seriesLayout.js";
|
|
12
|
+
import { finiteMidpoint } from "../../grammar/numeric.js";
|
|
13
13
|
import {
|
|
14
14
|
BAR_ORIENTATIONS,
|
|
15
15
|
resolveBarChannels,
|
|
@@ -58,7 +58,10 @@ export function deriveGroupedRectangles(required, resolved, widthConfig) {
|
|
|
58
58
|
colorScale.range[index % colorScale.range.length]
|
|
59
59
|
])
|
|
60
60
|
);
|
|
61
|
-
const offsetMidpoint = (
|
|
61
|
+
const offsetMidpoint = finiteMidpoint(
|
|
62
|
+
offsetScale.range[0],
|
|
63
|
+
offsetScale.range[1]
|
|
64
|
+
);
|
|
62
65
|
const categoryDirection = Math.sign(
|
|
63
66
|
categoryScale.step ?? categoryScale.range[1] - categoryScale.range[0]
|
|
64
67
|
) || 1;
|
|
@@ -78,7 +81,6 @@ export function deriveGroupedRectangles(required, resolved, widthConfig) {
|
|
|
78
81
|
);
|
|
79
82
|
const existing = resolved.graphicSpec.objects[layer.id].items;
|
|
80
83
|
const config = resolved.markConfigs[layer.id] ?? {};
|
|
81
|
-
const appearance = config.barAppearance ?? {};
|
|
82
84
|
|
|
83
85
|
return cells.map((cell, index) => {
|
|
84
86
|
const categoryValue = cell[channels.category];
|
|
@@ -136,20 +138,7 @@ export function deriveGroupedRectangles(required, resolved, widthConfig) {
|
|
|
136
138
|
return {
|
|
137
139
|
...geometry,
|
|
138
140
|
fill: colors.get(cell.color),
|
|
139
|
-
|
|
140
|
-
? "transparent"
|
|
141
|
-
: appearance.stroke ?? config.stroke ?? existing[index]?.properties.stroke ?? DEFAULT_BAR_STROKE,
|
|
142
|
-
strokeWidth:
|
|
143
|
-
appearance.stroke === false
|
|
144
|
-
? 0
|
|
145
|
-
: appearance.strokeWidth ?? config.strokeWidth ??
|
|
146
|
-
existing[index]?.properties.strokeWidth ??
|
|
147
|
-
DEFAULT_BAR_STROKE_WIDTH,
|
|
148
|
-
...((appearance.opacity ?? config.opacity) === undefined
|
|
149
|
-
? (existing[index]?.properties.opacity === undefined
|
|
150
|
-
? {}
|
|
151
|
-
: { opacity: existing[index].properties.opacity })
|
|
152
|
-
: { opacity: appearance.opacity ?? config.opacity })
|
|
141
|
+
...resolveBarAppearance(config, existing[index]?.properties)
|
|
153
142
|
};
|
|
154
143
|
});
|
|
155
144
|
}
|
|
@@ -13,8 +13,7 @@ import { resolveBarColorLayout } from "../../grammar/bars/policy.js";
|
|
|
13
13
|
import { layoutSeriesPartition } from "../../grammar/seriesLayout.js";
|
|
14
14
|
import {
|
|
15
15
|
DEFAULT_BAR_FILL,
|
|
16
|
-
|
|
17
|
-
DEFAULT_BAR_STROKE_WIDTH
|
|
16
|
+
resolveBarAppearance
|
|
18
17
|
} from "./resolve.js";
|
|
19
18
|
|
|
20
19
|
export function deriveHistogramSegments({
|
|
@@ -141,23 +140,7 @@ export function deriveHistogramRectangles(required, resolved) {
|
|
|
141
140
|
config.fill ??
|
|
142
141
|
existing[index]?.properties.fill ??
|
|
143
142
|
DEFAULT_BAR_FILL,
|
|
144
|
-
|
|
145
|
-
appearance.stroke === false
|
|
146
|
-
? "transparent"
|
|
147
|
-
: appearance.stroke ?? config.stroke ??
|
|
148
|
-
existing[index]?.properties.stroke ??
|
|
149
|
-
DEFAULT_BAR_STROKE,
|
|
150
|
-
strokeWidth:
|
|
151
|
-
appearance.stroke === false
|
|
152
|
-
? 0
|
|
153
|
-
: appearance.strokeWidth ?? config.strokeWidth ??
|
|
154
|
-
existing[index]?.properties.strokeWidth ??
|
|
155
|
-
DEFAULT_BAR_STROKE_WIDTH,
|
|
156
|
-
...((appearance.opacity ?? config.opacity) === undefined
|
|
157
|
-
? (existing[index]?.properties.opacity === undefined
|
|
158
|
-
? {}
|
|
159
|
-
: { opacity: existing[index].properties.opacity })
|
|
160
|
-
: { opacity: appearance.opacity ?? config.opacity })
|
|
143
|
+
...resolveBarAppearance(config, existing[index]?.properties)
|
|
161
144
|
};
|
|
162
145
|
});
|
|
163
146
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { mapContinuousScaleValues, mapOrdinalPositionValues, mapOrdinalValues } from "../../grammar/scales/index.js";
|
|
2
2
|
import { resolveBarWidth } from "../../grammar/bars/geometry.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_BAR_FILL,
|
|
5
|
+
DEFAULT_BAR_STROKE,
|
|
6
|
+
resolveBarAppearance
|
|
7
|
+
} from "./resolve.js";
|
|
4
8
|
|
|
5
9
|
export function deriveRangedRectangles(required, program, width) {
|
|
6
10
|
const { layer, dataset } = required;
|
|
@@ -29,25 +33,26 @@ export function deriveRangedRectangles(required, program, width) {
|
|
|
29
33
|
const fills = color === undefined
|
|
30
34
|
? dataset.values.map(() => appearance.fill ?? config.fill ?? DEFAULT_BAR_FILL)
|
|
31
35
|
: mapOrdinalValues(dataset.values.map(row => row[color.field]), program.resolvedScales[color.scale].domain, program.resolvedScales[color.scale].range);
|
|
36
|
+
const appearanceConfig = color === undefined
|
|
37
|
+
? config
|
|
38
|
+
: { ...config, stroke: undefined };
|
|
32
39
|
return dataset.values.map((_, index) => vertical ? {
|
|
33
40
|
x: centers[index] - band / 2, y: Math.min(first[index], second[index]), width: band,
|
|
34
41
|
height: Math.abs(second[index] - first[index]), fill: fills[index],
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
opacity: appearance.opacity ?? config.opacity ?? 1
|
|
42
|
+
...resolveBarAppearance(
|
|
43
|
+
appearanceConfig,
|
|
44
|
+
undefined,
|
|
45
|
+
color === undefined ? DEFAULT_BAR_STROKE : fills[index],
|
|
46
|
+
1
|
|
47
|
+
)
|
|
42
48
|
} : {
|
|
43
49
|
x: Math.min(first[index], second[index]), y: centers[index] - band / 2,
|
|
44
50
|
width: Math.abs(second[index] - first[index]), height: band, fill: fills[index],
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
opacity: appearance.opacity ?? config.opacity ?? 1
|
|
51
|
+
...resolveBarAppearance(
|
|
52
|
+
appearanceConfig,
|
|
53
|
+
undefined,
|
|
54
|
+
color === undefined ? DEFAULT_BAR_STROKE : fills[index],
|
|
55
|
+
1
|
|
56
|
+
)
|
|
52
57
|
});
|
|
53
58
|
}
|
|
@@ -8,6 +8,27 @@ export const DEFAULT_BAR_FILL = DEFAULT_COLORS.mark;
|
|
|
8
8
|
export const DEFAULT_BAR_STROKE = "white";
|
|
9
9
|
export const DEFAULT_BAR_STROKE_WIDTH = 0.5;
|
|
10
10
|
|
|
11
|
+
export function resolveBarAppearance(
|
|
12
|
+
config,
|
|
13
|
+
existing,
|
|
14
|
+
defaultStroke = DEFAULT_BAR_STROKE,
|
|
15
|
+
defaultOpacity
|
|
16
|
+
) {
|
|
17
|
+
const appearance = config.barAppearance ?? {};
|
|
18
|
+
const opacity = appearance.opacity ?? config.opacity ??
|
|
19
|
+
existing?.opacity ?? defaultOpacity;
|
|
20
|
+
return {
|
|
21
|
+
stroke: appearance.stroke === false
|
|
22
|
+
? "transparent"
|
|
23
|
+
: appearance.stroke ?? config.stroke ?? existing?.stroke ?? defaultStroke,
|
|
24
|
+
strokeWidth: appearance.stroke === false
|
|
25
|
+
? 0
|
|
26
|
+
: appearance.strokeWidth ?? config.strokeWidth ??
|
|
27
|
+
existing?.strokeWidth ?? DEFAULT_BAR_STROKE_WIDTH,
|
|
28
|
+
...(opacity === undefined ? {} : { opacity })
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
11
32
|
export function requireCompleteBar(program, id) {
|
|
12
33
|
const layer = findLayer(program, id);
|
|
13
34
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveSharedFacetLegends
|
|
3
3
|
} from "../../grammar/facets/guides.js";
|
|
4
|
+
import { formatVisibleText } from "../../core/textMetrics.js";
|
|
4
5
|
import { mapOrdinalValues } from "../../grammar/scales/index.js";
|
|
5
6
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
|
|
6
7
|
|
|
@@ -10,7 +11,7 @@ function textItem(text, x, y, options = {}) {
|
|
|
10
11
|
properties: {
|
|
11
12
|
x,
|
|
12
13
|
y,
|
|
13
|
-
text:
|
|
14
|
+
text: formatVisibleText(text),
|
|
14
15
|
fill: options.color ?? DEFAULT_COLORS.strongText,
|
|
15
16
|
fontSize: options.fontSize ?? 12,
|
|
16
17
|
fontFamily: options.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
@@ -68,7 +69,13 @@ export function resolveLegacyCategoricalLegend(program) {
|
|
|
68
69
|
return {
|
|
69
70
|
mode: "legacyCategorical",
|
|
70
71
|
compatibility,
|
|
71
|
-
reservation: {
|
|
72
|
+
reservation: {
|
|
73
|
+
position: "right",
|
|
74
|
+
align: "center",
|
|
75
|
+
gap: 18,
|
|
76
|
+
width: 132,
|
|
77
|
+
height: 0
|
|
78
|
+
},
|
|
72
79
|
legend: {
|
|
73
80
|
field: config.field,
|
|
74
81
|
mark: encodings[0].layer.mark.type,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isPlainObject } from "../../core/immutable.js";
|
|
2
|
+
import { resolveConcreteGraphicBounds } from
|
|
3
|
+
"../../grammar/schemas/graphicBounds.js";
|
|
2
4
|
import { planFacetGuideOwnership } from "../../grammar/facets/guides.js";
|
|
3
5
|
import {
|
|
4
6
|
axisGraphicIds,
|
|
@@ -30,25 +32,29 @@ function removeNamespacedGraphics(program, namespace, ids) {
|
|
|
30
32
|
return next;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
function gradientAnchor(prepared, plot, layout) {
|
|
34
|
-
const strips = prepared.source.graphicSpec.objects.colorGradientStrips;
|
|
35
|
-
if (strips?.items?.[0] === undefined) return undefined;
|
|
36
|
-
const properties = strips.items[0].properties;
|
|
37
|
-
const length = prepared.source.guideConfigs.legend.gradient.gradient.length;
|
|
38
|
-
return {
|
|
39
|
-
sourceX: properties.x,
|
|
40
|
-
sourceY: properties.y,
|
|
41
|
-
targetX: layout.legend.x,
|
|
42
|
-
targetY: plot.y + (plot.height - length) / 2
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
35
|
function legendTranslation(prepared, plot, layout) {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
36
|
+
const position = prepared.reservation.position;
|
|
37
|
+
if (["top", "bottom"].includes(position)) {
|
|
38
|
+
const width = prepared.bounds.right - prepared.bounds.left;
|
|
39
|
+
const left = prepared.reservation.align === "left"
|
|
40
|
+
? plot.x
|
|
41
|
+
: prepared.reservation.align === "right"
|
|
42
|
+
? plot.x + plot.width - width
|
|
43
|
+
: plot.x + (plot.width - width) / 2;
|
|
49
44
|
return {
|
|
50
|
-
x:
|
|
51
|
-
y:
|
|
45
|
+
x: left - prepared.bounds.left,
|
|
46
|
+
y: layout.legend.y - prepared.bounds.top
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const strips = prepared.source.graphicSpec.objects.colorGradientStrips;
|
|
50
|
+
if (strips?.items?.[0] !== undefined) {
|
|
51
|
+
const properties = strips.items[0].properties;
|
|
52
|
+
const length = prepared.source.guideConfigs.legend.gradient.gradient.length;
|
|
53
|
+
return {
|
|
54
|
+
x: layout.legend.x - (position === "left"
|
|
55
|
+
? prepared.bounds.left
|
|
56
|
+
: properties.x),
|
|
57
|
+
y: plot.y + (plot.height - length) / 2 - properties.y
|
|
52
58
|
};
|
|
53
59
|
}
|
|
54
60
|
const height = prepared.bounds.bottom - prepared.bounds.top;
|
|
@@ -79,6 +85,25 @@ function attachParentLegend(program, prepared, plot, layout) {
|
|
|
79
85
|
return next;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
function assertSharedLegendFits(program, id, layout, plot) {
|
|
89
|
+
const bounds = resolveConcreteGraphicBounds(program.graphicSpec, id);
|
|
90
|
+
if (bounds === undefined) {
|
|
91
|
+
throw new Error("Facet shared legend requires measurable concrete bounds.");
|
|
92
|
+
}
|
|
93
|
+
const outside = bounds.left < 0 || bounds.right > layout.width ||
|
|
94
|
+
bounds.top < 0 || bounds.bottom > layout.height;
|
|
95
|
+
const overlapsPlot = Math.min(bounds.right, plot.x + plot.width) >
|
|
96
|
+
Math.max(bounds.left, plot.x) &&
|
|
97
|
+
Math.min(bounds.bottom, plot.y + plot.height) >
|
|
98
|
+
Math.max(bounds.top, plot.y);
|
|
99
|
+
if (outside || overlapsPlot) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"Facet shared legend requires more space than the composed Canvas provides."
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return program;
|
|
105
|
+
}
|
|
106
|
+
|
|
82
107
|
export function applyFacetGuideComposition(program, { layout, plot } = {}) {
|
|
83
108
|
if (!isPlainObject(layout) || !Array.isArray(layout.children)) {
|
|
84
109
|
throw new TypeError("Facet guide composition requires a resolved layout.");
|
|
@@ -119,8 +144,13 @@ export function applyFacetGuideComposition(program, { layout, plot } = {}) {
|
|
|
119
144
|
}
|
|
120
145
|
}
|
|
121
146
|
if (prepared === undefined) return next;
|
|
147
|
+
let id;
|
|
122
148
|
if (prepared.mode === "legacyCategorical") {
|
|
123
|
-
|
|
149
|
+
id = `${program.compositionSpec.id}-legend`;
|
|
150
|
+
next = materializeLegacyCategoricalLegend(next, prepared, layout);
|
|
151
|
+
} else {
|
|
152
|
+
id = `${program.compositionSpec.id}-shared-legend`;
|
|
153
|
+
next = attachParentLegend(next, prepared, plot, layout);
|
|
124
154
|
}
|
|
125
|
-
return
|
|
155
|
+
return assertSharedLegendFits(next, id, layout, plot);
|
|
126
156
|
}
|
|
@@ -9,6 +9,10 @@ import { findSemanticScale } from "../../selectors/scales.js";
|
|
|
9
9
|
import { allLegendGraphicIds } from "../guides/resources.js";
|
|
10
10
|
import { DEFAULT_COLORS } from "../../theme/defaults.js";
|
|
11
11
|
import { resolveLegacyCategoricalLegend } from "./legacyCategorical.js";
|
|
12
|
+
import {
|
|
13
|
+
formatDistinctNumericSamples,
|
|
14
|
+
sampleNumericRange
|
|
15
|
+
} from "../../grammar/numeric.js";
|
|
12
16
|
|
|
13
17
|
const LEGEND_SOURCE_CANVAS = Object.freeze({
|
|
14
18
|
width: 640,
|
|
@@ -71,10 +75,7 @@ function compactNumber(value) {
|
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
function continuousValues(domain, count) {
|
|
74
|
-
return
|
|
75
|
-
{ length: count },
|
|
76
|
-
(_, index) => domain[0] + index / (count - 1) * (domain[1] - domain[0])
|
|
77
|
-
);
|
|
78
|
+
return sampleNumericRange(domain[0], domain[1], count, "Facet legend domain");
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
function prepareAutoLegendSource(child) {
|
|
@@ -123,7 +124,10 @@ function prepareAutoLegendSource(child) {
|
|
|
123
124
|
.editGraphics({
|
|
124
125
|
target: "colorGradientLabels",
|
|
125
126
|
property: "text",
|
|
126
|
-
value:
|
|
127
|
+
value: formatDistinctNumericSamples(
|
|
128
|
+
continuousValues(resolved.domain, 5),
|
|
129
|
+
compactNumber
|
|
130
|
+
)
|
|
127
131
|
})
|
|
128
132
|
.editGraphics({
|
|
129
133
|
target: "colorGradientTitle",
|
|
@@ -145,6 +149,27 @@ function compatibleLegendChildren(program, source, inferred) {
|
|
|
145
149
|
});
|
|
146
150
|
}
|
|
147
151
|
|
|
152
|
+
function promotedLegendPlacement(source, kinds) {
|
|
153
|
+
const positioned = kinds.map(kind => source.guideConfigs.legend[kind])
|
|
154
|
+
.filter(config => config.position !== undefined);
|
|
155
|
+
const positions = new Set(positioned.map(config => config.position));
|
|
156
|
+
if (positions.size > 1) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"Shared facet legends must use one common edge position."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const aligns = new Set(positioned.map(config => config.align ?? "center"));
|
|
162
|
+
if (aligns.size > 1) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
"Shared facet legends must use one common alignment."
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
position: positioned[0]?.position ?? "right",
|
|
169
|
+
align: positioned[0]?.align ?? "center"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
148
173
|
export function prepareSharedFacetLegend(program) {
|
|
149
174
|
if (program.compositionSpec.facet.guides.legend !== "shared") {
|
|
150
175
|
return undefined;
|
|
@@ -172,6 +197,8 @@ export function prepareSharedFacetLegend(program) {
|
|
|
172
197
|
throw new Error("Shared facet legend has no measurable concrete bounds.");
|
|
173
198
|
}
|
|
174
199
|
const gradient = kinds.includes("gradient");
|
|
200
|
+
const placement = promotedLegendPlacement(source, kinds);
|
|
201
|
+
const width = Math.ceil(bounds.right - bounds.left);
|
|
175
202
|
return {
|
|
176
203
|
mode: "promoted",
|
|
177
204
|
source,
|
|
@@ -181,8 +208,10 @@ export function prepareSharedFacetLegend(program) {
|
|
|
181
208
|
bounds,
|
|
182
209
|
compatibility,
|
|
183
210
|
reservation: {
|
|
211
|
+
...placement,
|
|
184
212
|
gap: gradient ? 24 : 18,
|
|
185
|
-
width: gradient ? 112 :
|
|
213
|
+
width: Math.max(gradient ? 112 : 132, width),
|
|
214
|
+
height: Math.ceil(bounds.bottom - bounds.top)
|
|
186
215
|
}
|
|
187
216
|
};
|
|
188
217
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { resolveGraphicBounds } from "../layout/canvas.js";
|
|
2
|
+
import {
|
|
3
|
+
formatVisibleText,
|
|
4
|
+
resolveTextBounds,
|
|
5
|
+
textBoundsIntersect
|
|
6
|
+
} from "../core/textMetrics.js";
|
|
2
7
|
import { resolvePlacedPlotBounds } from "../layout/composition.js";
|
|
3
8
|
import {
|
|
4
9
|
alignedTextAnchor,
|
|
@@ -12,9 +17,8 @@ import {
|
|
|
12
17
|
clearCompositionChildren,
|
|
13
18
|
compositionChildDescriptor
|
|
14
19
|
} from "./composition.js";
|
|
15
|
-
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../theme/defaults.js";
|
|
16
20
|
|
|
17
|
-
const ZERO_MARGIN =
|
|
21
|
+
const ZERO_MARGIN = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
18
22
|
|
|
19
23
|
function facetConfig(program) {
|
|
20
24
|
const config = program.materializationConfigs.facets?.[program.compositionSpec.id];
|
|
@@ -46,29 +50,35 @@ function titleLayout(program) {
|
|
|
46
50
|
return { config, block, top, height: Math.ceil(top + block.height) };
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
function textItem(text, x, y, options = {}) {
|
|
50
|
-
return {
|
|
51
|
-
type: "text",
|
|
52
|
-
properties: {
|
|
53
|
-
x,
|
|
54
|
-
y,
|
|
55
|
-
text: String(text),
|
|
56
|
-
fill: options.color ?? DEFAULT_COLORS.strongText,
|
|
57
|
-
fontSize: options.fontSize ?? 12,
|
|
58
|
-
fontFamily: options.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
59
|
-
fontWeight: options.fontWeight ?? "normal",
|
|
60
|
-
textAlign: options.textAlign ?? "left",
|
|
61
|
-
textBaseline: options.textBaseline ?? "middle"
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
53
|
function collection(program, id, items) {
|
|
67
54
|
return program
|
|
68
55
|
.createGraphics({ id, type: "collection", parent: "canvas" })
|
|
69
56
|
.editGraphics({ target: id, property: "items", value: items });
|
|
70
57
|
}
|
|
71
58
|
|
|
59
|
+
function assertFacetHeadersFit(items, layout, plotById) {
|
|
60
|
+
const previous = [];
|
|
61
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
62
|
+
const bounds = resolveTextBounds(items[index].properties);
|
|
63
|
+
const cell = layout.children[index];
|
|
64
|
+
const plot = plotById.get(cell.id);
|
|
65
|
+
if (bounds.left < 0 || bounds.right > layout.width ||
|
|
66
|
+
bounds.top < 0 || bounds.bottom > layout.height ||
|
|
67
|
+
previous.some(item => textBoundsIntersect(item, bounds)) ||
|
|
68
|
+
textBoundsIntersect(bounds, {
|
|
69
|
+
left: cell.x + plot.x,
|
|
70
|
+
right: cell.x + plot.x + plot.width,
|
|
71
|
+
top: cell.y + plot.y,
|
|
72
|
+
bottom: cell.y + plot.y + plot.height
|
|
73
|
+
})) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Facet headers require sufficient non-overlapping space above every child plot."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
previous.push(bounds);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
function materializeHeaders(program, layout, plots, config) {
|
|
73
83
|
const plotById = new Map(plots.map(plot => [plot.id, plot]));
|
|
74
84
|
const items = layout.children.map(cell => {
|
|
@@ -76,20 +86,22 @@ function materializeHeaders(program, layout, plots, config) {
|
|
|
76
86
|
if (plot === undefined) {
|
|
77
87
|
throw new Error(`Facet header requires plot bounds for "${cell.id}".`);
|
|
78
88
|
}
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
return {
|
|
90
|
+
type: "text",
|
|
91
|
+
properties: {
|
|
92
|
+
x: cell.x + plot.x + plot.width / 2,
|
|
93
|
+
y: cell.y + config.offset,
|
|
94
|
+
text: formatVisibleText(cell.value),
|
|
95
|
+
fill: config.color,
|
|
85
96
|
fontSize: config.fontSize,
|
|
86
97
|
fontFamily: config.fontFamily,
|
|
87
98
|
fontWeight: config.fontWeight,
|
|
88
99
|
textAlign: "center",
|
|
89
|
-
textBaseline: "
|
|
100
|
+
textBaseline: "middle"
|
|
90
101
|
}
|
|
91
|
-
|
|
102
|
+
};
|
|
92
103
|
});
|
|
104
|
+
assertFacetHeadersFit(items, layout, plotById);
|
|
93
105
|
return collection(program, `${program.compositionSpec.id}-headers`, items);
|
|
94
106
|
}
|
|
95
107
|
|
|
@@ -163,7 +175,9 @@ export function resolveFacetProgramLayout(program, preparedLegend) {
|
|
|
163
175
|
sharedLegend: spec.facet.guides.legend === "shared",
|
|
164
176
|
...(preparedLegend === undefined ? {} : {
|
|
165
177
|
sharedLegendGap: preparedLegend.reservation.gap,
|
|
166
|
-
sharedLegendWidth: preparedLegend.reservation.width
|
|
178
|
+
sharedLegendWidth: preparedLegend.reservation.width,
|
|
179
|
+
sharedLegendHeight: preparedLegend.reservation.height,
|
|
180
|
+
sharedLegendPosition: preparedLegend.reservation.position
|
|
167
181
|
})
|
|
168
182
|
});
|
|
169
183
|
const plots = spec.children.map(id => ({
|
|
@@ -44,7 +44,8 @@ const LEGEND_RESOURCE_POLICIES = Object.freeze({
|
|
|
44
44
|
family: "interval",
|
|
45
45
|
rematerializeOp: "rematerializeIntervalLegend",
|
|
46
46
|
graphicIds: Object.freeze([
|
|
47
|
-
"
|
|
47
|
+
"colorLegendBackground", "colorLegendSymbols", "colorLegendLabels",
|
|
48
|
+
"colorLegendTitle"
|
|
48
49
|
])
|
|
49
50
|
}),
|
|
50
51
|
opacity: Object.freeze({
|
|
@@ -59,7 +59,10 @@ export function canMaterializeLine(program, layer) {
|
|
|
59
59
|
return (
|
|
60
60
|
layer.mark?.type === "line" &&
|
|
61
61
|
hasCartesianPositionScales(layer) &&
|
|
62
|
-
(isAggregate(layer.encoding.y.aggregate)
|
|
62
|
+
((isAggregate(layer.encoding.y.aggregate) &&
|
|
63
|
+
(layer.encoding.x.fieldType === "temporal" ||
|
|
64
|
+
(layer.encoding.x.fieldType === "quantitative" &&
|
|
65
|
+
layer.encoding.x.bin !== undefined))) ||
|
|
63
66
|
(layer.encoding.x.fieldType === "quantitative" &&
|
|
64
67
|
layer.encoding.y.fieldType === "quantitative") ||
|
|
65
68
|
((interval || layer.encoding.y.aggregate === undefined) &&
|
|
@@ -105,11 +108,19 @@ export function isIntentionallyEmptyArea(program, layer) {
|
|
|
105
108
|
export function canMaterializeArc(_program, layer) {
|
|
106
109
|
if (
|
|
107
110
|
layer.mark?.type !== "arc" ||
|
|
108
|
-
layer.encoding?.theta?.scale === undefined
|
|
109
|
-
!["nominal", "ordinal"].includes(layer.encoding.theta.fieldType)
|
|
111
|
+
layer.encoding?.theta?.scale === undefined
|
|
110
112
|
) {
|
|
111
113
|
return false;
|
|
112
114
|
}
|
|
115
|
+
if (layer.encoding.theta.fieldType === "quantitative") {
|
|
116
|
+
return (
|
|
117
|
+
layer.encoding.theta.aggregate === undefined &&
|
|
118
|
+
layer.encoding?.radius === undefined
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (!["nominal", "ordinal"].includes(layer.encoding.theta.fieldType)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
113
124
|
if (["count", "sum"].includes(layer.encoding.theta.aggregate)) {
|
|
114
125
|
return layer.encoding?.radius === undefined;
|
|
115
126
|
}
|
|
@@ -159,7 +170,7 @@ export function canMaterializeText(program, layer) {
|
|
|
159
170
|
if (layer.source !== undefined) {
|
|
160
171
|
const source = findLayer(program, layer.source);
|
|
161
172
|
return source !== undefined &&
|
|
162
|
-
["point", "bar", "rule", "rect"].includes(source.mark?.type) &&
|
|
173
|
+
["point", "bar", "rule", "rect", "arc"].includes(source.mark?.type) &&
|
|
163
174
|
Array.isArray(program.graphicSpec.objects[source.id]?.items);
|
|
164
175
|
}
|
|
165
176
|
return hasCartesianPositionScales(layer);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
POSITION_CHANNELS,
|
|
3
|
+
POSITION_ENCODING_CHANNELS
|
|
4
|
+
} from "../../core/vocabulary.js";
|
|
2
5
|
import { getMarkMaterializationPolicy } from "./policies.js";
|
|
3
6
|
|
|
4
7
|
export {
|
|
@@ -42,20 +45,40 @@ export function getSourceDependentMarkSteps(program, sourceId) {
|
|
|
42
45
|
export function getPositionEncodingMaterializationSteps(program, layer, scaleId) {
|
|
43
46
|
const policy = getMarkMaterializationPolicy(layer);
|
|
44
47
|
if (policy === undefined) return [];
|
|
45
|
-
const scale = { op: "rematerializeScale", args: { id: scaleId } };
|
|
46
48
|
const complete = policy.canMaterialize(program, layer);
|
|
47
49
|
const mark = getMarkRematerializationStep(layer);
|
|
50
|
+
const sharedConsumerMarks = (program.semanticSpec.layers ?? [])
|
|
51
|
+
.filter(candidate =>
|
|
52
|
+
candidate.id !== layer.id &&
|
|
53
|
+
POSITION_CHANNELS.some(channel =>
|
|
54
|
+
candidate.encoding?.[channel]?.scale === scaleId
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
.map(candidate => getMarkMaterializationStep(program, candidate))
|
|
58
|
+
.filter(step => step !== undefined);
|
|
59
|
+
const scale = {
|
|
60
|
+
op: "rematerializeScale",
|
|
61
|
+
args: sharedConsumerMarks.length === 0
|
|
62
|
+
? { id: scaleId }
|
|
63
|
+
: { id: scaleId, marks: false }
|
|
64
|
+
};
|
|
48
65
|
if (!complete) {
|
|
49
|
-
if (policy.positionEncoding.incomplete === "scale")
|
|
50
|
-
|
|
66
|
+
if (policy.positionEncoding.incomplete === "scale") {
|
|
67
|
+
return [scale, ...sharedConsumerMarks];
|
|
68
|
+
}
|
|
69
|
+
return policy.positionEncoding.scaleFirst
|
|
70
|
+
? [scale, mark, ...sharedConsumerMarks]
|
|
71
|
+
: [mark, ...sharedConsumerMarks];
|
|
51
72
|
}
|
|
52
|
-
return policy.positionEncoding.scaleFirst
|
|
73
|
+
return policy.positionEncoding.scaleFirst
|
|
74
|
+
? [scale, mark, ...sharedConsumerMarks]
|
|
75
|
+
: [mark, ...sharedConsumerMarks];
|
|
53
76
|
}
|
|
54
77
|
|
|
55
78
|
export function getScaleConsumerMaterializationMode(layer, channel) {
|
|
56
79
|
const policy = getMarkMaterializationPolicy(layer);
|
|
57
80
|
if (policy === undefined) return "direct";
|
|
58
|
-
if (
|
|
81
|
+
if (POSITION_ENCODING_CHANNELS.includes(channel)) {
|
|
59
82
|
return policy.scaleApplication.position ?? policy.scaleApplication.default;
|
|
60
83
|
}
|
|
61
84
|
if (policy.scaleApplication.deferredChannels?.includes(channel)) {
|