@genome-spy/core 0.30.0 → 0.30.3

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 (234) hide show
  1. package/dist/index.es.js +16379 -0
  2. package/dist/index.js +43 -43
  3. package/package.json +10 -7
  4. package/src/data/collector.js +0 -183
  5. package/src/data/collector.test.js +0 -84
  6. package/src/data/dataFlow.js +0 -148
  7. package/src/data/dataFlow.test.js +0 -5
  8. package/src/data/facetNode.js +0 -17
  9. package/src/data/flow.test.js +0 -72
  10. package/src/data/flowBatch.d.ts +0 -40
  11. package/src/data/flowNode.js +0 -283
  12. package/src/data/flowNode.test.js +0 -50
  13. package/src/data/flowOptimizer.js +0 -123
  14. package/src/data/flowOptimizer.test.js +0 -193
  15. package/src/data/flowTestUtils.js +0 -63
  16. package/src/data/formats/fasta.js +0 -32
  17. package/src/data/formats/fasta.test.js +0 -27
  18. package/src/data/sources/dataSource.js +0 -22
  19. package/src/data/sources/dataSourceFactory.js +0 -24
  20. package/src/data/sources/dataUtils.js +0 -78
  21. package/src/data/sources/dynamicCallbackSource.js +0 -57
  22. package/src/data/sources/dynamicSource.js +0 -37
  23. package/src/data/sources/inlineSource.js +0 -67
  24. package/src/data/sources/inlineSource.test.js +0 -56
  25. package/src/data/sources/namedSource.js +0 -79
  26. package/src/data/sources/sequenceSource.js +0 -46
  27. package/src/data/sources/sequenceSource.test.js +0 -46
  28. package/src/data/sources/urlSource.js +0 -74
  29. package/src/data/transforms/aggregate.js +0 -70
  30. package/src/data/transforms/clone.js +0 -40
  31. package/src/data/transforms/clone.test.js +0 -11
  32. package/src/data/transforms/coverage.js +0 -187
  33. package/src/data/transforms/coverage.test.js +0 -123
  34. package/src/data/transforms/filter.js +0 -37
  35. package/src/data/transforms/filter.test.js +0 -18
  36. package/src/data/transforms/filterScoredLabels.js +0 -134
  37. package/src/data/transforms/flattenCompressedExons.js +0 -57
  38. package/src/data/transforms/flattenDelimited.js +0 -74
  39. package/src/data/transforms/flattenDelimited.test.js +0 -87
  40. package/src/data/transforms/flattenSequence.js +0 -39
  41. package/src/data/transforms/flattenSequence.test.js +0 -34
  42. package/src/data/transforms/formula.js +0 -39
  43. package/src/data/transforms/formula.test.js +0 -19
  44. package/src/data/transforms/identifier.js +0 -108
  45. package/src/data/transforms/identifier.test.js +0 -83
  46. package/src/data/transforms/linearizeGenomicCoordinate.js +0 -101
  47. package/src/data/transforms/measureText.js +0 -44
  48. package/src/data/transforms/pileup.js +0 -128
  49. package/src/data/transforms/pileup.test.js +0 -70
  50. package/src/data/transforms/project.js +0 -41
  51. package/src/data/transforms/project.test.js +0 -32
  52. package/src/data/transforms/regexExtract.js +0 -61
  53. package/src/data/transforms/regexExtract.test.js +0 -67
  54. package/src/data/transforms/regexFold.js +0 -141
  55. package/src/data/transforms/regexFold.test.js +0 -160
  56. package/src/data/transforms/sample.js +0 -101
  57. package/src/data/transforms/sample.test.js +0 -38
  58. package/src/data/transforms/stack.js +0 -137
  59. package/src/data/transforms/stack.test.js +0 -91
  60. package/src/data/transforms/transformFactory.js +0 -60
  61. package/src/embedApi.d.ts +0 -67
  62. package/src/encoder/accessor.js +0 -82
  63. package/src/encoder/accessor.test.js +0 -47
  64. package/src/encoder/encoder.js +0 -394
  65. package/src/encoder/encoder.test.js +0 -98
  66. package/src/fonts/Lato-Regular.json +0 -1267
  67. package/src/fonts/Lato-Regular.png +0 -0
  68. package/src/fonts/OFL.txt +0 -93
  69. package/src/fonts/README.md +0 -3
  70. package/src/fonts/bmFont.d.ts +0 -58
  71. package/src/fonts/bmFontManager.js +0 -357
  72. package/src/fonts/bmFontMetrics.js +0 -108
  73. package/src/genome/genome.js +0 -317
  74. package/src/genome/genome.test.js +0 -188
  75. package/src/genome/genomeStore.js +0 -54
  76. package/src/genome/locusFormat.js +0 -31
  77. package/src/genome/scaleIndex.d.ts +0 -38
  78. package/src/genome/scaleIndex.js +0 -166
  79. package/src/genome/scaleIndex.test.js +0 -78
  80. package/src/genome/scaleLocus.d.ts +0 -11
  81. package/src/genome/scaleLocus.js +0 -108
  82. package/src/genome/scaleLocus.test.js +0 -4
  83. package/src/genomeSpy.js +0 -785
  84. package/src/gl/arrayBuilder.js +0 -199
  85. package/src/gl/dataToVertices.js +0 -636
  86. package/src/gl/includes/common.glsl +0 -63
  87. package/src/gl/includes/picking.fragment.glsl +0 -1
  88. package/src/gl/includes/picking.vertex.glsl +0 -27
  89. package/src/gl/includes/sampleFacet.glsl +0 -107
  90. package/src/gl/includes/scales.glsl +0 -112
  91. package/src/gl/link.fragment.glsl +0 -18
  92. package/src/gl/link.vertex.glsl +0 -111
  93. package/src/gl/point.fragment.glsl +0 -123
  94. package/src/gl/point.vertex.glsl +0 -129
  95. package/src/gl/rect.fragment.glsl +0 -51
  96. package/src/gl/rect.vertex.glsl +0 -114
  97. package/src/gl/rule.fragment.glsl +0 -52
  98. package/src/gl/rule.vertex.glsl +0 -89
  99. package/src/gl/text.fragment.glsl +0 -31
  100. package/src/gl/text.vertex.glsl +0 -246
  101. package/src/gl/webGLHelper.js +0 -504
  102. package/src/img/bowtie.svg +0 -1
  103. package/src/img/genomespy-favicon.svg +0 -34
  104. package/src/index.html +0 -11
  105. package/src/index.js +0 -128
  106. package/src/marks/link.js +0 -175
  107. package/src/marks/mark.js +0 -975
  108. package/src/marks/markUtils.js +0 -125
  109. package/src/marks/pointMark.js +0 -251
  110. package/src/marks/rectMark.js +0 -241
  111. package/src/marks/rule.js +0 -250
  112. package/src/marks/text.js +0 -278
  113. package/src/node_modules/.vitest/results.json +0 -1
  114. package/src/scale/colorUtils.js +0 -184
  115. package/src/scale/glslScaleGenerator.js +0 -502
  116. package/src/scale/scale.js +0 -451
  117. package/src/scale/scale.test.js +0 -324
  118. package/src/scale/ticks.js +0 -203
  119. package/src/scale/ticks.test.js +0 -40
  120. package/src/singlePageApp.js +0 -13
  121. package/src/spec/axis.d.ts +0 -296
  122. package/src/spec/channel.d.ts +0 -430
  123. package/src/spec/data.d.ts +0 -196
  124. package/src/spec/font.d.ts +0 -15
  125. package/src/spec/genome.d.ts +0 -35
  126. package/src/spec/mark.d.ts +0 -429
  127. package/src/spec/root.d.ts +0 -17
  128. package/src/spec/sampleView.d.ts +0 -180
  129. package/src/spec/scale.d.ts +0 -273
  130. package/src/spec/title.d.ts +0 -102
  131. package/src/spec/tooltip.d.ts +0 -9
  132. package/src/spec/transform.d.ts +0 -479
  133. package/src/spec/view.d.ts +0 -201
  134. package/src/styles/genome-spy.scss +0 -153
  135. package/src/tooltip/dataTooltipHandler.js +0 -64
  136. package/src/tooltip/refseqGeneTooltipHandler.js +0 -78
  137. package/src/tooltip/tooltipHandler.ts +0 -12
  138. package/src/types/filetypes.d.ts +0 -14
  139. package/src/types/flatqueue.d.ts +0 -53
  140. package/src/types/glsl.d.ts +0 -4
  141. package/src/types/internmap.d.ts +0 -22
  142. package/src/types/object.d.ts +0 -21
  143. package/src/types/vega-loader.d.ts +0 -1
  144. package/src/types/vega-scale.d.ts +0 -60
  145. package/src/utils/addBaseUrl.js +0 -19
  146. package/src/utils/addBaseUrl.test.js +0 -22
  147. package/src/utils/animator.js +0 -83
  148. package/src/utils/arrayUtils.js +0 -61
  149. package/src/utils/binnedIndex.js +0 -167
  150. package/src/utils/binnedIndex.test.js +0 -155
  151. package/src/utils/clamp.js +0 -8
  152. package/src/utils/cloner.js +0 -34
  153. package/src/utils/cloner.test.js +0 -24
  154. package/src/utils/coalesce.js +0 -11
  155. package/src/utils/coalesce.test.js +0 -16
  156. package/src/utils/concatIterables.js +0 -26
  157. package/src/utils/concatIterables.test.js +0 -8
  158. package/src/utils/debounce.js +0 -37
  159. package/src/utils/domainArray.js +0 -216
  160. package/src/utils/domainArray.test.js +0 -130
  161. package/src/utils/eerp.js +0 -13
  162. package/src/utils/expression.js +0 -32
  163. package/src/utils/field.js +0 -28
  164. package/src/utils/formatObject.js +0 -31
  165. package/src/utils/indexer.js +0 -43
  166. package/src/utils/indexer.test.js +0 -47
  167. package/src/utils/inertia.js +0 -124
  168. package/src/utils/interactionEvent.js +0 -33
  169. package/src/utils/iterateNestedMaps.js +0 -21
  170. package/src/utils/iterateNestedMaps.test.js +0 -33
  171. package/src/utils/kWayMerge.js +0 -42
  172. package/src/utils/kWayMerge.test.js +0 -26
  173. package/src/utils/layout/flexLayout.js +0 -368
  174. package/src/utils/layout/flexLayout.test.js +0 -311
  175. package/src/utils/layout/grid.js +0 -95
  176. package/src/utils/layout/grid.test.js +0 -71
  177. package/src/utils/layout/padding.js +0 -120
  178. package/src/utils/layout/point.js +0 -23
  179. package/src/utils/layout/rectangle.js +0 -288
  180. package/src/utils/layout/rectangle.test.js +0 -172
  181. package/src/utils/mergeObjects.js +0 -99
  182. package/src/utils/mergeObjects.test.js +0 -42
  183. package/src/utils/numberExtractor.js +0 -24
  184. package/src/utils/numberExtractor.test.js +0 -6
  185. package/src/utils/point.js +0 -14
  186. package/src/utils/propertyCacher.js +0 -70
  187. package/src/utils/propertyCacher.test.js +0 -85
  188. package/src/utils/propertyCoalescer.js +0 -42
  189. package/src/utils/propertyCoalescer.test.js +0 -22
  190. package/src/utils/reservationMap.js +0 -103
  191. package/src/utils/reservationMap.test.js +0 -20
  192. package/src/utils/scaleNull.js +0 -19
  193. package/src/utils/setOperations.js +0 -75
  194. package/src/utils/smoothstep.js +0 -10
  195. package/src/utils/throttle.js +0 -34
  196. package/src/utils/topK.js +0 -76
  197. package/src/utils/topK.test.js +0 -64
  198. package/src/utils/transition.js +0 -74
  199. package/src/utils/ui/tooltip.js +0 -189
  200. package/src/utils/url.js +0 -22
  201. package/src/utils/variableTools.js +0 -24
  202. package/src/utils/variableTools.test.js +0 -13
  203. package/src/view/axisResolution.js +0 -140
  204. package/src/view/axisResolution.test.js +0 -201
  205. package/src/view/axisView.js +0 -747
  206. package/src/view/concatView.js +0 -45
  207. package/src/view/containerView.js +0 -159
  208. package/src/view/facetView.js +0 -491
  209. package/src/view/flowBuilder.js +0 -367
  210. package/src/view/flowBuilder.test.js +0 -125
  211. package/src/view/gridView.js +0 -786
  212. package/src/view/implicitRootView.js +0 -14
  213. package/src/view/importView.js +0 -19
  214. package/src/view/layerView.js +0 -74
  215. package/src/view/rendering.d.ts +0 -44
  216. package/src/view/renderingContext/compositeViewRenderingContext.js +0 -51
  217. package/src/view/renderingContext/deferredViewRenderingContext.js +0 -176
  218. package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +0 -128
  219. package/src/view/renderingContext/simpleViewRenderingContext.js +0 -64
  220. package/src/view/renderingContext/svgViewRenderingContext.js +0 -125
  221. package/src/view/renderingContext/viewRenderingContext.js +0 -41
  222. package/src/view/scaleResolution.js +0 -797
  223. package/src/view/scaleResolution.test.js +0 -572
  224. package/src/view/scaleResolutionApi.d.ts +0 -40
  225. package/src/view/testUtils.js +0 -51
  226. package/src/view/title.js +0 -165
  227. package/src/view/unitView.js +0 -382
  228. package/src/view/view.js +0 -612
  229. package/src/view/view.test.js +0 -214
  230. package/src/view/viewContext.d.ts +0 -62
  231. package/src/view/viewFactory.js +0 -181
  232. package/src/view/viewFactory.test.js +0 -17
  233. package/src/view/viewUtils.js +0 -327
  234. package/src/view/zoom.js +0 -89
