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.
Files changed (96) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +77 -14
  3. package/package.json +19 -2
  4. package/src/BasicChartProgram.js +10 -0
  5. package/src/actions/basic.js +21 -0
  6. package/src/actions/canvas/actions.js +42 -0
  7. package/src/actions/canvas/index.js +5 -1
  8. package/src/actions/categoryOrder/index.js +111 -0
  9. package/src/actions/charts/basic.js +13 -0
  10. package/src/actions/data/basic.js +11 -0
  11. package/src/actions/data/basicBin2d.js +43 -0
  12. package/src/actions/data/bin2d.js +1 -26
  13. package/src/actions/data/bin2dMaterialize.js +29 -0
  14. package/src/actions/data/index.js +6 -0
  15. package/src/actions/data/timeUnit.js +48 -0
  16. package/src/actions/encodings/angle.js +77 -0
  17. package/src/actions/encodings/appearance.js +6 -2
  18. package/src/actions/encodings/basic.js +21 -0
  19. package/src/actions/encodings/color/index.js +17 -13
  20. package/src/actions/encodings/color/layout.js +2 -0
  21. package/src/actions/encodings/color/policy.js +3 -0
  22. package/src/actions/encodings/index.js +2 -0
  23. package/src/actions/encodings/position/index.js +5 -1
  24. package/src/actions/encodings/position/policies/area.js +40 -2
  25. package/src/actions/encodings/position/policies/bar.js +6 -0
  26. package/src/actions/encodings/position/policies/index.js +2 -0
  27. package/src/actions/encodings/position/policies/tick.js +19 -0
  28. package/src/actions/encodings/ranged.js +6 -2
  29. package/src/actions/encodings/remove.js +1 -1
  30. package/src/actions/guides/axes/index.js +10 -2
  31. package/src/actions/guides/basic.js +19 -0
  32. package/src/actions/guides/grids/grid.js +7 -3
  33. package/src/actions/guides/grids/index.js +1 -1
  34. package/src/actions/guides/legends/categorical/actions.js +23 -6
  35. package/src/actions/guides/legends/categorical/index.js +14 -2
  36. package/src/actions/guides/legends/continuous/common.js +17 -5
  37. package/src/actions/guides/legends/continuous/gradient.js +12 -8
  38. package/src/actions/guides/legends/continuous/index.js +6 -2
  39. package/src/actions/guides/legends/continuous/opacity.js +70 -17
  40. package/src/actions/guides/legends/edit.js +13 -8
  41. package/src/actions/guides/legends/lane.js +468 -0
  42. package/src/actions/guides/legends/remove.js +3 -7
  43. package/src/actions/index.js +2 -0
  44. package/src/actions/marks/area/actions.js +3 -1
  45. package/src/actions/marks/area/materialize.js +12 -3
  46. package/src/actions/marks/bar/index.js +5 -1
  47. package/src/actions/marks/basic.js +11 -0
  48. package/src/actions/marks/index.js +2 -0
  49. package/src/actions/marks/line/actions.js +5 -1
  50. package/src/actions/marks/line/index.js +4 -1
  51. package/src/actions/marks/point/index.js +5 -1
  52. package/src/actions/marks/point/materialize.js +17 -5
  53. package/src/actions/marks/rect/actions.js +5 -1
  54. package/src/actions/marks/rect/index.js +4 -1
  55. package/src/actions/marks/tick/actions.js +225 -0
  56. package/src/actions/marks/tick/index.js +1 -0
  57. package/src/actions/primitives/semanticValidation/layer.js +2 -0
  58. package/src/actions/scales/consumers/index.js +8 -0
  59. package/src/actions/scales/consumers/seriesLayout.js +22 -2
  60. package/src/actions/scales/index.js +5 -1
  61. package/src/actions/scales/materialize.js +2 -0
  62. package/src/actions/selection/actions.js +5 -2
  63. package/src/basic.js +2 -0
  64. package/src/core/vocabulary.js +7 -3
  65. package/src/grammar/areaSeries.js +112 -2
  66. package/src/grammar/categoryOrder.js +138 -0
  67. package/src/grammar/direction.js +46 -0
  68. package/src/grammar/facets/index.js +1 -1
  69. package/src/grammar/pointShapes.js +34 -6
  70. package/src/grammar/positionCompatibility.js +4 -0
  71. package/src/grammar/schemas/semanticPath.js +2 -1
  72. package/src/grammar/seriesLayout.js +10 -2
  73. package/src/grammar/timeUnit.js +108 -0
  74. package/src/grammar/transforms.js +6 -0
  75. package/src/grammar/window.js +73 -7
  76. package/src/layout/legendLane.js +338 -0
  77. package/src/materialization/marks/capabilities.js +9 -0
  78. package/src/materialization/marks/index.js +2 -1
  79. package/src/materialization/marks/policies.js +13 -0
  80. package/src/materialization/scales/policies/series.js +9 -0
  81. package/src/materialization/scales/resolve.js +25 -3
  82. package/src/materialization/selection/items/index.js +1 -0
  83. package/src/materialization/selection/items/path.js +4 -1
  84. package/src/materialization/selection/items/tick.js +42 -0
  85. package/src/materialization/selection/policies/index.js +2 -0
  86. package/src/materialization/selection/policies/tick.js +10 -0
  87. package/src/renderers/canvas/index.js +96 -60
  88. package/src/renderers/canvas/text.js +3 -14
  89. package/src/renderers/pdf.js +112 -0
  90. package/src/renderers/svg.js +559 -0
  91. package/src/renderers/text.js +11 -0
  92. package/types/basic.d.ts +107 -0
  93. package/types/index.d.ts +10 -0
  94. package/types/pdf.d.ts +26 -0
  95. package/types/program.d.ts +96 -3
  96. 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
- const removedSemanticKinds = new Set(
117
- kinds.map(kind => legendResourcePolicy(kind).semanticKind)
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));
@@ -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
- ? series.values.map(value => value.y)
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
- ? series.values.map(value => value.y2)
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
- ? deriveAreaSeries(rows, layer)
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
  }
@@ -1 +1,4 @@
1
- export { registerLineMarkActions } from "./actions.js";
1
+ export {
2
+ registerBasicLineMarkActions,
3
+ registerLineMarkActions
4
+ } from "./actions.js";
@@ -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
  }