@genome-spy/core 0.72.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 (103) hide show
  1. package/LICENSE +1 -1
  2. package/dist/bundle/index.es.js +6779 -5393
  3. package/dist/bundle/index.js +133 -121
  4. package/dist/schema.json +281 -17
  5. package/dist/src/data/formats/bed.d.ts +8 -0
  6. package/dist/src/data/formats/bed.d.ts.map +1 -0
  7. package/dist/src/data/formats/bed.js +53 -0
  8. package/dist/src/data/formats/bedpe.d.ts +8 -0
  9. package/dist/src/data/formats/bedpe.d.ts.map +1 -0
  10. package/dist/src/data/formats/bedpe.js +160 -0
  11. package/dist/src/data/sources/dataUtils.d.ts +16 -0
  12. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  13. package/dist/src/data/sources/dataUtils.js +53 -3
  14. package/dist/src/data/sources/urlSource.d.ts +4 -0
  15. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  16. package/dist/src/data/sources/urlSource.js +133 -14
  17. package/dist/src/genome/assemblyPreflight.d.ts +31 -0
  18. package/dist/src/genome/assemblyPreflight.d.ts.map +1 -0
  19. package/dist/src/genome/assemblyPreflight.js +99 -0
  20. package/dist/src/genome/genome.d.ts +2 -2
  21. package/dist/src/genome/genome.d.ts.map +1 -1
  22. package/dist/src/genome/genome.js +4 -0
  23. package/dist/src/genome/genomeStore.d.ts +34 -3
  24. package/dist/src/genome/genomeStore.d.ts.map +1 -1
  25. package/dist/src/genome/genomeStore.js +409 -18
  26. package/dist/src/genome/rootGenomeConfig.d.ts +26 -0
  27. package/dist/src/genome/rootGenomeConfig.d.ts.map +1 -0
  28. package/dist/src/genome/rootGenomeConfig.js +94 -0
  29. package/dist/src/genomeSpy/interactionController.d.ts +5 -1
  30. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  31. package/dist/src/genomeSpy/interactionController.js +244 -29
  32. package/dist/src/genomeSpy/renderCoordinator.js +1 -1
  33. package/dist/src/genomeSpy.d.ts +13 -3
  34. package/dist/src/genomeSpy.d.ts.map +1 -1
  35. package/dist/src/genomeSpy.js +81 -7
  36. package/dist/src/gl/canvasSizeHelper.d.ts +74 -0
  37. package/dist/src/gl/canvasSizeHelper.d.ts.map +1 -0
  38. package/dist/src/gl/canvasSizeHelper.js +203 -0
  39. package/dist/src/gl/webGLHelper.d.ts +25 -11
  40. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  41. package/dist/src/gl/webGLHelper.js +59 -33
  42. package/dist/src/index.d.ts.map +1 -1
  43. package/dist/src/index.js +5 -2
  44. package/dist/src/marks/link.d.ts.map +1 -1
  45. package/dist/src/marks/link.js +5 -3
  46. package/dist/src/marks/mark.d.ts.map +1 -1
  47. package/dist/src/marks/mark.js +6 -1
  48. package/dist/src/scales/domainPlanner.d.ts +34 -3
  49. package/dist/src/scales/domainPlanner.d.ts.map +1 -1
  50. package/dist/src/scales/domainPlanner.js +247 -26
  51. package/dist/src/scales/scaleInstanceManager.d.ts +2 -1
  52. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  53. package/dist/src/scales/scaleInstanceManager.js +10 -11
  54. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  55. package/dist/src/scales/scaleInteractionController.js +16 -14
  56. package/dist/src/scales/scaleResolution.d.ts +16 -0
  57. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  58. package/dist/src/scales/scaleResolution.js +314 -54
  59. package/dist/src/scales/scaleResolutionTestUtils.d.ts +21 -0
  60. package/dist/src/scales/scaleResolutionTestUtils.d.ts.map +1 -0
  61. package/dist/src/scales/scaleResolutionTestUtils.js +33 -0
  62. package/dist/src/scales/selectionDomainUtils.d.ts +22 -0
  63. package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -0
  64. package/dist/src/scales/selectionDomainUtils.js +79 -0
  65. package/dist/src/scales/zoomDomainUtils.d.ts +18 -0
  66. package/dist/src/scales/zoomDomainUtils.d.ts.map +1 -0
  67. package/dist/src/scales/zoomDomainUtils.js +69 -0
  68. package/dist/src/screenshotHarness.d.ts +16 -0
  69. package/dist/src/screenshotHarness.d.ts.map +1 -0
  70. package/dist/src/screenshotHarness.js +242 -0
  71. package/dist/src/singlePageApp.js +1 -1
  72. package/dist/src/spec/data.d.ts +23 -3
  73. package/dist/src/spec/genome.d.ts +22 -2
  74. package/dist/src/spec/parameter.d.ts +39 -2
  75. package/dist/src/spec/root.d.ts +20 -1
  76. package/dist/src/spec/scale.d.ts +41 -5
  77. package/dist/src/styles/genome-spy.css +8 -0
  78. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  79. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  80. package/dist/src/styles/genome-spy.css.js +8 -0
  81. package/dist/src/tooltip/dataTooltipHandler.js +59 -10
  82. package/dist/src/types/embedApi.d.ts +19 -0
  83. package/dist/src/utils/inferSpecBaseUrl.d.ts +14 -0
  84. package/dist/src/utils/inferSpecBaseUrl.d.ts.map +1 -0
  85. package/dist/src/utils/inferSpecBaseUrl.js +73 -0
  86. package/dist/src/utils/interactionEvent.d.ts +53 -3
  87. package/dist/src/utils/interactionEvent.d.ts.map +1 -1
  88. package/dist/src/utils/interactionEvent.js +62 -1
  89. package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
  90. package/dist/src/view/containerMutationHelper.js +8 -0
  91. package/dist/src/view/dataReadiness.d.ts +2 -2
  92. package/dist/src/view/dataReadiness.d.ts.map +1 -1
  93. package/dist/src/view/dataReadiness.js +63 -58
  94. package/dist/src/view/facetView.js +1 -1
  95. package/dist/src/view/gridView/gridChild.d.ts +7 -0
  96. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  97. package/dist/src/view/gridView/gridChild.js +180 -11
  98. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  99. package/dist/src/view/gridView/gridView.js +60 -17
  100. package/dist/src/view/zoom.d.ts +14 -2
  101. package/dist/src/view/zoom.d.ts.map +1 -1
  102. package/dist/src/view/zoom.js +373 -76
  103. package/package.json +4 -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":"AAiDA;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;;;;iCAhkBY,eAAe,GAAG,QAAQ,GAAG,gBAAgB,GAAG,kBAAkB;4BAdnC,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"}
