ggaction 0.0.1

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 (222) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/package.json +84 -0
  5. package/src/AGENTS.md +119 -0
  6. package/src/ChartProgram.js +10 -0
  7. package/src/actions/boxPlots/components.js +88 -0
  8. package/src/actions/boxPlots/create.js +84 -0
  9. package/src/actions/boxPlots/index.js +10 -0
  10. package/src/actions/boxPlots/materialize.js +183 -0
  11. package/src/actions/boxPlots/options.js +147 -0
  12. package/src/actions/boxPlots/resolve.js +38 -0
  13. package/src/actions/canvas/actions.js +144 -0
  14. package/src/actions/canvas/index.js +6 -0
  15. package/src/actions/coordinates/actions.js +85 -0
  16. package/src/actions/coordinates/index.js +5 -0
  17. package/src/actions/data/box.js +40 -0
  18. package/src/actions/data/create.js +33 -0
  19. package/src/actions/data/density.js +62 -0
  20. package/src/actions/data/derived.js +52 -0
  21. package/src/actions/data/filter.js +168 -0
  22. package/src/actions/data/index.js +32 -0
  23. package/src/actions/data/interval.js +69 -0
  24. package/src/actions/data/regression.js +71 -0
  25. package/src/actions/data/shared.js +25 -0
  26. package/src/actions/encodings/appearance.js +243 -0
  27. package/src/actions/encodings/barWidth.js +60 -0
  28. package/src/actions/encodings/color.js +329 -0
  29. package/src/actions/encodings/density.js +225 -0
  30. package/src/actions/encodings/histogram.js +50 -0
  31. package/src/actions/encodings/index.js +23 -0
  32. package/src/actions/encodings/offset.js +104 -0
  33. package/src/actions/encodings/position/policies/area.js +24 -0
  34. package/src/actions/encodings/position/policies/bar.js +110 -0
  35. package/src/actions/encodings/position/policies/common.js +17 -0
  36. package/src/actions/encodings/position/policies/index.js +25 -0
  37. package/src/actions/encodings/position/policies/line.js +74 -0
  38. package/src/actions/encodings/position/policies/point.js +19 -0
  39. package/src/actions/encodings/position/policies/rule.js +17 -0
  40. package/src/actions/encodings/position/resolve.js +170 -0
  41. package/src/actions/encodings/position.js +176 -0
  42. package/src/actions/encodings/ranged.js +276 -0
  43. package/src/actions/encodings/ruleAppearance.js +47 -0
  44. package/src/actions/encodings/shared.js +164 -0
  45. package/src/actions/encodings/strokeDash.js +145 -0
  46. package/src/actions/errorBands/components.js +81 -0
  47. package/src/actions/errorBands/create.js +159 -0
  48. package/src/actions/errorBands/index.js +7 -0
  49. package/src/actions/errorBands/resolve.js +28 -0
  50. package/src/actions/errorBars/components.js +62 -0
  51. package/src/actions/errorBars/create.js +144 -0
  52. package/src/actions/errorBars/index.js +7 -0
  53. package/src/actions/errorBars/resolve.js +25 -0
  54. package/src/actions/guides/axes/axes.js +204 -0
  55. package/src/actions/guides/axes/axis.js +131 -0
  56. package/src/actions/guides/axes/index.js +17 -0
  57. package/src/actions/guides/axes/labels.js +231 -0
  58. package/src/actions/guides/axes/lines.js +149 -0
  59. package/src/actions/guides/axes/policy.js +155 -0
  60. package/src/actions/guides/axes/tickGroups.js +149 -0
  61. package/src/actions/guides/axes/ticks.js +179 -0
  62. package/src/actions/guides/axes/titles.js +264 -0
  63. package/src/actions/guides/grids/grid.js +228 -0
  64. package/src/actions/guides/grids/index.js +1 -0
  65. package/src/actions/guides/grids/resolve.js +258 -0
  66. package/src/actions/guides/guides.js +138 -0
  67. package/src/actions/guides/index.js +11 -0
  68. package/src/actions/guides/legends/categorical/actions.js +329 -0
  69. package/src/actions/guides/legends/categorical/components.js +140 -0
  70. package/src/actions/guides/legends/categorical/index.js +49 -0
  71. package/src/actions/guides/legends/categorical/layout.js +445 -0
  72. package/src/actions/guides/legends/categorical/options.js +188 -0
  73. package/src/actions/guides/legends/categorical/recipes.js +120 -0
  74. package/src/actions/guides/legends/categorical/resolve.js +131 -0
  75. package/src/actions/guides/legends/categorical/symbols.js +280 -0
  76. package/src/actions/guides/legends/categorical/validation.js +27 -0
  77. package/src/actions/guides/legends/continuous/common.js +273 -0
  78. package/src/actions/guides/legends/continuous/gradient.js +363 -0
  79. package/src/actions/guides/legends/continuous/index.js +25 -0
  80. package/src/actions/guides/legends/continuous/interval.js +234 -0
  81. package/src/actions/guides/legends/continuous/opacity.js +382 -0
  82. package/src/actions/guides/legends/edit.js +314 -0
  83. package/src/actions/guides/legends/index.js +9 -0
  84. package/src/actions/guides/legends/size.js +220 -0
  85. package/src/actions/guides/tickValues.js +84 -0
  86. package/src/actions/index.js +31 -0
  87. package/src/actions/intervals/resolve.js +352 -0
  88. package/src/actions/marks/area.js +369 -0
  89. package/src/actions/marks/bar/create.js +46 -0
  90. package/src/actions/marks/bar/edit.js +90 -0
  91. package/src/actions/marks/bar/index.js +9 -0
  92. package/src/actions/marks/bar/materialize.js +130 -0
  93. package/src/actions/marks/index.js +13 -0
  94. package/src/actions/marks/line.js +248 -0
  95. package/src/actions/marks/point.js +420 -0
  96. package/src/actions/marks/rule.js +287 -0
  97. package/src/actions/marks/shared.js +112 -0
  98. package/src/actions/primitives/createGraphics.js +163 -0
  99. package/src/actions/primitives/editGraphics.js +208 -0
  100. package/src/actions/primitives/index.js +9 -0
  101. package/src/actions/primitives/semantic.js +168 -0
  102. package/src/actions/primitives/semanticValue.js +182 -0
  103. package/src/actions/regression/components.js +216 -0
  104. package/src/actions/regression/create.js +102 -0
  105. package/src/actions/regression/index.js +23 -0
  106. package/src/actions/regression/resolve.js +57 -0
  107. package/src/actions/scales/consumers.js +283 -0
  108. package/src/actions/scales/create.js +148 -0
  109. package/src/actions/scales/definitions.js +319 -0
  110. package/src/actions/scales/edit.js +290 -0
  111. package/src/actions/scales/index.js +11 -0
  112. package/src/actions/scales/materialize.js +466 -0
  113. package/src/actions/scales/quantitativeColor.js +45 -0
  114. package/src/actions/selection/actions.js +384 -0
  115. package/src/actions/selection/index.js +23 -0
  116. package/src/actions/titles/actions.js +235 -0
  117. package/src/actions/titles/index.js +19 -0
  118. package/src/actions/titles/resolve.js +321 -0
  119. package/src/core/ChartProgram.js +316 -0
  120. package/src/core/action.js +120 -0
  121. package/src/core/identifiers.js +28 -0
  122. package/src/core/immutable.js +94 -0
  123. package/src/core/specs.js +29 -0
  124. package/src/core/textMetrics.js +23 -0
  125. package/src/core/validation.js +54 -0
  126. package/src/core/vocabulary.js +58 -0
  127. package/src/extension.js +2 -0
  128. package/src/grammar/aggregate.js +259 -0
  129. package/src/grammar/areaSeries.js +217 -0
  130. package/src/grammar/bars/aggregate.js +98 -0
  131. package/src/grammar/bars/geometry.js +54 -0
  132. package/src/grammar/bars/policy.js +90 -0
  133. package/src/grammar/boxPlot.js +140 -0
  134. package/src/grammar/coordinates.js +25 -0
  135. package/src/grammar/curveCommands.js +356 -0
  136. package/src/grammar/density.js +296 -0
  137. package/src/grammar/filter.js +169 -0
  138. package/src/grammar/histogram.js +332 -0
  139. package/src/grammar/interval.js +343 -0
  140. package/src/grammar/lineSeries.js +197 -0
  141. package/src/grammar/markFilter.js +46 -0
  142. package/src/grammar/markSelection.js +326 -0
  143. package/src/grammar/palettes.js +208 -0
  144. package/src/grammar/pathCommands.js +86 -0
  145. package/src/grammar/pointShapes.js +164 -0
  146. package/src/grammar/positionCompatibility.js +32 -0
  147. package/src/grammar/regression.js +578 -0
  148. package/src/grammar/ruleAppearance.js +12 -0
  149. package/src/grammar/rules.js +90 -0
  150. package/src/grammar/scales/appearance.js +185 -0
  151. package/src/grammar/scales/color.js +349 -0
  152. package/src/grammar/scales/continuous.js +111 -0
  153. package/src/grammar/scales/definition.js +153 -0
  154. package/src/grammar/scales/discretized.js +159 -0
  155. package/src/grammar/scales/fields.js +133 -0
  156. package/src/grammar/scales/index.js +12 -0
  157. package/src/grammar/scales/mapping.js +27 -0
  158. package/src/grammar/scales/ordinal.js +221 -0
  159. package/src/grammar/scales/policies.js +50 -0
  160. package/src/grammar/scales/temporal.js +53 -0
  161. package/src/grammar/scales/transformed.js +236 -0
  162. package/src/grammar/scales/types.js +102 -0
  163. package/src/grammar/scales/validation.js +66 -0
  164. package/src/grammar/scales.js +1 -0
  165. package/src/grammar/schemas/concreteGraphic.js +73 -0
  166. package/src/grammar/schemas/graphic.js +131 -0
  167. package/src/grammar/schemas/graphicBounds.js +146 -0
  168. package/src/grammar/schemas/graphicTree.js +133 -0
  169. package/src/grammar/schemas/mark.js +4 -0
  170. package/src/grammar/schemas/semanticPath.js +174 -0
  171. package/src/grammar/seriesLayout.js +76 -0
  172. package/src/grammar/ticks.js +149 -0
  173. package/src/grammar/transforms.js +37 -0
  174. package/src/index.js +2 -0
  175. package/src/layout/canvas.js +114 -0
  176. package/src/layout/grid.js +17 -0
  177. package/src/layout/legend.js +51 -0
  178. package/src/layout/text.js +62 -0
  179. package/src/layout/title.js +107 -0
  180. package/src/materialization/bars/aggregate.js +184 -0
  181. package/src/materialization/bars/grouped.js +117 -0
  182. package/src/materialization/bars/histogram.js +163 -0
  183. package/src/materialization/bars/ranged.js +53 -0
  184. package/src/materialization/bars/resolve.js +56 -0
  185. package/src/materialization/dataProvenance.js +15 -0
  186. package/src/materialization/density.js +29 -0
  187. package/src/materialization/dependencies.js +134 -0
  188. package/src/materialization/encodings.js +50 -0
  189. package/src/materialization/graphicHierarchy.js +104 -0
  190. package/src/materialization/legends.js +20 -0
  191. package/src/materialization/marks.js +110 -0
  192. package/src/materialization/planner.js +38 -0
  193. package/src/materialization/selection/filter.js +40 -0
  194. package/src/materialization/selection/items.js +503 -0
  195. package/src/materialization/selection/path.js +37 -0
  196. package/src/materialization/selection/point.js +118 -0
  197. package/src/materialization/selection/policies/area.js +10 -0
  198. package/src/materialization/selection/policies/bar.js +10 -0
  199. package/src/materialization/selection/policies/index.js +40 -0
  200. package/src/materialization/selection/policies/line.js +10 -0
  201. package/src/materialization/selection/policies/point.js +10 -0
  202. package/src/materialization/selection/policies/rule.js +10 -0
  203. package/src/materialization/selection/state.js +67 -0
  204. package/src/materialization/selection/styles.js +183 -0
  205. package/src/renderers/canvas/circle.js +62 -0
  206. package/src/renderers/canvas/index.js +132 -0
  207. package/src/renderers/canvas/line.js +54 -0
  208. package/src/renderers/canvas/path.js +98 -0
  209. package/src/renderers/canvas/rect.js +57 -0
  210. package/src/renderers/canvas/text.js +96 -0
  211. package/src/renderers/canvas/validation.js +23 -0
  212. package/src/renderers/png.js +29 -0
  213. package/src/selectors/coordinates.js +13 -0
  214. package/src/selectors/datasets.js +13 -0
  215. package/src/selectors/index.js +4 -0
  216. package/src/selectors/layers.js +32 -0
  217. package/src/selectors/scales.js +21 -0
  218. package/src/theme/defaults.js +13 -0
  219. package/types/extension.d.ts +14 -0
  220. package/types/index.d.ts +81 -0
  221. package/types/png.d.ts +14 -0
  222. package/types/program.d.ts +1096 -0
