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,185 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolvePlacedPlotBounds } from "../layout/composition.js";
|
|
3
|
-
import {
|
|
4
|
-
alignedTextAnchor,
|
|
5
|
-
buildTitleReadingBlock
|
|
6
|
-
} from "../layout/title.js";
|
|
7
|
-
import { resolveFacetLayout } from "../layout/facets.js";
|
|
8
|
-
import { namespaceGraphicSnapshot } from "./compositionSnapshot.js";
|
|
9
|
-
import { prepareSharedFacetLegend } from "./facetGuides/index.js";
|
|
10
|
-
import {
|
|
11
|
-
attachSnapshotObject,
|
|
12
|
-
clearCompositionChildren,
|
|
13
|
-
compositionChildDescriptor
|
|
14
|
-
} from "./composition.js";
|
|
15
|
-
import {
|
|
16
|
-
materializeFacetHeaders,
|
|
17
|
-
prepareFacetHeaders
|
|
18
|
-
} from "./facetHeaders.js";
|
|
19
|
-
|
|
20
|
-
const ZERO_MARGIN = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
21
|
-
|
|
22
|
-
function facetConfig(program) {
|
|
23
|
-
const config = program.materializationConfigs.facets?.[program.compositionSpec.id];
|
|
24
|
-
if (config === undefined) {
|
|
25
|
-
throw new Error(`Facet "${program.compositionSpec.id}" requires materialization config.`);
|
|
26
|
-
}
|
|
27
|
-
return config;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function titleLayout(program) {
|
|
31
|
-
if (program.semanticSpec.title.text === undefined) {
|
|
32
|
-
return { height: 0 };
|
|
33
|
-
}
|
|
34
|
-
const config = program.titleConfig;
|
|
35
|
-
if (config === undefined) {
|
|
36
|
-
throw new Error("Facet title requires chart title configuration.");
|
|
37
|
-
}
|
|
38
|
-
if (config.position !== "top") {
|
|
39
|
-
throw new Error("Facet parent title currently supports only top position.");
|
|
40
|
-
}
|
|
41
|
-
const block = buildTitleReadingBlock({
|
|
42
|
-
text: program.semanticSpec.title.text,
|
|
43
|
-
subtitle: program.semanticSpec.title.subtitle
|
|
44
|
-
}, config);
|
|
45
|
-
const top = 8 + config.offset;
|
|
46
|
-
if (top < 0) {
|
|
47
|
-
throw new Error("Facet parent title offset places text outside the Canvas.");
|
|
48
|
-
}
|
|
49
|
-
return { config, block, top, height: Math.ceil(top + block.height) };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function materializeTitleComponent(program, id, lines, centers, style, plot, top) {
|
|
53
|
-
if (lines.length === 0) return program;
|
|
54
|
-
const x = alignedTextAnchor(plot.x, plot.width, program.titleConfig.align);
|
|
55
|
-
const count = lines.length;
|
|
56
|
-
let next = program.createGraphics({
|
|
57
|
-
id,
|
|
58
|
-
type: "text",
|
|
59
|
-
...(count > 1 ? { length: count } : {}),
|
|
60
|
-
parent: "canvas"
|
|
61
|
-
});
|
|
62
|
-
for (const [property, value] of Object.entries({
|
|
63
|
-
x,
|
|
64
|
-
y: count === 1 ? top + centers[0] : centers.map(center => top + center),
|
|
65
|
-
text: count === 1 ? lines[0] : lines,
|
|
66
|
-
fill: style.color,
|
|
67
|
-
fontSize: style.fontSize,
|
|
68
|
-
fontFamily: style.fontFamily,
|
|
69
|
-
fontWeight: style.fontWeight,
|
|
70
|
-
textAlign: program.titleConfig.align,
|
|
71
|
-
textBaseline: "middle"
|
|
72
|
-
})) {
|
|
73
|
-
next = next.editGraphics({ target: id, property, value });
|
|
74
|
-
}
|
|
75
|
-
return next;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function materializeTitle(program, plot, title) {
|
|
79
|
-
let next = materializeTitleComponent(
|
|
80
|
-
program,
|
|
81
|
-
"chartTitle",
|
|
82
|
-
title.block.titleLines,
|
|
83
|
-
title.block.titleCenters,
|
|
84
|
-
title.config.titleStyle,
|
|
85
|
-
plot,
|
|
86
|
-
title.top
|
|
87
|
-
);
|
|
88
|
-
if (title.block.subtitleLines.length > 0) {
|
|
89
|
-
next = materializeTitleComponent(
|
|
90
|
-
next,
|
|
91
|
-
"chartSubtitle",
|
|
92
|
-
title.block.subtitleLines,
|
|
93
|
-
title.block.subtitleCenters,
|
|
94
|
-
title.config.subtitleStyle,
|
|
95
|
-
plot,
|
|
96
|
-
title.top
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
return next;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function resolveFacetProgramLayout(program, preparedLegend) {
|
|
103
|
-
program._assertCompositionProgram("resolveFacetProgramLayout");
|
|
104
|
-
if (program.compositionSpec.type !== "facet") {
|
|
105
|
-
throw new Error("resolveFacetProgramLayout requires a facet composition.");
|
|
106
|
-
}
|
|
107
|
-
const title = titleLayout(program);
|
|
108
|
-
const spec = program.compositionSpec;
|
|
109
|
-
const preparedHeaders = prepareFacetHeaders(
|
|
110
|
-
program,
|
|
111
|
-
facetConfig(program).headers
|
|
112
|
-
);
|
|
113
|
-
const gridCells = new Map(
|
|
114
|
-
(spec.facet.grid?.cells ?? []).map(cell => [cell.id, cell])
|
|
115
|
-
);
|
|
116
|
-
const layout = resolveFacetLayout({
|
|
117
|
-
children: spec.children.map((id, index) => ({
|
|
118
|
-
...compositionChildDescriptor(id, program.children[id]),
|
|
119
|
-
value: spec.facet.values[index],
|
|
120
|
-
...(gridCells.has(id) ? {
|
|
121
|
-
row: gridCells.get(id).row,
|
|
122
|
-
column: gridCells.get(id).column
|
|
123
|
-
} : {})
|
|
124
|
-
})),
|
|
125
|
-
columns: spec.columns,
|
|
126
|
-
gap: spec.gap,
|
|
127
|
-
align: spec.align,
|
|
128
|
-
padding: spec.padding,
|
|
129
|
-
titleHeight: title.height,
|
|
130
|
-
headerLayout: preparedHeaders.headerLayout,
|
|
131
|
-
sharedLegend: spec.facet.guides.legend === "shared",
|
|
132
|
-
...(preparedLegend === undefined ? {} : {
|
|
133
|
-
sharedLegendGap: preparedLegend.reservation.gap,
|
|
134
|
-
sharedLegendWidth: preparedLegend.reservation.width,
|
|
135
|
-
sharedLegendHeight: preparedLegend.reservation.height,
|
|
136
|
-
sharedLegendPosition: preparedLegend.reservation.position
|
|
137
|
-
})
|
|
138
|
-
});
|
|
139
|
-
const plots = spec.children.map(id => ({
|
|
140
|
-
id,
|
|
141
|
-
...resolveGraphicBounds(program.children[id])
|
|
142
|
-
}));
|
|
143
|
-
const plot = resolvePlacedPlotBounds({
|
|
144
|
-
placements: layout.children,
|
|
145
|
-
plots
|
|
146
|
-
});
|
|
147
|
-
return { layout, title, plot, plots, preparedHeaders };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function materializeFacetGraphics(program) {
|
|
151
|
-
const preparedLegend = prepareSharedFacetLegend(program);
|
|
152
|
-
const { layout, title, plot, plots, preparedHeaders } = resolveFacetProgramLayout(
|
|
153
|
-
program,
|
|
154
|
-
preparedLegend
|
|
155
|
-
);
|
|
156
|
-
const background = program.graphicSpec.objects.canvas?.properties
|
|
157
|
-
?.background ?? "white";
|
|
158
|
-
let next = clearCompositionChildren(program);
|
|
159
|
-
if (next.graphicSpec.objects.canvas === undefined) {
|
|
160
|
-
next = next.createGraphics({ id: "canvas", type: "canvas" });
|
|
161
|
-
}
|
|
162
|
-
for (const [property, value] of Object.entries({
|
|
163
|
-
width: layout.width,
|
|
164
|
-
height: layout.height,
|
|
165
|
-
background
|
|
166
|
-
})) {
|
|
167
|
-
next = next.editGraphics({ target: "canvas", property, value });
|
|
168
|
-
}
|
|
169
|
-
for (const placement of layout.children) {
|
|
170
|
-
const child = program.children[placement.id];
|
|
171
|
-
const snapshot = namespaceGraphicSnapshot(child.graphicSpec, {
|
|
172
|
-
namespace: `${program.compositionSpec.id}-${placement.id}`,
|
|
173
|
-
x: placement.x,
|
|
174
|
-
y: placement.y
|
|
175
|
-
});
|
|
176
|
-
next = attachSnapshotObject(next, snapshot, snapshot.order[0], "canvas");
|
|
177
|
-
}
|
|
178
|
-
next = materializeFacetHeaders(next, layout, plots, preparedHeaders);
|
|
179
|
-
next = next.composeFacetGuides({ layout, plot });
|
|
180
|
-
if (title.height > 0) next = materializeTitle(next, plot, title);
|
|
181
|
-
return next._withCanvasConfig({
|
|
182
|
-
margin: ZERO_MARGIN,
|
|
183
|
-
size: { width: "auto", height: "auto" }
|
|
184
|
-
});
|
|
185
|
-
}
|
|
1
|
+
import{resolveConcreteGraphicBounds as C}from"../grammar/schemas/graphicBounds.js";import{textBoundsFitCanvas as G,textBoundsIntersect as m,resolveTextBounds as F}from"../core/textMetrics.js";import{axisGraphicIds as k,allLegendGraphicIds as L}from"./guides/resources.js";import{resolveGraphicBounds as u}from"../layout/canvas.js";import{resolvePlacedPlotBounds as j}from"../layout/composition.js";import{alignedTextAnchor as P,buildTitleReadingBlock as z,paintedTitleComponent as T,resolveTitleComponentBounds as B,titleTextProperties as E}from"../layout/title.js";import{resolveFacetLayout as $}from"../layout/facets.js";import{namespaceGraphicId as b,namespaceGraphicSnapshot as M}from"./compositionSnapshot.js";import{prepareSharedFacetLegend as v}from"./facetGuides/index.js";import{resolvePromotedFacetLegendLayout as O}from"./facetGuides/layout.js";import{attachSnapshotObject as H,clearCompositionChildren as I,compositionChildDescriptor as q}from"./composition.js";import{materializeFacetHeaders as A,prepareFacetHeaders as R}from"./facetHeaders.js";const _={top:0,right:0,bottom:0,left:0};function W(e){const n=e.materializationConfigs.facets?.[e.compositionSpec.id];if(n===void 0)throw new Error(`Facet "${e.compositionSpec.id}" requires materialization config.`);return n}function D(e){if(e.semanticSpec.title.text===void 0)return{height:0};const n=e.titleConfig;if(n===void 0)throw new Error("Facet title requires chart title configuration.");if(n.position!=="top")throw new Error("Facet parent title currently supports only top position.");const i=z({text:e.semanticSpec.title.text,subtitle:e.semanticSpec.title.subtitle},n),o=8+n.offset;if(o<0)throw new Error("Facet parent title offset places text outside the Canvas.");return{config:n,block:i,top:o,height:Math.ceil(o+i.height)}}function w(e,n,i,o,l,d,s){if(i.length===0)return e;const r=P(d.x,d.width,e.titleConfig.align)+(e.titleConfig.alongOffset??0),t=T({x:r,y:o.map(h=>s+h),lines:i,textAlign:e.titleConfig.align}),c=B(t,l,e.materializationConfigs.textMetrics);if(!G(c,e.graphicSpec.objects.canvas.properties))throw new Error("Facet parent title places text outside the Canvas.");const a=t.lines.length;let p=e.createGraphics({id:n,type:"text",...a>1?{length:a}:{},parent:"canvas"});for(const[h,f]of Object.entries(E(t,l)))p=p.editGraphics({target:n,property:h,value:f});return p}function N(e,n,i){let o=w(e,"chartTitle",i.block.titleLines,i.block.titleCenters,i.config.titleStyle,n,i.top);return i.block.subtitleLines.length>0&&(o=w(o,"chartSubtitle",i.block.subtitleLines,i.block.subtitleCenters,i.config.subtitleStyle,n,i.top)),o}function Z(e,n){if(e._assertCompositionProgram("resolveFacetProgramLayout"),e.compositionSpec.type!=="facet")throw new Error("resolveFacetProgramLayout requires a facet composition.");n??=v(e);const i=D(e),o=e.compositionSpec,l=R(e,W(e).headers),d=new Map((o.facet.grid?.cells??[]).map(c=>[c.id,c]));let s=$({children:o.children.map((c,a)=>({...q(c,e.children[c]),value:o.facet.values[a],...d.has(c)?{row:d.get(c).row,column:d.get(c).column}:{}})),spacing:o.spacing,plots:o.children.map(c=>({id:c,...u(e.children[c])})),columns:o.columns,gap:o.gap,align:o.align,padding:o.padding,titleHeight:i.height,headerLayout:l.headerLayout,sharedLegend:o.facet.guides.legend==="shared"&&n?.mode!=="promoted",...n===void 0?{}:{sharedLegendGap:n.reservation.gap,sharedLegendWidth:n.reservation.width,sharedLegendHeight:n.reservation.height,sharedLegendPosition:n.reservation.position}});const r=o.children.map(c=>({id:c,...u(e.children[c])}));let t=j({placements:s.children,plots:r});return{layout:s,plot:t}=O(e,{layout:s,plot:t,plots:r,title:i,preparedHeaders:l,prepared:n}),{layout:s,title:i,plot:t,plots:r,preparedHeaders:l}}function J(e,n,i){const o=[],l=n.children.map((s,r)=>({id:s.id,left:s.x+i[r].x,right:s.x+i[r].x+i[r].width,top:s.y+i[r].y,bottom:s.y+i[r].y+i[r].height}));for(const s of n.children){const r=e.children[s.id],t=[...["x","y"].flatMap(k).filter(c=>/Labels|Title/.test(c)),...L(Object.keys(r.guideConfigs.legend??{}))];for(const c of t){const a=b(`${e.compositionSpec.id}-${s.id}`,c);if(e.graphicSpec.objects[a]===void 0)continue;const p=C(e.graphicSpec,a,e.materializationConfigs.textMetrics);p!==void 0&&o.push({owner:s.id,bounds:p})}}const d=e.graphicSpec.objects[`${e.compositionSpec.id}-headers`];for(const s of d?.items??[])s.properties.text!==""&&o.push({owner:"headers",bounds:F(s.properties,e.materializationConfigs.textMetrics)});for(let s=0;s<o.length;s+=1){const r=o[s];if(l.some(t=>t.id!==r.owner&&m(r.bounds,t))||o.slice(0,s).some(t=>t.owner!==r.owner&&m(r.bounds,t.bounds)))throw new Error("Plot spacing gap is too small for the retained facet guides or headers.")}}function se(e){const n=v(e),{layout:i,title:o,plot:l,plots:d,preparedHeaders:s}=Z(e,n),r=e.graphicSpec.objects.canvas?.properties?.background??"white";let t=I(e);t.graphicSpec.objects.canvas===void 0&&(t=t.createGraphics({id:"canvas",type:"canvas"}));for(const[c,a]of Object.entries({width:i.width,height:i.height,background:r}))t=t.editGraphics({target:"canvas",property:c,value:a});for(const c of i.children){const a=e.children[c.id],p=M(a.graphicSpec,{namespace:`${e.compositionSpec.id}-${c.id}`,x:c.x,y:c.y});if(t=H(t,p,p.order[0],"canvas"),i.spacing==="plot"){const h=p.order[0],f=b(`${e.compositionSpec.id}-${c.id}`,"plot-background"),y=u(a),g=t.graphicSpec.objects[h].children?.[0];t=t.editGraphics({target:h,property:"background",value:"transparent"}).createGraphics({id:f,type:"rect",parent:h,...g===void 0?{}:{before:g}});for(const[x,S]of Object.entries({...y,stroke:"none",strokeWidth:0,fill:a.graphicSpec.objects.canvas.properties.background??"transparent"}))t=t.editGraphics({target:f,property:x,value:S})}}return t=A(t,i,d,s),t=t.composeFacetGuides({layout:i,plot:l}),i.spacing==="plot"&&J(t,i,d),o.height>0&&(t=N(t,l,o)),t._withCanvasConfig({margin:_,size:{width:"auto",height:"auto"}})}export{se as materializeFacetGraphics,Z as resolveFacetProgramLayout};
|
|
@@ -56,7 +56,7 @@ export function resolveMarkGraphicPlacement(program, { data, markType }) {
|
|
|
56
56
|
: undefined;
|
|
57
57
|
if (transform?.type !== "regression") {
|
|
58
58
|
const axis = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children?.find(
|
|
59
|
-
id => /^(x|y)Axis/.test(id)
|
|
59
|
+
id => id === "plotBorder" || /^(x|y)Axis/.test(id)
|
|
60
60
|
);
|
|
61
61
|
return resolvePlotGraphicPlacement(
|
|
62
62
|
program,
|
|
@@ -1,57 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { unionConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
|
|
3
|
-
import { axisGraphicIds, legendResourcePolicies } from "./resources.js";
|
|
4
|
-
|
|
5
|
-
// Only domain-owned guides participate. Extension primitives may deliberately
|
|
6
|
-
// overlap, and a combined categorical/size legend is one occupied group.
|
|
7
|
-
export function resolveGuideCollisionBlocks(graphicSpec, guideConfigs, titleConfig, profile) {
|
|
8
|
-
const blocks = [];
|
|
9
|
-
const append = (id, kind, position, ids) => {
|
|
10
|
-
if (position === undefined) return;
|
|
11
|
-
const existing = ids.filter(id => graphicSpec.objects[id] !== undefined);
|
|
12
|
-
const bounds = unionConcreteGraphicBounds(graphicSpec, existing, profile);
|
|
13
|
-
if (bounds !== undefined) blocks.push({ id, kind, position, bounds });
|
|
14
|
-
};
|
|
15
|
-
append("chart title", "title", titleConfig?.position, ["chartTitle", "chartSubtitle"]);
|
|
16
|
-
for (const channel of ["x", "y"]) {
|
|
17
|
-
for (const id of axisGraphicIds(channel)) {
|
|
18
|
-
const component = id.slice(`${channel}Axis`.length).toLowerCase();
|
|
19
|
-
append(`${channel}-axis ${component}`, "axis", guideConfigs.axis?.[channel]?.[component]?.position, [id]);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const policies = legendResourcePolicies();
|
|
23
|
-
const configs = guideConfigs.legend ?? {};
|
|
24
|
-
const categorical = policies.find(policy => policy.family === "categorical" && configs[policy.kind] !== undefined);
|
|
25
|
-
const combined = categorical !== undefined && configs.size !== undefined &&
|
|
26
|
-
configs[categorical.kind].target === configs.size.target;
|
|
27
|
-
for (const policy of policies) {
|
|
28
|
-
const config = configs[policy.kind];
|
|
29
|
-
if (config === undefined || combined && policy.kind === "size") continue;
|
|
30
|
-
const ids = combined && policy === categorical
|
|
31
|
-
? [...policy.graphicIds, ...policies.find(item => item.kind === "size").graphicIds]
|
|
32
|
-
: policy.graphicIds;
|
|
33
|
-
append(`${policy.kind}${combined && policy === categorical ? "+size" : ""} legend`, "legend", config.position, ids);
|
|
34
|
-
}
|
|
35
|
-
return blocks;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Defer cross-guide checks while sibling materializers still carry old
|
|
40
|
-
// coordinates. The scope is transient and never survives a returned program.
|
|
41
|
-
export function withGuideLayoutTransaction(program, apply) {
|
|
42
|
-
if (program.context.deferGuideLayoutValidation === true) return apply(program);
|
|
43
|
-
const result = apply(program._withContext({ deferGuideLayoutValidation: true }));
|
|
44
|
-
const { deferGuideLayoutValidation: _deferred, ...context } = result.context;
|
|
45
|
-
if (Object.hasOwn(program.context, "deferGuideLayoutValidation")) {
|
|
46
|
-
context.deferGuideLayoutValidation = program.context.deferGuideLayoutValidation;
|
|
47
|
-
}
|
|
48
|
-
const next = result._clone({ context });
|
|
49
|
-
assertGuideCollisionBlocks(resolveGuideCollisionBlocks(next.graphicSpec, next.guideConfigs, next.titleConfig, next.materializationConfigs.textMetrics));
|
|
50
|
-
return next;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function withGuideLayoutValidation(implementation) {
|
|
54
|
-
return function (args) {
|
|
55
|
-
return withGuideLayoutTransaction(this, program => implementation.call(program, args));
|
|
56
|
-
};
|
|
57
|
-
}
|
|
1
|
+
import{needsLegendAxisReflow as c}from"./resources.js";import{getErrorDetails as u}from"../../core/diagnostics.js";import{planCanvasRematerialization as m}from"../layout.js";import{applyMaterializationPlan as p}from"../planner.js";import{assertGuideCollisionBlocks as g}from"../../layout/guideCollisions.js";import{resolveGuideCollisionBlocks as h}from"./resources.js";import{resolveGuideCollisionBlocks as k}from"./resources.js";function C(i,d){if(i.context.deferGuideLayoutValidation===!0)return d(i);const n=i.materializationConfigs.canvas?.plot;let o=i.materializationConfigs.canvas?.margin,e;for(let f=0;;f+=1)try{let t=i._withContext({deferGuideLayoutValidation:!0});f>0&&(t=t.editGraphics({target:"canvas",property:"width",value:n.width+o.left+o.right}).editGraphics({target:"canvas",property:"height",value:n.height+o.top+o.bottom})._withCanvasConfig({...t.materializationConfigs.canvas,margin:o}),t=p(t,m(t))),e=d(t),e.compositionSpec===void 0&&Object.values(e.guideConfigs.legend??{}).some(r=>["left","right"].includes(r.position))&&(e=e.rematerializeSideLegendLane()),e.compositionSpec===void 0&&c(i,e)&&(e=e.rematerializeHorizontalLegendLane()),e.compositionSpec===void 0&&Object.keys(e.guideConfigs.legendGroups??{}).length>0&&(e=e.rematerializeLegendGroups()),n!==void 0&&e.titleConfig!==void 0&&(e=e.rematerializeTitle());break}catch(t){const r=u(t)?.canvasOverflow;if(n===void 0||r===void 0||f>=16)throw t;o=Object.fromEntries(Object.keys(o).map(l=>[l,r[l]>0?Math.ceil((o[l]+r[l])*4)/4:o[l]]))}const{deferGuideLayoutValidation:v,...s}=e.context;Object.hasOwn(i.context,"deferGuideLayoutValidation")&&(s.deferGuideLayoutValidation=i.context.deferGuideLayoutValidation);const a=e._clone({context:s});return g(h(a.graphicSpec,a.guideConfigs,a.titleConfig,a.materializationConfigs.textMetrics)),a}function b(i){return function(d){return C(this,n=>i.call(n,d))}}export{k as resolveGuideCollisionBlocks,C as withGuideLayoutTransaction,b as withGuideLayoutValidation};
|
|
@@ -1,123 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const CATEGORICAL_COMPONENTS = Object.freeze([
|
|
4
|
-
"Symbols", "SymbolLines", "SymbolPoints", "SymbolSwatches",
|
|
5
|
-
"Labels", "Title", "Background"
|
|
6
|
-
]);
|
|
7
|
-
|
|
8
|
-
const LEGEND_RESOURCE_POLICIES = Object.freeze({
|
|
9
|
-
series: Object.freeze({
|
|
10
|
-
semanticKind: "series",
|
|
11
|
-
family: "categorical",
|
|
12
|
-
rematerializeOp: undefined,
|
|
13
|
-
graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
14
|
-
component => `seriesLegend${component}`
|
|
15
|
-
))
|
|
16
|
-
}),
|
|
17
|
-
color: Object.freeze({
|
|
18
|
-
semanticKind: "color",
|
|
19
|
-
family: "categorical",
|
|
20
|
-
rematerializeOp: undefined,
|
|
21
|
-
graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
22
|
-
component => `colorLegend${component}`
|
|
23
|
-
))
|
|
24
|
-
}),
|
|
25
|
-
stroke: Object.freeze({
|
|
26
|
-
semanticKind: "stroke",
|
|
27
|
-
family: "categorical",
|
|
28
|
-
rematerializeOp: undefined,
|
|
29
|
-
graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
30
|
-
component => `strokeLegend${component}`
|
|
31
|
-
))
|
|
32
|
-
}),
|
|
33
|
-
size: Object.freeze({
|
|
34
|
-
semanticKind: "size",
|
|
35
|
-
family: "size",
|
|
36
|
-
rematerializeOp: "rematerializeSizeLegend",
|
|
37
|
-
graphicIds: Object.freeze([
|
|
38
|
-
"sizeLegendBackground", "sizeLegendSymbols", "sizeLegendLabels", "sizeLegendTitle"
|
|
39
|
-
])
|
|
40
|
-
}),
|
|
41
|
-
gradient: Object.freeze({
|
|
42
|
-
semanticKind: "color",
|
|
43
|
-
family: "continuous",
|
|
44
|
-
rematerializeOp: "rematerializeGradientLegend",
|
|
45
|
-
graphicIds: Object.freeze([
|
|
46
|
-
"colorGradientBackground", "colorGradientStrips", "colorGradientTicks",
|
|
47
|
-
"colorGradientLabels", "colorGradientTitle"
|
|
48
|
-
])
|
|
49
|
-
}),
|
|
50
|
-
interval: Object.freeze({
|
|
51
|
-
semanticKind: "color",
|
|
52
|
-
family: "interval",
|
|
53
|
-
rematerializeOp: "rematerializeIntervalLegend",
|
|
54
|
-
graphicIds: Object.freeze([
|
|
55
|
-
"colorLegendBackground", "colorLegendSymbols", "colorLegendLabels",
|
|
56
|
-
"colorLegendTitle"
|
|
57
|
-
])
|
|
58
|
-
}),
|
|
59
|
-
strokeGradient: Object.freeze({
|
|
60
|
-
semanticKind: "stroke",
|
|
61
|
-
family: "continuous",
|
|
62
|
-
rematerializeOp: "rematerializeStrokeGradientLegend",
|
|
63
|
-
graphicIds: Object.freeze([
|
|
64
|
-
"strokeGradientBackground", "strokeGradientStrips", "strokeGradientTicks",
|
|
65
|
-
"strokeGradientLabels", "strokeGradientTitle"
|
|
66
|
-
])
|
|
67
|
-
}),
|
|
68
|
-
strokeInterval: Object.freeze({
|
|
69
|
-
semanticKind: "stroke",
|
|
70
|
-
family: "interval",
|
|
71
|
-
rematerializeOp: "rematerializeStrokeIntervalLegend",
|
|
72
|
-
graphicIds: Object.freeze([
|
|
73
|
-
"strokeIntervalBackground", "strokeIntervalSymbols",
|
|
74
|
-
"strokeIntervalLabels", "strokeIntervalTitle"
|
|
75
|
-
])
|
|
76
|
-
}),
|
|
77
|
-
opacity: Object.freeze({
|
|
78
|
-
semanticKind: "opacity",
|
|
79
|
-
family: "continuous",
|
|
80
|
-
rematerializeOp: "rematerializeOpacityLegend",
|
|
81
|
-
graphicIds: Object.freeze([
|
|
82
|
-
"opacityLegendBackground", "opacityLegendSymbols", "opacityLegendLabels",
|
|
83
|
-
"opacityLegendTitle"
|
|
84
|
-
])
|
|
85
|
-
}),
|
|
86
|
-
strokeWidth: Object.freeze({
|
|
87
|
-
semanticKind: "strokeWidth",
|
|
88
|
-
family: "strokeWidth",
|
|
89
|
-
rematerializeOp: "rematerializeStrokeWidthLegend",
|
|
90
|
-
graphicIds: Object.freeze([
|
|
91
|
-
"strokeWidthLegendBackground", "strokeWidthLegendSymbols", "strokeWidthLegendLabels",
|
|
92
|
-
"strokeWidthLegendTitle"
|
|
93
|
-
])
|
|
94
|
-
})
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
export function axisGraphicIds(channel) {
|
|
98
|
-
const prefix = channel === "radius" ? "radial" : channel;
|
|
99
|
-
return AXIS_COMPONENTS.map(component => `${prefix}Axis${component}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function legendGraphicIds(kind) {
|
|
103
|
-
return legendResourcePolicy(kind).graphicIds;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function legendResourcePolicy(kind) {
|
|
107
|
-
const policy = LEGEND_RESOURCE_POLICIES[kind];
|
|
108
|
-
if (policy === undefined) {
|
|
109
|
-
throw new Error(`Unknown legend graphic kind "${kind}".`);
|
|
110
|
-
}
|
|
111
|
-
return policy;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function legendResourcePolicies() {
|
|
115
|
-
return Object.entries(LEGEND_RESOURCE_POLICIES).map(([kind, policy]) => ({
|
|
116
|
-
kind,
|
|
117
|
-
...policy
|
|
118
|
-
}));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function allLegendGraphicIds(kinds) {
|
|
122
|
-
return [...new Set(kinds.flatMap(legendGraphicIds))];
|
|
123
|
-
}
|
|
1
|
+
import{unionConcreteGraphicBounds as h}from"../../grammar/schemas/graphicBounds.js";function z(e,t,o,n){const r=[],s=(i,a,c,f)=>{if(c===void 0)return;const k=f.filter(L=>e.objects[L]!==void 0),b=h(e,k,n);b!==void 0&&r.push({id:i,kind:a,position:c,bounds:b})};s("chart title","title",o?.position,["chartTitle","chartSubtitle"]);for(const i of["x","y"])for(const a of I(i)){const c=a.slice(`${i}Axis`.length).toLowerCase();s(`${i}-axis ${c}`,"axis",t.axis?.[i]?.[c]?.position,[a])}const l=S(),d=t.legend??{},p=l.find(i=>i.family==="categorical"&&d[i.kind]!==void 0),g=p!==void 0&&d.size!==void 0&&d[p.kind].target===d.size.target&&(d.size.inheritPosition!==!1||d[p.kind].position===d.size.position),m=t.legendGroups??{};for(const[i,a]of Object.entries(m)){const c=l.filter(f=>d[f.kind]?.target===i).flatMap(f=>f.graphicIds);s(`${i} legend group`,"legend",a.placement.position??"free",c)}for(const i of l){const a=d[i.kind];if(m[a?.target]!==void 0||a===void 0||g&&i.kind==="size")continue;const c=g&&i===p?[...i.graphicIds,...l.find(f=>f.kind==="size").graphicIds]:i.graphicIds;s(`${i.kind}${g&&i===p?"+size":""} legend`,"legend",a.position,c)}return r}const y=Object.freeze(["Line","Ticks","Labels","Title"]),u=Object.freeze(["Symbols","SymbolLines","SymbolPoints","SymbolSwatches","Labels","Title","Background"]),O=Object.freeze({series:Object.freeze({semanticKind:"series",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(u.map(e=>`seriesLegend${e}`))}),color:Object.freeze({semanticKind:"color",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(u.map(e=>`colorLegend${e}`))}),stroke:Object.freeze({semanticKind:"stroke",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(u.map(e=>`strokeLegend${e}`))}),size:Object.freeze({semanticKind:"size",family:"size",rematerializeOp:"rematerializeSizeLegend",graphicIds:Object.freeze(["sizeLegendBackground","sizeLegendSymbols","sizeLegendLabels","sizeLegendTitle"])}),gradient:Object.freeze({semanticKind:"color",family:"continuous",rematerializeOp:"rematerializeGradientLegend",graphicIds:Object.freeze(["colorGradientBackground","colorGradientStrips","colorGradientTicks","colorGradientSymbols","colorGradientLabels","colorGradientTitle"])}),interval:Object.freeze({semanticKind:"color",family:"interval",rematerializeOp:"rematerializeIntervalLegend",graphicIds:Object.freeze(["colorLegendBackground","colorLegendSymbols","colorLegendLabels","colorLegendTitle"])}),strokeGradient:Object.freeze({semanticKind:"stroke",family:"continuous",rematerializeOp:"rematerializeStrokeGradientLegend",graphicIds:Object.freeze(["strokeGradientBackground","strokeGradientStrips","strokeGradientTicks","strokeGradientLabels","strokeGradientTitle"])}),strokeInterval:Object.freeze({semanticKind:"stroke",family:"interval",rematerializeOp:"rematerializeStrokeIntervalLegend",graphicIds:Object.freeze(["strokeIntervalBackground","strokeIntervalSymbols","strokeIntervalLabels","strokeIntervalTitle"])}),opacity:Object.freeze({semanticKind:"opacity",family:"continuous",rematerializeOp:"rematerializeOpacityLegend",graphicIds:Object.freeze(["opacityLegendBackground","opacityLegendSymbols","opacityLegendLabels","opacityLegendTitle"])}),strokeWidth:Object.freeze({semanticKind:"strokeWidth",family:"strokeWidth",rematerializeOp:"rematerializeStrokeWidthLegend",graphicIds:Object.freeze(["strokeWidthLegendBackground","strokeWidthLegendSymbols","strokeWidthLegendLabels","strokeWidthLegendTitle"])})});function I(e){const t=e==="radius"?"radial":e;return y.map(o=>`${t}Axis${o}`)}function j(e){return x(e).graphicIds}function x(e){const t=O[e];if(t===void 0)throw new Error(`Unknown legend graphic kind "${e}".`);return t}function S(){return Object.entries(O).map(([e,t])=>({kind:e,...t}))}function v(e){return[...new Set(e.flatMap(j))]}function C(e,t,o){if(t.inferredOffset!==!0||!["top","bottom"].includes(t.position)||t.layout==="legacy-bottom")return t.offset;const n=z(e.graphicSpec,e.guideConfigs,void 0,e.materializationConfigs.textMetrics).filter(s=>s.kind==="axis"&&s.position===t.position),r=t.position==="bottom"?Math.max(0,...n.map(s=>s.bounds.bottom-o.y-o.height)):Math.max(0,...n.map(s=>o.y-s.bounds.top));return t.offset+r}function G(e,t,o){if(t!=="size"||o.inheritPosition===!1)return o;const n=["series","color","stroke"].map(r=>e.guideConfigs.legend?.[r]).find(r=>r?.target===o.target);return n===void 0?o:{...o,position:n.position,offset:n.offset,inferredOffset:n.inferredOffset}}function $(e,t){return[e,t].some(n=>Object.entries(n.guideConfigs.legend??{}).some(([r,s])=>{const l=G(n,r,s);return l.inferredOffset===!0&&["top","bottom"].includes(l.position)&&l.layout!=="legacy-bottom"})||Object.values(n.guideConfigs.legendGroups??{}).some(r=>r.inferredOffset===!0&&["top","bottom"].includes(r.placement.position)))&&[e,t].some(n=>z(n.graphicSpec,n.guideConfigs,void 0,n.materializationConfigs.textMetrics).some(r=>r.kind==="axis"&&["top","bottom"].includes(r.position)))}export{v as allLegendGraphicIds,I as axisGraphicIds,G as effectiveLegendOffsetConfig,j as legendGraphicIds,S as legendResourcePolicies,x as legendResourcePolicy,$ as needsLegendAxisReflow,z as resolveGuideCollisionBlocks,C as resolveLegendAxisOffset};
|
|
@@ -1,4 +1,57 @@
|
|
|
1
|
+
import { canDeferScaleConsumerApplication, getLayerScaleIds, getMarkMaterializationStep } from "./marks/index.js";
|
|
2
|
+
import { buildMaterializationPlan } from "./planner.js";
|
|
3
|
+
import { hasMaterializedLegend } from "./legends.js";
|
|
4
|
+
import { needsCanvasScaleRematerialization, planScaleGuideRematerialization } from "./scaleGuideDependencies.js";
|
|
5
|
+
|
|
6
|
+
export function planCanvasRematerialization(program) {
|
|
7
|
+
const marks = [];
|
|
8
|
+
for (const layer of program.semanticSpec.layers) {
|
|
9
|
+
const step = getMarkMaterializationStep(program, layer);
|
|
10
|
+
if (step !== undefined) marks.push(step);
|
|
11
|
+
}
|
|
12
|
+
const deferredMarkIds = new Set(
|
|
13
|
+
marks
|
|
14
|
+
.map(step => step.args.id)
|
|
15
|
+
);
|
|
16
|
+
const scales = [];
|
|
17
|
+
for (const scale of program.semanticSpec.scales) {
|
|
18
|
+
if (needsCanvasScaleRematerialization(program, scale)) {
|
|
19
|
+
const deferredConsumers = program.semanticSpec.layers.filter(layer =>
|
|
20
|
+
canDeferScaleConsumerApplication(layer) &&
|
|
21
|
+
getLayerScaleIds(layer).includes(scale.id)
|
|
22
|
+
);
|
|
23
|
+
const canDeferMarks = deferredConsumers.length === 0 ||
|
|
24
|
+
deferredConsumers.every(layer => deferredMarkIds.has(layer.id));
|
|
25
|
+
scales.push({
|
|
26
|
+
op: "rematerializeScale",
|
|
27
|
+
args: {
|
|
28
|
+
id: scale.id,
|
|
29
|
+
guides: false,
|
|
30
|
+
...(canDeferMarks ? { marks: false } : {})
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const guides = program.semanticSpec.scales.flatMap(scale =>
|
|
36
|
+
needsCanvasScaleRematerialization(program, scale)
|
|
37
|
+
? planScaleGuideRematerialization(program, scale.id)
|
|
38
|
+
: []
|
|
39
|
+
);
|
|
40
|
+
if (hasMaterializedLegend(program)) {
|
|
41
|
+
guides.push({ op: "rematerializeLegend" });
|
|
42
|
+
}
|
|
43
|
+
const layout = planLayoutRematerialization(program);
|
|
44
|
+
return buildMaterializationPlan({ scales, marks, guides, layout });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
1
48
|
const LAYOUT_CONSUMER_POLICIES = Object.freeze([
|
|
49
|
+
Object.freeze({
|
|
50
|
+
applies(program) {
|
|
51
|
+
return program.compositionSpec === undefined && program.materializationConfigs?.canvas?.plotBorder !== undefined;
|
|
52
|
+
},
|
|
53
|
+
step: Object.freeze({ op: "rematerializePlotBorder" })
|
|
54
|
+
}),
|
|
2
55
|
Object.freeze({
|
|
3
56
|
applies(program) {
|
|
4
57
|
return program.semanticSpec.title.text !== undefined &&
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{PLOT_LEGEND_POSITIONS as u}from"../layout/legend.js";function p(r,t){return r.guideConfigs.legendGroups?.[t]}function c(r,t){const n=p(r,t.target);if(n===void 0)return t;const o=n.placement.position,e=u.includes(o),i=e?t.direction==="horizontal"?"top":"right":o??(u.includes(t.position)?t.direction==="horizontal"?"top":"right":t.position??"right");return{...t,position:i,offset:0,align:"left",groupIntrinsic:!0,...e?{groupCorner:!0}:{},...i==="right"?{align:"center"}:{}}}export{c as intrinsicLegendConfig,p as legendGroup};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{materializeTextLines as o}from"./textLines.js";import{resolveLegendTitleText as l}from"../layout/legendText.js";function m(e,i,t,n){const{lines:r}=l(t,n,e.materializationConfigs.textMetrics);return o(e,i,r)}export{m as materializeLegendTitleLines};
|
|
@@ -2,7 +2,7 @@ import { findLayer } from "../selectors/layers.js";
|
|
|
2
2
|
import { LEGEND_CONFIG_KINDS } from "../core/vocabulary.js";
|
|
3
3
|
|
|
4
4
|
export function isOpacityLegendLayer(layer) {
|
|
5
|
-
return ["point", "line"].includes(layer.mark?.type) &&
|
|
5
|
+
return ["point", "line", "bar"].includes(layer.mark?.type) &&
|
|
6
6
|
layer.encoding?.opacity?.scale !== undefined;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -60,10 +60,10 @@ export function canMaterializeLine(program, layer) {
|
|
|
60
60
|
layer.mark?.type === "line" &&
|
|
61
61
|
hasCartesianPositionScales(layer) &&
|
|
62
62
|
((isAggregate(layer.encoding.y.aggregate) &&
|
|
63
|
-
(layer.encoding.x.fieldType
|
|
63
|
+
(["temporal", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) ||
|
|
64
64
|
(layer.encoding.x.fieldType === "quantitative" &&
|
|
65
65
|
layer.encoding.x.bin !== undefined))) ||
|
|
66
|
-
(layer.encoding.x.fieldType
|
|
66
|
+
(["quantitative", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) &&
|
|
67
67
|
layer.encoding.y.fieldType === "quantitative") ||
|
|
68
68
|
((interval || layer.encoding.y.aggregate === undefined) &&
|
|
69
69
|
["quantitative", "temporal"].includes(layer.encoding.x.fieldType) &&
|
|
@@ -114,15 +114,14 @@ export function canMaterializeArc(_program, layer) {
|
|
|
114
114
|
}
|
|
115
115
|
if (layer.encoding.theta.fieldType === "quantitative") {
|
|
116
116
|
return (
|
|
117
|
-
layer.encoding.theta.aggregate === undefined
|
|
118
|
-
layer.encoding?.radius === undefined
|
|
117
|
+
layer.encoding.theta.aggregate === undefined
|
|
119
118
|
);
|
|
120
119
|
}
|
|
121
120
|
if (!["nominal", "ordinal"].includes(layer.encoding.theta.fieldType)) {
|
|
122
121
|
return false;
|
|
123
122
|
}
|
|
124
123
|
if (["count", "sum"].includes(layer.encoding.theta.aggregate)) {
|
|
125
|
-
return
|
|
124
|
+
return true;
|
|
126
125
|
}
|
|
127
126
|
return (
|
|
128
127
|
layer.encoding.theta.aggregate === undefined &&
|
|
@@ -136,7 +135,7 @@ export function canMaterializeBar(program, layer) {
|
|
|
136
135
|
return false;
|
|
137
136
|
}
|
|
138
137
|
const grain = resolveBarGrain(layer);
|
|
139
|
-
if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(grain)) {
|
|
138
|
+
if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged, BAR_GRAINS.centered].includes(grain)) {
|
|
140
139
|
return false;
|
|
141
140
|
}
|
|
142
141
|
if (
|
|
@@ -144,7 +144,7 @@ export function resolveRectRows(program, layer, dataset) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
export function resolveRectGraphicItems(program, layer, dataset) {
|
|
147
|
-
const radius = program.markConfigs[layer.id]
|
|
147
|
+
const radius = program.markConfigs[layer.id] ?? {};
|
|
148
148
|
return resolveRectRows(program, layer, dataset).map(item =>
|
|
149
149
|
materializeRectItem(item.properties, radius)
|
|
150
150
|
);
|
|
@@ -64,7 +64,7 @@ export function needsCanvasScaleRematerialization(program, scale) {
|
|
|
64
64
|
usesRadialScale(program, scale.id) ||
|
|
65
65
|
semanticGuideUsesScale(program, scale.id)) &&
|
|
66
66
|
(program.resolvedScales[scale.id] !== undefined || scale.radialMapping !== undefined) &&
|
|
67
|
-
usesPositionalScale(program, scale.id)
|
|
67
|
+
(usesPositionalScale(program, scale.id) || semanticGuideUsesScale(program, scale.id))
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -20,6 +20,9 @@ export function mapScaleConsumerValues(values, resolvedScale, channel) {
|
|
|
20
20
|
resolvedScale.domain,
|
|
21
21
|
resolvedScale.range,
|
|
22
22
|
{
|
|
23
|
+
type: resolvedScale.type,
|
|
24
|
+
base: resolvedScale.base,
|
|
25
|
+
constant: resolvedScale.constant,
|
|
23
26
|
interpolation: resolvedScale.interpolate,
|
|
24
27
|
midpoint: resolvedScale.midpoint,
|
|
25
28
|
clamp: resolvedScale.clamp ?? false,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveArcInnerRadius } from "../../../grammar/arcs.js";
|
|
1
2
|
import { validateMeasuredRadiusScale } from "../../../grammar/scales/radial.js";
|
|
2
3
|
export function resolveArcAutoPositionRange({
|
|
3
4
|
consumers,
|
|
@@ -15,16 +16,16 @@ export function resolveArcAutoPositionRange({
|
|
|
15
16
|
return range;
|
|
16
17
|
}
|
|
17
18
|
if (channel === "radius") {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const outer = Math.max(...range);
|
|
20
|
+
const inner = consumers.map(consumer => resolveArcInnerRadius(
|
|
21
|
+
markConfigs[consumer.layer.id]?.innerRadius ?? 0, outer
|
|
22
|
+
));
|
|
23
|
+
if (new Set(inner).size !== 1) {
|
|
22
24
|
throw new Error(
|
|
23
25
|
`Shared arc radius scale "${scale.id}" requires one innerRadius policy.`
|
|
24
26
|
);
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
return [outer * ratios[0], outer];
|
|
28
|
+
return [inner[0], outer];
|
|
28
29
|
}
|
|
29
30
|
if (
|
|
30
31
|
channel === "theta" &&
|
|
@@ -67,7 +68,7 @@ export function validateMeasuredRadiusConsumers({ scale, domain, range, consumer
|
|
|
67
68
|
const config = markConfigs[layer.id] ?? {};
|
|
68
69
|
if ((config.padAngle ?? 0) !== 0) throw new Error("Measured Arc requires padAngle 0.");
|
|
69
70
|
if (scale.range !== "auto" && config.innerRadiusExplicit === true &&
|
|
70
|
-
Math.abs(config.innerRadius - range[0] / range[1]) > Number.EPSILON * 8) {
|
|
71
|
+
Math.abs(resolveArcInnerRadius(config.innerRadius, range[1]) / range[1] - range[0] / range[1]) > Number.EPSILON * 8) {
|
|
71
72
|
throw new Error("Measured Arc innerRadius must agree with its explicit radius range.");
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -7,14 +7,19 @@ import {
|
|
|
7
7
|
interpolateNumber,
|
|
8
8
|
inverseLerp
|
|
9
9
|
} from "../../../grammar/numeric.js";
|
|
10
|
-
export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
10
|
+
export function resolveTemporalBarBand(valuesByConsumer, domain, range, markConfigs = {}, preserveRange = false) {
|
|
11
11
|
const temporalBars = valuesByConsumer.filter(({ consumer }) => {
|
|
12
12
|
const channels = resolveBarChannels(consumer.layer);
|
|
13
|
-
return resolveBarGrain(consumer.layer)
|
|
13
|
+
return [BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(resolveBarGrain(consumer.layer)) &&
|
|
14
14
|
channels?.category === consumer.channel &&
|
|
15
15
|
consumer.encoding.fieldType === "temporal";
|
|
16
16
|
});
|
|
17
17
|
if (temporalBars.length === 0) return undefined;
|
|
18
|
+
const intervalBars = temporalBars.filter(({consumer}) => markConfigs[consumer.layer.id]?.barWidth?.interval !== undefined);
|
|
19
|
+
if (intervalBars.length > 0) {
|
|
20
|
+
if (intervalBars.length !== temporalBars.length) throw new Error("Temporal interval and centered bar widths cannot share a position scale.");
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
18
23
|
const temporalFields = new Set(
|
|
19
24
|
temporalBars.map(({ consumer }) => consumer.encoding.field)
|
|
20
25
|
);
|
|
@@ -31,6 +36,11 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
|
31
36
|
"A temporal bar position scale requires compatible bar, line, rule, or text consumers of one field."
|
|
32
37
|
);
|
|
33
38
|
}
|
|
39
|
+
// Explicit pixel widths do not need an inferred half-slot inset. Keep one
|
|
40
|
+
// common range when a shared scale also has band-width bar consumers.
|
|
41
|
+
preserveRange ||= temporalBars.every(({ consumer }) =>
|
|
42
|
+
markConfigs[consumer.layer.id]?.barWidth?.pixels !== undefined
|
|
43
|
+
);
|
|
34
44
|
const values = temporalBars.flatMap(item => item.values);
|
|
35
45
|
const ordered = [...new Set(values)].sort((left, right) => left - right);
|
|
36
46
|
if (ordered.length < 2) {
|
|
@@ -59,7 +69,7 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
|
59
69
|
interpolateNumber(range[0], range[1], bandwidthFraction) - range[0]
|
|
60
70
|
)
|
|
61
71
|
: directBandwidth;
|
|
62
|
-
const resolvedRange = stable
|
|
72
|
+
const resolvedRange = preserveRange ? [...range] : stable
|
|
63
73
|
? [
|
|
64
74
|
interpolateNumber(range[0], range[1], bandwidthFraction / 2),
|
|
65
75
|
interpolateNumber(range[0], range[1], 1 - bandwidthFraction / 2)
|
|
@@ -79,10 +89,12 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
|
79
89
|
});
|
|
80
90
|
let bandwidth = Infinity;
|
|
81
91
|
for (let index = 1; index < positions.length; index += 1) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
const gap = Math.abs(positions[index] - positions[index - 1]);
|
|
93
|
+
// Keep a finite conservative slot when an explicitly requested span itself
|
|
94
|
+
// exceeds the representable difference of two finite endpoints.
|
|
95
|
+
bandwidth = Math.min(bandwidth,
|
|
96
|
+
preserveRange && !Number.isFinite(gap) ? estimatedBandwidth : gap);
|
|
97
|
+
|
|
86
98
|
}
|
|
87
99
|
if (![estimatedBandwidth, bandwidth, ...resolvedRange].every(Number.isFinite)) {
|
|
88
100
|
throw new RangeError("Temporal bar position exceeds the finite numeric range.");
|