@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.
Files changed (249) hide show
  1. package/dist/bundle/browser-BRemItdO.js +138 -0
  2. package/dist/bundle/{index-CD7FLu9x.js → index-BatuyGAI.js} +23 -21
  3. package/dist/bundle/{index-C0llXMqm.js → index-ByuE8dvu.js} +140 -88
  4. package/dist/bundle/index-Cq3QFUxX.js +1781 -0
  5. package/dist/bundle/index-D28m8tSW.js +1607 -0
  6. package/dist/bundle/index-DbJ0oeYM.js +631 -0
  7. package/dist/bundle/index.es.js +15821 -14601
  8. package/dist/bundle/index.js +214 -212
  9. package/dist/bundle/{inflate-DRgHi_KK.js → inflate-GtwLkvSP.js} +222 -224
  10. package/dist/bundle/unzip-NywezaRR.js +1492 -0
  11. package/dist/schema.json +13 -3
  12. package/dist/src/config/scaleDefaults.d.ts +8 -0
  13. package/dist/src/config/scaleDefaults.d.ts.map +1 -0
  14. package/dist/src/config/scaleDefaults.js +45 -0
  15. package/dist/src/data/flowHandle.d.ts +2 -0
  16. package/dist/src/data/flowHandle.d.ts.map +1 -1
  17. package/dist/src/data/flowHandle.js +1 -0
  18. package/dist/src/data/flowInit.d.ts +12 -4
  19. package/dist/src/data/flowInit.d.ts.map +1 -1
  20. package/dist/src/data/flowInit.js +115 -16
  21. package/dist/src/data/sources/lazy/axisTickSource.js +1 -1
  22. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
  23. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  24. package/dist/src/data/sources/lazy/singleAxisLazySource.js +10 -3
  25. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  26. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +5 -1
  27. package/dist/src/data/transforms/filterScoredLabels.d.ts +1 -1
  28. package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
  29. package/dist/src/data/transforms/filterScoredLabels.js +1 -1
  30. package/dist/src/data/transforms/linearizeGenomicCoordinate.d.ts.map +1 -1
  31. package/dist/src/data/transforms/linearizeGenomicCoordinate.js +2 -1
  32. package/dist/src/encoder/encoder.d.ts +1 -1
  33. package/dist/src/encoder/encoder.d.ts.map +1 -1
  34. package/dist/src/encoder/encoder.js +1 -1
  35. package/dist/src/genome/scaleLocus.d.ts +39 -0
  36. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  37. package/dist/src/genome/scaleLocus.js +76 -0
  38. package/dist/src/genomeSpy/canvasExport.d.ts +19 -0
  39. package/dist/src/genomeSpy/canvasExport.d.ts.map +1 -0
  40. package/dist/src/genomeSpy/canvasExport.js +66 -0
  41. package/dist/src/genomeSpy/containerUi.d.ts +17 -0
  42. package/dist/src/genomeSpy/containerUi.d.ts.map +1 -0
  43. package/dist/src/genomeSpy/containerUi.js +78 -0
  44. package/dist/src/genomeSpy/eventListenerRegistry.d.ts +19 -0
  45. package/dist/src/genomeSpy/eventListenerRegistry.d.ts.map +1 -0
  46. package/dist/src/genomeSpy/eventListenerRegistry.js +38 -0
  47. package/dist/src/genomeSpy/inputBindingManager.d.ts +14 -0
  48. package/dist/src/genomeSpy/inputBindingManager.d.ts.map +1 -0
  49. package/dist/src/genomeSpy/inputBindingManager.js +63 -0
  50. package/dist/src/genomeSpy/interactionController.d.ts +40 -0
  51. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -0
  52. package/dist/src/genomeSpy/interactionController.js +371 -0
  53. package/dist/src/genomeSpy/keyboardListenerManager.d.ts +10 -0
  54. package/dist/src/genomeSpy/keyboardListenerManager.d.ts.map +1 -0
  55. package/dist/src/genomeSpy/keyboardListenerManager.js +31 -0
  56. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +15 -0
  57. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -0
  58. package/dist/src/genomeSpy/loadingIndicatorManager.js +92 -0
  59. package/dist/src/genomeSpy/renderCoordinator.d.ts +22 -0
  60. package/dist/src/genomeSpy/renderCoordinator.d.ts.map +1 -0
  61. package/dist/src/genomeSpy/renderCoordinator.js +118 -0
  62. package/dist/src/genomeSpy/viewContextFactory.d.ts +18 -0
  63. package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -0
  64. package/dist/src/genomeSpy/viewContextFactory.js +79 -0
  65. package/dist/src/genomeSpy/viewDataInit.d.ts +22 -0
  66. package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -0
  67. package/dist/src/genomeSpy/viewDataInit.js +160 -0
  68. package/dist/src/genomeSpy/viewDataInit.test.d.ts +2 -0
  69. package/dist/src/genomeSpy/viewDataInit.test.d.ts.map +1 -0
  70. package/dist/src/genomeSpy/viewHierarchyConfig.d.ts +14 -0
  71. package/dist/src/genomeSpy/viewHierarchyConfig.d.ts.map +1 -0
  72. package/dist/src/genomeSpy/viewHierarchyConfig.js +24 -0
  73. package/dist/src/genomeSpy/viewHighlight.d.ts +5 -0
  74. package/dist/src/genomeSpy/viewHighlight.d.ts.map +1 -0
  75. package/dist/src/genomeSpy/viewHighlight.js +30 -0
  76. package/dist/src/genomeSpy.d.ts +17 -71
  77. package/dist/src/genomeSpy.d.ts.map +1 -1
  78. package/dist/src/genomeSpy.js +197 -741
  79. package/dist/src/gl/dataToVertices.d.ts.map +1 -1
  80. package/dist/src/gl/dataToVertices.js +16 -4
  81. package/dist/src/gl/glslScaleGenerator.d.ts +1 -1
  82. package/dist/src/gl/webGLHelper.d.ts +2 -2
  83. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  84. package/dist/src/gl/webGLHelper.js +4 -4
  85. package/dist/src/index.d.ts.map +1 -1
  86. package/dist/src/index.js +2 -12
  87. package/dist/src/marks/mark.d.ts.map +1 -1
  88. package/dist/src/marks/mark.js +4 -2
  89. package/dist/src/{view → scales}/axisResolution.d.ts +9 -16
  90. package/dist/src/scales/axisResolution.d.ts.map +1 -0
  91. package/dist/src/{view → scales}/axisResolution.js +29 -18
  92. package/dist/src/scales/axisResolution.test.d.ts.map +1 -0
  93. package/dist/src/scales/scaleDomainAggregator.d.ts +57 -0
  94. package/dist/src/scales/scaleDomainAggregator.d.ts.map +1 -0
  95. package/dist/src/scales/scaleDomainAggregator.js +167 -0
  96. package/dist/src/scales/scaleDomainAggregator.test.d.ts +2 -0
  97. package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +1 -0
  98. package/dist/src/scales/scaleInstanceManager.d.ts +40 -0
  99. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -0
  100. package/dist/src/scales/scaleInstanceManager.js +317 -0
  101. package/dist/src/scales/scaleInstanceManager.test.d.ts +2 -0
  102. package/dist/src/scales/scaleInstanceManager.test.d.ts.map +1 -0
  103. package/dist/src/scales/scaleInteractionController.d.ts +73 -0
  104. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -0
  105. package/dist/src/scales/scaleInteractionController.js +336 -0
  106. package/dist/src/scales/scaleInteractionController.test.d.ts +2 -0
  107. package/dist/src/scales/scaleInteractionController.test.d.ts.map +1 -0
  108. package/dist/src/scales/scalePropsResolver.d.ts +23 -0
  109. package/dist/src/scales/scalePropsResolver.d.ts.map +1 -0
  110. package/dist/src/scales/scalePropsResolver.js +74 -0
  111. package/dist/src/{view → scales}/scaleResolution.d.ts +53 -35
  112. package/dist/src/scales/scaleResolution.d.ts.map +1 -0
  113. package/dist/src/scales/scaleResolution.js +732 -0
  114. package/dist/src/scales/scaleResolution.test.d.ts.map +1 -0
  115. package/dist/src/scales/scaleResolutionConstants.d.ts +6 -0
  116. package/dist/src/scales/scaleResolutionConstants.d.ts.map +1 -0
  117. package/dist/src/scales/scaleResolutionConstants.js +5 -0
  118. package/dist/src/scales/scaleRules.d.ts +16 -0
  119. package/dist/src/scales/scaleRules.d.ts.map +1 -0
  120. package/dist/src/scales/scaleRules.js +103 -0
  121. package/dist/src/scales/scaleRules.test.d.ts +2 -0
  122. package/dist/src/scales/scaleRules.test.d.ts.map +1 -0
  123. package/dist/src/spec/channel.d.ts +13 -18
  124. package/dist/src/spec/scale.d.ts +6 -0
  125. package/dist/src/types/embedApi.d.ts +5 -0
  126. package/dist/src/types/scaleResolutionApi.d.ts +1 -1
  127. package/dist/src/utils/domainArray.d.ts.map +1 -1
  128. package/dist/src/utils/domainArray.js +3 -0
  129. package/dist/src/utils/indexer.d.ts +3 -0
  130. package/dist/src/utils/indexer.d.ts.map +1 -1
  131. package/dist/src/utils/indexer.js +3 -0
  132. package/dist/src/view/concatView.d.ts +18 -0
  133. package/dist/src/view/concatView.d.ts.map +1 -1
  134. package/dist/src/view/concatView.js +73 -0
  135. package/dist/src/view/concatView.test.d.ts +2 -0
  136. package/dist/src/view/concatView.test.d.ts.map +1 -0
  137. package/dist/src/view/containerMutationHelper.d.ts +74 -0
  138. package/dist/src/view/containerMutationHelper.d.ts.map +1 -0
  139. package/dist/src/view/containerMutationHelper.js +118 -0
  140. package/dist/src/view/containerView.d.ts +0 -7
  141. package/dist/src/view/containerView.d.ts.map +1 -1
  142. package/dist/src/view/containerView.js +0 -10
  143. package/dist/src/view/facetView.d.ts.map +1 -1
  144. package/dist/src/view/facetView.js +0 -15
  145. package/dist/src/view/flowBuilder.d.ts +5 -3
  146. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  147. package/dist/src/view/flowBuilder.js +69 -6
  148. package/dist/src/view/gridView/gridChild.d.ts +11 -0
  149. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  150. package/dist/src/view/gridView/gridChild.js +32 -6
  151. package/dist/src/view/gridView/gridView.d.ts +39 -1
  152. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  153. package/dist/src/view/gridView/gridView.js +106 -48
  154. package/dist/src/view/gridView/gridView.test.d.ts +2 -0
  155. package/dist/src/view/gridView/gridView.test.d.ts.map +1 -0
  156. package/dist/src/view/gridView/scrollbar.d.ts +39 -8
  157. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  158. package/dist/src/view/gridView/scrollbar.js +184 -69
  159. package/dist/src/view/layerView.d.ts +14 -0
  160. package/dist/src/view/layerView.d.ts.map +1 -1
  161. package/dist/src/view/layerView.js +66 -0
  162. package/dist/src/view/layerView.test.d.ts +2 -0
  163. package/dist/src/view/layerView.test.d.ts.map +1 -0
  164. package/dist/src/view/testUtils.d.ts.map +1 -1
  165. package/dist/src/view/testUtils.js +7 -1
  166. package/dist/src/view/unitView.d.ts.map +1 -1
  167. package/dist/src/view/unitView.js +41 -36
  168. package/dist/src/view/view.d.ts +18 -6
  169. package/dist/src/view/view.d.ts.map +1 -1
  170. package/dist/src/view/view.js +30 -4
  171. package/package.json +2 -2
  172. package/dist/bundle/browser-txUcLy2H.js +0 -123
  173. package/dist/bundle/index-BQpbYrv4.js +0 -1712
  174. package/dist/bundle/index-BhtHKLUo.js +0 -73
  175. package/dist/bundle/index-CCe8rnZz.js +0 -716
  176. package/dist/bundle/index-DhcU-Gk-.js +0 -1487
  177. package/dist/src/data/collector.test.js +0 -138
  178. package/dist/src/data/dataFlow.test.js +0 -38
  179. package/dist/src/data/flow.test.js +0 -81
  180. package/dist/src/data/flowInit.test.js +0 -413
  181. package/dist/src/data/flowNode.test.js +0 -50
  182. package/dist/src/data/flowOptimizer.test.js +0 -209
  183. package/dist/src/data/formats/fasta.test.js +0 -27
  184. package/dist/src/data/sources/inlineSource.test.js +0 -63
  185. package/dist/src/data/sources/sequenceSource.test.js +0 -81
  186. package/dist/src/data/transforms/aggregate.test.js +0 -134
  187. package/dist/src/data/transforms/clone.test.js +0 -11
  188. package/dist/src/data/transforms/coverage.test.js +0 -238
  189. package/dist/src/data/transforms/filter.test.js +0 -20
  190. package/dist/src/data/transforms/flatten.test.js +0 -96
  191. package/dist/src/data/transforms/flattenDelimited.test.js +0 -90
  192. package/dist/src/data/transforms/flattenSequence.test.js +0 -34
  193. package/dist/src/data/transforms/formula.test.js +0 -25
  194. package/dist/src/data/transforms/identifier.test.js +0 -92
  195. package/dist/src/data/transforms/pileup.test.js +0 -70
  196. package/dist/src/data/transforms/project.test.js +0 -32
  197. package/dist/src/data/transforms/regexExtract.test.js +0 -70
  198. package/dist/src/data/transforms/regexFold.test.js +0 -201
  199. package/dist/src/data/transforms/sample.test.js +0 -38
  200. package/dist/src/data/transforms/stack.test.js +0 -91
  201. package/dist/src/encoder/accessor.test.js +0 -162
  202. package/dist/src/encoder/encoder.test.js +0 -105
  203. package/dist/src/genome/genome.test.js +0 -268
  204. package/dist/src/genome/genomes.test.js +0 -8
  205. package/dist/src/genome/scaleIndex.test.js +0 -78
  206. package/dist/src/genome/scaleLocus.test.js +0 -4
  207. package/dist/src/scale/scale.test.js +0 -326
  208. package/dist/src/scale/ticks.test.js +0 -46
  209. package/dist/src/selection/selection.test.js +0 -14
  210. package/dist/src/utils/addBaseUrl.test.js +0 -30
  211. package/dist/src/utils/binnedIndex.test.js +0 -201
  212. package/dist/src/utils/cloner.test.js +0 -35
  213. package/dist/src/utils/coalesce.test.js +0 -16
  214. package/dist/src/utils/concatIterables.test.js +0 -8
  215. package/dist/src/utils/domainArray.test.js +0 -130
  216. package/dist/src/utils/indexer.test.js +0 -49
  217. package/dist/src/utils/interactionEvent.test.js +0 -35
  218. package/dist/src/utils/iterateNestedMaps.test.js +0 -33
  219. package/dist/src/utils/kWayMerge.test.js +0 -30
  220. package/dist/src/utils/mergeObjects.test.js +0 -42
  221. package/dist/src/utils/numberExtractor.test.js +0 -6
  222. package/dist/src/utils/propertyCacher.test.js +0 -89
  223. package/dist/src/utils/propertyCoalescer.test.js +0 -25
  224. package/dist/src/utils/radixSort.test.js +0 -51
  225. package/dist/src/utils/reservationMap.test.js +0 -20
  226. package/dist/src/utils/ringBuffer.test.js +0 -39
  227. package/dist/src/utils/topK.test.js +0 -54
  228. package/dist/src/utils/trees.test.js +0 -135
  229. package/dist/src/utils/url.test.js +0 -28
  230. package/dist/src/utils/variableTools.test.js +0 -13
  231. package/dist/src/view/axisResolution.d.ts.map +0 -1
  232. package/dist/src/view/axisResolution.test.d.ts.map +0 -1
  233. package/dist/src/view/axisResolution.test.js +0 -206
  234. package/dist/src/view/flowBuilder.test.js +0 -125
  235. package/dist/src/view/gridView/selectionRect.test.js +0 -87
  236. package/dist/src/view/layout/flexLayout.test.js +0 -323
  237. package/dist/src/view/layout/grid.test.js +0 -71
  238. package/dist/src/view/layout/rectangle.test.js +0 -192
  239. package/dist/src/view/paramMediator.test.js +0 -282
  240. package/dist/src/view/scaleResolution.d.ts.map +0 -1
  241. package/dist/src/view/scaleResolution.js +0 -1059
  242. package/dist/src/view/scaleResolution.test.d.ts.map +0 -1
  243. package/dist/src/view/scaleResolution.test.js +0 -645
  244. package/dist/src/view/view.test.js +0 -245
  245. package/dist/src/view/viewDispose.test.js +0 -110
  246. package/dist/src/view/viewFactory.test.js +0 -25
  247. package/dist/src/view/viewUtils.test.js +0 -87
  248. /package/dist/src/{view → scales}/axisResolution.test.d.ts +0 -0
  249. /package/dist/src/{view → scales}/scaleResolution.test.d.ts +0 -0