@@ -0,0 +1,503 @@
1
+ import { cloneAndFreeze, isPlainObject } from "../../core/immutable.js";
2
+ import { deriveAreaSeries, deriveDensityAreaSeries } from "../../grammar/areaSeries.js";
3
+ import { deriveBarAggregates } from "../../grammar/bars/aggregate.js";
4
+ import {
5
+ BAR_GRAINS,
6
+ resolveBarChannels,
7
+ resolveBarColorLayout,
8
+ resolveBarGrain
9
+ } from "../../grammar/bars/policy.js";
10
+ import { deriveLineSeries } from "../../grammar/lineSeries.js";
11
+ import { deriveRuleValues } from "../../grammar/rules.js";
12
+ import {
13
+ MARK_GRAPHIC_PROPERTIES,
14
+ selectMarkItemKeys
15
+ } from "../../grammar/markSelection.js";
16
+ import { layoutSeriesPartition } from "../../grammar/seriesLayout.js";
17
+ import {
18
+ readNominalField,
19
+ readTemporalField
20
+ } from "../../grammar/scales.js";
21
+ import { deriveHistogramSegments } from "../bars/histogram.js";
22
+ import { findUpstreamTransform } from "../dataProvenance.js";
23
+ import { unionConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
24
+ import { findSemanticScale } from "../../selectors/scales.js";
25
+
26
+ function itemKey(layer, grain, index) {
27
+ return `${layer.id}/${grain}/${index}`;
28
+ }
29
+
30
+ function graphicId(layer, index) {
31
+ return `${layer.id}:${index}`;
32
+ }
33
+
34
+ function encodingValue(row, encoding) {
35
+ if (encoding === undefined) return undefined;
36
+ if (Object.hasOwn(encoding, "field")) return row?.[encoding.field];
37
+ return encoding.datum;
38
+ }
39
+
40
+ function channelMapFromRow(row, layer) {
41
+ return Object.fromEntries(
42
+ Object.entries(layer.encoding ?? {})
43
+ .map(([channel, encoding]) => [channel, encodingValue(row, encoding)])
44
+ .filter(([, value]) => value !== undefined)
45
+ );
46
+ }
47
+
48
+ function ownFields(row) {
49
+ return isPlainObject(row) ? { ...row } : {};
50
+ }
51
+
52
+ function uniqueFields(rows) {
53
+ if (rows.length === 0) return {};
54
+ const fields = new Set(rows.flatMap(row =>
55
+ isPlainObject(row) ? Object.keys(row) : []
56
+ ));
57
+ return Object.fromEntries([...fields].flatMap(field => {
58
+ if (!rows.every(row => isPlainObject(row) && Object.hasOwn(row, field))) {
59
+ return [];
60
+ }
61
+ const value = rows[0][field];
62
+ return rows.every(row => row[field] === value) ? [[field, value]] : [];
63
+ }));
64
+ }
65
+
66
+ function requireResolvedGraphic(program, layer, type) {
67
+ const graphic = program.graphicSpec.objects[layer.id];
68
+ const compatibleCollection = graphic?.type === "collection" &&
69
+ graphic.items?.every(child => child.type === type);
70
+ if (
71
+ (graphic?.type !== type && !compatibleCollection) ||
72
+ !Array.isArray(graphic.items)
73
+ ) {
74
+ throw new Error(
75
+ `Mark "${layer.id}" requires a materialized ${type} collection for selection.`
76
+ );
77
+ }
78
+ return graphic;
79
+ }
80
+
81
+ const SELECTABLE_GRAPHIC_PROPERTIES = new Set(MARK_GRAPHIC_PROPERTIES);
82
+
83
+ function concreteProperties(properties) {
84
+ return Object.fromEntries(
85
+ Object.entries(properties ?? {}).filter(([key, value]) =>
86
+ SELECTABLE_GRAPHIC_PROPERTIES.has(key) &&
87
+ (Number.isFinite(value) || typeof value === "string")
88
+ )
89
+ );
90
+ }
91
+
92
+ function sharedConcreteProperties(items) {
93
+ if (items.length === 0) return {};
94
+ const shared = concreteProperties(items[0].properties);
95
+ for (const key of Object.keys(shared)) {
96
+ if (!items.every(child => child.properties?.[key] === shared[key])) {
97
+ delete shared[key];
98
+ }
99
+ }
100
+ return shared;
101
+ }
102
+
103
+ function collectionBounds(program, items) {
104
+ const bounds = unionConcreteGraphicBounds(
105
+ program.graphicSpec,
106
+ items.map(item => item.id)
107
+ );
108
+ if (bounds === undefined) return {};
109
+ const { left, top, right, bottom } = bounds;
110
+ return { x: left, y: top, width: right - left, height: bottom - top };
111
+ }
112
+
113
+ function finalizeItems(program, layer, grain, definitions, graphicType) {
114
+ const graphic = requireResolvedGraphic(program, layer, graphicType);
115
+ const referenced = definitions.flatMap((definition, index) =>
116
+ definition.graphicIndices ?? [index]
117
+ );
118
+ if (
119
+ referenced.some(index => graphic.items[index] === undefined) ||
120
+ (definitions.every(definition => definition.graphicIndices === undefined) &&
121
+ graphic.items.length !== definitions.length)
122
+ ) {
123
+ throw new Error(
124
+ `Mark "${layer.id}" item count does not match its materialized graphics.`
125
+ );
126
+ }
127
+ return Object.freeze(definitions.map((definition, index) => {
128
+ const indices = definition.graphicIndices ?? [index];
129
+ const items = indices.map(childIndex => graphic.items[childIndex]);
130
+ const properties = definition.properties ?? (
131
+ items.length === 1
132
+ ? concreteProperties(items[0].properties)
133
+ : {
134
+ ...sharedConcreteProperties(items),
135
+ ...collectionBounds(program, items)
136
+ }
137
+ );
138
+ return Object.freeze({
139
+ key: definition.key ?? itemKey(layer, grain, index),
140
+ layer: layer.id,
141
+ markType: layer.mark.type,
142
+ fields: cloneAndFreeze(definition.fields),
143
+ channels: cloneAndFreeze(definition.channels),
144
+ properties: cloneAndFreeze(properties),
145
+ members: Object.freeze([...definition.members]),
146
+ graphicIds: Object.freeze(items.map((child, childOffset) =>
147
+ child.id ?? graphicId(layer, indices[childOffset])
148
+ ))
149
+ });
150
+ }));
151
+ }
152
+
153
+ export function resolvePointItems(program, layer, dataset) {
154
+ const graphic = program.graphicSpec.objects[layer.id];
155
+ if (
156
+ !Array.isArray(graphic?.items) ||
157
+ layer.encoding?.x?.scale === undefined ||
158
+ layer.encoding?.y?.scale === undefined ||
159
+ (
160
+ layer.encoding?.size?.scale === undefined &&
161
+ !Number.isFinite(program.markConfigs[layer.id]?.radius)
162
+ )
163
+ ) {
164
+ throw new Error(`Point mark "${layer.id}" is incomplete for selection.`);
165
+ }
166
+ let definitions = dataset.values.map((row, index) => ({
167
+ key: itemKey(layer, "point", index),
168
+ fields: ownFields(row),
169
+ channels: channelMapFromRow(row, layer),
170
+ properties: concreteProperties(graphic.items[index]?.properties),
171
+ members: [row]
172
+ }));
173
+ for (const config of Object.values(
174
+ program.materializationConfigs.highlights ?? {}
175
+ )) {
176
+ if (config.target !== layer.id || config.bringToFront !== true) continue;
177
+ const selection = program.materializationConfigs.selections?.[config.selection];
178
+ if (selection?.target !== layer.id) continue;
179
+ const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
180
+ definitions = [
181
+ ...definitions.filter(definition => !selected.has(definition.key)),
182
+ ...definitions.filter(definition => selected.has(definition.key))
183
+ ];
184
+ }
185
+ return finalizeItems(
186
+ program,
187
+ layer,
188
+ "point",
189
+ definitions,
190
+ program.graphicSpec.objects[layer.id]?.type
191
+ );
192
+ }
193
+
194
+ function categoryValues(rows, encoding) {
195
+ return encoding.fieldType === "temporal"
196
+ ? readTemporalField(rows, encoding.field)
197
+ : readNominalField(rows, encoding.field);
198
+ }
199
+
200
+ function aggregateMembers(rows, layer, cell, channels) {
201
+ const category = layer.encoding[channels.category];
202
+ const categories = categoryValues(rows, category);
203
+ const categoryValue = cell[channels.category];
204
+ const color = layer.encoding?.color;
205
+ const colors = color?.fieldType !== "nominal"
206
+ ? undefined
207
+ : readNominalField(rows, color.field);
208
+ return rows.filter((_, index) =>
209
+ categories[index] === categoryValue &&
210
+ (colors === undefined || colors[index] === cell.color)
211
+ );
212
+ }
213
+
214
+ function aggregateCellDefinitions(program, layer, dataset) {
215
+ const channels = resolveBarChannels(layer);
216
+ const derived = deriveBarAggregates(dataset.values, layer).values;
217
+ const categoryEncoding = layer.encoding[channels.category];
218
+ const categoryScale = program.resolvedScales[categoryEncoding.scale];
219
+ const measureScale = program.resolvedScales[
220
+ layer.encoding[channels.measure].scale
221
+ ];
222
+ const colorEncoding = layer.encoding?.color;
223
+ const colorScale = program.resolvedScales[colorEncoding?.scale];
224
+ const offsetScale = program.resolvedScales[layer.encoding?.xOffset?.scale];
225
+ const layout = resolveBarColorLayout(layer);
226
+
227
+ function definition(cell, start, end) {
228
+ const members = aggregateMembers(dataset.values, layer, cell, channels);
229
+ return {
230
+ fields: uniqueFields(members),
231
+ channels: {
232
+ [channels.category]: cell[channels.category],
233
+ [channels.measure]: start,
234
+ [`${channels.measure}2`]: end,
235
+ ...(cell.color === undefined ? {} : { color: cell.color }),
236
+ ...(layer.encoding?.xOffset === undefined
237
+ ? {}
238
+ : { xOffset: cell.color })
239
+ },
240
+ members
241
+ };
242
+ }
243
+
244
+ if (colorEncoding?.fieldType === "quantitative") {
245
+ const cells = new Map(derived.map(cell => [
246
+ cell[channels.category],
247
+ cell
248
+ ]));
249
+ const categories = ["ordinal", "band", "point"].includes(categoryScale.type)
250
+ ? categoryScale.domain
251
+ : [...new Set(derived.map(cell => cell[channels.category]))]
252
+ .sort((left, right) => left - right);
253
+ return categories.flatMap(category => {
254
+ const cell = cells.get(category);
255
+ return cell === undefined
256
+ ? []
257
+ : [definition(
258
+ cell,
259
+ measureScale.domain[0],
260
+ cell[channels.measure]
261
+ )];
262
+ });
263
+ }
264
+
265
+ if (layout === "group" && offsetScale !== undefined) {
266
+ const categoryIndex = new Map(
267
+ categoryScale.domain.map((value, index) => [value, index])
268
+ );
269
+ const offsetIndex = new Map(
270
+ offsetScale.domain.map((value, index) => [value, index])
271
+ );
272
+ const cells = [...derived].sort((left, right) =>
273
+ categoryIndex.get(left[channels.category]) -
274
+ categoryIndex.get(right[channels.category]) ||
275
+ offsetIndex.get(left.color) - offsetIndex.get(right.color)
276
+ );
277
+ return cells.map(cell => definition(
278
+ cell,
279
+ measureScale.domain[0],
280
+ cell[channels.measure]
281
+ ));
282
+ }
283
+
284
+ const categories = ["ordinal", "band", "point"].includes(categoryScale.type)
285
+ ? categoryScale.domain
286
+ : [...new Set(derived.map(cell => cell[channels.category]))]
287
+ .sort((left, right) => left - right);
288
+ const series = colorScale?.domain ?? [undefined];
289
+ const lookup = new Map(derived.map(cell => [
290
+ JSON.stringify([cell[channels.category], cell.color]),
291
+ cell
292
+ ]));
293
+ const baseline = layout === "overlay" ? measureScale.domain[0] : 0;
294
+ return categories.flatMap(category => {
295
+ const cells = series.map(color =>
296
+ lookup.get(JSON.stringify([category, color]))
297
+ );
298
+ const values = cells.map(cell => cell?.[channels.measure] ?? 0);
299
+ return layoutSeriesPartition(values, layout, { baseline }).flatMap(segment => {
300
+ const cell = cells[segment.index];
301
+ return cell === undefined
302
+ ? []
303
+ : [definition(cell, segment.start, segment.end)];
304
+ });
305
+ });
306
+ }
307
+
308
+ function histogramDefinitions(program, layer, dataset) {
309
+ const xEncoding = layer.encoding.x;
310
+ const colorEncoding = layer.encoding?.color;
311
+ const segments = deriveHistogramSegments({
312
+ dataset,
313
+ layer,
314
+ xEncoding,
315
+ xScale: findSemanticScale(program, xEncoding.scale),
316
+ resolvedScales: program.resolvedScales
317
+ });
318
+ const colorScale = program.resolvedScales[colorEncoding?.scale];
319
+ return segments.map(segment => {
320
+ const colorValue = colorScale?.domain[segment.category];
321
+ return {
322
+ fields: uniqueFields(segment.members),
323
+ channels: {
324
+ x: segment.start,
325
+ x2: segment.end,
326
+ y: segment.stackStart,
327
+ y2: segment.stackEnd,
328
+ ...(colorValue === undefined ? {} : { color: colorValue })
329
+ },
330
+ members: segment.members
331
+ };
332
+ });
333
+ }
334
+
335
+ function rangedDefinitions(layer, dataset) {
336
+ return dataset.values.map(row => ({
337
+ fields: ownFields(row),
338
+ channels: channelMapFromRow(row, layer),
339
+ members: [row]
340
+ }));
341
+ }
342
+
343
+ function sharedChannels(definitions) {
344
+ if (definitions.length === 0) return {};
345
+ const shared = { ...definitions[0].channels };
346
+ for (const channel of Object.keys(shared)) {
347
+ if (!definitions.every(definition =>
348
+ definition.channels[channel] === shared[channel]
349
+ )) delete shared[channel];
350
+ }
351
+ return shared;
352
+ }
353
+
354
+ function barStackDefinitions(layer, barGrain, definitions) {
355
+ const layout = resolveBarColorLayout(layer);
356
+ if (!["stack", "fill", "diverging"].includes(layout)) {
357
+ throw new Error(
358
+ `Bar mark "${layer.id}" does not define stacked items at its current "${layout}" layout.`
359
+ );
360
+ }
361
+ if (barGrain === BAR_GRAINS.ranged) {
362
+ throw new Error(`Ranged bar mark "${layer.id}" has no stack grain.`);
363
+ }
364
+ const channels = resolveBarChannels(layer);
365
+ const categoryChannels = barGrain === BAR_GRAINS.histogram
366
+ ? ["x", "x2"]
367
+ : [channels.category];
368
+ const measure = channels.measure;
369
+ const secondary = `${measure}2`;
370
+ const groups = new Map();
371
+ definitions.forEach((definition, index) => {
372
+ const key = JSON.stringify(categoryChannels.map(channel =>
373
+ definition.channels[channel]
374
+ ));
375
+ const group = groups.get(key) ?? [];
376
+ group.push({ definition, index });
377
+ groups.set(key, group);
378
+ });
379
+
380
+ return [...groups.values()].map((entries, index) => {
381
+ const grouped = entries.map(entry => entry.definition);
382
+ const endpoints = grouped.flatMap(definition => [
383
+ definition.channels[measure],
384
+ definition.channels[secondary]
385
+ ]).filter(Number.isFinite);
386
+ if (endpoints.length === 0) {
387
+ throw new Error(
388
+ `Bar mark "${layer.id}" stack ${index} has no resolved semantic endpoints.`
389
+ );
390
+ }
391
+ const members = grouped.flatMap(definition => definition.members);
392
+ return {
393
+ key: itemKey(layer, "stack", index),
394
+ fields: uniqueFields(members),
395
+ channels: {
396
+ ...sharedChannels(grouped),
397
+ [measure]: Math.min(...endpoints),
398
+ [secondary]: Math.max(...endpoints)
399
+ },
400
+ members,
401
+ graphicIndices: entries.map(entry => entry.index)
402
+ };
403
+ });
404
+ }
405
+
406
+ export function resolveBarItems(program, layer, dataset, selectionGrain) {
407
+ const grain = resolveBarGrain(layer);
408
+ const definitions = grain === BAR_GRAINS.histogram
409
+ ? histogramDefinitions(program, layer, dataset)
410
+ : grain === BAR_GRAINS.aggregate
411
+ ? aggregateCellDefinitions(program, layer, dataset)
412
+ : grain === BAR_GRAINS.ranged
413
+ ? rangedDefinitions(layer, dataset)
414
+ : undefined;
415
+ if (definitions === undefined) {
416
+ throw new Error(`Bar mark "${layer.id}" is incomplete for selection.`);
417
+ }
418
+ if (selectionGrain === "stack") {
419
+ return finalizeItems(
420
+ program,
421
+ layer,
422
+ "stack",
423
+ barStackDefinitions(layer, grain, definitions),
424
+ "rect"
425
+ );
426
+ }
427
+ return finalizeItems(program, layer, grain, definitions, "rect");
428
+ }
429
+
430
+ function rowsForSeries(rows, key) {
431
+ const entries = Object.entries(key);
432
+ return entries.length === 0
433
+ ? rows
434
+ : rows.filter(row => entries.every(([field, value]) => row[field] === value));
435
+ }
436
+
437
+ function seriesDefinitions(layer, rows, series) {
438
+ return series.map(item => {
439
+ const members = rowsForSeries(rows, item.key);
440
+ const fields = { ...uniqueFields(members), ...item.key };
441
+ const channels = Object.fromEntries(
442
+ Object.entries(layer.encoding ?? {}).flatMap(([channel, encoding]) => {
443
+ if (encoding.field === undefined) {
444
+ return Object.hasOwn(encoding, "datum")
445
+ ? [[channel, encoding.datum]]
446
+ : [];
447
+ }
448
+ return Object.hasOwn(fields, encoding.field)
449
+ ? [[channel, fields[encoding.field]]]
450
+ : [];
451
+ })
452
+ );
453
+ return { fields, channels, members };
454
+ });
455
+ }
456
+
457
+ export function resolveLineItems(program, layer, dataset) {
458
+ const derived = deriveLineSeries(dataset.values, layer);
459
+ return finalizeItems(
460
+ program,
461
+ layer,
462
+ "series",
463
+ seriesDefinitions(layer, dataset.values, derived.series),
464
+ "path"
465
+ );
466
+ }
467
+
468
+ export function resolveAreaItems(program, layer, dataset) {
469
+ const transform = findUpstreamTransform(program, dataset, "density");
470
+ const derived = transform === undefined
471
+ ? deriveAreaSeries(dataset.values, layer)
472
+ : deriveDensityAreaSeries(dataset.values, layer, transform);
473
+ return finalizeItems(
474
+ program,
475
+ layer,
476
+ "series",
477
+ seriesDefinitions(layer, dataset.values, derived.series),
478
+ "path"
479
+ );
480
+ }
481
+
482
+ export function resolveRuleItems(program, layer, dataset) {
483
+ const derived = deriveRuleValues(dataset.values, layer);
484
+ const hasField = Object.values(layer.encoding ?? {}).some(encoding =>
485
+ Object.hasOwn(encoding, "field")
486
+ );
487
+ const definitions = Array.from({ length: derived.length }, (_, index) => {
488
+ const members = hasField ? [dataset.values[index]] : dataset.values;
489
+ return {
490
+ fields: hasField
491
+ ? ownFields(dataset.values[index])
492
+ : uniqueFields(dataset.values),
493
+ channels: Object.fromEntries(
494
+ Object.entries(derived.values).map(([channel, values]) => [
495
+ channel,
496
+ values[index]
497
+ ])
498
+ ),
499
+ members
500
+ };
501
+ });
502
+ return finalizeItems(program, layer, "rule", definitions, "line");
503
+ }
@@ -0,0 +1,37 @@
1
+ function translateCommand(command, offset) {
2
+ const translated = { ...command };
3
+ for (const key of ["x", "x1", "x2"]) {
4
+ if (Number.isFinite(translated[key])) translated[key] += offset.x;
5
+ }
6
+ for (const key of ["y", "y1", "y2"]) {
7
+ if (Number.isFinite(translated[key])) translated[key] += offset.y;
8
+ }
9
+ return translated;
10
+ }
11
+
12
+ export function transformPathHighlightProperties(properties, style) {
13
+ const { offset, ...appearance } = style;
14
+ return {
15
+ ...properties,
16
+ ...appearance,
17
+ ...(offset.x === 0 && offset.y === 0
18
+ ? {}
19
+ : {
20
+ commands: properties.commands.map(command =>
21
+ translateCommand(command, offset)
22
+ )
23
+ })
24
+ };
25
+ }
26
+
27
+ export function transformRuleHighlightProperties(properties, style) {
28
+ const { offset, ...appearance } = style;
29
+ return {
30
+ ...properties,
31
+ ...appearance,
32
+ x1: properties.x1 + offset.x,
33
+ x2: properties.x2 + offset.x,
34
+ y1: properties.y1 + offset.y,
35
+ y2: properties.y2 + offset.y
36
+ };
37
+ }
@@ -0,0 +1,118 @@
1
+ import { createPointShapeGraphic } from "../../grammar/pointShapes.js";
2
+
3
+ function pointCenterAndArea(child) {
4
+ const properties = child.properties;
5
+ if (child.type === "circle") {
6
+ return {
7
+ x: properties.x,
8
+ y: properties.y,
9
+ area: Math.PI * properties.radius ** 2
10
+ };
11
+ }
12
+ if (child.type === "rect") {
13
+ return {
14
+ x: properties.x + properties.width / 2,
15
+ y: properties.y + properties.height / 2,
16
+ area: properties.width * properties.height
17
+ };
18
+ }
19
+ const points = properties.commands.filter(command =>
20
+ command.op === "M" || command.op === "L"
21
+ );
22
+ const xs = points.map(point => point.x);
23
+ const ys = points.map(point => point.y);
24
+ let twiceArea = 0;
25
+ for (let index = 0; index < points.length; index += 1) {
26
+ const current = points[index];
27
+ const next = points[(index + 1) % points.length];
28
+ twiceArea += current.x * next.y - next.x * current.y;
29
+ }
30
+ return {
31
+ x: (Math.min(...xs) + Math.max(...xs)) / 2,
32
+ y: (Math.min(...ys) + Math.max(...ys)) / 2,
33
+ area: Math.abs(twiceArea) / 2
34
+ };
35
+ }
36
+
37
+ function transformCommand(command, geometry, center, scale) {
38
+ if (command.op === "Z") return command;
39
+ const transform = (x, y) => ({
40
+ x: center.x + (x - geometry.x) * scale,
41
+ y: center.y + (y - geometry.y) * scale
42
+ });
43
+ if (command.op !== "C") {
44
+ return { op: command.op, ...transform(command.x, command.y) };
45
+ }
46
+ const control1 = transform(command.x1, command.y1);
47
+ const control2 = transform(command.x2, command.y2);
48
+ return {
49
+ op: "C",
50
+ x1: control1.x,
51
+ y1: control1.y,
52
+ x2: control2.x,
53
+ y2: control2.y,
54
+ ...transform(command.x, command.y)
55
+ };
56
+ }
57
+
58
+ export function transformPointHighlightChild(child, style) {
59
+ const geometry = pointCenterAndArea(child);
60
+ const center = {
61
+ x: geometry.x + style.offset.x,
62
+ y: geometry.y + style.offset.y
63
+ };
64
+ const area = geometry.area * style.size;
65
+ const shared = {
66
+ fill: style.fill ?? child.properties.fill,
67
+ ...(style.opacity === undefined
68
+ ? (child.properties.opacity === undefined ? {} : { opacity: child.properties.opacity })
69
+ : { opacity: style.opacity }),
70
+ ...(style.stroke === undefined ? {} : { stroke: style.stroke }),
71
+ ...(style.strokeWidth === undefined ? {} : { strokeWidth: style.strokeWidth })
72
+ };
73
+ if (style.shape !== undefined) {
74
+ return createPointShapeGraphic({
75
+ shape: style.shape,
76
+ ...center,
77
+ area,
78
+ ...shared
79
+ });
80
+ }
81
+ const scale = Math.sqrt(style.size);
82
+ if (child.type === "circle") {
83
+ return {
84
+ type: "circle",
85
+ properties: {
86
+ ...child.properties,
87
+ ...center,
88
+ radius: child.properties.radius * scale,
89
+ ...shared
90
+ }
91
+ };
92
+ }
93
+ if (child.type === "rect") {
94
+ const width = child.properties.width * scale;
95
+ const height = child.properties.height * scale;
96
+ return {
97
+ type: "rect",
98
+ properties: {
99
+ ...child.properties,
100
+ x: center.x - width / 2,
101
+ y: center.y - height / 2,
102
+ width,
103
+ height,
104
+ ...shared
105
+ }
106
+ };
107
+ }
108
+ return {
109
+ type: "path",
110
+ properties: {
111
+ ...child.properties,
112
+ commands: child.properties.commands.map(command =>
113
+ transformCommand(command, geometry, center, scale)
114
+ ),
115
+ ...shared
116
+ }
117
+ };
118
+ }
@@ -0,0 +1,10 @@
1
+ import { resolveAreaItems } from "../items.js";
2
+ import { normalizeAreaHighlightStyle } from "../styles.js";
3
+
4
+ export const areaSelectionPolicy = Object.freeze({
5
+ supportedGrains: Object.freeze(["item"]),
6
+ resolveItems: resolveAreaItems,
7
+ normalizeHighlightStyle: normalizeAreaHighlightStyle,
8
+ applyHighlightOp: "applyPathHighlight",
9
+ rematerializeOp: "rematerializeAreaMark"
10
+ });
@@ -0,0 +1,10 @@
1
+ import { resolveBarItems } from "../items.js";
2
+ import { normalizeBarHighlightStyle } from "../styles.js";
3
+
4
+ export const barSelectionPolicy = Object.freeze({
5
+ supportedGrains: Object.freeze(["item", "stack"]),
6
+ resolveItems: resolveBarItems,
7
+ normalizeHighlightStyle: normalizeBarHighlightStyle,
8
+ applyHighlightOp: "applyBarHighlight",
9
+ rematerializeOp: "rematerializeBarMark"
10
+ });