ggaction 0.0.16 → 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 +66 -0
- package/README.md +3 -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 +10 -4
- 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/bin.js +1 -1
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/create.js +18 -5
- package/src/actions/data/density.js +1 -1
- package/src/actions/data/derived.js +15 -1
- package/src/actions/data/edit.js +20 -7
- package/src/actions/data/filter.js +15 -4
- package/src/actions/data/index.js +3 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/regression.js +6 -2
- package/src/actions/data/revise.js +37 -5
- package/src/actions/data/shared.js +33 -4
- package/src/actions/data/sort.js +21 -0
- package/src/actions/data/stack.js +3 -1
- package/src/actions/data/summary.js +1 -1
- package/src/actions/encodings/angle.js +4 -1
- package/src/actions/encodings/appearance.js +43 -31
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +5 -2
- package/src/actions/encodings/color/index.js +5 -2
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/offset.js +4 -1
- 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 +9 -3
- package/src/actions/encodings/ranged.js +23 -1
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/ruleAppearance.js +4 -1
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/stroke.js +4 -1
- package/src/actions/encodings/strokeDash.js +6 -1
- package/src/actions/encodings/text.js +12 -4
- 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 +17 -4
- package/src/actions/marks/bar/edit.js +18 -7
- 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/create.js +8 -1
- package/src/actions/marks/point/edit.js +13 -6
- package/src/actions/marks/point/materialize.js +4 -2
- package/src/actions/marks/rect/actions.js +26 -11
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +31 -12
- package/src/actions/marks/text/actions.js +42 -11
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/marks/tick/actions.js +23 -8
- package/src/actions/primitives/semanticAction.js +37 -3
- package/src/actions/primitives/semanticValidation/dataset.js +13 -1
- package/src/actions/primitives/semanticValidation/layer.js +26 -2
- package/src/actions/primitives/semanticValidation/scale.js +6 -0
- package/src/actions/regression/create.js +26 -5
- package/src/actions/regression/edit.js +50 -15
- package/src/actions/regression/follow.js +22 -0
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +20 -15
- package/src/actions/scales/consumers/common.js +2 -1
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -4
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/parallel.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +49 -2
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +83 -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/action.js +3 -2
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +5 -1
- 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 +41 -22
- 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 +382 -0
- package/src/grammar/density.js +53 -5
- 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/filter.js +133 -39
- 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 +34 -10
- package/src/grammar/itemMissing.js +59 -0
- 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 +63 -16
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/models.js +116 -59
- package/src/grammar/regression/parameters.js +69 -6
- 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 -190
- 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 +5 -3
- package/src/grammar/sort.js +88 -0
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +120 -45
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +25 -12
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspection.js +495 -0
- package/src/inspectionDescriptors.js +3 -0
- 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 +26 -3
- 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/revisionIdentity.js +19 -0
- 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 +72 -7
- 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 +17 -6
- 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 +94 -8
- 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/inspection.d.ts +17 -0
- package/types/program.d.ts +494 -112
|
@@ -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
|
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
let activeReplacements;
|
|
2
|
+
|
|
3
|
+
export function withLogicalDataReplacements(replacements, operation) {
|
|
4
|
+
const previous = activeReplacements;
|
|
5
|
+
activeReplacements = replacements;
|
|
6
|
+
try {
|
|
7
|
+
return operation();
|
|
8
|
+
} finally {
|
|
9
|
+
activeReplacements = previous;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function comparableDataIdentity(id) {
|
|
14
|
+
if (activeReplacements === undefined) return id;
|
|
15
|
+
for (const [before, after] of activeReplacements) {
|
|
16
|
+
if (after === id) return before;
|
|
17
|
+
}
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
@@ -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
|
}
|