igv 2.13.3 → 2.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/igv.esm.js CHANGED
@@ -21388,7 +21388,6 @@ class SequenceTrack {
21388
21388
 
21389
21389
  let sequence = options.features.sequence;
21390
21390
  if(!sequence) {
21391
- console.error("No sequence");
21392
21391
  return
21393
21392
  }
21394
21393
 
@@ -21555,17 +21554,9 @@ class Viewport {
21555
21554
  this.alert = new AlertDialog(this.$viewport.get(0));
21556
21555
  }
21557
21556
 
21558
- this.$content = $$1("<div>", {class: 'igv-viewport-content'});
21559
- this.$viewport.append(this.$content);
21560
-
21561
- this.$content.height(this.$viewport.height());
21562
- this.contentDiv = this.$content.get(0);
21557
+ this.contentTop = 0;
21558
+ this.contentHeight = this.$viewport.height();
21563
21559
 
21564
- // this.$canvas = $('<canvas>')
21565
- // this.$content.append(this.$canvas)
21566
- //
21567
- // this.canvas = this.$canvas.get(0)
21568
- // this.ctx = this.canvas.getContext("2d")
21569
21560
 
21570
21561
  this.$viewport.width(width);
21571
21562
 
@@ -21581,7 +21572,8 @@ class Viewport {
21581
21572
  if (!this.messageDiv) {
21582
21573
  this.messageDiv = document.createElement('div');
21583
21574
  this.messageDiv.className = 'igv-viewport-message';
21584
- this.contentDiv.append(this.messageDiv);
21575
+ //this.contentDiv.append(this.messageDiv)
21576
+ this.$viewport.append($$1(this.messageDiv));
21585
21577
  }
21586
21578
  this.messageDiv.textContent = message;
21587
21579
  this.messageDiv.style.display = 'inline-block';
@@ -21610,15 +21602,15 @@ class Viewport {
21610
21602
 
21611
21603
  setTop(contentTop) {
21612
21604
 
21613
- const viewportHeight = this.$viewport.height();
21614
- const viewTop = -contentTop;
21615
- const viewBottom = viewTop + viewportHeight;
21616
-
21617
- this.$content.css('top', `${contentTop}px`);
21605
+ this.contentTop = contentTop;
21606
+ this.$viewport.height();
21618
21607
 
21619
- if (undefined === this.canvasVerticalRange || this.canvasVerticalRange.bottom < viewBottom || this.canvasVerticalRange.top > viewTop) {
21620
- this.repaint();
21621
- }
21608
+ //this.$content.css('top', `${contentTop}px`)
21609
+ //
21610
+ // if (undefined === this.canvasVerticalRange || this.canvasVerticalRange.bottom < viewBottom || this.canvasVerticalRange.top > viewTop) {
21611
+ // console.log("Repaint " + this.canvasVerticalRange)
21612
+ // this.repaint()
21613
+ // }
21622
21614
 
21623
21615
  }
21624
21616
 
@@ -21646,7 +21638,8 @@ class Viewport {
21646
21638
  } else if (typeof track.computePixelHeight === 'function') {
21647
21639
  if (features && features.length > 0) {
21648
21640
  let requiredContentHeight = track.computePixelHeight(features);
21649
- let currentContentHeight = this.$content.height();
21641
+ //let currentContentHeight = this.$content.height()
21642
+ let currentContentHeight = this.contentHeight;
21650
21643
  if (requiredContentHeight !== currentContentHeight) {
21651
21644
  this.setContentHeight(requiredContentHeight);
21652
21645
  }
@@ -21655,14 +21648,12 @@ class Viewport {
21655
21648
  }
21656
21649
 
21657
21650
  getContentHeight() {
21658
- return this.$content.height()
21651
+ //return this.$content.height()
21652
+ return this.contentHeight
21659
21653
  }
21660
21654
 
21661
21655
  setContentHeight(contentHeight) {
21662
-
21663
- // Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
21664
- contentHeight = Math.min(contentHeight, 32000);
21665
- this.$content.height(contentHeight);
21656
+ this.contentHeight = contentHeight;
21666
21657
  }
21667
21658
 
21668
21659
  isLoading() {
@@ -21686,7 +21677,7 @@ class Viewport {
21686
21677
  }
21687
21678
 
21688
21679
  getContentTop() {
21689
- return this.contentDiv.offsetTop
21680
+ return this.contentTop
21690
21681
  }
21691
21682
 
21692
21683
  containsPosition(chr, position) {
@@ -23852,7 +23843,7 @@ const Cytoband = function (start, end, name, typestain) {
23852
23843
  }
23853
23844
  };
23854
23845
 
23855
- const _version = "2.13.3";
23846
+ const _version = "2.13.4";
23856
23847
  function version() {
23857
23848
  return _version
23858
23849
  }
@@ -24358,7 +24349,7 @@ class TrackViewport extends Viewport {
24358
24349
 
24359
24350
  const track = this.trackView.track;
24360
24351
  if ('sequence' !== track.type) {
24361
- this.$zoomInNotice = this.createZoomInNotice(this.$content);
24352
+ this.$zoomInNotice = this.createZoomInNotice(this.$viewport);
24362
24353
  }
24363
24354
 
24364
24355
  if (track.name && "sequence" !== track.id) {
@@ -24469,6 +24460,34 @@ class TrackViewport extends Viewport {
24469
24460
  }
24470
24461
  }
24471
24462
 
24463
+ /**
24464
+ * Set the content top of the current view. This is triggered by scrolling. If the current canvas extent is not
24465
+ * sufficient to cover the new vertical range repaint.
24466
+ *
24467
+ * @param contentTop - the "top" property of the virtual content div, 0 unless track is scrolled vertically
24468
+ *
24469
+ *
24470
+ */
24471
+ setTop(contentTop) {
24472
+ super.setTop(contentTop);
24473
+
24474
+ if (!this.canvas) {
24475
+ this.repaint();
24476
+ } else {
24477
+ // See if currently painted canvas covers the vertical range of the viewport. If not repaint
24478
+ const h = this.$viewport.height();
24479
+ const vt = contentTop + this.canvas._data.canvasTop;
24480
+ const vb = contentTop + this.canvas._data.canvasBottom;
24481
+ if (vt > 0 || vb < h) {
24482
+ this.repaint();
24483
+ }
24484
+ }
24485
+
24486
+ // Now offset backing canvas to align with the contentTop visual offset.
24487
+ let offset = contentTop + this.canvas._data.canvasTop;
24488
+ this.canvas.style.top = `${offset}px`;
24489
+ }
24490
+
24472
24491
  async loadFeatures() {
24473
24492
 
24474
24493
  const referenceFrame = this.referenceFrame;
@@ -24476,7 +24495,7 @@ class TrackViewport extends Viewport {
24476
24495
 
24477
24496
  // Expand the requested range so we can pan a bit without reloading. But not beyond chromosome bounds
24478
24497
  const chrLength = this.browser.genome.getChromosome(chr).bpLength;
24479
- const pixelWidth = this.$content.width();// * 3;
24498
+ const pixelWidth = this.$viewport.width();// * 3;
24480
24499
  const bpWidth = pixelWidth * referenceFrame.bpPerPixel;
24481
24500
  const bpStart = Math.floor(Math.max(0, referenceFrame.start - bpWidth));
24482
24501
  const bpEnd = Math.ceil(Math.min(chrLength, referenceFrame.start + bpWidth + bpWidth)); // Add one screen width to end
@@ -24517,6 +24536,12 @@ class TrackViewport extends Viewport {
24517
24536
  }
24518
24537
  }
24519
24538
 
24539
+ /**
24540
+ * Compute the genomic extent and needed pixelWidth to repaint the canvas for the current genomic state.
24541
+ * Normally the canvas is size 3X the width of the viewport, however there is no left-right panning for WGV so
24542
+ * canvas width is viewport width.
24543
+ * @returns {{bpEnd: *, pixelWidth: (*|number), bpStart: number}}
24544
+ */
24520
24545
  repaintDimensions() {
24521
24546
  const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
24522
24547
  const pixelWidth = isWGV ? this.$viewport.width() : 3 * this.$viewport.width();
@@ -24539,12 +24564,8 @@ class TrackViewport extends Viewport {
24539
24564
  }
24540
24565
 
24541
24566
  const {features, roiFeatures} = this.featureCache;
24542
- //this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
24543
24567
 
24544
- // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
24545
- GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
24546
-
24547
- // Canvas dimensions. There is no left-right panning for WGV so canvas width is viewport width.
24568
+ // Canvas dimensions.
24548
24569
  // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
24549
24570
  const {bpStart, bpEnd, pixelWidth} = this.repaintDimensions();
24550
24571
  const viewportHeight = this.$viewport.height();
@@ -24557,14 +24578,15 @@ class TrackViewport extends Viewport {
24557
24578
  }
24558
24579
  return
24559
24580
  }
24560
- const canvasTop = Math.max(0, -(this.$content.position().top) - viewportHeight);
24581
+ const canvasTop = Math.max(0, -this.contentTop - viewportHeight);
24582
+
24561
24583
 
24562
24584
  const bpPerPixel = this.referenceFrame.bpPerPixel;
24563
- //const bpStart = this.referenceFrame.start - (isWGV ? 0 : pixelWidth / 3 * bpPerPixel)
24564
- //const bpEnd = this.referenceFrame.end + (isWGV ? 0 : pixelWidth / 3 * bpPerPixel)
24565
24585
  const pixelXOffset = Math.round((bpStart - this.referenceFrame.start) / bpPerPixel);
24566
24586
 
24567
- const newCanvas = $$1('<canvas class="igv-canvas">').get(0);
24587
+ const newCanvas = document.createElement('canvas');// $('<canvas class="igv-canvas">').get(0)
24588
+ newCanvas.style.position = 'relative';
24589
+ newCanvas.style.display = 'block';
24568
24590
  newCanvas.style.width = pixelWidth + "px";
24569
24591
  newCanvas.style.height = pixelHeight + "px";
24570
24592
  newCanvas.style.left = pixelXOffset + "px";
@@ -24590,6 +24612,8 @@ class TrackViewport extends Viewport {
24590
24612
  bpStart: bpStart,
24591
24613
  bpEnd: bpEnd,
24592
24614
  bpPerPixel,
24615
+ canvasTop,
24616
+ canvasBottom: canvasTop + pixelHeight,
24593
24617
  referenceFrame: this.referenceFrame,
24594
24618
  selection: this.selection,
24595
24619
  viewport: this,
@@ -24603,7 +24627,7 @@ class TrackViewport extends Viewport {
24603
24627
  }
24604
24628
  newCanvas._data = drawConfiguration;
24605
24629
  this.canvas = newCanvas;
24606
- this.$content.append($$1(newCanvas));
24630
+ this.$viewport.append($$1(newCanvas));
24607
24631
 
24608
24632
  }
24609
24633
 
@@ -24625,13 +24649,11 @@ class TrackViewport extends Viewport {
24625
24649
  */
24626
24650
  draw(drawConfiguration, features, roiFeatures) {
24627
24651
 
24628
- // console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
24629
-
24630
24652
  if (features) {
24631
24653
  drawConfiguration.features = features;
24632
24654
  this.trackView.track.draw(drawConfiguration);
24633
24655
  }
24634
- if (roiFeatures) {
24656
+ if (roiFeatures && roiFeatures.length > 0) {
24635
24657
  for (let r of roiFeatures) {
24636
24658
  drawConfiguration.features = r.features;
24637
24659
  r.track.draw(drawConfiguration);
@@ -24655,13 +24677,13 @@ class TrackViewport extends Viewport {
24655
24677
 
24656
24678
  if (!this.canvas) return
24657
24679
 
24658
- const canvasMetadata = this.featureCache;
24680
+ const canvasMetadata = this.canvas._data;
24659
24681
  const canvasTop = canvasMetadata ? canvasMetadata.canvasTop : 0;
24660
24682
  const devicePixelRatio = window.devicePixelRatio;
24661
24683
  const w = this.$viewport.width() * devicePixelRatio;
24662
24684
  const h = this.$viewport.height() * devicePixelRatio;
24663
24685
  const x = -$$1(this.canvas).position().left * devicePixelRatio;
24664
- const y = (-this.$content.position().top - canvasTop) * devicePixelRatio;
24686
+ const y = (-this.contentTop - canvasTop) * devicePixelRatio;
24665
24687
 
24666
24688
  const ctx = this.canvas.getContext("2d");
24667
24689
  const imageData = ctx.getImageData(x, y, w, h);
@@ -24679,57 +24701,82 @@ class TrackViewport extends Viewport {
24679
24701
 
24680
24702
  saveSVG() {
24681
24703
 
24682
- const {width, height} = this.$viewport.get(0).getBoundingClientRect();
24704
+ const marginTop = 32;
24705
+ const marginLeft = 32;
24706
+
24707
+ let {width, height} = this.browser.columnContainer.getBoundingClientRect();
24708
+
24709
+ const h_render = 8000;
24683
24710
 
24684
24711
  const config =
24685
24712
  {
24713
+
24686
24714
  width,
24687
- height,
24715
+ height: h_render,
24716
+
24717
+ backdropColor: 'white',
24718
+
24719
+ multiLocusGap: 0,
24720
+
24688
24721
  viewbox:
24689
24722
  {
24690
24723
  x: 0,
24691
- y: -this.$content.position().top,
24724
+ y: 0,
24692
24725
  width,
24693
- height
24726
+ height: h_render
24694
24727
  }
24695
24728
 
24696
24729
  };
24697
24730
 
24698
24731
  const context = new ctx(config);
24699
24732
 
24700
- const str = (this.trackView.track.name || this.trackView.track.id).replace(/\W/g, '');
24733
+ const delta =
24734
+ {
24735
+ deltaX: marginLeft,
24736
+ deltaY: marginTop
24737
+ };
24701
24738
 
24702
- const index = this.browser.referenceFrameList.indexOf(this.referenceFrame);
24703
- const id = `${str}_referenceFrame_${index}_guid_${guid$2()}`;
24739
+ this.renderViewportToSVG(context, delta);
24740
+
24741
+ // reset height to trim away unneeded svg canvas real estate. Yes, a bit of a hack.
24742
+ context.setHeight(height);
24704
24743
 
24705
- this.drawSVGWithContext(context, width, height, id, 0, 0, 0);
24744
+ const str = (this.trackView.track.name || this.trackView.track.id).replace(/\W/g, '');
24745
+ const index = this.browser.referenceFrameList.indexOf(this.referenceFrame);
24706
24746
 
24707
24747
  const svg = context.getSerializedSvg(true);
24708
24748
  const data = URL.createObjectURL(new Blob([svg], {type: "application/octet-stream"}));
24709
24749
 
24750
+ const id = `${str}_referenceFrame_${index}_guid_${guid$2()}`;
24710
24751
  download(`${id}.svg`, data);
24752
+
24711
24753
  }
24712
24754
 
24713
24755
  // called by trackView.renderSVGContext() when rendering
24714
24756
  // entire browser as SVG
24715
24757
 
24716
- renderSVGContext(context, {deltaX, deltaY}) {
24758
+ renderViewportToSVG(context, {deltaX, deltaY}) {
24717
24759
 
24718
- // Nothing to do if zoomInNotice is active
24719
24760
  if (this.$zoomInNotice && this.$zoomInNotice.is(":visible")) {
24720
24761
  return
24721
24762
  }
24722
24763
 
24723
- const str = (this.trackView.track.name || this.trackView.track.id).replace(/\W/g, '');
24764
+ const {width, height} = this.$viewport.get(0).getBoundingClientRect();
24724
24765
 
24766
+ const str = (this.trackView.track.name || this.trackView.track.id).replace(/\W/g, '');
24725
24767
  const index = this.browser.referenceFrameList.indexOf(this.referenceFrame);
24726
24768
  const id = `${str}_referenceFrame_${index}_guid_${guid$2()}`;
24769
+ this.drawSVGWithContext(context, width, height, id, deltaX, deltaY + this.contentTop, -this.contentTop);
24727
24770
 
24728
- const {top: yScrollDelta} = this.$content.position();
24771
+ }
24729
24772
 
24730
- const {width, height} = this.$viewport.get(0).getBoundingClientRect();
24773
+ renderSVGContext(context, {deltaX, deltaY}) {
24731
24774
 
24732
- this.drawSVGWithContext(context, width, height, id, deltaX, deltaY + yScrollDelta, -yScrollDelta);
24775
+ this.renderViewportToSVG(context, {deltaX, deltaY});
24776
+
24777
+ if (this.$zoomInNotice && this.$zoomInNotice.is(":visible")) {
24778
+ return
24779
+ }
24733
24780
 
24734
24781
  if (this.$trackLabel && true === this.browser.trackLabelsVisible) {
24735
24782
  const {x, y, width, height} = relativeDOMBBox(this.$viewport.get(0), this.$trackLabel.get(0));
@@ -24787,7 +24834,7 @@ class TrackViewport extends Viewport {
24787
24834
  selection: this.selection
24788
24835
  };
24789
24836
 
24790
- const features = this.featureCache ? this.featureCache.features : [];
24837
+ const features = this.featureCache ? this.featureCache.features : undefined;
24791
24838
  const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
24792
24839
  this.draw(config, features, roiFeatures);
24793
24840
 
@@ -24887,22 +24934,21 @@ class TrackViewport extends Viewport {
24887
24934
  viewport.addEventListener('touchend', mu);
24888
24935
 
24889
24936
  // Mouse move
24890
- if (typeof this.trackView.track._hoverText === 'function') {
24937
+ if (typeof this.trackView.track.hoverText === 'function') {
24891
24938
  viewport.addEventListener('mousemove', (event => {
24892
- if (Date.now() - lastHoverUpdateTime > 100) {
24939
+ if (event.buttons === 0 && (Date.now() - lastHoverUpdateTime > 100)) {
24893
24940
  lastHoverUpdateTime = Date.now();
24894
- const clickState = createClickState(event, this);
24941
+ const clickState = this.createClickState(event);
24895
24942
  if (clickState) {
24896
- const hoverText = this.trackView.track._hoverText(clickState);
24897
- if (hoverText) {
24898
- this.$viewport[0].setAttribute("title", hoverText);
24943
+ const tooltip = this.trackView.track.hoverText(clickState);
24944
+ if (tooltip) {
24945
+ this.$viewport[0].setAttribute("title", tooltip);
24899
24946
  } else {
24900
24947
  this.$viewport[0].removeAttribute("title");
24901
24948
  }
24902
24949
  }
24903
24950
  }
24904
24951
  }));
24905
-
24906
24952
  }
24907
24953
 
24908
24954
  this.addViewportClickHandler(this.$viewport.get(0));
@@ -24922,7 +24968,7 @@ class TrackViewport extends Viewport {
24922
24968
  return false
24923
24969
  }
24924
24970
 
24925
- const clickState = createClickState(event, this);
24971
+ const clickState = this.createClickState(event);
24926
24972
 
24927
24973
  if (undefined === clickState) {
24928
24974
  return false
@@ -25024,7 +25070,7 @@ class TrackViewport extends Viewport {
25024
25070
 
25025
25071
  popupTimerID = setTimeout(() => {
25026
25072
 
25027
- const content = getPopupContent(event, this);
25073
+ const content = this.getPopupContent(event);
25028
25074
  if (content) {
25029
25075
  if (this.popover) this.popover.dispose();
25030
25076
  this.popover = new Popover(this.browser.columnContainer);
@@ -25068,57 +25114,58 @@ class TrackViewport extends Viewport {
25068
25114
  });
25069
25115
  }
25070
25116
 
25071
- }
25117
+ createClickState(event) {
25072
25118
 
25073
- function createClickState(event, viewport) {
25119
+ if (!this.canvas) return // Can happen during initialization
25074
25120
 
25075
- if (!viewport.contentDiv || !viewport.canvas) return // Can happen during initialization
25121
+ const referenceFrame = this.referenceFrame;
25122
+ const viewportCoords = translateMouseCoordinates$1(event, this.$viewport.get(0));
25123
+ const canvasCoords = translateMouseCoordinates$1(event, this.canvas);
25124
+ const genomicLocation = ((referenceFrame.start) + referenceFrame.toBP(viewportCoords.x));
25076
25125
 
25077
- const referenceFrame = viewport.referenceFrame;
25078
- const viewportCoords = translateMouseCoordinates$1(event, viewport.contentDiv);
25079
- const canvasCoords = translateMouseCoordinates$1(event, viewport.canvas);
25080
- const genomicLocation = ((referenceFrame.start) + referenceFrame.toBP(viewportCoords.x));
25126
+ return {
25127
+ event,
25128
+ viewport: this,
25129
+ referenceFrame,
25130
+ genomicLocation,
25131
+ y: viewportCoords.y - this.contentTop,
25132
+ canvasX: canvasCoords.x,
25133
+ canvasY: canvasCoords.y
25134
+ }
25081
25135
 
25082
- return {
25083
- event,
25084
- viewport,
25085
- referenceFrame,
25086
- genomicLocation,
25087
- x: viewportCoords.x,
25088
- y: viewportCoords.y,
25089
- canvasX: canvasCoords.x,
25090
- canvasY: canvasCoords.y
25091
25136
  }
25092
25137
 
25093
- }
25138
+ getPopupContent(event) {
25094
25139
 
25095
- function getPopupContent(event, viewport) {
25140
+ const clickState = this.createClickState(event);
25096
25141
 
25097
- const clickState = createClickState(event, viewport);
25142
+ if (undefined === clickState) {
25143
+ return
25144
+ }
25098
25145
 
25099
- if (undefined === clickState) {
25100
- return
25101
- }
25146
+ let track = this.trackView.track;
25147
+ const dataList = track.popupData(clickState);
25102
25148
 
25103
- let track = viewport.trackView.track;
25104
- const dataList = track.popupData(clickState);
25149
+ const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList]);
25105
25150
 
25106
- const popupClickHandlerResult = viewport.browser.fireEvent('trackclick', [track, dataList]);
25151
+ let content;
25152
+ if (undefined === popupClickHandlerResult || true === popupClickHandlerResult) {
25153
+ // Indicates handler did not handle the result, or the handler wishes default behavior to occur
25154
+ if (dataList && dataList.length > 0) {
25155
+ content = formatPopoverText(dataList);
25156
+ }
25107
25157
 
25108
- let content;
25109
- if (undefined === popupClickHandlerResult || true === popupClickHandlerResult) {
25110
- // Indicates handler did not handle the result, or the handler wishes default behavior to occur
25111
- if (dataList && dataList.length > 0) {
25112
- content = formatPopoverText(dataList);
25158
+ } else if (typeof popupClickHandlerResult === 'string') {
25159
+ content = popupClickHandlerResult;
25113
25160
  }
25114
25161
 
25115
- } else if (typeof popupClickHandlerResult === 'string') {
25116
- content = popupClickHandlerResult;
25162
+ return content
25117
25163
  }
25118
25164
 
25119
- return content
25165
+
25120
25166
  }
25121
25167
 
25168
+
25122
25169
  function formatPopoverText(nameValues) {
25123
25170
 
25124
25171
  const rows = nameValues.map(nameValue => {
@@ -26717,6 +26764,7 @@ class TrackBase {
26717
26764
  * @param config
26718
26765
  */
26719
26766
  init(config) {
26767
+
26720
26768
  if (config.displayMode) {
26721
26769
  config.displayMode = config.displayMode.toUpperCase();
26722
26770
  }
@@ -26772,8 +26820,12 @@ class TrackBase {
26772
26820
  }
26773
26821
  }
26774
26822
 
26775
- if(config.hoverTextFields) {
26776
- this.hoverTextFields = config.hoverTextFields;
26823
+ // Support for mouse hover text. This can be expensive, off by default.
26824
+ // this.hoverText = function(clickState) => return tool tip text
26825
+ if (config.hoverTextFields) {
26826
+ this.hoverText = hoverText.bind(this);
26827
+ } else if (typeof this.config.hoverText === 'function') {
26828
+ this.hoverText = this.config.hoverText;
26777
26829
  }
26778
26830
  }
26779
26831
 
@@ -26888,7 +26940,7 @@ class TrackBase {
26888
26940
  */
26889
26941
  setTrackProperties(properties) {
26890
26942
 
26891
- if(this.disposed) return; // This track was removed during async load
26943
+ if (this.disposed) return // This track was removed during async load
26892
26944
 
26893
26945
  const tracklineConfg = {};
26894
26946
  let tokens;
@@ -26991,20 +27043,20 @@ class TrackBase {
26991
27043
  }
26992
27044
 
26993
27045
  /**
26994
- * Return the features clicked over. Default implementation assumes a single row of features and only considers
27046
+ * Return the features clicked over. Default implementation assumes an array of features and only considers
26995
27047
  * the genomic location. Overriden by most subclasses.
26996
27048
  *
26997
27049
  * @param clickState
26998
27050
  * @param features
26999
27051
  * @returns {[]|*[]}
27000
27052
  */
27001
- clickedFeatures(clickState, features) {
27053
+ clickedFeatures(clickState) {
27002
27054
 
27003
27055
  // We use the cached features rather than method to avoid async load. If the
27004
27056
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
27005
- if (!features) features = clickState.viewport.cachedFeatures;
27057
+ const features = clickState.viewport.cachedFeatures;
27006
27058
 
27007
- if (!features || features.length === 0) {
27059
+ if (!features || !Array.isArray(features) || features.length === 0) {
27008
27060
  return []
27009
27061
  }
27010
27062
 
@@ -27102,65 +27154,6 @@ class TrackBase {
27102
27154
 
27103
27155
  }
27104
27156
 
27105
- /**
27106
- * Return a plain string for descriptive text given the list of features. The string is used to set a "title"
27107
- * attribute and cannot contain any html.
27108
- *
27109
- * @returns {undefined}
27110
- */
27111
- _hoverText(clickState) {
27112
-
27113
- const features = this.clickedFeatures(clickState);
27114
-
27115
- if (features && features.length > 0) {
27116
- let str = "";
27117
- for (let i = 0; i < features.length; i++) {
27118
- if (i === 10) {
27119
- str += "; ...";
27120
- break
27121
- }
27122
- if (!features[i]) continue
27123
-
27124
- const f = features[i]._f || features[i];
27125
- if (str.length > 0) str += "\n";
27126
-
27127
- if(this.hoverTextFields) {
27128
- str = "";
27129
- for(let field of this.hoverTextFields) {
27130
- if(str.length > 0) str += "\n";
27131
- if(f.hasOwnProperty(field)) {
27132
- str += f[field];
27133
- } else if(typeof f.getAttribute === "function") {
27134
- str += f.getAttribute(field);
27135
- }
27136
- }
27137
- }
27138
- else if (typeof this.config.hoverText === 'function') {
27139
- str += this.config.hoverText(f);
27140
- } else if (typeof f.hoverText === 'function') {
27141
- str += f.hoverText();
27142
- } else {
27143
- str += this.hoverText(f);
27144
- }
27145
- }
27146
- return str
27147
- }
27148
- }
27149
-
27150
- /**
27151
- * Default hoverText function. Can be overridden in track configuration or subclasses
27152
- *
27153
- * @param f - feature hovered over
27154
- */
27155
- hoverText(f) {
27156
- let str = "";
27157
- if (f.name) {
27158
- str += f.name;
27159
- if (f.value) str += ": ";
27160
- }
27161
- if (f.value) str += f.value; // TODO format value if number
27162
- return str;
27163
- }
27164
27157
 
27165
27158
  /**
27166
27159
  * Default track description -- displayed on click of track label. This can be overriden in the track
@@ -27233,6 +27226,38 @@ class TrackBase {
27233
27226
  }
27234
27227
  }
27235
27228
 
27229
+ function hoverText(clickState) {
27230
+
27231
+ if (!this.hoverTextFields) return
27232
+
27233
+ const features = this.clickedFeatures(clickState);
27234
+
27235
+ if (features && features.length > 0) {
27236
+ let str = "";
27237
+ for (let i = 0; i < features.length; i++) {
27238
+ if (i === 10) {
27239
+ str += "; ...";
27240
+ break
27241
+ }
27242
+ if (!features[i]) continue
27243
+
27244
+ const f = features[i]._f || features[i];
27245
+ if (str.length > 0) str += "\n";
27246
+
27247
+ str = "";
27248
+ for (let field of this.hoverTextFields) {
27249
+ if (str.length > 0) str += "\n";
27250
+ if (f.hasOwnProperty(field)) {
27251
+ str += f[field];
27252
+ } else if (typeof f.getAttribute === "function") {
27253
+ str += f.getAttribute(field);
27254
+ }
27255
+ }
27256
+
27257
+ }
27258
+ return str
27259
+ }
27260
+ }
27236
27261
 
27237
27262
  /**
27238
27263
  * Map UCSC track line "type" setting to file format. In igv.js "type" refers to the track type, not the input file format
@@ -27431,12 +27456,6 @@ class SegFeature {
27431
27456
  return pd
27432
27457
  }
27433
27458
 
27434
- hoverText() {
27435
-
27436
- return `${this.sample}: ${this.value}`
27437
- }
27438
-
27439
-
27440
27459
  extractCravatLink(genomeId) {
27441
27460
 
27442
27461
  let ref, alt;
@@ -27720,10 +27739,6 @@ class Variant {
27720
27739
  return fields
27721
27740
  }
27722
27741
 
27723
- hoverText() {
27724
- return `${this.names} ${this.referenceBases}->${this.alternateBases}`
27725
- }
27726
-
27727
27742
  getInfo(tag) {
27728
27743
  return this.info ? this.info[tag] : undefined;
27729
27744
  }
@@ -28942,10 +28957,9 @@ class CSIIndex {
28942
28957
  for (let bin = binRange[0]; bin <= binRange[1]; bin++) {
28943
28958
  if (ba.binIndex[bin]) {
28944
28959
  const binChunks = ba.binIndex[bin];
28945
- const nchnk = binChunks.length;
28946
- for (let c = 0; c < nchnk; ++c) {
28947
- const cs = binChunks[c][0];
28948
- const ce = binChunks[c][1];
28960
+ for (let c of binChunks) {
28961
+ const cs = c[0];
28962
+ const ce = c[1];
28949
28963
  chunks.push({minv: cs, maxv: ce, bin: bin});
28950
28964
  }
28951
28965
  }
@@ -28963,7 +28977,7 @@ class CSIIndex {
28963
28977
  reg2bins(beg, end) {
28964
28978
  beg -= 1; // < convert to 1-based closed
28965
28979
  if (beg < 1) beg = 1;
28966
- if (end > 2 ** 50) end = 2 ** 34; // 17 GiB ought to be enough for anybody
28980
+ if (end > 2 ** 34) end = 2 ** 34; // 17 GiB ought to be enough for anybody
28967
28981
  end -= 1;
28968
28982
  let l = 0;
28969
28983
  let t = 0;
@@ -28972,25 +28986,18 @@ class CSIIndex {
28972
28986
  for (; l <= this.depth; s -= 3, t += (1 << l * 3), l += 1) {
28973
28987
  const b = t + (beg >> s);
28974
28988
  const e = t + (end >> s);
28975
- if (e - b + bins.length > this.maxBinNumber)
28976
- throw new Error(
28977
- `query ${beg}-${end} is too large for current binning scheme (shift ${this.minShift}, depth ${this.depth}), try a smaller query or a coarser index binning scheme`,
28978
- )
28979
- //for (let i = b; i <= e; i += 1) bins.push(i)
28989
+ //
28990
+ // ITS NOT CLEAR WHERE THIS TEST CAME FROM, but maxBinNumber is never set, and its not clear what it represents.
28991
+ // if (e - b + bins.length > this.maxBinNumber)
28992
+ // throw new Error(
28993
+ // `query ${beg}-${end} is too large for current binning scheme (shift ${this.minShift}, depth ${this.depth}), try a smaller query or a coarser index binning scheme`,
28994
+ // )
28995
+ //
28980
28996
  bins.push([b, e]);
28981
28997
  }
28982
28998
  return bins
28983
28999
  }
28984
29000
 
28985
- // function reg2bins(beg, end, min_shift, depth) {
28986
- // let l, t, n, s = min_shift + depth * 3;
28987
- // const bins = [];
28988
- // for (--end, l = n = t = 0; l <= depth; s -= 3, t += 1 << l * 3, ++l) {
28989
- // let b = t + (beg >> s), e = t + (end >> s), i;
28990
- // for (i = b; i <= e; ++i) bins[n++] = i;
28991
- // }
28992
- // return bins;
28993
- // }
28994
29001
 
28995
29002
  bin_limit() {
28996
29003
  return ((1 << (this.depth + 1) * 3) - 1) / 7
@@ -29193,11 +29200,10 @@ class BamIndex {
29193
29200
  for (let binRange of overlappingBins) {
29194
29201
  for (let bin = binRange[0]; bin <= binRange[1]; bin++) {
29195
29202
  if (ba.binIndex[bin]) {
29196
- const binChunks = ba.binIndex[bin],
29197
- nchnk = binChunks.length;
29198
- for (let c = 0; c < nchnk; ++c) {
29199
- const cs = binChunks[c][0];
29200
- const ce = binChunks[c][1];
29203
+ const binChunks = ba.binIndex[bin];
29204
+ for (let c of binChunks) {
29205
+ const cs = c[0];
29206
+ const ce = c[1];
29201
29207
  chunks.push({minv: cs, maxv: ce, bin: bin});
29202
29208
  }
29203
29209
  }
@@ -29209,7 +29215,7 @@ class BamIndex {
29209
29215
  let lowest = null;
29210
29216
  const minLin = Math.min(min >> 14, nintv - 1);
29211
29217
  const maxLin = Math.min(max >> 14, nintv - 1);
29212
- for (let i = minLin; i <= maxLin; ++i) {
29218
+ for (let i = minLin; i < maxLin; i++) {
29213
29219
  const vp = ba.linearIndex[i];
29214
29220
  if (vp) {
29215
29221
  // todo -- I think, but am not sure, that the values in the linear index have to be in increasing order. So the first non-null should be minimum
@@ -35129,10 +35135,6 @@ class BamAlignment {
35129
35135
  return (genomicLocation >= s && genomicLocation <= (s + l))
35130
35136
  }
35131
35137
 
35132
- hoverText() {
35133
- return this.readName;
35134
- }
35135
-
35136
35138
  popupData(genomicLocation) {
35137
35139
 
35138
35140
  // if the user clicks on a base next to an insertion, show just the
@@ -39984,6 +39986,14 @@ class BAMTrack extends TrackBase {
39984
39986
  return clickedObject ? [clickedObject] : undefined
39985
39987
  }
39986
39988
 
39989
+ hoverText(clickState) {
39990
+ if (true === this.showCoverage && clickState.y >= this.coverageTrack.top && clickState.y < this.coverageTrack.height) {
39991
+ const clickedObject = this.coverageTrack.getClickedObject(clickState);
39992
+ return clickedObject.hoverText()
39993
+ }
39994
+
39995
+ }
39996
+
39987
39997
  menuItemList() {
39988
39998
 
39989
39999
 
@@ -41401,6 +41411,10 @@ class RulerViewport extends TrackViewport {
41401
41411
  super(trackView, $viewportColumn, referenceFrame, width);
41402
41412
  }
41403
41413
 
41414
+ get contentDiv() {
41415
+ return this.$viewport.get(0)
41416
+ }
41417
+
41404
41418
  initializationHelper() {
41405
41419
 
41406
41420
  this.$multiLocusCloseButton = $$1('<div>', {class: 'igv-multi-locus-close-button'});
@@ -41628,7 +41642,8 @@ class IdeogramViewport extends TrackViewport {
41628
41642
 
41629
41643
  this.canvas = document.createElement('canvas');
41630
41644
  this.canvas.className = 'igv-ideogram-canvas';
41631
- this.$content.append($$1(this.canvas));
41645
+ //this.$content.append($(this.canvas))
41646
+ this.$viewport.append($$1(this.canvas));
41632
41647
  this.ideogram_ctx = this.canvas.getContext('2d');
41633
41648
 
41634
41649
  this.addMouseHandlers();
@@ -42202,7 +42217,7 @@ const hideAllMenuPopups = () => {
42202
42217
  * THE SOFTWARE.
42203
42218
  */
42204
42219
 
42205
- const scrollbarExclusionTypes = new Set(['ruler', 'sequence', 'ideogram']);
42220
+ const scrollbarExclusionTypes = new Set(['ruler', 'ideogram']);
42206
42221
  const colorPickerExclusionTypes = new Set(['ruler', 'sequence', 'ideogram']);
42207
42222
 
42208
42223
  class TrackView {
@@ -42582,17 +42597,18 @@ class TrackView {
42582
42597
  }
42583
42598
 
42584
42599
  if (this.track.autoscale) {
42585
- let allFeatures = [];
42600
+ let allFeatures;
42586
42601
  for (let visibleViewport of visibleViewports) {
42587
42602
  const referenceFrame = visibleViewport.referenceFrame;
42588
42603
  const start = referenceFrame.start;
42589
- const end = start + referenceFrame.toBP($$1(visibleViewport.contentDiv).width());
42604
+ const end = start + referenceFrame.toBP(visibleViewport.getWidth());
42590
42605
  if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
42606
+ // If the "features" object has a getMax function use it. Currently only alignmentContainer implements this, for coverage.
42591
42607
  if (typeof visibleViewport.featureCache.features.getMax === 'function') {
42592
42608
  const max = visibleViewport.featureCache.features.getMax(start, end);
42593
- allFeatures.push({value: max});
42609
+ allFeatures = [{value: max}];
42594
42610
  } else {
42595
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end));
42611
+ allFeatures = FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end);
42596
42612
  }
42597
42613
  }
42598
42614
  }
@@ -42657,7 +42673,7 @@ class TrackView {
42657
42673
 
42658
42674
  const referenceFrame = vp.referenceFrame;
42659
42675
  const {chr, start, bpPerPixel} = vp.referenceFrame;
42660
- const end = start + referenceFrame.toBP($$1(vp.contentDiv).width());
42676
+ const end = start + referenceFrame.toBP(vp.getWidth());
42661
42677
  const needsReload = !vp.featureCache || !vp.featureCache.containsRange(chr, start, end, bpPerPixel);
42662
42678
 
42663
42679
  if (needsReload) {
@@ -43549,7 +43565,7 @@ class FeatureTrack extends TrackBase {
43549
43565
 
43550
43566
  if (typeof this.featureSource.getHeader === "function") {
43551
43567
  this.header = await this.featureSource.getHeader();
43552
- if(this.disposed) return; // This track was removed during async load
43568
+ if (this.disposed) return // This track was removed during async load
43553
43569
  }
43554
43570
 
43555
43571
  // Set properties from track line
@@ -43642,7 +43658,7 @@ class FeatureTrack extends TrackBase {
43642
43658
  options.rowLastLabelX[row] = -Number.MAX_SAFE_INTEGER;
43643
43659
  }
43644
43660
  }
43645
- const maxFeatureCount = Math.max(1, Math.max(...rowFeatureCount));
43661
+ const maxFeatureCount = Math.max(1, Math.max(...rowFeatureCount));
43646
43662
  const pixelsPerFeature = pixelWidth / maxFeatureCount;
43647
43663
 
43648
43664
  let lastPxEnd = [];
@@ -43673,10 +43689,10 @@ class FeatureTrack extends TrackBase {
43673
43689
 
43674
43690
  };
43675
43691
 
43676
- clickedFeatures(clickState, features) {
43692
+ clickedFeatures(clickState) {
43677
43693
 
43678
43694
  const y = clickState.y - this.margin;
43679
- const allFeatures = super.clickedFeatures(clickState, features);
43695
+ const allFeatures = super.clickedFeatures(clickState);
43680
43696
 
43681
43697
  let row;
43682
43698
  switch (this.displayMode) {
@@ -43700,9 +43716,8 @@ class FeatureTrack extends TrackBase {
43700
43716
  */
43701
43717
  popupData(clickState, features) {
43702
43718
 
43703
- features = this.clickedFeatures(clickState, features);
43719
+ if (features === undefined) features = this.clickedFeatures(clickState);
43704
43720
  const genomicLocation = clickState.genomicLocation;
43705
-
43706
43721
  const data = [];
43707
43722
  for (let feature of features) {
43708
43723
 
@@ -43827,8 +43842,7 @@ class FeatureTrack extends TrackBase {
43827
43842
  let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
43828
43843
  if (!seq) {
43829
43844
  seq = "Unknown sequence";
43830
- }
43831
- else if (f.strand === '-') {
43845
+ } else if (f.strand === '-') {
43832
43846
  seq = reverseComplementSequence(seq);
43833
43847
  }
43834
43848
  this.browser.alert.present(seq);
@@ -43844,8 +43858,7 @@ class FeatureTrack extends TrackBase {
43844
43858
  let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
43845
43859
  if (!seq) {
43846
43860
  seq = "Unknown sequence";
43847
- }
43848
- else if (f.strand === '-') {
43861
+ } else if (f.strand === '-') {
43849
43862
  seq = reverseComplementSequence(seq);
43850
43863
  }
43851
43864
  try {
@@ -44162,10 +44175,7 @@ class WigTrack extends TrackBase {
44162
44175
 
44163
44176
  popupData(clickState, features) {
44164
44177
 
44165
- // We use the featureCache property rather than method to avoid async load. If the
44166
- // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
44167
-
44168
- features = this.clickedFeatures(clickState, features);
44178
+ if(features === undefined) features = this.clickedFeatures(clickState);
44169
44179
 
44170
44180
  if (features && features.length > 0) {
44171
44181
 
@@ -44483,7 +44493,7 @@ class SegTrack extends TrackBase {
44483
44493
 
44484
44494
  draw({context, renderSVG, pixelTop, pixelWidth, pixelHeight, features, bpPerPixel, bpStart}) {
44485
44495
 
44486
- IGVGraphics.fillRect(context, 0, 0, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
44496
+ IGVGraphics.fillRect(context, 0, pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
44487
44497
 
44488
44498
  if (features && features.length > 0) {
44489
44499
 
@@ -44707,9 +44717,9 @@ class SegTrack extends TrackBase {
44707
44717
 
44708
44718
  }
44709
44719
 
44710
- clickedFeatures(clickState, features) {
44720
+ clickedFeatures(clickState) {
44711
44721
 
44712
- const allFeatures = super.clickedFeatures(clickState, features);
44722
+ const allFeatures = super.clickedFeatures(clickState);
44713
44723
  const y = clickState.y;
44714
44724
  return allFeatures.filter(function (feature) {
44715
44725
  const rect = feature.pixelRect;
@@ -44718,13 +44728,16 @@ class SegTrack extends TrackBase {
44718
44728
 
44719
44729
  }
44720
44730
 
44721
- hoverText(feature) {
44722
- return `${feature.sample}: ${feature.value}`
44731
+ hoverText(clickState) {
44732
+ const features = this.clickedFeatures(clickState);
44733
+ if(features && features.length > 0) {
44734
+ return `${features[0].sample}: ${features[0].value}`
44735
+ }
44723
44736
  }
44724
44737
 
44725
44738
  popupData(clickState, featureList) {
44726
44739
 
44727
- featureList = this.clickedFeatures(clickState);
44740
+ if(featureList === undefined) featureList = this.clickedFeatures(clickState);
44728
44741
 
44729
44742
  const items = [];
44730
44743
 
@@ -44880,6 +44893,7 @@ const MUT_COLORS = {
44880
44893
  * THE SOFTWARE.
44881
44894
  */
44882
44895
 
44896
+
44883
44897
  /**
44884
44898
  * Represents 2 or more wig tracks overlaid on a common viewport.
44885
44899
  */
@@ -44970,49 +44984,78 @@ class MergedTrack extends TrackBase {
44970
44984
  return items
44971
44985
  }
44972
44986
 
44987
+ /**
44988
+ * Returns a MergedFeatureCollection containing an array of features for the specified range, 1 for each track.
44989
+ */
44973
44990
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
44974
44991
 
44975
44992
  const promises = this.tracks.map((t) => t.getFeatures(chr, bpStart, bpEnd, bpPerPixel));
44976
- return Promise.all(promises)
44993
+ const featureArrays = await Promise.all(promises);
44994
+ return new MergedFeatureCollection(featureArrays)
44977
44995
  }
44978
44996
 
44979
44997
  draw(options) {
44980
44998
 
44981
- const mergedFeatures = options.features; // Array of feature arrays, 1 for each track
44982
-
44983
- if (this.autoscale) {
44984
- this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
44985
- }
44999
+ const mergedFeatures = options.features; // A MergedFeatureCollection
44986
45000
 
44987
45001
  for (let i = 0, len = this.tracks.length; i < len; i++) {
44988
45002
  const trackOptions = Object.assign({}, options);
44989
- trackOptions.features = mergedFeatures[i];
45003
+ trackOptions.features = mergedFeatures.featureArrays[i];
44990
45004
  this.tracks[i].dataRange = this.dataRange;
44991
45005
  this.tracks[i].flipAxis = this.flipAxis;
44992
45006
  this.tracks[i].logScale = this.logScale;
44993
- if(this.graphType){
45007
+ if (this.graphType) {
44994
45008
  this.tracks[i].graphType = this.graphType;
44995
45009
  }
44996
45010
  this.tracks[i].draw(trackOptions);
44997
45011
  }
44998
45012
  }
44999
45013
 
45000
- popupData(clickState, features) {
45014
+ popupData(clickState) {
45001
45015
 
45002
- const featuresArray = features || clickState.viewport.cachedFeatures;
45016
+ if(clickState.viewport && clickState.viewport.cachedFeatures) {
45003
45017
 
45004
- if (featuresArray && featuresArray.length === this.tracks.length) {
45005
- // Array of feature arrays, 1 for each track
45006
- const popupData = [];
45007
- for (let i = 0; i < this.tracks.length; i++) {
45008
- if (i > 0) popupData.push('<hr/>');
45009
- popupData.push(`<div style=background-color:rgb(245,245,245);border-bottom-style:dashed;border-bottom-width:1px;padding-bottom:5px;padding-top:10px;font-weight:bold;font-size:larger >${this.tracks[i].name}</div>`);
45010
- const trackPopupData = this.tracks[i].popupData(clickState, featuresArray[i]);
45011
- popupData.push(...trackPopupData);
45018
+ const featuresArray = clickState.viewport.cachedFeatures.featureArrays;
45019
+
45020
+ if (featuresArray && featuresArray.length === this.tracks.length) {
45021
+ // Array of feature arrays, 1 for each track
45022
+ const popupData = [];
45023
+ for (let i = 0; i < this.tracks.length; i++) {
45024
+ if (i > 0) popupData.push('<hr/>');
45025
+ popupData.push(`<div style=background-color:rgb(245,245,245);border-bottom-style:dashed;border-bottom-width:1px;padding-bottom:5px;padding-top:10px;font-weight:bold;font-size:larger >${this.tracks[i].name}</div>`);
45026
+ const trackPopupData = this.tracks[i].popupData(clickState, featuresArray[i]);
45027
+ popupData.push(...trackPopupData);
45012
45028
 
45029
+ }
45030
+ return popupData
45031
+ }
45032
+ }
45033
+ }
45034
+
45035
+ clickedFeatures(clickState) {
45036
+
45037
+
45038
+ // We use the cached features rather than method to avoid async load. If the
45039
+ // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
45040
+ const mergedFeaturesCollection = clickState.viewport.cachedFeatures;
45041
+
45042
+ if(!mergedFeaturesCollection) {
45043
+ return [];
45044
+ }
45045
+
45046
+ const genomicLocation = clickState.genomicLocation;
45047
+ const clickedFeatures = [];
45048
+ for(let features of mergedFeaturesCollection.featureArrays) {
45049
+ // When zoomed out we need some tolerance around genomicLocation
45050
+ const tolerance = (clickState.referenceFrame.bpPerPixel > 0.2) ? 3 * clickState.referenceFrame.bpPerPixel : 0.2;
45051
+ const ss = genomicLocation - tolerance;
45052
+ const ee = genomicLocation + tolerance;
45053
+ const tmp = (FeatureUtils.findOverlapping(features, ss, ee));
45054
+ for(let f of tmp) {
45055
+ clickedFeatures.push(f);
45013
45056
  }
45014
- return popupData
45015
45057
  }
45058
+ return clickedFeatures;
45016
45059
  }
45017
45060
 
45018
45061
 
@@ -45021,19 +45064,31 @@ class MergedTrack extends TrackBase {
45021
45064
  }
45022
45065
  }
45023
45066
 
45024
- function autoscale(chr, featureArrays) {
45025
45067
 
45026
- let min = 0;
45027
- let max = -Number.MAX_VALUE;
45028
- for(let features of featureArrays) {
45029
- for(let f of features) {
45030
- if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
45031
- min = Math.min(min, f.value);
45032
- max = Math.max(max, f.value);
45068
+ class MergedFeatureCollection {
45069
+
45070
+ constructor(featureArrays) {
45071
+ this.featureArrays = featureArrays;
45072
+ }
45073
+
45074
+ getMax(start, end) {
45075
+ let max = -Number.MAX_VALUE;
45076
+ for (let a of this.featureArrays) {
45077
+ for (let f of a) {
45078
+ if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
45079
+ if (f.end < start) {
45080
+ continue
45081
+ }
45082
+ if (f.start > end) {
45083
+ break
45084
+ }
45085
+ max = Math.max(max, f.value);
45086
+ }
45033
45087
  }
45034
45088
  }
45089
+ return max
45035
45090
  }
45036
- return {min: min, max: max}
45091
+
45037
45092
  }
45038
45093
 
45039
45094
  /*
@@ -45616,7 +45671,7 @@ class InteractionTrack extends TrackBase {
45616
45671
 
45617
45672
  popupData(clickState, features) {
45618
45673
 
45619
- features = this.clickedFeatures(clickState);
45674
+ if(features === undefined) features = this.clickedFeatures(clickState);
45620
45675
 
45621
45676
  const data = [];
45622
45677
  for (let feature of features) {
@@ -45658,11 +45713,11 @@ class InteractionTrack extends TrackBase {
45658
45713
  return data
45659
45714
  }
45660
45715
 
45661
- clickedFeatures(clickState, features) {
45716
+ clickedFeatures(clickState) {
45662
45717
 
45663
45718
  // We use the cached features rather than method to avoid async load. If the
45664
45719
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
45665
- const featureList = features || clickState.viewport.cachedFeatures;
45720
+ const featureList = clickState.viewport.cachedFeatures;
45666
45721
  const candidates = [];
45667
45722
  if (featureList) {
45668
45723
  const proportional = (this.arcType === "proportional" || this.arcType === "inView" || this.arcType === "partialInView");
@@ -46252,9 +46307,9 @@ class VariantTrack extends TrackBase {
46252
46307
  }
46253
46308
  }
46254
46309
 
46255
- clickedFeatures(clickState, features) {
46310
+ clickedFeatures(clickState) {
46256
46311
 
46257
- let featureList = super.clickedFeatures(clickState, features);
46312
+ let featureList = super.clickedFeatures(clickState);
46258
46313
 
46259
46314
  const vGap = (this.displayMode === 'EXPANDED') ? this.expandedVGap : this.squishedVGap;
46260
46315
  const callHeight = vGap + ("SQUISHED" === this.displayMode ? this.squishedCallHeight : this.expandedCallHeight);
@@ -46290,9 +46345,9 @@ class VariantTrack extends TrackBase {
46290
46345
  /**
46291
46346
  * Return "popup data" for feature @ genomic location. Data is an array of key-value pairs
46292
46347
  */
46293
- popupData(clickState, features) {
46348
+ popupData(clickState, featureList) {
46294
46349
 
46295
- const featureList = this.clickedFeatures(clickState, features);
46350
+ if(featureList === undefined) featureList = this.clickedFeatures(clickState);
46296
46351
  const genomicLocation = clickState.genomicLocation;
46297
46352
  const genomeID = this.browser.genome.id;
46298
46353
  const sampleInformation = this.browser.sampleInformation;
@@ -46820,9 +46875,9 @@ class EqtlTrack extends TrackBase {
46820
46875
  /**
46821
46876
  * Return "popup data" for feature @ genomic location. Data is an array of key-value pairs
46822
46877
  */
46823
- popupData(clickState) {
46878
+ popupData(clickState, features) {
46824
46879
 
46825
- let features = clickState.viewport.cachedFeatures;
46880
+ if(features === undefined) features = clickState.viewport.cachedFeatures;
46826
46881
  if (!features || features.length === 0) return []
46827
46882
 
46828
46883
  const tolerance = 3;
@@ -47149,11 +47204,12 @@ class GWASTrack extends TrackBase {
47149
47204
  }
47150
47205
  }
47151
47206
 
47152
- popupData(clickState) {
47207
+ popupData(clickState, features) {
47208
+
47209
+ if(features === undefined) features = clickState.viewport.cachedFeatures;
47153
47210
 
47154
47211
  let data = [];
47155
47212
  const track = clickState.viewport.trackView.track;
47156
- const features = clickState.viewport.cachedFeatures;
47157
47213
 
47158
47214
  if (features) {
47159
47215
  let count = 0;
@@ -47541,12 +47597,12 @@ class GCNVTrack extends TrackBase {
47541
47597
  return []
47542
47598
  }
47543
47599
 
47544
- popupData(clickState, featureList) {
47600
+ popupData(clickState, features) {
47545
47601
 
47546
- featureList = this.clickedFeatures(clickState, featureList);
47602
+ if(features === undefined) features = this.clickedFeatures(clickState);
47547
47603
 
47548
47604
  const items = [];
47549
- featureList.forEach(function (f) {
47605
+ features.forEach(function (f) {
47550
47606
  for (let property of Object.keys(f)) {
47551
47607
  if (isSimpleType(f[property])) {
47552
47608
  items.push({name: property, value: f[property]});
@@ -47732,9 +47788,9 @@ class RnaStructTrack extends TrackBase {
47732
47788
  }
47733
47789
  }
47734
47790
 
47735
- clickedFeatures(clickState, features) {
47791
+ clickedFeatures(clickState) {
47736
47792
 
47737
- features = super.clickedFeatures(clickState, features);
47793
+ const features = super.clickedFeatures(clickState);
47738
47794
 
47739
47795
  const clicked = [];
47740
47796
 
@@ -47776,10 +47832,7 @@ class RnaStructTrack extends TrackBase {
47776
47832
 
47777
47833
  popupData(clickState, features) {
47778
47834
 
47779
- // We use the featureCache property rather than method to avoid async load. If the
47780
- // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
47781
-
47782
- features = this.clickedFeatures(clickState, features);
47835
+ if(features === undefined) features = this.clickedFeatures(clickState);
47783
47836
 
47784
47837
  if (features && features.length > 0) {
47785
47838
 
@@ -48207,7 +48260,7 @@ class SpliceJunctionTrack extends TrackBase {
48207
48260
 
48208
48261
  if (typeof this.featureSource.getHeader === "function") {
48209
48262
  this.header = await this.featureSource.getHeader();
48210
- if(this.disposed) return; // This track was removed during async load
48263
+ if (this.disposed) return // This track was removed during async load
48211
48264
  }
48212
48265
 
48213
48266
  // Set properties from track line
@@ -48264,7 +48317,8 @@ class SpliceJunctionTrack extends TrackBase {
48264
48317
 
48265
48318
  junctionRenderingContext.referenceFrame = options.viewport.referenceFrame;
48266
48319
  junctionRenderingContext.referenceFrameStart = junctionRenderingContext.referenceFrame.start;
48267
- junctionRenderingContext.referenceFrameEnd = junctionRenderingContext.referenceFrameStart + junctionRenderingContext.referenceFrame.toBP($$1(options.viewport.contentDiv).width());
48320
+ junctionRenderingContext.referenceFrameEnd = junctionRenderingContext.referenceFrameStart +
48321
+ junctionRenderingContext.referenceFrame.toBP(options.viewport.getWidth());
48268
48322
 
48269
48323
  // For a given viewport, records where features that are < 2px in width have been rendered already.
48270
48324
  // This prevents wasteful rendering of multiple such features onto the same pixels.
@@ -48512,9 +48566,9 @@ class SpliceJunctionTrack extends TrackBase {
48512
48566
  ctx.fillText(label, junctionMiddlePx - ctx.measureText(label).width / 2, (7 * topY + cy) / 8);
48513
48567
  }
48514
48568
 
48515
- clickedFeatures(clickState, features) {
48569
+ clickedFeatures(clickState) {
48516
48570
 
48517
- const allFeatures = super.clickedFeatures(clickState, features);
48571
+ const allFeatures = super.clickedFeatures(clickState);
48518
48572
 
48519
48573
  return allFeatures.filter(function (feature) {
48520
48574
  return (feature.isVisible && feature.attributes)
@@ -48526,7 +48580,7 @@ class SpliceJunctionTrack extends TrackBase {
48526
48580
  */
48527
48581
  popupData(clickState, features) {
48528
48582
 
48529
- features = this.clickedFeatures(clickState, features);
48583
+ if (features === undefined) features = this.clickedFeatures(clickState);
48530
48584
  const genomicLocation = clickState.genomicLocation;
48531
48585
 
48532
48586
  const data = [];
@@ -51970,11 +52024,11 @@ class Browser {
51970
52024
  */
51971
52025
  toSVG() {
51972
52026
 
51973
- let {x, y, width, height} = this.columnContainer.getBoundingClientRect();
52027
+ const {y, width, height} = this.columnContainer.getBoundingClientRect();
51974
52028
 
51975
52029
  const h_render = 8000;
51976
52030
 
51977
- let context = new ctx(
52031
+ const config =
51978
52032
  {
51979
52033
 
51980
52034
  width,
@@ -51992,13 +52046,14 @@ class Browser {
51992
52046
  height: h_render
51993
52047
  }
51994
52048
 
51995
- });
52049
+ };
52050
+
52051
+ const context = new ctx(config);
51996
52052
 
51997
52053
  // tracks -> SVG
51998
52054
  for (let trackView of this.trackViews) {
51999
52055
  trackView.renderSVGContext(context, {deltaX: 0, deltaY: -y});
52000
52056
  }
52001
-
52002
52057
  // reset height to trim away unneeded svg canvas real estate. Yes, a bit of a hack.
52003
52058
  context.setHeight(height);
52004
52059