igv 2.11.2 → 2.12.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.
package/dist/igv.js CHANGED
@@ -17943,7 +17943,7 @@
17943
17943
  * @constructor
17944
17944
  */
17945
17945
 
17946
- class FeatureCache {
17946
+ class FeatureCache$1 {
17947
17947
  constructor(featureList, genome, range) {
17948
17948
  featureList = featureList || [];
17949
17949
  this.treeMap = this.buildTreeMap(featureList, genome);
@@ -20032,6 +20032,7 @@
20032
20032
 
20033
20033
  case "bedpe":
20034
20034
  case "bedpe-loop":
20035
+ case "biginteract":
20035
20036
  return "interact";
20036
20037
 
20037
20038
  case "bp":
@@ -20043,7 +20044,6 @@
20043
20044
  case "bed":
20044
20045
  case "bigbed":
20045
20046
  case "bb":
20046
- case "biginteract":
20047
20047
  return "bedtype";
20048
20048
 
20049
20049
  default:
@@ -20287,6 +20287,30 @@
20287
20287
  return window.location.protocol === "https:" || window.location.hostname === "localhost";
20288
20288
  }
20289
20289
 
20290
+ const pairs = [['A', 'T'], ['G', 'C'], ['Y', 'R'], ['W', 'S'], ['K', 'M'], ['D', 'H'], ['B', 'V']];
20291
+ const complements = new Map();
20292
+
20293
+ for (let p of pairs) {
20294
+ const p1 = p[0];
20295
+ const p2 = p[1];
20296
+ complements.set(p1, p2);
20297
+ complements.set(p2, p1);
20298
+ complements.set(p1.toLowerCase(), p2.toLowerCase());
20299
+ complements.set(p2.toLowerCase(), p1.toLowerCase());
20300
+ }
20301
+
20302
+ function reverseComplementSequence(sequence) {
20303
+ let comp = '';
20304
+ let idx = sequence.length;
20305
+
20306
+ while (idx-- > 0) {
20307
+ const base = sequence[idx];
20308
+ comp += complements.has(base) ? complements.get(base) : base;
20309
+ }
20310
+
20311
+ return comp;
20312
+ }
20313
+
20290
20314
  /*
20291
20315
  * The MIT License (MIT)
20292
20316
  *
@@ -20440,16 +20464,21 @@
20440
20464
  const viewport = clickState.viewport;
20441
20465
 
20442
20466
  if (viewport.referenceFrame.bpPerPixel <= 1) {
20467
+ const pixelWidth = viewport.getWidth();
20468
+ const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20469
+ const chr = viewport.referenceFrame.chr;
20470
+ const start = Math.floor(viewport.referenceFrame.start);
20471
+ const end = Math.ceil(start + bpWindow);
20443
20472
  const items = [{
20444
- label: 'View visible sequence...',
20473
+ label: this.reversed ? 'View visible sequence (reversed)...' : 'View visible sequence...',
20445
20474
  click: async () => {
20446
- const pixelWidth = viewport.getWidth();
20447
- const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20448
- const chr = viewport.referenceFrame.chr;
20449
- const start = viewport.referenceFrame.start;
20450
- const end = start + bpWindow;
20451
- const sequence = await this.browser.genome.sequence.getSequence(chr, start, end);
20452
- Alert.presentAlert(sequence);
20475
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20476
+
20477
+ if (this.reversed) {
20478
+ seq = reverseComplementSequence(seq);
20479
+ }
20480
+
20481
+ Alert.presentAlert(seq);
20453
20482
  }
20454
20483
  }];
20455
20484
 
@@ -20457,13 +20486,18 @@
20457
20486
  items.push({
20458
20487
  label: 'Copy visible sequence',
20459
20488
  click: async () => {
20460
- const pixelWidth = viewport.getWidth();
20461
- const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20462
- const chr = viewport.referenceFrame.chr;
20463
- const start = viewport.referenceFrame.start;
20464
- const end = start + bpWindow;
20465
- const sequence = await this.browser.genome.sequence.getSequence(chr, start, end);
20466
- navigator.clipboard.writeText(sequence);
20489
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20490
+
20491
+ if (this.reversed) {
20492
+ seq = reverseComplementSequence(seq);
20493
+ }
20494
+
20495
+ try {
20496
+ await navigator.clipboard.writeText(seq);
20497
+ } catch (e) {
20498
+ console.error(e);
20499
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
20500
+ }
20467
20501
  }
20468
20502
  });
20469
20503
  }
@@ -20604,7 +20638,7 @@
20604
20638
  }
20605
20639
  }
20606
20640
 
20607
- supportsWholeGenome() {
20641
+ get supportsWholeGenome() {
20608
20642
  return false;
20609
20643
  }
20610
20644
 
@@ -20672,12 +20706,13 @@
20672
20706
  });
20673
20707
  this.$viewport.append(this.$content);
20674
20708
  this.$content.height(this.$viewport.height());
20675
- this.contentDiv = this.$content.get(0);
20676
- this.$canvas = $$1('<canvas>');
20677
- this.$content.append(this.$canvas);
20678
- this.canvas = this.$canvas.get(0);
20679
- this.ctx = this.canvas.getContext("2d");
20680
- this.setWidth(width);
20709
+ this.contentDiv = this.$content.get(0); // this.$canvas = $('<canvas>')
20710
+ // this.$content.append(this.$canvas)
20711
+ //
20712
+ // this.canvas = this.$canvas.get(0)
20713
+ // this.ctx = this.canvas.getContext("2d")
20714
+
20715
+ this.$viewport.width(width);
20681
20716
  this.initializationHelper();
20682
20717
  }
20683
20718
 
@@ -20733,14 +20768,13 @@
20733
20768
  console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
20734
20769
  }
20735
20770
 
20736
- checkContentHeight() {
20771
+ checkContentHeight(features) {
20737
20772
  let track = this.trackView.track;
20773
+ features = features || this.cachedFeatures;
20738
20774
 
20739
20775
  if ("FILL" === track.displayMode) {
20740
20776
  this.setContentHeight(this.$viewport.height());
20741
20777
  } else if (typeof track.computePixelHeight === 'function') {
20742
- let features = this.cachedFeatures;
20743
-
20744
20778
  if (features && features.length > 0) {
20745
20779
  let requiredContentHeight = track.computePixelHeight(features);
20746
20780
  let currentContentHeight = this.$content.height();
@@ -20760,7 +20794,6 @@
20760
20794
  // Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
20761
20795
  contentHeight = Math.min(contentHeight, 32000);
20762
20796
  this.$content.height(contentHeight);
20763
- if (this.tile) this.tile.invalidate = true;
20764
20797
  }
20765
20798
 
20766
20799
  isLoading() {
@@ -20775,8 +20808,6 @@
20775
20808
 
20776
20809
  setWidth(width) {
20777
20810
  this.$viewport.width(width);
20778
- this.canvas.style.width = `${width}px`;
20779
- this.canvas.setAttribute('width', width);
20780
20811
  }
20781
20812
 
20782
20813
  getWidth() {
@@ -20804,7 +20835,6 @@
20804
20835
  this.popover.dispose();
20805
20836
  }
20806
20837
 
20807
- this.removeMouseHandlers();
20808
20838
  this.$viewport.get(0).remove(); // Null out all properties -- this should not be neccessary, but just in case there is a
20809
20839
  // reference to self somewhere we want to free memory.
20810
20840
 
@@ -23061,7 +23091,7 @@
23061
23091
  }
23062
23092
  };
23063
23093
 
23064
- const _version = "2.11.2";
23094
+ const _version = "2.12.0";
23065
23095
 
23066
23096
  function version$1() {
23067
23097
  return _version;
@@ -23411,6 +23441,7 @@
23411
23441
  }
23412
23442
 
23413
23443
  async getSequence(chr, start, end) {
23444
+ chr = this.getChromosomeName(chr);
23414
23445
  return this.sequence.getSequence(chr, start, end);
23415
23446
  }
23416
23447
 
@@ -23537,15 +23568,13 @@
23537
23568
  });
23538
23569
  this.$viewport.append(this.$spinner);
23539
23570
  this.$spinner.append($$1('<div>'));
23540
- const {
23541
- track
23542
- } = this.trackView;
23571
+ const track = this.trackView.track;
23543
23572
 
23544
23573
  if ('sequence' !== track.type) {
23545
23574
  this.$zoomInNotice = this.createZoomInNotice(this.$content);
23546
23575
  }
23547
23576
 
23548
- if (track.name && "sequence" !== track.config.type) {
23577
+ if (track.name && "sequence" !== track.id) {
23549
23578
  this.$trackLabel = $$1('<div class="igv-track-label">');
23550
23579
  this.$viewport.append(this.$trackLabel);
23551
23580
  this.setTrackLabel(track.name);
@@ -23559,6 +23588,11 @@
23559
23588
  this.addMouseHandlers();
23560
23589
  }
23561
23590
 
23591
+ setContentHeight(contentHeight) {
23592
+ super.setContentHeight(contentHeight);
23593
+ if (this.featureCache) this.featureCache.redraw = true;
23594
+ }
23595
+
23562
23596
  setTrackLabel(label) {
23563
23597
  this.$trackLabel.empty();
23564
23598
  this.$trackLabel.html(label);
@@ -23575,55 +23609,72 @@
23575
23609
  this.$spinner.hide();
23576
23610
  }
23577
23611
  }
23612
+ /**
23613
+ * Test to determine if we are zoomed in far enough to see features. Applicable to tracks with visibility windows.
23614
+ *
23615
+ * As a side effect the viewports canvas is removed if zoomed out.
23616
+ *
23617
+ * @returns {boolean} true if we are zoomed in past visibility window, false otherwise
23618
+ */
23578
23619
 
23579
- checkZoomIn() {
23580
- const showZoomInNotice = () => {
23581
- const referenceFrame = this.referenceFrame;
23582
23620
 
23583
- if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome()) {
23621
+ checkZoomIn() {
23622
+ const zoomedOutOfWindow = () => {
23623
+ if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome) {
23584
23624
  return true;
23585
23625
  } else {
23586
23626
  const visibilityWindow = this.trackView.track.visibilityWindow;
23587
- return visibilityWindow !== undefined && visibilityWindow > 0 && referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow;
23627
+ return visibilityWindow !== undefined && visibilityWindow > 0 && this.referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow;
23588
23628
  }
23589
23629
  };
23590
23630
 
23631
+ if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
23632
+ $$1(this.canvas).remove();
23633
+ this.canvas = undefined; //this.featureCache = undefined
23634
+
23635
+ return false;
23636
+ }
23637
+
23591
23638
  if (!this.viewIsReady()) {
23592
23639
  return false;
23593
23640
  }
23594
23641
 
23595
- if (this.$zoomInNotice) {
23596
- if (showZoomInNotice()) {
23597
- // Out of visibility window
23598
- if (this.canvas) {
23599
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
23600
- this.tile = undefined;
23601
- }
23642
+ if (zoomedOutOfWindow()) {
23643
+ // Out of visibility window
23644
+ if (this.canvas) {
23645
+ $$1(this.canvas).remove();
23646
+ this.canvas = undefined; //this.featureCache = undefined
23647
+ }
23602
23648
 
23603
- this.$zoomInNotice.show();
23649
+ if (this.trackView.track.autoHeight) {
23650
+ const minHeight = this.trackView.minHeight || 0;
23651
+ this.setContentHeight(minHeight);
23652
+ }
23604
23653
 
23605
- if (this.trackView.track.autoHeight) {
23606
- const minHeight = this.trackView.minHeight || 0;
23607
- this.setContentHeight(minHeight);
23608
- }
23654
+ if (this.$zoomInNotice) {
23655
+ this.$zoomInNotice.show();
23656
+ }
23609
23657
 
23610
- return false;
23611
- } else {
23658
+ return false;
23659
+ } else {
23660
+ if (this.$zoomInNotice) {
23612
23661
  this.$zoomInNotice.hide();
23613
- return true;
23614
23662
  }
23615
- }
23616
23663
 
23617
- return true;
23664
+ return true;
23665
+ }
23618
23666
  }
23667
+ /**
23668
+ * Adjust the canvas to the current genomic state.
23669
+ */
23670
+
23619
23671
 
23620
23672
  shift() {
23621
- const self = this;
23622
- const referenceFrame = self.referenceFrame;
23673
+ const referenceFrame = this.referenceFrame;
23623
23674
 
23624
- if (self.canvas && self.tile && self.tile.chr === self.referenceFrame.chr && self.tile.bpPerPixel === referenceFrame.bpPerPixel) {
23625
- const pixelOffset = Math.round((self.tile.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23626
- self.canvas.style.left = pixelOffset + "px";
23675
+ if (this.canvas && this.canvas._data && this.canvas._data.chr === this.referenceFrame.chr && this.canvas._data.bpPerPixel === referenceFrame.bpPerPixel) {
23676
+ const pixelOffset = Math.round((this.canvas._data.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23677
+ this.canvas.style.left = pixelOffset + "px";
23627
23678
  }
23628
23679
  }
23629
23680
 
@@ -23649,9 +23700,10 @@
23649
23700
  this.startSpinner();
23650
23701
 
23651
23702
  try {
23652
- const features = await this.getFeatures(this.trackView.track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23703
+ const track = this.trackView.track;
23704
+ const features = await this.getFeatures(track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23653
23705
  let roiFeatures = [];
23654
- const roi = mergeArrays(this.browser.roi, this.trackView.track.roi);
23706
+ const roi = mergeArrays(this.browser.roi, track.roi);
23655
23707
 
23656
23708
  if (roi) {
23657
23709
  for (let r of roi) {
@@ -23663,11 +23715,13 @@
23663
23715
  }
23664
23716
  }
23665
23717
 
23666
- this.tile = new Tile(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures);
23718
+ const mr = track && ("wig" === track.type || "merged" === track.type); // wig tracks are potentially multiresolution (e.g. bigwig)
23719
+
23720
+ this.featureCache = new FeatureCache(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures, mr);
23667
23721
  this.loading = false;
23668
23722
  this.hideMessage();
23669
23723
  this.stopSpinner();
23670
- return this.tile;
23724
+ return this.featureCache;
23671
23725
  } catch (error) {
23672
23726
  // Track might have been removed during load
23673
23727
  if (this.trackView && this.trackView.disposed !== true) {
@@ -23680,38 +23734,32 @@
23680
23734
  this.stopSpinner();
23681
23735
  }
23682
23736
  }
23737
+ /**
23738
+ * Repaint the canvas using the cached features
23739
+ *
23740
+ */
23683
23741
 
23684
- async repaint() {
23685
- if (undefined === this.tile) {
23742
+
23743
+ repaint() {
23744
+ if (undefined === this.featureCache) {
23686
23745
  return;
23687
23746
  }
23688
23747
 
23689
23748
  let {
23690
23749
  features,
23691
- roiFeatures,
23692
- bpPerPixel,
23693
- startBP,
23694
- endBP
23695
- } = this.tile; // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23696
-
23697
- const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
23698
- let pixelWidth;
23699
-
23700
- if (isWGV) {
23701
- bpPerPixel = this.referenceFrame.end / this.$viewport.width();
23702
- startBP = 0;
23703
- endBP = this.referenceFrame.end;
23704
- pixelWidth = this.$viewport.width();
23705
- } else {
23706
- pixelWidth = Math.ceil((endBP - startBP) / bpPerPixel);
23707
- } // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
23750
+ roiFeatures
23751
+ } = this.featureCache; //this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
23752
+ // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23708
23753
 
23754
+ const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr); // Canvas dimensions. There is no left-right panning for WGV so canvas width is viewport width.
23755
+ // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
23709
23756
 
23757
+ const pixelWidth = isWGV ? this.$viewport.width() : 3 * this.$viewport.width();
23710
23758
  const viewportHeight = this.$viewport.height();
23711
23759
  const contentHeight = this.getContentHeight();
23712
23760
  const minHeight = roiFeatures ? Math.max(contentHeight, viewportHeight) : contentHeight; // Need to fill viewport for ROIs.
23713
23761
 
23714
- let pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23762
+ const pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23715
23763
 
23716
23764
  if (0 === pixelWidth || 0 === pixelHeight) {
23717
23765
  if (this.canvas) {
@@ -23721,26 +23769,22 @@
23721
23769
  return;
23722
23770
  }
23723
23771
 
23724
- const canvasTop = Math.max(0, -this.$content.position().top - viewportHeight); // Always use high DPI if in compressed display mode, otherwise use preference setting;
23725
-
23726
- let devicePixelRatio;
23727
-
23728
- if ("FILL" === this.trackView.track.displayMode) {
23729
- devicePixelRatio = window.devicePixelRatio;
23730
- } else {
23731
- devicePixelRatio = this.trackView.track.supportHiDPI === false ? 1 : window.devicePixelRatio;
23732
- }
23733
-
23734
- const pixelXOffset = Math.round((startBP - this.referenceFrame.start) / this.referenceFrame.bpPerPixel);
23772
+ const canvasTop = Math.max(0, -this.$content.position().top - viewportHeight);
23773
+ const bpPerPixel = this.referenceFrame.bpPerPixel;
23774
+ const startBP = this.referenceFrame.start - (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23775
+ const endBP = this.referenceFrame.end + (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23776
+ const pixelXOffset = Math.round((startBP - this.referenceFrame.start) / bpPerPixel);
23735
23777
  const newCanvas = $$1('<canvas class="igv-canvas">').get(0);
23736
- const ctx = newCanvas.getContext("2d");
23737
23778
  newCanvas.style.width = pixelWidth + "px";
23738
23779
  newCanvas.style.height = pixelHeight + "px";
23780
+ newCanvas.style.left = pixelXOffset + "px";
23781
+ newCanvas.style.top = canvasTop + "px"; // Always use high DPI if in "FILL" display mode, otherwise use track setting;
23782
+
23783
+ const devicePixelRatio = "FILL" === this.trackView.track.displayMode || this.trackView.track.supportHiDPI !== false ? window.devicePixelRatio : 1;
23739
23784
  newCanvas.width = devicePixelRatio * pixelWidth;
23740
23785
  newCanvas.height = devicePixelRatio * pixelHeight;
23786
+ const ctx = newCanvas.getContext("2d");
23741
23787
  ctx.scale(devicePixelRatio, devicePixelRatio);
23742
- newCanvas.style.left = pixelXOffset + "px";
23743
- newCanvas.style.top = canvasTop + "px";
23744
23788
  ctx.translate(0, -canvasTop);
23745
23789
  const drawConfiguration = {
23746
23790
  context: ctx,
@@ -23757,20 +23801,32 @@
23757
23801
  viewportWidth: this.$viewport.width()
23758
23802
  };
23759
23803
  this.draw(drawConfiguration, features, roiFeatures);
23760
- this.canvasVerticalRange = {
23761
- top: canvasTop,
23762
- bottom: canvasTop + pixelHeight
23763
- };
23804
+ this.featureCache.canvasTop = canvasTop;
23805
+ this.featureCache.height = pixelHeight;
23764
23806
 
23765
- if (this.$canvas) {
23766
- this.$canvas.remove();
23807
+ if (this.canvas) {
23808
+ $$1(this.canvas).remove();
23767
23809
  }
23768
23810
 
23769
- this.$canvas = $$1(newCanvas);
23770
- this.$content.append(this.$canvas);
23811
+ newCanvas._data = {
23812
+ chr: this.featureCache.chr,
23813
+ bpPerPixel,
23814
+ startBP,
23815
+ endBP,
23816
+ pixelHeight,
23817
+ pixelTop: canvasTop
23818
+ };
23771
23819
  this.canvas = newCanvas;
23772
- this.ctx = ctx;
23820
+ this.$content.append($$1(newCanvas));
23773
23821
  }
23822
+ /**
23823
+ * Draw the associated track.
23824
+ *
23825
+ * @param drawConfiguration
23826
+ * @param features
23827
+ * @param roiFeatures
23828
+ */
23829
+
23774
23830
 
23775
23831
  draw(drawConfiguration, features, roiFeatures) {
23776
23832
  // console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
@@ -23785,51 +23841,6 @@
23785
23841
  r.track.draw(drawConfiguration);
23786
23842
  }
23787
23843
  }
23788
- } // TODO: Nolonger used. Will discard
23789
-
23790
-
23791
- async toSVG(tile) {
23792
- // Nothing to do if zoomInNotice is active
23793
- if (this.$zoomInNotice && this.$zoomInNotice.is(":visible")) {
23794
- return;
23795
- }
23796
-
23797
- const referenceFrame = this.referenceFrame;
23798
- const bpPerPixel = tile.bpPerPixel;
23799
- const features = tile.features;
23800
- const roiFeatures = tile.roiFeatures;
23801
- const pixelWidth = this.$viewport.width();
23802
- const pixelHeight = this.$viewport.height();
23803
- const bpStart = referenceFrame.start;
23804
- const bpEnd = referenceFrame.start + pixelWidth * referenceFrame.bpPerPixel;
23805
- const ctx$1 = new ctx({
23806
- // svg
23807
- width: pixelWidth,
23808
- height: pixelHeight,
23809
- viewbox: {
23810
- x: 0,
23811
- y: -this.$content.position().top,
23812
- width: pixelWidth,
23813
- height: pixelHeight
23814
- }
23815
- });
23816
- const drawConfiguration = {
23817
- viewport: this,
23818
- context: ctx$1,
23819
- top: -this.$content.position().top,
23820
- pixelTop: 0,
23821
- // for compatibility with canvas draw
23822
- pixelWidth,
23823
- pixelHeight,
23824
- bpStart,
23825
- bpEnd,
23826
- bpPerPixel,
23827
- referenceFrame: this.referenceFrame,
23828
- selection: this.selection,
23829
- viewportWidth: pixelWidth
23830
- };
23831
- this.draw(drawConfiguration, features, roiFeatures);
23832
- return ctx$1.getSerializedSvg(true);
23833
23844
  }
23834
23845
 
23835
23846
  containsPosition(chr, position) {
@@ -23844,15 +23855,17 @@
23844
23855
  return this.loading;
23845
23856
  }
23846
23857
 
23847
- saveImage() {
23848
- if (!this.ctx) return;
23849
- const canvasTop = this.canvasVerticalRange ? this.canvasVerticalRange.top : 0;
23858
+ savePNG() {
23859
+ if (!this.canvas) return;
23860
+ const canvasMetadata = this.featureCache;
23861
+ const canvasTop = canvasMetadata ? canvasMetadata.canvasTop : 0;
23850
23862
  const devicePixelRatio = window.devicePixelRatio;
23851
23863
  const w = this.$viewport.width() * devicePixelRatio;
23852
23864
  const h = this.$viewport.height() * devicePixelRatio;
23853
23865
  const x = -$$1(this.canvas).position().left * devicePixelRatio;
23854
23866
  const y = (-this.$content.position().top - canvasTop) * devicePixelRatio;
23855
- const imageData = this.ctx.getImageData(x, y, w, h);
23867
+ const ctx = this.canvas.getContext("2d");
23868
+ const imageData = ctx.getImageData(x, y, w, h);
23856
23869
  const exportCanvas = document.createElement('canvas');
23857
23870
  const exportCtx = exportCanvas.getContext('2d');
23858
23871
  exportCanvas.width = imageData.width;
@@ -23968,29 +23981,44 @@
23968
23981
  viewportWidth: width,
23969
23982
  selection: this.selection
23970
23983
  };
23971
- const features = this.tile ? this.tile.features : [];
23972
- const roiFeatures = this.tile ? this.tile.roiFeatures : undefined;
23984
+ const features = this.featureCache ? this.featureCache.features : [];
23985
+ const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
23973
23986
  this.draw(config, features, roiFeatures);
23974
23987
  context.restore();
23975
23988
  }
23976
23989
 
23977
- getCachedFeatures() {
23978
- return this.tile ? this.tile.features : [];
23990
+ get cachedFeatures() {
23991
+ return this.featureCache ? this.featureCache.features : [];
23979
23992
  }
23980
23993
 
23981
23994
  async getFeatures(track, chr, start, end, bpPerPixel) {
23982
- if (this.tile && this.tile.containsRange(chr, start, end, bpPerPixel)) {
23983
- return this.tile.features;
23995
+ if (this.featureCache && this.featureCache.containsRange(chr, start, end, bpPerPixel)) {
23996
+ return this.featureCache.features;
23984
23997
  } else if (typeof track.getFeatures === "function") {
23985
23998
  const features = await track.getFeatures(chr, start, end, bpPerPixel, this);
23986
- this.cachedFeatures = features;
23987
- this.checkContentHeight();
23999
+ this.checkContentHeight(features);
23988
24000
  return features;
23989
24001
  } else {
23990
24002
  return undefined;
23991
24003
  }
23992
24004
  }
23993
24005
 
24006
+ needsRepaint() {
24007
+ if (!this.canvas) return true;
24008
+ const data = this.canvas._data;
24009
+ return !data || this.referenceFrame.start < data.startBP || this.referenceFrame.end > data.endBP || this.referenceFrame.chr !== data.chr || this.referenceFrame.bpPerPixel != data.bpPerPixel;
24010
+ }
24011
+
24012
+ needsReload() {
24013
+ if (!this.featureCache) return true;
24014
+ const referenceFrame = this.referenceFrame;
24015
+ const chr = this.referenceFrame.chr;
24016
+ const start = referenceFrame.start;
24017
+ const end = start + referenceFrame.toBP($$1(this.contentDiv).width());
24018
+ const bpPerPixel = referenceFrame.bpPerPixel;
24019
+ return !this.featureCache.containsRange(chr, start, end, bpPerPixel);
24020
+ }
24021
+
23994
24022
  createZoomInNotice($parent) {
23995
24023
  const $container = $$1('<div>', {
23996
24024
  class: 'igv-zoom-in-notice-container'
@@ -24008,36 +24036,42 @@
24008
24036
  }
24009
24037
 
24010
24038
  addMouseHandlers() {
24011
- this.addViewportContextMenuHandler(this.$viewport.get(0));
24012
- this.addViewportMouseDownHandler(this.$viewport.get(0));
24013
- this.addViewportTouchStartHandler(this.$viewport.get(0));
24014
- this.addViewportMouseUpHandler(this.$viewport.get(0));
24015
- this.addViewportTouchEndHandler(this.$viewport.get(0));
24016
- this.addViewportClickHandler(this.$viewport.get(0));
24039
+ const viewport = this.$viewport.get(0);
24040
+ this.addViewportContextMenuHandler(viewport);
24017
24041
 
24018
- if (this.trackView.track.name && "sequence" !== this.trackView.track.config.type) {
24019
- this.addTrackLabelClickHandler(this.$trackLabel.get(0));
24020
- }
24021
- }
24042
+ const md = event => {
24043
+ this.enableClick = true;
24044
+ this.browser.mouseDownOnViewport(event, this);
24045
+ pageCoordinates$1(event);
24046
+ };
24022
24047
 
24023
- removeMouseHandlers() {
24024
- this.removeViewportContextMenuHandler(this.$viewport.get(0));
24025
- this.removeViewportMouseDownHandler(this.$viewport.get(0));
24026
- this.removeViewportTouchStartHandler(this.$viewport.get(0));
24027
- this.removeViewportMouseUpHandler(this.$viewport.get(0));
24028
- this.removeViewportTouchEndHandler(this.$viewport.get(0));
24029
- this.removeViewportClickHandler(this.$viewport.get(0));
24048
+ viewport.addEventListener('mousedown', md);
24049
+ viewport.addEventListener('touchstart', md);
24050
+
24051
+ const mu = event => {
24052
+ // Any mouse up cancels drag and scrolling
24053
+ if (this.browser.dragObject || this.browser.isScrolling) {
24054
+ this.browser.cancelTrackPan(); // event.preventDefault();
24055
+ // event.stopPropagation();
24056
+
24057
+ this.enableClick = false; // Until next mouse down
24058
+ } else {
24059
+ this.browser.cancelTrackPan();
24060
+ this.browser.endTrackDrag();
24061
+ }
24062
+ };
24063
+
24064
+ viewport.addEventListener('mouseup', mu);
24065
+ viewport.addEventListener('touchend', mu);
24066
+ this.addViewportClickHandler(this.$viewport.get(0));
24030
24067
 
24031
24068
  if (this.trackView.track.name && "sequence" !== this.trackView.track.config.type) {
24032
- this.removeTrackLabelClickHandler(this.$trackLabel.get(0));
24069
+ this.addTrackLabelClickHandler(this.$trackLabel.get(0));
24033
24070
  }
24034
24071
  }
24035
24072
 
24036
24073
  addViewportContextMenuHandler(viewport) {
24037
- this.boundContextMenuHandler = contextMenuHandler.bind(this);
24038
- viewport.addEventListener('contextmenu', this.boundContextMenuHandler);
24039
-
24040
- function contextMenuHandler(event) {
24074
+ viewport.addEventListener('contextmenu', event => {
24041
24075
  // Ignore if we are doing a drag. This can happen with touch events.
24042
24076
  if (this.browser.dragObject) {
24043
24077
  return false;
@@ -24077,54 +24111,11 @@
24077
24111
  click: () => this.saveSVG()
24078
24112
  });
24079
24113
  this.browser.menuPopup.presentTrackContextMenu(event, menuItems);
24080
- }
24081
- }
24082
-
24083
- removeViewportContextMenuHandler(viewport) {
24084
- viewport.removeEventListener('contextmenu', this.boundContextMenuHandler);
24085
- }
24086
-
24087
- addViewportMouseDownHandler(viewport) {
24088
- this.boundMouseDownHandler = mouseDownHandler.bind(this);
24089
- viewport.addEventListener('mousedown', this.boundMouseDownHandler);
24090
- }
24091
-
24092
- removeViewportMouseDownHandler(viewport) {
24093
- viewport.removeEventListener('mousedown', this.boundMouseDownHandler);
24094
- }
24095
-
24096
- addViewportTouchStartHandler(viewport) {
24097
- this.boundTouchStartHandler = mouseDownHandler.bind(this);
24098
- viewport.addEventListener('touchstart', this.boundTouchStartHandler);
24099
- }
24100
-
24101
- removeViewportTouchStartHandler(viewport) {
24102
- viewport.removeEventListener('touchstart', this.boundTouchStartHandler);
24103
- }
24104
-
24105
- addViewportMouseUpHandler(viewport) {
24106
- this.boundMouseUpHandler = mouseUpHandler.bind(this);
24107
- viewport.addEventListener('mouseup', this.boundMouseUpHandler);
24108
- }
24109
-
24110
- removeViewportMouseUpHandler(viewport) {
24111
- viewport.removeEventListener('mouseup', this.boundMouseUpHandler);
24112
- }
24113
-
24114
- addViewportTouchEndHandler(viewport) {
24115
- this.boundTouchEndHandler = mouseUpHandler.bind(this);
24116
- viewport.addEventListener('touchend', this.boundTouchEndHandler);
24117
- }
24118
-
24119
- removeViewportTouchEndHandler(viewport) {
24120
- viewport.removeEventListener('touchend', this.boundTouchEndHandler);
24114
+ });
24121
24115
  }
24122
24116
 
24123
24117
  addViewportClickHandler(viewport) {
24124
- this.boundClickHandler = clickHandler.bind(this);
24125
- viewport.addEventListener('click', this.boundClickHandler);
24126
-
24127
- function clickHandler(event) {
24118
+ viewport.addEventListener('click', event => {
24128
24119
  if (this.enableClick) {
24129
24120
  if (3 === event.which || event.ctrlKey) {
24130
24121
  return;
@@ -24199,18 +24190,11 @@
24199
24190
 
24200
24191
  lastClickTime = time;
24201
24192
  }
24202
- }
24203
- }
24204
-
24205
- removeViewportClickHandler(viewport) {
24206
- viewport.removeEventListener('click', this.boundClickHandler);
24193
+ });
24207
24194
  }
24208
24195
 
24209
24196
  addTrackLabelClickHandler(trackLabel) {
24210
- this.boundTrackLabelClickHandler = clickHandler.bind(this);
24211
- trackLabel.addEventListener('click', this.boundTrackLabelClickHandler);
24212
-
24213
- function clickHandler(event) {
24197
+ trackLabel.addEventListener('click', event => {
24214
24198
  event.stopPropagation();
24215
24199
  const {
24216
24200
  track
@@ -24231,44 +24215,16 @@
24231
24215
  this.popover = new Popover(this.browser.columnContainer, track.name || '');
24232
24216
  this.popover.presentContentWithEvent(event, str);
24233
24217
  }
24234
- }
24235
- }
24236
-
24237
- removeTrackLabelClickHandler(trackLabel) {
24238
- trackLabel.removeEventListener('click', this.boundTrackLabelClickHandler);
24218
+ });
24239
24219
  }
24240
24220
 
24241
24221
  }
24242
24222
 
24243
- function mouseDownHandler(event) {
24244
- this.enableClick = true;
24245
- this.browser.mouseDownOnViewport(event, this);
24246
- pageCoordinates$1(event);
24247
- }
24248
-
24249
- function mouseUpHandler(event) {
24250
- // Any mouse up cancels drag and scrolling
24251
- if (this.browser.dragObject || this.browser.isScrolling) {
24252
- this.browser.cancelTrackPan(); // event.preventDefault();
24253
- // event.stopPropagation();
24254
-
24255
- this.enableClick = false; // Until next mouse down
24256
- } else {
24257
- this.browser.cancelTrackPan();
24258
- this.browser.endTrackDrag();
24259
- }
24260
- }
24261
-
24262
24223
  function createClickState(event, viewport) {
24263
24224
  const referenceFrame = viewport.referenceFrame;
24264
24225
  const viewportCoords = translateMouseCoordinates$1(event, viewport.contentDiv);
24265
24226
  const canvasCoords = translateMouseCoordinates$1(event, viewport.canvas);
24266
24227
  const genomicLocation = referenceFrame.start + referenceFrame.toBP(viewportCoords.x);
24267
-
24268
- if (undefined === genomicLocation || null === viewport.tile) {
24269
- return undefined;
24270
- }
24271
-
24272
24228
  return {
24273
24229
  event,
24274
24230
  viewport,
@@ -24322,22 +24278,28 @@
24322
24278
  return rows.join('');
24323
24279
  }
24324
24280
 
24325
- var Tile = function (chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures) {
24326
- this.chr = chr;
24327
- this.startBP = tileStart;
24328
- this.endBP = tileEnd;
24329
- this.bpPerPixel = bpPerPixel;
24330
- this.features = features;
24331
- this.roiFeatures = roiFeatures;
24332
- };
24281
+ class FeatureCache {
24282
+ constructor(chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures, multiresolution) {
24283
+ this.chr = chr;
24284
+ this.startBP = tileStart;
24285
+ this.endBP = tileEnd;
24286
+ this.bpPerPixel = bpPerPixel;
24287
+ this.features = features;
24288
+ this.roiFeatures = roiFeatures;
24289
+ this.multiresolution = multiresolution;
24290
+ }
24333
24291
 
24334
- Tile.prototype.containsRange = function (chr, start, end, bpPerPixel) {
24335
- return this.bpPerPixel === bpPerPixel && start >= this.startBP && end <= this.endBP && chr === this.chr;
24336
- };
24292
+ containsRange(chr, start, end, bpPerPixel) {
24293
+ // For multi-resolution tracks allow for a 2X change in bpPerPixel
24294
+ const r = this.multiresolution ? this.bpPerPixel / bpPerPixel : 1;
24295
+ return start >= this.startBP && end <= this.endBP && chr === this.chr && r > 0.5 && r < 2;
24296
+ }
24337
24297
 
24338
- Tile.prototype.overlapsRange = function (chr, start, end) {
24339
- return this.chr === chr && end >= this.startBP && start <= this.endBP;
24340
- };
24298
+ overlapsRange(chr, start, end) {
24299
+ return this.chr === chr && end >= this.startBP && start <= this.endBP;
24300
+ }
24301
+
24302
+ }
24341
24303
  /**
24342
24304
  * Merge 2 arrays. a and/or b can be undefined. If both are undefined, return undefined
24343
24305
  * @param a An array or undefined
@@ -24476,10 +24438,12 @@
24476
24438
  end: bp(this.rulerViewport.referenceFrame, left + width)
24477
24439
  };
24478
24440
  validateLocusExtent(this.rulerViewport.browser.genome.getChromosome(this.rulerViewport.referenceFrame.chr).bpLength, extent, this.rulerViewport.browser.minimumBases());
24479
- this.rulerViewport.referenceFrame.bpPerPixel = (Math.round(extent.end) - Math.round(extent.start)) / this.rulerViewport.contentDiv.clientWidth;
24480
- this.rulerViewport.referenceFrame.start = Math.round(extent.start);
24481
- this.rulerViewport.referenceFrame.end = Math.round(extent.end);
24482
- this.rulerViewport.browser.updateViews(this.rulerViewport.referenceFrame);
24441
+ const newStart = Math.round(extent.start);
24442
+ const newEnd = Math.round(extent.end);
24443
+ this.rulerViewport.referenceFrame.bpPerPixel = (newEnd - newStart) / this.rulerViewport.contentDiv.clientWidth;
24444
+ this.rulerViewport.referenceFrame.start = newStart;
24445
+ this.rulerViewport.referenceFrame.end = newEnd;
24446
+ this.rulerViewport.browser.updateViews();
24483
24447
  }
24484
24448
  }
24485
24449
  }
@@ -25481,6 +25445,18 @@
25481
25445
  return true; // By definition
25482
25446
  }
25483
25447
 
25448
+ isMateMapped() {
25449
+ return true; // By definition
25450
+ }
25451
+
25452
+ isProperPair() {
25453
+ return this.firstAlignment.isProperPair();
25454
+ }
25455
+
25456
+ get fragmentLength() {
25457
+ return Math.abs(this.firstAlignment.fragmentLength);
25458
+ }
25459
+
25484
25460
  firstOfPairStrand() {
25485
25461
  if (this.firstAlignment.isFirstOfPair()) {
25486
25462
  return this.firstAlignment.strand;
@@ -25847,7 +25823,15 @@
25847
25823
  */
25848
25824
 
25849
25825
  class AlignmentContainer {
25850
- constructor(chr, start, end, samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold) {
25826
+ // this.config.samplingWindowSize, this.config.samplingDepth,
25827
+ // this.config.pairsSupported, this.config.alleleFreqThreshold)
25828
+ constructor(chr, start, end, _ref) {
25829
+ let {
25830
+ samplingWindowSize,
25831
+ samplingDepth,
25832
+ pairsSupported,
25833
+ alleleFreqThreshold
25834
+ } = _ref;
25851
25835
  this.chr = chr;
25852
25836
  this.start = Math.floor(start);
25853
25837
  this.end = Math.ceil(end);
@@ -25870,17 +25854,10 @@
25870
25854
  // TODO -- pass this in
25871
25855
  return alignment.isMapped() && !alignment.isFailsVendorQualityCheck();
25872
25856
  };
25873
-
25874
- this.pairedEndStats = new PairedEndStats();
25875
25857
  }
25876
25858
 
25877
25859
  push(alignment) {
25878
25860
  if (this.filter(alignment) === false) return;
25879
-
25880
- if (alignment.isPaired()) {
25881
- this.pairedEndStats.push(alignment);
25882
- }
25883
-
25884
25861
  this.coverageMap.incCounts(alignment); // Count coverage before any downsampling
25885
25862
 
25886
25863
  if (this.pairsSupported && this.downsampledReads.has(alignment.readName)) {
@@ -25909,7 +25886,6 @@
25909
25886
  });
25910
25887
  this.pairsCache = undefined;
25911
25888
  this.downsampledReads = undefined;
25912
- this.pairedEndStats.compute();
25913
25889
  }
25914
25890
 
25915
25891
  contains(chr, start, end) {
@@ -26204,62 +26180,6 @@
26204
26180
 
26205
26181
  }
26206
26182
 
26207
- class PairedEndStats {
26208
- constructor(lowerPercentile, upperPercentile) {
26209
- this.totalCount = 0;
26210
- this.frCount = 0;
26211
- this.rfCount = 0;
26212
- this.ffCount = 0;
26213
- this.sumF = 0;
26214
- this.sumF2 = 0; //this.lp = lowerPercentile === undefined ? 0.005 : lowerPercentile;
26215
- //this.up = upperPercentile === undefined ? 0.995 : upperPercentile;
26216
- //this.digest = new Digest();
26217
- }
26218
-
26219
- push(alignment) {
26220
- if (alignment.isProperPair()) {
26221
- var fragmentLength = Math.abs(alignment.fragmentLength); //this.digest.push(fragmentLength);
26222
-
26223
- this.sumF += fragmentLength;
26224
- this.sumF2 += fragmentLength * fragmentLength;
26225
- var po = alignment.pairOrientation;
26226
-
26227
- if (typeof po === "string" && po.length === 4) {
26228
- var tmp = '' + po.charAt(0) + po.charAt(2);
26229
-
26230
- switch (tmp) {
26231
- case 'FF':
26232
- case 'RR':
26233
- this.ffCount++;
26234
- break;
26235
-
26236
- case "FR":
26237
- this.frCount++;
26238
- break;
26239
-
26240
- case "RF":
26241
- this.rfCount++;
26242
- }
26243
- }
26244
-
26245
- this.totalCount++;
26246
- }
26247
- }
26248
-
26249
- compute() {
26250
- if (this.totalCount > 100) {
26251
- if (this.ffCount / this.totalCount > 0.9) this.orienation = "ff";else if (this.frCount / this.totalCount > 0.9) this.orienation = "fr";else if (this.rfCount / this.totalCount > 0.9) this.orienation = "rf";
26252
- var fMean = this.sumF / this.totalCount;
26253
- var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount));
26254
- this.lowerFragmentLength = fMean - 3 * stdDev;
26255
- this.upperFragmentLength = fMean + 3 * stdDev; //this.lowerFragmentLength = this.digest.percentile(this.lp);
26256
- //this.upperFragmentLength = this.digest.percentile(this.up);
26257
- //this.digest = undefined;
26258
- }
26259
- }
26260
-
26261
- }
26262
-
26263
26183
  class SupplementaryAlignment {
26264
26184
  constructor(rec) {
26265
26185
  const tokens = rec.split(',');
@@ -27022,7 +26942,7 @@
27022
26942
  const lseq = readInt(ba, offset + 20);
27023
26943
  const mateChrIdx = readInt(ba, offset + 24);
27024
26944
  const matePos = readInt(ba, offset + 28);
27025
- const tlen = readInt(ba, offset + 32);
26945
+ const fragmentLength = readInt(ba, offset + 32);
27026
26946
  let readName = [];
27027
26947
 
27028
26948
  for (let j = 0; j < nl - 1; ++j) {
@@ -27063,7 +26983,7 @@
27063
26983
  alignment.readName = readName;
27064
26984
  alignment.cigar = cigar;
27065
26985
  alignment.lengthOnRef = lengthOnRef;
27066
- alignment.fragmentLength = tlen;
26986
+ alignment.fragmentLength = fragmentLength;
27067
26987
  alignment.mq = mq;
27068
26988
  BamUtils.bam_tag2cigar(ba, blockEnd, p, lseq, alignment, cigarArray);
27069
26989
  alignment.end = alignment.start + alignment.lengthOnRef;
@@ -27463,7 +27383,7 @@
27463
27383
  const header = this.header;
27464
27384
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
27465
27385
  const qAlignments = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
27466
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27386
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27467
27387
 
27468
27388
  for (let a of qAlignments) {
27469
27389
  alignmentContainer.push(a);
@@ -27490,13 +27410,13 @@
27490
27410
  const alignments = [];
27491
27411
  this.header = BamUtils.decodeBamHeader(data);
27492
27412
  BamUtils.decodeBamRecords(data, this.header.size, alignments, this.header.chrNames);
27493
- this.alignmentCache = new FeatureCache(alignments, this.genome);
27413
+ this.alignmentCache = new FeatureCache$1(alignments, this.genome);
27494
27414
  }
27495
27415
 
27496
27416
  fetchAlignments(chr, bpStart, bpEnd) {
27497
27417
  const queryChr = this.header.chrAliasTable.hasOwnProperty(chr) ? this.header.chrAliasTable[chr] : chr;
27498
27418
  const features = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
27499
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported);
27419
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27500
27420
 
27501
27421
  for (let feature of features) {
27502
27422
  alignmentContainer.push(feature);
@@ -28496,7 +28416,7 @@
28496
28416
  const chrToIndex = await this.getChrIndex();
28497
28417
  const queryChr = this.chrAliasTable.hasOwnProperty(chr) ? this.chrAliasTable[chr] : chr;
28498
28418
  const chrId = chrToIndex[queryChr];
28499
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config.samplingWindowSize, this.config.samplingDepth, this.config.pairsSupported, this.config.alleleFreqThreshold);
28419
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
28500
28420
 
28501
28421
  if (chrId === undefined) {
28502
28422
  return alignmentContainer;
@@ -28645,7 +28565,7 @@
28645
28565
 
28646
28566
  async readAlignments(chr, start, end) {
28647
28567
  if (!this.bamReaders.hasOwnProperty(chr)) {
28648
- return new AlignmentContainer(chr, start, end);
28568
+ return new AlignmentContainer(chr, start, end, this.config);
28649
28569
  } else {
28650
28570
  let reader = this.bamReaders[chr];
28651
28571
  const a = await reader.readAlignments(chr, start, end);
@@ -28717,7 +28637,7 @@
28717
28637
  return igvxhr.loadString(url, buildOptions(self.config)).then(function (sam) {
28718
28638
  var alignmentContainer;
28719
28639
  header.chrToIndex[queryChr];
28720
- alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold);
28640
+ alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.config);
28721
28641
  BamUtils.decodeSamRecords(sam, alignmentContainer, queryChr, bpStart, bpEnd, self.filter);
28722
28642
  return alignmentContainer;
28723
28643
  });
@@ -28927,7 +28847,7 @@
28927
28847
 
28928
28848
  const ba = unbgzf(compressedData.buffer);
28929
28849
  const chrIdx = this.header.chrToIndex[chr];
28930
- const alignmentContainer = new AlignmentContainer(chr, start, end, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
28850
+ const alignmentContainer = new AlignmentContainer(chr, start, end, this.config);
28931
28851
  BamUtils.decodeBamRecords(ba, this.header.size, alignmentContainer, this.header.chrNames, chrIdx, start, end);
28932
28852
  alignmentContainer.finish();
28933
28853
  return alignmentContainer;
@@ -44465,7 +44385,7 @@
44465
44385
  const header = await this.getHeader();
44466
44386
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
44467
44387
  const chrIdx = header.chrToIndex[queryChr];
44468
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
44388
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
44469
44389
 
44470
44390
  if (chrIdx === undefined) {
44471
44391
  return alignmentContainer;
@@ -45074,7 +44994,7 @@
45074
44994
  "pageSize": "10000"
45075
44995
  },
45076
44996
  decode: decodeGa4ghReads,
45077
- results: new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold)
44997
+ results: new AlignmentContainer(chr, bpStart, bpEnd, self.config)
45078
44998
  });
45079
44999
  });
45080
45000
 
@@ -45381,7 +45301,6 @@
45381
45301
  const genome = browser.genome;
45382
45302
  this.config = config;
45383
45303
  this.genome = genome;
45384
- this.alignmentContainer = undefined;
45385
45304
 
45386
45305
  if (isDataURL(config.url)) {
45387
45306
  if ("cram" === config.format) {
@@ -45432,58 +45351,44 @@
45432
45351
  }
45433
45352
 
45434
45353
  setViewAsPairs(bool) {
45435
-
45436
- if (this.viewAsPairs !== bool) {
45437
- this.viewAsPairs = bool; // if (this.alignmentContainer) {
45438
- // this.alignmentContainer.setViewAsPairs(bool);
45439
- // }
45440
- }
45354
+ this.viewAsPairs = bool;
45441
45355
  }
45442
45356
 
45443
45357
  setShowSoftClips(bool) {
45444
- if (this.showSoftClips !== bool) {
45445
- this.showSoftClips = bool;
45446
- }
45358
+ this.showSoftClips = bool;
45447
45359
  }
45448
45360
 
45449
45361
  async getAlignments(chr, bpStart, bpEnd) {
45450
45362
  const genome = this.genome;
45451
45363
  const showSoftClips = this.showSoftClips;
45364
+ const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
45365
+ let alignments = alignmentContainer.alignments;
45452
45366
 
45453
- if (this.alignmentContainer && this.alignmentContainer.contains(chr, bpStart, bpEnd)) {
45454
- return this.alignmentContainer;
45455
- } else {
45456
- const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
45457
- let alignments = alignmentContainer.alignments;
45458
-
45459
- if (!this.viewAsPairs) {
45460
- alignments = unpairAlignments([{
45461
- alignments: alignments
45462
- }]);
45463
- }
45464
-
45465
- const hasAlignments = alignments.length > 0;
45466
- alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
45467
- alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
45367
+ if (!this.viewAsPairs) {
45368
+ alignments = unpairAlignments([{
45369
+ alignments: alignments
45370
+ }]);
45371
+ }
45468
45372
 
45469
- this.alignmentContainer = alignmentContainer;
45373
+ const hasAlignments = alignments.length > 0;
45374
+ alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
45375
+ this.alignmentContainer = alignmentContainer;
45470
45376
 
45471
- if (hasAlignments) {
45472
- const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
45377
+ if (hasAlignments) {
45378
+ const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
45473
45379
 
45474
- if (sequence) {
45475
- alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
45380
+ if (sequence) {
45381
+ alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
45476
45382
 
45477
- alignmentContainer.sequence = sequence; // TODO -- fix this
45383
+ alignmentContainer.sequence = sequence; // TODO -- fix this
45478
45384
 
45479
- return alignmentContainer;
45480
- } else {
45481
- console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
45482
- }
45385
+ return alignmentContainer;
45386
+ } else {
45387
+ console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
45483
45388
  }
45484
-
45485
- return alignmentContainer;
45486
45389
  }
45390
+
45391
+ return alignmentContainer;
45487
45392
  }
45488
45393
 
45489
45394
  }
@@ -45667,7 +45572,7 @@
45667
45572
  return state;
45668
45573
  }
45669
45574
 
45670
- supportsWholeGenome() {
45575
+ get supportsWholeGenome() {
45671
45576
  return false;
45672
45577
  }
45673
45578
  /**
@@ -45828,7 +45733,7 @@
45828
45733
  clickedFeatures(clickState, features) {
45829
45734
  // We use the cached features rather than method to avoid async load. If the
45830
45735
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
45831
- if (!features) features = clickState.viewport.getCachedFeatures();
45736
+ if (!features) features = clickState.viewport.cachedFeatures;
45832
45737
 
45833
45738
  if (!features || features.length === 0) {
45834
45739
  return [];
@@ -48467,52 +48372,125 @@
48467
48372
  return regions;
48468
48373
  }
48469
48374
 
48375
+ function sendChords(chords, track, refFrame, alpha) {
48376
+ const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? track.color : getChrColor(refFrame.chr), alpha);
48377
+ const trackColor = IGVColor.addAlpha(track.color || 'rgb(0,0,255)', alpha); // name the chord set to include locus and filtering information
48378
+
48379
+ const encodedName = track.name.replaceAll(' ', '%20');
48380
+ const chordSetName = "all" === refFrame.chr ? encodedName : `${encodedName} ${refFrame.chr}:${refFrame.start}-${refFrame.end}`;
48381
+ track.browser.circularView.addChords(chords, {
48382
+ track: chordSetName,
48383
+ color: chordSetColor,
48384
+ trackColor: trackColor
48385
+ }); // show circular view if hidden
48386
+
48387
+ if (!track.browser.circularViewVisible) track.browser.circularViewVisible = true;
48388
+ }
48389
+
48470
48390
  function createCircularView(el, browser) {
48471
48391
  const circularView = new CircularView(el, {
48472
48392
  onChordClick: (feature, chordTrack, pluginManager) => {
48473
48393
  const f1 = feature.data;
48474
48394
  const f2 = f1.mate;
48475
- const flanking = 2000;
48476
- const l1 = new Locus({
48477
- chr: browser.genome.getChromosomeName(f1.refName),
48478
- start: f1.start,
48479
- end: f1.end
48480
- });
48481
- const l2 = new Locus({
48482
- chr: browser.genome.getChromosomeName(f2.refName),
48483
- start: f2.start,
48484
- end: f2.end
48485
- });
48486
- let loci; // If there is overlap with current loci
48487
-
48488
- loci = browser.currentLoci().map(str => Locus.fromLocusString(str));
48489
-
48490
- if (loci.some(locus => locus.contains(l1)) || loci.some(locus => locus.contains(l2))) {
48491
- for (let l of [l1, l2]) {
48492
- if (!loci.some(locus => {
48493
- return locus.contains(l);
48494
- })) {
48495
- // add flanking
48496
- l.start = Math.max(0, l.start - flanking);
48497
- l.end += flanking;
48498
- loci.push(l);
48395
+ addFrameForFeature(f1);
48396
+ addFrameForFeature(f2);
48397
+
48398
+ function addFrameForFeature(feature) {
48399
+ feature.chr = browser.genome.getChromosomeName(feature.refName);
48400
+ let frameFound = false;
48401
+
48402
+ for (let referenceFrame of browser.referenceFrameList) {
48403
+ const l = Locus.fromLocusString(referenceFrame.getLocusString());
48404
+
48405
+ if (l.contains(feature)) {
48406
+ frameFound = true;
48407
+ break;
48408
+ } else if (l.overlaps(feature)) {
48409
+ referenceFrame.extend(feature);
48410
+ frameFound = true;
48411
+ break;
48499
48412
  }
48500
48413
  }
48501
- } else {
48502
- l1.start = Math.max(0, l1.start - flanking);
48503
- l1.end += flanking;
48504
- l2.start = Math.max(0, l2.start - flanking);
48505
- l2.end += flanking;
48506
- loci = [l1, l2];
48507
- }
48508
48414
 
48509
- const searchString = loci.map(l => l.getLocusString()).join(" ");
48510
- browser.search(searchString);
48415
+ if (!frameFound) {
48416
+ const flanking = 2000;
48417
+ const center = (feature.start + feature.end) / 2;
48418
+ browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
48419
+ }
48420
+ }
48511
48421
  }
48512
48422
  });
48513
48423
  return circularView;
48514
48424
  }
48515
48425
 
48426
+ class PairedEndStats {
48427
+ constructor(alignments, _ref) {
48428
+ let {
48429
+ minTLENPercentile,
48430
+ maxTLENPercentile
48431
+ } = _ref;
48432
+ this.totalCount = 0;
48433
+ this.frCount = 0;
48434
+ this.rfCount = 0;
48435
+ this.ffCount = 0;
48436
+ this.sumF = 0;
48437
+ this.sumF2 = 0;
48438
+ this.lp = minTLENPercentile === undefined ? 0.1 : minTLENPercentile;
48439
+ this.up = maxTLENPercentile === undefined ? 99.5 : maxTLENPercentile;
48440
+ this.isizes = [];
48441
+ this.compute(alignments);
48442
+ }
48443
+
48444
+ compute(alignments) {
48445
+ for (let alignment of alignments) {
48446
+ if (alignment.isProperPair()) {
48447
+ var tlen = Math.abs(alignment.fragmentLength);
48448
+ this.sumF += tlen;
48449
+ this.sumF2 += tlen * tlen;
48450
+ this.isizes.push(tlen);
48451
+ var po = alignment.pairOrientation;
48452
+
48453
+ if (typeof po === "string" && po.length === 4) {
48454
+ var tmp = '' + po.charAt(0) + po.charAt(2);
48455
+
48456
+ switch (tmp) {
48457
+ case 'FF':
48458
+ case 'RR':
48459
+ this.ffCount++;
48460
+ break;
48461
+
48462
+ case "FR":
48463
+ this.frCount++;
48464
+ break;
48465
+
48466
+ case "RF":
48467
+ this.rfCount++;
48468
+ }
48469
+ }
48470
+
48471
+ this.totalCount++;
48472
+ }
48473
+ }
48474
+
48475
+ if (this.ffCount / this.totalCount > 0.9) this.orienation = "ff";else if (this.frCount / this.totalCount > 0.9) this.orienation = "fr";else if (this.rfCount / this.totalCount > 0.9) this.orienation = "rf";
48476
+ this.minTLEN = this.lp === 0 ? 0 : percentile(this.isizes, this.lp);
48477
+ this.maxTLEN = percentile(this.isizes, this.up); // var fMean = this.sumF / this.totalCount
48478
+ // var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount))
48479
+ // this.minTLEN = fMean - 3 * stdDev
48480
+ // this.maxTLEN = fMean + 3 * stdDev
48481
+ }
48482
+
48483
+ }
48484
+
48485
+ function percentile(array, p) {
48486
+ if (array.length === 0) return undefined;
48487
+ var k = Math.floor(array.length * (p / 100));
48488
+ array.sort(function (a, b) {
48489
+ return a - b;
48490
+ });
48491
+ return array[k];
48492
+ }
48493
+
48516
48494
  /*
48517
48495
  * The MIT License (MIT)
48518
48496
  *
@@ -48572,10 +48550,7 @@
48572
48550
  this.showInsertions = false !== config.showInsertions;
48573
48551
  this.showMismatches = false !== config.showMismatches;
48574
48552
  this.color = config.color;
48575
- this.coverageColor = config.coverageColor;
48576
- this.minFragmentLength = config.minFragmentLength; // Optional, might be undefined
48577
-
48578
- this.maxFragmentLength = config.maxFragmentLength || 1000; // The sort object can be an array in the case of multi-locus view, however if multiple sort positions
48553
+ this.coverageColor = config.coverageColor; // The sort object can be an array in the case of multi-locus view, however if multiple sort positions
48579
48554
  // are present for a given reference frame the last one will take precedence
48580
48555
 
48581
48556
  if (config.sort) {
@@ -48603,12 +48578,22 @@
48603
48578
  return this._height;
48604
48579
  }
48605
48580
 
48581
+ get minTemplateLength() {
48582
+ const configMinTLEN = this.config.minTLEN !== undefined ? this.config.minTLEN : this.config.minFragmentLength;
48583
+ return configMinTLEN !== undefined ? configMinTLEN : this._pairedEndStats ? this._pairedEndStats.minTLEN : 0;
48584
+ }
48585
+
48586
+ get maxTemplateLength() {
48587
+ const configMaxTLEN = this.config.maxTLEN !== undefined ? this.config.maxTLEN : this.config.maxFragmentLength;
48588
+ return configMaxTLEN !== undefined ? configMaxTLEN : this._pairedEndStats ? this._pairedEndStats.maxTLEN : 1000;
48589
+ }
48590
+
48606
48591
  sort(options) {
48607
48592
  options = this.assignSort(options);
48608
48593
 
48609
48594
  for (let vp of this.trackView.viewports) {
48610
48595
  if (vp.containsPosition(options.chr, options.position)) {
48611
- const alignmentContainer = vp.getCachedFeatures();
48596
+ const alignmentContainer = vp.cachedFeatures;
48612
48597
 
48613
48598
  if (alignmentContainer) {
48614
48599
  sortAlignmentRows(options, alignmentContainer);
@@ -48643,16 +48628,16 @@
48643
48628
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel, viewport) {
48644
48629
  const alignmentContainer = await this.featureSource.getAlignments(chr, bpStart, bpEnd);
48645
48630
 
48646
- if (alignmentContainer.alignments && alignmentContainer.alignments.length > 99) {
48647
- if (undefined === this.minFragmentLength) {
48648
- this.minFragmentLength = alignmentContainer.pairedEndStats.lowerFragmentLength;
48649
- }
48631
+ if (alignmentContainer.paired && !this._pairedEndStats && !this.config.maxFragmentLength) {
48632
+ const pairedEndStats = new PairedEndStats(alignmentContainer.alignments, this.config);
48650
48633
 
48651
- if (undefined === this.maxFragmentLength) {
48652
- this.maxFragmentLength = alignmentContainer.pairedEndStats.upperFragmentLength;
48634
+ if (pairedEndStats.totalCount > 99) {
48635
+ this._pairedEndStats = pairedEndStats;
48653
48636
  }
48654
48637
  }
48655
48638
 
48639
+ alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
48640
+
48656
48641
  const sort = this.sortObject;
48657
48642
 
48658
48643
  if (sort) {
@@ -48757,7 +48742,7 @@
48757
48742
  label: 'pair orientation'
48758
48743
  });
48759
48744
  colorByMenuItems.push({
48760
- key: 'fragmentLength',
48745
+ key: 'tlen',
48761
48746
  label: 'insert size (TLEN)'
48762
48747
  });
48763
48748
  colorByMenuItems.push({
@@ -48868,36 +48853,19 @@
48868
48853
  this.trackView.repaintViews();
48869
48854
  }
48870
48855
  });
48871
- } // Experimental JBrowse feature
48856
+ } // Add chords to JBrowse circular view, if present
48872
48857
 
48873
48858
 
48874
- if (this.browser.circularView && true === this.browser.circularViewVisible && (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
48859
+ if (this.browser.circularView && (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
48875
48860
  menuItems.push('<hr/>');
48876
48861
 
48877
48862
  if (this.alignmentTrack.hasPairs) {
48878
48863
  menuItems.push({
48879
48864
  label: 'Add discordant pairs to circular view',
48880
48865
  click: () => {
48881
- const maxFragmentLength = this.maxFragmentLength;
48882
- const inView = [];
48883
-
48884
48866
  for (let viewport of this.trackView.viewports) {
48885
- for (let a of viewport.getCachedFeatures().allAlignments()) {
48886
- const referenceFrame = viewport.referenceFrame;
48887
-
48888
- if (a.end >= referenceFrame.start && a.start <= referenceFrame.end && a.mate && a.mate.chr && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxFragmentLength)) {
48889
- inView.push(a);
48890
- }
48891
- }
48867
+ this.addPairedChordsForViewport(viewport);
48892
48868
  }
48893
-
48894
- this.browser.circularViewVisible = true;
48895
- const chords = makePairedAlignmentChords(inView);
48896
- const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02);
48897
- this.browser.circularView.addChords(chords, {
48898
- track: this.name,
48899
- color: color
48900
- });
48901
48869
  }
48902
48870
  });
48903
48871
  }
@@ -48906,25 +48874,9 @@
48906
48874
  menuItems.push({
48907
48875
  label: 'Add split reads to circular view',
48908
48876
  click: () => {
48909
- const inView = [];
48910
-
48911
48877
  for (let viewport of this.trackView.viewports) {
48912
- for (let a of viewport.getCachedFeatures().allAlignments()) {
48913
- const referenceFrame = viewport.referenceFrame;
48914
- const sa = a.hasTag('SA');
48915
-
48916
- if (a.end >= referenceFrame.start && a.start <= referenceFrame.end && sa) {
48917
- inView.push(a);
48918
- }
48919
- }
48878
+ this.addSplitChordsForViewport(viewport);
48920
48879
  }
48921
-
48922
- const chords = makeSupplementalAlignmentChords(inView);
48923
- const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.1);
48924
- this.browser.circularView.addChords(chords, {
48925
- track: this.name,
48926
- color: color
48927
- });
48928
48880
  }
48929
48881
  });
48930
48882
  }
@@ -49043,7 +48995,7 @@
49043
48995
  }
49044
48996
 
49045
48997
  getCachedAlignmentContainers() {
49046
- return this.trackView.viewports.map(vp => vp.getCachedFeatures());
48998
+ return this.trackView.viewports.map(vp => vp.cachedFeatures);
49047
48999
  }
49048
49000
 
49049
49001
  get dataRange() {
@@ -49069,6 +49021,56 @@
49069
49021
  set autoscale(autoscale) {
49070
49022
  this.coverageTrack.autoscale = autoscale;
49071
49023
  }
49024
+ /**
49025
+ * Add chords to the circular view for the given viewport, represented by its reference frame
49026
+ * @param refFrame
49027
+ */
49028
+
49029
+
49030
+ addPairedChordsForViewport(viewport) {
49031
+ const maxTemplateLength = this.maxTemplateLength;
49032
+ const inView = [];
49033
+ const refFrame = viewport.referenceFrame;
49034
+
49035
+ for (let a of viewport.cachedFeatures.allAlignments()) {
49036
+ if (a.end >= refFrame.start && a.start <= refFrame.end && a.mate && a.mate.chr && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxTemplateLength)) {
49037
+ inView.push(a);
49038
+ }
49039
+ }
49040
+
49041
+ const chords = makePairedAlignmentChords(inView);
49042
+ sendChords(chords, this, refFrame, 0.02); // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
49043
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
49044
+ //
49045
+ // // name the chord set to include track name and locus
49046
+ // const encodedName = this.name.replaceAll(' ', '%20')
49047
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
49048
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
49049
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
49050
+ }
49051
+
49052
+ addSplitChordsForViewport(viewport) {
49053
+ const inView = [];
49054
+ const refFrame = viewport.referenceFrame;
49055
+
49056
+ for (let a of viewport.cachedFeatures.allAlignments()) {
49057
+ const sa = a.hasTag('SA');
49058
+
49059
+ if (a.end >= refFrame.start && a.start <= refFrame.end && sa) {
49060
+ inView.push(a);
49061
+ }
49062
+ }
49063
+
49064
+ const chords = makeSupplementalAlignmentChords(inView);
49065
+ sendChords(chords, this, refFrame, 0.02); // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
49066
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
49067
+ //
49068
+ // // name the chord set to include track name and locus
49069
+ // const encodedName = this.name.replaceAll(' ', '%20')
49070
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
49071
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
49072
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
49073
+ }
49072
49074
 
49073
49075
  }
49074
49076
 
@@ -49179,7 +49181,7 @@
49179
49181
  }
49180
49182
 
49181
49183
  getClickedObject(clickState) {
49182
- let features = clickState.viewport.getCachedFeatures();
49184
+ let features = clickState.viewport.cachedFeatures;
49183
49185
  if (!features || features.length === 0) return;
49184
49186
  const genomicLocation = Math.floor(clickState.genomicLocation);
49185
49187
  const coverageMap = features.coverageMap;
@@ -49265,14 +49267,14 @@
49265
49267
  this.deletionColor = config.deletionColor || "black";
49266
49268
  this.skippedColor = config.skippedColor || "rgb(150, 170, 170)";
49267
49269
  this.pairConnectorColor = config.pairConnectorColor;
49268
- this.smallFragmentLengthColor = config.smallFragmentLengthColor || "rgb(0, 0, 150)";
49269
- this.largeFragmentLengthColor = config.largeFragmentLengthColor || "rgb(200, 0, 0)";
49270
+ this.smallTLENColor = config.smallTLENColor || config.smallFragmentLengthColor || "rgb(0, 0, 150)";
49271
+ this.largeTLENColor = config.largeTLENColor || config.largeFragmentLengthColor || "rgb(200, 0, 0)";
49270
49272
  this.pairOrientation = config.pairOrienation || 'fr';
49271
49273
  this.pairColors = {};
49272
49274
  this.pairColors["RL"] = config.rlColor || "rgb(0, 150, 0)";
49273
49275
  this.pairColors["RR"] = config.rrColor || "rgb(20, 50, 200)";
49274
49276
  this.pairColors["LL"] = config.llColor || "rgb(0, 150, 150)";
49275
- this.colorBy = config.colorBy || "pairOrientation";
49277
+ this.colorBy = config.colorBy || "unexpectedPair";
49276
49278
  this.colorByTag = config.colorByTag ? config.colorByTag.toUpperCase() : undefined;
49277
49279
  this.bamColorTag = config.bamColorTag === undefined ? "YC" : config.bamColorTag;
49278
49280
  this.hideSmallIndels = config.hideSmallIndels;
@@ -49371,7 +49373,7 @@
49371
49373
  for (let alignment of alignmentRow.alignments) {
49372
49374
  this.hasPairs = this.hasPairs || alignment.isPaired();
49373
49375
 
49374
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
49376
+ if (this.browser.circularView) {
49375
49377
  // This is an expensive check, only do it if needed
49376
49378
  this.hasSupplemental = this.hasSupplemental || alignment.hasTag('SA');
49377
49379
  }
@@ -49627,7 +49629,7 @@
49627
49629
  direction: direction
49628
49630
  };
49629
49631
  this.parent.sortObject = newSortObject;
49630
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
49632
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
49631
49633
  viewport.repaint();
49632
49634
  };
49633
49635
 
@@ -49679,7 +49681,7 @@
49679
49681
  };
49680
49682
  this.sortByTag = tag;
49681
49683
  this.parent.sortObject = newSortObject;
49682
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
49684
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
49683
49685
  viewport.repaint();
49684
49686
  }
49685
49687
  }
@@ -49703,8 +49705,12 @@
49703
49705
  const referenceFrame = clickState.viewport.referenceFrame;
49704
49706
 
49705
49707
  if (this.browser.genome.getChromosome(clickedAlignment.mate.chr)) {
49706
- this.highlightedAlignmentReadNamed = clickedAlignment.readName;
49707
- this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame);
49708
+ this.highlightedAlignmentReadNamed = clickedAlignment.readName; //this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame)
49709
+
49710
+ const bpWidth = referenceFrame.end - referenceFrame.start;
49711
+ const frameStart = clickedAlignment.mate.position - bpWidth / 2;
49712
+ const frameEnd = clickedAlignment.mate.position + bpWidth / 2;
49713
+ this.browser.addMultiLocusPanel(clickedAlignment.mate.chr, frameStart, frameEnd, referenceFrame);
49708
49714
  } else {
49709
49715
  Alert.presentAlert(`Reference does not contain chromosome: ${clickedAlignment.mate.chr}`);
49710
49716
  }
@@ -49717,9 +49723,7 @@
49717
49723
  list.push({
49718
49724
  label: 'View read sequence',
49719
49725
  click: () => {
49720
- const alignment = clickedAlignment;
49721
- if (!alignment) return;
49722
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
49726
+ const seqstring = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
49723
49727
 
49724
49728
  if (!seqstring || "*" === seqstring) {
49725
49729
  Alert.presentAlert("Read sequence: *");
@@ -49732,12 +49736,16 @@
49732
49736
  if (isSecureContext()) {
49733
49737
  list.push({
49734
49738
  label: 'Copy read sequence',
49735
- click: () => {
49736
- const alignment = clickedAlignment;
49737
- if (!alignment) return;
49738
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
49739
+ click: async () => {
49740
+ const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
49739
49741
 
49740
- navigator.clipboard.writeText(seqstring);
49742
+ try {
49743
+ //console.log(`seq: ${seq}`)
49744
+ await navigator.clipboard.writeText(seq);
49745
+ } catch (e) {
49746
+ console.error(e);
49747
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
49748
+ }
49741
49749
  }
49742
49750
  });
49743
49751
  }
@@ -49747,25 +49755,12 @@
49747
49755
  } // Experimental JBrowse feature
49748
49756
 
49749
49757
 
49750
- if (this.browser.circularView && true === this.browser.circularViewVisible && (this.hasPairs || this.hasSupplemental)) {
49758
+ if (this.browser.circularView && (this.hasPairs || this.hasSupplemental)) {
49751
49759
  if (this.hasPairs) {
49752
49760
  list.push({
49753
49761
  label: 'Add discordant pairs to circular view',
49754
49762
  click: () => {
49755
- const maxFragmentLength = this.parent.maxFragmentLength;
49756
- const {
49757
- referenceFrame
49758
- } = viewport;
49759
- const inView = viewport.getCachedFeatures().allAlignments().filter(a => {
49760
- return a.end >= referenceFrame.start && a.start <= referenceFrame.end && a.mate && a.mate.chr && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxFragmentLength);
49761
- });
49762
- this.browser.circularViewVisible = true;
49763
- const chords = makePairedAlignmentChords(inView);
49764
- const color = IGVColor.addAlpha(this.parent.color || 'rgb(0,0,255)', 0.02);
49765
- this.browser.circularView.addChords(chords, {
49766
- track: this.parent.name,
49767
- color: color
49768
- });
49763
+ this.parent.addPairedChordsForViewport(viewport);
49769
49764
  }
49770
49765
  });
49771
49766
  }
@@ -49774,23 +49769,7 @@
49774
49769
  list.push({
49775
49770
  label: 'Add split reads to circular view',
49776
49771
  click: () => {
49777
- const inView = [];
49778
-
49779
- for (let a of viewport.getCachedFeatures().allAlignments()) {
49780
- const referenceFrame = viewport.referenceFrame;
49781
- const sa = a.hasTag('SA');
49782
-
49783
- if (a.end >= referenceFrame.start && a.start <= referenceFrame.end && sa) {
49784
- inView.push(a);
49785
- }
49786
- }
49787
-
49788
- const chords = makeSupplementalAlignmentChords(inView);
49789
- const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.1);
49790
- this.browser.circularView.addChords(chords, {
49791
- track: this.name,
49792
- color: color
49793
- });
49772
+ this.parent.addSplitChordsForViewport(viewport);
49794
49773
  }
49795
49774
  });
49796
49775
  }
@@ -49806,7 +49785,7 @@
49806
49785
  const y = clickState.y;
49807
49786
  const genomicLocation = clickState.genomicLocation;
49808
49787
  const showSoftClips = this.parent.showSoftClips;
49809
- let features = viewport.getCachedFeatures();
49788
+ let features = viewport.cachedFeatures;
49810
49789
  if (!features || features.length === 0) return;
49811
49790
  let packedAlignmentRows = features.packedAlignmentRows;
49812
49791
  let downsampledIntervals = features.downsampledIntervals;
@@ -49892,13 +49871,16 @@
49892
49871
  break;
49893
49872
  }
49894
49873
 
49874
+ case "tlen":
49895
49875
  case "fragmentLength":
49896
- if (alignment.mate && alignment.isMateMapped() && alignment.mate.chr !== alignment.chr) {
49897
- color = getChrColor(alignment.mate.chr);
49898
- } else if (this.parent.minFragmentLength && Math.abs(alignment.fragmentLength) < this.parent.minFragmentLength) {
49899
- color = this.smallFragmentLengthColor;
49900
- } else if (this.parent.maxFragmentLength && Math.abs(alignment.fragmentLength) > this.parent.maxFragmentLength) {
49901
- color = this.largeFragmentLengthColor;
49876
+ if (alignment.mate && alignment.isMateMapped()) {
49877
+ if (alignment.mate.chr !== alignment.chr) {
49878
+ color = getChrColor(alignment.mate.chr);
49879
+ } else if (this.parent.minTemplateLength && Math.abs(alignment.fragmentLength) < this.parent.minTemplateLength) {
49880
+ color = this.smallTLENColor;
49881
+ } else if (this.parent.maxTemplateLength && Math.abs(alignment.fragmentLength) > this.parent.maxTemplateLength) {
49882
+ color = this.largeTLENColor;
49883
+ }
49902
49884
  }
49903
49885
 
49904
49886
  break;
@@ -49940,10 +49922,7 @@
49940
49922
  alignmentContainer.packedAlignmentRows.sort(function (rowA, rowB) {
49941
49923
  const i = rowA.score > rowB.score ? 1 : rowA.score < rowB.score ? -1 : 0;
49942
49924
  return true === direction ? i : -i;
49943
- }); // For debugging
49944
- // for(let r of alignmentContainer.packedAlignmentRows) {
49945
- // console.log(r.score);
49946
- // }
49925
+ });
49947
49926
  }
49948
49927
 
49949
49928
  function shadedBaseColor(qual, baseColor) {
@@ -50098,7 +50077,7 @@
50098
50077
  });
50099
50078
  this.$viewport.append(this.$rulerLabel);
50100
50079
  this.$rulerLabel.click(async () => {
50101
- await this.browser.selectMultiLocusPanel(this.referenceFrame); // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
50080
+ await this.browser.gotoMultilocusPanel(this.referenceFrame); // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
50102
50081
  // for (let referenceFrame of removals) {
50103
50082
  // await this.browser.removeMultiLocusPanel(referenceFrame)
50104
50083
  // }
@@ -50204,7 +50183,10 @@
50204
50183
  currentViewport = this;
50205
50184
  this.$tooltip.show();
50206
50185
  } else if (currentViewport.guid !== this.guid) {
50207
- currentViewport.$tooltip.hide();
50186
+ if (currentViewport.$tooltip) {
50187
+ currentViewport.$tooltip.hide();
50188
+ }
50189
+
50208
50190
  this.$tooltip.show();
50209
50191
  currentViewport = this;
50210
50192
  } else {
@@ -50238,7 +50220,9 @@
50238
50220
  }); // hide tooltip when movement stops
50239
50221
 
50240
50222
  clearTimeout(timer);
50241
- timer = setTimeout(() => this.$tooltip.hide(), toolTipTimeout);
50223
+ timer = setTimeout(() => {
50224
+ if (this.$tooltip) this.$tooltip.hide();
50225
+ }, toolTipTimeout);
50242
50226
  }
50243
50227
  }
50244
50228
 
@@ -50253,65 +50237,6 @@
50253
50237
 
50254
50238
  }
50255
50239
 
50256
- const viewportColumnManager = {
50257
- createColumns: (columnContainer, count) => {
50258
- for (let i = 0; i < count; i++) {
50259
- if (0 === i) {
50260
- createColumn(columnContainer, 'igv-column');
50261
- } else {
50262
- columnContainer.appendChild(div$1({
50263
- class: 'igv-column-shim'
50264
- }));
50265
- createColumn(columnContainer, 'igv-column');
50266
- }
50267
- }
50268
- },
50269
- removeColumnAtIndex: (i, column) => {
50270
- const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
50271
- column.remove();
50272
- shim.remove();
50273
- },
50274
- insertAfter: referenceElement => {
50275
- const shim = div$1({
50276
- class: 'igv-column-shim'
50277
- });
50278
- insertElementAfter(shim, referenceElement);
50279
- const column = div$1({
50280
- class: 'igv-column'
50281
- });
50282
- insertElementAfter(column, shim);
50283
- return column;
50284
- },
50285
- insertBefore: (referenceElement, count) => {
50286
- for (let i = 0; i < count; i++) {
50287
- const column = div$1({
50288
- class: 'igv-column'
50289
- });
50290
- insertElementBefore(column, referenceElement);
50291
-
50292
- if (count > 1 && i > 0) {
50293
- const columnShim = div$1({
50294
- class: 'igv-column-shim'
50295
- });
50296
- insertElementBefore(columnShim, column);
50297
- }
50298
- }
50299
- },
50300
- indexOfColumn: (columnContainer, column) => {
50301
- const allColumns = columnContainer.querySelectorAll('.igv-column');
50302
-
50303
- for (let i = 0; i < allColumns.length; i++) {
50304
- const c = allColumns[i];
50305
-
50306
- if (c === column) {
50307
- return i;
50308
- }
50309
- }
50310
-
50311
- return undefined;
50312
- }
50313
- };
50314
-
50315
50240
  /*
50316
50241
  * The MIT License (MIT)
50317
50242
  *
@@ -50343,60 +50268,30 @@
50343
50268
  }
50344
50269
 
50345
50270
  initializationHelper() {
50346
- this.$ideogramCanvas = $$1('<canvas>', {
50347
- class: 'igv-ideogram-canvas'
50348
- });
50349
- this.$ideogramCanvas.insertBefore(this.$canvas);
50350
- const canvas = this.$ideogramCanvas.get(0);
50351
- this.ideogram_ctx = canvas.getContext('2d');
50352
- this.$canvas.remove();
50353
- this.canvas = undefined;
50354
- this.ctx = undefined;
50271
+ this.canvas = document.createElement('canvas');
50272
+ this.canvas.className = 'igv-ideogram-canvas';
50273
+ this.$content.append($$1(this.canvas));
50274
+ this.ideogram_ctx = this.canvas.getContext('2d');
50355
50275
  this.addMouseHandlers();
50356
50276
  }
50357
50277
 
50358
50278
  addMouseHandlers() {
50359
- this.addBrowserObserver();
50360
50279
  this.addViewportClickHandler(this.$viewport.get(0));
50361
50280
  }
50362
50281
 
50363
- removeMouseHandlers() {
50364
- this.removeBrowserObserver();
50365
- this.removeViewportClickHandler(this.$viewport.get(0));
50366
- }
50367
-
50368
- addBrowserObserver() {
50369
- function observerHandler(referenceFrameList) {
50370
- const column = this.$viewport.get(0).parentElement;
50371
-
50372
- if (null !== column) {
50373
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column); // console.log(`ideogram-viewport - locus-change-handler index(${ index }) ${ referenceFrameList[ index ].getLocusString() } ${ Date.now() } `)
50374
-
50375
- this.update(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height(), referenceFrameList[index]);
50376
- }
50377
- }
50378
-
50379
- this.boundObserverHandler = observerHandler.bind(this);
50380
- this.browser.on('locuschange', this.boundObserverHandler);
50381
- }
50382
-
50383
- removeBrowserObserver() {
50384
- this.browser.off('locuschange', this.boundObserverHandler);
50385
- }
50386
-
50387
50282
  addViewportClickHandler(viewport) {
50283
+ this.boundClickHandler = clickHandler.bind(this);
50284
+ viewport.addEventListener('click', this.boundClickHandler);
50285
+
50388
50286
  function clickHandler(event) {
50389
- const column = viewport.parentElement;
50390
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column);
50391
- const referenceFrame = this.browser.referenceFrameList[index];
50392
50287
  const {
50393
50288
  xNormalized,
50394
50289
  width
50395
50290
  } = translateMouseCoordinates$1(event, this.ideogram_ctx.canvas);
50396
50291
  const {
50397
50292
  bpLength
50398
- } = this.browser.genome.getChromosome(referenceFrame.chr);
50399
- const locusLength = referenceFrame.bpPerPixel * width;
50293
+ } = this.browser.genome.getChromosome(this.referenceFrame.chr);
50294
+ const locusLength = this.referenceFrame.bpPerPixel * width;
50400
50295
  const chrCoveragePercentage = locusLength / bpLength;
50401
50296
  let xPercentage = xNormalized;
50402
50297
 
@@ -50410,18 +50305,11 @@
50410
50305
 
50411
50306
  const ss = Math.round((xPercentage - chrCoveragePercentage / 2.0) * bpLength);
50412
50307
  const ee = Math.round((xPercentage + chrCoveragePercentage / 2.0) * bpLength);
50413
- referenceFrame.start = ss;
50414
- referenceFrame.end = ee;
50415
- referenceFrame.bpPerPixel = (ee - ss) / width;
50416
- this.browser.updateViews(referenceFrame, this.browser.trackViews, true);
50308
+ this.referenceFrame.start = ss;
50309
+ this.referenceFrame.end = ee;
50310
+ this.referenceFrame.bpPerPixel = (ee - ss) / width;
50311
+ this.browser.updateViews(this.referenceFrame, this.browser.trackViews, true);
50417
50312
  }
50418
-
50419
- this.boundClickHandler = clickHandler.bind(this);
50420
- viewport.addEventListener('click', this.boundClickHandler);
50421
- }
50422
-
50423
- removeViewportClickHandler(viewport) {
50424
- viewport.removeEventListener('click', this.boundClickHandler);
50425
50313
  }
50426
50314
 
50427
50315
  setWidth(width) {
@@ -50439,14 +50327,22 @@
50439
50327
  context.restore();
50440
50328
  }
50441
50329
 
50442
- update(context, pixelWidth, pixelHeight, referenceFrame) {
50443
- this.$canvas.hide();
50444
- IGVGraphics.configureHighDPICanvas(context, pixelWidth, pixelHeight);
50330
+ repaint() {
50331
+ this.draw({
50332
+ referenceFrame: this.referenceFrame
50333
+ });
50334
+ }
50335
+
50336
+ draw(_ref) {
50337
+ let {
50338
+ referenceFrame
50339
+ } = _ref;
50340
+ IGVGraphics.configureHighDPICanvas(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height());
50445
50341
  this.trackView.track.draw({
50446
- context,
50342
+ context: this.ideogram_ctx,
50447
50343
  referenceFrame,
50448
- pixelWidth,
50449
- pixelHeight
50344
+ pixelWidth: this.$viewport.width(),
50345
+ pixelHeight: this.$viewport.height()
50450
50346
  });
50451
50347
  }
50452
50348
 
@@ -50484,7 +50380,7 @@
50484
50380
  function createViewport(trackView, column, referenceFrame, width) {
50485
50381
  if ('ruler' === trackView.track.type) {
50486
50382
  return new RulerViewport(trackView, column, referenceFrame, width);
50487
- } else if ('ideogram' === trackView.track.type) {
50383
+ } else if ('ideogram' === trackView.track.id) {
50488
50384
  return new IdeogramViewport(trackView, column, referenceFrame, width);
50489
50385
  } else {
50490
50386
  return new TrackViewport(trackView, column, referenceFrame, width);
@@ -51011,7 +50907,6 @@
51011
50907
 
51012
50908
  class TrackView {
51013
50909
  constructor(browser, columnContainer, track) {
51014
- this.namespace = `trackview-${guid$2()}`;
51015
50910
  this.browser = browser;
51016
50911
  this.track = track;
51017
50912
  track.trackView = this;
@@ -51037,7 +50932,7 @@
51037
50932
 
51038
50933
  addDOMToColumnContainer(browser, columnContainer, referenceFrameList) {
51039
50934
  // Axis
51040
- this.axis = this.createAxis(browser, this.track); // Track Viewports
50935
+ this.axis = this.createAxis(browser, this.track); // Create a viewport for each reference frame
51041
50936
 
51042
50937
  this.viewports = [];
51043
50938
  const viewportWidth = browser.calculateViewportWidth(referenceFrameList.length);
@@ -51104,7 +50999,6 @@
51104
50999
  this.axis.remove(); // Track Viewports
51105
51000
 
51106
51001
  for (let viewport of this.viewports) {
51107
- viewport.removeMouseHandlers();
51108
51002
  viewport.$viewport.remove();
51109
51003
  } // SampleName Viewport
51110
51004
 
@@ -51222,7 +51116,7 @@
51222
51116
  $viewport.height(newHeight);
51223
51117
  }
51224
51118
 
51225
- this.sampleNameViewport.viewport.style.height = `${newHeight}px`; // If the track does not manage its own content height set it here
51119
+ this.sampleNameViewport.viewport.style.height = `${newHeight}px`; // If the track does not manage its own content height set it equal to the viewport height here
51226
51120
 
51227
51121
  if (typeof this.track.computePixelHeight !== "function") {
51228
51122
  for (let vp of this.viewports) {
@@ -51274,14 +51168,6 @@
51274
51168
  if (viewport.isLoading()) return true;
51275
51169
  }
51276
51170
  }
51277
-
51278
- resize(viewportWidth) {
51279
- for (let viewport of this.viewports) {
51280
- viewport.setWidth(viewportWidth);
51281
- }
51282
-
51283
- this.updateViews(true);
51284
- }
51285
51171
  /**
51286
51172
  * Repaint all viewports without loading any new data. Use this for events that change visual aspect of data,
51287
51173
  * e.g. color, sort order, etc, but do not change the genomic state.
@@ -51290,7 +51176,9 @@
51290
51176
 
51291
51177
  repaintViews() {
51292
51178
  for (let viewport of this.viewports) {
51293
- viewport.repaint();
51179
+ if (viewport.isVisible()) {
51180
+ viewport.repaint();
51181
+ }
51294
51182
  }
51295
51183
 
51296
51184
  if (typeof this.track.paintAxis === 'function') {
@@ -51312,41 +51200,60 @@
51312
51200
  setTrackLabelName(name) {
51313
51201
  this.viewports.forEach(viewport => viewport.setTrackLabel(name));
51314
51202
  }
51203
+ /**
51204
+ * Called in response to a window resize event, change in # of multilocus panels, or other event that changes
51205
+ * the width of the track view.
51206
+ *
51207
+ * @param viewportWidth The width of each viewport in this track view.
51208
+ */
51209
+
51210
+
51211
+ resize(viewportWidth) {
51212
+ for (let viewport of this.viewports) {
51213
+ viewport.setWidth(viewportWidth);
51214
+ }
51215
+ }
51315
51216
  /**
51316
51217
  * Update viewports to reflect current genomic state, possibly loading additional data.
51218
+ *
51219
+ * @param force - if true, force a repaint even if no new data is loaded
51220
+ * @returns {Promise<void>}
51317
51221
  */
51318
51222
 
51319
51223
 
51320
- async updateViews(force) {
51224
+ async updateViews() {
51321
51225
  if (!(this.browser && this.browser.referenceFrameList)) return;
51322
51226
  const visibleViewports = this.viewports.filter(viewport => viewport.isVisible()); // Shift viewports left/right to current genomic state (pans canvas)
51323
51227
 
51324
- visibleViewports.forEach(viewport => viewport.shift());
51325
- const isDragging = this.browser.dragObject;
51228
+ visibleViewports.forEach(viewport => viewport.shift()); // If dragging (panning) return
51326
51229
 
51327
- if (isDragging) {
51230
+ if (this.browser.dragObject) {
51328
51231
  return;
51329
- } // rpv: viewports whose image (canvas) does not fully cover current genomic range
51232
+ } // Get viewports to repaint
51233
+
51330
51234
 
51235
+ let viewportsToRepaint = this.track.autoscale || this.track.autoscaleGroup || this.track.type === 'ruler' ? visibleViewports : visibleViewports.filter(vp => vp.needsRepaint()); // Filter zoomed out views. This has the side effect or turning off or no the zoomed out notice
51331
51236
 
51332
- const reloadableViewports = this.viewportsToReload(force); // Trigger viewport to load features needed to cover current genomic range
51237
+ viewportsToRepaint = viewportsToRepaint.filter(viewport => viewport.checkZoomIn()); // Get viewports that require a data load
51238
+
51239
+ const viewportsToReload = viewportsToRepaint.filter(viewport => viewport.needsReload()); // Trigger viewport to load features needed to cover current genomic range
51333
51240
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
51334
51241
 
51335
- for (let viewport of reloadableViewports) {
51242
+ for (let viewport of viewportsToReload) {
51336
51243
  await viewport.loadFeatures();
51337
51244
  }
51338
51245
 
51339
51246
  if (this.disposed) return; // Track was removed during load
51340
- // Very special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
51247
+ // Special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
51341
51248
  // section depends on data from all the views. We only need to adjust this however if any data was loaded
51342
51249
  // (i.e. reloadableViewports.length > 0)
51343
51250
 
51344
- if (this.track && typeof this.track.variantRowCount === 'function' && reloadableViewports.length > 0) {
51251
+ if (this.track && typeof this.track.variantRowCount === 'function' && viewportsToReload.length > 0) {
51345
51252
  let maxRow = 0;
51346
51253
 
51347
51254
  for (let viewport of this.viewports) {
51348
- if (viewport.tile && viewport.tile.features) {
51349
- maxRow = Math.max(maxRow, viewport.tile.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
51255
+ if (viewport.featureCache && viewport.featureCache.features) {
51256
+ maxRow = Math.max(maxRow, viewport.featureCache.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
51350
51257
  }
51351
51258
  }
51352
51259
 
@@ -51369,14 +51276,14 @@
51369
51276
  const start = referenceFrame.start;
51370
51277
  const end = start + referenceFrame.toBP($$1(visibleViewport.contentDiv).width());
51371
51278
 
51372
- if (visibleViewport.tile && visibleViewport.tile.features) {
51373
- if (typeof visibleViewport.tile.features.getMax === 'function') {
51374
- const max = visibleViewport.tile.features.getMax(start, end);
51279
+ if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
51280
+ if (typeof visibleViewport.featureCache.features.getMax === 'function') {
51281
+ const max = visibleViewport.featureCache.features.getMax(start, end);
51375
51282
  allFeatures.push({
51376
51283
  value: max
51377
51284
  });
51378
51285
  } else {
51379
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.tile.features, start, end));
51286
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end));
51380
51287
  }
51381
51288
  }
51382
51289
  }
@@ -51386,17 +51293,10 @@
51386
51293
  } else {
51387
51294
  this.track.dataRange = doAutoscale(allFeatures);
51388
51295
  }
51389
- } // Must repaint all viewports if autoscaling
51390
-
51296
+ }
51391
51297
 
51392
- if (!isDragging && (this.track.autoscale || this.track.autoscaleGroup)) {
51393
- for (let visibleViewport of visibleViewports) {
51394
- visibleViewport.repaint();
51395
- }
51396
- } else {
51397
- for (let vp of reloadableViewports) {
51398
- vp.repaint();
51399
- }
51298
+ for (let vp of viewportsToRepaint) {
51299
+ vp.repaint();
51400
51300
  }
51401
51301
 
51402
51302
  this.adjustTrackHeight(); // Repaint sample names last
@@ -51419,36 +51319,34 @@
51419
51319
  }
51420
51320
  }
51421
51321
  /**
51422
- * Return a promise to get all in-view features. Used for group autoscaling.
51322
+ * Return a promise to get all in-view features across all viewports. Used for group autoscaling.
51423
51323
  */
51424
51324
 
51425
51325
 
51426
- async getInViewFeatures(force) {
51326
+ async getInViewFeatures() {
51427
51327
  if (!(this.browser && this.browser.referenceFrameList)) {
51428
51328
  return [];
51429
- } // List of viewports that need reloading
51430
-
51329
+ }
51431
51330
 
51432
- const rpV = this.viewportsToReload(force);
51433
- const promises = rpV.map(function (vp) {
51434
- return vp.loadFeatures();
51435
- });
51436
- await Promise.all(promises);
51437
51331
  let allFeatures = [];
51438
51332
 
51439
51333
  for (let vp of this.viewports) {
51440
- if (vp.tile && vp.tile.features) {
51334
+ if (vp.needsReload()) {
51335
+ await vp.loadFeatures();
51336
+ }
51337
+
51338
+ if (vp.featureCache && vp.featureCache.features) {
51441
51339
  const referenceFrame = vp.referenceFrame;
51442
51340
  const start = referenceFrame.start;
51443
51341
  const end = start + referenceFrame.toBP($$1(vp.contentDiv).width());
51444
51342
 
51445
- if (typeof vp.tile.features.getMax === 'function') {
51446
- const max = vp.tile.features.getMax(start, end);
51343
+ if (typeof vp.featureCache.features.getMax === 'function') {
51344
+ const max = vp.featureCache.features.getMax(start, end);
51447
51345
  allFeatures.push({
51448
51346
  value: max
51449
51347
  });
51450
51348
  } else {
51451
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.tile.features, start, end));
51349
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.featureCache.features, start, end));
51452
51350
  }
51453
51351
  }
51454
51352
  }
@@ -51490,27 +51388,6 @@
51490
51388
  }
51491
51389
  }
51492
51390
 
51493
- viewportsToReload(force) {
51494
- // List of viewports that need reloading
51495
- const viewports = this.viewports.filter(viewport => {
51496
- if (!viewport.isVisible()) {
51497
- return false;
51498
- }
51499
-
51500
- if (!viewport.checkZoomIn()) {
51501
- return false;
51502
- } else {
51503
- const referenceFrame = viewport.referenceFrame;
51504
- const chr = viewport.referenceFrame.chr;
51505
- const start = referenceFrame.start;
51506
- const end = start + referenceFrame.toBP($$1(viewport.contentDiv).width());
51507
- const bpPerPixel = referenceFrame.bpPerPixel;
51508
- return force || !viewport.tile || viewport.tile.invalidate || !viewport.tile.containsRange(chr, start, end, bpPerPixel);
51509
- }
51510
- });
51511
- return viewports;
51512
- }
51513
-
51514
51391
  createTrackScrollbar(browser) {
51515
51392
  const outerScroll = div$1();
51516
51393
  browser.columnContainer.querySelector('.igv-scrollbar-column').appendChild(outerScroll);
@@ -51604,7 +51481,7 @@
51604
51481
  }
51605
51482
 
51606
51483
  addTrackDragMouseHandlers(browser) {
51607
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
51484
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
51608
51485
  let currentDragHandle = undefined; // Mouse Down
51609
51486
 
51610
51487
  this.boundTrackDragMouseDownHandler = trackDragMouseDownHandler.bind(this);
@@ -51668,7 +51545,7 @@
51668
51545
  }
51669
51546
 
51670
51547
  removeTrackDragMouseHandlers() {
51671
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
51548
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
51672
51549
  this.dragHandle.removeEventListener('mousedown', this.boundTrackDragMouseDownHandler);
51673
51550
  document.removeEventListener('mouseup', this.boundDocumentTrackDragMouseUpHandler);
51674
51551
  this.dragHandle.removeEventListener('mouseup', this.boundTrackDragMouseEnterHandler);
@@ -56327,7 +56204,7 @@
56327
56204
  }
56328
56205
 
56329
56206
  this.queryable = false;
56330
- this.featureCache = new FeatureCache(features, genome);
56207
+ this.featureCache = new FeatureCache$1(features, genome);
56331
56208
  } else if (config.reader) {
56332
56209
  // Explicit reader implementation
56333
56210
  this.reader = config.reader;
@@ -56498,13 +56375,13 @@
56498
56375
  } // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
56499
56376
 
56500
56377
 
56501
- this.featureCache = new FeatureCache(features, this.genome, genomicInterval); // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
56378
+ this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval); // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
56502
56379
 
