ggaction 0.0.3 → 0.0.4
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 +28 -3
- package/README.md +95 -83
- package/package.json +11 -6
- package/src/actions/data/regression.js +1 -1
- package/src/actions/encodings/appearance.js +1 -1
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +151 -0
- package/src/actions/encodings/color/layout.js +91 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/index.js +2 -2
- package/src/actions/encodings/offset.js +1 -1
- package/src/actions/encodings/{position.js → position/index.js} +6 -6
- package/src/actions/encodings/position/resolve.js +1 -1
- package/src/actions/encodings/ranged.js +2 -2
- package/src/actions/encodings/strokeDash.js +1 -1
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/options.js +1 -1
- package/src/actions/errorBars/options.js +1 -1
- package/src/actions/facets/actions.js +1 -1
- package/src/actions/facets/derive.js +2 -2
- package/src/actions/facets/guides.js +1 -1
- package/src/actions/guides/axes/labels.js +1 -1
- package/src/actions/guides/axes/ticks.js +1 -1
- package/src/actions/guides/axes/titles.js +1 -1
- package/src/actions/guides/grids/resolve.js +1 -1
- package/src/actions/guides/legends/categorical/layout.js +4 -2
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +2 -20
- package/src/actions/guides/legends/remove.js +2 -20
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
- package/src/actions/guides/polar/index.js +1 -1
- package/src/actions/guides/polar/resolve.js +1 -1
- package/src/actions/guides/tickValues.js +1 -1
- package/src/actions/intervals/resolve.js +1 -1
- package/src/actions/marks/arc/actions.js +2 -2
- package/src/actions/marks/area/actions.js +2 -2
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/line/actions.js +3 -3
- package/src/actions/marks/rect/actions.js +1 -1
- package/src/actions/marks/rule/actions.js +1 -1
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +1 -1
- package/src/actions/primitives/semanticValue.js +1 -1
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +1 -1
- package/src/actions/scales/consumers/common.js +91 -0
- package/src/actions/scales/consumers/families.js +47 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +2 -2
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/materialize.js +2 -2
- package/src/core/ChartProgram.js +22 -236
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/bars/aggregate.js +1 -1
- package/src/grammar/density.js +1 -1
- package/src/grammar/{facets.js → facets/index.js} +5 -5
- package/src/grammar/interval.js +2 -96
- package/src/grammar/lineSeries.js +1 -1
- package/src/grammar/palettes.js +5 -7
- package/src/grammar/polarLineCommands.js +1 -1
- package/src/grammar/regression/derive.js +97 -0
- package/src/grammar/regression/index.js +22 -0
- package/src/grammar/regression/models.js +282 -0
- package/src/grammar/regression/parameters.js +106 -0
- package/src/grammar/rules.js +1 -1
- package/src/grammar/scales/color.js +4 -2
- package/src/grammar/statistics/studentT.js +98 -0
- package/src/grammar/transforms.js +1 -1
- package/src/materialization/bars/aggregate.js +1 -1
- package/src/materialization/bars/grouped.js +1 -1
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +6 -87
- package/src/materialization/encodings.js +2 -2
- package/src/materialization/facetGuides/index.js +2 -0
- package/src/materialization/facetGuides/legacyCategorical.js +125 -0
- package/src/materialization/facetGuides/placement.js +126 -0
- package/src/materialization/facetGuides/preparation.js +188 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/marks/capabilities.js +141 -0
- package/src/materialization/marks/index.js +105 -0
- package/src/materialization/marks/policies.js +87 -0
- package/src/materialization/rect.js +1 -1
- package/src/materialization/rowEncoding.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +94 -0
- package/src/materialization/scales/map.js +1 -1
- package/src/materialization/scales/policies/series.js +1 -1
- package/src/materialization/scales/resolve.js +1 -1
- package/src/materialization/selection/items/bar.js +1 -1
- package/src/materialization/selection/styles.js +1 -1
- package/src/renderers/canvas/text.js +14 -1
- package/types/extension.d.ts +4 -1
- package/src/actions/encodings/color.js +0 -424
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/facetGuides.js +0 -426
- package/src/materialization/marks.js +0 -316
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { isPlainObject } from "../core/immutable.js";
|
|
2
|
-
import { unionConcreteGraphicBounds } from
|
|
3
|
-
"../grammar/schemas/graphicBounds.js";
|
|
4
|
-
import {
|
|
5
|
-
planFacetGuideOwnership,
|
|
6
|
-
resolveSharedFacetLegends
|
|
7
|
-
} from "../grammar/facets/guides.js";
|
|
8
|
-
import { mapOrdinalValues } from "../grammar/scales.js";
|
|
9
|
-
import { findLayer } from "../selectors/layers.js";
|
|
10
|
-
import { findSemanticScale } from "../selectors/scales.js";
|
|
11
|
-
import { axisGraphicIds, allLegendGraphicIds } from
|
|
12
|
-
"./guides/resources.js";
|
|
13
|
-
import { attachSnapshotObject } from "./composition.js";
|
|
14
|
-
import { namespaceGraphicId } from "./compositionSnapshot.js";
|
|
15
|
-
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../theme/defaults.js";
|
|
16
|
-
|
|
17
|
-
const LEGEND_SOURCE_CANVAS = Object.freeze({
|
|
18
|
-
width: 640,
|
|
19
|
-
height: 400,
|
|
20
|
-
margin: Object.freeze({ top: 40, right: 170, bottom: 60, left: 70 })
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function legendKinds(program) {
|
|
24
|
-
return Object.keys(program.guideConfigs.legend ?? {});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function textItem(text, x, y, options = {}) {
|
|
28
|
-
return {
|
|
29
|
-
type: "text",
|
|
30
|
-
properties: {
|
|
31
|
-
x,
|
|
32
|
-
y,
|
|
33
|
-
text: String(text),
|
|
34
|
-
fill: options.color ?? DEFAULT_COLORS.strongText,
|
|
35
|
-
fontSize: options.fontSize ?? 12,
|
|
36
|
-
fontFamily: options.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
37
|
-
fontWeight: options.fontWeight ?? "normal",
|
|
38
|
-
textAlign: options.textAlign ?? "left",
|
|
39
|
-
textBaseline: options.textBaseline ?? "middle"
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function collection(program, id, items) {
|
|
45
|
-
return program
|
|
46
|
-
.createGraphics({ id, type: "collection", parent: "canvas" })
|
|
47
|
-
.editGraphics({ target: id, property: "items", value: items });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function resolveLegacyCategoricalLegend(program) {
|
|
51
|
-
const encodings = program.semanticSpec.layers.flatMap(layer =>
|
|
52
|
-
layer.encoding?.color === undefined
|
|
53
|
-
? []
|
|
54
|
-
: [{ layer, encoding: layer.encoding.color }]
|
|
55
|
-
);
|
|
56
|
-
if (encodings.length === 0) return undefined;
|
|
57
|
-
const fields = new Set(encodings.map(entry => entry.encoding.field));
|
|
58
|
-
const scales = new Set(encodings.map(entry => entry.encoding.scale));
|
|
59
|
-
const marks = new Set(encodings.map(entry => entry.layer.mark?.type));
|
|
60
|
-
if (fields.size !== 1 || scales.size !== 1 || marks.size !== 1) {
|
|
61
|
-
throw new Error(
|
|
62
|
-
"Shared facet legend requires one compatible color field, scale, and mark recipe."
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
if (encodings.some(entry =>
|
|
66
|
-
!["nominal", "ordinal"].includes(entry.encoding.fieldType)
|
|
67
|
-
)) {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
const firstId = program.compositionSpec.children[0];
|
|
71
|
-
const scaleId = encodings[0].encoding.scale;
|
|
72
|
-
const scale = program.children[firstId].resolvedScales[scaleId];
|
|
73
|
-
if (scale?.type !== "ordinal") return undefined;
|
|
74
|
-
const config = {
|
|
75
|
-
kind: "color",
|
|
76
|
-
target: encodings[0].layer.id,
|
|
77
|
-
field: encodings[0].encoding.field,
|
|
78
|
-
scale: scaleId,
|
|
79
|
-
title: encodings[0].encoding.field
|
|
80
|
-
};
|
|
81
|
-
const compatibility = resolveSharedFacetLegends(
|
|
82
|
-
program.compositionSpec.children.map(id => ({
|
|
83
|
-
id,
|
|
84
|
-
guideConfigs: { legend: { color: config } },
|
|
85
|
-
resolvedScales: program.children[id].resolvedScales
|
|
86
|
-
}))
|
|
87
|
-
);
|
|
88
|
-
return {
|
|
89
|
-
mode: "legacyCategorical",
|
|
90
|
-
compatibility,
|
|
91
|
-
reservation: { gap: 18, width: 132 },
|
|
92
|
-
legend: {
|
|
93
|
-
field: config.field,
|
|
94
|
-
mark: encodings[0].layer.mark.type,
|
|
95
|
-
domain: scale.domain,
|
|
96
|
-
colors: mapOrdinalValues(scale.domain, scale.domain, scale.range)
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function materializeLegacyCategoricalLegend(program, prepared, layout) {
|
|
102
|
-
const legend = prepared.legend;
|
|
103
|
-
const items = [textItem(legend.field, layout.legend.x, layout.legend.y, {
|
|
104
|
-
fontSize: 12,
|
|
105
|
-
fontWeight: 700,
|
|
106
|
-
textBaseline: "top"
|
|
107
|
-
})];
|
|
108
|
-
legend.domain.forEach((value, index) => {
|
|
109
|
-
const y = layout.legend.y + 29 + index * 26;
|
|
110
|
-
items.push(legend.mark === "point"
|
|
111
|
-
? {
|
|
112
|
-
type: "circle",
|
|
113
|
-
properties: {
|
|
114
|
-
x: layout.legend.x + 7,
|
|
115
|
-
y,
|
|
116
|
-
radius: 5.5,
|
|
117
|
-
fill: legend.colors[index],
|
|
118
|
-
stroke: "#ffffff",
|
|
119
|
-
strokeWidth: 0.35,
|
|
120
|
-
opacity: 1
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
: {
|
|
124
|
-
type: "rect",
|
|
125
|
-
properties: {
|
|
126
|
-
x: layout.legend.x + 1,
|
|
127
|
-
y: y - 6,
|
|
128
|
-
width: 12,
|
|
129
|
-
height: 12,
|
|
130
|
-
fill: legend.colors[index],
|
|
131
|
-
stroke: "#ffffff",
|
|
132
|
-
strokeWidth: 0.6,
|
|
133
|
-
opacity: 1
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
items.push(textItem(value, layout.legend.x + 22, y, {
|
|
137
|
-
fontSize: 10.5
|
|
138
|
-
}));
|
|
139
|
-
});
|
|
140
|
-
return collection(
|
|
141
|
-
program,
|
|
142
|
-
`${program.compositionSpec.id}-legend`,
|
|
143
|
-
items
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function legendCandidates(program) {
|
|
148
|
-
const layers = program.semanticSpec.layers;
|
|
149
|
-
const color = layers.filter(layer => {
|
|
150
|
-
const encoding = layer.encoding?.color;
|
|
151
|
-
return encoding?.scale !== undefined &&
|
|
152
|
-
findSemanticScale(program, encoding.scale) !== undefined;
|
|
153
|
-
});
|
|
154
|
-
if (color.length === 1) {
|
|
155
|
-
const layer = color[0];
|
|
156
|
-
return {
|
|
157
|
-
target: layer.id,
|
|
158
|
-
channels: ["color", "shape", "strokeDash"].filter(
|
|
159
|
-
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
160
|
-
)
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
if (color.length > 1) {
|
|
164
|
-
throw new Error(
|
|
165
|
-
"Shared facet legend requires one unambiguous color legend target."
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
for (const channel of ["shape", "strokeDash", "size", "opacity"]) {
|
|
169
|
-
const candidates = layers.filter(
|
|
170
|
-
layer => layer.encoding?.[channel]?.scale !== undefined
|
|
171
|
-
);
|
|
172
|
-
if (candidates.length === 1) {
|
|
173
|
-
return { target: candidates[0].id, channels: [channel] };
|
|
174
|
-
}
|
|
175
|
-
if (candidates.length > 1) {
|
|
176
|
-
throw new Error(
|
|
177
|
-
`Shared facet legend requires one unambiguous ${channel} legend target.`
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
throw new Error("Shared facet legend requires one eligible encoded scale.");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function compactNumber(value) {
|
|
185
|
-
if (Math.abs(value) >= 1_000_000_000) {
|
|
186
|
-
return `${+(value / 1_000_000_000).toFixed(1)}B`;
|
|
187
|
-
}
|
|
188
|
-
if (Math.abs(value) >= 1_000_000) {
|
|
189
|
-
return `${Math.round(value / 1_000_000)}M`;
|
|
190
|
-
}
|
|
191
|
-
if (Math.abs(value) >= 1_000) {
|
|
192
|
-
return `${Math.round(value / 1_000)}K`;
|
|
193
|
-
}
|
|
194
|
-
return String(+value.toPrecision(3));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function continuousValues(domain, count) {
|
|
198
|
-
return Array.from(
|
|
199
|
-
{ length: count },
|
|
200
|
-
(_, index) => domain[0] + index / (count - 1) * (domain[1] - domain[0])
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function prepareAutoLegendSource(child) {
|
|
205
|
-
const request = legendCandidates(child);
|
|
206
|
-
const canvas = child.graphicSpec.objects.canvas;
|
|
207
|
-
let source = child
|
|
208
|
-
._withCanvasConfig({
|
|
209
|
-
margin: LEGEND_SOURCE_CANVAS.margin,
|
|
210
|
-
size: { width: "explicit", height: "explicit" }
|
|
211
|
-
})
|
|
212
|
-
.editGraphics({
|
|
213
|
-
target: "canvas",
|
|
214
|
-
property: "width",
|
|
215
|
-
value: LEGEND_SOURCE_CANVAS.width
|
|
216
|
-
})
|
|
217
|
-
.editGraphics({
|
|
218
|
-
target: "canvas",
|
|
219
|
-
property: "height",
|
|
220
|
-
value: LEGEND_SOURCE_CANVAS.height
|
|
221
|
-
});
|
|
222
|
-
if (canvas?.properties.background !== undefined) {
|
|
223
|
-
source = source.editGraphics({
|
|
224
|
-
target: "canvas",
|
|
225
|
-
property: "background",
|
|
226
|
-
value: canvas.properties.background
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
const scale = findSemanticScale(
|
|
230
|
-
source,
|
|
231
|
-
findLayer(source, request.target)?.encoding?.color?.scale
|
|
232
|
-
);
|
|
233
|
-
const gradient = scale?.type === "sequential";
|
|
234
|
-
source = source.createLegend({
|
|
235
|
-
target: request.target,
|
|
236
|
-
channels: request.channels,
|
|
237
|
-
...(gradient ? {
|
|
238
|
-
count: 5,
|
|
239
|
-
gradient: { length: 180, thickness: 12 },
|
|
240
|
-
labels: { offset: 9, fontSize: 10 },
|
|
241
|
-
titleStyle: { color: DEFAULT_COLORS.strongText, fontSize: 11 }
|
|
242
|
-
} : {})
|
|
243
|
-
});
|
|
244
|
-
if (gradient) {
|
|
245
|
-
const resolved = source.resolvedScales[scale.id];
|
|
246
|
-
source = source
|
|
247
|
-
.editGraphics({
|
|
248
|
-
target: "colorGradientLabels",
|
|
249
|
-
property: "text",
|
|
250
|
-
value: continuousValues(resolved.domain, 5).map(compactNumber)
|
|
251
|
-
})
|
|
252
|
-
.editGraphics({
|
|
253
|
-
target: "colorGradientTitle",
|
|
254
|
-
property: "y",
|
|
255
|
-
value: source.graphicSpec.objects.colorGradientTitle.properties.y + 8
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
return source;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function compatibleLegendChildren(program, source, inferred) {
|
|
262
|
-
return program.compositionSpec.children.map(id => {
|
|
263
|
-
const child = program.children[id];
|
|
264
|
-
return {
|
|
265
|
-
id,
|
|
266
|
-
guideConfigs: inferred ? source.guideConfigs : child.guideConfigs,
|
|
267
|
-
resolvedScales: child.resolvedScales
|
|
268
|
-
};
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function prepareSharedFacetLegend(program) {
|
|
273
|
-
if (program.compositionSpec.facet.guides.legend !== "shared") {
|
|
274
|
-
return undefined;
|
|
275
|
-
}
|
|
276
|
-
const firstId = program.compositionSpec.children[0];
|
|
277
|
-
const first = program.children[firstId];
|
|
278
|
-
const inferred = legendKinds(first).length === 0;
|
|
279
|
-
if (inferred) {
|
|
280
|
-
const legacy = resolveLegacyCategoricalLegend(program);
|
|
281
|
-
if (legacy !== undefined) return legacy;
|
|
282
|
-
}
|
|
283
|
-
const source = inferred ? prepareAutoLegendSource(first) : first;
|
|
284
|
-
const kinds = legendKinds(source);
|
|
285
|
-
const compatibility = resolveSharedFacetLegends(
|
|
286
|
-
compatibleLegendChildren(program, source, inferred)
|
|
287
|
-
);
|
|
288
|
-
const roots = allLegendGraphicIds(kinds).filter(
|
|
289
|
-
id => source.graphicSpec.objects[id] !== undefined
|
|
290
|
-
);
|
|
291
|
-
if (roots.length === 0) {
|
|
292
|
-
throw new Error("Shared facet legend has no concrete guide graphics.");
|
|
293
|
-
}
|
|
294
|
-
const bounds = unionConcreteGraphicBounds(source.graphicSpec, roots);
|
|
295
|
-
if (bounds === undefined) {
|
|
296
|
-
throw new Error("Shared facet legend has no measurable concrete bounds.");
|
|
297
|
-
}
|
|
298
|
-
const gradient = kinds.includes("gradient");
|
|
299
|
-
return {
|
|
300
|
-
mode: "promoted",
|
|
301
|
-
source,
|
|
302
|
-
inferred,
|
|
303
|
-
kinds,
|
|
304
|
-
roots,
|
|
305
|
-
bounds,
|
|
306
|
-
compatibility,
|
|
307
|
-
reservation: {
|
|
308
|
-
gap: gradient ? 24 : 18,
|
|
309
|
-
width: gradient ? 112 : Math.max(132, Math.ceil(bounds.right - bounds.left))
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function copyLegendConfig(program, source) {
|
|
315
|
-
let next = program;
|
|
316
|
-
for (const [kind, config] of Object.entries(source.guideConfigs.legend ?? {})) {
|
|
317
|
-
next = next._withLegendConfig(kind, config);
|
|
318
|
-
}
|
|
319
|
-
return next;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function removeNamespacedGraphics(program, namespace, ids) {
|
|
323
|
-
let next = program;
|
|
324
|
-
for (const id of ids) {
|
|
325
|
-
const target = namespaceGraphicId(namespace, id);
|
|
326
|
-
if (next.graphicSpec.objects[target] !== undefined) {
|
|
327
|
-
next = next.editGraphics({ target, remove: true });
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return next;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function gradientAnchor(prepared, plot, layout) {
|
|
334
|
-
const strips = prepared.source.graphicSpec.objects.colorGradientStrips;
|
|
335
|
-
if (strips?.items?.[0] === undefined) return undefined;
|
|
336
|
-
const properties = strips.items[0].properties;
|
|
337
|
-
const length = prepared.source.guideConfigs.legend.gradient.gradient.length;
|
|
338
|
-
return {
|
|
339
|
-
sourceX: properties.x,
|
|
340
|
-
sourceY: properties.y,
|
|
341
|
-
targetX: layout.legend.x,
|
|
342
|
-
targetY: plot.y + (plot.height - length) / 2
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function legendTranslation(prepared, plot, layout) {
|
|
347
|
-
const gradient = gradientAnchor(prepared, plot, layout);
|
|
348
|
-
if (gradient !== undefined) {
|
|
349
|
-
return {
|
|
350
|
-
x: gradient.targetX - gradient.sourceX,
|
|
351
|
-
y: gradient.targetY - gradient.sourceY
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
const height = prepared.bounds.bottom - prepared.bounds.top;
|
|
355
|
-
return {
|
|
356
|
-
x: layout.legend.x - prepared.bounds.left,
|
|
357
|
-
y: plot.y + Math.max(0, (plot.height - height) / 2) - prepared.bounds.top
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function attachParentLegend(program, prepared, plot, layout) {
|
|
362
|
-
const translation = legendTranslation(prepared, plot, layout);
|
|
363
|
-
const canvas = prepared.source.graphicSpec.objects.canvas;
|
|
364
|
-
const id = `${program.compositionSpec.id}-shared-legend`;
|
|
365
|
-
let next = copyLegendConfig(program, prepared.source)
|
|
366
|
-
.createGraphics({ id, type: "canvas", parent: "canvas" });
|
|
367
|
-
for (const [property, value] of Object.entries({
|
|
368
|
-
x: translation.x,
|
|
369
|
-
y: translation.y,
|
|
370
|
-
width: canvas.properties.width,
|
|
371
|
-
height: canvas.properties.height,
|
|
372
|
-
background: "transparent"
|
|
373
|
-
})) {
|
|
374
|
-
next = next.editGraphics({ target: id, property, value });
|
|
375
|
-
}
|
|
376
|
-
for (const root of prepared.roots) {
|
|
377
|
-
next = attachSnapshotObject(next, prepared.source.graphicSpec, root, id);
|
|
378
|
-
}
|
|
379
|
-
return next;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
export function applyFacetGuideComposition(program, { layout, plot } = {}) {
|
|
383
|
-
if (!isPlainObject(layout) || !Array.isArray(layout.children)) {
|
|
384
|
-
throw new TypeError("Facet guide composition requires a resolved layout.");
|
|
385
|
-
}
|
|
386
|
-
if (!isPlainObject(plot)) {
|
|
387
|
-
throw new TypeError("Facet guide composition requires resolved plot bounds.");
|
|
388
|
-
}
|
|
389
|
-
const prepared = prepareSharedFacetLegend(program);
|
|
390
|
-
const children = layout.children.map(cell => {
|
|
391
|
-
const child = program.children[cell.id];
|
|
392
|
-
return {
|
|
393
|
-
id: cell.id,
|
|
394
|
-
axes: Object.keys(child.guideConfigs.axis ?? {})
|
|
395
|
-
.filter(channel => ["x", "y"].includes(channel)),
|
|
396
|
-
legendKinds: legendKinds(child)
|
|
397
|
-
};
|
|
398
|
-
});
|
|
399
|
-
const ownership = planFacetGuideOwnership({
|
|
400
|
-
placements: layout.children,
|
|
401
|
-
children,
|
|
402
|
-
axes: program.compositionSpec.facet.guides.axes,
|
|
403
|
-
legend: program.compositionSpec.facet.guides.legend,
|
|
404
|
-
...(prepared === undefined ? {} : { sharedLegends: prepared.compatibility })
|
|
405
|
-
});
|
|
406
|
-
let next = program;
|
|
407
|
-
for (const cell of layout.children) {
|
|
408
|
-
const namespace = `${program.compositionSpec.id}-${cell.id}`;
|
|
409
|
-
const childPlan = ownership.children[cell.id];
|
|
410
|
-
for (const channel of childPlan.removeAxes) {
|
|
411
|
-
next = removeNamespacedGraphics(next, namespace, axisGraphicIds(channel));
|
|
412
|
-
}
|
|
413
|
-
if (childPlan.removeLegends.length > 0) {
|
|
414
|
-
next = removeNamespacedGraphics(
|
|
415
|
-
next,
|
|
416
|
-
namespace,
|
|
417
|
-
allLegendGraphicIds(childPlan.removeLegends)
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
if (prepared === undefined) return next;
|
|
422
|
-
if (prepared.mode === "legacyCategorical") {
|
|
423
|
-
return materializeLegacyCategoricalLegend(next, prepared, layout);
|
|
424
|
-
}
|
|
425
|
-
return attachParentLegend(next, prepared, plot, layout);
|
|
426
|
-
}
|