@genome-spy/core 0.71.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +1 -1
  2. package/dist/bundle/index.es.js +6842 -5365
  3. package/dist/bundle/index.js +159 -140
  4. package/dist/bundle/parquetRead-BnAGCa4_.js +1663 -0
  5. package/dist/schema.json +281 -17
  6. package/dist/src/data/formats/bed.d.ts +8 -0
  7. package/dist/src/data/formats/bed.d.ts.map +1 -0
  8. package/dist/src/data/formats/bed.js +53 -0
  9. package/dist/src/data/formats/bedpe.d.ts +8 -0
  10. package/dist/src/data/formats/bedpe.d.ts.map +1 -0
  11. package/dist/src/data/formats/bedpe.js +160 -0
  12. package/dist/src/data/formats/parquet.d.ts +12 -0
  13. package/dist/src/data/formats/parquet.d.ts.map +1 -0
  14. package/dist/src/data/formats/parquet.js +29 -0
  15. package/dist/src/data/formats/parquetRead.d.ts +18 -0
  16. package/dist/src/data/formats/parquetRead.d.ts.map +1 -0
  17. package/dist/src/data/formats/parquetRead.js +326 -0
  18. package/dist/src/data/sources/dataUtils.d.ts +16 -0
  19. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  20. package/dist/src/data/sources/dataUtils.js +53 -3
  21. package/dist/src/data/sources/urlSource.d.ts +4 -0
  22. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  23. package/dist/src/data/sources/urlSource.js +141 -17
  24. package/dist/src/encoder/encoder.d.ts +2 -2
  25. package/dist/src/fonts/bmFontManager.d.ts +1 -1
  26. package/dist/src/genome/assemblyPreflight.d.ts +31 -0
  27. package/dist/src/genome/assemblyPreflight.d.ts.map +1 -0
  28. package/dist/src/genome/assemblyPreflight.js +99 -0
  29. package/dist/src/genome/genome.d.ts +2 -2
  30. package/dist/src/genome/genome.d.ts.map +1 -1
  31. package/dist/src/genome/genome.js +4 -0
  32. package/dist/src/genome/genomeStore.d.ts +34 -3
  33. package/dist/src/genome/genomeStore.d.ts.map +1 -1
  34. package/dist/src/genome/genomeStore.js +409 -18
  35. package/dist/src/genome/rootGenomeConfig.d.ts +26 -0
  36. package/dist/src/genome/rootGenomeConfig.d.ts.map +1 -0
  37. package/dist/src/genome/rootGenomeConfig.js +94 -0
  38. package/dist/src/genomeSpy/interactionController.d.ts +5 -1
  39. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  40. package/dist/src/genomeSpy/interactionController.js +244 -29
  41. package/dist/src/genomeSpy/renderCoordinator.js +1 -1
  42. package/dist/src/genomeSpy.d.ts +13 -3
  43. package/dist/src/genomeSpy.d.ts.map +1 -1
  44. package/dist/src/genomeSpy.js +83 -7
  45. package/dist/src/gl/canvasSizeHelper.d.ts +74 -0
  46. package/dist/src/gl/canvasSizeHelper.d.ts.map +1 -0
  47. package/dist/src/gl/canvasSizeHelper.js +203 -0
  48. package/dist/src/gl/hashTable.d.ts +78 -0
  49. package/dist/src/gl/hashTable.d.ts.map +1 -0
  50. package/dist/src/gl/hashTable.js +164 -0
  51. package/dist/src/gl/includes/common.glsl.js +1 -1
  52. package/dist/src/gl/webGLHelper.d.ts +25 -11
  53. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  54. package/dist/src/gl/webGLHelper.js +71 -39
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +5 -2
  57. package/dist/src/marks/link.d.ts.map +1 -1
  58. package/dist/src/marks/link.js +5 -3
  59. package/dist/src/marks/mark.d.ts +1 -1
  60. package/dist/src/marks/mark.d.ts.map +1 -1
  61. package/dist/src/marks/mark.js +8 -4
  62. package/dist/src/scales/domainPlanner.d.ts +34 -3
  63. package/dist/src/scales/domainPlanner.d.ts.map +1 -1
  64. package/dist/src/scales/domainPlanner.js +247 -26
  65. package/dist/src/scales/scaleInstanceManager.d.ts +2 -1
  66. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  67. package/dist/src/scales/scaleInstanceManager.js +10 -11
  68. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  69. package/dist/src/scales/scaleInteractionController.js +16 -14
  70. package/dist/src/scales/scaleResolution.d.ts +16 -0
  71. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  72. package/dist/src/scales/scaleResolution.js +314 -54
  73. package/dist/src/scales/scaleResolutionTestUtils.d.ts +21 -0
  74. package/dist/src/scales/scaleResolutionTestUtils.d.ts.map +1 -0
  75. package/dist/src/scales/scaleResolutionTestUtils.js +33 -0
  76. package/dist/src/scales/selectionDomainUtils.d.ts +22 -0
  77. package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -0
  78. package/dist/src/scales/selectionDomainUtils.js +79 -0
  79. package/dist/src/scales/zoomDomainUtils.d.ts +18 -0
  80. package/dist/src/scales/zoomDomainUtils.d.ts.map +1 -0
  81. package/dist/src/scales/zoomDomainUtils.js +69 -0
  82. package/dist/src/screenshotHarness.d.ts +16 -0
  83. package/dist/src/screenshotHarness.d.ts.map +1 -0
  84. package/dist/src/screenshotHarness.js +242 -0
  85. package/dist/src/singlePageApp.js +1 -1
  86. package/dist/src/spec/data.d.ts +23 -3
  87. package/dist/src/spec/genome.d.ts +22 -2
  88. package/dist/src/spec/parameter.d.ts +39 -2
  89. package/dist/src/spec/root.d.ts +20 -1
  90. package/dist/src/spec/scale.d.ts +41 -5
  91. package/dist/src/styles/genome-spy.css +8 -0
  92. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  93. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  94. package/dist/src/styles/genome-spy.css.js +8 -0
  95. package/dist/src/tooltip/dataTooltipHandler.js +59 -10
  96. package/dist/src/types/embedApi.d.ts +19 -0
  97. package/dist/src/utils/inferSpecBaseUrl.d.ts +14 -0
  98. package/dist/src/utils/inferSpecBaseUrl.d.ts.map +1 -0
  99. package/dist/src/utils/inferSpecBaseUrl.js +73 -0
  100. package/dist/src/utils/interactionEvent.d.ts +53 -3
  101. package/dist/src/utils/interactionEvent.d.ts.map +1 -1
  102. package/dist/src/utils/interactionEvent.js +62 -1
  103. package/dist/src/utils/radixSort.d.ts.map +1 -1
  104. package/dist/src/utils/radixSort.js +26 -1
  105. package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
  106. package/dist/src/view/containerMutationHelper.js +8 -0
  107. package/dist/src/view/dataReadiness.d.ts +2 -2
  108. package/dist/src/view/dataReadiness.d.ts.map +1 -1
  109. package/dist/src/view/dataReadiness.js +63 -58
  110. package/dist/src/view/facetView.d.ts +1 -1
  111. package/dist/src/view/facetView.js +1 -1
  112. package/dist/src/view/gridView/gridChild.d.ts +7 -0
  113. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  114. package/dist/src/view/gridView/gridChild.js +180 -11
  115. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  116. package/dist/src/view/gridView/gridView.js +60 -17
  117. package/dist/src/view/unitView.d.ts +1 -1
  118. package/dist/src/view/zoom.d.ts +14 -2
  119. package/dist/src/view/zoom.d.ts.map +1 -1
  120. package/dist/src/view/zoom.js +373 -76
  121. package/package.json +5 -2
