@genome-spy/core 0.65.0 → 0.67.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/browser-BRemItdO.js +138 -0
- package/dist/bundle/{index-CD7FLu9x.js → index-BatuyGAI.js} +23 -21
- package/dist/bundle/{index-C0llXMqm.js → index-ByuE8dvu.js} +140 -88
- package/dist/bundle/index-Cq3QFUxX.js +1781 -0
- package/dist/bundle/index-D28m8tSW.js +1607 -0
- package/dist/bundle/index-DbJ0oeYM.js +631 -0
- package/dist/bundle/index.es.js +15821 -14601
- package/dist/bundle/index.js +214 -212
- package/dist/bundle/{inflate-DRgHi_KK.js → inflate-GtwLkvSP.js} +222 -224
- package/dist/bundle/unzip-NywezaRR.js +1492 -0
- package/dist/schema.json +13 -3
- package/dist/src/config/scaleDefaults.d.ts +8 -0
- package/dist/src/config/scaleDefaults.d.ts.map +1 -0
- package/dist/src/config/scaleDefaults.js +45 -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 -16
- package/dist/src/data/sources/lazy/axisTickSource.js +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.js +10 -3
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +5 -1
- package/dist/src/data/transforms/filterScoredLabels.d.ts +1 -1
- package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
- package/dist/src/data/transforms/filterScoredLabels.js +1 -1
- package/dist/src/data/transforms/linearizeGenomicCoordinate.d.ts.map +1 -1
- package/dist/src/data/transforms/linearizeGenomicCoordinate.js +2 -1
- package/dist/src/encoder/encoder.d.ts +1 -1
- package/dist/src/encoder/encoder.d.ts.map +1 -1
- package/dist/src/encoder/encoder.js +1 -1
- package/dist/src/genome/scaleLocus.d.ts +39 -0
- package/dist/src/genome/scaleLocus.d.ts.map +1 -1
- package/dist/src/genome/scaleLocus.js +76 -0
- package/dist/src/genomeSpy/canvasExport.d.ts +19 -0
- package/dist/src/genomeSpy/canvasExport.d.ts.map +1 -0
- package/dist/src/genomeSpy/canvasExport.js +66 -0
- package/dist/src/genomeSpy/containerUi.d.ts +17 -0
- package/dist/src/genomeSpy/containerUi.d.ts.map +1 -0
- package/dist/src/genomeSpy/containerUi.js +78 -0
- package/dist/src/genomeSpy/eventListenerRegistry.d.ts +19 -0
- package/dist/src/genomeSpy/eventListenerRegistry.d.ts.map +1 -0
- package/dist/src/genomeSpy/eventListenerRegistry.js +38 -0
- package/dist/src/genomeSpy/inputBindingManager.d.ts +14 -0
- package/dist/src/genomeSpy/inputBindingManager.d.ts.map +1 -0
- package/dist/src/genomeSpy/inputBindingManager.js +63 -0
- package/dist/src/genomeSpy/interactionController.d.ts +40 -0
- package/dist/src/genomeSpy/interactionController.d.ts.map +1 -0
- package/dist/src/genomeSpy/interactionController.js +371 -0
- package/dist/src/genomeSpy/keyboardListenerManager.d.ts +10 -0
- package/dist/src/genomeSpy/keyboardListenerManager.d.ts.map +1 -0
- package/dist/src/genomeSpy/keyboardListenerManager.js +31 -0
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +15 -0
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -0
- package/dist/src/genomeSpy/loadingIndicatorManager.js +92 -0
- package/dist/src/genomeSpy/renderCoordinator.d.ts +22 -0
- package/dist/src/genomeSpy/renderCoordinator.d.ts.map +1 -0
- package/dist/src/genomeSpy/renderCoordinator.js +118 -0
- package/dist/src/genomeSpy/viewContextFactory.d.ts +18 -0
- package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewContextFactory.js +79 -0
- package/dist/src/genomeSpy/viewDataInit.d.ts +22 -0
- package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewDataInit.js +160 -0
- 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/viewHierarchyConfig.d.ts +14 -0
- package/dist/src/genomeSpy/viewHierarchyConfig.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewHierarchyConfig.js +24 -0
- package/dist/src/genomeSpy/viewHighlight.d.ts +5 -0
- package/dist/src/genomeSpy/viewHighlight.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewHighlight.js +30 -0
- package/dist/src/genomeSpy.d.ts +17 -71
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +197 -741
- package/dist/src/gl/dataToVertices.d.ts.map +1 -1
- package/dist/src/gl/dataToVertices.js +16 -4
- package/dist/src/gl/glslScaleGenerator.d.ts +1 -1
- package/dist/src/gl/webGLHelper.d.ts +2 -2
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +4 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -12
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +4 -2
- package/dist/src/{view → scales}/axisResolution.d.ts +9 -16
- package/dist/src/scales/axisResolution.d.ts.map +1 -0
- package/dist/src/{view → scales}/axisResolution.js +29 -18
- package/dist/src/scales/axisResolution.test.d.ts.map +1 -0
- package/dist/src/scales/scaleDomainAggregator.d.ts +57 -0
- package/dist/src/scales/scaleDomainAggregator.d.ts.map +1 -0
- package/dist/src/scales/scaleDomainAggregator.js +167 -0
- package/dist/src/scales/scaleDomainAggregator.test.d.ts +2 -0
- package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +1 -0
- package/dist/src/scales/scaleInstanceManager.d.ts +40 -0
- package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -0
- package/dist/src/scales/scaleInstanceManager.js +317 -0
- package/dist/src/scales/scaleInstanceManager.test.d.ts +2 -0
- package/dist/src/scales/scaleInstanceManager.test.d.ts.map +1 -0
- package/dist/src/scales/scaleInteractionController.d.ts +73 -0
- package/dist/src/scales/scaleInteractionController.d.ts.map +1 -0
- package/dist/src/scales/scaleInteractionController.js +336 -0
- package/dist/src/scales/scaleInteractionController.test.d.ts +2 -0
- package/dist/src/scales/scaleInteractionController.test.d.ts.map +1 -0
- package/dist/src/scales/scalePropsResolver.d.ts +23 -0
- package/dist/src/scales/scalePropsResolver.d.ts.map +1 -0
- package/dist/src/scales/scalePropsResolver.js +74 -0
- package/dist/src/{view → scales}/scaleResolution.d.ts +53 -35
- package/dist/src/scales/scaleResolution.d.ts.map +1 -0
- package/dist/src/scales/scaleResolution.js +732 -0
- package/dist/src/scales/scaleResolution.test.d.ts.map +1 -0
- package/dist/src/scales/scaleResolutionConstants.d.ts +6 -0
- package/dist/src/scales/scaleResolutionConstants.d.ts.map +1 -0
- package/dist/src/scales/scaleResolutionConstants.js +5 -0
- package/dist/src/scales/scaleRules.d.ts +16 -0
- package/dist/src/scales/scaleRules.d.ts.map +1 -0
- package/dist/src/scales/scaleRules.js +103 -0
- package/dist/src/scales/scaleRules.test.d.ts +2 -0
- package/dist/src/scales/scaleRules.test.d.ts.map +1 -0
- package/dist/src/spec/channel.d.ts +13 -18
- package/dist/src/spec/scale.d.ts +6 -0
- package/dist/src/types/embedApi.d.ts +5 -0
- package/dist/src/types/scaleResolutionApi.d.ts +1 -1
- 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/view/concatView.d.ts +18 -0
- package/dist/src/view/concatView.d.ts.map +1 -1
- package/dist/src/view/concatView.js +73 -0
- package/dist/src/view/concatView.test.d.ts +2 -0
- package/dist/src/view/concatView.test.d.ts.map +1 -0
- package/dist/src/view/containerMutationHelper.d.ts +74 -0
- package/dist/src/view/containerMutationHelper.d.ts.map +1 -0
- package/dist/src/view/containerMutationHelper.js +118 -0
- package/dist/src/view/containerView.d.ts +0 -7
- package/dist/src/view/containerView.d.ts.map +1 -1
- package/dist/src/view/containerView.js +0 -10
- package/dist/src/view/facetView.d.ts.map +1 -1
- package/dist/src/view/facetView.js +0 -15
- 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 +69 -6
- package/dist/src/view/gridView/gridChild.d.ts +11 -0
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +32 -6
- package/dist/src/view/gridView/gridView.d.ts +39 -1
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +106 -48
- package/dist/src/view/gridView/gridView.test.d.ts +2 -0
- package/dist/src/view/gridView/gridView.test.d.ts.map +1 -0
- package/dist/src/view/gridView/scrollbar.d.ts +39 -8
- package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
- package/dist/src/view/gridView/scrollbar.js +184 -69
- package/dist/src/view/layerView.d.ts +14 -0
- package/dist/src/view/layerView.d.ts.map +1 -1
- package/dist/src/view/layerView.js +66 -0
- package/dist/src/view/layerView.test.d.ts +2 -0
- package/dist/src/view/layerView.test.d.ts.map +1 -0
- package/dist/src/view/testUtils.d.ts.map +1 -1
- package/dist/src/view/testUtils.js +7 -1
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +41 -36
- package/dist/src/view/view.d.ts +18 -6
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +30 -4
- package/package.json +2 -2
- package/dist/bundle/browser-txUcLy2H.js +0 -123
- package/dist/bundle/index-BQpbYrv4.js +0 -1712
- package/dist/bundle/index-BhtHKLUo.js +0 -73
- package/dist/bundle/index-CCe8rnZz.js +0 -716
- package/dist/bundle/index-DhcU-Gk-.js +0 -1487
- package/dist/src/data/collector.test.js +0 -138
- package/dist/src/data/dataFlow.test.js +0 -38
- package/dist/src/data/flow.test.js +0 -81
- package/dist/src/data/flowInit.test.js +0 -413
- package/dist/src/data/flowNode.test.js +0 -50
- package/dist/src/data/flowOptimizer.test.js +0 -209
- package/dist/src/data/formats/fasta.test.js +0 -27
- package/dist/src/data/sources/inlineSource.test.js +0 -63
- package/dist/src/data/sources/sequenceSource.test.js +0 -81
- package/dist/src/data/transforms/aggregate.test.js +0 -134
- package/dist/src/data/transforms/clone.test.js +0 -11
- package/dist/src/data/transforms/coverage.test.js +0 -238
- package/dist/src/data/transforms/filter.test.js +0 -20
- package/dist/src/data/transforms/flatten.test.js +0 -96
- package/dist/src/data/transforms/flattenDelimited.test.js +0 -90
- package/dist/src/data/transforms/flattenSequence.test.js +0 -34
- package/dist/src/data/transforms/formula.test.js +0 -25
- package/dist/src/data/transforms/identifier.test.js +0 -92
- package/dist/src/data/transforms/pileup.test.js +0 -70
- package/dist/src/data/transforms/project.test.js +0 -32
- package/dist/src/data/transforms/regexExtract.test.js +0 -70
- package/dist/src/data/transforms/regexFold.test.js +0 -201
- package/dist/src/data/transforms/sample.test.js +0 -38
- package/dist/src/data/transforms/stack.test.js +0 -91
- package/dist/src/encoder/accessor.test.js +0 -162
- package/dist/src/encoder/encoder.test.js +0 -105
- package/dist/src/genome/genome.test.js +0 -268
- package/dist/src/genome/genomes.test.js +0 -8
- package/dist/src/genome/scaleIndex.test.js +0 -78
- package/dist/src/genome/scaleLocus.test.js +0 -4
- package/dist/src/scale/scale.test.js +0 -326
- package/dist/src/scale/ticks.test.js +0 -46
- package/dist/src/selection/selection.test.js +0 -14
- package/dist/src/utils/addBaseUrl.test.js +0 -30
- package/dist/src/utils/binnedIndex.test.js +0 -201
- package/dist/src/utils/cloner.test.js +0 -35
- package/dist/src/utils/coalesce.test.js +0 -16
- package/dist/src/utils/concatIterables.test.js +0 -8
- package/dist/src/utils/domainArray.test.js +0 -130
- package/dist/src/utils/indexer.test.js +0 -49
- package/dist/src/utils/interactionEvent.test.js +0 -35
- package/dist/src/utils/iterateNestedMaps.test.js +0 -33
- package/dist/src/utils/kWayMerge.test.js +0 -30
- package/dist/src/utils/mergeObjects.test.js +0 -42
- package/dist/src/utils/numberExtractor.test.js +0 -6
- package/dist/src/utils/propertyCacher.test.js +0 -89
- package/dist/src/utils/propertyCoalescer.test.js +0 -25
- package/dist/src/utils/radixSort.test.js +0 -51
- package/dist/src/utils/reservationMap.test.js +0 -20
- package/dist/src/utils/ringBuffer.test.js +0 -39
- package/dist/src/utils/topK.test.js +0 -54
- package/dist/src/utils/trees.test.js +0 -135
- package/dist/src/utils/url.test.js +0 -28
- package/dist/src/utils/variableTools.test.js +0 -13
- package/dist/src/view/axisResolution.d.ts.map +0 -1
- package/dist/src/view/axisResolution.test.d.ts.map +0 -1
- package/dist/src/view/axisResolution.test.js +0 -206
- package/dist/src/view/flowBuilder.test.js +0 -125
- package/dist/src/view/gridView/selectionRect.test.js +0 -87
- package/dist/src/view/layout/flexLayout.test.js +0 -323
- package/dist/src/view/layout/grid.test.js +0 -71
- package/dist/src/view/layout/rectangle.test.js +0 -192
- package/dist/src/view/paramMediator.test.js +0 -282
- package/dist/src/view/scaleResolution.d.ts.map +0 -1
- package/dist/src/view/scaleResolution.js +0 -1059
- package/dist/src/view/scaleResolution.test.d.ts.map +0 -1
- package/dist/src/view/scaleResolution.test.js +0 -645
- package/dist/src/view/view.test.js +0 -245
- package/dist/src/view/viewDispose.test.js +0 -110
- package/dist/src/view/viewFactory.test.js +0 -25
- package/dist/src/view/viewUtils.test.js +0 -87
- /package/dist/src/{view → scales}/axisResolution.test.d.ts +0 -0
- /package/dist/src/{view → scales}/scaleResolution.test.d.ts +0 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
import scaleLocus, {
|
|
2
|
+
fromComplexInterval as locusFromComplexInterval,
|
|
3
|
+
fromComplexValue,
|
|
4
|
+
getGenomeExtent,
|
|
5
|
+
toComplexInterval,
|
|
6
|
+
toComplexValue,
|
|
7
|
+
} from "../genome/scaleLocus.js";
|
|
8
|
+
import scaleIndex from "../genome/scaleIndex.js";
|
|
9
|
+
import scaleNull from "../utils/scaleNull.js";
|
|
10
|
+
|
|
11
|
+
import { scale as vegaScale, isDiscrete, isContinuous } from "vega-scale";
|
|
12
|
+
import { configureDomain } from "../scale/scale.js";
|
|
13
|
+
|
|
14
|
+
import ScaleInstanceManager from "./scaleInstanceManager.js";
|
|
15
|
+
import { resolveScalePropsBase } from "./scalePropsResolver.js";
|
|
16
|
+
import ScaleDomainAggregator from "./scaleDomainAggregator.js";
|
|
17
|
+
import ScaleInteractionController from "./scaleInteractionController.js";
|
|
18
|
+
import {
|
|
19
|
+
INDEX,
|
|
20
|
+
LOCUS,
|
|
21
|
+
NOMINAL,
|
|
22
|
+
ORDINAL,
|
|
23
|
+
QUANTITATIVE,
|
|
24
|
+
} from "./scaleResolutionConstants.js";
|
|
25
|
+
|
|
26
|
+
import { isSecondaryChannel } from "../encoder/encoder.js";
|
|
27
|
+
import { NominalDomain } from "../utils/domainArray.js";
|
|
28
|
+
import { asArray, shallowArrayEquals } from "../utils/arrayUtils.js";
|
|
29
|
+
import { VISIT_SKIP } from "../view/view.js";
|
|
30
|
+
import createIndexer from "../utils/indexer.js";
|
|
31
|
+
|
|
32
|
+
// Register scaleLocus to Vega-Scale.
|
|
33
|
+
// Loci are discrete but the scale's domain can be adjusted in a continuous manner.
|
|
34
|
+
vegaScale("index", scaleIndex, ["continuous"]);
|
|
35
|
+
vegaScale("locus", scaleLocus, ["continuous"]);
|
|
36
|
+
vegaScale("null", scaleNull, []);
|
|
37
|
+
|
|
38
|
+
export { INDEX, LOCUS, NOMINAL, ORDINAL, QUANTITATIVE };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @template {ChannelWithScale}[T=ChannelWithScale]
|
|
42
|
+
*
|
|
43
|
+
* @typedef {object} ScaleResolutionMember
|
|
44
|
+
* @prop {import("../view/unitView.js").default} view TODO: Get rid of the view reference
|
|
45
|
+
* @prop {T} channel
|
|
46
|
+
* @prop {import("../spec/channel.js").ChannelDefWithScale} channelDef
|
|
47
|
+
* @prop {(channel: ChannelWithScale, type: import("../spec/channel.js").Type) => DomainArray} dataDomainSource
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Resolves a shared scale for a channel by merging scale properties and domains
|
|
51
|
+
* across participating views, then coordinating range updates and zoom/pan
|
|
52
|
+
* interactions. It is the central wiring point for scale-related state and
|
|
53
|
+
* notifications, while delegating domain aggregation, scale instance setup, and
|
|
54
|
+
* interaction logic to focused helpers.
|
|
55
|
+
*
|
|
56
|
+
* @implements {ScaleResolutionApi}
|
|
57
|
+
*/
|
|
58
|
+
export default class ScaleResolution {
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionApi} ScaleResolutionApi
|
|
61
|
+
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionEventType} ScaleResolutionEventType
|
|
62
|
+
* @typedef {import("../spec/channel.js").Channel} Channel
|
|
63
|
+
* @typedef {import("../spec/channel.js").ChannelWithScale} ChannelWithScale
|
|
64
|
+
* @typedef {import("../spec/scale.js").NumericDomain} NumericDomain
|
|
65
|
+
* @typedef {import("../spec/scale.js").ScalarDomain} ScalarDomain
|
|
66
|
+
* @typedef {import("../spec/scale.js").ComplexDomain} ComplexDomain
|
|
67
|
+
* @typedef {import("../view/unitView.js").default} UnitView
|
|
68
|
+
* @typedef {import("../types/encoder.js").VegaScale} VegaScale
|
|
69
|
+
* @typedef {import("../utils/domainArray.js").DomainArray} DomainArray
|
|
70
|
+
* @typedef {import("../genome/genome.js").ChromosomalLocus} ChromosomalLocus
|
|
71
|
+
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionListener} ScaleResolutionListener
|
|
72
|
+
*
|
|
73
|
+
* @typedef {VegaScale & { props: import("../spec/scale.js").Scale }} ScaleWithProps
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/** @type {Set<ScaleResolutionMember>} The involved views */
|
|
77
|
+
#members = new Set();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
|
|
81
|
+
*/
|
|
82
|
+
#listeners = {
|
|
83
|
+
domain: new Set(),
|
|
84
|
+
range: new Set(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** @type {ScaleInstanceManager} */
|
|
88
|
+
#scaleManager;
|
|
89
|
+
|
|
90
|
+
/** @type {ScaleDomainAggregator} */
|
|
91
|
+
#domainAggregator;
|
|
92
|
+
|
|
93
|
+
/** @type {ScaleInteractionController} */
|
|
94
|
+
#interactionController;
|
|
95
|
+
|
|
96
|
+
/** @type {ReturnType<typeof createIndexer> | undefined} */
|
|
97
|
+
#categoricalIndexer;
|
|
98
|
+
|
|
99
|
+
#categoricalIndexerExplicit = false;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {Channel} channel
|
|
103
|
+
*/
|
|
104
|
+
constructor(channel) {
|
|
105
|
+
this.channel = channel;
|
|
106
|
+
/** @type {import("../spec/channel.js").Type} Data type (quantitative, nominal, etc...) */
|
|
107
|
+
this.type = null;
|
|
108
|
+
|
|
109
|
+
/** @type {string} An optional unique identifier for the scale */
|
|
110
|
+
this.name = undefined;
|
|
111
|
+
|
|
112
|
+
this.#domainAggregator = new ScaleDomainAggregator({
|
|
113
|
+
getMembers: () => this.#getActiveMembers(),
|
|
114
|
+
getType: () => this.type,
|
|
115
|
+
getLocusExtent: () => this.#getLocusExtent(),
|
|
116
|
+
fromComplexInterval: this.fromComplexInterval.bind(this),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.#scaleManager = new ScaleInstanceManager({
|
|
120
|
+
getParamMediator: () => this.#firstMemberView.paramMediator,
|
|
121
|
+
onRangeChange: () => this.#notifyListeners("range"),
|
|
122
|
+
onDomainChange: () => this.#notifyListeners("domain"),
|
|
123
|
+
getGenomeStore: () => this.#viewContext.genomeStore,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
this.#interactionController = new ScaleInteractionController({
|
|
127
|
+
getScale: () => this.getScale(),
|
|
128
|
+
getAnimator: () => this.#viewContext.animator,
|
|
129
|
+
getInitialDomainSnapshot: () =>
|
|
130
|
+
this.#domainAggregator.initialDomainSnapshot,
|
|
131
|
+
getResetDomain: () =>
|
|
132
|
+
this.#domainAggregator.getConfiguredOrDefaultDomain(),
|
|
133
|
+
fromComplexInterval: this.fromComplexInterval.bind(this),
|
|
134
|
+
getGenomeExtent: () => this.#getLocusExtent(),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @returns {import("../view/view.js").default}
|
|
140
|
+
*/
|
|
141
|
+
get #firstMemberView() {
|
|
142
|
+
const first = this.#members.values().next().value;
|
|
143
|
+
if (!first) {
|
|
144
|
+
throw new Error("ScaleResolution has no members!");
|
|
145
|
+
}
|
|
146
|
+
return first.view;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#getActiveMembers() {
|
|
150
|
+
/** @type {Set<ScaleResolutionMember>} */
|
|
151
|
+
const active = new Set();
|
|
152
|
+
for (const member of this.#members) {
|
|
153
|
+
const view = member.view;
|
|
154
|
+
if (!view.isConfiguredVisible()) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
!view.isDataInitialized() &&
|
|
159
|
+
!member.channelDef?.scale?.domain
|
|
160
|
+
) {
|
|
161
|
+
// Explicit domains should be honored even before data init.
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
active.add(member);
|
|
165
|
+
}
|
|
166
|
+
return active;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get #viewContext() {
|
|
170
|
+
return this.#firstMemberView.context;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
get zoomExtent() {
|
|
174
|
+
return (
|
|
175
|
+
(this.#scaleManager.scale &&
|
|
176
|
+
isContinuous(this.#scaleManager.scale.type) &&
|
|
177
|
+
this.#interactionController.getZoomExtent()) ?? [
|
|
178
|
+
-Infinity,
|
|
179
|
+
Infinity,
|
|
180
|
+
]
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @returns {number[]}
|
|
186
|
+
*/
|
|
187
|
+
#getLocusExtent() {
|
|
188
|
+
return getGenomeExtent(this.#getGenomeSource());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @returns {import("../genome/scaleLocus.js").GenomeSource}
|
|
193
|
+
*/
|
|
194
|
+
#getGenomeSource() {
|
|
195
|
+
if (this.type !== LOCUS) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return /** @type {import("../genome/scaleLocus.js").GenomeSource} */ (
|
|
199
|
+
this.#scaleManager.scale ?? this.#scaleManager.getLocusGenome()
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Adds a listener that is called when the scale domain is changed,
|
|
205
|
+
* e.g., zoomed. The call is synchronous and happens before the views
|
|
206
|
+
* are rendered.
|
|
207
|
+
*
|
|
208
|
+
* @param {ScaleResolutionEventType} type
|
|
209
|
+
* @param {ScaleResolutionListener} listener function
|
|
210
|
+
*/
|
|
211
|
+
addEventListener(type, listener) {
|
|
212
|
+
this.#listeners[type].add(listener);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {ScaleResolutionEventType} type
|
|
217
|
+
* @param {ScaleResolutionListener} listener function
|
|
218
|
+
*/
|
|
219
|
+
removeEventListener(type, listener) {
|
|
220
|
+
this.#listeners[type].delete(listener);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param {ScaleResolutionEventType} type
|
|
225
|
+
*/
|
|
226
|
+
#notifyListeners(type) {
|
|
227
|
+
for (const listener of this.#listeners[type].values()) {
|
|
228
|
+
listener({
|
|
229
|
+
type,
|
|
230
|
+
scaleResolution: this,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Add a view to this resolution.
|
|
237
|
+
* N.B. This is expected to be called in depth-first order
|
|
238
|
+
*
|
|
239
|
+
* @param {ScaleResolutionMember} newMember
|
|
240
|
+
*/
|
|
241
|
+
#addMember(newMember) {
|
|
242
|
+
const { channel, channelDef } = newMember;
|
|
243
|
+
|
|
244
|
+
// A convenience hack for cases where the new member should adapt
|
|
245
|
+
// the scale type to the existing one. For example: SelectionRect
|
|
246
|
+
// TODO: Add test
|
|
247
|
+
const adapt = channelDef.type == null && this.type;
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
// @ts-expect-error "sample" is not really a channel with scale
|
|
251
|
+
channel != "sample" &&
|
|
252
|
+
!channelDef.type &&
|
|
253
|
+
!isSecondaryChannel(channel) &&
|
|
254
|
+
!adapt
|
|
255
|
+
) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`The "type" property must be defined in channel definition: "${channel}": ${JSON.stringify(
|
|
258
|
+
channelDef
|
|
259
|
+
)}. Must be one of: "quantitative", "ordinal", "nominal", "locus", "index"`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// A hack for sample channel, which really doesn't have a scale but the
|
|
264
|
+
// domain is needed when samples are not specified explicitly.
|
|
265
|
+
// @ts-expect-error "sample" is not really a channel with scale
|
|
266
|
+
const type = channel == "sample" ? "nominal" : channelDef.type;
|
|
267
|
+
const name = channelDef?.scale?.name;
|
|
268
|
+
|
|
269
|
+
if (name) {
|
|
270
|
+
if (this.name !== undefined && name != this.name) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`Shared scales have conflicting names: "${name}" vs. "${this.name}"!`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
this.name = name;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!adapt) {
|
|
279
|
+
if (!this.type) {
|
|
280
|
+
this.type = type;
|
|
281
|
+
} else if (type !== this.type && !isSecondaryChannel(channel)) {
|
|
282
|
+
// TODO: Include a reference to the layer
|
|
283
|
+
throw new Error(
|
|
284
|
+
`Can not use shared scale for different data types: ${this.type} vs. ${type}. Use "resolve: independent" for channel ${this.channel}`
|
|
285
|
+
);
|
|
286
|
+
// Actually, point scale could be changed into band scale
|
|
287
|
+
// TODO: Use the same merging logic as in: https://github.com/vega/vega-lite/blob/master/src/scale.ts
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.#members.add(newMember);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @param {ScaleResolutionMember} member
|
|
296
|
+
* @returns {() => boolean}
|
|
297
|
+
*/
|
|
298
|
+
registerMember(member) {
|
|
299
|
+
this.#addMember(member);
|
|
300
|
+
return () => {
|
|
301
|
+
const removed = this.#members.delete(member);
|
|
302
|
+
return removed && this.#members.size === 0;
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
|
|
308
|
+
*/
|
|
309
|
+
#isExplicitDomain() {
|
|
310
|
+
return this.#domainAggregator.hasConfiguredDomain();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#isDomainInitialized() {
|
|
314
|
+
const s = this.#scaleManager.scale;
|
|
315
|
+
if (!s) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const domain = s.domain();
|
|
320
|
+
|
|
321
|
+
// We could alternatively have a flag that is set when the domain is initialized.
|
|
322
|
+
if (isContinuous(s.type)) {
|
|
323
|
+
return (
|
|
324
|
+
domain.length > 2 ||
|
|
325
|
+
(domain.length == 2 && (domain[0] !== 0 || domain[1] !== 0))
|
|
326
|
+
);
|
|
327
|
+
} else {
|
|
328
|
+
return domain.length > 0;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Collects and merges scale properties from the participating views.
|
|
334
|
+
* Does not include inferred default values such as schemes etc.
|
|
335
|
+
*
|
|
336
|
+
* @returns {import("../spec/scale.js").Scale}
|
|
337
|
+
*/
|
|
338
|
+
#getMergedScaleProps() {
|
|
339
|
+
return resolveScalePropsBase({
|
|
340
|
+
channel: this.channel,
|
|
341
|
+
dataType: this.type,
|
|
342
|
+
members: this.#members,
|
|
343
|
+
isExplicitDomain: this.#isExplicitDomain(),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Returns the merged scale properties supplemented with inferred properties
|
|
349
|
+
* and domain.
|
|
350
|
+
*
|
|
351
|
+
* @param {boolean} [extractDataDomain]
|
|
352
|
+
* @returns {import("../spec/scale.js").Scale}
|
|
353
|
+
*/
|
|
354
|
+
#getScaleProps(extractDataDomain = false) {
|
|
355
|
+
const props = this.#getMergedScaleProps();
|
|
356
|
+
if (props === null || props.type == "null") {
|
|
357
|
+
// No scale (pass-thru)
|
|
358
|
+
// TODO: Check that the channel is compatible
|
|
359
|
+
return { type: "null" };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const domain =
|
|
363
|
+
this.#domainAggregator.getConfiguredOrDefaultDomain(
|
|
364
|
+
extractDataDomain
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (isDiscrete(props.type)) {
|
|
368
|
+
const isExplicit = this.#isExplicitDomain();
|
|
369
|
+
const indexer = this.#getCategoricalIndexer(isExplicit);
|
|
370
|
+
if (domain != null) {
|
|
371
|
+
if (
|
|
372
|
+
isExplicit &&
|
|
373
|
+
indexer.domain().length > 0 &&
|
|
374
|
+
!shallowArrayEquals(indexer.domain(), domain)
|
|
375
|
+
) {
|
|
376
|
+
this.#categoricalIndexer = undefined;
|
|
377
|
+
return this.#getScaleProps(extractDataDomain);
|
|
378
|
+
}
|
|
379
|
+
indexer.addAll(domain);
|
|
380
|
+
const active = new Set(domain);
|
|
381
|
+
const indexedDomain = indexer
|
|
382
|
+
.domain()
|
|
383
|
+
.filter((value) => active.has(value));
|
|
384
|
+
props.domain =
|
|
385
|
+
indexedDomain.length > 0
|
|
386
|
+
? /** @type {import("../spec/scale.js").ScalarDomain} */ (
|
|
387
|
+
indexedDomain
|
|
388
|
+
)
|
|
389
|
+
: new NominalDomain();
|
|
390
|
+
} else {
|
|
391
|
+
const indexedDomain = indexer.domain();
|
|
392
|
+
props.domain =
|
|
393
|
+
indexedDomain.length > 0
|
|
394
|
+
? /** @type {import("../spec/scale.js").ScalarDomain} */ (
|
|
395
|
+
indexedDomain
|
|
396
|
+
)
|
|
397
|
+
: new NominalDomain();
|
|
398
|
+
}
|
|
399
|
+
// Scale props are spec-shaped; keep the indexer off the public type.
|
|
400
|
+
/** @type {any} */ (props).domainIndexer = indexer;
|
|
401
|
+
} else if (domain && domain.length > 0) {
|
|
402
|
+
props.domain = domain;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!props.domain && props.domainMid !== undefined) {
|
|
406
|
+
// Initialize with a bogus domain so that scale.js can inject the domainMid.
|
|
407
|
+
// The number of domain elements must be know before the glsl scale is generated.
|
|
408
|
+
props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return props;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @param {boolean} isExplicit
|
|
416
|
+
*/
|
|
417
|
+
#getCategoricalIndexer(isExplicit) {
|
|
418
|
+
if (
|
|
419
|
+
!this.#categoricalIndexer ||
|
|
420
|
+
this.#categoricalIndexerExplicit !== isExplicit
|
|
421
|
+
) {
|
|
422
|
+
this.#categoricalIndexer = createIndexer();
|
|
423
|
+
this.#categoricalIndexerExplicit = isExplicit;
|
|
424
|
+
}
|
|
425
|
+
return this.#categoricalIndexer;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Reconfigures the scale: updates domain and other settings.
|
|
430
|
+
*
|
|
431
|
+
* Use this when the set of participating members changes (views added or removed),
|
|
432
|
+
* or when scale properties are otherwise re-resolved from the view hierarchy.
|
|
433
|
+
*/
|
|
434
|
+
reconfigure() {
|
|
435
|
+
const props = this.#getScaleProps(true);
|
|
436
|
+
this.#reconfigureWith(() => this.#scaleManager.reconfigureScale(props));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Reconfigures only the effective domain (configured + data-derived).
|
|
441
|
+
*
|
|
442
|
+
* Use this when data changes but the scale membership and properties are stable.
|
|
443
|
+
*/
|
|
444
|
+
reconfigureDomain() {
|
|
445
|
+
const props = this.#getScaleProps(true);
|
|
446
|
+
this.#reconfigureWith(() => {
|
|
447
|
+
configureDomain(this.#scaleManager.scale, props);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* @param {() => void} apply
|
|
453
|
+
*/
|
|
454
|
+
#reconfigureWith(apply) {
|
|
455
|
+
const scale = this.#scaleManager.scale;
|
|
456
|
+
|
|
457
|
+
if (!scale || scale.type == "null") {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const domainWasInitialized = this.#isDomainInitialized();
|
|
462
|
+
const previousDomain = scale.domain();
|
|
463
|
+
|
|
464
|
+
this.#scaleManager.withDomainNotificationsSuppressed(apply);
|
|
465
|
+
|
|
466
|
+
if (
|
|
467
|
+
this.#domainAggregator.captureInitialDomain(
|
|
468
|
+
scale,
|
|
469
|
+
domainWasInitialized
|
|
470
|
+
)
|
|
471
|
+
) {
|
|
472
|
+
// Domain changes were suppressed during reconfigure; notify explicitly.
|
|
473
|
+
this.#notifyListeners("domain");
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const newDomain = scale.domain();
|
|
478
|
+
if (!shallowArrayEquals(newDomain, previousDomain)) {
|
|
479
|
+
if (this.isZoomable()) {
|
|
480
|
+
// Don't mess with zoomed views, restore the previous domain
|
|
481
|
+
this.#scaleManager.withDomainNotificationsSuppressed(() => {
|
|
482
|
+
scale.domain(previousDomain);
|
|
483
|
+
});
|
|
484
|
+
} else if (this.#interactionController.isZoomingSupported()) {
|
|
485
|
+
// It can be zoomed, so lets make a smooth transition.
|
|
486
|
+
// Restore the previous domain and zoom smoothly to the new domain.
|
|
487
|
+
this.#scaleManager.withDomainNotificationsSuppressed(() => {
|
|
488
|
+
scale.domain(previousDomain);
|
|
489
|
+
});
|
|
490
|
+
this.zoomTo(newDomain, 500); // TODO: Configurable duration
|
|
491
|
+
} else {
|
|
492
|
+
// Update immediately if the previous domain was the initial domain [0, 0]
|
|
493
|
+
// Notifications were suppressed during reconfigure; notify explicitly.
|
|
494
|
+
this.#notifyListeners("domain");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* @returns {ScaleWithProps}
|
|
501
|
+
*/
|
|
502
|
+
get scale() {
|
|
503
|
+
if (this.#scaleManager.scale) {
|
|
504
|
+
return this.#scaleManager.scale;
|
|
505
|
+
}
|
|
506
|
+
throw new Error(
|
|
507
|
+
"ScaleResolution.scale accessed before initialization. Call initializeScale()."
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Returns the scale instance, creating it if needed.
|
|
513
|
+
*
|
|
514
|
+
* Use this from call sites that may run before explicit initialization.
|
|
515
|
+
*
|
|
516
|
+
* @returns {ScaleWithProps}
|
|
517
|
+
*/
|
|
518
|
+
getScale() {
|
|
519
|
+
return this.#scaleManager.scale ?? this.initializeScale();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Initializes the scale instance once resolution has stabilized.
|
|
524
|
+
*
|
|
525
|
+
* @returns {ScaleWithProps}
|
|
526
|
+
*/
|
|
527
|
+
initializeScale() {
|
|
528
|
+
if (this.#scaleManager.scale) {
|
|
529
|
+
return this.#scaleManager.scale;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const props = this.#getScaleProps();
|
|
533
|
+
const scale = this.#scaleManager.createScale(props);
|
|
534
|
+
|
|
535
|
+
return scale;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
getDomain() {
|
|
539
|
+
return this.getScale().domain();
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Extracts and unions the data domains of all participating views.
|
|
544
|
+
*
|
|
545
|
+
* @return { DomainArray }
|
|
546
|
+
*/
|
|
547
|
+
getDataDomain() {
|
|
548
|
+
return this.#domainAggregator.getDataDomain();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* @returns {NumericDomain | ComplexDomain}
|
|
553
|
+
*/
|
|
554
|
+
getComplexDomain() {
|
|
555
|
+
return /** @type {NumericDomain | ComplexDomain} */ (
|
|
556
|
+
toComplexInterval(this.#getGenomeSource(), this.getDomain())
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Return true if the scale is zoomable and the current domain differs from the initial domain.
|
|
562
|
+
*
|
|
563
|
+
* @returns true if zoomed
|
|
564
|
+
*/
|
|
565
|
+
isZoomed() {
|
|
566
|
+
return this.#interactionController.isZoomed();
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Returns true if zooming is supported and allowed in view spec.
|
|
571
|
+
*/
|
|
572
|
+
isZoomable() {
|
|
573
|
+
// Check explicit configuration
|
|
574
|
+
return this.#interactionController.isZoomable();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Pans (translates) and zooms using a specified scale factor.
|
|
579
|
+
*
|
|
580
|
+
* @param {number} scaleFactor
|
|
581
|
+
* @param {number} scaleAnchor
|
|
582
|
+
* @param {number} pan
|
|
583
|
+
* @returns {boolean} true if the scale was zoomed
|
|
584
|
+
*/
|
|
585
|
+
zoom(scaleFactor, scaleAnchor, pan) {
|
|
586
|
+
return this.#interactionController.zoom(scaleFactor, scaleAnchor, pan);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Immediately zooms to the given interval.
|
|
591
|
+
*
|
|
592
|
+
* @param {NumericDomain | ComplexDomain} domain
|
|
593
|
+
* @param {boolean | number} [duration] an approximate duration for transition.
|
|
594
|
+
* Zero duration zooms immediately. Boolean `true` indicates a default duration.
|
|
595
|
+
*/
|
|
596
|
+
async zoomTo(domain, duration = false) {
|
|
597
|
+
return this.#interactionController.zoomTo(domain, duration);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Resets the current domain to the initial one
|
|
602
|
+
*
|
|
603
|
+
* @returns true if the domain was changed
|
|
604
|
+
*/
|
|
605
|
+
resetZoom() {
|
|
606
|
+
return this.#interactionController.resetZoom();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Returns the zoom level with respect to the reference domain span (the original domain).
|
|
611
|
+
*
|
|
612
|
+
* In principle, this is highly specific to positional channels. However, zooming can
|
|
613
|
+
* be generalized to other quantitative channels such as color, opacity, size, etc.
|
|
614
|
+
*/
|
|
615
|
+
getZoomLevel() {
|
|
616
|
+
return this.#interactionController.getZoomLevel();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Returns the length of the axis in pixels. Chooses the smallest of the views.
|
|
621
|
+
* They should all be the same, but some exotic configuration might break that assumption.
|
|
622
|
+
*
|
|
623
|
+
* This method is needed because positional channels have unit ranges and the
|
|
624
|
+
* length of the axis is not directly available from the scale. Ideally, ranges would
|
|
625
|
+
* be configured as pixels, but that is yet to be materialized.
|
|
626
|
+
*/
|
|
627
|
+
getAxisLength() {
|
|
628
|
+
if (this.channel !== "x" && this.channel !== "y") {
|
|
629
|
+
throw new Error(
|
|
630
|
+
"Axis length is only defined for x and y channels!"
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Here's a problem: if the view has been hidden, it may have stale coords.
|
|
635
|
+
// TODO: They should be cleared when the layout is invalidated.
|
|
636
|
+
// Alternatively, scale ranges could be set in pixels.
|
|
637
|
+
const lengths = Array.from(this.#members)
|
|
638
|
+
.map(
|
|
639
|
+
(m) =>
|
|
640
|
+
m.view.coords?.[this.channel === "x" ? "width" : "height"]
|
|
641
|
+
)
|
|
642
|
+
.filter((len) => len > 0);
|
|
643
|
+
|
|
644
|
+
return lengths.length
|
|
645
|
+
? lengths.reduce((a, b) => Math.min(a, b), 10000)
|
|
646
|
+
: 0;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Inverts a value in range to a value on domain. Returns an object in
|
|
651
|
+
* case of locus scale.
|
|
652
|
+
*
|
|
653
|
+
* @param {number} value
|
|
654
|
+
*/
|
|
655
|
+
invertToComplex(value) {
|
|
656
|
+
const scale = this.getScale();
|
|
657
|
+
if ("invert" in scale) {
|
|
658
|
+
const inverted = /** @type {number} */ (scale.invert(value));
|
|
659
|
+
return this.toComplex(inverted);
|
|
660
|
+
} else {
|
|
661
|
+
throw new Error("The scale does not support inverting!");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* @param {number} value
|
|
667
|
+
*/
|
|
668
|
+
toComplex(value) {
|
|
669
|
+
return toComplexValue(this.#getGenomeSource(), value);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* @param {number | ChromosomalLocus} complex
|
|
674
|
+
* @returns {number}
|
|
675
|
+
*/
|
|
676
|
+
fromComplex(complex) {
|
|
677
|
+
return fromComplexValue(this.#getGenomeSource(), complex);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* @param {ScalarDomain | ComplexDomain} interval
|
|
682
|
+
* @returns {number[]}
|
|
683
|
+
*/
|
|
684
|
+
fromComplexInterval(interval) {
|
|
685
|
+
if (this.type == LOCUS) {
|
|
686
|
+
return locusFromComplexInterval(this.#getGenomeSource(), interval);
|
|
687
|
+
}
|
|
688
|
+
return /** @type {number[]} */ (interval);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Reconfigures scale domains for resolutions used by the given view(s).
|
|
694
|
+
*
|
|
695
|
+
* Use this for data-driven updates where only domains need refreshing.
|
|
696
|
+
*
|
|
697
|
+
* TODO: This should be made unnecessary. Collectors should trigger the reconfiguration
|
|
698
|
+
* for those views that get their data from the collector.
|
|
699
|
+
*
|
|
700
|
+
* TODO: This may reconfigure channels that are not affected by the change.
|
|
701
|
+
* Causes performance issues with domains that are extracted from data.
|
|
702
|
+
*
|
|
703
|
+
* @param {import("../view/view.js").default | import("../view/view.js").default[]} fromViews
|
|
704
|
+
* @param {(view: import("../view/view.js").default) => boolean} [viewFilter]
|
|
705
|
+
*/
|
|
706
|
+
export function reconfigureScaleDomains(fromViews, viewFilter) {
|
|
707
|
+
/** @type {Set<ScaleResolution>} */
|
|
708
|
+
const uniqueResolutions = new Set();
|
|
709
|
+
|
|
710
|
+
/** @param {import("../view/view.js").default} view */
|
|
711
|
+
function collectResolutions(view) {
|
|
712
|
+
for (const resolution of Object.values(view.resolutions.scale)) {
|
|
713
|
+
uniqueResolutions.add(resolution);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/** @type {import("../view/view.js").VisitorCallback} */
|
|
718
|
+
function collectVisibleResolutions(view) {
|
|
719
|
+
if (viewFilter && !viewFilter(view)) {
|
|
720
|
+
return VISIT_SKIP;
|
|
721
|
+
}
|
|
722
|
+
if (view.options.contributesToScaleDomain) {
|
|
723
|
+
collectResolutions(view);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
for (const fromView of asArray(fromViews)) {
|
|
728
|
+
fromView.visit(collectVisibleResolutions);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
uniqueResolutions.forEach((resolution) => resolution.reconfigureDomain());
|
|
732
|
+
}
|