@@ -0,0 +1,371 @@
1
+ import UnitView from "../view/unitView.js";
2
+ import { VISIT_STOP } from "../view/view.js";
3
+ import { readPickingPixel } from "../gl/webGLHelper.js";
4
+ import InteractionEvent from "../utils/interactionEvent.js";
5
+ import Inertia, { makeEventTemplate } from "../utils/inertia.js";
6
+ import Point from "../view/layout/point.js";
7
+ import { isStillZooming } from "../view/zoom.js";
8
+
9
+ export default class InteractionController {
10
+ /** @type {import("../view/view.js").default} */
11
+ #viewRoot;
12
+ /** @type {import("../gl/webGLHelper.js").default} */
13
+ #glHelper;
14
+ /** @type {import("../utils/ui/tooltip.js").default} */
15
+ #tooltip;
16
+ /** @type {import("../utils/animator.js").default} */
17
+ #animator;
18
+ /** @type {(type: string, event: any) => void} */
19
+ #emitEvent;
20
+ /** @type {Record<string, import("../tooltip/tooltipHandler.js").TooltipHandler>} */
21
+ #tooltipHandlers;
22
+ /** @type {() => void} */
23
+ #renderPickingFramebuffer;
24
+ /** @type {() => number} */
25
+ #getDevicePixelRatio;
26
+ /**
27
+ * @type {{ mark: import("../marks/mark.js").default, datum: import("../data/flowNode.js").Datum, uniqueId: number }}
28
+ */
29
+ #currentHover;
30
+ /** @type {Inertia} */
31
+ #wheelInertia;
32
+ /** @type {Point} */
33
+ #mouseDownCoords;
34
+ /** @type {boolean} */
35
+ #tooltipUpdateRequested;
36
+ /**
37
+ * @param {object} options
38
+ * @param {import("../view/view.js").default} options.viewRoot
39
+ * @param {import("../gl/webGLHelper.js").default} options.glHelper
40
+ * @param {import("../utils/ui/tooltip.js").default} options.tooltip
41
+ * @param {import("../utils/animator.js").default} options.animator
42
+ * @param {(type: string, event: any) => void} options.emitEvent
43
+ * @param {Record<string, import("../tooltip/tooltipHandler.js").TooltipHandler>} options.tooltipHandlers
44
+ * @param {() => void} options.renderPickingFramebuffer
45
+ * @param {() => number} options.getDevicePixelRatio
46
+ */
47
+ constructor({
48
+ viewRoot,
49
+ glHelper,
50
+ tooltip,
51
+ animator,
52
+ emitEvent,
53
+ tooltipHandlers,
54
+ renderPickingFramebuffer,
55
+ getDevicePixelRatio,
56
+ }) {
57
+ this.#viewRoot = viewRoot;
58
+ this.#glHelper = glHelper;
59
+ this.#tooltip = tooltip;
60
+ this.#animator = animator;
61
+ this.#emitEvent = emitEvent;
62
+ this.#tooltipHandlers = tooltipHandlers;
63
+ this.#renderPickingFramebuffer = renderPickingFramebuffer;
64
+ this.#getDevicePixelRatio = getDevicePixelRatio;
65
+
66
+ /**
67
+ * Currently hovered mark and datum
68
+ * @type {{ mark: import("../marks/mark.js").default, datum: import("../data/flowNode.js").Datum, uniqueId: number }}
69
+ */
70
+ this.#currentHover = undefined;
71
+
72
+ this.#wheelInertia = new Inertia(this.#animator);
73
+
74
+ /** @type {Point} */
75
+ this.#mouseDownCoords = undefined;
76
+
77
+ this.#tooltipUpdateRequested = false;
78
+ }
79
+
80
+ getCurrentHover() {
81
+ return this.#currentHover;
82
+ }
83
+
84
+ registerMouseEvents() {
85
+ const canvas = this.#glHelper.canvas;
86
+
87
+ let lastWheelEvent = performance.now();
88
+ let longPressTriggered = false;
89
+
90
+ /** @param {Event} event */
91
+ const listener = (event) => {
92
+ const now = performance.now();
93
+ const wheeling = now - lastWheelEvent < 200;
94
+
95
+ if (event instanceof MouseEvent) {
96
+ const rect = canvas.getBoundingClientRect();
97
+ const point = new Point(
98
+ event.clientX - rect.left - canvas.clientLeft,
99
+ event.clientY - rect.top - canvas.clientTop
100
+ );
101
+
102
+ if (event.type == "mousemove" && !wheeling) {
103
+ this.#tooltip.handleMouseMove(event);
104
+ this.#tooltipUpdateRequested = false;
105
+
106
+ // Disable picking during dragging. Also postpone picking until
107
+ // the user has stopped zooming as reading pixels from the
108
+ // picking buffer is slow and ruins smooth animations.
109
+ if (event.buttons == 0 && !isStillZooming()) {
110
+ this.#renderPickingFramebuffer();
111
+ this.#handlePicking(point.x, point.y);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * @param {MouseEvent} dispatchedEvent
117
+ */
118
+ const dispatchEvent = (dispatchedEvent) => {
119
+ this.#viewRoot.propagateInteractionEvent(
120
+ new InteractionEvent(point, dispatchedEvent)
121
+ );
122
+
123
+ if (!this.#tooltipUpdateRequested) {
124
+ this.#tooltip.clear();
125
+ }
126
+ };
127
+
128
+ if (event.type != "wheel") {
129
+ this.#wheelInertia.cancel();
130
+ }
131
+
132
+ if (
133
+ (event.type == "mousedown" || event.type == "mouseup") &&
134
+ !isStillZooming()
135
+ ) {
136
+ // Actually, only needed when clicking on a mark
137
+ this.#renderPickingFramebuffer();
138
+ } else if (event.type == "wheel") {
139
+ lastWheelEvent = now;
140
+ this.#tooltipUpdateRequested = false;
141
+
142
+ const wheelEvent = /** @type {WheelEvent} */ (event);
143
+
144
+ if (
145
+ Math.abs(wheelEvent.deltaX) >
146
+ Math.abs(wheelEvent.deltaY)
147
+ ) {
148
+ // If the viewport is panned (horizontally) using the wheel (touchpad),
149
+ // the picking buffer becomes stale and needs redrawing. However, we
150
+ // optimize by just clearing the currently hovered item so that snapping
151
+ // doesn't work incorrectly when zooming in/out.
152
+
153
+ // TODO: More robust solution (handle at higher level such as ScaleResolution's zoom method)
154
+ this.#currentHover = null;
155
+
156
+ this.#wheelInertia.cancel();
157
+ } else {
158
+ // Vertical wheeling zooms.
159
+ // We use inertia to generate fake wheel events for smoother zooming
160
+
161
+ const template = makeEventTemplate(wheelEvent);
162
+
163
+ this.#wheelInertia.setMomentum(
164
+ wheelEvent.deltaY * (wheelEvent.deltaMode ? 80 : 1),
165
+ (delta) => {
166
+ const e = new WheelEvent("wheel", {
167
+ ...template,
168
+ deltaMode: 0,
169
+ deltaX: 0,
170
+ deltaY: delta,
171
+ });
172
+ dispatchEvent(e);
173
+ }
174
+ );
175
+
176
+ wheelEvent.preventDefault();
177
+ return;
178
+ }
179
+ }
180
+
181
+ // TODO: Should be handled at the view level, not globally
182
+ if (event.type == "click") {
183
+ if (longPressTriggered) {
184
+ return;
185
+ }
186
+
187
+ const e = this.#currentHover
188
+ ? {
189
+ type: event.type,
190
+ viewPath: this.#currentHover.mark.unitView
191
+ .getLayoutAncestors()
192
+ .map(
193
+ /** @param {import("../view/view.js").default} view */
194
+ (view) => view.name
195
+ )
196
+ .reverse(),
197
+ datum: this.#currentHover.datum,
198
+ }
199
+ : {
200
+ type: event.type,
201
+ viewPath: null,
202
+ datum: null,
203
+ };
204
+
205
+ this.#emitEvent("click", e);
206
+ }
207
+
208
+ if (
209
+ event.type != "click" ||
210
+ // Suppress click events if the mouse has been dragged
211
+ this.#mouseDownCoords?.subtract(Point.fromMouseEvent(event))
212
+ .length < 3
213
+ ) {
214
+ dispatchEvent(event);
215
+ }
216
+ }
217
+ };
218
+
219
+ [
220
+ "mousedown",
221
+ "mouseup",
222
+ "wheel",
223
+ "click",
224
+ "mousemove",
225
+ "gesturechange",
226
+ "contextmenu",
227
+ "dblclick",
228
+ ].forEach((type) => canvas.addEventListener(type, listener));
229
+
230
+ canvas.addEventListener("mousedown", (/** @type {MouseEvent} */ e) => {
231
+ this.#mouseDownCoords = Point.fromMouseEvent(e);
232
+ if (this.#tooltip.sticky) {
233
+ this.#tooltip.sticky = false;
234
+ this.#tooltip.clear();
235
+ // A hack to prevent selection if the tooltip is sticky.
236
+ // Let the tooltip be destickified first.
237
+ longPressTriggered = true;
238
+ } else {
239
+ longPressTriggered = false;
240
+ }
241
+
242
+ const disableTooltip = () => {
243
+ document.addEventListener(
244
+ "mouseup",
245
+ () => this.#tooltip.popEnabledState(),
246
+ { once: true }
247
+ );
248
+ this.#tooltip.pushEnabledState(false);
249
+ };
250
+
251
+ // Opening context menu or using modifier keys disables the tooltip
252
+ if (e.button == 2 || e.shiftKey || e.ctrlKey || e.metaKey) {
253
+ disableTooltip();
254
+ } else if (this.#tooltip.visible) {
255
+ // Make tooltip sticky if the user long-presses
256
+ const timeout = setTimeout(() => {
257
+ longPressTriggered = true;
258
+ this.#tooltip.sticky = true;
259
+ }, 400);
260
+
261
+ const clear = () => clearTimeout(timeout);
262
+ document.addEventListener("mouseup", clear, { once: true });
263
+ document.addEventListener("mousemove", clear, { once: true });
264
+ }
265
+ });
266
+
267
+ // Prevent text selections etc while dragging
268
+ canvas.addEventListener("dragstart", (event) =>
269
+ event.stopPropagation()
270
+ );
271
+
272
+ canvas.addEventListener("mouseout", () => {
273
+ this.#tooltip.clear();
274
+ this.#currentHover = null;
275
+ });
276
+ }
277
+
278
+ /**
279
+ * @param {number} x
280
+ * @param {number} y
281
+ */
282
+ #handlePicking(x, y) {
283
+ const dpr = this.#getDevicePixelRatio();
284
+ const pp = readPickingPixel(
285
+ this.#glHelper.gl,
286
+ this.#glHelper._pickingBufferInfo,
287
+ x * dpr,
288
+ y * dpr
289
+ );
290
+
291
+ const uniqueId = pp[0] | (pp[1] << 8) | (pp[2] << 16) | (pp[3] << 24);
292
+
293
+ if (uniqueId == 0) {
294
+ this.#currentHover = null;
295
+ return;
296
+ }
297
+
298
+ if (uniqueId !== this.#currentHover?.uniqueId) {
299
+ this.#currentHover = null;
300
+ }
301
+
302
+ if (!this.#currentHover) {
303
+ this.#viewRoot.visit((view) => {
304
+ if (view instanceof UnitView) {
305
+ if (
306
+ view.mark.isPickingParticipant() &&
307
+ [...view.facetCoords.values()].some((coords) =>
308
+ coords.containsPoint(x, y)
309
+ )
310
+ ) {
311
+ const datum = view
312
+ .getCollector()
313
+ .findDatumByUniqueId(uniqueId);
314
+ if (datum) {
315
+ this.#currentHover = {
316
+ mark: view.mark,
317
+ datum,
318
+ uniqueId,
319
+ };
320
+ }
321
+ }
322
+ if (this.#currentHover) {
323
+ return VISIT_STOP;
324
+ }
325
+ }
326
+ });
327
+ }
328
+
329
+ if (this.#currentHover) {
330
+ const mark = this.#currentHover.mark;
331
+ this.updateTooltip(this.#currentHover.datum, async (datum) => {
332
+ if (!mark.isPickingParticipant()) {
333
+ return;
334
+ }
335
+
336
+ const tooltipProps = mark.properties.tooltip;
337
+
338
+ if (tooltipProps !== null) {
339
+ const handlerName = tooltipProps?.handler ?? "default";
340
+ const handler = this.#tooltipHandlers[handlerName];
341
+ if (!handler) {
342
+ throw new Error(
343
+ "No such tooltip handler: " + handlerName
344
+ );
345
+ }
346
+
347
+ return handler(datum, mark, tooltipProps?.params);
348
+ }
349
+ });
350
+ }
351
+ }
352
+
353
+ /**
354
+ * This method should be called in a mouseMove handler. If not called, the
355
+ * tooltip will be hidden.
356
+ *
357
+ * @param {T} datum
358
+ * @param {function(T):Promise<string | HTMLElement | import("lit").TemplateResult>} [converter]
359
+ * @template T
360
+ */
361
+ updateTooltip(datum, converter) {
362
+ if (!this.#tooltipUpdateRequested || !datum) {
363
+ this.#tooltip.updateWithDatum(datum, converter);
364
+ this.#tooltipUpdateRequested = true;
365
+ } else {
366
+ throw new Error(
367
+ "Tooltip has already been updated! Duplicate event handler?"
368
+ );
369
+ }
370
+ }
371
+ }
@@ -0,0 +1,10 @@
1
+ export default class KeyboardListenerManager {
2
+ /**
3
+ * @param {"keydown" | "keyup"} type
4
+ * @param {(event: KeyboardEvent) => void} listener
5
+ */
6
+ add(type: "keydown" | "keyup", listener: (event: KeyboardEvent) => void): void;
7
+ removeAll(): void;
8
+ #private;
9
+ }
10
+ //# sourceMappingURL=keyboardListenerManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboardListenerManager.d.ts","sourceRoot":"","sources":["../../../src/genomeSpy/keyboardListenerManager.js"],"names":[],"mappings":"AAAA;IAQI;;;OAGG;IACH,UAHW,SAAS,GAAG,OAAO,YACnB,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,QAUxC;IAED,kBAOC;;CACJ"}
@@ -0,0 +1,31 @@
1
+ export default class KeyboardListenerManager {
2
+ /** @type {Map<string, (function(KeyboardEvent):void)[]>} */
3
+ #listeners;
4
+
5
+ constructor() {
6
+ this.#listeners = new Map();
7
+ }
8
+
9
+ /**
10
+ * @param {"keydown" | "keyup"} type
11
+ * @param {(event: KeyboardEvent) => void} listener
12
+ */
13
+ add(type, listener) {
14
+ document.addEventListener(type, listener);
15
+ let listeners = this.#listeners.get(type);
16
+ if (!listeners) {
17
+ listeners = [];
18
+ this.#listeners.set(type, listeners);
19
+ }
20
+ listeners.push(listener);
21
+ }
22
+
23
+ removeAll() {
24
+ for (const [type, listeners] of this.#listeners) {
25
+ for (const listener of listeners) {
26
+ document.removeEventListener(type, listener);
27
+ }
28
+ }
29
+ this.#listeners.clear();
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ export default class LoadingIndicatorManager {
2
+ /**
3
+ * @param {HTMLElement} loadingIndicatorsElement
4
+ */
5
+ constructor(loadingIndicatorsElement: HTMLElement);
6
+ /**
7
+ * @param {import("../view/view.js").default} view
8
+ * @param {import("../types/viewContext.js").DataLoadingStatus} status
9
+ * @param {string} [detail]
10
+ */
11
+ setDataLoadingStatus(view: import("../view/view.js").default, status: import("../types/viewContext.js").DataLoadingStatus, detail?: string): void;
12
+ updateLayout(): void;
13
+ #private;
14
+ }
15
+ //# sourceMappingURL=loadingIndicatorManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadingIndicatorManager.d.ts","sourceRoot":"","sources":["../../../src/genomeSpy/loadingIndicatorManager.js"],"names":[],"mappings":"AAIA;IAQI;;OAEG;IACH,sCAFW,WAAW,EASrB;IAED;;;;OAIG;IACH,2BAJW,OAAO,iBAAiB,EAAE,OAAO,UACjC,OAAO,yBAAyB,EAAE,iBAAiB,WACnD,MAAM,QAKhB;IAED,qBAwDC;;CACJ"}
@@ -0,0 +1,92 @@
1
+ import { html, nothing, render } from "lit";
2
+ import { styleMap } from "lit/directives/style-map.js";
3
+ import SPINNER from "../img/90-ring-with-bg.svg";
4
+
5
+ export default class LoadingIndicatorManager {
6
+ /** @type {HTMLElement} */
7
+ #loadingIndicatorsElement;
8
+ /**
9
+ * @type {Map<import("../view/view.js").default, { status: import("../types/viewContext.js").DataLoadingStatus, detail?: string }>}
10
+ */
11
+ #loadingViews;
12
+
13
+ /**
14
+ * @param {HTMLElement} loadingIndicatorsElement
15
+ */
16
+ constructor(loadingIndicatorsElement) {
17
+ this.#loadingIndicatorsElement = loadingIndicatorsElement;
18
+
19
+ /**
20
+ * @type {Map<import("../view/view.js").default, { status: import("../types/viewContext.js").DataLoadingStatus, detail?: string }>}
21
+ */
22
+ this.#loadingViews = new Map();
23
+ }
24
+
25
+ /**
26
+ * @param {import("../view/view.js").default} view
27
+ * @param {import("../types/viewContext.js").DataLoadingStatus} status
28
+ * @param {string} [detail]
29
+ */
30
+ setDataLoadingStatus(view, status, detail) {
31
+ this.#loadingViews.set(view, { status, detail });
32
+ this.updateLayout();
33
+ }
34
+
35
+ updateLayout() {
36
+ /** @type {import("lit").TemplateResult[]} */
37
+ const indicators = [];
38
+
39
+ const isSomethingVisible = () =>
40
+ [...this.#loadingViews.values()].some(
41
+ (v) => v.status == "loading" || v.status == "error"
42
+ );
43
+
44
+ for (const [view, status] of this.#loadingViews) {
45
+ const c = view.coords;
46
+ if (c) {
47
+ const style = {
48
+ left: `${c.x}px`,
49
+ top: `${c.y}px`,
50
+ width: `${c.width}px`,
51
+ height: `${c.height}px`,
52
+ };
53
+ indicators.push(
54
+ html`<div style=${styleMap(style)}>
55
+ <div class=${status.status}>
56
+ ${status.status == "error"
57
+ ? html`<span
58
+ >Loading
59
+ failed${status.detail
60
+ ? html`: ${status.detail}`
61
+ : nothing}</span
62
+ >`
63
+ : html`
64
+ <img src="${SPINNER}" alt="" />
65
+ <span>Loading...</span>
66
+ `}
67
+ </div>
68
+ </div>`
69
+ );
70
+ }
71
+ }
72
+
73
+ // Do some hacks to stop css animations of the loading indicators.
74
+ // Otherwise they fire animation frames even when their opacity is zero.
75
+ // TODO: Instead of this, replace the animated spinners with static images.
76
+ // Or even better, once more widely supported, use `allow-discrete`
77
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior
78
+ // to enable transition of the display property.
79
+ if (isSomethingVisible()) {
80
+ this.#loadingIndicatorsElement.style.display = "block";
81
+ } else {
82
+ // TODO: Clear previous timeout
83
+ setTimeout(() => {
84
+ if (!isSomethingVisible()) {
85
+ this.#loadingIndicatorsElement.style.display = "none";
86
+ }
87
+ }, 3000);
88
+ }
89
+
90
+ render(indicators, this.#loadingIndicatorsElement);
91
+ }
92
+ }
@@ -0,0 +1,22 @@
1
+ export default class RenderCoordinator {
2
+ /**
3
+ * @param {object} options
4
+ * @param {import("../view/view.js").default} options.viewRoot
5
+ * @param {import("../gl/webGLHelper.js").default} options.glHelper
6
+ * @param {() => string} options.getBackground
7
+ * @param {(type: import("../genomeSpy.js").BroadcastEventType, payload?: any) => void} options.broadcast
8
+ * @param {() => void} options.onLayoutComputed
9
+ */
10
+ constructor({ viewRoot, glHelper, getBackground, broadcast, onLayoutComputed, }: {
11
+ viewRoot: import("../view/view.js").default;
12
+ glHelper: import("../gl/webGLHelper.js").default;
13
+ getBackground: () => string;
14
+ broadcast: (type: import("../genomeSpy.js").BroadcastEventType, payload?: any) => void;
15
+ onLayoutComputed: () => void;
16
+ });
17
+ computeLayout(): void;
18
+ renderAll(): void;
19
+ renderPickingFramebuffer(): void;
20
+ #private;
21
+ }
22
+ //# sourceMappingURL=renderCoordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderCoordinator.d.ts","sourceRoot":"","sources":["../../../src/genomeSpy/renderCoordinator.js"],"names":[],"mappings":"AAIA;IAiBI;;;;;;;OAOG;IACH,iFANG;QAAmD,QAAQ,EAAnD,OAAO,iBAAiB,EAAE,OAAO;QACe,QAAQ,EAAxD,OAAO,sBAAsB,EAAE,OAAO;QAChB,aAAa,EAAnC,MAAM,MAAM;QACyE,SAAS,EAA9F,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,IAAI;QACvD,gBAAgB,EAApC,MAAM,IAAI;KACpB,EAqBA;IAED,sBAkDC;IAED,kBAIC;IAED,iCAOC;;CACJ"}
@@ -0,0 +1,118 @@
1
+ import BufferedViewRenderingContext from "../view/renderingContext/bufferedViewRenderingContext.js";
2
+ import CompositeViewRenderingContext from "../view/renderingContext/compositeViewRenderingContext.js";
3
+ import Rectangle from "../view/layout/rectangle.js";
4
+
5
+ export default class RenderCoordinator {
6
+ /** @type {import("../view/view.js").default} */
7
+ #viewRoot;
8
+ /** @type {import("../gl/webGLHelper.js").default} */
9
+ #glHelper;
10
+ /** @type {() => string} */
11
+ #getBackground;
12
+ /** @type {(type: import("../genomeSpy.js").BroadcastEventType, payload?: any) => void} */
13
+ #broadcast;
14
+ /** @type {() => void} */
15
+ #onLayoutComputed;
16
+ /** @type {BufferedViewRenderingContext} */
17
+ #renderingContext;
18
+ /** @type {BufferedViewRenderingContext} */
19
+ #pickingContext;
20
+ /** @type {boolean} */
21
+ #dirtyPickingBuffer;
22
+ /**
23
+ * @param {object} options
24
+ * @param {import("../view/view.js").default} options.viewRoot
25
+ * @param {import("../gl/webGLHelper.js").default} options.glHelper
26
+ * @param {() => string} options.getBackground
27
+ * @param {(type: import("../genomeSpy.js").BroadcastEventType, payload?: any) => void} options.broadcast
28
+ * @param {() => void} options.onLayoutComputed
29
+ */
30
+ constructor({
31
+ viewRoot,
32
+ glHelper,
33
+ getBackground,
34
+ broadcast,
35
+ onLayoutComputed,
36
+ }) {
37
+ this.#viewRoot = viewRoot;
38
+ this.#glHelper = glHelper;
39
+ this.#getBackground = getBackground;
40
+ this.#broadcast = broadcast;
41
+ this.#onLayoutComputed = onLayoutComputed;
42
+
43
+ /** @type {BufferedViewRenderingContext} */
44
+ this.#renderingContext = undefined;
45
+ /** @type {BufferedViewRenderingContext} */
46
+ this.#pickingContext = undefined;
47
+
48
+ /** Does picking buffer need to be rendered again */
49
+ this.#dirtyPickingBuffer = false;
50
+ }
51
+
52
+ computeLayout() {
53
+ const root = this.#viewRoot;
54
+ if (!root) {
55
+ return;
56
+ }
57
+
58
+ this.#broadcast("layout");
59
+
60
+ const canvasSize = this.#glHelper.getLogicalCanvasSize();
61
+
62
+ if (isNaN(canvasSize.width) || isNaN(canvasSize.height)) {
63
+ // TODO: Figure out what causes this
64
+ console.log(
65
+ `NaN in canvas size: ${canvasSize.width}x${canvasSize.height}. Skipping computeLayout().`
66
+ );
67
+ return;
68
+ }
69
+
70
+ const commonOptions = {
71
+ webGLHelper: this.#glHelper,
72
+ canvasSize,
73
+ devicePixelRatio: window.devicePixelRatio ?? 1,
74
+ };
75
+
76
+ this.#renderingContext = new BufferedViewRenderingContext(
77
+ { picking: false },
78
+ {
79
+ ...commonOptions,
80
+ clearColor: this.#getBackground(),
81
+ }
82
+ );
83
+ this.#pickingContext = new BufferedViewRenderingContext(
84
+ { picking: true },
85
+ {
86
+ ...commonOptions,
87
+ framebufferInfo: this.#glHelper._pickingBufferInfo,
88
+ }
89
+ );
90
+
91
+ root.render(
92
+ new CompositeViewRenderingContext(
93
+ this.#renderingContext,
94
+ this.#pickingContext
95
+ ),
96
+ // Canvas should now be sized based on the root view or the container
97
+ Rectangle.create(0, 0, canvasSize.width, canvasSize.height)
98
+ );
99
+
100
+ this.#onLayoutComputed();
101
+ this.#broadcast("layoutComputed");
102
+ }
103
+
104
+ renderAll() {
105
+ this.#renderingContext?.render();
106
+
107
+ this.#dirtyPickingBuffer = true;
108
+ }
109
+
110
+ renderPickingFramebuffer() {
111
+ if (!this.#dirtyPickingBuffer) {
112
+ return;
113
+ }
114
+
115
+ this.#pickingContext.render();
116
+ this.#dirtyPickingBuffer = false;
117
+ }
118
+ }