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.
Files changed (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/src/actions/categoryOrder/index.js +111 -0
  5. package/src/actions/data/index.js +6 -0
  6. package/src/actions/data/timeUnit.js +48 -0
  7. package/src/actions/encodings/angle.js +77 -0
  8. package/src/actions/encodings/color/index.js +17 -13
  9. package/src/actions/encodings/color/layout.js +2 -0
  10. package/src/actions/encodings/color/policy.js +3 -0
  11. package/src/actions/encodings/index.js +2 -0
  12. package/src/actions/encodings/position/policies/area.js +40 -2
  13. package/src/actions/encodings/position/policies/bar.js +6 -0
  14. package/src/actions/encodings/position/policies/index.js +2 -0
  15. package/src/actions/encodings/position/policies/tick.js +19 -0
  16. package/src/actions/encodings/remove.js +1 -1
  17. package/src/actions/guides/legends/categorical/actions.js +23 -6
  18. package/src/actions/guides/legends/categorical/index.js +8 -0
  19. package/src/actions/guides/legends/continuous/common.js +17 -5
  20. package/src/actions/guides/legends/continuous/gradient.js +12 -8
  21. package/src/actions/guides/legends/continuous/opacity.js +70 -17
  22. package/src/actions/guides/legends/edit.js +13 -8
  23. package/src/actions/guides/legends/lane.js +468 -0
  24. package/src/actions/guides/legends/remove.js +3 -7
  25. package/src/actions/index.js +2 -0
  26. package/src/actions/marks/area/actions.js +3 -1
  27. package/src/actions/marks/area/materialize.js +12 -3
  28. package/src/actions/marks/index.js +2 -0
  29. package/src/actions/marks/point/materialize.js +17 -5
  30. package/src/actions/marks/tick/actions.js +225 -0
  31. package/src/actions/marks/tick/index.js +1 -0
  32. package/src/actions/primitives/semanticValidation/layer.js +2 -0
  33. package/src/actions/scales/consumers/index.js +8 -0
  34. package/src/actions/scales/consumers/seriesLayout.js +22 -2
  35. package/src/actions/scales/materialize.js +2 -0
  36. package/src/actions/selection/actions.js +5 -2
  37. package/src/core/vocabulary.js +7 -3
  38. package/src/grammar/areaSeries.js +112 -2
  39. package/src/grammar/categoryOrder.js +138 -0
  40. package/src/grammar/direction.js +46 -0
  41. package/src/grammar/facets/index.js +1 -1
  42. package/src/grammar/pointShapes.js +34 -6
  43. package/src/grammar/positionCompatibility.js +4 -0
  44. package/src/grammar/schemas/semanticPath.js +2 -1
  45. package/src/grammar/seriesLayout.js +10 -2
  46. package/src/grammar/timeUnit.js +108 -0
  47. package/src/grammar/transforms.js +6 -0
  48. package/src/grammar/window.js +73 -7
  49. package/src/layout/legendLane.js +338 -0
  50. package/src/materialization/marks/capabilities.js +9 -0
  51. package/src/materialization/marks/index.js +2 -1
  52. package/src/materialization/marks/policies.js +13 -0
  53. package/src/materialization/scales/policies/series.js +9 -0
  54. package/src/materialization/scales/resolve.js +25 -3
  55. package/src/materialization/selection/items/index.js +1 -0
  56. package/src/materialization/selection/items/path.js +4 -1
  57. package/src/materialization/selection/items/tick.js +42 -0
  58. package/src/materialization/selection/policies/index.js +2 -0
  59. package/src/materialization/selection/policies/tick.js +10 -0
  60. package/types/index.d.ts +10 -0
  61. package/types/program.d.ts +96 -3
@@ -0,0 +1,338 @@
1
+ export const SIDE_LEGEND_BLOCK_GAP = 24;
2
+ export const HORIZONTAL_LEGEND_BLOCK_GAP = 40;
3
+ export const SIDE_LEGEND_SYMBOL_CENTER = 16;
4
+ export const SIDE_LEGEND_LABEL_START = 44;
5
+ export const HORIZONTAL_LEGEND_TITLE_ELEMENT_GAP = 12;
6
+
7
+ function unionBounds(bounds) {
8
+ return {
9
+ left: Math.min(...bounds.map(item => item.left)),
10
+ right: Math.max(...bounds.map(item => item.right)),
11
+ top: Math.min(...bounds.map(item => item.top)),
12
+ bottom: Math.max(...bounds.map(item => item.bottom))
13
+ };
14
+ }
15
+
16
+ function decoration(border) {
17
+ return border === false
18
+ ? 0
19
+ : border.padding + border.lineWidth / 2;
20
+ }
21
+
22
+ function horizontalExtent(blocks) {
23
+ const left = [0];
24
+ const right = [0];
25
+ for (const block of blocks) {
26
+ if (block.title !== undefined) right.push(block.title.width);
27
+ left.push(
28
+ SIDE_LEGEND_SYMBOL_CENTER + block.symbol.left - block.symbol.centerX
29
+ );
30
+ right.push(
31
+ SIDE_LEGEND_SYMBOL_CENTER + block.symbol.right - block.symbol.centerX
32
+ );
33
+ right.push(SIDE_LEGEND_LABEL_START + block.labels.width);
34
+ }
35
+ return { left: Math.min(...left), right: Math.max(...right) };
36
+ }
37
+
38
+ function placeBlock(block, cursor) {
39
+ let dy;
40
+ if (block.title === undefined) {
41
+ dy = cursor - block.bounds.top;
42
+ } else {
43
+ const desiredTitleY = Math.ceil(cursor + block.title.fontSize / 2);
44
+ dy = desiredTitleY - block.title.y;
45
+ const futureTop = block.bounds.top + dy;
46
+ if (futureTop < cursor) dy += cursor - futureTop;
47
+ }
48
+ const bounds = {
49
+ left: block.bounds.left,
50
+ right: block.bounds.right,
51
+ top: block.bounds.top + dy,
52
+ bottom: block.bounds.bottom + dy
53
+ };
54
+ return {
55
+ id: block.id,
56
+ dy,
57
+ titleDx: block.title === undefined
58
+ ? undefined
59
+ : -block.title.x,
60
+ symbolDx: SIDE_LEGEND_SYMBOL_CENTER - block.symbol.centerX,
61
+ labelDx: SIDE_LEGEND_LABEL_START - block.labels.x,
62
+ bounds
63
+ };
64
+ }
65
+
66
+ function overlap(a, b) {
67
+ return a.left < b.right && a.right > b.left &&
68
+ a.top < b.bottom && a.bottom > b.top;
69
+ }
70
+
71
+ export function resolveSideLegendLane({
72
+ side,
73
+ plot,
74
+ canvas,
75
+ groups,
76
+ axisBounds
77
+ }) {
78
+ if (!["right", "left"].includes(side)) {
79
+ throw new Error(`Unsupported side legend lane "${side}".`);
80
+ }
81
+ const blocks = groups.flatMap(group => group.blocks);
82
+ if (blocks.length < 2) return undefined;
83
+ const laneExtent = horizontalExtent(blocks);
84
+ const offset = Math.max(...blocks.map(block => block.offset));
85
+ const titleStartX = side === "right"
86
+ ? plot.x + plot.width + offset
87
+ : plot.x - offset - laneExtent.right;
88
+ const symbolCenterX = titleStartX + SIDE_LEGEND_SYMBOL_CENTER;
89
+ const labelStartX = titleStartX + SIDE_LEGEND_LABEL_START;
90
+ const first = blocks[0];
91
+ let cursor = first.title === undefined
92
+ ? plot.y + 12
93
+ : plot.y + 20 - first.title.fontSize / 2;
94
+ const placements = [];
95
+ const backgrounds = [];
96
+ const occupiedGroups = [];
97
+
98
+ for (const group of groups) {
99
+ const inset = decoration(group.border);
100
+ if (occupiedGroups.length > 0) cursor += inset;
101
+ const groupPlacements = [];
102
+ for (const block of group.blocks) {
103
+ const placement = placeBlock(block, cursor);
104
+ groupPlacements.push(placement);
105
+ placements.push({
106
+ ...placement,
107
+ titleDx: placement.titleDx === undefined
108
+ ? undefined
109
+ : placement.titleDx + titleStartX,
110
+ symbolDx: placement.symbolDx + titleStartX,
111
+ labelDx: placement.labelDx + titleStartX
112
+ });
113
+ cursor = placement.bounds.bottom + SIDE_LEGEND_BLOCK_GAP;
114
+ }
115
+ const vertical = unionBounds(groupPlacements.map(item => item.bounds));
116
+ const groupExtent = horizontalExtent(group.blocks);
117
+ const foreground = {
118
+ left: titleStartX + groupExtent.left,
119
+ right: titleStartX + groupExtent.right,
120
+ top: vertical.top,
121
+ bottom: vertical.bottom
122
+ };
123
+ const occupied = {
124
+ left: foreground.left - inset,
125
+ right: foreground.right + inset,
126
+ top: foreground.top - inset,
127
+ bottom: foreground.bottom + inset
128
+ };
129
+ occupiedGroups.push(occupied);
130
+ if (group.backgroundId !== undefined) {
131
+ backgrounds.push({
132
+ id: group.backgroundId,
133
+ x: foreground.left - group.border.padding,
134
+ y: foreground.top - group.border.padding,
135
+ width: foreground.right - foreground.left + group.border.padding * 2,
136
+ height: foreground.bottom - foreground.top + group.border.padding * 2
137
+ });
138
+ }
139
+ cursor = occupied.bottom + SIDE_LEGEND_BLOCK_GAP;
140
+ }
141
+
142
+ const occupied = unionBounds(occupiedGroups);
143
+ if (
144
+ occupied.left < 0 || occupied.right > canvas.width ||
145
+ occupied.top < 0 || occupied.bottom > canvas.height
146
+ ) {
147
+ throw new Error(`Legend lane requires more ${side}-margin or vertical Canvas space.`);
148
+ }
149
+ if (axisBounds !== undefined && overlap(occupied, axisBounds)) {
150
+ throw new Error(`${side[0].toUpperCase()}${side.slice(1)} legend lane and y-axis guides require more margin space.`);
151
+ }
152
+ return {
153
+ side,
154
+ titleStartX,
155
+ symbolCenterX,
156
+ labelStartX,
157
+ placements,
158
+ backgrounds,
159
+ occupied
160
+ };
161
+ }
162
+
163
+ function translateBounds(bounds, dy) {
164
+ return {
165
+ left: bounds.left,
166
+ right: bounds.right,
167
+ top: bounds.top + dy,
168
+ bottom: bounds.bottom + dy
169
+ };
170
+ }
171
+
172
+ function translateBoundsX(bounds, dx) {
173
+ return {
174
+ left: bounds.left + dx,
175
+ right: bounds.right + dx,
176
+ top: bounds.top,
177
+ bottom: bounds.bottom
178
+ };
179
+ }
180
+
181
+ function expandBounds(bounds, inset) {
182
+ return {
183
+ left: bounds.left - inset,
184
+ right: bounds.right + inset,
185
+ top: bounds.top - inset,
186
+ bottom: bounds.bottom + inset
187
+ };
188
+ }
189
+
190
+ function packHorizontalRows(groups, plot) {
191
+ const rows = [];
192
+ let row = [];
193
+ let cursor = plot.x;
194
+ for (const group of groups) {
195
+ const interval = expandBounds(group.horizontal, group.inset);
196
+ const width = interval.right - interval.left;
197
+ if (width > plot.width) {
198
+ throw new Error("Horizontal legend block requires more plot width.");
199
+ }
200
+ let dx = cursor - interval.left;
201
+ let placed = translateBoundsX(interval, dx);
202
+ if (row.length > 0 && placed.right > plot.x + plot.width) {
203
+ rows.push(row);
204
+ row = [];
205
+ cursor = plot.x;
206
+ dx = cursor - interval.left;
207
+ placed = translateBoundsX(interval, dx);
208
+ }
209
+ row.push({ group, interval: placed, dx });
210
+ cursor = placed.right + HORIZONTAL_LEGEND_BLOCK_GAP;
211
+ }
212
+ if (row.length > 0) rows.push(row);
213
+ return rows;
214
+ }
215
+
216
+ function normalizeHorizontalRow(entries) {
217
+ const stacked = entries.filter(entry =>
218
+ entry.group.title !== undefined && entry.group.inline !== true
219
+ );
220
+ const commonTitleY = stacked[0]?.group.title.y;
221
+ const titleDescent = stacked.length === 0
222
+ ? 0
223
+ : Math.max(...stacked.map(
224
+ entry => entry.group.title.bounds.bottom - entry.group.title.y
225
+ ));
226
+ const elementAnchor = commonTitleY === undefined
227
+ ? (entries[0].group.element.top + entries[0].group.element.bottom) / 2
228
+ : commonTitleY + titleDescent + HORIZONTAL_LEGEND_TITLE_ELEMENT_GAP;
229
+ return entries.map(({ group, dx }) => {
230
+ const contentDy = commonTitleY === undefined
231
+ ? elementAnchor - (group.element.top + group.element.bottom) / 2
232
+ : elementAnchor - group.element.top;
233
+ const titleDy = group.title === undefined
234
+ ? 0
235
+ : group.inline === true
236
+ ? contentDy
237
+ : commonTitleY - group.title.y;
238
+ const foreground = unionBounds([
239
+ ...(group.title === undefined
240
+ ? []
241
+ : [translateBoundsX(translateBounds(group.title.bounds, titleDy), dx)]),
242
+ translateBoundsX(translateBounds(group.content, contentDy), dx)
243
+ ]);
244
+ return {
245
+ id: group.id,
246
+ dx,
247
+ titleDy,
248
+ contentDy,
249
+ foreground,
250
+ occupied: expandBounds(foreground, group.inset),
251
+ padding: group.padding,
252
+ backgroundId: group.backgroundId
253
+ };
254
+ });
255
+ }
256
+
257
+ function translateHorizontalPlacement(placement, dy) {
258
+ return {
259
+ ...placement,
260
+ titleDy: placement.titleDy + dy,
261
+ contentDy: placement.contentDy + dy,
262
+ foreground: translateBounds(placement.foreground, dy),
263
+ occupied: translateBounds(placement.occupied, dy)
264
+ };
265
+ }
266
+
267
+ export function resolveHorizontalLegendLane({
268
+ edge,
269
+ plot,
270
+ canvas,
271
+ groups,
272
+ collisionBounds = []
273
+ }) {
274
+ if (!["top", "bottom"].includes(edge)) {
275
+ throw new Error(`Unsupported horizontal legend lane "${edge}".`);
276
+ }
277
+ if (groups.length < 2) return undefined;
278
+ const packed = packHorizontalRows(groups, plot);
279
+ const normalized = packed.map(normalizeHorizontalRow);
280
+ const placements = [];
281
+ let previousRowBounds;
282
+ for (let index = 0; index < normalized.length; index += 1) {
283
+ const row = normalized[index];
284
+ const rowBounds = unionBounds(row.map(item => item.occupied));
285
+ let dy;
286
+ if (index === 0) {
287
+ const anchor = edge === "top"
288
+ ? Math.max(...packed[index].map(item => item.group.horizontal.bottom))
289
+ : Math.min(...packed[index].map(item => item.group.horizontal.top));
290
+ dy = edge === "top"
291
+ ? anchor - rowBounds.bottom
292
+ : anchor - rowBounds.top;
293
+ } else {
294
+ dy = edge === "bottom"
295
+ ? previousRowBounds.bottom + HORIZONTAL_LEGEND_BLOCK_GAP - rowBounds.top
296
+ : previousRowBounds.top - HORIZONTAL_LEGEND_BLOCK_GAP - rowBounds.bottom;
297
+ }
298
+ const translated = row.map(item => translateHorizontalPlacement(item, dy));
299
+ placements.push(...translated);
300
+ previousRowBounds = unionBounds(translated.map(item => item.occupied));
301
+ }
302
+ const occupied = unionBounds(placements.map(item => item.occupied));
303
+ if (placements.some(placement =>
304
+ placement.occupied.left < 0 || placement.occupied.right > canvas.width ||
305
+ placement.occupied.top < 0 || placement.occupied.bottom > canvas.height
306
+ )) {
307
+ throw new Error(`Legend lane requires more ${edge}-margin or Canvas space.`);
308
+ }
309
+ if (placements.some(placement =>
310
+ collisionBounds.some(bounds => overlap(placement.occupied, bounds))
311
+ )) {
312
+ const owner = edge === "top" ? "chart titles" : "x-axis guides";
313
+ throw new Error(
314
+ `${edge[0].toUpperCase()}${edge.slice(1)} legend lane and ${owner} require more margin space.`
315
+ );
316
+ }
317
+ return {
318
+ edge,
319
+ placements: placements.map(placement => ({
320
+ ...placement,
321
+ ...(placement.backgroundId === undefined
322
+ ? {}
323
+ : {
324
+ background: {
325
+ id: placement.backgroundId,
326
+ x: placement.foreground.left - placement.padding,
327
+ y: placement.foreground.top - placement.padding,
328
+ width: placement.foreground.right - placement.foreground.left +
329
+ placement.padding * 2,
330
+ height: placement.foreground.bottom - placement.foreground.top +
331
+ placement.padding * 2
332
+ }
333
+ })
334
+ })),
335
+ occupied,
336
+ rowCount: packed.length
337
+ };
338
+ }
@@ -32,6 +32,10 @@ export function canMaterializePoint(_program, layer) {
32
32
  );
33
33
  }