56503
56380
  if (this.config.searchable || this.config.searchableFields) {
56504
56381
  this.addFeaturesToDB(features);
56505
56382
  }
56506
56383
  } else {
56507
- this.featureCache = new FeatureCache([], genomicInterval); // Empty cache
56384
+ this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
56508
56385
  }
56509
56386
  }
56510
56387
 
@@ -58254,7 +58131,7 @@
58254
58131
  return features;
58255
58132
  }
58256
58133
 
58257
- supportsWholeGenome() {
58134
+ get supportsWholeGenome() {
58258
58135
  return true;
58259
58136
  }
58260
58137
 
@@ -58372,30 +58249,6 @@
58372
58249
  }
58373
58250
  }
58374
58251
 
58375
- const pairs = [['A', 'T'], ['G', 'C'], ['Y', 'R'], ['W', 'S'], ['K', 'M'], ['D', 'H'], ['B', 'V']];
58376
- const complements = new Map();
58377
-
58378
- for (let p of pairs) {
58379
- const p1 = p[0];
58380
- const p2 = p[1];
58381
- complements.set(p1, p2);
58382
- complements.set(p2, p1);
58383
- complements.set(p1.toLowerCase(), p2.toLowerCase());
58384
- complements.set(p2.toLowerCase(), p1.toLowerCase());
58385
- }
58386
-
58387
- function reverseComplementSequence(sequence) {
58388
- let comp = '';
58389
- let idx = sequence.length;
58390
-
58391
- while (idx-- > 0) {
58392
- const base = sequence[idx];
58393
- comp += complements.has(base) ? complements.get(base) : base;
58394
- }
58395
-
58396
- return comp;
58397
- }
58398
-
58399
58252
  const GtexUtils = {
58400
58253
  getTissueInfo: function (datasetId, baseURL) {
58401
58254
  datasetId = datasetId || 'gtex_v8';
@@ -58918,7 +58771,7 @@
58918
58771
  return this;
58919
58772
  }
58920
58773
 
58921
- supportsWholeGenome() {
58774
+ get supportsWholeGenome() {
58922
58775
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false;
58923
58776
  }
58924
58777
 
@@ -59071,15 +58924,9 @@
59071
58924
  for (let fd of featureData) {
59072
58925
  data.push(fd);
59073
58926
 
59074
- if (infoURL) {
59075
- if (fd.name && fd.name.toLowerCase() === "name" && fd.value && isString$3(fd.value) && !fd.value.startsWith("<")) {
59076
- const url = this.infoURL || this.config.infoURL;
59077
- const href = url.replace("$$", feature.name);
59078
- data.push({
59079
- name: "Info",
59080
- value: `<a target="_blank" href=${href}>${fd.value}</a>`
59081
- });
59082
- }
58927
+ if (infoURL && fd.name && fd.name.toLowerCase() === "name" && fd.value && isString$3(fd.value) && !fd.value.startsWith("<")) {
58928
+ const href = infoURL.replace("$$", feature.name);
58929
+ fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
59083
58930
  }
59084
58931
  } //Array.prototype.push.apply(data, featureData);
59085
58932
  // If we have clicked over an exon number it.
@@ -59153,17 +59000,31 @@
59153
59000
  }
59154
59001
 
59155
59002
  contextMenuItemList(clickState) {
59156
- if (isSecureContext()) {
59157
- const features = this.clickedFeatures(clickState);
59003
+ const features = this.clickedFeatures(clickState);
59158
59004
 
59159
- if (features.length > 1) {
59160
- features.sort((a, b) => a.end - a.start - (b.end - b.start));
59161
- }
59005
+ if (features.length > 1) {
59006
+ features.sort((a, b) => b.end - b.start - (a.end - a.start));
59007
+ }
59162
59008
 
59163
- const f = features[0]; // The longest feature
59009
+ const f = features[0]; // The shortest clicked feature
59164
59010
 
59165
- if (f.end - f.start <= 1000000) {
59166
- return [{
59011
+ if (f.end - f.start <= 1000000) {
59012
+ const list = [{
59013
+ label: 'View feature sequence',
59014
+ click: async () => {
59015
+ let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
59016
+
59017
+ if (f.strand === '-') {
59018
+ seq = reverseComplementSequence(seq);
59019
+ }
59020
+
59021
+ if (!seq) seq = "Unknown sequence";
59022
+ Alert.presentAlert(seq);
59023
+ }
59024
+ }];
59025
+
59026
+ if (isSecureContext() && navigator.clipboard !== undefined) {
59027
+ list.push({
59167
59028
  label: 'Copy feature sequence',
59168
59029
  click: async () => {
59169
59030
  let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
@@ -59172,14 +59033,21 @@
59172
59033
  seq = reverseComplementSequence(seq);
59173
59034
  }
59174
59035
 
59175
- navigator.clipboard.writeText(seq);
59036
+ try {
59037
+ await navigator.clipboard.writeText(seq);
59038
+ } catch (e) {
59039
+ console.error(e);
59040
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
59041
+ }
59176
59042
  }
59177
- }, '<hr/>'];
59043
+ });
59178
59044
  }
59179
- } // Either not a secure context (i.e. http: protocol), or feature is too long
59180
-
59181
59045
 
59182
- return undefined;
59046
+ list.push('<hr/>');
59047
+ return list;
59048
+ } else {
59049
+ return undefined;
59050
+ }
59183
59051
  }
59184
59052
 
59185
59053
  description() {
@@ -59275,14 +59143,12 @@
59275
59143
  this.featureType = 'numeric';
59276
59144
  this.paintAxis = paintAxis;
59277
59145
  const format = config.format ? config.format.toLowerCase() : config.format;
59146
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
59147
+ this.logScale = config.logScale ? config.logScale : false;
59278
59148
 
59279
59149
  if ("bigwig" === format) {
59280
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
59281
- this.logScale = config.logScale ? config.logScale : false;
59282
59150
  this.featureSource = new BWSource(config, this.browser.genome);
59283
59151
  } else if ("tdf" === format) {
59284
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
59285
- this.logScale = config.logScale ? config.logScale : false;
59286
59152
  this.featureSource = new TDFSource(config, this.browser.genome);
59287
59153
  } else {
59288
59154
  this.featureSource = FeatureSource(config, this.browser.genome);
@@ -59520,7 +59386,7 @@
59520
59386
  }
59521
59387
  }
59522
59388
 
59523
- supportsWholeGenome() {
59389
+ get supportsWholeGenome() {
59524
59390
  return !this.config.indexURL && this.config.supportsWholeGenome !== false;
59525
59391
  }
59526
59392
  /**
@@ -60059,7 +59925,7 @@
60059
59925
 
60060
59926
  const sortHandler = sort => {
60061
59927
  const viewport = clickState.viewport;
60062
- const features = viewport.getCachedFeatures();
59928
+ const features = viewport.cachedFeatures;
60063
59929
  this.sortSamples(sort.chr, sort.start, sort.end, sort.direction, features);
60064
59930
  };
60065
59931
 
@@ -60079,7 +59945,7 @@
60079
59945
  }];
60080
59946
  }
60081
59947
 
60082
- supportsWholeGenome() {
59948
+ get supportsWholeGenome() {
60083
59949
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false;
60084
59950
  }
60085
59951
 
@@ -60165,10 +60031,16 @@
60165
60031
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
60166
60032
  * THE SOFTWARE.
60167
60033
  */
60034
+ /**
60035
+ * Represents 2 or more wig tracks overlaid on a common viewport.
60036
+ */
60168
60037
 
60169
60038
  class MergedTrack extends TrackBase {
60170
60039
  constructor(config, browser) {
60171
60040
  super(config, browser);
60041
+ this.type = "merged";
60042
+ this.featureType = 'numeric';
60043
+ this.paintAxis = paintAxis;
60172
60044
  }
60173
60045
 
60174
60046
  init(config) {
@@ -60179,21 +60051,6 @@
60179
60051
  super.init(config);
60180
60052
  }
60181
60053
 
60182
- get height() {
60183
- return this._height;
60184
- }
60185
-
60186
- set height(h) {
60187
- this._height = h;
60188
-
60189
- if (this.tracks) {
60190
- for (let t of this.tracks) {
60191
- t.height = h;
60192
- t.config.height = h;
60193
- }
60194
- }
60195
- }
60196
-
60197
60054
  async postInit() {
60198
60055
  this.tracks = [];
60199
60056
  const p = [];
@@ -60215,45 +60072,83 @@
60215
60072
  }
60216
60073
  }
60217
60074
 
60218
- this.height = this.config.height || 100;
60075
+ this.flipAxis = this.config.flipAxis ? this.config.flipAxis : false;
60076
+ this.logScale = this.config.logScale ? this.config.logScale : false;
60077
+ this.autoscale = this.config.autoscale || this.config.max === undefined;
60078
+
60079
+ if (!this.autoscale) {
60080
+ this.dataRange = {
60081
+ min: this.config.min || 0,
60082
+ max: this.config.max
60083
+ };
60084
+ }
60085
+
60086
+ for (let t of this.tracks) {
60087
+ t.autoscale = false;
60088
+ t.dataRange = this.dataRange;
60089
+ }
60090
+
60091
+ this.height = this.config.height || 50;
60219
60092
  return Promise.all(p);
60220
60093
  }
60221
60094
 
60095
+ get height() {
60096
+ return this._height;
60097
+ }
60098
+
60099
+ set height(h) {
60100
+ this._height = h;
60101
+
60102
+ if (this.tracks) {
60103
+ for (let t of this.tracks) {
60104
+ t.height = h;
60105
+ t.config.height = h;
60106
+ }
60107
+ }
60108
+ }
60109
+
60110
+ menuItemList() {
60111
+ let items = [];
60112
+
60113
+ if (this.flipAxis !== undefined) {
60114
+ items.push({
60115
+ label: "Flip y-axis",
60116
+ click: () => {
60117
+ this.flipAxis = !this.flipAxis;
60118
+ this.trackView.repaintViews();
60119
+ }
60120
+ });
60121
+ }
60122
+
60123
+ items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
60124
+ return items;
60125
+ }
60126
+
60222
60127
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
60223
60128
  const promises = this.tracks.map(t => t.getFeatures(chr, bpStart, bpEnd, bpPerPixel));
60224
60129
  return Promise.all(promises);
60225
60130
  }
60226
60131
 
60227
60132
  draw(options) {
60228
- var i, len, mergedFeatures, trackOptions, dataRange;
60229
- mergedFeatures = options.features; // Array of feature arrays, 1 for each track
60133
+ const mergedFeatures = options.features; // Array of feature arrays, 1 for each track
60230
60134
 
60231
- dataRange = autoscale(options.referenceFrame.chr, mergedFeatures); //IGVGraphics.fillRect(options.context, 0, options.pixelTop, options.pixelWidth, options.pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
60135
+ if (this.autoscale) {
60136
+ this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
60137
+ }
60232
60138
 
60233
- for (i = 0, len = this.tracks.length; i < len; i++) {
60234
- trackOptions = Object.assign({}, options);
60139
+ for (let i = 0, len = this.tracks.length; i < len; i++) {
60140
+ const trackOptions = Object.assign({}, options);
60235
60141
  trackOptions.features = mergedFeatures[i];
60236
- this.tracks[i].dataRange = dataRange;
60142
+ this.tracks[i].dataRange = this.dataRange;
60143
+ this.tracks[i].flipAxis = this.flipAxis;
60144
+ this.tracks[i].logScale = this.logScale;
60145
+ this.tracks[i].graphType = this.graphType;
60237
60146
  this.tracks[i].draw(trackOptions);
60238
60147
  }
60239
60148
  }
60240
60149
 
60241
- paintAxis(ctx, pixelWidth, pixelHeight) {
60242
- var i, len, autoscale, track;
60243
- autoscale = true; // Hardcoded for now
60244
-
60245
- for (i = 0, len = this.tracks.length; i < len; i++) {
60246
- track = this.tracks[i];
60247
-
60248
- if (typeof track.paintAxis === 'function') {
60249
- track.paintAxis(ctx, pixelWidth, pixelHeight);
60250
- if (autoscale) break;
60251
- }
60252
- }
60253
- }
60254
-
60255
60150
  popupData(clickState, features) {
60256
- const featuresArray = features || clickState.viewport.getCachedFeatures();
60151
+ const featuresArray = features || clickState.viewport.cachedFeatures;
60257
60152
 
60258
60153
  if (featuresArray && featuresArray.length === this.tracks.length) {
60259
60154
  // Array of feature arrays, 1 for each track
@@ -60270,40 +60165,24 @@
60270
60165
  }
60271
60166
  }
60272
60167
 
60273
- supportsWholeGenome() {
60274
- const b = this.tracks.every(track => track.supportsWholeGenome());
60275
- return b;
60168
+ get supportsWholeGenome() {
60169
+ return this.tracks.every(track => track.supportsWholeGenome());
60276
60170
  }
60277
60171
 
60278
60172
  }
60279
60173
 
60280
60174
  function autoscale(chr, featureArrays) {
60281
- var min = 0,
60282
- max = -Number.MAX_VALUE;
60283
- // if (chr === 'all') {
60284
- // allValues = [];
60285
- // featureArrays.forEach(function (features) {
60286
- // features.forEach(function (f) {
60287
- // if (!Number.isNaN(f.value)) {
60288
- // allValues.push(f.value);
60289
- // }
60290
- // });
60291
- // });
60292
- //
60293
- // min = Math.min(0, IGVMath.percentile(allValues, .1));
60294
- // max = IGVMath.percentile(allValues, 99.9);
60295
- //
60296
- // }
60297
- // else {
60175
+ let min = 0;
60176
+ let max = -Number.MAX_VALUE;
60298
60177
 
60299
- featureArrays.forEach(function (features, i) {
60300
- features.forEach(function (f) {
60178
+ for (let features of featureArrays) {
60179
+ for (let f of features) {
60301
60180
  if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
60302
60181
  min = Math.min(min, f.value);
60303
60182
  max = Math.max(max, f.value);
60304
60183
  }
60305
- });
60306
- }); // }
60184
+ }
60185
+ }
60307
60186
 
60308
60187
  return {
60309
60188
  min: min,
@@ -60424,7 +60303,7 @@
60424
60303
  return this;
60425
60304
  }
60426
60305
 
60427
- supportsWholeGenome() {
60306
+ get supportsWholeGenome() {
60428
60307
  return true;
60429
60308
  }
60430
60309
 
@@ -60845,7 +60724,7 @@
60845
60724
  items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
60846
60725
  }
60847
60726
 
60848
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
60727
+ if (this.browser.circularView) {
60849
60728
  items.push('<hr/>');
60850
60729
  items.push({
60851
60730
  label: 'Add interactions to circular view',
@@ -60862,7 +60741,7 @@
60862
60741
 
60863
60742
  contextMenuItemList(clickState) {
60864
60743
  // Experimental JBrowse feature
60865
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
60744
+ if (this.browser.circularView) {
60866
60745
  const viewport = clickState.viewport;
60867
60746
  const list = [];
60868
60747
  list.push({
@@ -60887,20 +60766,20 @@
60887
60766
  const cachedFeatures = "all" === refFrame.chr ? this.featureSource.getAllFeatures() : this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end); // inView features are simply features that have been drawn, i.e. have a drawState
60888
60767
 
60889
60768
  const inView = cachedFeatures.filter(f => f.drawState);
60890
- if (inView.length === 0) erturn;
60891
- this.browser.circularViewVisible = true;
60892
- const chords = makeBedPEChords(inView); // for filtered set, distinguishing the chromosomes is more critical than tracks
60893
-
60894
- const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5);
60895
- const trackColor = IGVColor.addAlpha(this.color, 0.5); // name the chord set to include filtering information
60896
-
60897
- const encodedName = this.name.replaceAll(' ', '%20');
60898
- const chordSetName = "all" === refFrame.chr ? encodedName : `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`;
60899
- this.browser.circularView.addChords(chords, {
60900
- track: chordSetName,
60901
- color: chordSetColor,
60902
- trackColor: trackColor
60903
- });
60769
+ if (inView.length === 0) return;
60770
+ const chords = makeBedPEChords(inView);
60771
+ sendChords(chords, this, refFrame, 0.5); //
60772
+ //
60773
+ // // for filtered set, distinguishing the chromosomes is more critical than tracks
60774
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5)
60775
+ // const trackColor = IGVColor.addAlpha(this.color, 0.5)
60776
+ //
60777
+ // // name the chord set to include locus and filtering information
60778
+ // const encodedName = this.name.replaceAll(' ', '%20')
60779
+ // const chordSetName = "all" === refFrame.chr ?
60780
+ // encodedName :
60781
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`
60782
+ // this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor})
60904
60783
  }
60905
60784
 
60906
60785
  doAutoscale(features) {
@@ -60987,7 +60866,7 @@
60987
60866
  clickedFeatures(clickState, features) {
60988
60867
  // We use the cached features rather than method to avoid async load. If the
60989
60868
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
60990
- const featureList = features || clickState.viewport.getCachedFeatures();
60869
+ const featureList = features || clickState.viewport.cachedFeatures;
60991
60870
  const candidates = [];
60992
60871
 
60993
60872
  if (featureList) {
@@ -61358,7 +61237,7 @@
61358
61237
  return this;
61359
61238
  }
61360
61239
 
61361
- supportsWholeGenome() {
61240
+ get supportsWholeGenome() {
61362
61241
  return this.config.indexed === false || this.config.supportsWholeGenome === true;
61363
61242
  }
61364
61243
 
@@ -61573,7 +61452,7 @@
61573
61452
  variantColor = "gray";
61574
61453
  }
61575
61454
  } else if (this._color) {
61576
- variantColor = typeof this._color === "function" ? this._color(v) : this._color;
61455
+ variantColor = this.color;
61577
61456
  } else if ("NONVARIANT" === v.type) {
61578
61457
  variantColor = this.nonRefColor;
61579
61458
  } else if ("MIXED" === v.type) {
@@ -61585,6 +61464,10 @@
61585
61464
  return variantColor;
61586
61465
  }
61587
61466
 
61467
+ get color() {
61468
+ return this._color ? typeof this._color === "function" ? this._color(v) : this._color : this.defaultColor;
61469
+ }
61470
+
61588
61471
  clickedFeatures(clickState, features) {
61589
61472
  let featureList = super.clickedFeatures(clickState, features);
61590
61473
  const vGap = this.displayMode === 'EXPANDED' ? this.expandedVGap : this.squishedVGap;
@@ -61845,29 +61728,15 @@
61845
61728
  } // Experimental JBrowse circular view integration
61846
61729
 
61847
61730
 
61848
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
61731
+ if (this.browser.circularView) {
61849
61732
  menuItems.push('<hr>');
61850
61733
  menuItems.push({
61851
61734
  label: 'Add SVs to circular view',
61852
61735
  click: () => {
61853
- const inView = [];
61854
61736
 
61855
61737
  for (let viewport of this.trackView.viewports) {
61856
- const refFrame = viewport.referenceFrame;
61857
-
61858
- for (let f of viewport.getCachedFeatures()) {
61859
- if (f.end >= refFrame.start && f.start <= refFrame.end) {
61860
- inView.push(f);
61861
- }
61862
- }
61738
+ this.sendChordsForViewport(viewport);
61863
61739
  }
61864
-
61865
- const chords = makeVCFChords(inView);
61866
- const color = IGVColor.addAlpha(this._color || this.defaultColor, 0.5);
61867
- this.browser.circularView.addChords(chords, {
61868
- track: this.name,
61869
- color: color
61870
- });
61871
61740
  }
61872
61741
  });
61873
61742
  }
@@ -61877,26 +61746,26 @@
61877
61746
 
61878
61747
  contextMenuItemList(clickState) {
61879
61748
  // Experimental JBrowse circular view integration
61880
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
61749
+ if (this.browser.circularView) {
61881
61750
  const viewport = clickState.viewport;
61882
61751
  const list = [];
61883
61752
  list.push({
61884
61753
  label: 'Add SVs to Circular View',
61885
61754
  click: () => {
61886
- const refFrame = viewport.referenceFrame;
61887
- const inView = "all" === refFrame.chr ? this.featureSource.getAllFeatures() : this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
61888
- const chords = makeVCFChords(inView);
61889
- const color = IGVColor.addAlpha(this._color || this.defaultColor, 0.5);
61890
- this.browser.circularView.addChords(chords, {
61891
- track: this.name,
61892
- color: color
61893
- });
61755
+ this.sendChordsForViewport(viewport);
61894
61756
  }
61895
61757
  });
61896
61758
  list.push('<hr/>');
61897
61759
  return list;
61898
61760
  }
61899
61761
  }
61762
+
61763
+ sendChordsForViewport(viewport) {
61764
+ const refFrame = viewport.referenceFrame;
61765
+ const inView = "all" === refFrame.chr ? this.featureSource.getAllFeatures() : this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
61766
+ const chords = makeVCFChords(inView);
61767
+ sendChords(chords, this, refFrame, 0.5);
61768
+ }
61900
61769
  /**
61901
61770
  * Create a "color by" checkbox menu item, optionally initially checked
61902
61771
  * @param menuItem
@@ -62199,7 +62068,7 @@
62199
62068
 
62200
62069
 
62201
62070
  popupData(clickState) {
62202
- let features = clickState.viewport.getCachedFeatures();
62071
+ let features = clickState.viewport.cachedFeatures;
62203
62072
  if (!features || features.length === 0) return [];
62204
62073
  const tolerance = 3;
62205
62074
  const tissue = this.name;
@@ -62407,7 +62276,7 @@
62407
62276
  return this;
62408
62277
  }
62409
62278
 
62410
- supportsWholeGenome() {
62279
+ get supportsWholeGenome() {
62411
62280
  return true;
62412
62281
  }
62413
62282
 
@@ -62545,7 +62414,7 @@
62545
62414
  popupData(clickState) {
62546
62415
  let data = [];
62547
62416
  const track = clickState.viewport.trackView.track;
62548
- const features = clickState.viewport.getCachedFeatures();
62417
+ const features = clickState.viewport.cachedFeatures;
62549
62418
 
62550
62419
  if (features) {
62551
62420
  let count = 0;
@@ -62973,7 +62842,7 @@
62973
62842
  return items;
62974
62843
  }
62975
62844
 
62976
- supportsWholeGenome() {
62845
+ get supportsWholeGenome() {
62977
62846
  return false;
62978
62847
  }
62979
62848
 
@@ -63234,7 +63103,7 @@
63234
63103
  if (!this.featureCache) {
63235
63104
  const options = buildOptions(this.config);
63236
63105
  const data = await igvxhr.loadString(this.config.url, options);
63237
- this.featureCache = new FeatureCache(parseBP(data), genome);
63106
+ this.featureCache = new FeatureCache$1(parseBP(data), genome);
63238
63107
  return this.featureCache.queryFeatures(chr, start, end);
63239
63108
  } else {
63240
63109
  return this.featureCache.queryFeatures(chr, start, end);
@@ -63334,12 +63203,15 @@
63334
63203
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
63335
63204
  * THE SOFTWARE.
63336
63205
  */
63206
+ /**
63207
+ * Class represents an ideogram of a chromsome cytobands. It is used for the header of a track panel.
63208
+ *
63209
+ */
63337
63210
 
63338
63211
  class IdeogramTrack {
63339
63212
  constructor(browser) {
63340
63213
  this.browser = browser;
63341
63214
  this.type = 'ideogram';
63342
- this.id = this.type;
63343
63215
  this.height = 16;
63344
63216
  this.order = Number.MIN_SAFE_INTEGER;
63345
63217
  this.disableButtons = true;
@@ -63599,7 +63471,7 @@
63599
63471
  return this;
63600
63472
  }
63601
63473
 
63602
- supportsWholeGenome() {
63474
+ get supportsWholeGenome() {
63603
63475
  return false;
63604
63476
  }
63605
63477
 
@@ -64477,6 +64349,15 @@
64477
64349
  this.id = guid$2();
64478
64350
  }
64479
64351
 
64352
+ extend(locus) {
64353
+ const newStart = Math.min(locus.start, this.start);
64354
+ const newEnd = Math.max(locus.end, this.end);
64355
+ const ratio = (newEnd - newStart) / (this.end - this.start);
64356
+ this.start = newStart;
64357
+ this.end = newEnd;
64358
+ this.bpPerPixel *= ratio;
64359
+ }
64360
+
64480
64361
  calculateEnd(pixels) {
64481
64362
  return this.start + this.bpPerPixel * pixels;
64482
64363
  }
@@ -64561,7 +64442,7 @@
64561
64442
  const viewChanged = start !== this.start || bpPerPixel !== this.bpPerPixel;
64562
64443
 
64563
64444
  if (viewChanged) {
64564
- await browser.updateViews(this);
64445
+ await browser.updateViews(true);
64565
64446
  }
64566
64447
  }
64567
64448
 
@@ -64631,23 +64512,6 @@
64631
64512
  });
64632
64513
  }
64633
64514
 
64634
- function adjustReferenceFrame(scaleFactor, referenceFrame, viewportWidth, alignmentStart, alignmentLength) {
64635
- referenceFrame.bpPerPixel *= scaleFactor;
64636
- const alignmentEE = alignmentStart + alignmentLength;
64637
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
64638
- referenceFrame.start = alignmentCC - referenceFrame.bpPerPixel * (viewportWidth / 2);
64639
- referenceFrame.end = referenceFrame.start + referenceFrame.bpPerPixel * viewportWidth;
64640
- referenceFrame.locusSearchString = referenceFrame.getLocusString();
64641
- }
64642
-
64643
- function createReferenceFrameWithAlignment(genome, chromosomeName, bpp, viewportWidth, alignmentStart, alignmentLength) {
64644
- const alignmentEE = alignmentStart + alignmentLength;
64645
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
64646
- const ss = alignmentCC - bpp * (viewportWidth / 2);
64647
- const ee = ss + bpp * viewportWidth;
64648
- return new ReferenceFrame(genome, chromosomeName, ss, ee, bpp);
64649
- }
64650
-
64651
64515
  const defaultNucleotideColors = {
64652
64516
  "A": "rgb( 0, 200, 0)",
64653
64517
  "C": "rgb( 0,0,200)",
@@ -65694,6 +65558,65 @@
65694
65558
  button.addEventListener('click', () => browser.saveSVGtoFile({}));
65695
65559
  };
65696
65560
 
65561
+ const viewportColumnManager = {
65562
+ createColumns: (columnContainer, count) => {
65563
+ for (let i = 0; i < count; i++) {
65564
+ if (0 === i) {
65565
+ createColumn(columnContainer, 'igv-column');
65566
+ } else {
65567
+ columnContainer.appendChild(div$1({
65568
+ class: 'igv-column-shim'
65569
+ }));
65570
+ createColumn(columnContainer, 'igv-column');
65571
+ }
65572
+ }
65573
+ },
65574
+ removeColumnAtIndex: (i, column) => {
65575
+ const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
65576
+ column.remove();
65577
+ shim.remove();
65578
+ },
65579
+ insertAfter: referenceElement => {
65580
+ const shim = div$1({
65581
+ class: 'igv-column-shim'
65582
+ });
65583
+ insertElementAfter(shim, referenceElement);
65584
+ const column = div$1({
65585
+ class: 'igv-column'
65586
+ });
65587
+ insertElementAfter(column, shim);
65588
+ return column;
65589
+ },
65590
+ insertBefore: (referenceElement, count) => {
65591
+ for (let i = 0; i < count; i++) {
65592
+ const column = div$1({
65593
+ class: 'igv-column'
65594
+ });
65595
+ insertElementBefore(column, referenceElement);
65596
+
65597
+ if (count > 1 && i > 0) {
65598
+ const columnShim = div$1({
65599
+ class: 'igv-column-shim'
65600
+ });
65601
+ insertElementBefore(columnShim, column);
65602
+ }
65603
+ }
65604
+ },
65605
+ indexOfColumn: (columnContainer, column) => {
65606
+ const allColumns = columnContainer.querySelectorAll('.igv-column');
65607
+
65608
+ for (let i = 0; i < allColumns.length; i++) {
65609
+ const c = allColumns[i];
65610
+
65611
+ if (c === column) {
65612
+ return i;
65613
+ }
65614
+ }
65615
+
65616
+ return undefined;
65617
+ }
65618
+ };
65619
+
65697
65620
  /*
65698
65621
  * The MIT License (MIT)
65699
65622
  *
@@ -65944,7 +65867,7 @@
65944
65867
  }
65945
65868
  }
65946
65869
 
65947
- supportsWholeGenome() {
65870
+ get supportsWholeGenome() {
65948
65871
  return true;
65949
65872
  }
65950
65873
 
@@ -66476,7 +66399,9 @@
66476
66399
  // deferred because ideogram and ruler are treated as "tracks", and tracks require a reference frame
66477
66400
 
66478
66401
  if (false !== session.showIdeogram) {
66479
- this.trackViews.push(new TrackView(this, this.columnContainer, new IdeogramTrack(this)));
66402
+ const ideogramTrack = new IdeogramTrack(this);
66403
+ ideogramTrack.id = 'ideogram';
66404
+ this.trackViews.push(new TrackView(this, this.columnContainer, ideogramTrack));
66480
66405
  }
66481
66406
 
66482
66407
  if (false !== session.showRuler) {
@@ -66526,7 +66451,12 @@
66526
66451
  }
66527
66452
  }
66528
66453
 
66529
- await this.loadTrackList(trackConfigurations);
66454
+ await this.loadTrackList(trackConfigurations); // The ruler and ideogram tracks are not explicitly loaded, but needs updated nonetheless.
66455
+
66456
+ for (let rtv of this.trackViews.filter(tv => tv.track.type === 'ruler' || tv.track.type === 'ideogram')) {
66457
+ rtv.updateViews();
66458
+ }
66459
+
66530
66460
  this.updateUIWithReferenceFrameList();
66531
66461
  }
66532
66462
 
@@ -66691,26 +66621,22 @@
66691
66621
  }
66692
66622
 
66693
66623
  async loadTrackList(configList) {
66694
- try {
66695
- const promises = [];
66696
-
66697
- for (let config of configList) {
66698
- promises.push(this.loadTrack(config, false));
66699
- }
66624
+ const promises = [];
66700
66625
 
66701
- const loadedTracks = await Promise.all(promises);
66702
- const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
66703
- return trackView.track.autoscaleGroup;
66704
- });
66626
+ for (let config of configList) {
66627
+ promises.push(this.loadTrack(config));
66628
+ }
66705
66629
 
66706
- if (groupAutoscaleViews.length > 0) {
66707
- this.updateViews(groupAutoscaleViews);
66708
- }
66630
+ const loadedTracks = await Promise.all(promises);
66631
+ const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
66632
+ return trackView.track.autoscaleGroup;
66633
+ });
66709
66634
 
66710
- return loadedTracks;
66711
- } finally {
66712
- await this.resize();
66635
+ if (groupAutoscaleViews.length > 0) {
66636
+ this.updateViews();
66713
66637
  }
66638
+
66639
+ return loadedTracks;
66714
66640
  }
66715
66641
 
66716
66642
  async loadROI(config) {
@@ -66724,7 +66650,9 @@
66724
66650
  }
66725
66651
  } else {
66726
66652
  this.roi.push(new ROI(config, this.genome));
66727
- }
66653
+ } // Force reload all views (force = true) to insure ROI features are loaded. Wasteful but this function is
66654
+ // rarely called.
66655
+
66728
66656
 
66729
66657
  await this.updateViews(true);
66730
66658
  }
@@ -66738,7 +66666,7 @@
66738
66666
  }
66739
66667
 
66740
66668
  for (let tv of this.trackViews) {
66741
- tv.updateViews(true);
66669
+ tv.repaintViews();
66742
66670
  }
66743
66671
  }
66744
66672
 
@@ -66746,7 +66674,7 @@
66746
66674
  this.roi = [];
66747
66675
 
66748
66676
  for (let tv of this.trackViews) {
66749
- tv.updateViews(true);
66677
+ tv.repaintViews();
66750
66678
  }
66751
66679
  }
66752
66680
 
@@ -66762,25 +66690,13 @@
66762
66690
  /**
66763
66691
  * Return a promise to load a track.
66764
66692
  *
66765
- * Each track is associated with the following DOM elements
66766
- *
66767
- * leftHandGutter - div on the left for track controls and legend
66768
- * contentDiv - a div element wrapping all the track content. Height can be > viewportDiv height
66769
- * viewportDiv - a div element through which the track is viewed. This might have a vertical scrollbar
66770
- * canvas - canvas element upon which the track is drawn. Child of contentDiv
66771
- *
66772
- * The width of all elements should be equal. Height of the viewportDiv is controlled by the user, but never
66773
- * greater than the contentDiv height. Height of contentDiv and canvas are equal, and governed by the data
66774
- * loaded.
66775
- *
66776
- *
66777
66693
  * @param config
66778
66694
  * @param doResize - undefined by default
66779
66695
  * @returns {*}
66780
66696
  */
66781
66697
 
66782
66698
 
66783
- async loadTrack(config, doResize) {
66699
+ async loadTrack(config) {
66784
66700
  // config might be json
66785
66701
  if (isString$3(config)) {
66786
66702
  config = JSON.parse(config);
@@ -66845,11 +66761,6 @@
66845
66761
 
66846
66762
  msg += ": " + config.url;
66847
66763
  Alert.presentAlert(new Error(msg), undefined);
66848
- } finally {
66849
- // TODO: If loadTrack() is called individually - not via loadTrackList() - call this.resize()
66850
- if (false === doResize) ; else {
66851
- await this.resize();
66852
- }
66853
66764
  }
66854
66765
  }
66855
66766
  /**
@@ -67082,43 +66993,11 @@
67082
66993
  this.navbarManager.navbarDidResize(this.$navigation.width(), isWGV);
67083
66994
  }
67084
66995
 
67085
- await this.resize();
67086
- }
67087
-
67088
- async resize() {
67089
- const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
67090
-
67091
- for (let referenceFrame of this.referenceFrameList) {
67092
- const index = this.referenceFrameList.indexOf(referenceFrame);
67093
- const {
67094
- chr,
67095
- genome
67096
- } = referenceFrame;
67097
- const {
67098
- bpLength
67099
- } = genome.getChromosome(referenceFrame.chr);
67100
- const viewportWidthBP = referenceFrame.toBP(viewportWidth); // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
67101
-
67102
- if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
67103
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
67104
- referenceFrame.bpPerPixel = bpLength / viewportWidth;
67105
- } else {
67106
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
67107
- referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
67108
- }
67109
-
67110
- for (let {
67111
- viewports
67112
- } of this.trackViews) {
67113
- viewports[index].setWidth(viewportWidth);
67114
- }
67115
- }
67116
-
67117
- await this.updateViews(true);
67118
- this.updateUIWithReferenceFrameList();
66996
+ resize.call(this);
66997
+ await this.updateViews();
67119
66998
  }
67120
66999
 
67121
- async updateViews(force) {
67000
+ async updateViews() {
67122
67001
  const trackViews = this.trackViews;
67123
67002
  this.updateLocusSearchWidget();
67124
67003
 
@@ -67129,7 +67008,7 @@
67129
67008
 
67130
67009
  if (this.dragObject) {
67131
67010
  for (let trackView of trackViews) {
67132
- await trackView.updateViews(force);
67011
+ await trackView.updateViews();
67133
67012
  }
67134
67013
  } else {
67135
67014
  // Group autoscale
@@ -67178,19 +67057,25 @@
67178
67057
  for (let trackView of groupTrackViews) {
67179
67058
  trackView.track.dataRange = dataRange;
67180
67059
  trackView.track.autoscale = false;
67181
- p.push(trackView.updateViews(force));
67060
+ p.push(trackView.updateViews());
67182
67061
  }
67183
67062
 
67184
67063
  await Promise.all(p);
67185
67064
  }
67186
67065
  }
67187
67066
 
67188
- await Promise.all(otherTracks.map(tv => tv.updateViews(force))); // for (let trackView of otherTracks) {
67067
+ await Promise.all(otherTracks.map(tv => tv.updateViews())); // for (let trackView of otherTracks) {
67189
67068
  // await trackView.updateViews(force);
67190
67069
  // }
67191
67070
  }
67192
67071
  }
67193
67072
 
67073
+ repaintViews() {
67074
+ for (let trackView of this.trackViews) {
67075
+ trackView.repaintViews();
67076
+ }
67077
+ }
67078
+
67194
67079
  updateLocusSearchWidget() {
67195
67080
  const referenceFrameList = this.referenceFrameList; // Update end position of reference frames based on pixel widths. This is hacky, but its been done here
67196
67081
  // for a long time, although indirectly.
@@ -67248,41 +67133,53 @@
67248
67133
  referenceFrame.zoomWithScaleFactor(this, scaleFactor, viewportWidth, centerBPOrUndefined);
67249
67134
  }
67250
67135
  }
67136
+ /**
67137
+ * Add a new multi-locus panel for the specified region
67138
+ * @param chr
67139
+ * @param start
67140
+ * @param end
67141
+ * @param referenceFrameLeft - optional, if supplied new panel should be placed to the immediate right
67142
+ */
67143
+
67251
67144
 
67252
- async presentMultiLocusPanel(alignment, referenceFrameLeft) {
67145
+ async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
67253
67146
  // account for reduced viewport width as a result of adding right mate pair panel
67254
67147
  const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
67255
67148
  const scaleFactor = this.calculateViewportWidth(this.referenceFrameList.length) / this.calculateViewportWidth(1 + this.referenceFrameList.length);
67256
- adjustReferenceFrame(scaleFactor, referenceFrameLeft, viewportWidth, alignment.start, alignment.lengthOnRef); // create right mate pair reference frame
67257
67149
 
67258
- const mateChrName = this.genome.getChromosomeName(alignment.mate.chr);
67259
- const referenceFrameRight = createReferenceFrameWithAlignment(this.genome, mateChrName, referenceFrameLeft.bpPerPixel, viewportWidth, alignment.mate.position, alignment.lengthOnRef); // add right mate panel beside left mate panel
67150
+ for (let refFrame of this.referenceFrameList) {
67151
+ refFrame.bpPerPixel *= scaleFactor;
67152
+ }
67153
+
67154
+ const bpp = (end - start) / viewportWidth;
67155
+ const newReferenceFrame = new ReferenceFrame(this.genome, chr, start, end, bpp);
67156
+ const indexLeft = referenceFrameLeft ? this.referenceFrameList.indexOf(referenceFrameLeft) : this.referenceFrameList.length - 1;
67157
+ const indexRight = 1 + indexLeft; // TODO -- this is really ugly
67260
67158
 
67261
- const indexLeft = this.referenceFrameList.indexOf(referenceFrameLeft);
67262
- const indexRight = 1 + this.referenceFrameList.indexOf(referenceFrameLeft);
67263
67159
  const {
67264
67160
  $viewport
67265
67161
  } = this.trackViews[0].viewports[indexLeft];
67266
67162
  const viewportColumn = viewportColumnManager.insertAfter($viewport.get(0).parentElement);
67267
67163
 
67268
67164
  if (indexRight === this.referenceFrameList.length) {
67269
- this.referenceFrameList.push(referenceFrameRight);
67165
+ this.referenceFrameList.push(newReferenceFrame);
67270
67166
 
67271
67167
  for (let trackView of this.trackViews) {
67272
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
67168
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
67273
67169
  trackView.viewports.push(viewport);
67274
67170
  }
67275
67171
  } else {
67276
- this.referenceFrameList.splice(indexRight, 0, referenceFrameRight);
67172
+ this.referenceFrameList.splice(indexRight, 0, newReferenceFrame);
67277
67173
 
67278
67174
  for (let trackView of this.trackViews) {
67279
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
67175
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
67280
67176
  trackView.viewports.splice(indexRight, 0, viewport);
67281
67177
  }
67282
67178
  }
67283
67179
 
67284
67180
  this.centerLineList = this.createCenterLineList(this.columnContainer);
67285
- await this.resize();
67181
+ resize.call(this);
67182
+ await this.updateViews(true);
67286
67183
  }
67287
67184
 
67288
67185
  async removeMultiLocusPanel(referenceFrame) {
@@ -67311,8 +67208,15 @@
67311
67208
  const scaleFactor = this.calculateViewportWidth(1 + this.referenceFrameList.length) / this.calculateViewportWidth(this.referenceFrameList.length);
67312
67209
  await this.rescaleForMultiLocus(scaleFactor);
67313
67210
  }
67211
+ /**
67212
+ * Goto the locus represented by the selected referenceFrame, discarding all other panels
67213
+ *
67214
+ * @param referenceFrame
67215
+ * @returns {Promise<void>}
67216
+ */
67217
+
67314
67218
 
67315
- async selectMultiLocusPanel(referenceFrame) {
67219
+ async gotoMultilocusPanel(referenceFrame) {
67316
67220
  const referenceFrameIndex = this.referenceFrameList.indexOf(referenceFrame); // Remove columns for unselected panels
67317
67221
 
67318
67222
  this.columnContainer.querySelectorAll('.igv-column').forEach((column, c) => {
@@ -67360,7 +67264,7 @@
67360
67264
 
67361
67265
  this.centerLineList = this.createCenterLineList(this.columnContainer);
67362
67266
  this.updateUIWithReferenceFrameList();
67363
- await this.updateViews(true);
67267
+ await this.updateViews();
67364
67268
  }
67365
67269
  /**
67366
67270
  * @deprecated This is a deprecated method with no known usages. To be removed in a future release.
@@ -67613,20 +67517,6 @@
67613
67517
  const surl = (idx > 0 ? path.substring(0, idx) : path) + "?sessionURL=blob:" + this.compressedSession();
67614
67518
  return surl;
67615
67519
  }
67616
-
67617
- currentLoci() {
67618
- const loci = [];
67619
- const anyTrackView = this.trackViews[0];
67620
-
67621
- for (let {
67622
- referenceFrame
67623
- } of anyTrackView.viewports) {
67624
- const locusString = referenceFrame.getLocusString();
67625
- loci.push(locusString);
67626
- }
67627
-
67628
- return loci;
67629
- }
67630
67520
  /**
67631
67521
  * Record a mouse click on a specific viewport. This might be the start of a drag operation. Dragging
67632
67522
  * (panning) is handled here so that the mouse can move out of a specific viewport (e.g. stray into another
@@ -67661,10 +67551,22 @@
67661
67551
  this.fireEvent('trackdragend');
67662
67552
  }
67663
67553
  }
67554
+ /**
67555
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
67556
+ *
67557
+ * @param trackView
67558
+ */
67559
+
67664
67560
 
67665
67561
  startTrackDrag(trackView) {
67666
67562
  this.dragTrack = trackView;
67667
67563
  }
67564
+ /**
67565
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
67566
+ *
67567
+ * @param dragDestination
67568
+ */
67569
+
67668
67570
 
67669
67571
  updateTrackDrag(dragDestination) {
67670
67572
  if (dragDestination && this.dragTrack) {
@@ -67738,12 +67640,9 @@
67738
67640
  }
67739
67641
 
67740
67642
  addWindowResizeHandler() {
67741
- this.boundWindowResizeHandler = windowResizeHandler.bind(this);
67643
+ // Create a copy of the prototype "resize" function bound to this instance. Neccessary to support removing.
67644
+ this.boundWindowResizeHandler = resize.bind(this);
67742
67645
  window.addEventListener('resize', this.boundWindowResizeHandler);
67743
-
67744
- function windowResizeHandler() {
67745
- this.resize();
67746
- }
67747
67646
  }
67748
67647
 
67749
67648
  removeWindowResizeHandler() {
@@ -67822,7 +67721,7 @@
67822
67721
  id: this.genome.id,
67823
67722
  chromosomes: makeCircViewChromosomes(this.genome)
67824
67723
  });
67825
- this.circularViewVisible = show === true;
67724
+ this.circularViewVisible = show;
67826
67725
  }
67827
67726
 
67828
67727
  get circularViewVisible() {
@@ -67837,6 +67736,48 @@
67837
67736
  }
67838
67737
 
67839
67738
  }
67739
+ /**
67740
+ * Function called win window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
67741
+ * than class method because it needs to be copied and bound to specific instances of browser to support listener
67742
+ * removal
67743
+ *
67744
+ * @returns {Promise<void>}
67745
+ */
67746
+
67747
+
67748
+ async function resize() {
67749
+ const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
67750
+
67751
+ for (let referenceFrame of this.referenceFrameList) {
67752
+ const index = this.referenceFrameList.indexOf(referenceFrame);
67753
+ const {
67754
+ chr,
67755
+ genome
67756
+ } = referenceFrame;
67757
+ const {
67758
+ bpLength
67759
+ } = genome.getChromosome(referenceFrame.chr);
67760
+ const viewportWidthBP = referenceFrame.toBP(viewportWidth); // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
67761
+
67762
+ if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
67763
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
67764
+ referenceFrame.bpPerPixel = bpLength / viewportWidth;
67765
+ } else {
67766
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
67767
+ referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
67768
+ }
67769
+
67770
+ for (let {
67771
+ viewports
67772
+ } of this.trackViews) {
67773
+ viewports[index].setWidth(viewportWidth);
67774
+ }
67775
+ }
67776
+
67777
+ this.updateUIWithReferenceFrameList(); //TODO -- update view only if needed. Reducing size never needed. Increasing size maybe
67778
+
67779
+ await this.updateViews(true);
67780
+ }
67840
67781
 
67841
67782
  function handleMouseMove(e) {
67842
67783
  e.preventDefault();