package/src/genomeSpy.js DELETED
@@ -1,785 +0,0 @@
1
- import scaleLocus from "./genome/scaleLocus";
2
- import { scale as vegaScale } from "vega-scale";
3
- import { formats as vegaFormats } from "vega-loader";
4
-
5
- import "./styles/genome-spy.scss";
6
- import Tooltip from "./utils/ui/tooltip";
7
-
8
- import AccessorFactory from "./encoder/accessor";
9
- import {
10
- resolveScalesAndAxes,
11
- processImports,
12
- setImplicitScaleNames,
13
- } from "./view/viewUtils";
14
- import UnitView from "./view/unitView";
15
-
16
- import WebGLHelper from "./gl/webGLHelper";
17
- import Rectangle from "./utils/layout/rectangle";
18
- import DeferredViewRenderingContext from "./view/renderingContext/deferredViewRenderingContext";
19
- import CompositeViewRenderingContext from "./view/renderingContext/compositeViewRenderingContext";
20
- import InteractionEvent from "./utils/interactionEvent";
21
- import Point from "./utils/layout/point";
22
- import Animator from "./utils/animator";
23
- import DataFlow from "./data/dataFlow";
24
- import scaleIndex from "./genome/scaleIndex";
25
- import { buildDataFlow } from "./view/flowBuilder";
26
- import { optimizeDataFlow } from "./data/flowOptimizer";
27
- import scaleNull from "./utils/scaleNull";
28
- import GenomeStore from "./genome/genomeStore";
29
- import BmFontManager from "./fonts/bmFontManager";
30
- import fasta from "./data/formats/fasta";
31
- import { VISIT_STOP } from "./view/view";
32
- import Inertia, { makeEventTemplate } from "./utils/inertia";
33
- import refseqGeneTooltipHandler from "./tooltip/refseqGeneTooltipHandler";
34
- import dataTooltipHandler from "./tooltip/dataTooltipHandler";
35
- import { invalidatePrefix } from "./utils/propertyCacher";
36
- import { ViewFactory } from "./view/viewFactory";
37
- import LayerView from "./view/layerView";
38
- import ImplicitRootView from "./view/implicitRootView";
39
-
40
- /**
41
- * @typedef {import("./spec/view").UnitSpec} UnitSpec
42
- * @typedef {import("./spec/view").ViewSpec} ViewSpec
43
- * @typedef {import("./spec/view").ImportSpec} ImportSpec
44
- * @typedef {import("./spec/view").VConcatSpec} TrackSpec
45
- * @typedef {import("./spec/root").RootSpec} RootSpec
46
- * @typedef {import("./spec/root").RootConfig} RootConfig
47
- */
48
-
49
- // Register scaleLocus to Vega-Scale.
50
- // Loci are discrete but the scale's domain can be adjusted in a continuous manner.
51
- vegaScale("index", scaleIndex, ["continuous"]);
52
- vegaScale("locus", scaleLocus, ["continuous"]);
53
- vegaScale("null", scaleNull, []);
54
-
55
- vegaFormats("fasta", fasta);
56
-
57
- export default class GenomeSpy {
58
- /**
59
- *
60
- * @param {HTMLElement} container
61
- * @param {RootSpec} spec
62
- * @param {import("./embedApi").EmbedOptions} [options]
63
- */
64
- constructor(container, spec, options = {}) {
65
- this.container = container;
66
-
67
- /** Root level configuration object */
68
- this.spec = spec;
69
-
70
- this.accessorFactory = new AccessorFactory();
71
- this.viewFactory = new ViewFactory();
72
-
73
- /** @type {(function(string):object[])[]} */
74
- this.namedDataProviders = [];
75
-
76
- this.animator = new Animator(() => this.renderAll());
77
-
78
- /** @type {GenomeStore} */
79
- this.genomeStore = undefined;
80
-
81
- /**
82
- * View visibility is checked using a predicate that can be overridden
83
- * for more dynamic visibility management.
84
- *
85
- * @type {(view: import("./view/view").default) => boolean}
86
- */
87
- this.viewVisibilityPredicate = (view) => view.isVisibleInSpec();
88
-
89
- /** @type {DeferredViewRenderingContext} */
90
- this._renderingContext = undefined;
91
- /** @type {DeferredViewRenderingContext} */
92
- this._pickingContext = undefined;
93
-
94
- /** Does picking buffer need to be rendered again */
95
- this._dirtyPickingBuffer = false;
96
-
97
- /**
98
- * Currently hovered mark and datum
99
- * @type {{ mark: import("./marks/Mark").default, datum: import("./data/flowNode").Datum, uniqueId: number }}
100
- */
101
- this._currentHover = undefined;
102
-
103
- this._wheelInertia = new Inertia(this.animator);
104
-
105
- /**
106
- * Keeping track so that these can be cleaned up upon finalization.
107
- * @type {Map<string, (function(KeyboardEvent):void)[]>}
108
- */
109
- this._keyboardListeners = new Map();
110
-
111
- /**
112
- * Listers for exposed high-level events such as click on a mark instance.
113
- * These should probably be in the View class and support bubbling through
114
- * the hierarchy.
115
- *
116
- * @type {Map<string, Set<(event: any) => void>>}
117
- */
118
- this._eventListeners = new Map();
119
-
120
- /** @type {Record<string, import("./tooltip/tooltipHandler").TooltipHandler>}> */
121
- this.tooltipHandlers = {
122
- default: dataTooltipHandler,
123
- refseqgene: refseqGeneTooltipHandler,
124
- ...(options.tooltipHandlers ?? {}),
125
- };
126
-
127
- /** @type {import("./view/view").default} */
128
- this.viewRoot = undefined;
129
- }
130
-
131
- /**
132
- *
133
- * @param {(name: string) => any[]} provider
134
- */
135
- registerNamedDataProvider(provider) {
136
- this.namedDataProviders.unshift(provider);
137
- }
138
-
139
- /**
140
- * @param {string} name
141
- */
142
- getNamedDataFromProvider(name) {
143
- for (const provider of this.namedDataProviders) {
144
- const data = provider(name);
145
- if (data) {
146
- return data;
147
- }
148
- }
149
- }
150
-
151
- /**
152
- *
153
- * @param {string} name
154
- * @param {any[]} data
155
- */
156
- updateNamedData(name, data) {
157
- const namedSource =
158
- this.viewRoot.context.dataFlow.findNamedDataSource(name);
159
- if (!namedSource) {
160
- throw new Error("No such named data source: " + name);
161
- }
162
-
163
- namedSource.dataSource.updateDynamicData(data);
164
-
165
- // Scale domains may need adjustment.
166
- // TODO: Refactor so that Collectors handle scale extents etc.
167
- for (const host of namedSource.hosts) {
168
- host.visit((view) => {
169
- for (const resolution of Object.values(
170
- view.resolutions.scale
171
- )) {
172
- // TODO: Only update domain
173
- resolution.reconfigure();
174
- }
175
- });
176
- }
177
-
178
- this.animator.requestRender();
179
- }
180
-
181
- /**
182
- * Broadcast a message to all views
183
- *
184
- * @param {string} type
185
- * @param {any} [payload]
186
- */
187
- broadcast(type, payload) {
188
- const message = { type, payload };
189
- this.viewRoot.visit((view) => view.handleBroadcast(message));
190
- }
191
-
192
- _prepareContainer() {
193
- this.container.classList.add("genome-spy");
194
- this.container.classList.add("loading");
195
-
196
- this._glHelper = new WebGLHelper(this.container, () => {
197
- if (this.viewRoot) {
198
- const size = this.viewRoot
199
- .getSize()
200
- .addPadding(this.viewRoot.getOverhang());
201
-
202
- // If a dimension has an absolutely specified size (in pixels), use it for the canvas size.
203
- // However, if the dimension has a growing component, the canvas should be fit to the
204
- // container.
205
- // TODO: Enforce the minimum size (in case of both absolute and growing components).
206
-
207
- /** @param {import("./utils/layout/flexLayout").SizeDef} dim */
208
- const f = (dim) => (dim.grow > 0 ? undefined : dim.px);
209
- return {
210
- width: f(size.width),
211
- height: f(size.height),
212
- };
213
- }
214
- });
215
-
216
- this.loadingMessageElement = document.createElement("div");
217
- this.loadingMessageElement.className = "loading-message";
218
- this.loadingMessageElement.innerHTML = `<div class="message">Loading<span class="ellipsis">...</span></div>`;
219
- this.container.appendChild(this.loadingMessageElement);
220
-
221
- this.tooltip = new Tooltip(this.container);
222
-
223
- this.loadingMessageElement
224
- .querySelector(".message")
225
- .addEventListener("transitionend", () => {
226
- /** @type {HTMLElement} */ (
227
- this.loadingMessageElement
228
- ).style.display = "none";
229
- });
230
- }
231
-
232
- /**
233
- * Unregisters all listeners, removes all created dom elements, removes all css classes from the container
234
- */
235
- destroy() {
236
- // TODO: There's a memory leak somewhere
237
-
238
- this.container.classList.remove("genome-spy");
239
- this.container.classList.remove("loading");
240
-
241
- for (const [type, listeners] of this._keyboardListeners) {
242
- for (const listener of listeners) {
243
- document.removeEventListener(type, listener);
244
- }
245
- }
246
-
247
- this._glHelper.finalize();
248
-
249
- while (this.container.firstChild) {
250
- this.container.firstChild.remove();
251
- }
252
- }
253
-
254
- async _prepareViewsAndData() {
255
- if (this.spec.genome) {
256
- this.genomeStore = new GenomeStore(this);
257
- await this.genomeStore.initialize(this.spec.genome);
258
- }
259
-
260
- // eslint-disable-next-line consistent-this
261
- const self = this;
262
-
263
- /** @type {import("./view/viewContext").default} */
264
- const context = {
265
- dataFlow: new DataFlow(),
266
- accessorFactory: this.accessorFactory,
267
- glHelper: this._glHelper,
268
- animator: this.animator,
269
- genomeStore: this.genomeStore,
270
- fontManager: new BmFontManager(this._glHelper),
271
- requestLayoutReflow: () => {
272
- // placeholder
273
- },
274
- updateTooltip: this.updateTooltip.bind(this),
275
- getNamedDataFromProvider: this.getNamedDataFromProvider.bind(this),
276
- getCurrentHover: () => this._currentHover,
277
-
278
- addKeyboardListener: (type, listener) => {
279
- // TODO: Listeners should be called only when the mouse pointer is inside the
280
- // container or the app covers the full document.
281
- document.addEventListener(type, listener);
282
- let listeners = this._keyboardListeners.get(type);
283
- if (!listeners) {
284
- listeners = [];
285
- this._keyboardListeners.set(type, listeners);
286
- }
287
- listeners.push(listener);
288
- },
289
-
290
- isViewVisible: self.viewVisibilityPredicate,
291
-
292
- isViewSpec: (spec) => self.viewFactory.isViewSpec(spec),
293
-
294
- createView: function (spec, parent, defaultName) {
295
- return self.viewFactory.createView(
296
- spec,
297
- context,
298
- parent,
299
- defaultName
300
- );
301
- },
302
- };
303
-
304
- /** @type {import("./spec/view").ViewSpec & RootConfig} */
305
- const rootSpec = this.spec;
306
-
307
- if (rootSpec.datasets) {
308
- this.registerNamedDataProvider((name) => rootSpec.datasets[name]);
309
- }
310
-
311
- // Create the view hierarchy
312
- this.viewRoot = context.createView(rootSpec, null, "viewRoot");
313
-
314
- // Replace placeholder ImportViews with actual views.
315
- await processImports(this.viewRoot);
316
-
317
- if (
318
- this.viewRoot instanceof UnitView ||
319
- this.viewRoot instanceof LayerView
320
- ) {
321
- this.viewRoot = new ImplicitRootView(context, this.viewRoot);
322
- }
323
-
324
- // Resolve scales, i.e., if possible, pull them towards the root
325
- resolveScalesAndAxes(this.viewRoot);
326
- setImplicitScaleNames(this.viewRoot);
327
-
328
- // Wrap unit or layer views that need axes
329
- //this.viewRoot = addDecorators(this.viewRoot);
330
-
331
- // We should now have a complete view hierarchy. Let's update the canvas size
332
- // and ensure that the loading message is visible.
333
- this._glHelper.invalidateSize();
334
-
335
- // Collect all unit views to a list because they need plenty of initialization
336
- /** @type {UnitView[]} */
337
- const unitViews = [];
338
- this.viewRoot.visit((view) => {
339
- if (view instanceof UnitView) {
340
- unitViews.push(view);
341
- }
342
- });
343
-
344
- // Build the data flow based on the view hierarchy
345
- const flow = buildDataFlow(this.viewRoot, context.dataFlow);
346
- optimizeDataFlow(flow);
347
- this.broadcast("dataFlowBuilt", flow);
348
-
349
- flow.dataSources.forEach((ds) => console.log(ds.subtreeToString()));
350
-
351
- // Create encoders (accessors, scales and related metadata)
352
- unitViews.forEach((view) => view.mark.initializeEncoders());
353
-
354
- // Compile shaders, create or load textures, etc.
355
- const graphicsInitialized = Promise.all(
356
- unitViews.map((view) => view.mark.initializeGraphics())
357
- );
358
-
359
- for (const view of unitViews) {
360
- flow.addObserver((collector) => {
361
- view.mark.initializeData();
362
- // Update WebGL buffers
363
- view.mark.updateGraphicsData();
364
- }, view);
365
- }
366
-
367
- // Have to wait until asynchronous font loading is complete.
368
- // Text mark's geometry builder needs font metrics before data can be
369
- // converted into geometries.
370
- await context.fontManager.waitUntilReady();
371
-
372
- // Find all data sources and initiate loading
373
- flow.initialize();
374
- await Promise.all(
375
- flow.dataSources.map((dataSource) => dataSource.load())
376
- );
377
-
378
- // Now that all data have been loaded, the domains may need adjusting
379
- this.viewRoot.visit((view) => {
380
- for (const resolution of Object.values(view.resolutions.scale)) {
381
- // TODO: Don't reconfigure multiple times
382
- // IMPORTANT TODO: Check that discrete domains and indexers match!!!!!!!!!
383
- resolution.reconfigure();
384
- }
385
- });
386
-
387
- // This event is needed by SampleView so that it can extract the sample ids
388
- // from the data once they are loaded.
389
- // TODO: It would be great if this could be attached to the data flow,
390
- // because now this is somewhat a hack and is incompatible with dynamic data
391
- // loading in the future.
392
- this.broadcast("dataLoaded");
393
-
394
- await graphicsInitialized;
395
-
396
- this.viewRoot.visit((view) => {
397
- for (const resolution of Object.values(view.resolutions.scale)) {
398
- this._glHelper.createRangeTexture(resolution);
399
- }
400
- });
401
-
402
- for (const view of unitViews) {
403
- view.mark.finalizeGraphicsInitialization();
404
- }
405
-
406
- // Allow layout computation
407
- // eslint-disable-next-line require-atomic-updates
408
- context.requestLayoutReflow = this.computeLayout.bind(this);
409
-
410
- // Invalidate cached sizes to ensure that step-based sizes are current.
411
- // TODO: This should be done automatically when the domains of band/point scales are updated.
412
- this.viewRoot.visit((view) => invalidatePrefix(view, "size"));
413
- this._glHelper.invalidateSize();
414
- }
415
-
416
- /**
417
- * TODO: Come up with a sensible name. And maybe this should be called at the end of the constructor.
418
- * @returns {Promise<boolean>} true if the launch was successful
419
- */
420
- async launch() {
421
- try {
422
- this._prepareContainer();
423
-
424
- await this._prepareViewsAndData();
425
-
426
- this.registerMouseEvents();
427
-
428
- this.computeLayout();
429
- this.animator.requestRender();
430
-
431
- // Register resize listener after the initial layout computation to prevent
432
- // incomplete layouts from accidentally polluting any caches related to sizes.
433
- this._glHelper.addEventListener("resize", () => {
434
- this.computeLayout();
435
- // Render immediately, without RAF
436
- this.renderAll();
437
- });
438
-
439
- return true;
440
- } catch (reason) {
441
- const message = `${
442
- reason.view ? `At "${reason.view.getPathString()}": ` : ""
443
- }${reason.toString()}`;
444
- console.error(reason.stack);
445
- createMessageBox(this.container, message);
446
-
447
- return false;
448
- } finally {
449
- this.container.classList.remove("loading");
450
- // Transition listener doesn't appear to work on observablehq
451
- window.setTimeout(() => {
452
- this.loadingMessageElement.style.display = "none";
453
- }, 2000);
454
- }
455
- }
456
-
457
- registerMouseEvents() {
458
- const canvas = this._glHelper.canvas;
459
-
460
- // TODO: This function is huge. Refactor this into a separate class
461
- // that would also contain state-related stuff that currently pollute the
462
- // GenomeSpy class.
463
-
464
- /** @param {Event} event */
465
- const listener = (event) => {
466
- if (event instanceof MouseEvent) {
467
- if (event.type == "mousemove") {
468
- this.tooltip.handleMouseMove(event);
469
- this._tooltipUpdateRequested = false;
470
-
471
- if (event.buttons == 0) {
472
- // Disable during dragging
473
- this.renderPickingFramebuffer();
474
- }
475
- }
476
-
477
- const rect = canvas.getBoundingClientRect();
478
- const point = new Point(
479
- event.clientX - rect.left - canvas.clientLeft,
480
- event.clientY - rect.top - canvas.clientTop
481
- );
482
-
483
- /**
484
- * @param {MouseEvent} event
485
- */
486
- const dispatchEvent = (event) => {
487
- this.viewRoot.propagateInteractionEvent(
488
- new InteractionEvent(point, event)
489
- );
490
-
491
- if (!this._tooltipUpdateRequested) {
492
- this.tooltip.clear();
493
- }
494
- };
495
-
496
- if (event.type != "wheel") {
497
- this._wheelInertia.cancel();
498
- }
499
-
500
- if (event.type == "mousemove") {
501
- this._handlePicking(point.x, point.y);
502
- } else if (
503
- event.type == "mousedown" ||
504
- event.type == "mouseup"
505
- ) {
506
- this.renderPickingFramebuffer();
507
- } else if (event.type == "wheel") {
508
- this._tooltipUpdateRequested = false;
509
-
510
- const wheelEvent = /** @type {WheelEvent} */ (event);
511
-
512
- if (
513
- Math.abs(wheelEvent.deltaX) >
514
- Math.abs(wheelEvent.deltaY)
515
- ) {
516
- // If the viewport is panned (horizontally) using the wheel (touchpad),
517
- // the picking buffer becomes stale and needs redrawing. However, we
518
- // optimize by just clearing the currently hovered item so that snapping
519
- // doesn't work incorrectly when zooming in/out.
520
-
521
- // TODO: More robust solution (handle at higher level such as ScaleResolution's zoom method)
522
- this._currentHover = null;
523
-
524
- this._wheelInertia.cancel();
525
- } else {
526
- // Vertical wheeling zooms.
527
- // We use inertia to generate fake wheel events for smoother zooming
528
-
529
- const template = makeEventTemplate(wheelEvent);
530
-
531
- this._wheelInertia.setMomentum(
532
- wheelEvent.deltaY * (wheelEvent.deltaMode ? 80 : 1),
533
- (delta) => {
534
- const e = new WheelEvent("wheel", {
535
- ...template,
536
- deltaMode: 0,
537
- deltaX: 0,
538
- deltaY: delta,
539
- });
540
- dispatchEvent(e);
541
- }
542
- );
543
-
544
- wheelEvent.preventDefault();
545
- return;
546
- }
547
- }
548
-
549
- // TODO: Should be handled at the view level, not globally
550
- if (event.type == "click") {
551
- const e = this._currentHover
552
- ? {
553
- type: event.type,
554
- viewPath: [
555
- ...this._currentHover.mark.unitView.getAncestors(),
556
- ]
557
- .map((view) => view.name)
558
- .reverse(),
559
- datum: this._currentHover.datum,
560
- }
561
- : {
562
- type: event.type,
563
- viewPath: null,
564
- datum: null,
565
- };
566
-
567
- this._eventListeners
568
- .get("click")
569
- ?.forEach((listener) => listener(e));
570
- }
571
-
572
- dispatchEvent(event);
573
- }
574
- };
575
-
576
- [
577
- "mousedown",
578
- "mouseup",
579
- "wheel",
580
- "click",
581
- "mousemove",
582
- "gesturechange",
583
- "contextmenu",
584
- ].forEach((type) => canvas.addEventListener(type, listener));
585
-
586
- canvas.addEventListener("mousedown", () => {
587
- document.addEventListener(
588
- "mouseup",
589
- () => this.tooltip.popEnabledState(),
590
- { once: true }
591
- );
592
- this.tooltip.pushEnabledState(false);
593
- });
594
-
595
- // Prevent text selections etc while dragging
596
- canvas.addEventListener("dragstart", (event) =>
597
- event.stopPropagation()
598
- );
599
- }
600
-
601
- /**
602
- * @param {number} x
603
- * @param {number} y
604
- */
605
- _handlePicking(x, y) {
606
- const pixelValue = this._glHelper.readPickingPixel(x, y);
607
-
608
- const uniqueId =
609
- pixelValue[0] | (pixelValue[1] << 8) | (pixelValue[2] << 16);
610
-
611
- if (uniqueId == 0) {
612
- this._currentHover = null;
613
- return;
614
- }
615
-
616
- if (uniqueId !== this._currentHover?.uniqueId) {
617
- this._currentHover = null;
618
- }
619
-
620
- if (!this._currentHover) {
621
- // We are doing an exhaustive search of the data. This is a bit slow with
622
- // millions of items.
623
- // TODO: Optimize by indexing or something
624
-
625
- this.viewRoot.visit((view) => {
626
- if (view instanceof UnitView) {
627
- if (view.mark.isPickingParticipant()) {
628
- const accessor = view.mark.encoders.uniqueId.accessor;
629
- view.getCollector().visitData((d) => {
630
- if (accessor(d) == uniqueId) {
631
- this._currentHover = {
632
- mark: view.mark,
633
- datum: d,
634
- uniqueId,
635
- };
636
- }
637
- });
638
- }
639
- if (this._currentHover) {
640
- return VISIT_STOP;
641
- }
642
- }
643
- });
644
- }
645
-
646
- if (this._currentHover) {
647
- const mark = this._currentHover.mark;
648
- this.updateTooltip(this._currentHover.datum, async (datum) => {
649
- if (!mark.isPickingParticipant()) {
650
- return;
651
- }
652
-
653
- const tooltipProps = mark.properties.tooltip;
654
-
655
- if (tooltipProps !== null) {
656
- const handlerName = tooltipProps?.handler ?? "default";
657
- const handler = this.tooltipHandlers[handlerName];
658
- if (!handler) {
659
- throw new Error(
660
- "No such tooltip handler: " + handlerName
661
- );
662
- }
663
-
664
- return handler(datum, mark, tooltipProps?.params);
665
- }
666
- });
667
- }
668
- }
669
-
670
- /**
671
- * This method should be called in a mouseMove handler. If not called, the
672
- * tooltip will be hidden.
673
- *
674
- * @param {T} datum
675
- * @param {function(T):Promise<string | HTMLElement | import("lit").TemplateResult>} [converter]
676
- * @template T
677
- */
678
- updateTooltip(datum, converter) {
679
- if (!this._tooltipUpdateRequested || !datum) {
680
- this.tooltip.updateWithDatum(datum, converter);
681
- this._tooltipUpdateRequested = true;
682
- } else {
683
- throw new Error(
684
- "Tooltip has already been updated! Duplicate event handler?"
685
- );
686
- }
687
- }
688
-
689
- computeLayout() {
690
- const root = this.viewRoot;
691
- if (!root) {
692
- return;
693
- }
694
-
695
- this.broadcast("layout");
696
-
697
- const canvasSize = this._glHelper.getLogicalCanvasSize();
698
-
699
- if (isNaN(canvasSize.width) || isNaN(canvasSize.height)) {
700
- // TODO: Figure out what causes this
701
- console.log(
702
- `NaN in canvas size: ${canvasSize.width}x${canvasSize.height}. Skipping computeLayout().`
703
- );
704
- return;
705
- }
706
-
707
- this._renderingContext = new DeferredViewRenderingContext(
708
- {
709
- picking: false,
710
- },
711
- this._glHelper
712
- );
713
- this._pickingContext = new DeferredViewRenderingContext(
714
- {
715
- picking: true,
716
- },
717
- this._glHelper
718
- );
719
-
720
- root.render(
721
- new CompositeViewRenderingContext(
722
- this._renderingContext,
723
- this._pickingContext
724
- ),
725
- // Canvas should now be sized based on the root view or the container
726
- Rectangle.create(0, 0, canvasSize.width, canvasSize.height)
727
- );
728
-
729
- this.broadcast("layoutComputed");
730
- }
731
-
732
- renderAll() {
733
- this._renderingContext?.renderDeferred();
734
-
735
- this._dirtyPickingBuffer = true;
736
- }
737
-
738
- renderPickingFramebuffer() {
739
- if (!this._dirtyPickingBuffer) {
740
- return;
741
- }
742
-
743
- this._pickingContext.renderDeferred();
744
- this._dirtyPickingBuffer = false;
745
- }
746
-
747
- getSearchableViews() {
748
- /** @type {UnitView[]} */
749
- const views = [];
750
- this.viewRoot.visit((view) => {
751
- if (view instanceof UnitView && view.getAccessor("search")) {
752
- views.push(view);
753
- }
754
- });
755
- return views;
756
- }
757
-
758
- getNamedScaleResolutions() {
759
- /** @type {Map<string, import("./view/scaleResolution").default>} */
760
- const resolutions = new Map();
761
- this.viewRoot.visit((view) => {
762
- for (const resolution of Object.values(view.resolutions.scale)) {
763
- if (resolution.name) {
764
- resolutions.set(resolution.name, resolution);
765
- }
766
- }
767
- });
768
- return resolutions;
769
- }
770
- }
771
-
772
- /**
773
- *
774
- * @param {HTMLElement} container
775
- * @param {string} message
776
- */
777
- function createMessageBox(container, message) {
778
- // Uh, need a templating thingy
779
- const messageBox = document.createElement("div");
780
- messageBox.className = "message-box";
781
- const messageText = document.createElement("div");
782
- messageText.textContent = message;
783
- messageBox.appendChild(messageText);
784
- container.appendChild(messageBox);
785
- }