34
34
 
35
+ export function canMaterializeTick(_program, layer) {
36
+ return layer.mark?.type === "tick" && hasCartesianPositionScales(layer);
37
+ }
38
+
35
39
  export function canMaterializeLine(program, layer) {
36
40
  const parallel = layer.encoding?.parallel;
37
41
  if (parallel !== undefined) {
@@ -76,8 +80,13 @@ export function canMaterializeArea(program, layer) {
76
80
  densityTransform !== undefined &&
77
81
  (densityTransform.groupBy === undefined ||
78
82
  layer.encoding?.group?.field === densityTransform.groupBy);
83
+ const completeCenter =
84
+ densityTransform === undefined &&
85
+ layer.encoding?.y?.stack === "center" &&
86
+ layer.encoding?.group?.fieldType === "nominal";
79
87
  return (
80
88
  completeDensity ||
89
+ completeCenter ||
81
90
  layer.encoding?.y2?.scale === layer.encoding.y.scale ||
82
91
  layer.encoding?.x2?.scale === layer.encoding.x.scale
83
92
  );
@@ -9,7 +9,8 @@ export {
9
9
  canMaterializePoint,
10
10
  canMaterializeRect,
11
11
  canMaterializeRule,
12
- canMaterializeText
12
+ canMaterializeText,
13
+ canMaterializeTick
13
14
  } from "./capabilities.js";
14
15
 
15
16
  export function getMarkRematerializationStep(layer) {
@@ -7,6 +7,7 @@ import {
7
7
  canMaterializeRect,
8
8
  canMaterializeRule,
9
9
  canMaterializeText,
10
+ canMaterializeTick,
10
11
  isIntentionallyEmptyArea
11
12
  } from "./capabilities.js";
12
13
 
@@ -24,6 +25,18 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
24
25
  }),
25
26
  rematerializeIncompleteExisting: true
26
27
  }),
