ggaction 0.0.2 → 0.0.3

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 (199) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +12 -1
  3. package/package.json +6 -1
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/encodings/appearance.js +11 -0
  16. package/src/actions/encodings/barWidth.js +5 -3
  17. package/src/actions/encodings/color.js +120 -25
  18. package/src/actions/encodings/index.js +2 -0
  19. package/src/actions/encodings/offset.js +90 -72
  20. package/src/actions/encodings/position/apply.js +66 -0
  21. package/src/actions/encodings/position/policies/arc.js +26 -0
  22. package/src/actions/encodings/position/policies/bar.js +9 -5
  23. package/src/actions/encodings/position/policies/index.js +5 -1
  24. package/src/actions/encodings/position/policies/line.js +20 -3
  25. package/src/actions/encodings/position/resolve.js +57 -8
  26. package/src/actions/encodings/position.js +44 -90
  27. package/src/actions/encodings/ranged.js +28 -6
  28. package/src/actions/encodings/shared.js +17 -21
  29. package/src/actions/encodings/text.js +73 -0
  30. package/src/actions/errorBands/create.js +37 -29
  31. package/src/actions/errorBands/edit.js +206 -0
  32. package/src/actions/errorBands/index.js +9 -0
  33. package/src/actions/errorBands/options.js +39 -0
  34. package/src/actions/errorBars/create.js +50 -43
  35. package/src/actions/errorBars/edit.js +130 -0
  36. package/src/actions/errorBars/index.js +3 -0
  37. package/src/actions/errorBars/options.js +52 -0
  38. package/src/actions/facets/actions.js +162 -0
  39. package/src/actions/facets/derive.js +200 -0
  40. package/src/actions/facets/guides.js +16 -0
  41. package/src/actions/facets/index.js +1 -0
  42. package/src/actions/facets/replay.js +69 -0
  43. package/src/actions/guides/applicability.js +113 -0
  44. package/src/actions/guides/axes/axes.js +70 -16
  45. package/src/actions/guides/axes/edit.js +126 -0
  46. package/src/actions/guides/axes/index.js +12 -0
  47. package/src/actions/guides/axes/labels.js +21 -18
  48. package/src/actions/guides/axes/remove.js +60 -0
  49. package/src/actions/guides/axes/tickGroups.js +7 -5
  50. package/src/actions/guides/axes/ticks.js +9 -4
  51. package/src/actions/guides/axes/titles.js +7 -5
  52. package/src/actions/guides/grids/grid.js +144 -15
  53. package/src/actions/guides/grids/resolve.js +5 -13
  54. package/src/actions/guides/guides.js +17 -75
  55. package/src/actions/guides/index.js +6 -0
  56. package/src/actions/guides/legends/categorical/actions.js +48 -9
  57. package/src/actions/guides/legends/categorical/components.js +16 -16
  58. package/src/actions/guides/legends/categorical/index.js +14 -14
  59. package/src/actions/guides/legends/categorical/layout.js +3 -1
  60. package/src/actions/guides/legends/categorical/options.js +2 -5
  61. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  62. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  63. package/src/actions/guides/legends/continuous/common.js +7 -7
  64. package/src/actions/guides/legends/continuous/interval.js +5 -5
  65. package/src/actions/guides/legends/edit.js +2 -6
  66. package/src/actions/guides/legends/focused.js +63 -0
  67. package/src/actions/guides/legends/index.js +4 -0
  68. package/src/actions/guides/legends/remove.js +64 -0
  69. package/src/actions/guides/legends/size.js +12 -14
  70. package/src/actions/guides/polar/axes/facade.js +219 -0
  71. package/src/actions/guides/polar/axes/labels.js +188 -0
  72. package/src/actions/guides/polar/axes/lines.js +113 -0
  73. package/src/actions/guides/polar/axes/shared.js +112 -0
  74. package/src/actions/guides/polar/axes/ticks.js +164 -0
  75. package/src/actions/guides/polar/axes/titles.js +165 -0
  76. package/src/actions/guides/polar/axes.js +55 -0
  77. package/src/actions/guides/polar/grids.js +252 -0
  78. package/src/actions/guides/polar/index.js +2 -0
  79. package/src/actions/guides/polar/resolve.js +269 -0
  80. package/src/actions/index.js +4 -0
  81. package/src/actions/marks/arc/actions.js +239 -0
  82. package/src/actions/marks/arc/index.js +1 -0
  83. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  84. package/src/actions/marks/area/index.js +4 -0
  85. package/src/actions/marks/bar/create.js +26 -5
  86. package/src/actions/marks/bar/materialize.js +25 -31
  87. package/src/actions/marks/index.js +12 -4
  88. package/src/actions/marks/lifecycle.js +27 -0
  89. package/src/actions/marks/line/actions.js +373 -0
  90. package/src/actions/marks/line/index.js +1 -0
  91. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  92. package/src/actions/marks/point/index.js +1 -0
  93. package/src/actions/marks/rect/actions.js +165 -0
  94. package/src/actions/marks/rect/index.js +1 -0
  95. package/src/actions/marks/remove.js +214 -0
  96. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  97. package/src/actions/marks/rule/index.js +1 -0
  98. package/src/actions/marks/shared.js +156 -25
  99. package/src/actions/marks/text/actions.js +192 -0
  100. package/src/actions/marks/text/index.js +1 -0
  101. package/src/actions/primitives/createGraphics.js +13 -6
  102. package/src/actions/primitives/editGraphics.js +10 -3
  103. package/src/actions/primitives/semantic.js +26 -3
  104. package/src/actions/primitives/semanticValue.js +25 -3
  105. package/src/actions/regression/components.js +1 -1
  106. package/src/actions/regression/create.js +18 -1
  107. package/src/actions/regression/edit.js +247 -0
  108. package/src/actions/regression/index.js +3 -0
  109. package/src/actions/regression/resolve.js +3 -1
  110. package/src/actions/scales/consumers.js +15 -6
  111. package/src/actions/scales/definitions.js +13 -4
  112. package/src/actions/scales/edit.js +42 -19
  113. package/src/actions/scales/materialize.js +57 -408
  114. package/src/actions/selection/actions.js +38 -24
  115. package/src/actions/selection/index.js +2 -0
  116. package/src/actions/titles/actions.js +63 -2
  117. package/src/actions/titles/index.js +2 -0
  118. package/src/actions/titles/resolve.js +12 -17
  119. package/src/composition.js +10 -0
  120. package/src/core/ChartProgram.js +200 -2
  121. package/src/core/action.js +10 -0
  122. package/src/core/validation.js +16 -0
  123. package/src/core/vocabulary.js +154 -4
  124. package/src/grammar/arcs.js +154 -0
  125. package/src/grammar/bars/aggregate.js +2 -2
  126. package/src/grammar/bars/geometry.js +3 -3
  127. package/src/grammar/bars/policy.js +7 -1
  128. package/src/grammar/coordinates.js +3 -7
  129. package/src/grammar/density.js +20 -1
  130. package/src/grammar/facets/dependencies.js +185 -0
  131. package/src/grammar/facets/guides.js +257 -0
  132. package/src/grammar/facets/scales.js +254 -0
  133. package/src/grammar/facets.js +122 -0
  134. package/src/grammar/lineSeries.js +111 -3
  135. package/src/grammar/polar.js +102 -0
  136. package/src/grammar/polarGuides.js +243 -0
  137. package/src/grammar/polarLineCommands.js +43 -0
  138. package/src/grammar/polarPaths.js +161 -0
  139. package/src/grammar/positionCompatibility.js +18 -2
  140. package/src/grammar/rects.js +26 -0
  141. package/src/grammar/scales/continuous.js +6 -1
  142. package/src/grammar/scales/fields.js +7 -0
  143. package/src/grammar/scales/ordinal.js +5 -2
  144. package/src/grammar/scales/policies.js +1 -1
  145. package/src/grammar/schemas/graphic.js +1 -1
  146. package/src/grammar/schemas/graphicBounds.js +64 -9
  147. package/src/grammar/schemas/graphicTree.js +44 -10
  148. package/src/grammar/schemas/semanticPath.js +31 -9
  149. package/src/grammar/seriesLayout.js +13 -2
  150. package/src/grammar/text.js +71 -0
  151. package/src/grammar/ticks.js +50 -13
  152. package/src/index.js +1 -0
  153. package/src/layout/composition.js +310 -0
  154. package/src/layout/facets.js +131 -0
  155. package/src/layout/title.js +6 -0
  156. package/src/materialization/bars/aggregate.js +5 -2
  157. package/src/materialization/bars/grouped.js +75 -37
  158. package/src/materialization/composition.js +152 -0
  159. package/src/materialization/compositionSnapshot.js +114 -0
  160. package/src/materialization/dependencies.js +71 -16
  161. package/src/materialization/encodings.js +14 -28
  162. package/src/materialization/facetGuides.js +426 -0
  163. package/src/materialization/facets.js +214 -0
  164. package/src/materialization/graphicHierarchy.js +15 -5
  165. package/src/materialization/guides/resources.js +46 -0
  166. package/src/materialization/marks.js +220 -14
  167. package/src/materialization/planner.js +105 -12
  168. package/src/materialization/rect.js +138 -0
  169. package/src/materialization/rectConfig.js +38 -0
  170. package/src/materialization/rowEncoding.js +33 -0
  171. package/src/materialization/scales/map.js +40 -0
  172. package/src/materialization/scales/policies/arc.js +37 -0
  173. package/src/materialization/scales/policies/bar.js +132 -0
  174. package/src/materialization/scales/policies/series.js +76 -0
  175. package/src/materialization/scales/resolve.js +326 -0
  176. package/src/materialization/selection/items/arc.js +29 -0
  177. package/src/materialization/selection/items/bar.js +255 -0
  178. package/src/materialization/selection/items/common.js +131 -0
  179. package/src/materialization/selection/items/index.js +6 -0
  180. package/src/materialization/selection/items/path.js +59 -0
  181. package/src/materialization/selection/items/point.js +55 -0
  182. package/src/materialization/selection/items/rect.js +23 -0
  183. package/src/materialization/selection/items/rule.js +25 -0
  184. package/src/materialization/selection/policies/arc.js +10 -0
  185. package/src/materialization/selection/policies/area.js +1 -1
  186. package/src/materialization/selection/policies/bar.js +1 -1
  187. package/src/materialization/selection/policies/index.js +5 -1
  188. package/src/materialization/selection/policies/line.js +1 -1
  189. package/src/materialization/selection/policies/point.js +1 -1
  190. package/src/materialization/selection/policies/rect.js +10 -0
  191. package/src/materialization/selection/policies/rule.js +1 -1
  192. package/src/materialization/selection/styles.js +24 -16
  193. package/src/materialization/text.js +156 -0
  194. package/src/renderers/canvas/index.js +68 -22
  195. package/types/extension.d.ts +1 -0
  196. package/types/index.d.ts +56 -1
  197. package/types/program.d.ts +664 -27
  198. package/src/actions/marks/line.js +0 -248
  199. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,255 @@