@@ -82,11 +82,29 @@ export default class InteractionController {
82
82
  return this.#currentHover;
83
83
  }
84
84
 
85
- registerMouseEvents() {
85
+ registerInteractionEvents() {
86
86
  const canvas = this.#glHelper.canvas;
87
87
 
88
88
  let lastWheelEvent = performance.now();
89
89
  let longPressTriggered = false;
90
+ /** @type {{ pointerCount: 1 | 2, centerX: number, centerY: number, distance: number } | undefined} */
91
+ let previousTouchGesture;
92
+
93
+ /**
94
+ * @param {Point} point
95
+ * @param {import("../utils/interactionEvent.js").InteractionUiEvent} uiEvent
96
+ * @returns {InteractionEvent}
97
+ */
98
+ const dispatchInteractionEvent = (point, uiEvent) => {
99
+ const interactionEvent = new InteractionEvent(point, uiEvent);
100
+ this.#viewRoot.propagateInteractionEvent(interactionEvent);
101
+
102
+ if (!this.#tooltipUpdateRequested) {
103
+ this.#tooltip.clear();
104
+ }
105
+
106
+ return interactionEvent;
107
+ };
90
108
 
91
109
  /** @param {Event} event */
92
110
  const listener = (event) => {
@@ -117,13 +135,7 @@ export default class InteractionController {
117
135
  * @param {MouseEvent} dispatchedEvent
118
136
  */
119
137
  const dispatchEvent = (dispatchedEvent) => {
120
- this.#viewRoot.propagateInteractionEvent(
121
- new InteractionEvent(point, dispatchedEvent)
122
- );
123
-
124
- if (!this.#tooltipUpdateRequested) {
125
- this.#tooltip.clear();
126
- }
138
+ dispatchInteractionEvent(point, dispatchedEvent);
127
139
  };
128
140
 
129
141
  if (event.type != "wheel") {
@@ -156,26 +168,41 @@ export default class InteractionController {
156
168
 
157
169
  this.#wheelInertia.cancel();
158
170
  } else {
159
- // Vertical wheeling zooms.
160
- // We use inertia to generate fake wheel events for smoother zooming
161
-
162
- const template = makeEventTemplate(wheelEvent);
163
-
164
- this.#wheelInertia.setMomentum(
165
- wheelEvent.deltaY * (wheelEvent.deltaMode ? 80 : 1),
166
- (delta) => {
167
- const e = new WheelEvent("wheel", {
168
- ...template,
169
- deltaMode: 0,
170
- deltaX: 0,
171
- deltaY: delta,
172
- });
173
- dispatchEvent(e);
174
- }
175
- );
176
-
177
- wheelEvent.preventDefault();
178
- return;
171
+ // We must decide on the native wheel event whether to
172
+ // call preventDefault() (to block page scrolling).
173
+ // This probe asks the pointed view hierarchy to claim
174
+ // wheel ownership without running real wheel side
175
+ // effects first. Inertia is layered on top of that
176
+ // decision and is not the reason for the probe.
177
+ const probeEvent = dispatchInteractionEvent(point, {
178
+ type: "wheelclaimprobe",
179
+ });
180
+
181
+ if (probeEvent.wheelClaimed) {
182
+ // Vertical wheeling zooms.
183
+ // We use inertia to generate fake wheel events for smoother zooming
184
+
185
+ const template = makeEventTemplate(wheelEvent);
186
+
187
+ this.#wheelInertia.setMomentum(
188
+ wheelEvent.deltaY *
189
+ (wheelEvent.deltaMode ? 80 : 1),
190
+ (delta) => {
191
+ const e = new WheelEvent("wheel", {
192
+ ...template,
193
+ deltaMode: 0,
194
+ deltaX: 0,
195
+ deltaY: delta,
196
+ });
197
+ dispatchEvent(e);
198
+ }
199
+ );
200
+
201
+ wheelEvent.preventDefault();
202
+ return;
203
+ } else {
204
+ this.#wheelInertia.cancel();
205
+ }
179
206
  }
180
207
  }
181
208
 
@@ -223,11 +250,168 @@ export default class InteractionController {
223
250
  "wheel",
224
251
  "click",
225
252
  "mousemove",
226
- "gesturechange",
227
253
  "contextmenu",
228
254
  "dblclick",
229
255
  ].forEach((type) => canvas.addEventListener(type, listener));
230
256
 
257
+ /**
258
+ * @param {number} clientX
259
+ * @param {number} clientY
260
+ */
261
+ const toCanvasPoint = (clientX, clientY) => {
262
+ const rect = canvas.getBoundingClientRect();
263
+ return new Point(
264
+ clientX - rect.left - canvas.clientLeft,
265
+ clientY - rect.top - canvas.clientTop
266
+ );
267
+ };
268
+
269
+ /**
270
+ * @param {TouchList} touches
271
+ */
272
+ const readTouchGesture = (touches) => {
273
+ if (touches.length <= 0) {
274
+ return;
275
+ }
276
+
277
+ const first = touches[0];
278
+
279
+ if (touches.length === 1) {
280
+ return {
281
+ pointerCount: /** @type {1} */ (1),
282
+ centerX: first.clientX,
283
+ centerY: first.clientY,
284
+ distance: 0,
285
+ };
286
+ }
287
+
288
+ const second = touches[1];
289
+ return {
290
+ pointerCount: /** @type {2} */ (2),
291
+ centerX: (first.clientX + second.clientX) / 2,
292
+ centerY: (first.clientY + second.clientY) / 2,
293
+ distance: getClientDistance(first, second),
294
+ };
295
+ };
296
+
297
+ /**
298
+ * @param {number} x
299
+ * @param {number} y
300
+ * @param {"move" | "end"} phase
301
+ * @param {1 | 2} pointerCount
302
+ * @param {number} xDelta
303
+ * @param {number} yDelta
304
+ * @param {number} zDelta
305
+ */
306
+ const dispatchTouchGestureEvent = (
307
+ x,
308
+ y,
309
+ phase,
310
+ pointerCount,
311
+ xDelta,
312
+ yDelta,
313
+ zDelta
314
+ ) => {
315
+ const point = toCanvasPoint(x, y);
316
+ dispatchInteractionEvent(point, {
317
+ type: "touchgesture",
318
+ phase,
319
+ pointerCount,
320
+ xDelta,
321
+ yDelta,
322
+ zDelta,
323
+ });
324
+ };
325
+
326
+ /**
327
+ * @param {TouchEvent} touchEvent
328
+ */
329
+ const handleTouchStartOrMove = (touchEvent) => {
330
+ touchEvent.preventDefault();
331
+ this.#wheelInertia.cancel();
332
+ this.#tooltipUpdateRequested = false;
333
+
334
+ const currentGesture = readTouchGesture(touchEvent.touches);
335
+ if (!currentGesture) {
336
+ previousTouchGesture = undefined;
337
+ return;
338
+ }
339
+
340
+ if (
341
+ !previousTouchGesture ||
342
+ previousTouchGesture.pointerCount !==
343
+ currentGesture.pointerCount
344
+ ) {
345
+ previousTouchGesture = currentGesture;
346
+ return;
347
+ }
348
+
349
+ const xDelta =
350
+ currentGesture.centerX - previousTouchGesture.centerX;
351
+ const yDelta =
352
+ currentGesture.centerY - previousTouchGesture.centerY;
353
+ const zDelta =
354
+ currentGesture.pointerCount === 2
355
+ ? pinchDistanceToZoomDelta(
356
+ previousTouchGesture.distance,
357
+ currentGesture.distance
358
+ )
359
+ : 0;
360
+
361
+ if (
362
+ (xDelta !== 0 || yDelta !== 0 || zDelta !== 0) &&
363
+ Number.isFinite(xDelta) &&
364
+ Number.isFinite(yDelta) &&
365
+ Number.isFinite(zDelta)
366
+ ) {
367
+ dispatchTouchGestureEvent(
368
+ previousTouchGesture.centerX,
369
+ previousTouchGesture.centerY,
370
+ "move",
371
+ currentGesture.pointerCount,
372
+ xDelta,
373
+ yDelta,
374
+ zDelta
375
+ );
376
+ }
377
+
378
+ previousTouchGesture = currentGesture;
379
+ };
380
+
381
+ /**
382
+ * @param {TouchEvent} touchEvent
383
+ */
384
+ const handleTouchEndOrCancel = (touchEvent) => {
385
+ touchEvent.preventDefault();
386
+ this.#tooltipUpdateRequested = false;
387
+ if (previousTouchGesture && touchEvent.touches.length === 0) {
388
+ dispatchTouchGestureEvent(
389
+ previousTouchGesture.centerX,
390
+ previousTouchGesture.centerY,
391
+ "end",
392
+ previousTouchGesture.pointerCount,
393
+ 0,
394
+ 0,
395
+ 0
396
+ );
397
+ }
398
+
399
+ previousTouchGesture = readTouchGesture(touchEvent.touches);
400
+ };
401
+
402
+ canvas.addEventListener("touchstart", handleTouchStartOrMove, {
403
+ passive: false,
404
+ });
405
+ canvas.addEventListener("touchmove", handleTouchStartOrMove, {
406
+ passive: false,
407
+ });
408
+ canvas.addEventListener("touchend", handleTouchEndOrCancel, {
409
+ passive: false,
410
+ });
411
+ canvas.addEventListener("touchcancel", handleTouchEndOrCancel, {
412
+ passive: false,
413
+ });
414
+
231
415
  canvas.addEventListener("mousedown", (/** @type {MouseEvent} */ e) => {
232
416
  this.#mouseDownCoords = Point.fromMouseEvent(e);
233
417
  if (this.#tooltip.sticky) {
@@ -375,3 +559,34 @@ export default class InteractionController {
375
559
  }
376
560
  }
377
561
  }
562
+
563
+ /**
564
+ * @typedef {{clientX: number, clientY: number}} ClientPointLike
565
+ */
566
+
567
+ /**
568
+ * Returns euclidean distance between two client-space points.
569
+ *
570
+ * @param {ClientPointLike} a
571
+ * @param {ClientPointLike} b
572
+ */
573
+ function getClientDistance(a, b) {
574
+ const dx = b.clientX - a.clientX;
575
+ const dy = b.clientY - a.clientY;
576
+ return Math.hypot(dx, dy);
577
+ }
578
+
579
+ /**
580
+ * Converts a pinch distance ratio to a zDelta used by interactionToZoom:
581
+ * scaleFactor = 2 ** zDelta.
582
+ *
583
+ * @param {number} previousDistance
584
+ * @param {number} currentDistance
585
+ */
586
+ function pinchDistanceToZoomDelta(previousDistance, currentDistance) {
587
+ if (previousDistance <= 0 || currentDistance <= 0) {
588
+ return 0;
589
+ }
590
+
591
+ return Math.log2(previousDistance / currentDistance);
592
+ }
@@ -70,7 +70,7 @@ export default class RenderCoordinator {
70
70
  const commonOptions = {
71
71
  webGLHelper: this.#glHelper,
72
72
  canvasSize,
73
- devicePixelRatio: window.devicePixelRatio ?? 1,
73
+ devicePixelRatio: this.#glHelper.getDevicePixelRatio(canvasSize),
74
74
  };
75
75
 
76
76
  this.#renderingContext = new BufferedViewRenderingContext(
@@ -77,7 +77,13 @@ export default class GenomeSpy {
77
77
  */
78
78
  launch(): Promise<boolean>;
79
79
  initializeVisibleViewData(): Promise<void>;
80
- registerMouseEvents(): void;
80
+ /**
81
+ * Waits until lazy sources under the root view have loaded data for the
82
+ * current visible positional domain.
83
+ *
84
+ * @param {AbortSignal} [signal]
85
+ */
86
+ awaitVisibleLazyData(signal?: AbortSignal): Promise<void>;
81
87
  /**
82
88
  * This method should be called in a mouseMove handler. If not called, the
83
89
  * tooltip will be hidden.
@@ -98,8 +104,12 @@ export default class GenomeSpy {
98
104
  */
99
105
  exportCanvas(logicalWidth?: number, logicalHeight?: number, devicePixelRatio?: number, clearColor?: string): string;
100
106
  getLogicalCanvasSize(): {
101
- width: any;
102
- height: any;
107
+ width: number;
108
+ height: number;
109
+ };
110
+ getRenderedBounds(): {
111
+ width: number | undefined;
112
+ height: number | undefined;
103
113
  };
104
114
  computeLayout(): void;
105
115
  renderAll(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"AA+CA;IAoBI;;;;;OAKG;IAEH;;;;;OAKG;IACH,uBAJW,WAAW,qDAEX,OAAO,qBAAqB,EAAE,YAAY,EA0CpD;IAvCG,uBAA0B;IAC1B,oDAAsB;IAItB,sCAAsC;IACtC,wCAAgB;IAEhB,yBAAoC;IAEpC,4CAA4C;IAC5C,oBADW,CAAC,CAAS,IAAM,EAAN,MAAM,KAAE,MAAM,EAAE,CAAC,EAAE,CACZ;IAE5B,mBAAoD;IAEpD,0BAA0B;IAC1B,aADW,WAAW,CACM;IAE5B;;;;;OAKG;IACH,yBAFU,CAAC,IAAI,qEAAM,KAAK,OAAO,CAE8B;IAE/D,oFAAoF;IACpF,iBADW,MAAM,CAAC,MAAM,EAAE,OAAO,6BAA6B,EAAE,cAAc,CAAC,CAK9E;IAED,mBAAmB;IACnB,8EAAyB;IAIzB,YAAkC;IAatC;;;OAGG;IACH,oCAFW,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,EAAE,QAIjC;IAED;;OAEG;IACH,+BAFW,MAAM,YAShB;IAED;;;;OAIG;IACH,sBAHW,MAAM,QACN,GAAG,EAAE,QAYf;IAED;;;OAGG;IACH,uBAHW,MAAM,YACN,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,QAI9B;IAED;;;OAGG;IACH,0BAHW,MAAM,YACN,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,QAI9B;IAED;;;;;OAKG;IACH,gBAHW,kBAAkB,YAClB,GAAG,QAMb;IA8DG,iDAAsB;IAQ1B;;OAEG;IACH,gBAqBC;IA+KD;;;OAGG;IACH,UAFa,OAAO,CAAC,OAAO,CAAC,CAyC5B;IAED,2CAiBC;IAED,4BAEC;IAED;;;;;;;OAOG;IACH,cAFa,CAAC,SAFH,CAAC,cACD,CAAS,IAAC,EAAD,CAAC,KAAE,OAAO,CAAC,MAAM,GAAG,WAAW,GAAG,OAAO,KAAK,EAAE,cAAc,CAAC,QAKlF;IAED;;;;;;;;OAQG;IACH,4BANW,MAAM,kBACN,MAAM,qBACN,MAAM,eACN,MAAM,UAuBhB;IAED;;;MAEC;IAED,sBAEC;IAED,kBAEC;IAED,iCAEC;IAED,oEAYC;IAED,uFAWC;;CACJ;;;;iCA/jBY,eAAe,GAAG,QAAQ,GAAG,gBAAgB,GAAG,kBAAkB;4BAbnC,uBAAuB;qBAR9C,qBAAqB;wBAElB,yBAAyB;qBAL5B,oBAAoB"}
1
+ {"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"AAyDA;IAoBI;;;;;OAKG;IAEH;;;;;OAKG;IACH,uBAJW,WAAW,qDAEX,OAAO,qBAAqB,EAAE,YAAY,EA0CpD;IAvCG,uBAA0B;IAC1B,oDAAsB;IAItB,sCAAsC;IACtC,wCAAgB;IAEhB,yBAAoC;IAEpC,4CAA4C;IAC5C,oBADW,CAAC,CAAS,IAAM,EAAN,MAAM,KAAE,MAAM,EAAE,CAAC,EAAE,CACZ;IAE5B,mBAAoD;IAEpD,0BAA0B;IAC1B,aADW,WAAW,CACM;IAE5B;;;;;OAKG;IACH,yBAFU,CAAC,IAAI,qEAAM,KAAK,OAAO,CAE8B;IAE/D,oFAAoF;IACpF,iBADW,MAAM,CAAC,MAAM,EAAE,OAAO,6BAA6B,EAAE,cAAc,CAAC,CAK9E;IAED,mBAAmB;IACnB,8EAAyB;IAIzB,YAAkC;IAatC;;;OAGG;IACH,oCAFW,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,EAAE,QAIjC;IAED;;OAEG;IACH,+BAFW,MAAM,YAShB;IAED;;;;OAIG;IACH,sBAHW,MAAM,QACN,GAAG,EAAE,QAYf;IAED;;;OAGG;IACH,uBAHW,MAAM,YACN,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,QAI9B;IAED;;;OAGG;IACH,0BAHW,MAAM,YACN,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,QAI9B;IAED;;;;;OAKG;IACH,gBAHW,kBAAkB,YAClB,GAAG,QAMb;IAgEG,iDAAsB;IAQ1B;;OAEG;IACH,gBAqBC;IA0LD;;;OAGG;IACH,UAFa,OAAO,CAAC,OAAO,CAAC,CAyC5B;IAED,2CAiBC;IAED;;;;;OAKG;IACH,8BAFW,WAAW,iBAgBrB;IAED;;;;;;;OAOG;IACH,cAFa,CAAC,SAFH,CAAC,cACD,CAAS,IAAC,EAAD,CAAC,KAAE,OAAO,CAAC,MAAM,GAAG,WAAW,GAAG,OAAO,KAAK,EAAE,cAAc,CAAC,QAKlF;IAED;;;;;;;;OAQG;IACH,4BANW,MAAM,kBACN,MAAM,qBACN,MAAM,eACN,MAAM,UAuBhB;IAED;;;MAEC;IAED;eACwB,MAAM,GAAG,SAAS;gBAAU,MAAM,GAAG,SAAS;MAcrE;IAED,sBAEC;IAED,kBAEC;IAED,iCAEC;IAED,oEAYC;IAED,uFAWC;;CACJ;;;;iCAlnBY,eAAe,GAAG,QAAQ,GAAG,gBAAgB,GAAG,kBAAkB;4BApBnC,uBAAuB;qBAR9C,qBAAqB;wBAElB,yBAAyB;qBAL5B,oBAAoB"}
@@ -37,6 +37,13 @@ import {
37
37
  } from "./genomeSpy/viewHierarchyConfig.js";
38
38
  import { exportCanvas } from "./genomeSpy/canvasExport.js";
39
39
  import { validateSelectorConstraints } from "./view/viewSelectors.js";
40
+ import parquet from "./data/formats/parquet.js";
41
+ import bed from "./data/formats/bed.js";
42
+ import bedpe from "./data/formats/bedpe.js";
43
+ import SingleAxisWindowedSource from "./data/sources/lazy/singleAxisWindowedSource.js";
44
+ import { ensureAssembliesForView } from "./genome/assemblyPreflight.js";
45
+ import { resolveRootGenomeConfig } from "./genome/rootGenomeConfig.js";
46
+ import { awaitSubtreeLazyReady } from "./view/dataReadiness.js";
40
47
 
41
48
  /**
42
49
  * Events that are broadcasted to all views.
@@ -44,6 +51,9 @@ import { validateSelectorConstraints } from "./view/viewSelectors.js";
44
51
  */
45
52
 
46
53
  vegaFormats("fasta", fasta);
54
+ vegaFormats("parquet", parquet);
55
+ vegaFormats("bed", bed);
56
+ vegaFormats("bedpe", bedpe);
47
57
 
48
58
  export default class GenomeSpy {
49
59
  /** @type {(() => void)[]} */
@@ -196,6 +206,8 @@ export default class GenomeSpy {
196
206
  }
197
207
 
198
208
  #setupDpr() {
209
+ this.dpr = this.#glHelper.getDevicePixelRatio();
210
+
199
211
  const dprSetter = this.viewRoot.paramRuntime.allocateSetter(
200
212
  "devicePixelRatio",
201
213
  this.dpr
@@ -203,7 +215,7 @@ export default class GenomeSpy {
203
215
 
204
216
  const resizeCallback = () => {
205
217
  this.#glHelper.invalidateSize();
206
- this.dpr = window.devicePixelRatio;
218
+ this.dpr = this.#glHelper.getDevicePixelRatio();
207
219
  dprSetter(this.dpr);
208
220
  this.computeLayout();
209
221
  // Render immediately, without RAF
@@ -303,9 +315,15 @@ export default class GenomeSpy {
303
315
  }
304
316
 
305
317
  async #initializeGenomeStore() {
306
- if (this.spec.genome) {
307
- this.genomeStore = new GenomeStore(this.spec.baseUrl);
308
- await this.genomeStore.initialize(this.spec.genome);
318
+ this.genomeStore = new GenomeStore(this.spec.baseUrl);
319
+
320
+ const { genomesByName, defaultAssembly, deprecationWarning } =
321
+ resolveRootGenomeConfig(this.spec);
322
+ this.genomeStore.configureGenomes(genomesByName, defaultAssembly);
323
+
324
+ if (deprecationWarning) {
325
+ // eslint-disable-next-line no-console
326
+ console.warn(deprecationWarning);
309
327
  }
310
328
  }
311
329
 
@@ -396,6 +414,11 @@ export default class GenomeSpy {
396
414
  VIEW_ROOT_NAME
397
415
  );
398
416
 
417
+ // Reminder: assemblies must be ensured after view creation (imports and
418
+ // inheritance resolved), but before any code path that may touch scales
419
+ // (e.g. step-based sizes, dynamic opacity, encoder initialization).
420
+ await ensureAssembliesForView(this.viewRoot, this.genomeStore);
421
+
399
422
  this.#loadingStatusRegistry.set(this.viewRoot, "loading");
400
423
 
401
424
  this.#canvasWrapper.style.flexGrow =
@@ -473,7 +496,7 @@ export default class GenomeSpy {
473
496
 
474
497
  await this.#prepareViewsAndData();
475
498
 
476
- this.registerMouseEvents();
499
+ this.#interactionController.registerInteractionEvents();
477
500
 
478
501
  this.computeLayout();
479
502
  this.animator.requestRender();
@@ -526,8 +549,26 @@ export default class GenomeSpy {
526
549
  this.animator.requestRender();
527
550
  }
528
551
 
529
- registerMouseEvents() {
530
- this.#interactionController.registerMouseEvents();
552
+ /**
553
+ * Waits until lazy sources under the root view have loaded data for the
554
+ * current visible positional domain.
555
+ *
556
+ * @param {AbortSignal} [signal]
557
+ */
558
+ async awaitVisibleLazyData(signal) {
559
+ if (!this.viewRoot) {
560
+ return;
561
+ }
562
+
563
+ await awaitSubtreeLazyReady(
564
+ this.viewRoot.context,
565
+ this.viewRoot,
566
+ undefined,
567
+ signal,
568
+ (view) =>
569
+ view.isConfiguredVisible() &&
570
+ hasWindowedLazyDataSource(view)
571
+ );
531
572
  }
532
573
 
533
574
  /**
@@ -577,6 +618,23 @@ export default class GenomeSpy {
577
618
  return this.#glHelper.getLogicalCanvasSize();
578
619
  }
579
620
 
621
+ getRenderedBounds() {
622
+ /** @type {{ width: number | undefined, height: number | undefined }} */
623
+ const bounds = {
624
+ width: undefined,
625
+ height: undefined,
626
+ };
627
+
628
+ this.viewRoot.visit((view) => {
629
+ for (const coords of view.facetCoords.values()) {
630
+ bounds.width = Math.max(bounds.width ?? 0, coords.x2);
631
+ bounds.height = Math.max(bounds.height ?? 0, coords.y2);
632
+ }
633
+ });
634
+
635
+ return bounds;
636
+ }
637
+
580
638
  computeLayout() {
581
639
  this.#renderCoordinator.computeLayout();
582
640
  }
@@ -616,3 +674,21 @@ export default class GenomeSpy {
616
674
  return resolutions;
617
675
  }
618
676
  }
677
+
678
+ /**
679
+ * @param {View} view
680
+ */
681
+ function hasWindowedLazyDataSource(view) {
682
+ /** @type {View | null} */
683
+ let current = view;
684
+
685
+ while (current) {
686
+ const dataSource = current.flowHandle?.dataSource;
687
+ if (dataSource) {
688
+ return dataSource instanceof SingleAxisWindowedSource;
689
+ }
690
+ current = current.dataParent;
691
+ }
692
+
693
+ return false;
694
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Handles logical/physical canvas size calculations and optional
3
+ * device-pixel-content-box observation.
4
+ */
5
+ export default class CanvasSizeHelper {
6
+ /**
7
+ * @param {HTMLElement} container
8
+ * @param {HTMLCanvasElement} canvas
9
+ * @param {() => {width: number, height: number}} sizeSource
10
+ * @param {() => void} [onPhysicalSizeChange]
11
+ */
12
+ constructor(container: HTMLElement, canvas: HTMLCanvasElement, sizeSource: () => {
13
+ width: number;
14
+ height: number;
15
+ }, onPhysicalSizeChange?: () => void);
16
+ _container: HTMLElement;
17
+ _canvas: HTMLCanvasElement;
18
+ _sizeSource: () => {
19
+ width: number;
20
+ height: number;
21
+ };
22
+ _onPhysicalSizeChange: () => void;
23
+ /**
24
+ * @type {{ width: number, height: number } | undefined}
25
+ */
26
+ _logicalCanvasSize: {
27
+ width: number;
28
+ height: number;
29
+ } | undefined;
30
+ /**
31
+ * @type {{ width: number, height: number } | undefined}
32
+ */
33
+ _devicePixelContentBoxSize: {
34
+ width: number;
35
+ height: number;
36
+ } | undefined;
37
+ /**
38
+ * @type {ResizeObserver | undefined}
39
+ */
40
+ _devicePixelContentBoxObserver: ResizeObserver | undefined;
41
+ invalidate(): void;
42
+ finalize(): void;
43
+ /**
44
+ * Returns the canvas size in true display pixels
45
+ *
46
+ * @param {{ width: number, height: number }} [logicalSize]
47
+ */
48
+ getPhysicalCanvasSize(logicalSize?: {
49
+ width: number;
50
+ height: number;
51
+ }): {
52
+ width: number;
53
+ height: number;
54
+ };
55
+ /**
56
+ * Returns the ratio between true display pixels and logical pixels.
57
+ *
58
+ * @param {{ width: number, height: number }} [logicalSize]
59
+ */
60
+ getDevicePixelRatio(logicalSize?: {
61
+ width: number;
62
+ height: number;
63
+ }): number;
64
+ /**
65
+ * Returns the size of the canvas canvas container size in logical pixels,
66
+ * without devicePixelRatio correction.
67
+ */
68
+ getLogicalCanvasSize(): {
69
+ width: number;
70
+ height: number;
71
+ };
72
+ _observeDevicePixelContentBox(): void;
73
+ }
74
+ //# sourceMappingURL=canvasSizeHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasSizeHelper.d.ts","sourceRoot":"","sources":["../../../src/gl/canvasSizeHelper.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH;IACI;;;;;OAKG;IACH,uBALW,WAAW,UACX,iBAAiB,cACjB,MAAM;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,yBACrC,MAAM,IAAI,EAwBpB;IArBG,wBAA2B;IAC3B,2BAAqB;IACrB,mBANa;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAMf;IAC7B,6BANa,IAAI,CAM8C;IAE/D;;OAEG;IACH,oBAFU;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAEpB;IAEnC;;OAEG;IACH,4BAFU;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAEZ;IAE3C;;OAEG;IACH,gCAFU,cAAc,GAAG,SAAS,CAEW;IAKnD,mBAGC;IAED,iBAIC;IAED;;;;OAIG;IACH,oCAFW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;eA1BrB,MAAM;gBAAU,MAAM;MA0C5C;IAED;;;;OAIG;IACH,kCAFW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UA2B3C;IAED;;;OAGG;IACH;eArFuB,MAAM;gBAAU,MAAM;MA8H5C;IAED,sCAuDC;CACJ"}