28
+ tick: Object.freeze({
29
+ canMaterialize: canMaterializeTick,
30
+ op: "rematerializeTickMark",
31
+ positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: true }),
32
+ encoding: Object.freeze({ scaleFirst: true }),
33
+ scaleApplication: Object.freeze({
34
+ deferWithMark: true,
35
+ position: "rematerialize",
36
+ default: "defer"
37
+ }),
38
+ rematerializeIncompleteExisting: true
39
+ }),
27
40
  line: Object.freeze({
28
41
  canMaterialize: canMaterializeLine,
29
42
  op: "rematerializeLineMark",
@@ -40,6 +40,15 @@ export function resolveSeriesLayoutDomain({
40
40
  ...activeLayouts.flatMap(item => item.values),
41
41
  ...directConsumers.flatMap(item => item.values)
42
42
  ];
43
+ if (layout === "center" && scale.domain !== "auto") {
44
+ const minimum = Math.min(...values);
45
+ const maximum = Math.max(...values);
46
+ if (Math.min(...scale.domain) > minimum || Math.max(...scale.domain) < maximum) {
47
+ throw new Error(
48
+ `Center layout scale "${id}" explicit domain must contain every centered bound.`
49
+ );
50
+ }
51
+ }
43
52
  if (
44
53
  ["group", "overlay"].includes(layout) &&
45
54
  scale.domain !== "auto" &&
@@ -72,6 +72,20 @@ function resolveDefaultDomain({
72
72
  });
73
73
  }
74
74
 
75
+ function resolveCategoryOrderDomain(valuesByConsumer) {
76
+ const orders = valuesByConsumer
77
+ .map(item => item.categoryOrder)
78
+ .filter(order => order !== undefined);
79
+ if (orders.length === 0) return undefined;
80
+ const [first, ...rest] = orders;
81
+ if (rest.some(order =>
82
+ order.length !== first.length ||
83
+ order.some((value, index) => !Object.is(value, first[index])))) {
84
+ throw new Error("Shared scale category order assignments must resolve identically.");
85
+ }
86
+ return first;
87
+ }
88
+
75
89
  function resolveRange({
76
90
  scale,
77
91
  channel,
@@ -225,9 +239,17 @@ export function resolveScaleMaterialization({
225
239
  valuesByConsumer,
226
240
  seriesLayouts
227
241
  });
242
+ const categoryOrderDomain = isDiscretizedColor
243
+ ? undefined
244
+ : resolveCategoryOrderDomain(valuesByConsumer);
245
+ if (categoryOrderDomain !== undefined && scale.domain !== "auto") {
246
+ throw new Error(
247
+ `Scale "${id}" cannot combine an explicit domain with category order.`
248
+ );
249
+ }
228
250
  const domain = isDiscretizedColor
229
251
  ? discretizedScale.domain
230
- : binnedDomain ?? seriesDomain ?? resolveDefaultDomain({
252
+ : categoryOrderDomain ?? binnedDomain ?? seriesDomain ?? resolveDefaultDomain({
231
253
  scale,
232
254
  allValues,
233
255
  isOrdinalAppearance,
@@ -272,7 +294,7 @@ export function resolveScaleMaterialization({
272
294
  : isDiscretePosition
273
295
  ? resolveDiscretePositionScale({
274
296
  type: scale.type,
275
- domain: scale.domain,
297
+ domain: categoryOrderDomain ?? scale.domain,
276
298
  values: allValues,
277
299
  range: channel === "theta" && consumers.every(
278
300
  consumer => consumer.layer.mark?.type === "arc"
@@ -290,7 +312,7 @@ export function resolveScaleMaterialization({
290
312
  })
291
313
  : isOrdinalPosition
292
314
  ? resolveOrdinalPositionScale({
293
- domain: scale.domain,
315
+ domain: categoryOrderDomain ?? scale.domain,
294
316
  values: allValues,
295
317
  range: scale.range,
296
318
  channel,
@@ -3,4 +3,5 @@ export { resolveArcItems } from "./arc.js";
3
3
  export { resolveBarItems } from "./bar.js";
4
4
  export { resolvePointItems } from "./point.js";
5
5
  export { resolveRuleItems } from "./rule.js";
6
+ export { resolveTickItems } from "./tick.js";
6
7
  export { resolveRectItems } from "./rect.js";
@@ -1,4 +1,5 @@
1
1
  import {
2
+ deriveCenteredAreaSeries,
2
3
  deriveAreaSeries,
3
4
  deriveDensityAreaSeries
4
5
  } from "../../../grammar/areaSeries.js";
@@ -73,7 +74,9 @@ export function resolveLineItems(program, layer, dataset) {
73
74
  export function resolveAreaItems(program, layer, dataset) {
74
75
  const transform = findUpstreamTransform(program, dataset, "density");
75
76
  const derived = transform === undefined
76
- ? deriveAreaSeries(dataset.values, layer)
77
+ ? layer.encoding?.y?.stack === "center"
78
+ ? deriveCenteredAreaSeries(dataset.values, layer)
79
+ : deriveAreaSeries(dataset.values, layer)
77
80
  : deriveDensityAreaSeries(dataset.values, layer, transform);
78
81
  return finalizeItems(
79
82
  program,
@@ -0,0 +1,42 @@
1
+ import { selectMarkItemKeys } from "../../../grammar/markSelection.js";
2
+ import {
3
+ channelMapFromRow,
4
+ concreteProperties,
5
+ finalizeItems,
6
+ itemKey,
7
+ ownFields
8
+ } from "./common.js";
9
+
10
+ export function resolveTickItems(program, layer, dataset) {
11
+ const graphic = program.graphicSpec.objects[layer.id];
12
+ const completePosition =
13
+ layer.encoding?.x?.scale !== undefined &&
14
+ layer.encoding?.y?.scale !== undefined;
15
+ if (!Array.isArray(graphic?.items) || !completePosition) {
16
+ throw new Error(`Tick mark "${layer.id}" is incomplete for selection.`);
17
+ }
18
+ let definitions = dataset.values.map((row, index) => ({
19
+ key: itemKey(layer, "tick", index),
20
+ fields: ownFields(row),
21
+ channels: channelMapFromRow(row, layer),
22
+ properties: concreteProperties(graphic.items[index]?.properties),
23
+ members: [row]
24
+ }));
25
+ for (const config of Object.values(
26
+ program.materializationConfigs.highlights ?? {}
27
+ )) {
28
+ if (config.target !== layer.id || config.bringToFront !== true) continue;
29
+ const selection = program.materializationConfigs.selections?.[config.selection];
30
+ if (selection?.target !== layer.id) continue;
31
+ const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
32
+ definitions = [
33
+ ...definitions.filter(definition => !selected.has(definition.key)),
34
+ ...definitions.filter(definition => selected.has(definition.key))
35
+ ];
36
+ }
37
+ definitions = definitions.map((definition, index) => ({
38
+ ...definition,
39
+ properties: concreteProperties(graphic.items[index]?.properties)
40
+ }));
41
+ return finalizeItems(program, layer, "tick", definitions, "line");
42
+ }
@@ -6,6 +6,7 @@ import { barSelectionPolicy } from "./bar.js";
6
6
  import { lineSelectionPolicy } from "./line.js";
7
7
  import { pointSelectionPolicy } from "./point.js";
8
8
  import { ruleSelectionPolicy } from "./rule.js";
9
+ import { tickSelectionPolicy } from "./tick.js";
9
10
  import { rectSelectionPolicy } from "./rect.js";
10
11
 
11
12
  const POLICIES = Object.freeze({
@@ -15,6 +16,7 @@ const POLICIES = Object.freeze({
15
16
  line: lineSelectionPolicy,
16
17
  point: pointSelectionPolicy,
17
18
  rule: ruleSelectionPolicy,
19
+ tick: tickSelectionPolicy,
18
20
  rect: rectSelectionPolicy
19
21
  });
20
22
 
@@ -0,0 +1,10 @@
1
+ import { resolveTickItems } from "../items/index.js";
2
+ import { normalizeStrokeHighlightStyle } from "../styles.js";
3
+
4
+ export const tickSelectionPolicy = Object.freeze({
5
+ supportedGrains: Object.freeze(["item"]),
6
+ resolveItems: resolveTickItems,
7
+ normalizeHighlightStyle: args => normalizeStrokeHighlightStyle(args, "Tick"),
8
+ applyHighlightOp: "applyRuleHighlight",
9
+ rematerializeOp: "rematerializeTickMark"
10
+ });
package/types/index.d.ts CHANGED
@@ -23,6 +23,9 @@ export type {
23
23
  Bin2DOutputFields,
24
24
  CanvasOptions,
25
25
  CategoricalEncodingOptions,
26
+ CategoryOrder,
27
+ CategoryOrderSummary,
28
+ CategoryValue,
26
29
  ColorLayout,
27
30
  ColorEncodingOptions,
28
31
  CompleteAxisOptions,
@@ -66,6 +69,7 @@ export type {
66
69
  DatasetFilterTransform,
67
70
  DatasetIntervalOutputFields,
68
71
  DatasetIntervalTransform,
72
+ DatasetTimeUnitTransform,
69
73
  DatasetWindowOperation,
70
74
  DatasetWindowSort,
71
75
  DatasetWindowTransform,
@@ -141,6 +145,7 @@ export type {
141
145
  ContinuousColorScaleOptions,
142
146
  OpacityEncodingOptions,
143
147
  OpacityScaleOptions,
148
+ OrderCategoriesOptions,
144
149
  OffsetScaleOptions,
145
150
  PathOrderEncodingOptions,
146
151
  ParallelCoordinatesEncodingOptions,
@@ -153,6 +158,7 @@ export type {
153
158
  Palette,
154
159
  PaletteName,
155
160
  PositionEncodingOptions,
161
+ YPositionEncodingOptions,
156
162
  RulePositionEncodingOptions,
157
163
  SecondaryPositionEncodingOptions,
158
164
  RegressionBandOptions,
@@ -165,6 +171,7 @@ export type {
165
171
  EditRegressionOptions,
166
172
  EditRectMarkOptions,
167
173
  RemoveAxisOptions,
174
+ RemoveCategoryOrderOptions,
168
175
  RemoveGridOptions,
169
176
  RemoveJitterOptions,
170
177
  RemoveLabelLayoutOptions,
@@ -175,6 +182,7 @@ export type {
175
182
  ScaleOptions,
176
183
  ScaleType,
177
184
  StackMode,
185
+ YStackMode,
178
186
  SemanticCoordinate,
179
187
  SemanticDataset,
180
188
  SemanticLayer,
@@ -186,6 +194,8 @@ export type {
186
194
  StrokeWidthScaleOptions,
187
195
  ThetaEncodingOptions,
188
196
  ThetaScaleOptions,
197
+ TimeUnit,
198
+ TimeUnitDataOptions,
189
199
  TraceNode,
190
200
  TitleOptions,
191
201
  EditTitleOptions,