ggaction 0.0.7 → 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 +20 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/actions/categoryOrder/index.js +111 -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/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/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/remove.js +1 -1
- package/src/actions/guides/legends/categorical/actions.js +23 -6
- package/src/actions/guides/legends/categorical/index.js +8 -0
- 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/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/index.js +2 -0
- package/src/actions/marks/point/materialize.js +17 -5
- 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/materialize.js +2 -0
- package/src/actions/selection/actions.js +5 -2
- 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/types/index.d.ts +10 -0
- package/types/program.d.ts +96 -3
|
@@ -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";
|
|
@@ -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
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
resolvePointJitter
|
|
13
13
|
} from "../../../grammar/jitter.js";
|
|
14
14
|
import { polarToCartesian, resolvePolarFrame } from "../../../grammar/polar.js";
|
|
15
|
+
import { resolveDirectionValues } from "../../../grammar/direction.js";
|
|
15
16
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
16
17
|
import { validateMarkOptions } from "../shared.js";
|
|
17
18
|
import {
|
|
@@ -60,8 +61,10 @@ function compactProperties(properties) {
|
|
|
60
61
|
);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function incompleteShapeGraphic(shape, properties) {
|
|
64
|
-
const type =
|
|
64
|
+
function incompleteShapeGraphic(shape, properties, angle) {
|
|
65
|
+
const type = angle !== undefined && shape === "square"
|
|
66
|
+
? "path"
|
|
67
|
+
: getPointGraphicType(shape);
|
|
65
68
|
const incomplete = type === "path"
|
|
66
69
|
? { fill: properties.fill, opacity: properties.opacity }
|
|
67
70
|
: properties;
|
|
@@ -99,6 +102,7 @@ function resolveJitterEntries({
|
|
|
99
102
|
y,
|
|
100
103
|
area,
|
|
101
104
|
shapes,
|
|
105
|
+
angles,
|
|
102
106
|
config,
|
|
103
107
|
graphic,
|
|
104
108
|
existingChildren
|
|
@@ -131,7 +135,8 @@ function resolveJitterEntries({
|
|
|
131
135
|
area: resolvedArea,
|
|
132
136
|
strokeWidth: typeof config.stroke === "string"
|
|
133
137
|
? config.strokeWidth ?? 1
|
|
134
|
-
: 0
|
|
138
|
+
: 0,
|
|
139
|
+
angle: angles?.[index]
|
|
135
140
|
});
|
|
136
141
|
return [{
|
|
137
142
|
index,
|
|
@@ -150,6 +155,7 @@ function applyPointJitter(program, {
|
|
|
150
155
|
y,
|
|
151
156
|
area,
|
|
152
157
|
shapes,
|
|
158
|
+
angles,
|
|
153
159
|
config,
|
|
154
160
|
graphic,
|
|
155
161
|
existingChildren
|
|
@@ -172,6 +178,7 @@ function applyPointJitter(program, {
|
|
|
172
178
|
y,
|
|
173
179
|
area,
|
|
174
180
|
shapes,
|
|
181
|
+
angles,
|
|
175
182
|
config,
|
|
176
183
|
graphic,
|
|
177
184
|
existingChildren
|
|
@@ -234,6 +241,7 @@ export const rematerializePointMark = action(
|
|
|
234
241
|
const area = resolveRowEncodingValues(this, layer, dataset, "size");
|
|
235
242
|
const encodedShape = resolveRowEncodingValues(this, layer, dataset, "shape");
|
|
236
243
|
const encodedOpacity = resolveRowEncodingValues(this, layer, dataset, "opacity");
|
|
244
|
+
const angles = resolveDirectionValues(dataset.values, layer.encoding?.angle);
|
|
237
245
|
const config = this.markConfigs[id] ?? {};
|
|
238
246
|
const fill = mappedFill ?? config.fill ?? DEFAULT_POINT_FILL;
|
|
239
247
|
const shapes = encodedShape ?? dataset.values.map(() => config.shape ?? "circle");
|
|
@@ -241,6 +249,7 @@ export const rematerializePointMark = action(
|
|
|
241
249
|
const constantShape = validatePointShape(config.shape ?? "circle");
|
|
242
250
|
const requiresMixedCollection =
|
|
243
251
|
encodedShape !== undefined ||
|
|
252
|
+
angles !== undefined ||
|
|
244
253
|
graphic.type === "collection" ||
|
|
245
254
|
getPointGraphicType(constantShape) !== graphic.type;
|
|
246
255
|
const jittered = applyPointJitter(this, {
|
|
@@ -251,6 +260,7 @@ export const rematerializePointMark = action(
|
|
|
251
260
|
y: positions.y,
|
|
252
261
|
area,
|
|
253
262
|
shapes,
|
|
263
|
+
angles,
|
|
254
264
|
config,
|
|
255
265
|
graphic,
|
|
256
266
|
existingChildren
|
|
@@ -273,6 +283,7 @@ export const rematerializePointMark = action(
|
|
|
273
283
|
);
|
|
274
284
|
const centerX = x?.[index] ?? existing.x;
|
|
275
285
|
const centerY = y?.[index] ?? existing.y;
|
|
286
|
+
const angle = angles?.[index];
|
|
276
287
|
if (
|
|
277
288
|
resolvedArea === undefined ||
|
|
278
289
|
!Number.isFinite(centerX) ||
|
|
@@ -283,7 +294,7 @@ export const rematerializePointMark = action(
|
|
|
283
294
|
y: centerY,
|
|
284
295
|
fill: color,
|
|
285
296
|
opacity
|
|
286
|
-
});
|
|
297
|
+
}, angle);
|
|
287
298
|
}
|
|
288
299
|
return createPointShapeGraphic({
|
|
289
300
|
shape,
|
|
@@ -295,7 +306,8 @@ export const rematerializePointMark = action(
|
|
|
295
306
|
...(config.strokeWidth === undefined
|
|
296
307
|
? {}
|
|
297
308
|
: { strokeWidth: config.strokeWidth }),
|
|
298
|
-
opacity
|
|
309
|
+
opacity,
|
|
310
|
+
angle
|
|
299
311
|
});
|
|
300
312
|
});
|
|
301
313
|
return jittered.program.editGraphics({
|