@@ -38,6 +38,12 @@ import {
38
38
  import { exportCanvas } from "./genomeSpy/canvasExport.js";
39
39
  import { validateSelectorConstraints } from "./view/viewSelectors.js";
40
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";
41
47
 
42
48
  /**
43
49
  * Events that are broadcasted to all views.
@@ -46,6 +52,8 @@ import parquet from "./data/formats/parquet.js";
46
52
 
47
53
  vegaFormats("fasta", fasta);
48
54
  vegaFormats("parquet", parquet);
55
+ vegaFormats("bed", bed);
56
+ vegaFormats("bedpe", bedpe);
49
57
 
50
58
  export default class GenomeSpy {
51
59
  /** @type {(() => void)[]} */
@@ -198,6 +206,8 @@ export default class GenomeSpy {
198
206
  }
199
207
 
200
208
  #setupDpr() {
209
+ this.dpr = this.#glHelper.getDevicePixelRatio();
210
+
201
211
  const dprSetter = this.viewRoot.paramRuntime.allocateSetter(
202
212
  "devicePixelRatio",
203
213
  this.dpr
@@ -205,7 +215,7 @@ export default class GenomeSpy {
205
215
 
206
216
  const resizeCallback = () => {
207
217
  this.#glHelper.invalidateSize();
208
- this.dpr = window.devicePixelRatio;
218
+ this.dpr = this.#glHelper.getDevicePixelRatio();
209
219
  dprSetter(this.dpr);
210
220
  this.computeLayout();
211
221
  // Render immediately, without RAF
@@ -305,9 +315,15 @@ export default class GenomeSpy {
305
315
  }
306
316
 
307
317
  async #initializeGenomeStore() {
308
- if (this.spec.genome) {
309
- this.genomeStore = new GenomeStore(this.spec.baseUrl);
310
- 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);
311
327
  }
312
328
  }
313
329
 
@@ -398,6 +414,11 @@ export default class GenomeSpy {
398
414
  VIEW_ROOT_NAME
399
415
  );
400
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
+
401
422
  this.#loadingStatusRegistry.set(this.viewRoot, "loading");
402
423
 
403
424
  this.#canvasWrapper.style.flexGrow =
@@ -475,7 +496,7 @@ export default class GenomeSpy {
475
496
 
476
497
  await this.#prepareViewsAndData();
477
498
 
478
- this.registerMouseEvents();
499
+ this.#interactionController.registerInteractionEvents();
479
500
 
480
501
  this.computeLayout();
481
502
  this.animator.requestRender();
@@ -528,8 +549,26 @@ export default class GenomeSpy {
528
549
  this.animator.requestRender();
529
550
  }
530
551
 
531
- registerMouseEvents() {
532
- 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
+ );
533
572
  }
534
573
 
535
574
  /**
@@ -579,6 +618,23 @@ export default class GenomeSpy {
579
618
  return this.#glHelper.getLogicalCanvasSize();
580
619
  }
581
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
+
582
638
  computeLayout() {
583
639
  this.#renderCoordinator.computeLayout();
584
640
  }
@@ -618,3 +674,21 @@ export default class GenomeSpy {
618
674
  return resolutions;
619
675
  }
620
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"}