ggaction 0.0.6 → 0.0.8
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 +43 -0
- package/README.md +77 -14
- package/package.json +19 -2
- package/src/BasicChartProgram.js +10 -0
- package/src/actions/basic.js +21 -0
- package/src/actions/canvas/actions.js +42 -0
- package/src/actions/canvas/index.js +5 -1
- package/src/actions/categoryOrder/index.js +111 -0
- package/src/actions/charts/basic.js +13 -0
- package/src/actions/data/basic.js +11 -0
- package/src/actions/data/basicBin2d.js +43 -0
- package/src/actions/data/bin2d.js +1 -26
- package/src/actions/data/bin2dMaterialize.js +29 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/timeUnit.js +48 -0
- package/src/actions/encodings/angle.js +77 -0
- package/src/actions/encodings/appearance.js +6 -2
- package/src/actions/encodings/basic.js +21 -0
- package/src/actions/encodings/color/index.js +17 -13
- package/src/actions/encodings/color/layout.js +2 -0
- package/src/actions/encodings/color/policy.js +3 -0
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/position/index.js +5 -1
- package/src/actions/encodings/position/policies/area.js +40 -2
- package/src/actions/encodings/position/policies/bar.js +6 -0
- package/src/actions/encodings/position/policies/index.js +2 -0
- package/src/actions/encodings/position/policies/tick.js +19 -0
- package/src/actions/encodings/ranged.js +6 -2
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/guides/axes/index.js +10 -2
- package/src/actions/guides/basic.js +19 -0
- package/src/actions/guides/grids/grid.js +7 -3
- package/src/actions/guides/grids/index.js +1 -1
- package/src/actions/guides/legends/categorical/actions.js +23 -6
- package/src/actions/guides/legends/categorical/index.js +14 -2
- package/src/actions/guides/legends/continuous/common.js +17 -5
- package/src/actions/guides/legends/continuous/gradient.js +12 -8
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/opacity.js +70 -17
- package/src/actions/guides/legends/edit.js +13 -8
- package/src/actions/guides/legends/lane.js +468 -0
- package/src/actions/guides/legends/remove.js +3 -7
- package/src/actions/index.js +2 -0
- package/src/actions/marks/area/actions.js +3 -1
- package/src/actions/marks/area/materialize.js +12 -3
- package/src/actions/marks/bar/index.js +5 -1
- package/src/actions/marks/basic.js +11 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/line/actions.js +5 -1
- package/src/actions/marks/line/index.js +4 -1
- package/src/actions/marks/point/index.js +5 -1
- package/src/actions/marks/point/materialize.js +17 -5
- package/src/actions/marks/rect/actions.js +5 -1
- package/src/actions/marks/rect/index.js +4 -1
- package/src/actions/marks/tick/actions.js +225 -0
- package/src/actions/marks/tick/index.js +1 -0
- package/src/actions/primitives/semanticValidation/layer.js +2 -0
- package/src/actions/scales/consumers/index.js +8 -0
- package/src/actions/scales/consumers/seriesLayout.js +22 -2
- package/src/actions/scales/index.js +5 -1
- package/src/actions/scales/materialize.js +2 -0
- package/src/actions/selection/actions.js +5 -2
- package/src/basic.js +2 -0
- package/src/core/vocabulary.js +7 -3
- package/src/grammar/areaSeries.js +112 -2
- package/src/grammar/categoryOrder.js +138 -0
- package/src/grammar/direction.js +46 -0
- package/src/grammar/facets/index.js +1 -1
- package/src/grammar/pointShapes.js +34 -6
- package/src/grammar/positionCompatibility.js +4 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/seriesLayout.js +10 -2
- package/src/grammar/timeUnit.js +108 -0
- package/src/grammar/transforms.js +6 -0
- package/src/grammar/window.js +73 -7
- package/src/layout/legendLane.js +338 -0
- package/src/materialization/marks/capabilities.js +9 -0
- package/src/materialization/marks/index.js +2 -1
- package/src/materialization/marks/policies.js +13 -0
- package/src/materialization/scales/policies/series.js +9 -0
- package/src/materialization/scales/resolve.js +25 -3
- package/src/materialization/selection/items/index.js +1 -0
- package/src/materialization/selection/items/path.js +4 -1
- package/src/materialization/selection/items/tick.js +42 -0
- package/src/materialization/selection/policies/index.js +2 -0
- package/src/materialization/selection/policies/tick.js +10 -0
- package/src/renderers/canvas/index.js +96 -60
- package/src/renderers/canvas/text.js +3 -14
- package/src/renderers/pdf.js +112 -0
- package/src/renderers/svg.js +559 -0
- package/src/renderers/text.js +11 -0
- package/types/basic.d.ts +107 -0
- package/types/index.d.ts +10 -0
- package/types/pdf.d.ts +26 -0
- package/types/program.d.ts +96 -3
- package/types/svg.d.ts +11 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { noOptions } from "../../../core/validation.js";
|
|
3
|
+
import {
|
|
4
|
+
resolveConcreteGraphicBounds,
|
|
5
|
+
unionConcreteGraphicBounds
|
|
6
|
+
} from "../../../grammar/schemas/graphicBounds.js";
|
|
7
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
8
|
+
import {
|
|
9
|
+
resolveHorizontalLegendLane,
|
|
10
|
+
resolveSideLegendLane
|
|
11
|
+
} from "../../../layout/legendLane.js";
|
|
12
|
+
import { findCanvasGraphic } from
|
|
13
|
+
"../../../materialization/graphicHierarchy.js";
|
|
14
|
+
import { legendResourcePolicy } from
|
|
15
|
+
"../../../materialization/guides/resources.js";
|
|
16
|
+
|
|
17
|
+
const FAMILY_ORDER = Object.freeze({
|
|
18
|
+
series: 0,
|
|
19
|
+
color: 0,
|
|
20
|
+
gradient: 0,
|
|
21
|
+
interval: 0,
|
|
22
|
+
size: 1,
|
|
23
|
+
opacity: 2,
|
|
24
|
+
strokeWidth: 3
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function categoricalFor(program, target) {
|
|
28
|
+
return ["series", "color"]
|
|
29
|
+
.map(kind => program.guideConfigs.legend?.[kind])
|
|
30
|
+
.find(config => config?.target === target);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function legendPosition(program, kind, config) {
|
|
34
|
+
if (kind === "size") {
|
|
35
|
+
return categoricalFor(program, config.target)?.position ?? "right";
|
|
36
|
+
}
|
|
37
|
+
if (kind === "strokeWidth" || kind === "interval") return "right";
|
|
38
|
+
return config.position;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function requestedOffset(kind, config) {
|
|
42
|
+
return ["size", "strokeWidth"].includes(kind)
|
|
43
|
+
? 30
|
|
44
|
+
: config.offset;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function existingIds(program, kind) {
|
|
48
|
+
return legendResourcePolicy(kind).graphicIds.filter(
|
|
49
|
+
id => program.graphicSpec.objects[id] !== undefined
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function componentIds(program, kind) {
|
|
54
|
+
const ids = existingIds(program, kind);
|
|
55
|
+
const titleId = ids.find(id => id.endsWith("Title"));
|
|
56
|
+
const labelId = ids.find(id => id.endsWith("Labels"));
|
|
57
|
+
const backgroundId = ids.find(id => id.endsWith("Background"));
|
|
58
|
+
const symbolIds = ids.filter(id =>
|
|
59
|
+
id !== titleId && id !== labelId && id !== backgroundId
|
|
60
|
+
);
|
|
61
|
+
return { titleId, labelId, backgroundId, symbolIds };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function textAnchor(program, id) {
|
|
65
|
+
const graphic = program.graphicSpec.objects[id];
|
|
66
|
+
const properties = graphic.items?.[0]?.properties ?? graphic.properties;
|
|
67
|
+
return properties.x;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function blockDescriptor(program, kind, config) {
|
|
71
|
+
const components = componentIds(program, kind);
|
|
72
|
+
if (components.labelId === undefined || components.symbolIds.length === 0) {
|
|
73
|
+
throw new Error(`Legend lane requires complete ${kind} graphics.`);
|
|
74
|
+
}
|
|
75
|
+
const foregroundIds = [
|
|
76
|
+
...components.symbolIds,
|
|
77
|
+
components.labelId,
|
|
78
|
+
...(components.titleId === undefined ? [] : [components.titleId])
|
|
79
|
+
];
|
|
80
|
+
const bounds = unionConcreteGraphicBounds(program.graphicSpec, foregroundIds);
|
|
81
|
+
const symbolAnchorIds = kind === "gradient"
|
|
82
|
+
? ["colorGradientStrips"]
|
|
83
|
+
: components.symbolIds;
|
|
84
|
+
const symbolAnchor = unionConcreteGraphicBounds(
|
|
85
|
+
program.graphicSpec,
|
|
86
|
+
symbolAnchorIds
|
|
87
|
+
);
|
|
88
|
+
const symbolBounds = unionConcreteGraphicBounds(
|
|
89
|
+
program.graphicSpec,
|
|
90
|
+
components.symbolIds
|
|
91
|
+
);
|
|
92
|
+
const labels = resolveConcreteGraphicBounds(
|
|
93
|
+
program.graphicSpec,
|
|
94
|
+
components.labelId
|
|
95
|
+
);
|
|
96
|
+
const title = components.titleId === undefined
|
|
97
|
+
? undefined
|
|
98
|
+
: (() => {
|
|
99
|
+
const graphic = program.graphicSpec.objects[components.titleId];
|
|
100
|
+
const titleBounds = resolveConcreteGraphicBounds(
|
|
101
|
+
program.graphicSpec,
|
|
102
|
+
components.titleId
|
|
103
|
+
);
|
|
104
|
+
return {
|
|
105
|
+
x: graphic.properties.x,
|
|
106
|
+
y: graphic.properties.y,
|
|
107
|
+
fontSize: graphic.properties.fontSize,
|
|
108
|
+
width: titleBounds.right - titleBounds.left,
|
|
109
|
+
bounds: titleBounds
|
|
110
|
+
};
|
|
111
|
+
})();
|
|
112
|
+
if ([bounds, symbolAnchor, symbolBounds, labels].includes(undefined)) {
|
|
113
|
+
throw new Error(`Legend lane could not measure ${kind} graphics.`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
id: kind,
|
|
117
|
+
kind,
|
|
118
|
+
target: config.target,
|
|
119
|
+
offset: requestedOffset(kind, config),
|
|
120
|
+
bounds,
|
|
121
|
+
title,
|
|
122
|
+
symbol: {
|
|
123
|
+
centerX: (symbolAnchor.left + symbolAnchor.right) / 2,
|
|
124
|
+
left: symbolBounds.left,
|
|
125
|
+
right: symbolBounds.right,
|
|
126
|
+
bounds: symbolAnchor
|
|
127
|
+
},
|
|
128
|
+
labels: {
|
|
129
|
+
x: textAnchor(program, components.labelId),
|
|
130
|
+
width: labels.right - labels.left
|
|
131
|
+
},
|
|
132
|
+
occupiedBounds: components.backgroundId === undefined
|
|
133
|
+
? bounds
|
|
134
|
+
: resolveConcreteGraphicBounds(
|
|
135
|
+
program.graphicSpec,
|
|
136
|
+
components.backgroundId
|
|
137
|
+
),
|
|
138
|
+
...components,
|
|
139
|
+
foregroundIds
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function borderFor(kind, config) {
|
|
144
|
+
return ["series", "color", "gradient", "opacity"].includes(kind)
|
|
145
|
+
? config.border
|
|
146
|
+
: false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function groupBlocks(blocks, configs) {
|
|
150
|
+
const groups = [];
|
|
151
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
152
|
+
const block = blocks[index];
|
|
153
|
+
const config = configs[block.kind];
|
|
154
|
+
const next = blocks[index + 1];
|
|
155
|
+
if (
|
|
156
|
+
["series", "color"].includes(block.kind) &&
|
|
157
|
+
next?.kind === "size" && next.target === block.target
|
|
158
|
+
) {
|
|
159
|
+
groups.push({
|
|
160
|
+
id: `${block.kind}+size`,
|
|
161
|
+
blocks: [block, next],
|
|
162
|
+
border: borderFor(block.kind, config),
|
|
163
|
+
backgroundId: block.backgroundId
|
|
164
|
+
});
|
|
165
|
+
index += 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
groups.push({
|
|
169
|
+
id: block.kind,
|
|
170
|
+
blocks: [block],
|
|
171
|
+
border: borderFor(block.kind, config),
|
|
172
|
+
backgroundId: block.backgroundId
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return groups;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function edgeKinds(program, edge) {
|
|
179
|
+
const configs = program.guideConfigs.legend ?? {};
|
|
180
|
+
const layerOrder = new Map(program.semanticSpec.layers.map(
|
|
181
|
+
(layer, index) => [layer.id, index]
|
|
182
|
+
));
|
|
183
|
+
return Object.entries(configs)
|
|
184
|
+
.filter(([kind, config]) => legendPosition(program, kind, config) === edge)
|
|
185
|
+
.sort(([kindA, configA], [kindB, configB]) =>
|
|
186
|
+
(layerOrder.get(configA.target) - layerOrder.get(configB.target)) ||
|
|
187
|
+
(FAMILY_ORDER[kindA] - FAMILY_ORDER[kindB])
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function hasMultiSideLegendLane(program) {
|
|
192
|
+
return ["right", "left"].some(side => edgeKinds(program, side).length > 1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function hasMultiHorizontalLegendLane(program) {
|
|
196
|
+
return ["top", "bottom"].some(edge => edgeKinds(program, edge).length > 1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function hasMultiLegendLane(program) {
|
|
200
|
+
return hasMultiSideLegendLane(program) ||
|
|
201
|
+
hasMultiHorizontalLegendLane(program);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function translateCommands(commands, dx, dy) {
|
|
205
|
+
return commands.map(command => {
|
|
206
|
+
if (command.op === "Z") return { ...command };
|
|
207
|
+
const next = {
|
|
208
|
+
...command,
|
|
209
|
+
x: command.x + dx,
|
|
210
|
+
y: command.y + dy
|
|
211
|
+
};
|
|
212
|
+
if (command.op === "C") {
|
|
213
|
+
next.x1 = command.x1 + dx;
|
|
214
|
+
next.y1 = command.y1 + dy;
|
|
215
|
+
next.x2 = command.x2 + dx;
|
|
216
|
+
next.y2 = command.y2 + dy;
|
|
217
|
+
}
|
|
218
|
+
return next;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function translatedProperties(type, properties, dx, dy) {
|
|
223
|
+
const next = { ...properties };
|
|
224
|
+
if (["circle", "rect", "text"].includes(type)) {
|
|
225
|
+
next.x += dx;
|
|
226
|
+
next.y += dy;
|
|
227
|
+
} else if (type === "line") {
|
|
228
|
+
next.x1 += dx;
|
|
229
|
+
next.x2 += dx;
|
|
230
|
+
next.y1 += dy;
|
|
231
|
+
next.y2 += dy;
|
|
232
|
+
} else if (type === "path") {
|
|
233
|
+
next.commands = translateCommands(next.commands, dx, dy);
|
|
234
|
+
}
|
|
235
|
+
return next;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function translateGraphic(program, id, dx, dy) {
|
|
239
|
+
const graphic = program.graphicSpec.objects[id];
|
|
240
|
+
if (dx === 0 && dy === 0) return program;
|
|
241
|
+
if (graphic.items !== undefined) {
|
|
242
|
+
return program.editGraphics({
|
|
243
|
+
target: id,
|
|
244
|
+
property: "items",
|
|
245
|
+
value: graphic.items.map(item => ({
|
|
246
|
+
type: item.type ?? graphic.type,
|
|
247
|
+
properties: translatedProperties(
|
|
248
|
+
item.type ?? graphic.type,
|
|
249
|
+
item.properties,
|
|
250
|
+
dx,
|
|
251
|
+
dy
|
|
252
|
+
)
|
|
253
|
+
}))
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
let next = program;
|
|
257
|
+
const properties = translatedProperties(
|
|
258
|
+
graphic.type,
|
|
259
|
+
graphic.properties,
|
|
260
|
+
dx,
|
|
261
|
+
dy
|
|
262
|
+
);
|
|
263
|
+
for (const [property, value] of Object.entries(properties)) {
|
|
264
|
+
if (value !== graphic.properties[property]) {
|
|
265
|
+
next = next.editGraphics({ target: id, property, value });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return next;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function yAxisBounds(program) {
|
|
272
|
+
const ids = ["yAxisLine", "yAxisTicks", "yAxisLabels", "yAxisTitle"]
|
|
273
|
+
.filter(id => program.graphicSpec.objects[id] !== undefined);
|
|
274
|
+
return ids.length === 0
|
|
275
|
+
? undefined
|
|
276
|
+
: unionConcreteGraphicBounds(program.graphicSpec, ids);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function horizontalCollisionBounds(program, edge) {
|
|
280
|
+
const ids = edge === "top"
|
|
281
|
+
? ["chartTitle", "chartSubtitle"]
|
|
282
|
+
: ["xAxisLine", "xAxisTicks", "xAxisLabels", "xAxisTitle"];
|
|
283
|
+
return ids
|
|
284
|
+
.filter(id => program.graphicSpec.objects[id] !== undefined)
|
|
285
|
+
.map(id => resolveConcreteGraphicBounds(program.graphicSpec, id))
|
|
286
|
+
.filter(bounds => bounds !== undefined);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function horizontalGroups(program, groups) {
|
|
290
|
+
return groups.map(group => {
|
|
291
|
+
const representative = group.blocks[0];
|
|
292
|
+
const config = program.guideConfigs.legend?.[representative.kind];
|
|
293
|
+
const titleId = representative.titleId;
|
|
294
|
+
const contentIds = group.blocks.flatMap(block => block.foregroundIds)
|
|
295
|
+
.filter(id => id !== titleId);
|
|
296
|
+
const content = unionConcreteGraphicBounds(program.graphicSpec, contentIds);
|
|
297
|
+
const foreground = unionConcreteGraphicBounds(
|
|
298
|
+
program.graphicSpec,
|
|
299
|
+
group.blocks.flatMap(block => block.foregroundIds)
|
|
300
|
+
);
|
|
301
|
+
if (content === undefined || foreground === undefined) {
|
|
302
|
+
throw new Error(`Legend lane could not measure ${group.id} content.`);
|
|
303
|
+
}
|
|
304
|
+
const border = group.border;
|
|
305
|
+
const inset = border === false
|
|
306
|
+
? 0
|
|
307
|
+
: border.padding + border.lineWidth / 2;
|
|
308
|
+
return {
|
|
309
|
+
id: group.id,
|
|
310
|
+
titleId,
|
|
311
|
+
contentIds,
|
|
312
|
+
title: representative.title,
|
|
313
|
+
inline: config?.titlePosition === "left",
|
|
314
|
+
element: representative.symbol.bounds,
|
|
315
|
+
content,
|
|
316
|
+
horizontal: { left: foreground.left, right: foreground.right,
|
|
317
|
+
top: foreground.top, bottom: foreground.bottom },
|
|
318
|
+
inset,
|
|
319
|
+
padding: border === false ? 0 : border.padding,
|
|
320
|
+
backgroundId: group.backgroundId
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const rematerializeSideLegendLane = action(
|
|
326
|
+
{
|
|
327
|
+
op: "rematerializeSideLegendLane",
|
|
328
|
+
description: "Align and stack every right or left legend block."
|
|
329
|
+
},
|
|
330
|
+
function (args = {}) {
|
|
331
|
+
noOptions(args, "rematerializeSideLegendLane");
|
|
332
|
+
const plot = resolveGraphicBounds(this);
|
|
333
|
+
const canvas = findCanvasGraphic(this)?.properties;
|
|
334
|
+
if (plot === undefined || canvas === undefined) {
|
|
335
|
+
throw new Error("Legend lane requires resolved Canvas and plot bounds.");
|
|
336
|
+
}
|
|
337
|
+
const configs = this.guideConfigs.legend ?? {};
|
|
338
|
+
const plans = ["right", "left"].flatMap(side => {
|
|
339
|
+
const entries = edgeKinds(this, side);
|
|
340
|
+
if (entries.length < 2) return [];
|
|
341
|
+
const blocks = entries.map(([kind, config]) =>
|
|
342
|
+
blockDescriptor(this, kind, config)
|
|
343
|
+
);
|
|
344
|
+
const plan = resolveSideLegendLane({
|
|
345
|
+
side,
|
|
346
|
+
plot,
|
|
347
|
+
canvas,
|
|
348
|
+
groups: groupBlocks(blocks, configs),
|
|
349
|
+
axisBounds: yAxisBounds(this)
|
|
350
|
+
});
|
|
351
|
+
return [{ plan, blocks }];
|
|
352
|
+
});
|
|
353
|
+
let next = this;
|
|
354
|
+
for (const { plan, blocks } of plans) {
|
|
355
|
+
const byId = new Map(blocks.map(block => [block.id, block]));
|
|
356
|
+
for (const placement of plan.placements) {
|
|
357
|
+
const block = byId.get(placement.id);
|
|
358
|
+
if (block.titleId !== undefined) {
|
|
359
|
+
next = translateGraphic(
|
|
360
|
+
next,
|
|
361
|
+
block.titleId,
|
|
362
|
+
placement.titleDx,
|
|
363
|
+
placement.dy
|
|
364
|
+
).editGraphics({
|
|
365
|
+
target: block.titleId,
|
|
366
|
+
property: "textAlign",
|
|
367
|
+
value: "left"
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
for (const id of block.symbolIds) {
|
|
371
|
+
next = translateGraphic(
|
|
372
|
+
next,
|
|
373
|
+
id,
|
|
374
|
+
placement.symbolDx,
|
|
375
|
+
placement.dy
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
next = translateGraphic(
|
|
379
|
+
next,
|
|
380
|
+
block.labelId,
|
|
381
|
+
placement.labelDx,
|
|
382
|
+
placement.dy
|
|
383
|
+
).editGraphics({
|
|
384
|
+
target: block.labelId,
|
|
385
|
+
property: "textAlign",
|
|
386
|
+
value: "left"
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
for (const background of plan.backgrounds) {
|
|
390
|
+
for (const property of ["x", "y", "width", "height"]) {
|
|
391
|
+
next = next.editGraphics({
|
|
392
|
+
target: background.id,
|
|
393
|
+
property,
|
|
394
|
+
value: background[property]
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return next;
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
export const rematerializeHorizontalLegendLane = action(
|
|
404
|
+
{
|
|
405
|
+
op: "rematerializeHorizontalLegendLane",
|
|
406
|
+
description: "Pack and align every top or bottom legend block."
|
|
407
|
+
},
|
|
408
|
+
function (args = {}) {
|
|
409
|
+
noOptions(args, "rematerializeHorizontalLegendLane");
|
|
410
|
+
const plot = resolveGraphicBounds(this);
|
|
411
|
+
const canvas = findCanvasGraphic(this)?.properties;
|
|
412
|
+
if (plot === undefined || canvas === undefined) {
|
|
413
|
+
throw new Error("Legend lane requires resolved Canvas and plot bounds.");
|
|
414
|
+
}
|
|
415
|
+
const configs = this.guideConfigs.legend ?? {};
|
|
416
|
+
const plans = ["top", "bottom"].flatMap(edge => {
|
|
417
|
+
const entries = edgeKinds(this, edge);
|
|
418
|
+
if (entries.length < 2) return [];
|
|
419
|
+
const blocks = entries.map(([kind, config]) =>
|
|
420
|
+
blockDescriptor(this, kind, config)
|
|
421
|
+
);
|
|
422
|
+
const groups = groupBlocks(blocks, configs);
|
|
423
|
+
if (groups.length < 2) return [];
|
|
424
|
+
const horizontal = horizontalGroups(this, groups);
|
|
425
|
+
const plan = resolveHorizontalLegendLane({
|
|
426
|
+
edge,
|
|
427
|
+
plot,
|
|
428
|
+
canvas,
|
|
429
|
+
groups: horizontal,
|
|
430
|
+
collisionBounds: horizontalCollisionBounds(this, edge)
|
|
431
|
+
});
|
|
432
|
+
return [{ plan, groups: horizontal }];
|
|
433
|
+
});
|
|
434
|
+
let next = this;
|
|
435
|
+
for (const { plan, groups } of plans) {
|
|
436
|
+
const byId = new Map(groups.map(group => [group.id, group]));
|
|
437
|
+
for (const placement of plan.placements) {
|
|
438
|
+
const group = byId.get(placement.id);
|
|
439
|
+
if (group.titleId !== undefined) {
|
|
440
|
+
next = translateGraphic(
|
|
441
|
+
next,
|
|
442
|
+
group.titleId,
|
|
443
|
+
placement.dx,
|
|
444
|
+
placement.titleDy
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
for (const id of group.contentIds) {
|
|
448
|
+
next = translateGraphic(
|
|
449
|
+
next,
|
|
450
|
+
id,
|
|
451
|
+
placement.dx,
|
|
452
|
+
placement.contentDy
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
if (placement.background !== undefined) {
|
|
456
|
+
for (const property of ["x", "y", "width", "height"]) {
|
|
457
|
+
next = next.editGraphics({
|
|
458
|
+
target: placement.background.id,
|
|
459
|
+
property,
|
|
460
|
+
value: placement.background[property]
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return next;
|
|
467
|
+
}
|
|
468
|
+
);
|
|
@@ -113,13 +113,9 @@ export const removeLegend = action(
|
|
|
113
113
|
let next = removeLegendKinds(this, kinds);
|
|
114
114
|
const retainedKinds = Object.keys(next.guideConfigs.legend ?? {});
|
|
115
115
|
if (args.channels === undefined) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const hasSharedSemanticKind = retainedKinds.some(kind =>
|
|
120
|
-
removedSemanticKinds.has(legendResourcePolicy(kind).semanticKind)
|
|
121
|
-
);
|
|
122
|
-
return hasSharedSemanticKind ? next.rematerializeLegend() : next;
|
|
116
|
+
return retainedKinds.length === 0
|
|
117
|
+
? next
|
|
118
|
+
: next.rematerializeLegend();
|
|
123
119
|
}
|
|
124
120
|
const remainingTargetKinds = targetKinds.filter(kind => !kinds.includes(kind));
|
|
125
121
|
const removedCategorical = kinds.some(kind => ["series", "color"].includes(kind));
|
package/src/actions/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { registerCanvasActions } from "./canvas/index.js";
|
|
2
2
|
import { registerChartActions } from "./charts/index.js";
|
|
3
|
+
import { registerCategoryOrderActions } from "./categoryOrder/index.js";
|
|
3
4
|
import { registerCoordinateActions } from "./coordinates/index.js";
|
|
4
5
|
import { registerCompositionActions } from "./composition/index.js";
|
|
5
6
|
import { registerDataActions } from "./data/index.js";
|
|
@@ -37,5 +38,6 @@ export function registerActions(ProgramClass) {
|
|
|
37
38
|
registerGradientPlotActions(ProgramClass);
|
|
38
39
|
registerViolinPlotActions(ProgramClass);
|
|
39
40
|
registerChartActions(ProgramClass);
|
|
41
|
+
registerCategoryOrderActions(ProgramClass);
|
|
40
42
|
registerSelectionActions(ProgramClass);
|
|
41
43
|
}
|
|
@@ -159,10 +159,12 @@ const rematerializeAreaMark = action(
|
|
|
159
159
|
const yScaleId = layer.encoding?.y?.scale;
|
|
160
160
|
const verticalRange = layer.encoding?.y2?.scale === yScaleId;
|
|
161
161
|
const horizontalRange = layer.encoding?.x2?.scale === xScaleId;
|
|
162
|
+
const centered = densityTransform === undefined &&
|
|
163
|
+
layer.encoding?.y?.stack === "center";
|
|
162
164
|
if (
|
|
163
165
|
xScaleId === undefined ||
|
|
164
166
|
yScaleId === undefined ||
|
|
165
|
-
(densityTransform === undefined && verticalRange === horizontalRange)
|
|
167
|
+
(densityTransform === undefined && !centered && verticalRange === horizontalRange)
|
|
166
168
|
) {
|
|
167
169
|
throw new Error(
|
|
168
170
|
densityTransform === undefined
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
deriveCenteredAreaSeries,
|
|
2
3
|
deriveAreaSeries,
|
|
3
4
|
deriveDensityAreaSeries,
|
|
4
5
|
layoutDensityAreaSeries
|
|
@@ -81,13 +82,17 @@ function resolveAreaPaths({
|
|
|
81
82
|
xScale
|
|
82
83
|
);
|
|
83
84
|
const lowerValues = densityTransform === undefined
|
|
84
|
-
?
|
|
85
|
+
? derived.mode === "y-center"
|
|
86
|
+
? series.values.map(value => value.lower)
|
|
87
|
+
: series.values.map(value => value.y)
|
|
85
88
|
: derived.mode === "y-density"
|
|
86
89
|
? series.values.map(value => value.lower)
|
|
87
90
|
: series.values.map(value => value.y);
|
|
88
91
|
const lower = mapContinuousScaleValues(lowerValues, yScale);
|
|
89
92
|
const upperValues = densityTransform === undefined
|
|
90
|
-
?
|
|
93
|
+
? derived.mode === "y-center"
|
|
94
|
+
? series.values.map(value => value.upper)
|
|
95
|
+
: series.values.map(value => value.y2)
|
|
91
96
|
: series.values.map(value => value.upper);
|
|
92
97
|
const upper = mapContinuousScaleValues(upperValues, yScale);
|
|
93
98
|
if (densityTransform !== undefined && layout === "overlay") {
|
|
@@ -122,8 +127,12 @@ export function resolveAreaMaterialization({
|
|
|
122
127
|
}) {
|
|
123
128
|
const xScale = resolvedScales[layer.encoding.x.scale];
|
|
124
129
|
const yScale = resolvedScales[layer.encoding.y.scale];
|
|
130
|
+
const centered = densityTransform === undefined &&
|
|
131
|
+
layer.encoding?.y?.stack === "center";
|
|
125
132
|
const rawDerived = densityTransform === undefined
|
|
126
|
-
?
|
|
133
|
+
? centered
|
|
134
|
+
? deriveCenteredAreaSeries(rows, layer)
|
|
135
|
+
: deriveAreaSeries(rows, layer)
|
|
127
136
|
: deriveDensityAreaSeries(rows, layer, densityTransform);
|
|
128
137
|
const colorEncoding = layer.encoding?.color;
|
|
129
138
|
const layout = colorEncoding?.layout ?? "overlay";
|
|
@@ -3,7 +3,11 @@ import { editBarMark } from "./edit.js";
|
|
|
3
3
|
import { rematerializeBarMark } from "./materialize.js";
|
|
4
4
|
|
|
5
5
|
export function registerBarMarkActions(ProgramClass) {
|
|
6
|
-
ProgramClass.prototype.createBarMark = createBarMark;
|
|
7
6
|
ProgramClass.prototype.editBarMark = editBarMark;
|
|
7
|
+
registerBasicBarMarkActions(ProgramClass);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function registerBasicBarMarkActions(ProgramClass) {
|
|
11
|
+
ProgramClass.prototype.createBarMark = createBarMark;
|
|
8
12
|
ProgramClass.prototype.rematerializeBarMark = rematerializeBarMark;
|
|
9
13
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerBarMarkActions } from "./bar/index.js";
|
|
2
|
+
import { registerLineMarkActions } from "./line/index.js";
|
|
3
|
+
import { registerPointMarkActions } from "./point/index.js";
|
|
4
|
+
import { registerRectMarkActions } from "./rect/index.js";
|
|
5
|
+
|
|
6
|
+
export function registerBasicMarkActions(ProgramClass) {
|
|
7
|
+
registerPointMarkActions(ProgramClass);
|
|
8
|
+
registerLineMarkActions(ProgramClass);
|
|
9
|
+
registerBarMarkActions(ProgramClass);
|
|
10
|
+
registerRectMarkActions(ProgramClass);
|
|
11
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { registerRectMarkActions } from "./rect/index.js";
|
|
10
10
|
import { registerRuleMarkActions } from "./rule/index.js";
|
|
11
11
|
import { registerTextMarkActions } from "./text/index.js";
|
|
12
|
+
import { registerTickMarkActions } from "./tick/index.js";
|
|
12
13
|
import { removeMark } from "./remove.js";
|
|
13
14
|
|
|
14
15
|
export function registerMarkActions(ProgramClass) {
|
|
@@ -21,5 +22,6 @@ export function registerMarkActions(ProgramClass) {
|
|
|
21
22
|
registerBarMarkActions(ProgramClass);
|
|
22
23
|
registerRuleMarkActions(ProgramClass);
|
|
23
24
|
registerTextMarkActions(ProgramClass);
|
|
25
|
+
registerTickMarkActions(ProgramClass);
|
|
24
26
|
ProgramClass.prototype.removeMark = removeMark;
|
|
25
27
|
}
|
|
@@ -386,7 +386,11 @@ const editLineMark = action(
|
|
|
386
386
|
);
|
|
387
387
|
|
|
388
388
|
export function registerLineMarkActions(ProgramClass) {
|
|
389
|
-
ProgramClass.prototype.createLineMark = createLineMark;
|
|
390
389
|
ProgramClass.prototype.editLineMark = editLineMark;
|
|
390
|
+
registerBasicLineMarkActions(ProgramClass);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function registerBasicLineMarkActions(ProgramClass) {
|
|
394
|
+
ProgramClass.prototype.createLineMark = createLineMark;
|
|
391
395
|
ProgramClass.prototype.rematerializeLineMark = rematerializeLineMark;
|
|
392
396
|
}
|
|
@@ -5,7 +5,11 @@ import { rematerializePointMark } from "./materialize.js";
|
|
|
5
5
|
export { registerPointJitterActions } from "./jitter.js";
|
|
6
6
|
|
|
7
7
|
export function registerPointMarkActions(ProgramClass) {
|
|
8
|
-
ProgramClass.prototype.createPointMark = createPointMark;
|
|
9
8
|
ProgramClass.prototype.editPointMark = editPointMark;
|
|
9
|
+
registerBasicPointMarkActions(ProgramClass);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerBasicPointMarkActions(ProgramClass) {
|
|
13
|
+
ProgramClass.prototype.createPointMark = createPointMark;
|
|
10
14
|
ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
|
|
11
15
|
}
|