@genome-spy/core 0.65.0 → 0.66.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +17587 -16593
- 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/flowInit.js +2 -2
- 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 +12 -0
- package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewDataInit.js +41 -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 +16 -71
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +179 -745
- 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 +162 -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 +313 -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 +52 -35
- package/dist/src/scales/scaleResolution.d.ts.map +1 -0
- package/dist/src/scales/scaleResolution.js +658 -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/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 +114 -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/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 +2 -1
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +24 -34
- package/dist/src/view/view.d.ts +6 -6
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +4 -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
package/dist/src/genomeSpy.js
CHANGED
|
@@ -1,43 +1,37 @@
|
|
|
1
1
|
import { formats as vegaFormats } from "vega-loader";
|
|
2
|
-
import { html, nothing, render } from "lit";
|
|
3
|
-
import { styleMap } from "lit/directives/style-map.js";
|
|
4
|
-
import SPINNER from "./img/90-ring-with-bg.svg";
|
|
5
|
-
|
|
6
|
-
import css from "./styles/genome-spy.css.js";
|
|
7
|
-
import Tooltip from "./utils/ui/tooltip.js";
|
|
8
2
|
|
|
9
3
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "./
|
|
15
|
-
import
|
|
4
|
+
createContainerUi,
|
|
5
|
+
createMessageBox,
|
|
6
|
+
} from "./genomeSpy/containerUi.js";
|
|
7
|
+
import LoadingIndicatorManager from "./genomeSpy/loadingIndicatorManager.js";
|
|
8
|
+
import { createViewHighlighter } from "./genomeSpy/viewHighlight.js";
|
|
9
|
+
import KeyboardListenerManager from "./genomeSpy/keyboardListenerManager.js";
|
|
10
|
+
import EventListenerRegistry from "./genomeSpy/eventListenerRegistry.js";
|
|
11
|
+
import InputBindingManager from "./genomeSpy/inputBindingManager.js";
|
|
12
|
+
|
|
13
|
+
import { calculateCanvasSize } from "./view/viewUtils.js";
|
|
14
|
+
import { initializeViewData } from "./genomeSpy/viewDataInit.js";
|
|
16
15
|
import UnitView from "./view/unitView.js";
|
|
17
16
|
|
|
18
|
-
import WebGLHelper
|
|
19
|
-
framebufferToDataUrl,
|
|
20
|
-
readPickingPixel,
|
|
21
|
-
} from "./gl/webGLHelper.js";
|
|
22
|
-
import Rectangle from "./view/layout/rectangle.js";
|
|
23
|
-
import BufferedViewRenderingContext from "./view/renderingContext/bufferedViewRenderingContext.js";
|
|
24
|
-
import CompositeViewRenderingContext from "./view/renderingContext/compositeViewRenderingContext.js";
|
|
25
|
-
import InteractionEvent from "./utils/interactionEvent.js";
|
|
26
|
-
import Point from "./view/layout/point.js";
|
|
17
|
+
import WebGLHelper from "./gl/webGLHelper.js";
|
|
27
18
|
import Animator from "./utils/animator.js";
|
|
28
19
|
import DataFlow from "./data/dataFlow.js";
|
|
29
20
|
import GenomeStore from "./genome/genomeStore.js";
|
|
30
21
|
import BmFontManager from "./fonts/bmFontManager.js";
|
|
31
22
|
import fasta from "./data/formats/fasta.js";
|
|
32
|
-
import { VISIT_STOP } from "./view/view.js";
|
|
33
|
-
import Inertia, { makeEventTemplate } from "./utils/inertia.js";
|
|
34
23
|
import refseqGeneTooltipHandler from "./tooltip/refseqGeneTooltipHandler.js";
|
|
35
24
|
import dataTooltipHandler from "./tooltip/dataTooltipHandler.js";
|
|
36
25
|
import { invalidatePrefix } from "./utils/propertyCacher.js";
|
|
37
26
|
import { VIEW_ROOT_NAME, ViewFactory } from "./view/viewFactory.js";
|
|
38
|
-
import
|
|
39
|
-
import
|
|
40
|
-
import {
|
|
27
|
+
import InteractionController from "./genomeSpy/interactionController.js";
|
|
28
|
+
import RenderCoordinator from "./genomeSpy/renderCoordinator.js";
|
|
29
|
+
import { createViewContext } from "./genomeSpy/viewContextFactory.js";
|
|
30
|
+
import {
|
|
31
|
+
configureViewHierarchy,
|
|
32
|
+
configureViewOpacity,
|
|
33
|
+
} from "./genomeSpy/viewHierarchyConfig.js";
|
|
34
|
+
import { exportCanvas } from "./genomeSpy/canvasExport.js";
|
|
41
35
|
|
|
42
36
|
/**
|
|
43
37
|
* Events that are broadcasted to all views.
|
|
@@ -47,6 +41,23 @@ import { createFramebufferInfo } from "twgl.js";
|
|
|
47
41
|
vegaFormats("fasta", fasta);
|
|
48
42
|
|
|
49
43
|
export default class GenomeSpy {
|
|
44
|
+
/** @type {(() => void)[]} */
|
|
45
|
+
#destructionCallbacks = [];
|
|
46
|
+
/** @type {RenderCoordinator} */
|
|
47
|
+
#renderCoordinator;
|
|
48
|
+
/** @type {LoadingIndicatorManager} */
|
|
49
|
+
#loadingIndicatorManager;
|
|
50
|
+
/** @type {InputBindingManager} */
|
|
51
|
+
#inputBindingManager;
|
|
52
|
+
/** @type {InteractionController} */
|
|
53
|
+
#interactionController;
|
|
54
|
+
/** @type {WebGLHelper} */
|
|
55
|
+
#glHelper;
|
|
56
|
+
|
|
57
|
+
#keyboardListenerManager = new KeyboardListenerManager();
|
|
58
|
+
#eventListeners = new EventListenerRegistry();
|
|
59
|
+
#extraBroadcastListeners = new EventListenerRegistry();
|
|
60
|
+
|
|
50
61
|
/**
|
|
51
62
|
* @typedef {import("./view/view.js").default} View
|
|
52
63
|
* @typedef {import("./spec/view.js").ViewSpec} ViewSpec
|
|
@@ -66,9 +77,6 @@ export default class GenomeSpy {
|
|
|
66
77
|
|
|
67
78
|
options.inputBindingContainer ??= "default";
|
|
68
79
|
|
|
69
|
-
/** @type {(() => void)[]} */
|
|
70
|
-
this._destructionCallbacks = [];
|
|
71
|
-
|
|
72
80
|
/** Root level configuration object */
|
|
73
81
|
this.spec = spec;
|
|
74
82
|
|
|
@@ -90,43 +98,6 @@ export default class GenomeSpy {
|
|
|
90
98
|
*/
|
|
91
99
|
this.viewVisibilityPredicate = (view) => view.isVisibleInSpec();
|
|
92
100
|
|
|
93
|
-
/** @type {BufferedViewRenderingContext} */
|
|
94
|
-
this._renderingContext = undefined;
|
|
95
|
-
/** @type {BufferedViewRenderingContext} */
|
|
96
|
-
this._pickingContext = undefined;
|
|
97
|
-
|
|
98
|
-
/** Does picking buffer need to be rendered again */
|
|
99
|
-
this._dirtyPickingBuffer = false;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Currently hovered mark and datum
|
|
103
|
-
* @type {{ mark: import("./marks/mark.js").default, datum: import("./data/flowNode.js").Datum, uniqueId: number }}
|
|
104
|
-
*/
|
|
105
|
-
this._currentHover = undefined;
|
|
106
|
-
|
|
107
|
-
this._wheelInertia = new Inertia(this.animator);
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Keeping track so that these can be cleaned up upon finalization.
|
|
111
|
-
* @type {Map<string, (function(KeyboardEvent):void)[]>}
|
|
112
|
-
*/
|
|
113
|
-
this._keyboardListeners = new Map();
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Listers for exposed high-level events such as click on a mark instance.
|
|
117
|
-
* These should probably be in the View class and support bubbling through
|
|
118
|
-
* the hierarchy.
|
|
119
|
-
*
|
|
120
|
-
* @type {Map<string, Set<(event: any) => void>>}
|
|
121
|
-
*/
|
|
122
|
-
this._eventListeners = new Map();
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
*
|
|
126
|
-
* @type {Map<string, Set<(event: any) => void>>}
|
|
127
|
-
*/
|
|
128
|
-
this._extraBroadcastListeners = new Map();
|
|
129
|
-
|
|
130
101
|
/** @type {Record<string, import("./tooltip/tooltipHandler.js").TooltipHandler>}> */
|
|
131
102
|
this.tooltipHandlers = {
|
|
132
103
|
default: dataTooltipHandler,
|
|
@@ -137,20 +108,7 @@ export default class GenomeSpy {
|
|
|
137
108
|
/** @type {View} */
|
|
138
109
|
this.viewRoot = undefined;
|
|
139
110
|
|
|
140
|
-
|
|
141
|
-
* Views that are currently loading data using lazy sources.
|
|
142
|
-
*
|
|
143
|
-
* @type {Map<View, { status: import("./types/viewContext.js").DataLoadingStatus, detail?: string }>}
|
|
144
|
-
*/
|
|
145
|
-
this._loadingViews = new Map();
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* @type {HTMLElement}
|
|
149
|
-
*/
|
|
150
|
-
this._inputBindingContainer = undefined;
|
|
151
|
-
|
|
152
|
-
/** @type {Point} */
|
|
153
|
-
this._mouseDownCoords = undefined;
|
|
111
|
+
this.#inputBindingManager = new InputBindingManager(container, options);
|
|
154
112
|
|
|
155
113
|
this.dpr = window.devicePixelRatio;
|
|
156
114
|
}
|
|
@@ -162,37 +120,7 @@ export default class GenomeSpy {
|
|
|
162
120
|
}
|
|
163
121
|
|
|
164
122
|
#initializeParameterBindings() {
|
|
165
|
-
|
|
166
|
-
const inputs = [];
|
|
167
|
-
|
|
168
|
-
this.viewRoot.visit((view) => {
|
|
169
|
-
const mediator = view.paramMediator;
|
|
170
|
-
inputs.push(...createBindingInputs(mediator));
|
|
171
|
-
});
|
|
172
|
-
const ibc = this.options.inputBindingContainer;
|
|
173
|
-
|
|
174
|
-
if (!ibc || ibc == "none" || !inputs.length) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
this._inputBindingContainer = element("div", {
|
|
179
|
-
className: "gs-input-bindings",
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
if (ibc == "default") {
|
|
183
|
-
this.container.appendChild(this._inputBindingContainer);
|
|
184
|
-
} else if (ibc instanceof HTMLElement) {
|
|
185
|
-
ibc.appendChild(this._inputBindingContainer);
|
|
186
|
-
} else {
|
|
187
|
-
throw new Error("Invalid inputBindingContainer");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (inputs.length) {
|
|
191
|
-
render(
|
|
192
|
-
html`<div class="gs-input-binding">${inputs}</div>`,
|
|
193
|
-
this._inputBindingContainer
|
|
194
|
-
);
|
|
195
|
-
}
|
|
123
|
+
this.#inputBindingManager.initialize(this.viewRoot);
|
|
196
124
|
}
|
|
197
125
|
|
|
198
126
|
/**
|
|
@@ -232,6 +160,22 @@ export default class GenomeSpy {
|
|
|
232
160
|
this.animator.requestRender();
|
|
233
161
|
}
|
|
234
162
|
|
|
163
|
+
/**
|
|
164
|
+
* @param {string} type
|
|
165
|
+
* @param {(event: any) => void} listener
|
|
166
|
+
*/
|
|
167
|
+
addEventListener(type, listener) {
|
|
168
|
+
this.#eventListeners.add(type, listener);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {string} type
|
|
173
|
+
* @param {(event: any) => void} listener
|
|
174
|
+
*/
|
|
175
|
+
removeEventListener(type, listener) {
|
|
176
|
+
this.#eventListeners.remove(type, listener);
|
|
177
|
+
}
|
|
178
|
+
|
|
235
179
|
/**
|
|
236
180
|
* Broadcast a message to all views
|
|
237
181
|
|
|
@@ -241,71 +185,7 @@ export default class GenomeSpy {
|
|
|
241
185
|
broadcast(type, payload) {
|
|
242
186
|
const message = { type, payload };
|
|
243
187
|
this.viewRoot.visit((view) => view.handleBroadcast(message));
|
|
244
|
-
this.
|
|
245
|
-
.get(type)
|
|
246
|
-
?.forEach((listener) => listener(message));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Draw some layers on top of the canvas. It's easier to do fancy spinning
|
|
251
|
-
* animations with html elements than with WebGL.
|
|
252
|
-
*/
|
|
253
|
-
_updateLoadingIndicators() {
|
|
254
|
-
/** @type {import("lit").TemplateResult[]} */
|
|
255
|
-
const indicators = [];
|
|
256
|
-
|
|
257
|
-
const isSomethingVisible = () =>
|
|
258
|
-
[...this._loadingViews.values()].some(
|
|
259
|
-
(v) => v.status == "loading" || v.status == "error"
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
for (const [view, status] of this._loadingViews) {
|
|
263
|
-
const c = view.coords;
|
|
264
|
-
if (c) {
|
|
265
|
-
const style = {
|
|
266
|
-
left: `${c.x}px`,
|
|
267
|
-
top: `${c.y}px`,
|
|
268
|
-
width: `${c.width}px`,
|
|
269
|
-
height: `${c.height}px`,
|
|
270
|
-
};
|
|
271
|
-
indicators.push(
|
|
272
|
-
html`<div style=${styleMap(style)}>
|
|
273
|
-
<div class=${status.status}>
|
|
274
|
-
${status.status == "error"
|
|
275
|
-
? html`<span
|
|
276
|
-
>Loading
|
|
277
|
-
failed${status.detail
|
|
278
|
-
? html`: ${status.detail}`
|
|
279
|
-
: nothing}</span
|
|
280
|
-
>`
|
|
281
|
-
: html`
|
|
282
|
-
<img src="${SPINNER}" alt="" />
|
|
283
|
-
<span>Loading...</span>
|
|
284
|
-
`}
|
|
285
|
-
</div>
|
|
286
|
-
</div>`
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Do some hacks to stop css animations of the loading indicators.
|
|
292
|
-
// Otherwise they fire animation frames even when their opacity is zero.
|
|
293
|
-
// TODO: Instead of this, replace the animated spinners with static images.
|
|
294
|
-
// Or even better, once more widely supported, use `allow-discrete`
|
|
295
|
-
// https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior
|
|
296
|
-
// to enable transition of the display property.
|
|
297
|
-
if (isSomethingVisible()) {
|
|
298
|
-
this.loadingIndicatorsElement.style.display = "block";
|
|
299
|
-
} else {
|
|
300
|
-
// TODO: Clear previous timeout
|
|
301
|
-
setTimeout(() => {
|
|
302
|
-
if (!isSomethingVisible()) {
|
|
303
|
-
this.loadingIndicatorsElement.style.display = "none";
|
|
304
|
-
}
|
|
305
|
-
}, 3000);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
render(indicators, this.loadingIndicatorsElement);
|
|
188
|
+
this.#extraBroadcastListeners.emit(type, message);
|
|
309
189
|
}
|
|
310
190
|
|
|
311
191
|
#setupDpr() {
|
|
@@ -315,7 +195,7 @@ export default class GenomeSpy {
|
|
|
315
195
|
);
|
|
316
196
|
|
|
317
197
|
const resizeCallback = () => {
|
|
318
|
-
this.
|
|
198
|
+
this.#glHelper.invalidateSize();
|
|
319
199
|
this.dpr = window.devicePixelRatio;
|
|
320
200
|
dprSetter(this.dpr);
|
|
321
201
|
this.computeLayout();
|
|
@@ -327,7 +207,7 @@ export default class GenomeSpy {
|
|
|
327
207
|
// TODO: Size should be observed only if the content is not absolutely sized
|
|
328
208
|
const resizeObserver = new ResizeObserver(resizeCallback);
|
|
329
209
|
resizeObserver.observe(this.container);
|
|
330
|
-
this.
|
|
210
|
+
this.#destructionCallbacks.push(() => resizeObserver.disconnect());
|
|
331
211
|
}
|
|
332
212
|
|
|
333
213
|
/** @type {() => void} */
|
|
@@ -349,25 +229,19 @@ export default class GenomeSpy {
|
|
|
349
229
|
updatePixelRatio();
|
|
350
230
|
|
|
351
231
|
if (remove) {
|
|
352
|
-
this.
|
|
232
|
+
this.#destructionCallbacks.push(remove);
|
|
353
233
|
}
|
|
354
234
|
}
|
|
355
235
|
|
|
356
236
|
#prepareContainer() {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const canvasWrapper = element("div", {
|
|
364
|
-
class: "canvas-wrapper",
|
|
365
|
-
});
|
|
366
|
-
this.container.appendChild(canvasWrapper);
|
|
367
|
-
|
|
368
|
-
canvasWrapper.classList.add("loading");
|
|
237
|
+
const {
|
|
238
|
+
canvasWrapper,
|
|
239
|
+
loadingMessageElement,
|
|
240
|
+
loadingIndicatorsElement,
|
|
241
|
+
tooltip,
|
|
242
|
+
} = createContainerUi(this.container);
|
|
369
243
|
|
|
370
|
-
this
|
|
244
|
+
this.#glHelper = new WebGLHelper(
|
|
371
245
|
canvasWrapper,
|
|
372
246
|
() =>
|
|
373
247
|
this.viewRoot
|
|
@@ -377,30 +251,16 @@ export default class GenomeSpy {
|
|
|
377
251
|
);
|
|
378
252
|
|
|
379
253
|
// The initial loading message that is shown until the first frame is rendered
|
|
380
|
-
this.loadingMessageElement =
|
|
381
|
-
class: "loading-message",
|
|
382
|
-
innerHTML: `<div class="message">Loading<span class="ellipsis">...</span></div>`,
|
|
383
|
-
});
|
|
384
|
-
canvasWrapper.appendChild(this.loadingMessageElement);
|
|
385
|
-
|
|
254
|
+
this.loadingMessageElement = loadingMessageElement;
|
|
386
255
|
// A container for loading indicators (for lazy data sources.)
|
|
387
256
|
// These could alternatively be included in the view hierarchy,
|
|
388
257
|
// but it's easier this way – particularly if we want to show
|
|
389
258
|
// some fancy animated spinners.
|
|
390
|
-
this.loadingIndicatorsElement =
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
this.tooltip = new Tooltip(this.container);
|
|
396
|
-
|
|
397
|
-
this.loadingMessageElement
|
|
398
|
-
.querySelector(".message")
|
|
399
|
-
.addEventListener("transitionend", () => {
|
|
400
|
-
/** @type {HTMLElement} */ (
|
|
401
|
-
this.loadingMessageElement
|
|
402
|
-
).style.display = "none";
|
|
403
|
-
});
|
|
259
|
+
this.loadingIndicatorsElement = loadingIndicatorsElement;
|
|
260
|
+
this.tooltip = tooltip;
|
|
261
|
+
this.#loadingIndicatorManager = new LoadingIndicatorManager(
|
|
262
|
+
loadingIndicatorsElement
|
|
263
|
+
);
|
|
404
264
|
}
|
|
405
265
|
|
|
406
266
|
/**
|
|
@@ -414,128 +274,91 @@ export default class GenomeSpy {
|
|
|
414
274
|
this.container.classList.remove("genome-spy");
|
|
415
275
|
canvasWrapper.classList.remove("loading");
|
|
416
276
|
|
|
417
|
-
|
|
418
|
-
for (const listener of listeners) {
|
|
419
|
-
document.removeEventListener(type, listener);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
277
|
+
this.#keyboardListenerManager.removeAll();
|
|
422
278
|
|
|
423
|
-
this.
|
|
279
|
+
this.#destructionCallbacks.forEach((callback) => callback());
|
|
424
280
|
|
|
425
|
-
this.
|
|
281
|
+
this.#glHelper.finalize();
|
|
426
282
|
|
|
427
|
-
this.
|
|
283
|
+
this.#inputBindingManager.remove();
|
|
428
284
|
|
|
429
285
|
while (this.container.firstChild) {
|
|
430
286
|
this.container.firstChild.remove();
|
|
431
287
|
}
|
|
432
288
|
}
|
|
433
289
|
|
|
434
|
-
async
|
|
290
|
+
async #prepareViewsAndData() {
|
|
291
|
+
await this.#initializeGenomeStore();
|
|
292
|
+
const context = this.#createViewContext();
|
|
293
|
+
await this.#initializeViewHierarchy(context);
|
|
294
|
+
await initializeViewData(
|
|
295
|
+
this.viewRoot,
|
|
296
|
+
context.dataFlow,
|
|
297
|
+
context.fontManager,
|
|
298
|
+
(flow) => this.broadcast("dataFlowBuilt", flow)
|
|
299
|
+
);
|
|
300
|
+
this.#finalizeViewInitialization(context);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async #initializeGenomeStore() {
|
|
435
304
|
if (this.spec.genome) {
|
|
436
305
|
this.genomeStore = new GenomeStore(this.spec.baseUrl);
|
|
437
306
|
await this.genomeStore.initialize(this.spec.genome);
|
|
438
307
|
}
|
|
308
|
+
}
|
|
439
309
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
/** @type {import("./types/viewContext.js").default} */
|
|
444
|
-
const context = {
|
|
310
|
+
#createViewContext() {
|
|
311
|
+
return createViewContext({
|
|
445
312
|
dataFlow: new DataFlow(),
|
|
446
|
-
glHelper: this
|
|
313
|
+
glHelper: this.#glHelper,
|
|
447
314
|
animator: this.animator,
|
|
448
315
|
genomeStore: this.genomeStore,
|
|
449
|
-
fontManager: new BmFontManager(this
|
|
450
|
-
|
|
451
|
-
requestLayoutReflow: () => {
|
|
452
|
-
// placeholder
|
|
453
|
-
},
|
|
316
|
+
fontManager: new BmFontManager(this.#glHelper),
|
|
454
317
|
updateTooltip: this.updateTooltip.bind(this),
|
|
455
318
|
getNamedDataFromProvider: this.getNamedDataFromProvider.bind(this),
|
|
456
|
-
getCurrentHover: () =>
|
|
457
|
-
|
|
458
|
-
setDataLoadingStatus: (view, status, detail) =>
|
|
459
|
-
this.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
319
|
+
getCurrentHover: () =>
|
|
320
|
+
this.#interactionController.getCurrentHover(),
|
|
321
|
+
setDataLoadingStatus: (view, status, detail) =>
|
|
322
|
+
this.#loadingIndicatorManager.setDataLoadingStatus(
|
|
323
|
+
view,
|
|
324
|
+
status,
|
|
325
|
+
detail
|
|
326
|
+
),
|
|
463
327
|
addKeyboardListener: (type, listener) => {
|
|
464
328
|
// TODO: Listeners should be called only when the mouse pointer is inside the
|
|
465
329
|
// container or the app covers the full document.
|
|
466
|
-
|
|
467
|
-
let listeners = this._keyboardListeners.get(type);
|
|
468
|
-
if (!listeners) {
|
|
469
|
-
listeners = [];
|
|
470
|
-
this._keyboardListeners.set(type, listeners);
|
|
471
|
-
}
|
|
472
|
-
listeners.push(listener);
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
addBroadcastListener(type, listener) {
|
|
476
|
-
const listenersByType = self._extraBroadcastListeners;
|
|
477
|
-
|
|
478
|
-
// Copy-paste code. TODO: Refactor into a helper function.
|
|
479
|
-
let listeners = listenersByType.get(type);
|
|
480
|
-
if (!listeners) {
|
|
481
|
-
listeners = new Set();
|
|
482
|
-
listenersByType.set(type, listeners);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
listeners.add(listener);
|
|
330
|
+
this.#keyboardListenerManager.add(type, listener);
|
|
486
331
|
},
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
isViewSpec: (spec) => self.viewFactory.isViewSpec(spec),
|
|
497
|
-
|
|
498
|
-
createOrImportView: async function (
|
|
332
|
+
addBroadcastListener: (type, listener) =>
|
|
333
|
+
this.#extraBroadcastListeners.add(type, listener),
|
|
334
|
+
removeBroadcastListener: (type, listener) =>
|
|
335
|
+
this.#extraBroadcastListeners.remove(type, listener),
|
|
336
|
+
isViewConfiguredVisible: this.viewVisibilityPredicate,
|
|
337
|
+
isViewSpec: (spec) => this.viewFactory.isViewSpec(spec),
|
|
338
|
+
createOrImportViewWithContext: (
|
|
339
|
+
ctx,
|
|
499
340
|
spec,
|
|
500
341
|
layoutParent,
|
|
501
342
|
dataParent,
|
|
502
343
|
defaultName,
|
|
503
344
|
validator
|
|
504
|
-
)
|
|
505
|
-
|
|
345
|
+
) =>
|
|
346
|
+
this.viewFactory.createOrImportView(
|
|
506
347
|
spec,
|
|
507
|
-
|
|
348
|
+
ctx,
|
|
508
349
|
layoutParent,
|
|
509
350
|
dataParent,
|
|
510
351
|
defaultName,
|
|
511
352
|
validator
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
this.container.querySelector(".view-highlight")?.remove();
|
|
517
|
-
if (view) {
|
|
518
|
-
if (!view.isConfiguredVisible()) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
const coords = view.coords;
|
|
522
|
-
if (coords) {
|
|
523
|
-
const div = document.createElement("div");
|
|
524
|
-
div.className = "view-highlight";
|
|
525
|
-
div.style.position = "absolute";
|
|
526
|
-
div.style.left = coords.x + "px";
|
|
527
|
-
div.style.top = coords.y + "px";
|
|
528
|
-
div.style.width = coords.width + "px";
|
|
529
|
-
div.style.height = coords.height + "px";
|
|
530
|
-
div.style.border = "1px solid green";
|
|
531
|
-
div.style.backgroundColor = "rgba(0, 255, 0, 0.1)";
|
|
532
|
-
div.style.pointerEvents = "none";
|
|
533
|
-
this.container.appendChild(div);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
},
|
|
537
|
-
};
|
|
353
|
+
),
|
|
354
|
+
highlightView: createViewHighlighter(this.container),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
538
357
|
|
|
358
|
+
/**
|
|
359
|
+
* @param {import("./types/viewContext.js").default} context
|
|
360
|
+
*/
|
|
361
|
+
async #initializeViewHierarchy(context) {
|
|
539
362
|
/** @type {ViewSpec & RootConfig} */
|
|
540
363
|
const rootSpec = this.spec;
|
|
541
364
|
|
|
@@ -557,47 +380,51 @@ export default class GenomeSpy {
|
|
|
557
380
|
|
|
558
381
|
this.#initializeParameterBindings();
|
|
559
382
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
setImplicitScaleNames(this.viewRoot);
|
|
563
|
-
|
|
564
|
-
const views = this.viewRoot.getDescendants();
|
|
565
|
-
|
|
566
|
-
// View opacity should be configured after all scales have been resolved.
|
|
567
|
-
// Currently this doesn't work if new views are added dynamically.
|
|
568
|
-
// TODO: Figure out how to handle dynamic view addition/removal nicely.
|
|
569
|
-
views.forEach((view) => view.configureViewOpacity());
|
|
383
|
+
configureViewHierarchy(this.viewRoot);
|
|
384
|
+
configureViewOpacity(this.viewRoot);
|
|
570
385
|
|
|
571
386
|
// We should now have a complete view hierarchy. Let's update the canvas size
|
|
572
387
|
// and ensure that the loading message is visible.
|
|
573
|
-
this.
|
|
574
|
-
this.#
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
this.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
// Have to wait until asynchronous font loading is complete.
|
|
583
|
-
// Text mark's geometry builder needs font metrics before data can be
|
|
584
|
-
// converted into geometries.
|
|
585
|
-
// TODO: Make updateGraphicsData async and await font loading there.
|
|
586
|
-
await context.fontManager.waitUntilReady();
|
|
388
|
+
this.#glHelper.invalidateSize();
|
|
389
|
+
this.#renderCoordinator = new RenderCoordinator({
|
|
390
|
+
viewRoot: this.viewRoot,
|
|
391
|
+
glHelper: this.#glHelper,
|
|
392
|
+
getBackground: () => this.spec.background,
|
|
393
|
+
broadcast: this.broadcast.bind(this),
|
|
394
|
+
onLayoutComputed: () =>
|
|
395
|
+
this.#loadingIndicatorManager.updateLayout(),
|
|
396
|
+
});
|
|
587
397
|
|
|
588
|
-
//
|
|
589
|
-
|
|
398
|
+
// Allow early layout requests from view subscriptions created during initialization.
|
|
399
|
+
// Layout will be recomputed anyway once launch completes.
|
|
400
|
+
context.requestLayoutReflow = this.computeLayout.bind(this);
|
|
590
401
|
|
|
591
|
-
|
|
402
|
+
this.#setupDpr();
|
|
403
|
+
}
|
|
592
404
|
|
|
593
|
-
|
|
405
|
+
/**
|
|
406
|
+
* @param {import("./types/viewContext.js").default} context
|
|
407
|
+
*/
|
|
408
|
+
#finalizeViewInitialization(context) {
|
|
409
|
+
// Allow layout computation (in case a custom context overrode the early assignment).
|
|
594
410
|
// eslint-disable-next-line require-atomic-updates
|
|
595
411
|
context.requestLayoutReflow = this.computeLayout.bind(this);
|
|
596
412
|
|
|
597
413
|
// Invalidate cached sizes to ensure that step-based sizes are current.
|
|
598
414
|
// TODO: This should be done automatically when the domains of band/point scales are updated.
|
|
599
415
|
this.viewRoot.visit((view) => invalidatePrefix(view, "size"));
|
|
600
|
-
this.
|
|
416
|
+
this.#glHelper.invalidateSize();
|
|
417
|
+
|
|
418
|
+
this.#interactionController = new InteractionController({
|
|
419
|
+
viewRoot: this.viewRoot,
|
|
420
|
+
glHelper: this.#glHelper,
|
|
421
|
+
tooltip: this.tooltip,
|
|
422
|
+
animator: this.animator,
|
|
423
|
+
emitEvent: this.#eventListeners.emit.bind(this.#eventListeners),
|
|
424
|
+
tooltipHandlers: this.tooltipHandlers,
|
|
425
|
+
renderPickingFramebuffer: this.renderPickingFramebuffer.bind(this),
|
|
426
|
+
getDevicePixelRatio: () => this.dpr,
|
|
427
|
+
});
|
|
601
428
|
}
|
|
602
429
|
|
|
603
430
|
/**
|
|
@@ -608,7 +435,7 @@ export default class GenomeSpy {
|
|
|
608
435
|
try {
|
|
609
436
|
this.#prepareContainer();
|
|
610
437
|
|
|
611
|
-
await this
|
|
438
|
+
await this.#prepareViewsAndData();
|
|
612
439
|
|
|
613
440
|
this.registerMouseEvents();
|
|
614
441
|
|
|
@@ -621,7 +448,10 @@ export default class GenomeSpy {
|
|
|
621
448
|
reason.view ? `At "${reason.view.getPathString()}": ` : ""
|
|
622
449
|
}${reason.toString()}`;
|
|
623
450
|
console.error(reason.stack);
|
|
624
|
-
|
|
451
|
+
const handled = this.options.onError?.(reason, this.container);
|
|
452
|
+
if (!handled) {
|
|
453
|
+
createMessageBox(this.container, message);
|
|
454
|
+
}
|
|
625
455
|
|
|
626
456
|
return false;
|
|
627
457
|
} finally {
|
|
@@ -634,276 +464,7 @@ export default class GenomeSpy {
|
|
|
634
464
|
}
|
|
635
465
|
|
|
636
466
|
registerMouseEvents() {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
// TODO: This function is huge. Refactor this into a separate class
|
|
640
|
-
// that would also contain state-related stuff that currently pollute the
|
|
641
|
-
// GenomeSpy class.
|
|
642
|
-
|
|
643
|
-
let lastWheelEvent = performance.now();
|
|
644
|
-
|
|
645
|
-
let longPressTriggered = false;
|
|
646
|
-
|
|
647
|
-
/** @param {Event} event */
|
|
648
|
-
const listener = (event) => {
|
|
649
|
-
const now = performance.now();
|
|
650
|
-
const wheeling = now - lastWheelEvent < 200;
|
|
651
|
-
|
|
652
|
-
if (event instanceof MouseEvent) {
|
|
653
|
-
const rect = canvas.getBoundingClientRect();
|
|
654
|
-
const point = new Point(
|
|
655
|
-
event.clientX - rect.left - canvas.clientLeft,
|
|
656
|
-
event.clientY - rect.top - canvas.clientTop
|
|
657
|
-
);
|
|
658
|
-
|
|
659
|
-
if (event.type == "mousemove" && !wheeling) {
|
|
660
|
-
this.tooltip.handleMouseMove(event);
|
|
661
|
-
this._tooltipUpdateRequested = false;
|
|
662
|
-
|
|
663
|
-
// Disable picking during dragging. Also postpone picking until
|
|
664
|
-
// the user has stopped zooming as reading pixels from the
|
|
665
|
-
// picking buffer is slow and ruins smooth animations.
|
|
666
|
-
if (event.buttons == 0 && !isStillZooming()) {
|
|
667
|
-
this.renderPickingFramebuffer();
|
|
668
|
-
this._handlePicking(point.x, point.y);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* @param {MouseEvent} event
|
|
674
|
-
*/
|
|
675
|
-
const dispatchEvent = (event) => {
|
|
676
|
-
this.viewRoot.propagateInteractionEvent(
|
|
677
|
-
new InteractionEvent(point, event)
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
if (!this._tooltipUpdateRequested) {
|
|
681
|
-
this.tooltip.clear();
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
if (event.type != "wheel") {
|
|
686
|
-
this._wheelInertia.cancel();
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if (
|
|
690
|
-
(event.type == "mousedown" || event.type == "mouseup") &&
|
|
691
|
-
!isStillZooming()
|
|
692
|
-
) {
|
|
693
|
-
// Actually, only needed when clicking on a mark
|
|
694
|
-
this.renderPickingFramebuffer();
|
|
695
|
-
} else if (event.type == "wheel") {
|
|
696
|
-
lastWheelEvent = now;
|
|
697
|
-
this._tooltipUpdateRequested = false;
|
|
698
|
-
|
|
699
|
-
const wheelEvent = /** @type {WheelEvent} */ (event);
|
|
700
|
-
|
|
701
|
-
if (
|
|
702
|
-
Math.abs(wheelEvent.deltaX) >
|
|
703
|
-
Math.abs(wheelEvent.deltaY)
|
|
704
|
-
) {
|
|
705
|
-
// If the viewport is panned (horizontally) using the wheel (touchpad),
|
|
706
|
-
// the picking buffer becomes stale and needs redrawing. However, we
|
|
707
|
-
// optimize by just clearing the currently hovered item so that snapping
|
|
708
|
-
// doesn't work incorrectly when zooming in/out.
|
|
709
|
-
|
|
710
|
-
// TODO: More robust solution (handle at higher level such as ScaleResolution's zoom method)
|
|
711
|
-
this._currentHover = null;
|
|
712
|
-
|
|
713
|
-
this._wheelInertia.cancel();
|
|
714
|
-
} else {
|
|
715
|
-
// Vertical wheeling zooms.
|
|
716
|
-
// We use inertia to generate fake wheel events for smoother zooming
|
|
717
|
-
|
|
718
|
-
const template = makeEventTemplate(wheelEvent);
|
|
719
|
-
|
|
720
|
-
this._wheelInertia.setMomentum(
|
|
721
|
-
wheelEvent.deltaY * (wheelEvent.deltaMode ? 80 : 1),
|
|
722
|
-
(delta) => {
|
|
723
|
-
const e = new WheelEvent("wheel", {
|
|
724
|
-
...template,
|
|
725
|
-
deltaMode: 0,
|
|
726
|
-
deltaX: 0,
|
|
727
|
-
deltaY: delta,
|
|
728
|
-
});
|
|
729
|
-
dispatchEvent(e);
|
|
730
|
-
}
|
|
731
|
-
);
|
|
732
|
-
|
|
733
|
-
wheelEvent.preventDefault();
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// TODO: Should be handled at the view level, not globally
|
|
739
|
-
if (event.type == "click") {
|
|
740
|
-
if (longPressTriggered) {
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
const e = this._currentHover
|
|
745
|
-
? {
|
|
746
|
-
type: event.type,
|
|
747
|
-
viewPath: this._currentHover.mark.unitView
|
|
748
|
-
.getLayoutAncestors()
|
|
749
|
-
.map((view) => view.name)
|
|
750
|
-
.reverse(),
|
|
751
|
-
datum: this._currentHover.datum,
|
|
752
|
-
}
|
|
753
|
-
: {
|
|
754
|
-
type: event.type,
|
|
755
|
-
viewPath: null,
|
|
756
|
-
datum: null,
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
this._eventListeners
|
|
760
|
-
.get("click")
|
|
761
|
-
?.forEach((listener) => listener(e));
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
if (
|
|
765
|
-
event.type != "click" ||
|
|
766
|
-
// Suppress click events if the mouse has been dragged
|
|
767
|
-
this._mouseDownCoords?.subtract(Point.fromMouseEvent(event))
|
|
768
|
-
.length < 3
|
|
769
|
-
) {
|
|
770
|
-
dispatchEvent(event);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
[
|
|
776
|
-
"mousedown",
|
|
777
|
-
"mouseup",
|
|
778
|
-
"wheel",
|
|
779
|
-
"click",
|
|
780
|
-
"mousemove",
|
|
781
|
-
"gesturechange",
|
|
782
|
-
"contextmenu",
|
|
783
|
-
"dblclick",
|
|
784
|
-
].forEach((type) => canvas.addEventListener(type, listener));
|
|
785
|
-
|
|
786
|
-
canvas.addEventListener("mousedown", (/** @type {MouseEvent} */ e) => {
|
|
787
|
-
this._mouseDownCoords = Point.fromMouseEvent(e);
|
|
788
|
-
if (this.tooltip.sticky) {
|
|
789
|
-
this.tooltip.sticky = false;
|
|
790
|
-
this.tooltip.clear();
|
|
791
|
-
// A hack to prevent selection if the tooltip is sticky.
|
|
792
|
-
// Let the tooltip be destickified first.
|
|
793
|
-
longPressTriggered = true;
|
|
794
|
-
} else {
|
|
795
|
-
longPressTriggered = false;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
const disableTooltip = () => {
|
|
799
|
-
document.addEventListener(
|
|
800
|
-
"mouseup",
|
|
801
|
-
() => this.tooltip.popEnabledState(),
|
|
802
|
-
{ once: true }
|
|
803
|
-
);
|
|
804
|
-
this.tooltip.pushEnabledState(false);
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
// Opening context menu or using modifier keys disables the tooltip
|
|
808
|
-
if (e.button == 2 || e.shiftKey || e.ctrlKey || e.metaKey) {
|
|
809
|
-
disableTooltip();
|
|
810
|
-
} else if (this.tooltip.visible) {
|
|
811
|
-
// Make tooltip sticky if the user long-presses
|
|
812
|
-
const timeout = setTimeout(() => {
|
|
813
|
-
longPressTriggered = true;
|
|
814
|
-
this.tooltip.sticky = true;
|
|
815
|
-
}, 400);
|
|
816
|
-
|
|
817
|
-
const clear = () => clearTimeout(timeout);
|
|
818
|
-
document.addEventListener("mouseup", clear, { once: true });
|
|
819
|
-
document.addEventListener("mousemove", clear, { once: true });
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
// Prevent text selections etc while dragging
|
|
824
|
-
canvas.addEventListener("dragstart", (event) =>
|
|
825
|
-
event.stopPropagation()
|
|
826
|
-
);
|
|
827
|
-
|
|
828
|
-
canvas.addEventListener("mouseout", () => {
|
|
829
|
-
this.tooltip.clear();
|
|
830
|
-
this._currentHover = null;
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
/**
|
|
835
|
-
* @param {number} x
|
|
836
|
-
* @param {number} y
|
|
837
|
-
*/
|
|
838
|
-
_handlePicking(x, y) {
|
|
839
|
-
const dpr = this.dpr;
|
|
840
|
-
const pp = readPickingPixel(
|
|
841
|
-
this._glHelper.gl,
|
|
842
|
-
this._glHelper._pickingBufferInfo,
|
|
843
|
-
x * dpr,
|
|
844
|
-
y * dpr
|
|
845
|
-
);
|
|
846
|
-
|
|
847
|
-
const uniqueId = pp[0] | (pp[1] << 8) | (pp[2] << 16) | (pp[3] << 24);
|
|
848
|
-
|
|
849
|
-
if (uniqueId == 0) {
|
|
850
|
-
this._currentHover = null;
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
if (uniqueId !== this._currentHover?.uniqueId) {
|
|
855
|
-
this._currentHover = null;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
if (!this._currentHover) {
|
|
859
|
-
this.viewRoot.visit((view) => {
|
|
860
|
-
if (view instanceof UnitView) {
|
|
861
|
-
if (
|
|
862
|
-
view.mark.isPickingParticipant() &&
|
|
863
|
-
[...view.facetCoords.values()].some((coords) =>
|
|
864
|
-
coords.containsPoint(x, y)
|
|
865
|
-
)
|
|
866
|
-
) {
|
|
867
|
-
const datum = view
|
|
868
|
-
.getCollector()
|
|
869
|
-
.findDatumByUniqueId(uniqueId);
|
|
870
|
-
if (datum) {
|
|
871
|
-
this._currentHover = {
|
|
872
|
-
mark: view.mark,
|
|
873
|
-
datum,
|
|
874
|
-
uniqueId,
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
if (this._currentHover) {
|
|
879
|
-
return VISIT_STOP;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
if (this._currentHover) {
|
|
886
|
-
const mark = this._currentHover.mark;
|
|
887
|
-
this.updateTooltip(this._currentHover.datum, async (datum) => {
|
|
888
|
-
if (!mark.isPickingParticipant()) {
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
const tooltipProps = mark.properties.tooltip;
|
|
893
|
-
|
|
894
|
-
if (tooltipProps !== null) {
|
|
895
|
-
const handlerName = tooltipProps?.handler ?? "default";
|
|
896
|
-
const handler = this.tooltipHandlers[handlerName];
|
|
897
|
-
if (!handler) {
|
|
898
|
-
throw new Error(
|
|
899
|
-
"No such tooltip handler: " + handlerName
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
return handler(datum, mark, tooltipProps?.params);
|
|
904
|
-
}
|
|
905
|
-
});
|
|
906
|
-
}
|
|
467
|
+
this.#interactionController.registerMouseEvents();
|
|
907
468
|
}
|
|
908
469
|
|
|
909
470
|
/**
|
|
@@ -915,14 +476,7 @@ export default class GenomeSpy {
|
|
|
915
476
|
* @template T
|
|
916
477
|
*/
|
|
917
478
|
updateTooltip(datum, converter) {
|
|
918
|
-
|
|
919
|
-
this.tooltip.updateWithDatum(datum, converter);
|
|
920
|
-
this._tooltipUpdateRequested = true;
|
|
921
|
-
} else {
|
|
922
|
-
throw new Error(
|
|
923
|
-
"Tooltip has already been updated! Duplicate event handler?"
|
|
924
|
-
);
|
|
925
|
-
}
|
|
479
|
+
this.#interactionController.updateTooltip(datum, converter);
|
|
926
480
|
}
|
|
927
481
|
|
|
928
482
|
/**
|
|
@@ -940,49 +494,14 @@ export default class GenomeSpy {
|
|
|
940
494
|
devicePixelRatio,
|
|
941
495
|
clearColor = "white"
|
|
942
496
|
) {
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
const width = Math.floor(logicalWidth * devicePixelRatio);
|
|
952
|
-
const height = Math.floor(logicalHeight * devicePixelRatio);
|
|
953
|
-
|
|
954
|
-
const framebufferInfo = createFramebufferInfo(
|
|
955
|
-
gl,
|
|
956
|
-
[
|
|
957
|
-
{
|
|
958
|
-
format: gl.RGBA,
|
|
959
|
-
type: gl.UNSIGNED_BYTE,
|
|
960
|
-
minMag: gl.LINEAR,
|
|
961
|
-
wrap: gl.CLAMP_TO_EDGE,
|
|
962
|
-
},
|
|
963
|
-
],
|
|
964
|
-
width,
|
|
965
|
-
height
|
|
966
|
-
);
|
|
967
|
-
|
|
968
|
-
const renderingContext = new BufferedViewRenderingContext(
|
|
969
|
-
{ picking: false },
|
|
970
|
-
{
|
|
971
|
-
webGLHelper: this._glHelper,
|
|
972
|
-
canvasSize: { width: logicalWidth, height: logicalHeight },
|
|
973
|
-
devicePixelRatio,
|
|
974
|
-
clearColor,
|
|
975
|
-
framebufferInfo,
|
|
976
|
-
}
|
|
977
|
-
);
|
|
978
|
-
|
|
979
|
-
this.viewRoot.render(
|
|
980
|
-
renderingContext,
|
|
981
|
-
Rectangle.create(0, 0, logicalWidth, logicalHeight)
|
|
982
|
-
);
|
|
983
|
-
renderingContext.render();
|
|
984
|
-
|
|
985
|
-
const pngUrl = framebufferToDataUrl(gl, framebufferInfo, "image/png");
|
|
497
|
+
const pngUrl = exportCanvas({
|
|
498
|
+
glHelper: this.#glHelper,
|
|
499
|
+
viewRoot: this.viewRoot,
|
|
500
|
+
logicalWidth,
|
|
501
|
+
logicalHeight,
|
|
502
|
+
devicePixelRatio,
|
|
503
|
+
clearColor,
|
|
504
|
+
});
|
|
986
505
|
|
|
987
506
|
// Clean up
|
|
988
507
|
this.computeLayout();
|
|
@@ -991,74 +510,20 @@ export default class GenomeSpy {
|
|
|
991
510
|
return pngUrl;
|
|
992
511
|
}
|
|
993
512
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
this.broadcast("layout");
|
|
1001
|
-
|
|
1002
|
-
const canvasSize = this._glHelper.getLogicalCanvasSize();
|
|
1003
|
-
|
|
1004
|
-
if (isNaN(canvasSize.width) || isNaN(canvasSize.height)) {
|
|
1005
|
-
// TODO: Figure out what causes this
|
|
1006
|
-
console.log(
|
|
1007
|
-
`NaN in canvas size: ${canvasSize.width}x${canvasSize.height}. Skipping computeLayout().`
|
|
1008
|
-
);
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
const commonOptions = {
|
|
1013
|
-
webGLHelper: this._glHelper,
|
|
1014
|
-
canvasSize,
|
|
1015
|
-
devicePixelRatio: window.devicePixelRatio ?? 1,
|
|
1016
|
-
};
|
|
1017
|
-
|
|
1018
|
-
this._renderingContext = new BufferedViewRenderingContext(
|
|
1019
|
-
{ picking: false },
|
|
1020
|
-
{
|
|
1021
|
-
...commonOptions,
|
|
1022
|
-
clearColor: this.spec.background,
|
|
1023
|
-
}
|
|
1024
|
-
);
|
|
1025
|
-
this._pickingContext = new BufferedViewRenderingContext(
|
|
1026
|
-
{ picking: true },
|
|
1027
|
-
{
|
|
1028
|
-
...commonOptions,
|
|
1029
|
-
framebufferInfo: this._glHelper._pickingBufferInfo,
|
|
1030
|
-
}
|
|
1031
|
-
);
|
|
1032
|
-
|
|
1033
|
-
root.render(
|
|
1034
|
-
new CompositeViewRenderingContext(
|
|
1035
|
-
this._renderingContext,
|
|
1036
|
-
this._pickingContext
|
|
1037
|
-
),
|
|
1038
|
-
// Canvas should now be sized based on the root view or the container
|
|
1039
|
-
Rectangle.create(0, 0, canvasSize.width, canvasSize.height)
|
|
1040
|
-
);
|
|
1041
|
-
|
|
1042
|
-
// The view coordinates may have not been known during the initial data loading.
|
|
1043
|
-
// Thus, update them so that possible error messages are shown in the correct place.
|
|
1044
|
-
this._updateLoadingIndicators();
|
|
513
|
+
getLogicalCanvasSize() {
|
|
514
|
+
return this.#glHelper.getLogicalCanvasSize();
|
|
515
|
+
}
|
|
1045
516
|
|
|
1046
|
-
|
|
517
|
+
computeLayout() {
|
|
518
|
+
this.#renderCoordinator.computeLayout();
|
|
1047
519
|
}
|
|
1048
520
|
|
|
1049
521
|
renderAll() {
|
|
1050
|
-
this.
|
|
1051
|
-
|
|
1052
|
-
this._dirtyPickingBuffer = true;
|
|
522
|
+
this.#renderCoordinator.renderAll();
|
|
1053
523
|
}
|
|
1054
524
|
|
|
1055
525
|
renderPickingFramebuffer() {
|
|
1056
|
-
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
this._pickingContext.render();
|
|
1061
|
-
this._dirtyPickingBuffer = false;
|
|
526
|
+
this.#renderCoordinator.renderPickingFramebuffer();
|
|
1062
527
|
}
|
|
1063
528
|
|
|
1064
529
|
getSearchableViews() {
|
|
@@ -1073,7 +538,7 @@ export default class GenomeSpy {
|
|
|
1073
538
|
}
|
|
1074
539
|
|
|
1075
540
|
getNamedScaleResolutions() {
|
|
1076
|
-
/** @type {Map<string, import("./
|
|
541
|
+
/** @type {Map<string, import("./scales/scaleResolution.js").default>} */
|
|
1077
542
|
const resolutions = new Map();
|
|
1078
543
|
this.viewRoot.visit((view) => {
|
|
1079
544
|
for (const resolution of Object.values(view.resolutions.scale)) {
|
|
@@ -1085,34 +550,3 @@ export default class GenomeSpy {
|
|
|
1085
550
|
return resolutions;
|
|
1086
551
|
}
|
|
1087
552
|
}
|
|
1088
|
-
|
|
1089
|
-
/**
|
|
1090
|
-
*
|
|
1091
|
-
* @param {HTMLElement} container
|
|
1092
|
-
* @param {string} message
|
|
1093
|
-
*/
|
|
1094
|
-
function createMessageBox(container, message) {
|
|
1095
|
-
// Uh, need a templating thingy
|
|
1096
|
-
const messageBox = document.createElement("div");
|
|
1097
|
-
messageBox.className = "message-box";
|
|
1098
|
-
const messageText = document.createElement("div");
|
|
1099
|
-
messageText.textContent = message;
|
|
1100
|
-
messageBox.appendChild(messageText);
|
|
1101
|
-
container.appendChild(messageBox);
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* @param {string} tag
|
|
1106
|
-
* @param {Record<string, any>} attrs
|
|
1107
|
-
*/
|
|
1108
|
-
function element(tag, attrs) {
|
|
1109
|
-
const el = document.createElement(tag);
|
|
1110
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
1111
|
-
if (["innerHTML", "innerText", "className"].includes(key)) {
|
|
1112
|
-
// @ts-ignore
|
|
1113
|
-
el[key] = value;
|
|
1114
|
-
}
|
|
1115
|
-
el.setAttribute(key, value);
|
|
1116
|
-
}
|
|
1117
|
-
return el;
|
|
1118
|
-
}
|