@genome-spy/core 0.72.0 → 0.73.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/LICENSE +1 -1
- package/dist/bundle/index.es.js +6779 -5393
- package/dist/bundle/index.js +133 -121
- package/dist/schema.json +281 -17
- package/dist/src/data/formats/bed.d.ts +8 -0
- package/dist/src/data/formats/bed.d.ts.map +1 -0
- package/dist/src/data/formats/bed.js +53 -0
- package/dist/src/data/formats/bedpe.d.ts +8 -0
- package/dist/src/data/formats/bedpe.d.ts.map +1 -0
- package/dist/src/data/formats/bedpe.js +160 -0
- package/dist/src/data/sources/dataUtils.d.ts +16 -0
- package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
- package/dist/src/data/sources/dataUtils.js +53 -3
- package/dist/src/data/sources/urlSource.d.ts +4 -0
- package/dist/src/data/sources/urlSource.d.ts.map +1 -1
- package/dist/src/data/sources/urlSource.js +133 -14
- package/dist/src/genome/assemblyPreflight.d.ts +31 -0
- package/dist/src/genome/assemblyPreflight.d.ts.map +1 -0
- package/dist/src/genome/assemblyPreflight.js +99 -0
- package/dist/src/genome/genome.d.ts +2 -2
- package/dist/src/genome/genome.d.ts.map +1 -1
- package/dist/src/genome/genome.js +4 -0
- package/dist/src/genome/genomeStore.d.ts +34 -3
- package/dist/src/genome/genomeStore.d.ts.map +1 -1
- package/dist/src/genome/genomeStore.js +409 -18
- package/dist/src/genome/rootGenomeConfig.d.ts +26 -0
- package/dist/src/genome/rootGenomeConfig.d.ts.map +1 -0
- package/dist/src/genome/rootGenomeConfig.js +94 -0
- package/dist/src/genomeSpy/interactionController.d.ts +5 -1
- package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
- package/dist/src/genomeSpy/interactionController.js +244 -29
- package/dist/src/genomeSpy/renderCoordinator.js +1 -1
- package/dist/src/genomeSpy.d.ts +13 -3
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +81 -7
- package/dist/src/gl/canvasSizeHelper.d.ts +74 -0
- package/dist/src/gl/canvasSizeHelper.d.ts.map +1 -0
- package/dist/src/gl/canvasSizeHelper.js +203 -0
- package/dist/src/gl/webGLHelper.d.ts +25 -11
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +59 -33
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -2
- package/dist/src/marks/link.d.ts.map +1 -1
- package/dist/src/marks/link.js +5 -3
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +6 -1
- package/dist/src/scales/domainPlanner.d.ts +34 -3
- package/dist/src/scales/domainPlanner.d.ts.map +1 -1
- package/dist/src/scales/domainPlanner.js +247 -26
- package/dist/src/scales/scaleInstanceManager.d.ts +2 -1
- package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
- package/dist/src/scales/scaleInstanceManager.js +10 -11
- package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
- package/dist/src/scales/scaleInteractionController.js +16 -14
- package/dist/src/scales/scaleResolution.d.ts +16 -0
- package/dist/src/scales/scaleResolution.d.ts.map +1 -1
- package/dist/src/scales/scaleResolution.js +314 -54
- package/dist/src/scales/scaleResolutionTestUtils.d.ts +21 -0
- package/dist/src/scales/scaleResolutionTestUtils.d.ts.map +1 -0
- package/dist/src/scales/scaleResolutionTestUtils.js +33 -0
- package/dist/src/scales/selectionDomainUtils.d.ts +22 -0
- package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -0
- package/dist/src/scales/selectionDomainUtils.js +79 -0
- package/dist/src/scales/zoomDomainUtils.d.ts +18 -0
- package/dist/src/scales/zoomDomainUtils.d.ts.map +1 -0
- package/dist/src/scales/zoomDomainUtils.js +69 -0
- package/dist/src/screenshotHarness.d.ts +16 -0
- package/dist/src/screenshotHarness.d.ts.map +1 -0
- package/dist/src/screenshotHarness.js +242 -0
- package/dist/src/singlePageApp.js +1 -1
- package/dist/src/spec/data.d.ts +23 -3
- package/dist/src/spec/genome.d.ts +22 -2
- package/dist/src/spec/parameter.d.ts +39 -2
- package/dist/src/spec/root.d.ts +20 -1
- package/dist/src/spec/scale.d.ts +41 -5
- package/dist/src/styles/genome-spy.css +8 -0
- 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 +8 -0
- package/dist/src/tooltip/dataTooltipHandler.js +59 -10
- package/dist/src/types/embedApi.d.ts +19 -0
- package/dist/src/utils/inferSpecBaseUrl.d.ts +14 -0
- package/dist/src/utils/inferSpecBaseUrl.d.ts.map +1 -0
- package/dist/src/utils/inferSpecBaseUrl.js +73 -0
- package/dist/src/utils/interactionEvent.d.ts +53 -3
- package/dist/src/utils/interactionEvent.d.ts.map +1 -1
- package/dist/src/utils/interactionEvent.js +62 -1
- package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
- package/dist/src/view/containerMutationHelper.js +8 -0
- package/dist/src/view/dataReadiness.d.ts +2 -2
- package/dist/src/view/dataReadiness.d.ts.map +1 -1
- package/dist/src/view/dataReadiness.js +63 -58
- package/dist/src/view/facetView.js +1 -1
- package/dist/src/view/gridView/gridChild.d.ts +7 -0
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +180 -11
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +60 -17
- package/dist/src/view/zoom.d.ts +14 -2
- package/dist/src/view/zoom.d.ts.map +1 -1
- package/dist/src/view/zoom.js +373 -76
- package/package.json +4 -2
|
@@ -24,10 +24,19 @@ import {
|
|
|
24
24
|
} from "./scaleResolutionConstants.js";
|
|
25
25
|
|
|
26
26
|
import { getAccessorDomainKey } from "../encoder/accessor.js";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
isPrimaryPositionalChannel,
|
|
29
|
+
isSecondaryChannel,
|
|
30
|
+
} from "../encoder/encoder.js";
|
|
28
31
|
import { NominalDomain } from "../utils/domainArray.js";
|
|
29
32
|
import { shallowArrayEquals } from "../utils/arrayUtils.js";
|
|
30
33
|
import createIndexer from "../utils/indexer.js";
|
|
34
|
+
import { resolveUrl } from "../utils/url.js";
|
|
35
|
+
import {
|
|
36
|
+
normalizeIntervalForSelection,
|
|
37
|
+
requireIntervalSelection,
|
|
38
|
+
requireParamRuntime,
|
|
39
|
+
} from "./selectionDomainUtils.js";
|
|
31
40
|
|
|
32
41
|
// Register scaleLocus to Vega-Scale.
|
|
33
42
|
// Loci are discrete but the scale's domain can be adjusted in a continuous manner.
|
|
@@ -111,6 +120,11 @@ export default class ScaleResolution {
|
|
|
111
120
|
|
|
112
121
|
#categoricalIndexerExplicit = false;
|
|
113
122
|
|
|
123
|
+
/** @type {(() => void)[]} */
|
|
124
|
+
#selectionDomainParamUnsubscribers = [];
|
|
125
|
+
|
|
126
|
+
#selectionReverseSyncSuppressionDepth = 0;
|
|
127
|
+
|
|
114
128
|
/**
|
|
115
129
|
* @param {Channel} channel
|
|
116
130
|
*/
|
|
@@ -127,7 +141,7 @@ export default class ScaleResolution {
|
|
|
127
141
|
getDataMembers: () =>
|
|
128
142
|
this.#getActiveMembers(this.#dataDomainMembers),
|
|
129
143
|
getType: () => this.type,
|
|
130
|
-
getLocusExtent: () => this.#getLocusExtent(),
|
|
144
|
+
getLocusExtent: (assembly) => this.#getLocusExtent(assembly),
|
|
131
145
|
fromComplexInterval: this.fromComplexInterval.bind(this),
|
|
132
146
|
});
|
|
133
147
|
|
|
@@ -200,21 +214,24 @@ export default class ScaleResolution {
|
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
/**
|
|
217
|
+
* @param {import("../spec/scale.js").Scale["assembly"]} [assembly]
|
|
203
218
|
* @returns {number[]}
|
|
204
219
|
*/
|
|
205
|
-
#getLocusExtent() {
|
|
206
|
-
return getGenomeExtent(this.#getGenomeSource());
|
|
220
|
+
#getLocusExtent(assembly) {
|
|
221
|
+
return getGenomeExtent(this.#getGenomeSource(assembly));
|
|
207
222
|
}
|
|
208
223
|
|
|
209
224
|
/**
|
|
225
|
+
* @param {import("../spec/scale.js").Scale["assembly"]} [assembly]
|
|
210
226
|
* @returns {import("../genome/scaleLocus.js").GenomeSource}
|
|
211
227
|
*/
|
|
212
|
-
#getGenomeSource() {
|
|
228
|
+
#getGenomeSource(assembly) {
|
|
213
229
|
if (this.type !== LOCUS) {
|
|
214
230
|
return undefined;
|
|
215
231
|
}
|
|
216
232
|
return /** @type {import("../genome/scaleLocus.js").GenomeSource} */ (
|
|
217
|
-
this.#scaleManager.scale ??
|
|
233
|
+
this.#scaleManager.scale ??
|
|
234
|
+
this.#scaleManager.getLocusGenome(assembly)
|
|
218
235
|
);
|
|
219
236
|
}
|
|
220
237
|
|
|
@@ -242,6 +259,13 @@ export default class ScaleResolution {
|
|
|
242
259
|
* @param {ScaleResolutionEventType} type
|
|
243
260
|
*/
|
|
244
261
|
#notifyListeners(type) {
|
|
262
|
+
if (
|
|
263
|
+
type === "domain" &&
|
|
264
|
+
this.#selectionReverseSyncSuppressionDepth === 0
|
|
265
|
+
) {
|
|
266
|
+
this.#syncLinkedSelectionFromDomain();
|
|
267
|
+
}
|
|
268
|
+
|
|
245
269
|
for (const listener of this.#listeners[type].values()) {
|
|
246
270
|
listener({
|
|
247
271
|
type,
|
|
@@ -250,14 +274,92 @@ export default class ScaleResolution {
|
|
|
250
274
|
}
|
|
251
275
|
}
|
|
252
276
|
|
|
277
|
+
/**
|
|
278
|
+
* @param {() => void} callback
|
|
279
|
+
*/
|
|
280
|
+
#withSelectionReverseSyncSuppressed(callback) {
|
|
281
|
+
this.#selectionReverseSyncSuppressionDepth += 1;
|
|
282
|
+
try {
|
|
283
|
+
callback();
|
|
284
|
+
} finally {
|
|
285
|
+
this.#selectionReverseSyncSuppressionDepth -= 1;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#syncLinkedSelectionFromDomain() {
|
|
290
|
+
const linkInfo =
|
|
291
|
+
this.#domainAggregator.getSelectionConfiguredDomainInfo();
|
|
292
|
+
if (!linkInfo) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const shouldReverseSync =
|
|
297
|
+
linkInfo.sync === "twoWay" ||
|
|
298
|
+
(linkInfo.sync === "auto" && this.isZoomable());
|
|
299
|
+
if (!shouldReverseSync) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const runtime = requireParamRuntime(
|
|
304
|
+
this.#firstMemberView.paramRuntime,
|
|
305
|
+
linkInfo.param
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const selection = requireIntervalSelection(
|
|
309
|
+
runtime.getValue(linkInfo.param),
|
|
310
|
+
linkInfo.param
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const interval = this.#normalizeDomainIntervalForLinkedSelection(
|
|
314
|
+
this.getScale().domain()
|
|
315
|
+
);
|
|
316
|
+
if (!interval) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const fallbackInterval =
|
|
321
|
+
this.#normalizeDomainIntervalForLinkedSelection(
|
|
322
|
+
this.#domainAggregator.getDefaultDomain(true)
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const syncedInterval =
|
|
326
|
+
fallbackInterval && shallowArrayEquals(interval, fallbackInterval)
|
|
327
|
+
? null
|
|
328
|
+
: interval;
|
|
329
|
+
|
|
330
|
+
const previousInterval = selection.intervals[linkInfo.encoding] ?? null;
|
|
331
|
+
if (intervalsEqual(previousInterval, syncedInterval)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
runtime.setValue(linkInfo.param, {
|
|
336
|
+
...selection,
|
|
337
|
+
type: "interval",
|
|
338
|
+
intervals: {
|
|
339
|
+
...selection.intervals,
|
|
340
|
+
[linkInfo.encoding]: syncedInterval,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @param {any[]} domain
|
|
347
|
+
* @returns {[number, number] | undefined}
|
|
348
|
+
*/
|
|
349
|
+
#normalizeDomainIntervalForLinkedSelection(domain) {
|
|
350
|
+
return normalizeIntervalForSelection(domain, this.zoomExtent);
|
|
351
|
+
}
|
|
352
|
+
|
|
253
353
|
/**
|
|
254
354
|
* Add a view to this resolution.
|
|
255
355
|
* N.B. This is expected to be called in depth-first order
|
|
256
356
|
*
|
|
257
357
|
* @param {ScaleResolutionMember} newMember
|
|
358
|
+
* @returns {ScaleResolutionMember}
|
|
258
359
|
*/
|
|
259
360
|
#addMember(newMember) {
|
|
260
|
-
const
|
|
361
|
+
const member = normalizeMember(newMember);
|
|
362
|
+
const { channel, channelDef } = member;
|
|
261
363
|
|
|
262
364
|
// A convenience hack for cases where the new member should adapt
|
|
263
365
|
// the scale type to the existing one. For example: SelectionRect
|
|
@@ -283,6 +385,20 @@ export default class ScaleResolution {
|
|
|
283
385
|
// @ts-expect-error "sample" is not really a channel with scale
|
|
284
386
|
const type = channel == "sample" ? "nominal" : channelDef.type;
|
|
285
387
|
const name = channelDef?.scale?.name;
|
|
388
|
+
const explicitScaleType = channelDef.scale?.type;
|
|
389
|
+
const effectiveScaleType =
|
|
390
|
+
explicitScaleType ??
|
|
391
|
+
(type === INDEX || type === LOCUS ? type : undefined);
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
effectiveScaleType &&
|
|
395
|
+
[INDEX, LOCUS].includes(effectiveScaleType) &&
|
|
396
|
+
!isPrimaryPositionalChannel(this.channel)
|
|
397
|
+
) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Index and locus scales are only supported on positional channels (x/y). Channel "${this.channel}" resolves to scale type "${effectiveScaleType}".`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
286
402
|
|
|
287
403
|
if (name) {
|
|
288
404
|
if (this.name !== undefined && name != this.name) {
|
|
@@ -306,11 +422,13 @@ export default class ScaleResolution {
|
|
|
306
422
|
}
|
|
307
423
|
}
|
|
308
424
|
|
|
309
|
-
this.#members.add(
|
|
310
|
-
if (
|
|
311
|
-
this.#dataDomainMembers.add(
|
|
425
|
+
this.#members.add(member);
|
|
426
|
+
if (member.contributesToDomain) {
|
|
427
|
+
this.#dataDomainMembers.add(member);
|
|
312
428
|
}
|
|
313
429
|
this.#domainAggregator.invalidateConfiguredDomain();
|
|
430
|
+
this.#refreshSelectionDomainParamSubscriptions();
|
|
431
|
+
return member;
|
|
314
432
|
}
|
|
315
433
|
|
|
316
434
|
/**
|
|
@@ -318,23 +436,58 @@ export default class ScaleResolution {
|
|
|
318
436
|
* @returns {() => boolean}
|
|
319
437
|
*/
|
|
320
438
|
registerMember(member) {
|
|
321
|
-
this.#addMember(member);
|
|
439
|
+
const registeredMember = this.#addMember(member);
|
|
322
440
|
return () => {
|
|
323
|
-
const removed = this.#members.delete(
|
|
441
|
+
const removed = this.#members.delete(registeredMember);
|
|
324
442
|
if (removed) {
|
|
325
|
-
this.#dataDomainMembers.delete(
|
|
443
|
+
this.#dataDomainMembers.delete(registeredMember);
|
|
326
444
|
this.#domainAggregator.invalidateConfiguredDomain();
|
|
445
|
+
this.#refreshSelectionDomainParamSubscriptions();
|
|
327
446
|
}
|
|
328
447
|
return removed && this.#members.size === 0;
|
|
329
448
|
};
|
|
330
449
|
}
|
|
331
450
|
|
|
332
451
|
dispose() {
|
|
452
|
+
this.#clearSelectionDomainParamSubscriptions();
|
|
333
453
|
this.#listeners.domain.clear();
|
|
334
454
|
this.#listeners.range.clear();
|
|
335
455
|
this.#scaleManager.dispose();
|
|
336
456
|
}
|
|
337
457
|
|
|
458
|
+
#clearSelectionDomainParamSubscriptions() {
|
|
459
|
+
for (const unsubscribe of this.#selectionDomainParamUnsubscribers) {
|
|
460
|
+
unsubscribe();
|
|
461
|
+
}
|
|
462
|
+
this.#selectionDomainParamUnsubscribers = [];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
#refreshSelectionDomainParamSubscriptions() {
|
|
466
|
+
this.#clearSelectionDomainParamSubscriptions();
|
|
467
|
+
|
|
468
|
+
if (this.#members.size === 0) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const linkInfo =
|
|
473
|
+
this.#domainAggregator.getSelectionConfiguredDomainInfo();
|
|
474
|
+
if (!linkInfo) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const runtime = requireParamRuntime(
|
|
479
|
+
this.#firstMemberView.paramRuntime,
|
|
480
|
+
linkInfo.param
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
this.#selectionDomainParamUnsubscribers.push(
|
|
484
|
+
runtime.subscribe(linkInfo.param, () => {
|
|
485
|
+
this.#domainAggregator.invalidateConfiguredDomain();
|
|
486
|
+
this.reconfigureDomain();
|
|
487
|
+
})
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
338
491
|
#hasRenderedMember() {
|
|
339
492
|
for (const member of this.#members) {
|
|
340
493
|
if (member.view.hasRendered()) {
|
|
@@ -424,6 +577,33 @@ export default class ScaleResolution {
|
|
|
424
577
|
});
|
|
425
578
|
}
|
|
426
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Returns locus assembly requirements without initializing the scale.
|
|
582
|
+
*
|
|
583
|
+
* This is intentionally side-effect free: it only inspects merged scale
|
|
584
|
+
* properties from registered members and does not touch default domains or
|
|
585
|
+
* instantiate scale instances.
|
|
586
|
+
*
|
|
587
|
+
* @returns {{
|
|
588
|
+
* assembly: import("../spec/scale.js").Scale["assembly"] | undefined,
|
|
589
|
+
* needsDefaultAssembly: boolean
|
|
590
|
+
* }}
|
|
591
|
+
*/
|
|
592
|
+
getAssemblyRequirement() {
|
|
593
|
+
const props = this.#getMergedScaleProps();
|
|
594
|
+
if (props === null || props.type === "null" || props.type !== LOCUS) {
|
|
595
|
+
return {
|
|
596
|
+
assembly: undefined,
|
|
597
|
+
needsDefaultAssembly: false,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
assembly: props.assembly,
|
|
603
|
+
needsDefaultAssembly: props.assembly === undefined,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
427
607
|
/**
|
|
428
608
|
* Returns the merged scale properties supplemented with inferred properties
|
|
429
609
|
* and domain.
|
|
@@ -439,10 +619,10 @@ export default class ScaleResolution {
|
|
|
439
619
|
return { type: "null" };
|
|
440
620
|
}
|
|
441
621
|
|
|
442
|
-
const domain =
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
622
|
+
const domain = this.#domainAggregator.getConfiguredOrDefaultDomain(
|
|
623
|
+
extractDataDomain,
|
|
624
|
+
props.type === LOCUS ? props.assembly : undefined
|
|
625
|
+
);
|
|
446
626
|
|
|
447
627
|
if (isDiscrete(props.type)) {
|
|
448
628
|
const isExplicit = this.#isExplicitDomain();
|
|
@@ -512,15 +692,17 @@ export default class ScaleResolution {
|
|
|
512
692
|
* or when scale properties are otherwise re-resolved from the view hierarchy.
|
|
513
693
|
*/
|
|
514
694
|
reconfigure() {
|
|
515
|
-
this.#
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
this.#
|
|
522
|
-
|
|
523
|
-
|
|
695
|
+
this.#withSelectionReverseSyncSuppressed(() => {
|
|
696
|
+
this.#domainAggregator.invalidateConfiguredDomain();
|
|
697
|
+
const state = this.#computeScaleState(true);
|
|
698
|
+
if (!state) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
this.#applyReconfigure(state, (scale, props) =>
|
|
702
|
+
this.#scaleManager.reconfigureScale(props)
|
|
703
|
+
);
|
|
704
|
+
this.#finalizeReconfigure(state);
|
|
705
|
+
});
|
|
524
706
|
}
|
|
525
707
|
|
|
526
708
|
/**
|
|
@@ -530,28 +712,30 @@ export default class ScaleResolution {
|
|
|
530
712
|
*
|
|
531
713
|
*/
|
|
532
714
|
reconfigureDomain() {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
scale
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
715
|
+
this.#withSelectionReverseSyncSuppressed(() => {
|
|
716
|
+
const state = this.#computeScaleState(true, true);
|
|
717
|
+
if (!state) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const { domainConfig, targetDomain } = state;
|
|
721
|
+
const domainMatches =
|
|
722
|
+
targetDomain != null &&
|
|
723
|
+
shallowArrayEquals(targetDomain, state.scale.domain());
|
|
724
|
+
|
|
725
|
+
if (targetDomain != null && !domainMatches) {
|
|
726
|
+
this.#applyReconfigure(state, (scale) => {
|
|
727
|
+
scale.domain(targetDomain);
|
|
728
|
+
if (domainConfig.applyOrdinalUnknown) {
|
|
729
|
+
// Keep ordinal unknown handling close to the domain write so
|
|
730
|
+
// domainImplicit semantics stay aligned with the applied domain.
|
|
731
|
+
/** @type {any} */ (scale).unknown(
|
|
732
|
+
domainConfig.ordinalUnknown
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
this.#finalizeReconfigure(state);
|
|
738
|
+
});
|
|
555
739
|
}
|
|
556
740
|
|
|
557
741
|
/**
|
|
@@ -562,6 +746,7 @@ export default class ScaleResolution {
|
|
|
562
746
|
* props: import("../spec/scale.js").Scale,
|
|
563
747
|
* previousDomain: any[],
|
|
564
748
|
* domainWasInitialized: boolean,
|
|
749
|
+
* hasSelectionConfiguredDomain: boolean,
|
|
565
750
|
* domainConfig?: ReturnType<typeof configureDomain>,
|
|
566
751
|
* targetDomain?: any[] | null,
|
|
567
752
|
* } | undefined}
|
|
@@ -578,6 +763,8 @@ export default class ScaleResolution {
|
|
|
578
763
|
props: this.#getScaleProps(extractDataDomain),
|
|
579
764
|
previousDomain: scale.domain(),
|
|
580
765
|
domainWasInitialized: this.#isDomainInitialized(),
|
|
766
|
+
hasSelectionConfiguredDomain:
|
|
767
|
+
this.#domainAggregator.hasSelectionConfiguredDomain(),
|
|
581
768
|
};
|
|
582
769
|
|
|
583
770
|
if (includeDomainConfig) {
|
|
@@ -610,10 +797,16 @@ export default class ScaleResolution {
|
|
|
610
797
|
* scale: ScaleWithProps,
|
|
611
798
|
* previousDomain: any[],
|
|
612
799
|
* domainWasInitialized: boolean,
|
|
800
|
+
* hasSelectionConfiguredDomain: boolean,
|
|
613
801
|
* }} inputs
|
|
614
802
|
*/
|
|
615
803
|
#finalizeReconfigure(inputs) {
|
|
616
|
-
const {
|
|
804
|
+
const {
|
|
805
|
+
scale,
|
|
806
|
+
previousDomain,
|
|
807
|
+
domainWasInitialized,
|
|
808
|
+
hasSelectionConfiguredDomain,
|
|
809
|
+
} = inputs;
|
|
617
810
|
|
|
618
811
|
if (
|
|
619
812
|
this.#domainAggregator.captureInitialDomain(
|
|
@@ -633,12 +826,22 @@ export default class ScaleResolution {
|
|
|
633
826
|
);
|
|
634
827
|
|
|
635
828
|
if (action === "restore") {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
829
|
+
if (hasSelectionConfiguredDomain) {
|
|
830
|
+
// Selection-linked domains are the source of truth and must not
|
|
831
|
+
// be overridden by previously zoomed domains.
|
|
832
|
+
this.#notifyListeners("domain");
|
|
833
|
+
} else {
|
|
834
|
+
// Don't mess with zoomed views, restore the previous domain
|
|
835
|
+
this.#scaleManager.withDomainNotificationsSuppressed(() => {
|
|
836
|
+
scale.domain(previousDomain);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
640
839
|
} else if (action === "animate") {
|
|
641
|
-
if (
|
|
840
|
+
if (hasSelectionConfiguredDomain) {
|
|
841
|
+
// Linked domains can update continuously (e.g., brushing), so
|
|
842
|
+
// skip zoomTo transitions and apply domain updates directly.
|
|
843
|
+
this.#notifyListeners("domain");
|
|
844
|
+
} else if (this.#hasRenderedMember()) {
|
|
642
845
|
// It can be zoomed, so lets make a smooth transition.
|
|
643
846
|
// Restore the previous domain and zoom smoothly to the new domain.
|
|
644
847
|
this.#scaleManager.withDomainNotificationsSuppressed(() => {
|
|
@@ -847,3 +1050,60 @@ export default class ScaleResolution {
|
|
|
847
1050
|
return /** @type {number[]} */ (interval);
|
|
848
1051
|
}
|
|
849
1052
|
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* @param {number[] | null} a
|
|
1056
|
+
* @param {number[] | null} b
|
|
1057
|
+
* @returns {boolean}
|
|
1058
|
+
*/
|
|
1059
|
+
function intervalsEqual(a, b) {
|
|
1060
|
+
if (a === b) {
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (!a || !b) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
return a.length === b.length && shallowArrayEquals(a, b);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Normalizes member-specific scale URLs so that inline `scale.assembly.url`
|
|
1073
|
+
* values resolve against the member view's base URL before scale props are
|
|
1074
|
+
* merged.
|
|
1075
|
+
*
|
|
1076
|
+
* @template {ChannelWithScale}[T=ChannelWithScale]
|
|
1077
|
+
* @param {ScaleResolutionMember<T>} member
|
|
1078
|
+
* @returns {ScaleResolutionMember<T>}
|
|
1079
|
+
*/
|
|
1080
|
+
function normalizeMember(member) {
|
|
1081
|
+
const scale = member.channelDef.scale;
|
|
1082
|
+
const assembly = scale?.assembly;
|
|
1083
|
+
if (!scale || !assembly || typeof assembly !== "object") {
|
|
1084
|
+
return member;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
if (!("url" in assembly)) {
|
|
1088
|
+
return member;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const resolvedUrl = resolveUrl(member.view.getBaseUrl(), assembly.url);
|
|
1092
|
+
if (resolvedUrl === assembly.url) {
|
|
1093
|
+
return member;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
return {
|
|
1097
|
+
...member,
|
|
1098
|
+
channelDef: {
|
|
1099
|
+
...member.channelDef,
|
|
1100
|
+
scale: {
|
|
1101
|
+
...scale,
|
|
1102
|
+
assembly: {
|
|
1103
|
+
...assembly,
|
|
1104
|
+
url: resolvedUrl,
|
|
1105
|
+
},
|
|
1106
|
+
},
|
|
1107
|
+
},
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import("../spec/view.js").ViewSpec} spec
|
|
3
|
+
* @param {{ new(...args: any[]): import("../view/view.js").default }} [viewType]
|
|
4
|
+
* @returns {Promise<import("../view/view.js").default>}
|
|
5
|
+
*/
|
|
6
|
+
export function initView(spec: import("../spec/view.js").ViewSpec, viewType?: {
|
|
7
|
+
new (...args: any[]): import("../view/view.js").default;
|
|
8
|
+
}): Promise<import("../view/view.js").default>;
|
|
9
|
+
/**
|
|
10
|
+
* @param {import("../view/view.js").default} view
|
|
11
|
+
* @param {import("../spec/channel.js").ChannelWithScale} channel
|
|
12
|
+
* @returns {import("./scaleResolution.js").default}
|
|
13
|
+
*/
|
|
14
|
+
export function getRequiredScaleResolution(view: import("../view/view.js").default, channel: import("../spec/channel.js").ChannelWithScale): import("./scaleResolution.js").default;
|
|
15
|
+
/**
|
|
16
|
+
* @param {import("../view/view.js").default} view
|
|
17
|
+
* @param {import("../spec/channel.js").ChannelWithScale} channel
|
|
18
|
+
* @returns {any[]}
|
|
19
|
+
*/
|
|
20
|
+
export function getScaleDomain(view: import("../view/view.js").default, channel: import("../spec/channel.js").ChannelWithScale): any[];
|
|
21
|
+
//# sourceMappingURL=scaleResolutionTestUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaleResolutionTestUtils.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolutionTestUtils.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,+BAJW,OAAO,iBAAiB,EAAE,QAAQ,aAClC;IAAE,KAAI,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,iBAAiB,EAAE,OAAO,CAAA;CAAE,GACxD,OAAO,CAAC,OAAO,iBAAiB,EAAE,OAAO,CAAC,CAItD;AAED;;;;GAIG;AACH,iDAJW,OAAO,iBAAiB,EAAE,OAAO,WACjC,OAAO,oBAAoB,EAAE,gBAAgB,GAC3C,OAAO,sBAAsB,EAAE,OAAO,CAQlD;AAED;;;;GAIG;AACH,qCAJW,OAAO,iBAAiB,EAAE,OAAO,WACjC,OAAO,oBAAoB,EAAE,gBAAgB,GAC3C,GAAG,EAAE,CAIjB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createAndInitialize } from "../view/testUtils.js";
|
|
2
|
+
import UnitView from "../view/unitView.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {import("../spec/view.js").ViewSpec} spec
|
|
6
|
+
* @param {{ new(...args: any[]): import("../view/view.js").default }} [viewType]
|
|
7
|
+
* @returns {Promise<import("../view/view.js").default>}
|
|
8
|
+
*/
|
|
9
|
+
export async function initView(spec, viewType = UnitView) {
|
|
10
|
+
return createAndInitialize(spec, viewType);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("../view/view.js").default} view
|
|
15
|
+
* @param {import("../spec/channel.js").ChannelWithScale} channel
|
|
16
|
+
* @returns {import("./scaleResolution.js").default}
|
|
17
|
+
*/
|
|
18
|
+
export function getRequiredScaleResolution(view, channel) {
|
|
19
|
+
const resolution = view.getScaleResolution(channel);
|
|
20
|
+
if (!resolution) {
|
|
21
|
+
throw new Error(`Expected ${channel} scale resolution.`);
|
|
22
|
+
}
|
|
23
|
+
return resolution;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {import("../view/view.js").default} view
|
|
28
|
+
* @param {import("../spec/channel.js").ChannelWithScale} channel
|
|
29
|
+
* @returns {any[]}
|
|
30
|
+
*/
|
|
31
|
+
export function getScaleDomain(view, channel) {
|
|
32
|
+
return getRequiredScaleResolution(view, channel).scale.domain();
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{ findRuntimeForParam: (name: string) => any }} paramRuntime
|
|
3
|
+
* @param {string} paramName
|
|
4
|
+
*/
|
|
5
|
+
export function requireParamRuntime(paramRuntime: {
|
|
6
|
+
findRuntimeForParam: (name: string) => any;
|
|
7
|
+
}, paramName: string): any;
|
|
8
|
+
/**
|
|
9
|
+
* @param {any} selection
|
|
10
|
+
* @param {string} paramName
|
|
11
|
+
*/
|
|
12
|
+
export function requireIntervalSelection(selection: any, paramName: string): import("../types/selectionTypes.js").IntervalSelection;
|
|
13
|
+
/**
|
|
14
|
+
* @param {number[]} interval
|
|
15
|
+
* @param {number[]} zoomExtent
|
|
16
|
+
* @param {{ roundToIntegers?: boolean }} [options]
|
|
17
|
+
* @returns {[number, number] | undefined}
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeIntervalForSelection(interval: number[], zoomExtent: number[], options?: {
|
|
20
|
+
roundToIntegers?: boolean;
|
|
21
|
+
}): [number, number] | undefined;
|
|
22
|
+
//# sourceMappingURL=selectionDomainUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectionDomainUtils.d.ts","sourceRoot":"","sources":["../../../src/scales/selectionDomainUtils.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,kDAHW;IAAE,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,CAAA;CAAE,aAC9C,MAAM,OAUhB;AAED;;;GAGG;AACH,oDAHW,GAAG,aACH,MAAM,0DAgBhB;AAED;;;;;GAKG;AACH,wDALW,MAAM,EAAE,cACR,MAAM,EAAE,YACR;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3B,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAsCxC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isIntervalSelection } from "../selection/selection.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {{ findRuntimeForParam: (name: string) => any }} paramRuntime
|
|
5
|
+
* @param {string} paramName
|
|
6
|
+
*/
|
|
7
|
+
export function requireParamRuntime(paramRuntime, paramName) {
|
|
8
|
+
const runtime = paramRuntime.findRuntimeForParam(paramName);
|
|
9
|
+
if (!runtime) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`Selection domain parameter "${paramName}" was not found.`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return runtime;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {any} selection
|
|
19
|
+
* @param {string} paramName
|
|
20
|
+
*/
|
|
21
|
+
export function requireIntervalSelection(selection, paramName) {
|
|
22
|
+
if (!selection) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Selection domain parameter "${paramName}" was not found.`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!isIntervalSelection(selection)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Selection domain parameter "${paramName}" must be an interval selection.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return selection;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {number[]} interval
|
|
39
|
+
* @param {number[]} zoomExtent
|
|
40
|
+
* @param {{ roundToIntegers?: boolean }} [options]
|
|
41
|
+
* @returns {[number, number] | undefined}
|
|
42
|
+
*/
|
|
43
|
+
export function normalizeIntervalForSelection(
|
|
44
|
+
interval,
|
|
45
|
+
zoomExtent,
|
|
46
|
+
options = {}
|
|
47
|
+
) {
|
|
48
|
+
if (!interval || interval.length !== 2) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const a = Number(interval[0]);
|
|
53
|
+
const b = Number(interval[1]);
|
|
54
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let min = Math.min(a, b);
|
|
59
|
+
let max = Math.max(a, b);
|
|
60
|
+
|
|
61
|
+
min = Math.max(zoomExtent[0], min);
|
|
62
|
+
max = Math.min(zoomExtent[1], max);
|
|
63
|
+
|
|
64
|
+
if (min > max) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (options.roundToIntegers) {
|
|
69
|
+
min = Math.ceil(min);
|
|
70
|
+
max = Math.ceil(max);
|
|
71
|
+
min = Math.max(zoomExtent[0], min);
|
|
72
|
+
max = Math.min(zoomExtent[1], max);
|
|
73
|
+
if (min > max) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return [min, max];
|
|
79
|
+
}
|