1
+ import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
+ import {
3
+ BAR_GRAINS,
4
+ resolveBarChannels,
5
+ resolveBarColorLayout,
6
+ resolveBarOffsetChannel,
7
+ resolveBarGrain
8
+ } from "../../../grammar/bars/policy.js";
9
+ import { layoutSeriesPartition } from "../../../grammar/seriesLayout.js";
10
+ import {
11
+ readNominalField,
12
+ readTemporalField
13
+ } from "../../../grammar/scales.js";
14
+ import { findSemanticScale } from "../../../selectors/scales.js";
15
+ import { deriveHistogramSegments } from "../../bars/histogram.js";
16
+ import {
17
+ channelMapFromRow,
18
+ finalizeItems,
19
+ itemKey,
20
+ ownFields,
21
+ uniqueFields
22
+ } from "./common.js";
23
+
24
+ function categoryValues(rows, encoding) {
25
+ return encoding.fieldType === "temporal"
26
+ ? readTemporalField(rows, encoding.field)
27
+ : readNominalField(rows, encoding.field);
28
+ }
29
+
30
+ function aggregateMembers(rows, layer, cell, channels) {
31
+ const category = layer.encoding[channels.category];
32
+ const categories = categoryValues(rows, category);
33
+ const categoryValue = cell[channels.category];
34
+ const color = layer.encoding?.color;
35
+ const colors = !["nominal", "ordinal"].includes(color?.fieldType)
36
+ ? undefined
37
+ : readNominalField(rows, color.field);
38
+ return rows.filter((_, index) =>
39
+ categories[index] === categoryValue &&
40
+ (colors === undefined || colors[index] === cell.color)
41
+ );
42
+ }
43
+
44
+ function aggregateCellDefinitions(program, layer, dataset) {
45
+ const channels = resolveBarChannels(layer);
46
+ const derived = deriveBarAggregates(dataset.values, layer).values;
47
+ const categoryEncoding = layer.encoding[channels.category];
48
+ const categoryScale = program.resolvedScales[categoryEncoding.scale];
49
+ const measureScale = program.resolvedScales[
50
+ layer.encoding[channels.measure].scale
51
+ ];
52
+ const colorEncoding = layer.encoding?.color;
53
+ const colorScale = program.resolvedScales[colorEncoding?.scale];
54
+ const offsetChannel = resolveBarOffsetChannel(layer);
55
+ const offsetScale = program.resolvedScales[layer.encoding?.[offsetChannel]?.scale];
56
+ const layout = resolveBarColorLayout(layer);
57
+
58
+ function definition(cell, start, end) {
59
+ const members = aggregateMembers(dataset.values, layer, cell, channels);
60
+ return {
61
+ fields: uniqueFields(members),
62
+ channels: {
63
+ [channels.category]: cell[channels.category],
64
+ [channels.measure]: start,
65
+ [`${channels.measure}2`]: end,
66
+ ...(cell.color === undefined ? {} : { color: cell.color }),
67
+ ...(layer.encoding?.[offsetChannel] === undefined
68
+ ? {}
69
+ : { [offsetChannel]: cell.color })
70
+ },
71
+ members
72
+ };
73
+ }
74
+
75
+ if (colorEncoding?.fieldType === "quantitative") {
76
+ const cells = new Map(derived.map(cell => [
77
+ cell[channels.category],
78
+ cell
79
+ ]));
80
+ const categories = ["ordinal", "band", "point"].includes(categoryScale.type)
81
+ ? categoryScale.domain
82
+ : [...new Set(derived.map(cell => cell[channels.category]))]
83
+ .sort((left, right) => left - right);
84
+ return categories.flatMap(category => {
85
+ const cell = cells.get(category);
86
+ return cell === undefined
87
+ ? []
88
+ : [definition(cell, measureScale.domain[0], cell[channels.measure])];
89
+ });
90
+ }
91
+
92
+ if (layout === "group" && offsetScale !== undefined) {
93
+ const categoryIndex = new Map(
94
+ categoryScale.domain.map((value, index) => [value, index])
95
+ );
96
+ const offsetIndex = new Map(
97
+ offsetScale.domain.map((value, index) => [value, index])
98
+ );
99
+ const cells = [...derived].sort((left, right) =>
100
+ categoryIndex.get(left[channels.category]) -
101
+ categoryIndex.get(right[channels.category]) ||
102
+ offsetIndex.get(left.color) - offsetIndex.get(right.color)
103
+ );
104
+ return cells.map(cell => definition(
105
+ cell,
106
+ measureScale.domain[0],
107
+ cell[channels.measure]
108
+ ));
109
+ }
110
+
111
+ const categories = ["ordinal", "band", "point"].includes(categoryScale.type)
112
+ ? categoryScale.domain
113
+ : [...new Set(derived.map(cell => cell[channels.category]))]
114
+ .sort((left, right) => left - right);
115
+ const series = colorScale?.domain ?? [undefined];
116
+ const lookup = new Map(derived.map(cell => [
117
+ JSON.stringify([cell[channels.category], cell.color]),
118
+ cell
119
+ ]));
120
+ const baseline = layout === "overlay" ? measureScale.domain[0] : 0;
121
+ return categories.flatMap(category => {
122
+ const cells = series.map(color =>
123
+ lookup.get(JSON.stringify([category, color]))
124
+ );
125
+ const values = cells.map(cell => cell?.[channels.measure] ?? 0);
126
+ return layoutSeriesPartition(values, layout, { baseline }).flatMap(segment => {
127
+ const cell = cells[segment.index];
128
+ return cell === undefined
129
+ ? []
130
+ : [definition(cell, segment.start, segment.end)];
131
+ });
132
+ });
133
+ }
134
+
135
+ function histogramDefinitions(program, layer, dataset) {
136
+ const xEncoding = layer.encoding.x;
137
+ const colorEncoding = layer.encoding?.color;
138
+ const segments = deriveHistogramSegments({
139
+ dataset,
140
+ layer,
141
+ xEncoding,
142
+ xScale: findSemanticScale(program, xEncoding.scale),
143
+ resolvedScales: program.resolvedScales
144
+ });
145
+ const colorScale = program.resolvedScales[colorEncoding?.scale];
146
+ return segments.map(segment => {
147
+ const colorValue = colorScale?.domain[segment.category];
148
+ return {
149
+ fields: uniqueFields(segment.members),
150
+ channels: {
151
+ x: segment.start,
152
+ x2: segment.end,
153
+ y: segment.stackStart,
154
+ y2: segment.stackEnd,
155
+ ...(colorValue === undefined ? {} : { color: colorValue })
156
+ },
157
+ members: segment.members
158
+ };
159
+ });
160
+ }
161
+
162
+ function rangedDefinitions(layer, dataset) {
163
+ return dataset.values.map(row => ({
164
+ fields: ownFields(row),
165
+ channels: channelMapFromRow(row, layer),
166
+ members: [row]
167
+ }));
168
+ }
169
+
170
+ function sharedChannels(definitions) {
171
+ if (definitions.length === 0) return {};
172
+ const shared = { ...definitions[0].channels };
173
+ for (const channel of Object.keys(shared)) {
174
+ if (!definitions.every(definition =>
175
+ definition.channels[channel] === shared[channel]
176
+ )) delete shared[channel];
177
+ }
178
+ return shared;
179
+ }
180
+
181
+ function barStackDefinitions(layer, barGrain, definitions) {
182
+ const layout = resolveBarColorLayout(layer);
183
+ if (!["stack", "fill", "diverging"].includes(layout)) {
184
+ throw new Error(
185
+ `Bar mark "${layer.id}" does not define stacked items at its current "${layout}" layout.`
186
+ );
187
+ }
188
+ if (barGrain === BAR_GRAINS.ranged) {
189
+ throw new Error(`Ranged bar mark "${layer.id}" has no stack grain.`);
190
+ }
191
+ const channels = resolveBarChannels(layer);
192
+ const categoryChannels = barGrain === BAR_GRAINS.histogram
193
+ ? ["x", "x2"]
194
+ : [channels.category];
195
+ const measure = channels.measure;
196
+ const secondary = `${measure}2`;
197
+ const groups = new Map();
198
+ definitions.forEach((definition, index) => {
199
+ const key = JSON.stringify(categoryChannels.map(channel =>
200
+ definition.channels[channel]
201
+ ));
202
+ const group = groups.get(key) ?? [];
203
+ group.push({ definition, index });
204
+ groups.set(key, group);
205
+ });
206
+
207
+ return [...groups.values()].map((entries, index) => {
208
+ const grouped = entries.map(entry => entry.definition);
209
+ const endpoints = grouped.flatMap(definition => [
210
+ definition.channels[measure],
211
+ definition.channels[secondary]
212
+ ]).filter(Number.isFinite);
213
+ if (endpoints.length === 0) {
214
+ throw new Error(
215
+ `Bar mark "${layer.id}" stack ${index} has no resolved semantic endpoints.`
216
+ );
217
+ }
218
+ const members = grouped.flatMap(definition => definition.members);
219
+ return {
220
+ key: itemKey(layer, "stack", index),
221
+ fields: uniqueFields(members),
222
+ channels: {
223
+ ...sharedChannels(grouped),
224
+ [measure]: Math.min(...endpoints),
225
+ [secondary]: Math.max(...endpoints)
226
+ },
227
+ members,
228
+ graphicIndices: entries.map(entry => entry.index)
229
+ };
230
+ });
231
+ }
232
+
233
+ export function resolveBarItems(program, layer, dataset, selectionGrain) {
234
+ const grain = resolveBarGrain(layer);
235
+ const definitions = grain === BAR_GRAINS.histogram
236
+ ? histogramDefinitions(program, layer, dataset)
237
+ : grain === BAR_GRAINS.aggregate
238
+ ? aggregateCellDefinitions(program, layer, dataset)
239
+ : grain === BAR_GRAINS.ranged
240
+ ? rangedDefinitions(layer, dataset)
241
+ : undefined;
242
+ if (definitions === undefined) {
243
+ throw new Error(`Bar mark "${layer.id}" is incomplete for selection.`);
244
+ }
245
+ if (selectionGrain === "stack") {
246
+ return finalizeItems(
247
+ program,
248
+ layer,
249
+ "stack",
250
+ barStackDefinitions(layer, grain, definitions),
251
+ "rect"
252
+ );
253
+ }
254
+ return finalizeItems(program, layer, grain, definitions, "rect");
255
+ }
@@ -0,0 +1,131 @@
1
+ import { cloneAndFreeze, isPlainObject } from "../../../core/immutable.js";
2
+ import { MARK_GRAPHIC_PROPERTIES } from "../../../grammar/markSelection.js";
3
+ import { unionConcreteGraphicBounds } from
4
+ "../../../grammar/schemas/graphicBounds.js";
5
+
6
+ export function itemKey(layer, grain, index) {
7
+ return `${layer.id}/${grain}/${index}`;
8
+ }
9
+
10
+ function graphicId(layer, index) {
11
+ return `${layer.id}:${index}`;
12
+ }
13
+
14
+ function encodingValue(row, encoding) {
15
+ if (encoding === undefined) return undefined;
16
+ if (Object.hasOwn(encoding, "field")) return row?.[encoding.field];
17
+ return encoding.datum;
18
+ }
19
+
20
+ export function channelMapFromRow(row, layer) {
21
+ return Object.fromEntries(
22
+ Object.entries(layer.encoding ?? {})
23
+ .map(([channel, encoding]) => [channel, encodingValue(row, encoding)])
24
+ .filter(([, value]) => value !== undefined)
25
+ );
26
+ }
27
+
28
+ export function ownFields(row) {
29
+ return isPlainObject(row) ? { ...row } : {};
30
+ }
31
+
32
+ export function uniqueFields(rows) {
33
+ if (rows.length === 0) return {};
34
+ const fields = new Set(rows.flatMap(row =>
35
+ isPlainObject(row) ? Object.keys(row) : []
36
+ ));
37
+ return Object.fromEntries([...fields].flatMap(field => {
38
+ if (!rows.every(row => isPlainObject(row) && Object.hasOwn(row, field))) {
39
+ return [];
40
+ }
41
+ const value = rows[0][field];
42
+ return rows.every(row => row[field] === value) ? [[field, value]] : [];
43
+ }));
44
+ }
45
+
46
+ function requireResolvedGraphic(program, layer, type) {
47
+ const graphic = program.graphicSpec.objects[layer.id];
48
+ const compatibleCollection = graphic?.type === "collection" &&
49
+ graphic.items?.every(child => child.type === type);
50
+ if (
51
+ (graphic?.type !== type && !compatibleCollection) ||
52
+ !Array.isArray(graphic.items)
53
+ ) {
54
+ throw new Error(
55
+ `Mark "${layer.id}" requires a materialized ${type} collection for selection.`
56
+ );
57
+ }
58
+ return graphic;
59
+ }
60
+
61
+ const SELECTABLE_GRAPHIC_PROPERTIES = new Set(MARK_GRAPHIC_PROPERTIES);
62
+
63
+ export function concreteProperties(properties) {
64
+ return Object.fromEntries(
65
+ Object.entries(properties ?? {}).filter(([key, value]) =>
66
+ SELECTABLE_GRAPHIC_PROPERTIES.has(key) &&
67
+ (Number.isFinite(value) || typeof value === "string")
68
+ )
69
+ );
70
+ }
71
+
72
+ function sharedConcreteProperties(items) {
73
+ if (items.length === 0) return {};
74
+ const shared = concreteProperties(items[0].properties);
75
+ for (const key of Object.keys(shared)) {
76
+ if (!items.every(child => child.properties?.[key] === shared[key])) {
77
+ delete shared[key];
78
+ }
79
+ }
80
+ return shared;
81
+ }
82
+
83
+ function collectionBounds(program, items) {
84
+ const bounds = unionConcreteGraphicBounds(
85
+ program.graphicSpec,
86
+ items.map(item => item.id)
87
+ );
88
+ if (bounds === undefined) return {};
89
+ const { left, top, right, bottom } = bounds;
90
+ return { x: left, y: top, width: right - left, height: bottom - top };
91
+ }
92
+
93
+ export function finalizeItems(program, layer, grain, definitions, graphicType) {
94
+ const graphic = requireResolvedGraphic(program, layer, graphicType);
95
+ const referenced = definitions.flatMap((definition, index) =>
96
+ definition.graphicIndices ?? [index]
97
+ );
98
+ if (
99
+ referenced.some(index => graphic.items[index] === undefined) ||
100
+ (definitions.every(definition => definition.graphicIndices === undefined) &&
101
+ graphic.items.length !== definitions.length)
102
+ ) {
103
+ throw new Error(
104
+ `Mark "${layer.id}" item count does not match its materialized graphics.`
105
+ );
106
+ }
107
+ return Object.freeze(definitions.map((definition, index) => {
108
+ const indices = definition.graphicIndices ?? [index];
109
+ const items = indices.map(childIndex => graphic.items[childIndex]);
110
+ const properties = definition.properties ?? (
111
+ items.length === 1
112
+ ? concreteProperties(items[0].properties)
113
+ : {
114
+ ...sharedConcreteProperties(items),
115
+ ...collectionBounds(program, items)
116
+ }
117
+ );
118
+ return Object.freeze({
119
+ key: definition.key ?? itemKey(layer, grain, index),
120
+ layer: layer.id,
121
+ markType: layer.mark.type,
122
+ fields: cloneAndFreeze(definition.fields),
123
+ channels: cloneAndFreeze(definition.channels),
124
+ properties: cloneAndFreeze(properties),
125
+ members: Object.freeze([...definition.members]),
126
+ graphicIds: Object.freeze(items.map((child, childOffset) =>
127
+ child.id ?? graphicId(layer, indices[childOffset])
128
+ ))
129
+ });
130
+ }));
131
+ }
@@ -0,0 +1,6 @@
1
+ export { resolveAreaItems, resolveLineItems } from "./path.js";
2
+ export { resolveArcItems } from "./arc.js";
3
+ export { resolveBarItems } from "./bar.js";
4
+ export { resolvePointItems } from "./point.js";
5
+ export { resolveRuleItems } from "./rule.js";
6
+ export { resolveRectItems } from "./rect.js";
@@ -0,0 +1,59 @@
1
+ import {
2
+ deriveAreaSeries,
3
+ deriveDensityAreaSeries
4
+ } from "../../../grammar/areaSeries.js";
5
+ import { deriveLineSeries } from "../../../grammar/lineSeries.js";
6
+ import { findUpstreamTransform } from "../../dataProvenance.js";
7
+ import { finalizeItems, uniqueFields } from "./common.js";
8
+
9
+ function rowsForSeries(rows, key) {
10
+ const entries = Object.entries(key);
11
+ return entries.length === 0
12
+ ? rows
13
+ : rows.filter(row => entries.every(([field, value]) => row[field] === value));
14
+ }
15
+
16
+ function seriesDefinitions(layer, rows, series) {
17
+ return series.map(item => {
18
+ const members = rowsForSeries(rows, item.key);
19
+ const fields = { ...uniqueFields(members), ...item.key };
20
+ const channels = Object.fromEntries(
21
+ Object.entries(layer.encoding ?? {}).flatMap(([channel, encoding]) => {
22
+ if (encoding.field === undefined) {
23
+ return Object.hasOwn(encoding, "datum")
24
+ ? [[channel, encoding.datum]]
25
+ : [];
26
+ }
27
+ return Object.hasOwn(fields, encoding.field)
28
+ ? [[channel, fields[encoding.field]]]
29
+ : [];
30
+ })
31
+ );
32
+ return { fields, channels, members };
33
+ });
34
+ }
35
+
36
+ export function resolveLineItems(program, layer, dataset) {
37
+ const derived = deriveLineSeries(dataset.values, layer);
38
+ return finalizeItems(
39
+ program,
40
+ layer,
41
+ "series",
42
+ seriesDefinitions(layer, dataset.values, derived.series),
43
+ "path"
44
+ );
45
+ }
46
+
47
+ export function resolveAreaItems(program, layer, dataset) {
48
+ const transform = findUpstreamTransform(program, dataset, "density");
49
+ const derived = transform === undefined
50
+ ? deriveAreaSeries(dataset.values, layer)
51
+ : deriveDensityAreaSeries(dataset.values, layer, transform);
52
+ return finalizeItems(
53
+ program,
54
+ layer,
55
+ "series",
56
+ seriesDefinitions(layer, dataset.values, derived.series),
57
+ "path"
58
+ );
59
+ }
@@ -0,0 +1,55 @@
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 resolvePointItems(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
+ ) || (
16
+ layer.encoding?.theta?.scale !== undefined &&
17
+ layer.encoding?.radius?.scale !== undefined
18
+ );
19
+ if (
20
+ !Array.isArray(graphic?.items) ||
21
+ !completePosition ||
22
+ (
23
+ layer.encoding?.size?.scale === undefined &&
24
+ !Number.isFinite(program.markConfigs[layer.id]?.radius)
25
+ )
26
+ ) {
27
+ throw new Error(`Point mark "${layer.id}" is incomplete for selection.`);
28
+ }
29
+ let definitions = dataset.values.map((row, index) => ({
30
+ key: itemKey(layer, "point", index),
31
+ fields: ownFields(row),
32
+ channels: channelMapFromRow(row, layer),
33
+ properties: concreteProperties(graphic.items[index]?.properties),
34
+ members: [row]
35
+ }));
36
+ for (const config of Object.values(
37
+ program.materializationConfigs.highlights ?? {}
38
+ )) {
39
+ if (config.target !== layer.id || config.bringToFront !== true) continue;
40
+ const selection = program.materializationConfigs.selections?.[config.selection];
41
+ if (selection?.target !== layer.id) continue;
42
+ const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
43
+ definitions = [
44
+ ...definitions.filter(definition => !selected.has(definition.key)),
45
+ ...definitions.filter(definition => selected.has(definition.key))
46
+ ];
47
+ }
48
+ return finalizeItems(
49
+ program,
50
+ layer,
51
+ "point",
52
+ definitions,
53
+ program.graphicSpec.objects[layer.id]?.type
54
+ );
55
+ }
@@ -0,0 +1,23 @@
1
+ import { resolveRectRows } from "../../rect.js";
2
+ import {
3
+ concreteProperties,
4
+ finalizeItems,
5
+ itemKey,
6
+ ownFields
7
+ } from "./common.js";
8
+
9
+ export function resolveRectItems(program, layer, dataset) {
10
+ const graphic = program.graphicSpec.objects[layer.id];
11
+ if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
12
+ throw new Error(`Rect mark "${layer.id}" is incomplete for selection.`);
13
+ }
14
+ const rows = resolveRectRows(program, layer, dataset);
15
+ const definitions = rows.map((item, graphicIndex) => ({
16
+ key: itemKey(layer, "rect", item.sourceIndex),
17
+ fields: ownFields(item.row),
18
+ channels: item.channels,
19
+ properties: concreteProperties(graphic.items[graphicIndex]?.properties),
20
+ members: [item.row]
21
+ }));
22
+ return finalizeItems(program, layer, "rect", definitions, "rect");
23
+ }
@@ -0,0 +1,25 @@
1
+ import { deriveRuleValues } from "../../../grammar/rules.js";
2
+ import { finalizeItems, ownFields, uniqueFields } from "./common.js";
3
+
4
+ export function resolveRuleItems(program, layer, dataset) {
5
+ const derived = deriveRuleValues(dataset.values, layer);
6
+ const hasField = Object.values(layer.encoding ?? {}).some(encoding =>
7
+ Object.hasOwn(encoding, "field")
8
+ );
9
+ const definitions = Array.from({ length: derived.length }, (_, index) => {
10
+ const members = hasField ? [dataset.values[index]] : dataset.values;
11
+ return {
12
+ fields: hasField
13
+ ? ownFields(dataset.values[index])
14
+ : uniqueFields(dataset.values),
15
+ channels: Object.fromEntries(
16
+ Object.entries(derived.values).map(([channel, values]) => [
17
+ channel,
18
+ values[index]
19
+ ])
20
+ ),
21
+ members
22
+ };
23
+ });
24
+ return finalizeItems(program, layer, "rule", definitions, "line");
25
+ }
@@ -0,0 +1,10 @@
1
+ import { resolveArcItems } from "../items/index.js";
2
+ import { normalizeAreaHighlightStyle } from "../styles.js";
3
+
4
+ export const arcSelectionPolicy = Object.freeze({
5
+ supportedGrains: Object.freeze(["item"]),
6
+ resolveItems: resolveArcItems,
7
+ normalizeHighlightStyle: args => normalizeAreaHighlightStyle(args, "Arc"),
8
+ applyHighlightOp: "applyPathHighlight",
9
+ rematerializeOp: "rematerializeArcMark"
10
+ });
@@ -1,4 +1,4 @@
1
- import { resolveAreaItems } from "../items.js";
1
+ import { resolveAreaItems } from "../items/index.js";
2
2
  import { normalizeAreaHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const areaSelectionPolicy = Object.freeze({
@@ -1,4 +1,4 @@
1
- import { resolveBarItems } from "../items.js";
1
+ import { resolveBarItems } from "../items/index.js";
2
2
  import { normalizeBarHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const barSelectionPolicy = Object.freeze({
@@ -1,17 +1,21 @@
1
1
  import { findDataset } from "../../../selectors/datasets.js";
2
2
  import { requireLayer } from "../../../selectors/layers.js";
3
3
  import { areaSelectionPolicy } from "./area.js";
4
+ import { arcSelectionPolicy } from "./arc.js";
4
5
  import { barSelectionPolicy } from "./bar.js";
5
6
  import { lineSelectionPolicy } from "./line.js";
6
7
  import { pointSelectionPolicy } from "./point.js";
7
8
  import { ruleSelectionPolicy } from "./rule.js";
9
+ import { rectSelectionPolicy } from "./rect.js";
8
10
 
9
11
  const POLICIES = Object.freeze({
12
+ arc: arcSelectionPolicy,
10
13
  area: areaSelectionPolicy,
11
14
  bar: barSelectionPolicy,
12
15
  line: lineSelectionPolicy,
13
16
  point: pointSelectionPolicy,
14
- rule: ruleSelectionPolicy
17
+ rule: ruleSelectionPolicy,
18
+ rect: rectSelectionPolicy
15
19
  });
16
20
 
17
21
  export function findSelectionPolicy(markType) {
@@ -1,4 +1,4 @@
1
- import { resolveLineItems } from "../items.js";
1
+ import { resolveLineItems } from "../items/index.js";
2
2
  import { normalizeStrokeHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const lineSelectionPolicy = Object.freeze({
@@ -1,4 +1,4 @@
1
- import { resolvePointItems } from "../items.js";
1
+ import { resolvePointItems } from "../items/index.js";
2
2
  import { normalizePointHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const pointSelectionPolicy = Object.freeze({
@@ -0,0 +1,10 @@
1
+ import { resolveRectItems } from "../items/index.js";
2
+ import { normalizeRectHighlightStyle } from "../styles.js";
3
+
4
+ export const rectSelectionPolicy = Object.freeze({
5
+ supportedGrains: Object.freeze(["item"]),
6
+ resolveItems: resolveRectItems,
7
+ normalizeHighlightStyle: normalizeRectHighlightStyle,
8
+ applyHighlightOp: "applyRectHighlight",
9
+ rematerializeOp: "rematerializeRectMark"
10
+ });
@@ -1,4 +1,4 @@
1
- import { resolveRuleItems } from "../items.js";
1
+ import { resolveRuleItems } from "../items/index.js";
2
2
  import { normalizeStrokeHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const ruleSelectionPolicy = Object.freeze({