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.
- package/CHANGELOG.md +28 -0
- package/README.md +12 -1
- package/package.json +6 -1
- package/src/actions/boxPlots/components.js +3 -3
- package/src/actions/boxPlots/edit.js +277 -0
- package/src/actions/boxPlots/index.js +2 -0
- package/src/actions/boxPlots/materialize.js +12 -1
- package/src/actions/boxPlots/options.js +25 -14
- package/src/actions/canvas/actions.js +21 -17
- package/src/actions/composition/actions.js +247 -0
- package/src/actions/composition/index.js +1 -0
- package/src/actions/coordinates/actions.js +2 -5
- package/src/actions/data/density.js +7 -1
- package/src/actions/data/filter.js +3 -1
- package/src/actions/encodings/appearance.js +11 -0
- package/src/actions/encodings/barWidth.js +5 -3
- package/src/actions/encodings/color.js +120 -25
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +90 -72
- package/src/actions/encodings/position/apply.js +66 -0
- package/src/actions/encodings/position/policies/arc.js +26 -0
- package/src/actions/encodings/position/policies/bar.js +9 -5
- package/src/actions/encodings/position/policies/index.js +5 -1
- package/src/actions/encodings/position/policies/line.js +20 -3
- package/src/actions/encodings/position/resolve.js +57 -8
- package/src/actions/encodings/position.js +44 -90
- package/src/actions/encodings/ranged.js +28 -6
- package/src/actions/encodings/shared.js +17 -21
- package/src/actions/encodings/text.js +73 -0
- package/src/actions/errorBands/create.js +37 -29
- package/src/actions/errorBands/edit.js +206 -0
- package/src/actions/errorBands/index.js +9 -0
- package/src/actions/errorBands/options.js +39 -0
- package/src/actions/errorBars/create.js +50 -43
- package/src/actions/errorBars/edit.js +130 -0
- package/src/actions/errorBars/index.js +3 -0
- package/src/actions/errorBars/options.js +52 -0
- package/src/actions/facets/actions.js +162 -0
- package/src/actions/facets/derive.js +200 -0
- package/src/actions/facets/guides.js +16 -0
- package/src/actions/facets/index.js +1 -0
- package/src/actions/facets/replay.js +69 -0
- package/src/actions/guides/applicability.js +113 -0
- package/src/actions/guides/axes/axes.js +70 -16
- package/src/actions/guides/axes/edit.js +126 -0
- package/src/actions/guides/axes/index.js +12 -0
- package/src/actions/guides/axes/labels.js +21 -18
- package/src/actions/guides/axes/remove.js +60 -0
- package/src/actions/guides/axes/tickGroups.js +7 -5
- package/src/actions/guides/axes/ticks.js +9 -4
- package/src/actions/guides/axes/titles.js +7 -5
- package/src/actions/guides/grids/grid.js +144 -15
- package/src/actions/guides/grids/resolve.js +5 -13
- package/src/actions/guides/guides.js +17 -75
- package/src/actions/guides/index.js +6 -0
- package/src/actions/guides/legends/categorical/actions.js +48 -9
- package/src/actions/guides/legends/categorical/components.js +16 -16
- package/src/actions/guides/legends/categorical/index.js +14 -14
- package/src/actions/guides/legends/categorical/layout.js +3 -1
- package/src/actions/guides/legends/categorical/options.js +2 -5
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +14 -14
- package/src/actions/guides/legends/continuous/common.js +7 -7
- package/src/actions/guides/legends/continuous/interval.js +5 -5
- package/src/actions/guides/legends/edit.js +2 -6
- package/src/actions/guides/legends/focused.js +63 -0
- package/src/actions/guides/legends/index.js +4 -0
- package/src/actions/guides/legends/remove.js +64 -0
- package/src/actions/guides/legends/size.js +12 -14
- package/src/actions/guides/polar/axes/facade.js +219 -0
- package/src/actions/guides/polar/axes/labels.js +188 -0
- package/src/actions/guides/polar/axes/lines.js +113 -0
- package/src/actions/guides/polar/axes/shared.js +112 -0
- package/src/actions/guides/polar/axes/ticks.js +164 -0
- package/src/actions/guides/polar/axes/titles.js +165 -0
- package/src/actions/guides/polar/axes.js +55 -0
- package/src/actions/guides/polar/grids.js +252 -0
- package/src/actions/guides/polar/index.js +2 -0
- package/src/actions/guides/polar/resolve.js +269 -0
- package/src/actions/index.js +4 -0
- package/src/actions/marks/arc/actions.js +239 -0
- package/src/actions/marks/arc/index.js +1 -0
- package/src/actions/marks/{area.js → area/actions.js} +51 -38
- package/src/actions/marks/area/index.js +4 -0
- package/src/actions/marks/bar/create.js +26 -5
- package/src/actions/marks/bar/materialize.js +25 -31
- package/src/actions/marks/index.js +12 -4
- package/src/actions/marks/lifecycle.js +27 -0
- package/src/actions/marks/line/actions.js +373 -0
- package/src/actions/marks/line/index.js +1 -0
- package/src/actions/marks/{point.js → point/actions.js} +67 -108
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/rect/actions.js +165 -0
- package/src/actions/marks/rect/index.js +1 -0
- package/src/actions/marks/remove.js +214 -0
- package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
- package/src/actions/marks/rule/index.js +1 -0
- package/src/actions/marks/shared.js +156 -25
- package/src/actions/marks/text/actions.js +192 -0
- package/src/actions/marks/text/index.js +1 -0
- package/src/actions/primitives/createGraphics.js +13 -6
- package/src/actions/primitives/editGraphics.js +10 -3
- package/src/actions/primitives/semantic.js +26 -3
- package/src/actions/primitives/semanticValue.js +25 -3
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +18 -1
- package/src/actions/regression/edit.js +247 -0
- package/src/actions/regression/index.js +3 -0
- package/src/actions/regression/resolve.js +3 -1
- package/src/actions/scales/consumers.js +15 -6
- package/src/actions/scales/definitions.js +13 -4
- package/src/actions/scales/edit.js +42 -19
- package/src/actions/scales/materialize.js +57 -408
- package/src/actions/selection/actions.js +38 -24
- package/src/actions/selection/index.js +2 -0
- package/src/actions/titles/actions.js +63 -2
- package/src/actions/titles/index.js +2 -0
- package/src/actions/titles/resolve.js +12 -17
- package/src/composition.js +10 -0
- package/src/core/ChartProgram.js +200 -2
- package/src/core/action.js +10 -0
- package/src/core/validation.js +16 -0
- package/src/core/vocabulary.js +154 -4
- package/src/grammar/arcs.js +154 -0
- package/src/grammar/bars/aggregate.js +2 -2
- package/src/grammar/bars/geometry.js +3 -3
- package/src/grammar/bars/policy.js +7 -1
- package/src/grammar/coordinates.js +3 -7
- package/src/grammar/density.js +20 -1
- package/src/grammar/facets/dependencies.js +185 -0
- package/src/grammar/facets/guides.js +257 -0
- package/src/grammar/facets/scales.js +254 -0
- package/src/grammar/facets.js +122 -0
- package/src/grammar/lineSeries.js +111 -3
- package/src/grammar/polar.js +102 -0
- package/src/grammar/polarGuides.js +243 -0
- package/src/grammar/polarLineCommands.js +43 -0
- package/src/grammar/polarPaths.js +161 -0
- package/src/grammar/positionCompatibility.js +18 -2
- package/src/grammar/rects.js +26 -0
- package/src/grammar/scales/continuous.js +6 -1
- package/src/grammar/scales/fields.js +7 -0
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +1 -1
- package/src/grammar/schemas/graphic.js +1 -1
- package/src/grammar/schemas/graphicBounds.js +64 -9
- package/src/grammar/schemas/graphicTree.js +44 -10
- package/src/grammar/schemas/semanticPath.js +31 -9
- package/src/grammar/seriesLayout.js +13 -2
- package/src/grammar/text.js +71 -0
- package/src/grammar/ticks.js +50 -13
- package/src/index.js +1 -0
- package/src/layout/composition.js +310 -0
- package/src/layout/facets.js +131 -0
- package/src/layout/title.js +6 -0
- package/src/materialization/bars/aggregate.js +5 -2
- package/src/materialization/bars/grouped.js +75 -37
- package/src/materialization/composition.js +152 -0
- package/src/materialization/compositionSnapshot.js +114 -0
- package/src/materialization/dependencies.js +71 -16
- package/src/materialization/encodings.js +14 -28
- package/src/materialization/facetGuides.js +426 -0
- package/src/materialization/facets.js +214 -0
- package/src/materialization/graphicHierarchy.js +15 -5
- package/src/materialization/guides/resources.js +46 -0
- package/src/materialization/marks.js +220 -14
- package/src/materialization/planner.js +105 -12
- package/src/materialization/rect.js +138 -0
- package/src/materialization/rectConfig.js +38 -0
- package/src/materialization/rowEncoding.js +33 -0
- package/src/materialization/scales/map.js +40 -0
- package/src/materialization/scales/policies/arc.js +37 -0
- package/src/materialization/scales/policies/bar.js +132 -0
- package/src/materialization/scales/policies/series.js +76 -0
- package/src/materialization/scales/resolve.js +326 -0
- package/src/materialization/selection/items/arc.js +29 -0
- package/src/materialization/selection/items/bar.js +255 -0
- package/src/materialization/selection/items/common.js +131 -0
- package/src/materialization/selection/items/index.js +6 -0
- package/src/materialization/selection/items/path.js +59 -0
- package/src/materialization/selection/items/point.js +55 -0
- package/src/materialization/selection/items/rect.js +23 -0
- package/src/materialization/selection/items/rule.js +25 -0
- package/src/materialization/selection/policies/arc.js +10 -0
- package/src/materialization/selection/policies/area.js +1 -1
- package/src/materialization/selection/policies/bar.js +1 -1
- package/src/materialization/selection/policies/index.js +5 -1
- package/src/materialization/selection/policies/line.js +1 -1
- package/src/materialization/selection/policies/point.js +1 -1
- package/src/materialization/selection/policies/rect.js +10 -0
- package/src/materialization/selection/policies/rule.js +1 -1
- package/src/materialization/selection/styles.js +24 -16
- package/src/materialization/text.js +156 -0
- package/src/renderers/canvas/index.js +68 -22
- package/types/extension.d.ts +1 -0
- package/types/index.d.ts +56 -1
- package/types/program.d.ts +664 -27
- package/src/actions/marks/line.js +0 -248
- 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,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) {
|
|
@@ -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
|
+
});
|