@genome-spy/core 0.66.1 → 0.68.0
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/dist/bundle/index.es.js +7669 -6115
- package/dist/bundle/index.js +114 -133
- package/dist/schema.json +534 -132
- package/dist/src/data/collector.d.ts +20 -0
- package/dist/src/data/collector.d.ts.map +1 -1
- package/dist/src/data/collector.js +148 -0
- package/dist/src/data/dataFlow.d.ts +6 -0
- package/dist/src/data/dataFlow.d.ts.map +1 -1
- package/dist/src/data/dataFlow.js +10 -0
- package/dist/src/data/flowHandle.d.ts +2 -0
- package/dist/src/data/flowHandle.d.ts.map +1 -1
- package/dist/src/data/flowHandle.js +1 -0
- package/dist/src/data/flowInit.d.ts +12 -4
- package/dist/src/data/flowInit.d.ts.map +1 -1
- package/dist/src/data/flowInit.js +115 -17
- package/dist/src/data/flowNode.d.ts +8 -0
- package/dist/src/data/flowNode.d.ts.map +1 -1
- package/dist/src/data/flowNode.js +18 -0
- package/dist/src/data/keyIndex.d.ts +18 -0
- package/dist/src/data/keyIndex.d.ts.map +1 -0
- package/dist/src/data/keyIndex.js +241 -0
- package/dist/src/data/keyIndex.test.d.ts +2 -0
- package/dist/src/data/keyIndex.test.d.ts.map +1 -0
- package/dist/src/data/sources/dataSource.d.ts.map +1 -1
- package/dist/src/data/sources/dataSource.js +5 -1
- package/dist/src/data/sources/dataSourceFactory.d.ts +14 -12
- package/dist/src/data/sources/dataSourceFactory.d.ts.map +1 -1
- package/dist/src/data/sources/dataSourceFactory.js +52 -16
- package/dist/src/data/sources/lazy/mockLazySource.d.ts +29 -0
- package/dist/src/data/sources/lazy/mockLazySource.d.ts.map +1 -0
- package/dist/src/data/sources/lazy/mockLazySource.js +44 -0
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +22 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.js +34 -2
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +15 -0
- package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/tabixSource.js +15 -5
- package/dist/src/data/transforms/stack.d.ts.map +1 -1
- package/dist/src/data/transforms/stack.js +1 -0
- package/dist/src/encoder/accessor.d.ts +43 -0
- package/dist/src/encoder/accessor.d.ts.map +1 -1
- package/dist/src/encoder/accessor.js +164 -0
- package/dist/src/encoder/encoder.d.ts +11 -2
- package/dist/src/encoder/encoder.d.ts.map +1 -1
- package/dist/src/encoder/encoder.js +24 -4
- package/dist/src/encoder/metadataChannels.d.ts +15 -0
- package/dist/src/encoder/metadataChannels.d.ts.map +1 -0
- package/dist/src/encoder/metadataChannels.js +65 -0
- package/dist/src/encoder/metadataChannels.test.d.ts +2 -0
- package/dist/src/encoder/metadataChannels.test.d.ts.map +1 -0
- package/dist/src/genome/scaleLocus.d.ts.map +1 -1
- package/dist/src/genome/scaleLocus.js +14 -1
- package/dist/src/genomeSpy/containerUi.d.ts +0 -1
- package/dist/src/genomeSpy/containerUi.d.ts.map +1 -1
- package/dist/src/genomeSpy/containerUi.js +0 -14
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +3 -7
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -1
- package/dist/src/genomeSpy/loadingIndicatorManager.js +68 -20
- package/dist/src/genomeSpy/loadingStatusRegistry.d.ts +52 -0
- package/dist/src/genomeSpy/loadingStatusRegistry.d.ts.map +1 -0
- package/dist/src/genomeSpy/loadingStatusRegistry.js +86 -0
- package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
- package/dist/src/genomeSpy/viewContextFactory.js +0 -1
- package/dist/src/genomeSpy/viewDataInit.d.ts +10 -0
- package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
- package/dist/src/genomeSpy/viewDataInit.js +166 -2
- package/dist/src/genomeSpy/viewDataInit.test.d.ts +2 -0
- package/dist/src/genomeSpy/viewDataInit.test.d.ts.map +1 -0
- package/dist/src/genomeSpy.d.ts +1 -2
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +69 -27
- package/dist/src/gl/dataToVertices.d.ts.map +1 -1
- package/dist/src/gl/dataToVertices.js +16 -4
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +18 -11
- package/dist/src/marks/markUtils.js +1 -1
- package/dist/src/scale/scale.d.ts +6 -1
- package/dist/src/scale/scale.d.ts.map +1 -1
- package/dist/src/scale/scale.js +83 -23
- package/dist/src/scales/axisResolution.d.ts.map +1 -1
- package/dist/src/scales/axisResolution.js +10 -0
- package/dist/src/scales/{scaleDomainAggregator.d.ts → domainPlanner.d.ts} +8 -5
- package/dist/src/scales/domainPlanner.d.ts.map +1 -0
- package/dist/src/scales/domainPlanner.js +285 -0
- package/dist/src/scales/domainPlanner.test.d.ts +2 -0
- package/dist/src/scales/domainPlanner.test.d.ts.map +1 -0
- package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
- package/dist/src/scales/scaleInstanceManager.js +8 -4
- package/dist/src/scales/scaleInteractionController.d.ts +6 -0
- package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
- package/dist/src/scales/scaleInteractionController.js +41 -3
- package/dist/src/scales/scaleResolution.d.ts +19 -16
- package/dist/src/scales/scaleResolution.d.ts.map +1 -1
- package/dist/src/scales/scaleResolution.js +255 -70
- package/dist/src/scales/scaleResolution.test.d.ts.map +1 -1
- package/dist/src/selection/selection.d.ts +21 -0
- package/dist/src/selection/selection.d.ts.map +1 -1
- package/dist/src/selection/selection.js +82 -0
- package/dist/src/spec/channel.d.ts +52 -15
- package/dist/src/spec/data.d.ts +4 -0
- package/dist/src/spec/parameter.d.ts +16 -11
- package/dist/src/spec/testing.d.ts +12 -0
- package/dist/src/spec/testing.d.ts.map +1 -0
- package/dist/src/spec/testing.js +20 -0
- package/dist/src/spec/view.d.ts +45 -10
- package/dist/src/styles/genome-spy.css +3 -31
- package/dist/src/styles/genome-spy.css.d.ts +1 -1
- package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
- package/dist/src/styles/genome-spy.css.js +0 -29
- package/dist/src/types/encoder.d.ts +37 -2
- package/dist/src/types/rendering.d.ts +4 -3
- package/dist/src/types/viewContext.d.ts +0 -14
- package/dist/src/utils/domainArray.d.ts.map +1 -1
- package/dist/src/utils/domainArray.js +3 -0
- package/dist/src/utils/indexer.d.ts +3 -0
- package/dist/src/utils/indexer.d.ts.map +1 -1
- package/dist/src/utils/indexer.js +3 -0
- package/dist/src/utils/throttle.d.ts +4 -1
- package/dist/src/utils/throttle.d.ts.map +1 -1
- package/dist/src/utils/throttle.js +54 -23
- package/dist/src/utils/throttle.test.d.ts +2 -0
- package/dist/src/utils/throttle.test.d.ts.map +1 -0
- package/dist/src/utils/transition.d.ts +21 -0
- package/dist/src/utils/transition.d.ts.map +1 -1
- package/dist/src/utils/transition.js +28 -0
- package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
- package/dist/src/utils/ui/tooltip.js +7 -1
- package/dist/src/utils/ui/tooltip.test.d.ts +2 -0
- package/dist/src/utils/ui/tooltip.test.d.ts.map +1 -0
- package/dist/src/view/axisGridView.d.ts.map +1 -1
- package/dist/src/view/axisGridView.js +22 -5
- package/dist/src/view/axisView.d.ts.map +1 -1
- package/dist/src/view/axisView.js +20 -5
- package/dist/src/view/concatView.js +3 -3
- package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
- package/dist/src/view/containerMutationHelper.js +6 -2
- package/dist/src/view/containerView.d.ts +9 -5
- package/dist/src/view/containerView.d.ts.map +1 -1
- package/dist/src/view/containerView.js +34 -9
- package/dist/src/view/dataReadiness.d.ts +46 -0
- package/dist/src/view/dataReadiness.d.ts.map +1 -0
- package/dist/src/view/dataReadiness.js +267 -0
- package/dist/src/view/dataReadiness.test.d.ts +2 -0
- package/dist/src/view/dataReadiness.test.d.ts.map +1 -0
- package/dist/src/view/facetView.d.ts.map +1 -1
- package/dist/src/view/facetView.js +7 -5
- package/dist/src/view/flowBuilder.d.ts +5 -3
- package/dist/src/view/flowBuilder.d.ts.map +1 -1
- package/dist/src/view/flowBuilder.js +74 -7
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +8 -0
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +119 -2
- package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
- package/dist/src/view/gridView/scrollbar.js +3 -0
- package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
- package/dist/src/view/gridView/selectionRect.js +20 -5
- package/dist/src/view/gridView/separatorView.d.ts +51 -0
- package/dist/src/view/gridView/separatorView.d.ts.map +1 -0
- package/dist/src/view/gridView/separatorView.js +275 -0
- package/dist/src/view/layerView.js +3 -3
- package/dist/src/view/layout/flexLayout.d.ts +0 -30
- package/dist/src/view/layout/flexLayout.d.ts.map +1 -1
- package/dist/src/view/layout/flexLayout.js +0 -86
- package/dist/src/view/paramMediator.d.ts +19 -0
- package/dist/src/view/paramMediator.d.ts.map +1 -1
- package/dist/src/view/paramMediator.js +86 -19
- package/dist/src/view/testUtils.d.ts.map +1 -1
- package/dist/src/view/testUtils.js +11 -1
- package/dist/src/view/unitView.d.ts +8 -13
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +127 -43
- package/dist/src/view/view.d.ts +34 -14
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +119 -9
- package/dist/src/view/viewFactory.d.ts.map +1 -1
- package/dist/src/view/viewFactory.js +20 -1
- package/dist/src/view/viewSelectors.d.ts +148 -0
- package/dist/src/view/viewSelectors.d.ts.map +1 -0
- package/dist/src/view/viewSelectors.js +773 -0
- package/dist/src/view/viewSelectors.test.d.ts +2 -0
- package/dist/src/view/viewSelectors.test.d.ts.map +1 -0
- package/dist/src/view/viewUtils.d.ts +0 -8
- package/dist/src/view/viewUtils.d.ts.map +1 -1
- package/dist/src/view/viewUtils.js +1 -21
- package/package.json +3 -3
- package/dist/src/scales/scaleDomainAggregator.d.ts.map +0 -1
- package/dist/src/scales/scaleDomainAggregator.js +0 -162
- package/dist/src/scales/scaleDomainAggregator.test.d.ts +0 -2
- package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +0 -1
|
@@ -13,7 +13,7 @@ import { configureDomain } from "../scale/scale.js";
|
|
|
13
13
|
|
|
14
14
|
import ScaleInstanceManager from "./scaleInstanceManager.js";
|
|
15
15
|
import { resolveScalePropsBase } from "./scalePropsResolver.js";
|
|
16
|
-
import
|
|
16
|
+
import DomainPlanner from "./domainPlanner.js";
|
|
17
17
|
import ScaleInteractionController from "./scaleInteractionController.js";
|
|
18
18
|
import {
|
|
19
19
|
INDEX,
|
|
@@ -23,9 +23,11 @@ import {
|
|
|
23
23
|
QUANTITATIVE,
|
|
24
24
|
} from "./scaleResolutionConstants.js";
|
|
25
25
|
|
|
26
|
+
import { getAccessorDomainKey } from "../encoder/accessor.js";
|
|
26
27
|
import { isSecondaryChannel } from "../encoder/encoder.js";
|
|
27
28
|
import { NominalDomain } from "../utils/domainArray.js";
|
|
28
|
-
import {
|
|
29
|
+
import { shallowArrayEquals } from "../utils/arrayUtils.js";
|
|
30
|
+
import createIndexer from "../utils/indexer.js";
|
|
29
31
|
|
|
30
32
|
// Register scaleLocus to Vega-Scale.
|
|
31
33
|
// Loci are discrete but the scale's domain can be adjusted in a continuous manner.
|
|
@@ -42,7 +44,7 @@ export { INDEX, LOCUS, NOMINAL, ORDINAL, QUANTITATIVE };
|
|
|
42
44
|
* @prop {import("../view/unitView.js").default} view TODO: Get rid of the view reference
|
|
43
45
|
* @prop {T} channel
|
|
44
46
|
* @prop {import("../spec/channel.js").ChannelDefWithScale} channelDef
|
|
45
|
-
* @prop {
|
|
47
|
+
* @prop {boolean} contributesToDomain
|
|
46
48
|
*/
|
|
47
49
|
/**
|
|
48
50
|
* Resolves a shared scale for a channel by merging scale properties and domains
|
|
@@ -51,6 +53,16 @@ export { INDEX, LOCUS, NOMINAL, ORDINAL, QUANTITATIVE };
|
|
|
51
53
|
* notifications, while delegating domain aggregation, scale instance setup, and
|
|
52
54
|
* interaction logic to focused helpers.
|
|
53
55
|
*
|
|
56
|
+
* Documentation overview of current concerns this class (and its helpers) deal with:
|
|
57
|
+
* - Resolution membership and rules (shared/independent/forced/excluded, visibility, registration).
|
|
58
|
+
* - Scale property aggregation (merge props, channel overrides, unique scale names).
|
|
59
|
+
* - Domain computation and caching (configured/data unions, defaults, indexer stability, subscriptions).
|
|
60
|
+
* - Scale instance lifecycle (create, reconfigure props, apply domains, notify changes).
|
|
61
|
+
* - Interaction and zoom (zoom/pan/reset coordination, snapshots, zoom extents).
|
|
62
|
+
* - Rendering integration (range textures, axis sizing/positioning).
|
|
63
|
+
* - Locus-specific conversions (complex intervals, genome extent bindings).
|
|
64
|
+
* - Diagnostics and edge cases (ordinal unknown, nice/zero/padding, log warnings).
|
|
65
|
+
*
|
|
54
66
|
* @implements {ScaleResolutionApi}
|
|
55
67
|
*/
|
|
56
68
|
export default class ScaleResolution {
|
|
@@ -74,6 +86,9 @@ export default class ScaleResolution {
|
|
|
74
86
|
/** @type {Set<ScaleResolutionMember>} The involved views */
|
|
75
87
|
#members = new Set();
|
|
76
88
|
|
|
89
|
+
/** @type {Set<ScaleResolutionMember>} */
|
|
90
|
+
#dataDomainMembers = new Set();
|
|
91
|
+
|
|
77
92
|
/**
|
|
78
93
|
* @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
|
|
79
94
|
*/
|
|
@@ -85,12 +100,17 @@ export default class ScaleResolution {
|
|
|
85
100
|
/** @type {ScaleInstanceManager} */
|
|
86
101
|
#scaleManager;
|
|
87
102
|
|
|
88
|
-
/** @type {
|
|
103
|
+
/** @type {DomainPlanner} */
|
|
89
104
|
#domainAggregator;
|
|
90
105
|
|
|
91
106
|
/** @type {ScaleInteractionController} */
|
|
92
107
|
#interactionController;
|
|
93
108
|
|
|
109
|
+
/** @type {ReturnType<typeof createIndexer> | undefined} */
|
|
110
|
+
#categoricalIndexer;
|
|
111
|
+
|
|
112
|
+
#categoricalIndexerExplicit = false;
|
|
113
|
+
|
|
94
114
|
/**
|
|
95
115
|
* @param {Channel} channel
|
|
96
116
|
*/
|
|
@@ -102,8 +122,10 @@ export default class ScaleResolution {
|
|
|
102
122
|
/** @type {string} An optional unique identifier for the scale */
|
|
103
123
|
this.name = undefined;
|
|
104
124
|
|
|
105
|
-
this.#domainAggregator = new
|
|
106
|
-
getMembers: () => this.#
|
|
125
|
+
this.#domainAggregator = new DomainPlanner({
|
|
126
|
+
getMembers: () => this.#getActiveMembers(),
|
|
127
|
+
getDataMembers: () =>
|
|
128
|
+
this.#getActiveMembers(this.#dataDomainMembers),
|
|
107
129
|
getType: () => this.type,
|
|
108
130
|
getLocusExtent: () => this.#getLocusExtent(),
|
|
109
131
|
fromComplexInterval: this.fromComplexInterval.bind(this),
|
|
@@ -139,6 +161,29 @@ export default class ScaleResolution {
|
|
|
139
161
|
return first.view;
|
|
140
162
|
}
|
|
141
163
|
|
|
164
|
+
/**
|
|
165
|
+
* @param {Set<ScaleResolutionMember>} [members]
|
|
166
|
+
*/
|
|
167
|
+
#getActiveMembers(members = this.#members) {
|
|
168
|
+
/** @type {Set<ScaleResolutionMember>} */
|
|
169
|
+
const active = new Set();
|
|
170
|
+
for (const member of members) {
|
|
171
|
+
const view = member.view;
|
|
172
|
+
if (!view.isConfiguredVisible()) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
!view.isDataInitialized() &&
|
|
177
|
+
!member.channelDef?.scale?.domain
|
|
178
|
+
) {
|
|
179
|
+
// Explicit domains should be honored even before data init.
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
active.add(member);
|
|
183
|
+
}
|
|
184
|
+
return active;
|
|
185
|
+
}
|
|
186
|
+
|
|
142
187
|
get #viewContext() {
|
|
143
188
|
return this.#firstMemberView.context;
|
|
144
189
|
}
|
|
@@ -262,6 +307,10 @@ export default class ScaleResolution {
|
|
|
262
307
|
}
|
|
263
308
|
|
|
264
309
|
this.#members.add(newMember);
|
|
310
|
+
if (newMember.contributesToDomain) {
|
|
311
|
+
this.#dataDomainMembers.add(newMember);
|
|
312
|
+
}
|
|
313
|
+
this.#domainAggregator.invalidateConfiguredDomain();
|
|
265
314
|
}
|
|
266
315
|
|
|
267
316
|
/**
|
|
@@ -272,10 +321,62 @@ export default class ScaleResolution {
|
|
|
272
321
|
this.#addMember(member);
|
|
273
322
|
return () => {
|
|
274
323
|
const removed = this.#members.delete(member);
|
|
324
|
+
if (removed) {
|
|
325
|
+
this.#dataDomainMembers.delete(member);
|
|
326
|
+
this.#domainAggregator.invalidateConfiguredDomain();
|
|
327
|
+
}
|
|
275
328
|
return removed && this.#members.size === 0;
|
|
276
329
|
};
|
|
277
330
|
}
|
|
278
331
|
|
|
332
|
+
#hasRenderedMember() {
|
|
333
|
+
for (const member of this.#members) {
|
|
334
|
+
if (member.view.hasRendered()) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @param {import("../data/collector.js").default} collector
|
|
343
|
+
* @param {Iterable<import("../types/encoder.js").ScaleAccessor>} accessors
|
|
344
|
+
* @returns {() => void}
|
|
345
|
+
*/
|
|
346
|
+
registerCollectorSubscriptions(collector, accessors) {
|
|
347
|
+
/** @type {Set<string>} */
|
|
348
|
+
const domainKeys = new Set();
|
|
349
|
+
|
|
350
|
+
for (const accessor of accessors) {
|
|
351
|
+
if (accessor.channelDef.domainInert) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
domainKeys.add(getAccessorDomainKey(accessor, this.type));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (domainKeys.size === 0) {
|
|
358
|
+
return () => undefined;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const listener = () => {
|
|
362
|
+
this.reconfigureDomain();
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/** @type {(() => void)[]} */
|
|
366
|
+
const unregisters = [];
|
|
367
|
+
for (const domainKey of domainKeys) {
|
|
368
|
+
unregisters.push(
|
|
369
|
+
collector.subscribeDomainChanges(domainKey, listener)
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return () => {
|
|
374
|
+
for (const unregister of unregisters) {
|
|
375
|
+
unregister();
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
279
380
|
/**
|
|
280
381
|
* Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
|
|
281
382
|
*/
|
|
@@ -337,10 +438,42 @@ export default class ScaleResolution {
|
|
|
337
438
|
extractDataDomain
|
|
338
439
|
);
|
|
339
440
|
|
|
340
|
-
if (
|
|
441
|
+
if (isDiscrete(props.type)) {
|
|
442
|
+
const isExplicit = this.#isExplicitDomain();
|
|
443
|
+
const indexer = this.#getCategoricalIndexer(isExplicit);
|
|
444
|
+
if (domain != null) {
|
|
445
|
+
if (
|
|
446
|
+
isExplicit &&
|
|
447
|
+
indexer.domain().length > 0 &&
|
|
448
|
+
!shallowArrayEquals(indexer.domain(), domain)
|
|
449
|
+
) {
|
|
450
|
+
this.#categoricalIndexer = undefined;
|
|
451
|
+
return this.#getScaleProps(extractDataDomain);
|
|
452
|
+
}
|
|
453
|
+
indexer.addAll(domain);
|
|
454
|
+
const active = new Set(domain);
|
|
455
|
+
const indexedDomain = indexer
|
|
456
|
+
.domain()
|
|
457
|
+
.filter((value) => active.has(value));
|
|
458
|
+
props.domain =
|
|
459
|
+
indexedDomain.length > 0
|
|
460
|
+
? /** @type {import("../spec/scale.js").ScalarDomain} */ (
|
|
461
|
+
indexedDomain
|
|
462
|
+
)
|
|
463
|
+
: new NominalDomain();
|
|
464
|
+
} else {
|
|
465
|
+
const indexedDomain = indexer.domain();
|
|
466
|
+
props.domain =
|
|
467
|
+
indexedDomain.length > 0
|
|
468
|
+
? /** @type {import("../spec/scale.js").ScalarDomain} */ (
|
|
469
|
+
indexedDomain
|
|
470
|
+
)
|
|
471
|
+
: new NominalDomain();
|
|
472
|
+
}
|
|
473
|
+
// Scale props are spec-shaped; keep the indexer off the public type.
|
|
474
|
+
/** @type {any} */ (props).domainIndexer = indexer;
|
|
475
|
+
} else if (domain && domain.length > 0) {
|
|
341
476
|
props.domain = domain;
|
|
342
|
-
} else if (isDiscrete(props.type)) {
|
|
343
|
-
props.domain = new NominalDomain();
|
|
344
477
|
}
|
|
345
478
|
|
|
346
479
|
if (!props.domain && props.domainMid !== undefined) {
|
|
@@ -352,6 +485,20 @@ export default class ScaleResolution {
|
|
|
352
485
|
return props;
|
|
353
486
|
}
|
|
354
487
|
|
|
488
|
+
/**
|
|
489
|
+
* @param {boolean} isExplicit
|
|
490
|
+
*/
|
|
491
|
+
#getCategoricalIndexer(isExplicit) {
|
|
492
|
+
if (
|
|
493
|
+
!this.#categoricalIndexer ||
|
|
494
|
+
this.#categoricalIndexerExplicit !== isExplicit
|
|
495
|
+
) {
|
|
496
|
+
this.#categoricalIndexer = createIndexer();
|
|
497
|
+
this.#categoricalIndexerExplicit = isExplicit;
|
|
498
|
+
}
|
|
499
|
+
return this.#categoricalIndexer;
|
|
500
|
+
}
|
|
501
|
+
|
|
355
502
|
/**
|
|
356
503
|
* Reconfigures the scale: updates domain and other settings.
|
|
357
504
|
*
|
|
@@ -359,36 +506,108 @@ export default class ScaleResolution {
|
|
|
359
506
|
* or when scale properties are otherwise re-resolved from the view hierarchy.
|
|
360
507
|
*/
|
|
361
508
|
reconfigure() {
|
|
362
|
-
|
|
363
|
-
|
|
509
|
+
this.#domainAggregator.invalidateConfiguredDomain();
|
|
510
|
+
const state = this.#computeScaleState(true);
|
|
511
|
+
if (!state) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
this.#applyReconfigure(state, (scale, props) =>
|
|
515
|
+
this.#scaleManager.reconfigureScale(props)
|
|
516
|
+
);
|
|
517
|
+
this.#finalizeReconfigure(state);
|
|
364
518
|
}
|
|
365
519
|
|
|
366
520
|
/**
|
|
367
521
|
* Reconfigures only the effective domain (configured + data-derived).
|
|
368
522
|
*
|
|
369
523
|
* Use this when data changes but the scale membership and properties are stable.
|
|
524
|
+
*
|
|
370
525
|
*/
|
|
371
526
|
reconfigureDomain() {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
527
|
+
const state = this.#computeScaleState(true, true);
|
|
528
|
+
if (!state) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const { domainConfig, targetDomain } = state;
|
|
532
|
+
const domainMatches =
|
|
533
|
+
targetDomain != null &&
|
|
534
|
+
shallowArrayEquals(targetDomain, state.scale.domain());
|
|
535
|
+
|
|
536
|
+
if (targetDomain != null && !domainMatches) {
|
|
537
|
+
this.#applyReconfigure(state, (scale) => {
|
|
538
|
+
scale.domain(targetDomain);
|
|
539
|
+
if (domainConfig.applyOrdinalUnknown) {
|
|
540
|
+
// Keep ordinal unknown handling close to the domain write so
|
|
541
|
+
// domainImplicit semantics stay aligned with the applied domain.
|
|
542
|
+
/** @type {any} */ (scale).unknown(
|
|
543
|
+
domainConfig.ordinalUnknown
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
this.#finalizeReconfigure(state);
|
|
376
549
|
}
|
|
377
550
|
|
|
378
551
|
/**
|
|
379
|
-
* @param {
|
|
552
|
+
* @param {boolean} extractDataDomain
|
|
553
|
+
* @param {boolean} [includeDomainConfig]
|
|
554
|
+
* @returns {{
|
|
555
|
+
* scale: ScaleWithProps,
|
|
556
|
+
* props: import("../spec/scale.js").Scale,
|
|
557
|
+
* previousDomain: any[],
|
|
558
|
+
* domainWasInitialized: boolean,
|
|
559
|
+
* domainConfig?: ReturnType<typeof configureDomain>,
|
|
560
|
+
* targetDomain?: any[] | null,
|
|
561
|
+
* } | undefined}
|
|
380
562
|
*/
|
|
381
|
-
#
|
|
563
|
+
#computeScaleState(extractDataDomain, includeDomainConfig = false) {
|
|
382
564
|
const scale = this.#scaleManager.scale;
|
|
383
565
|
|
|
384
566
|
if (!scale || scale.type == "null") {
|
|
385
567
|
return;
|
|
386
568
|
}
|
|
387
569
|
|
|
388
|
-
const
|
|
389
|
-
|
|
570
|
+
const state = {
|
|
571
|
+
scale,
|
|
572
|
+
props: this.#getScaleProps(extractDataDomain),
|
|
573
|
+
previousDomain: scale.domain(),
|
|
574
|
+
domainWasInitialized: this.#isDomainInitialized(),
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
if (includeDomainConfig) {
|
|
578
|
+
const domainConfig = configureDomain(scale, state.props);
|
|
579
|
+
return {
|
|
580
|
+
...state,
|
|
581
|
+
domainConfig,
|
|
582
|
+
targetDomain: domainConfig.domain,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return state;
|
|
587
|
+
}
|
|
390
588
|
|
|
391
|
-
|
|
589
|
+
/**
|
|
590
|
+
* @param {{
|
|
591
|
+
* scale: ScaleWithProps,
|
|
592
|
+
* props: import("../spec/scale.js").Scale,
|
|
593
|
+
* }} inputs
|
|
594
|
+
* @param {(scale: ScaleWithProps, props: import("../spec/scale.js").Scale) => void} apply
|
|
595
|
+
*/
|
|
596
|
+
#applyReconfigure(inputs, apply) {
|
|
597
|
+
this.#scaleManager.withDomainNotificationsSuppressed(() => {
|
|
598
|
+
apply(inputs.scale, inputs.props);
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* @param {{
|
|
604
|
+
* scale: ScaleWithProps,
|
|
605
|
+
* previousDomain: any[],
|
|
606
|
+
* domainWasInitialized: boolean,
|
|
607
|
+
* }} inputs
|
|
608
|
+
*/
|
|
609
|
+
#finalizeReconfigure(inputs) {
|
|
610
|
+
const { scale, previousDomain, domainWasInitialized } = inputs;
|
|
392
611
|
|
|
393
612
|
if (
|
|
394
613
|
this.#domainAggregator.captureInitialDomain(
|
|
@@ -402,13 +621,18 @@ export default class ScaleResolution {
|
|
|
402
621
|
}
|
|
403
622
|
|
|
404
623
|
const newDomain = scale.domain();
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
624
|
+
const action = this.#interactionController.getDomainChangeAction(
|
|
625
|
+
previousDomain,
|
|
626
|
+
newDomain
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
if (action === "restore") {
|
|
630
|
+
// Don't mess with zoomed views, restore the previous domain
|
|
631
|
+
this.#scaleManager.withDomainNotificationsSuppressed(() => {
|
|
632
|
+
scale.domain(previousDomain);
|
|
633
|
+
});
|
|
634
|
+
} else if (action === "animate") {
|
|
635
|
+
if (this.#hasRenderedMember()) {
|
|
412
636
|
// It can be zoomed, so lets make a smooth transition.
|
|
413
637
|
// Restore the previous domain and zoom smoothly to the new domain.
|
|
414
638
|
this.#scaleManager.withDomainNotificationsSuppressed(() => {
|
|
@@ -416,10 +640,12 @@ export default class ScaleResolution {
|
|
|
416
640
|
});
|
|
417
641
|
this.zoomTo(newDomain, 500); // TODO: Configurable duration
|
|
418
642
|
} else {
|
|
419
|
-
// Update immediately if the previous domain was the initial domain [0, 0]
|
|
420
|
-
// Notifications were suppressed during reconfigure; notify explicitly.
|
|
421
643
|
this.#notifyListeners("domain");
|
|
422
644
|
}
|
|
645
|
+
} else if (action === "notify") {
|
|
646
|
+
// Update immediately if the previous domain was the initial domain [0, 0]
|
|
647
|
+
// Notifications were suppressed during reconfigure; notify explicitly.
|
|
648
|
+
this.#notifyListeners("domain");
|
|
423
649
|
}
|
|
424
650
|
}
|
|
425
651
|
|
|
@@ -615,44 +841,3 @@ export default class ScaleResolution {
|
|
|
615
841
|
return /** @type {number[]} */ (interval);
|
|
616
842
|
}
|
|
617
843
|
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* Reconfigures scale domains, starting from the given view.
|
|
621
|
-
*
|
|
622
|
-
* Use this for data-driven updates where only domains need refreshing.
|
|
623
|
-
*
|
|
624
|
-
* TODO: This should be made unnecessary. Collectors should trigger the reconfiguration
|
|
625
|
-
* for those views that get their data from the collector.
|
|
626
|
-
*
|
|
627
|
-
* TODO: This may reconfigure channels that are not affected by the change.
|
|
628
|
-
* Causes performance issues with domains that are extracted from data.
|
|
629
|
-
*
|
|
630
|
-
* @param {import("../view/view.js").default | import("../view/view.js").default[]} fromViews
|
|
631
|
-
*/
|
|
632
|
-
export function reconfigureScaleDomains(fromViews) {
|
|
633
|
-
/** @type {Set<ScaleResolution>} */
|
|
634
|
-
const uniqueResolutions = new Set();
|
|
635
|
-
|
|
636
|
-
/** @param {import("../view/view.js").default} view */
|
|
637
|
-
function collectResolutions(view) {
|
|
638
|
-
for (const resolution of Object.values(view.resolutions.scale)) {
|
|
639
|
-
uniqueResolutions.add(resolution);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
for (const fromView of asArray(fromViews)) {
|
|
644
|
-
// Descendants
|
|
645
|
-
fromView.visit(collectResolutions);
|
|
646
|
-
|
|
647
|
-
// Ancestors
|
|
648
|
-
for (const view of fromView.getDataAncestors()) {
|
|
649
|
-
// Skip axis views etc. They should not mess with the domains.
|
|
650
|
-
if (!view.options.contributesToScaleDomain) {
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
collectResolutions(view);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
uniqueResolutions.forEach((resolution) => resolution.reconfigureDomain());
|
|
658
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaleResolution.test.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.test.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaleResolution.test.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.test.js"],"names":[],"mappings":"sBAWa,OAAO,oBAAoB,EAAE,OAAO"}
|
|
@@ -8,6 +8,27 @@ export function createSinglePointSelection(datum: import("../data/flowNode.js").
|
|
|
8
8
|
* @returns {import("../types/selectionTypes.js").MultiPointSelection}
|
|
9
9
|
*/
|
|
10
10
|
export function createMultiPointSelection(data?: import("../data/flowNode.js").Datum[]): import("../types/selectionTypes.js").MultiPointSelection;
|
|
11
|
+
/**
|
|
12
|
+
* Returns key tuples for a point selection.
|
|
13
|
+
*
|
|
14
|
+
* @param {import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection} selection
|
|
15
|
+
* @param {string[]} keyFields
|
|
16
|
+
* @returns {import("../spec/channel.js").Scalar[][] | undefined}
|
|
17
|
+
*/
|
|
18
|
+
export function getPointSelectionKeyTuples(selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, keyFields: string[]): import("../spec/channel.js").Scalar[][] | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Resolves key tuples to a point selection value object.
|
|
21
|
+
*
|
|
22
|
+
* @param {"single" | "multi"} type
|
|
23
|
+
* @param {string[]} keyFields
|
|
24
|
+
* @param {import("../spec/channel.js").Scalar[][]} keyTuples
|
|
25
|
+
* @param {(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined} resolveDatum
|
|
26
|
+
* @returns {{ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, unresolved: import("../spec/channel.js").Scalar[][] } | undefined}
|
|
27
|
+
*/
|
|
28
|
+
export function resolvePointSelectionFromKeyTuples(type: "single" | "multi", keyFields: string[], keyTuples: import("../spec/channel.js").Scalar[][], resolveDatum: (keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined): {
|
|
29
|
+
selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection;
|
|
30
|
+
unresolved: import("../spec/channel.js").Scalar[][];
|
|
31
|
+
} | undefined;
|
|
11
32
|
/**
|
|
12
33
|
*
|
|
13
34
|
* @param {import("../spec/channel.js").ChannelWithScale[]} channels
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection/selection.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection/selection.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,kDAHW,OAAO,qBAAqB,EAAE,KAAK,GACjC,OAAO,4BAA4B,EAAE,oBAAoB,CAQrE;AAED;;;GAGG;AACH,iDAHW,OAAO,qBAAqB,EAAE,KAAK,EAAE,GACnC,OAAO,4BAA4B,EAAE,mBAAmB,CAQpE;AAED;;;;;;GAMG;AACH,sDAJW,OAAO,4BAA4B,EAAE,oBAAoB,GAAG,OAAO,4BAA4B,EAAE,mBAAmB,aACpH,MAAM,EAAE,GACN,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CA2B/D;AAED;;;;;;;;GAQG;AACH,yDANW,QAAQ,GAAG,OAAO,aAClB,MAAM,EAAE,aACR,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,gBACvC,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,oBAAoB,EAAE,MAAM,EAAE,KAAK,OAAO,qBAAqB,EAAE,KAAK,GAAG,SAAS,GACvH;IAAE,SAAS,EAAE,OAAO,4BAA4B,EAAE,oBAAoB,GAAG,OAAO,4BAA4B,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,GAAG,SAAS,CAsChN;AAED;;;;GAIG;AACH,kDAHW,OAAO,oBAAoB,EAAE,gBAAgB,EAAE,GAC7C,OAAO,4BAA4B,EAAE,iBAAiB,CASlE;AAED;;;;;;;GAOG;AACH,qDAJW,OAAO,4BAA4B,EAAE,mBAAmB,2BACxD,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG,QAAQ,EAAE,QAAQ,CAAC,OAAO,qBAAqB,EAAE,KAAK,CAAC,CAAC,CAAC,GACzF,OAAO,4BAA4B,EAAE,mBAAmB,CA2BpE;AAED;;;;;GAKG;AACH,oDAHW,OAAO,sBAAsB,EAAE,qBAAqB,aACpD,OAAO,4BAA4B,EAAE,SAAS,UAgExD;AAED;;;GAGG;AACH,+CAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,iBAAiB,CAI/E;AAED;;;GAGG;AACH,kDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,oBAAoB,CAIlF;AAED;;;GAGG;AACH,iDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,mBAAmB,CAIjF;AAED;;;GAGG;AACH,gDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,kBAAkB,CAIhF;AAED;;;GAGG;AACH,gDAHW,OAAO,sBAAsB,EAAE,qBAAqB,GAClD,OAAO,sBAAsB,EAAE,eAAe,CA4B1D;AAED;;;GAGG;AACH,+CAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,oBAAoB,CAIzE;AAED;;;;GAIG;AACH,kDAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,uBAAuB,CAI5E;AAED;;GAEG;AACH,qDAFW,OAAO,4BAA4B,EAAE,iBAAiB,WAMhE;AAED;;;;;GAKG;AACH,kDAHW,iBAAiB,SACjB,aAAa,WAUvB;AAED;;;GAGG;AACH,yCAHW,OAAO,sBAAsB,EAAE,eAAe,CAAC,IAAI,CAAC,GAClD,OAAO,sBAAsB,EAAE,WAAW,CAsBtD;gCAvCY,OAAO,4BAA4B,EAAE,iBAAiB;4BACtD,OAAO,CAAC,MAAM,CAAC,MAAM,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC"}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isPrimaryPositionalChannel,
|
|
5
5
|
} from "../encoder/encoder.js";
|
|
6
6
|
import { validateParameterName } from "../view/paramMediator.js";
|
|
7
|
+
import { field } from "../utils/field.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @param {import("../data/flowNode.js").Datum} datum
|
|
@@ -29,6 +30,87 @@ export function createMultiPointSelection(data) {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Returns key tuples for a point selection.
|
|
35
|
+
*
|
|
36
|
+
* @param {import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection} selection
|
|
37
|
+
* @param {string[]} keyFields
|
|
38
|
+
* @returns {import("../spec/channel.js").Scalar[][] | undefined}
|
|
39
|
+
*/
|
|
40
|
+
export function getPointSelectionKeyTuples(selection, keyFields) {
|
|
41
|
+
if (!keyFields || keyFields.length === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const accessors = keyFields.map((fieldName) => field(fieldName));
|
|
46
|
+
const toTuple = (
|
|
47
|
+
/** @type {import("../data/flowNode.js").Datum} */ datum
|
|
48
|
+
) => accessors.map((accessor) => accessor(datum));
|
|
49
|
+
|
|
50
|
+
if (isSinglePointSelection(selection)) {
|
|
51
|
+
if (!selection.datum) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [toTuple(selection.datum)];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isMultiPointSelection(selection)) {
|
|
59
|
+
return [...selection.data.values()].map(toTuple);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Expected a point selection, got: ${JSON.stringify(selection)}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolves key tuples to a point selection value object.
|
|
69
|
+
*
|
|
70
|
+
* @param {"single" | "multi"} type
|
|
71
|
+
* @param {string[]} keyFields
|
|
72
|
+
* @param {import("../spec/channel.js").Scalar[][]} keyTuples
|
|
73
|
+
* @param {(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined} resolveDatum
|
|
74
|
+
* @returns {{ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, unresolved: import("../spec/channel.js").Scalar[][] } | undefined}
|
|
75
|
+
*/
|
|
76
|
+
export function resolvePointSelectionFromKeyTuples(
|
|
77
|
+
type,
|
|
78
|
+
keyFields,
|
|
79
|
+
keyTuples,
|
|
80
|
+
resolveDatum
|
|
81
|
+
) {
|
|
82
|
+
if (!keyFields || keyFields.length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (type === "single" && keyTuples.length > 1) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Single point selections expect at most one key tuple."
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @type {import("../data/flowNode.js").Datum[]} */
|
|
93
|
+
const datums = [];
|
|
94
|
+
/** @type {import("../spec/channel.js").Scalar[][]} */
|
|
95
|
+
const unresolved = [];
|
|
96
|
+
|
|
97
|
+
for (const tuple of keyTuples) {
|
|
98
|
+
const datum = resolveDatum(keyFields, tuple);
|
|
99
|
+
if (datum) {
|
|
100
|
+
datums.push(datum);
|
|
101
|
+
} else {
|
|
102
|
+
unresolved.push(tuple);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const selection =
|
|
107
|
+
type === "single"
|
|
108
|
+
? createSinglePointSelection(datums[0] ?? null)
|
|
109
|
+
: createMultiPointSelection(datums);
|
|
110
|
+
|
|
111
|
+
return { selection, unresolved };
|
|
112
|
+
}
|
|
113
|
+
|
|
32
114
|
/**
|
|
33
115
|
*
|
|
34
116
|
* @param {import("../spec/channel.js").ChannelWithScale[]} channels
|