igv 2.11.2 → 2.12.2

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);
@@ -19911,7 +19911,7 @@
19911
19911
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19912
19912
  * THE SOFTWARE.
19913
19913
  */
19914
- const knownFileExtensions = new Set(["narrowpeak", "broadpeak", "regionpeak", "peaks", "bedgraph", "wig", "gff3", "gff", "gtf", "fusionjuncspan", "refflat", "seg", "aed", "bed", "vcf", "bb", "bigbed", "biginteract", "bw", "bigwig", "bam", "tdf", "refgene", "genepred", "genepredext", "bedpe", "bp", "snp", "rmsk", "cram", "gwas", "maf", "mut"]);
19914
+ const knownFileExtensions = new Set(["narrowpeak", "broadpeak", "regionpeak", "peaks", "bedgraph", "wig", "gff3", "gff", "gtf", "fusionjuncspan", "refflat", "seg", "aed", "bed", "vcf", "bb", "bigbed", "biginteract", "biggenepred", "bignarrowpeak", "bw", "bigwig", "bam", "tdf", "refgene", "genepred", "genepredext", "bedpe", "bp", "snp", "rmsk", "cram", "gwas", "maf", "mut"]);
19915
19915
  /**
19916
19916
  * Return a custom format object with the given name.
19917
19917
  * @param name
@@ -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,8 @@
20043
20044
  case "bed":
20044
20045
  case "bigbed":
20045
20046
  case "bb":
20046
- case "biginteract":
20047
+ case "biggenepred":
20048
+ case "bignarrowpeak":
20047
20049
  return "bedtype";
20048
20050
 
20049
20051
  default:
@@ -20287,6 +20289,30 @@
20287
20289
  return window.location.protocol === "https:" || window.location.hostname === "localhost";
20288
20290
  }
20289
20291
 
20292
+ const pairs = [['A', 'T'], ['G', 'C'], ['Y', 'R'], ['W', 'S'], ['K', 'M'], ['D', 'H'], ['B', 'V']];
20293
+ const complements = new Map();
20294
+
20295
+ for (let p of pairs) {
20296
+ const p1 = p[0];
20297
+ const p2 = p[1];
20298
+ complements.set(p1, p2);
20299
+ complements.set(p2, p1);
20300
+ complements.set(p1.toLowerCase(), p2.toLowerCase());
20301
+ complements.set(p2.toLowerCase(), p1.toLowerCase());
20302
+ }
20303
+
20304
+ function reverseComplementSequence(sequence) {
20305
+ let comp = '';
20306
+ let idx = sequence.length;
20307
+
20308
+ while (idx-- > 0) {
20309
+ const base = sequence[idx];
20310
+ comp += complements.has(base) ? complements.get(base) : base;
20311
+ }
20312
+
20313
+ return comp;
20314
+ }
20315
+
20290
20316
  /*
20291
20317
  * The MIT License (MIT)
20292
20318
  *
@@ -20440,16 +20466,21 @@
20440
20466
  const viewport = clickState.viewport;
20441
20467
 
20442
20468
  if (viewport.referenceFrame.bpPerPixel <= 1) {
20469
+ const pixelWidth = viewport.getWidth();
20470
+ const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20471
+ const chr = viewport.referenceFrame.chr;
20472
+ const start = Math.floor(viewport.referenceFrame.start);
20473
+ const end = Math.ceil(start + bpWindow);
20443
20474
  const items = [{
20444
- label: 'View visible sequence...',
20475
+ label: this.reversed ? 'View visible sequence (reversed)...' : 'View visible sequence...',
20445
20476
  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);
20477
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20478
+
20479
+ if (this.reversed) {
20480
+ seq = reverseComplementSequence(seq);
20481
+ }
20482
+
20483
+ Alert.presentAlert(seq);
20453
20484
  }
20454
20485
  }];
20455
20486
 
@@ -20457,13 +20488,18 @@
20457
20488
  items.push({
20458
20489
  label: 'Copy visible sequence',
20459
20490
  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);
20491
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20492
+
20493
+ if (this.reversed) {
20494
+ seq = reverseComplementSequence(seq);
20495
+ }
20496
+
20497
+ try {
20498
+ await navigator.clipboard.writeText(seq);
20499
+ } catch (e) {
20500
+ console.error(e);
20501
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
20502
+ }
20467
20503
  }
20468
20504
  });
20469
20505
  }
@@ -20604,7 +20640,7 @@
20604
20640
  }
20605
20641
  }
20606
20642
 
20607
- supportsWholeGenome() {
20643
+ get supportsWholeGenome() {
20608
20644
  return false;
20609
20645
  }
20610
20646
 
@@ -20672,12 +20708,13 @@
20672
20708
  });
20673
20709
  this.$viewport.append(this.$content);
20674
20710
  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);
20711
+ this.contentDiv = this.$content.get(0); // this.$canvas = $('<canvas>')
20712
+ // this.$content.append(this.$canvas)
20713
+ //
20714
+ // this.canvas = this.$canvas.get(0)
20715
+ // this.ctx = this.canvas.getContext("2d")
20716
+
20717
+ this.$viewport.width(width);
20681
20718
  this.initializationHelper();
20682
20719
  }
20683
20720
 
@@ -20733,14 +20770,13 @@
20733
20770
  console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
20734
20771
  }
20735
20772
 
20736
- checkContentHeight() {
20773
+ checkContentHeight(features) {
20737
20774
  let track = this.trackView.track;
20775
+ features = features || this.cachedFeatures;
20738
20776
 
20739
20777
  if ("FILL" === track.displayMode) {
20740
20778
  this.setContentHeight(this.$viewport.height());
20741
20779
  } else if (typeof track.computePixelHeight === 'function') {
20742
- let features = this.cachedFeatures;
20743
-
20744
20780
  if (features && features.length > 0) {
20745
20781
  let requiredContentHeight = track.computePixelHeight(features);
20746
20782
  let currentContentHeight = this.$content.height();
@@ -20760,7 +20796,6 @@
20760
20796
  // Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
20761
20797
  contentHeight = Math.min(contentHeight, 32000);
20762
20798
  this.$content.height(contentHeight);
20763
- if (this.tile) this.tile.invalidate = true;
20764
20799
  }
20765
20800
 
20766
20801
  isLoading() {
@@ -20775,8 +20810,6 @@
20775
20810
 
20776
20811
  setWidth(width) {
20777
20812
  this.$viewport.width(width);
20778
- this.canvas.style.width = `${width}px`;
20779
- this.canvas.setAttribute('width', width);
20780
20813
  }
20781
20814
 
20782
20815
  getWidth() {
@@ -20804,7 +20837,6 @@
20804
20837
  this.popover.dispose();
20805
20838
  }
20806
20839
 
20807
- this.removeMouseHandlers();
20808
20840
  this.$viewport.get(0).remove(); // Null out all properties -- this should not be neccessary, but just in case there is a
20809
20841
  // reference to self somewhere we want to free memory.
20810
20842
 
@@ -23061,7 +23093,7 @@
23061
23093
  }
23062
23094
  };
23063
23095
 
23064
- const _version = "2.11.2";
23096
+ const _version = "2.12.2";
23065
23097
 
23066
23098
  function version$1() {
23067
23099
  return _version;
@@ -23204,7 +23236,7 @@
23204
23236
  class Genome {
23205
23237
  constructor(config, sequence, ideograms, aliases) {
23206
23238
  this.config = config;
23207
- this.id = config.id;
23239
+ this.id = config.id || generateGenomeID(config);
23208
23240
  this.sequence = sequence;
23209
23241
  this.chromosomeNames = sequence.chromosomeNames;
23210
23242
  this.chromosomes = sequence.chromosomes; // An object (functions as a dictionary)
@@ -23411,6 +23443,7 @@
23411
23443
  }
23412
23444
 
23413
23445
  async getSequence(chr, start, end) {
23446
+ chr = this.getChromosomeName(chr);
23414
23447
  return this.sequence.getSequence(chr, start, end);
23415
23448
  }
23416
23449
 
@@ -23519,6 +23552,18 @@
23519
23552
  }
23520
23553
  }
23521
23554
 
23555
+ function generateGenomeID(config) {
23556
+ if (config.id !== undefined) {
23557
+ return config.id;
23558
+ } else if (config.fastaURL && isString$3(config.fastaURL)) {
23559
+ return config.fastaURL;
23560
+ } else if (config.fastaURL && config.fastaURL.name) {
23561
+ return config.fastaURL.name;
23562
+ } else {
23563
+ return ("0000" + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4);
23564
+ }
23565
+ }
23566
+
23522
23567
  /**
23523
23568
  * Created by dat on 9/16/16.
23524
23569
  */
@@ -23537,15 +23582,13 @@
23537
23582
  });
23538
23583
  this.$viewport.append(this.$spinner);
23539
23584
  this.$spinner.append($$1('<div>'));
23540
- const {
23541
- track
23542
- } = this.trackView;
23585
+ const track = this.trackView.track;
23543
23586
 
23544
23587
  if ('sequence' !== track.type) {
23545
23588
  this.$zoomInNotice = this.createZoomInNotice(this.$content);
23546
23589
  }
23547
23590
 
23548
- if (track.name && "sequence" !== track.config.type) {
23591
+ if (track.name && "sequence" !== track.id) {
23549
23592
  this.$trackLabel = $$1('<div class="igv-track-label">');
23550
23593
  this.$viewport.append(this.$trackLabel);
23551
23594
  this.setTrackLabel(track.name);
@@ -23559,6 +23602,11 @@
23559
23602
  this.addMouseHandlers();
23560
23603
  }
23561
23604
 
23605
+ setContentHeight(contentHeight) {
23606
+ super.setContentHeight(contentHeight);
23607
+ if (this.featureCache) this.featureCache.redraw = true;
23608
+ }
23609
+
23562
23610
  setTrackLabel(label) {
23563
23611
  this.$trackLabel.empty();
23564
23612
  this.$trackLabel.html(label);
@@ -23575,55 +23623,72 @@
23575
23623
  this.$spinner.hide();
23576
23624
  }
23577
23625
  }
23626
+ /**
23627
+ * Test to determine if we are zoomed in far enough to see features. Applicable to tracks with visibility windows.
23628
+ *
23629
+ * As a side effect the viewports canvas is removed if zoomed out.
23630
+ *
23631
+ * @returns {boolean} true if we are zoomed in past visibility window, false otherwise
23632
+ */
23578
23633
 
23579
- checkZoomIn() {
23580
- const showZoomInNotice = () => {
23581
- const referenceFrame = this.referenceFrame;
23582
23634
 
23583
- if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome()) {
23635
+ checkZoomIn() {
23636
+ const zoomedOutOfWindow = () => {
23637
+ if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome) {
23584
23638
  return true;
23585
23639
  } else {
23586
23640
  const visibilityWindow = this.trackView.track.visibilityWindow;
23587
- return visibilityWindow !== undefined && visibilityWindow > 0 && referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow;
23641
+ return visibilityWindow !== undefined && visibilityWindow > 0 && this.referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow;
23588
23642
  }
23589
23643
  };
23590
23644
 
23645
+ if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
23646
+ $$1(this.canvas).remove();
23647
+ this.canvas = undefined; //this.featureCache = undefined
23648
+
23649
+ return false;
23650
+ }
23651
+
23591
23652
  if (!this.viewIsReady()) {
23592
23653
  return false;
23593
23654
  }
23594
23655
 
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
- }
23656
+ if (zoomedOutOfWindow()) {
23657
+ // Out of visibility window
23658
+ if (this.canvas) {
23659
+ $$1(this.canvas).remove();
23660
+ this.canvas = undefined; //this.featureCache = undefined
23661
+ }
23602
23662
 
23603
- this.$zoomInNotice.show();
23663
+ if (this.trackView.track.autoHeight) {
23664
+ const minHeight = this.trackView.minHeight || 0;
23665
+ this.setContentHeight(minHeight);
23666
+ }
23604
23667
 
23605
- if (this.trackView.track.autoHeight) {
23606
- const minHeight = this.trackView.minHeight || 0;
23607
- this.setContentHeight(minHeight);
23608
- }
23668
+ if (this.$zoomInNotice) {
23669
+ this.$zoomInNotice.show();
23670
+ }
23609
23671
 
23610
- return false;
23611
- } else {
23672
+ return false;
23673
+ } else {
23674
+ if (this.$zoomInNotice) {
23612
23675
  this.$zoomInNotice.hide();
23613
- return true;
23614
23676
  }
23615
- }
23616
23677
 
23617
- return true;
23678
+ return true;
23679
+ }
23618
23680
  }
23681
+ /**
23682
+ * Adjust the canvas to the current genomic state.
23683
+ */
23684
+
23619
23685
 
23620
23686
  shift() {
23621
- const self = this;
23622
- const referenceFrame = self.referenceFrame;
23687
+ const referenceFrame = this.referenceFrame;
23623
23688
 
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";
23689
+ if (this.canvas && this.canvas._data && this.canvas._data.chr === this.referenceFrame.chr && this.canvas._data.bpPerPixel === referenceFrame.bpPerPixel) {
23690
+ const pixelOffset = Math.round((this.canvas._data.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23691
+ this.canvas.style.left = pixelOffset + "px";
23627
23692
  }
23628
23693
  }
23629
23694
 
@@ -23649,9 +23714,10 @@
23649
23714
  this.startSpinner();
23650
23715
 
23651
23716
  try {
23652
- const features = await this.getFeatures(this.trackView.track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23717
+ const track = this.trackView.track;
23718
+ const features = await this.getFeatures(track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23653
23719
  let roiFeatures = [];
23654
- const roi = mergeArrays(this.browser.roi, this.trackView.track.roi);
23720
+ const roi = mergeArrays(this.browser.roi, track.roi);
23655
23721
 
23656
23722
  if (roi) {
23657
23723
  for (let r of roi) {
@@ -23663,11 +23729,13 @@
23663
23729
  }
23664
23730
  }
23665
23731
 
23666
- this.tile = new Tile(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures);
23732
+ const mr = track && ("wig" === track.type || "merged" === track.type); // wig tracks are potentially multiresolution (e.g. bigwig)
23733
+
23734
+ this.featureCache = new FeatureCache(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures, mr);
23667
23735
  this.loading = false;
23668
23736
  this.hideMessage();
23669
23737
  this.stopSpinner();
23670
- return this.tile;
23738
+ return this.featureCache;
23671
23739
  } catch (error) {
23672
23740
  // Track might have been removed during load
23673
23741
  if (this.trackView && this.trackView.disposed !== true) {
@@ -23680,38 +23748,32 @@
23680
23748
  this.stopSpinner();
23681
23749
  }
23682
23750
  }
23751
+ /**
23752
+ * Repaint the canvas using the cached features
23753
+ *
23754
+ */
23683
23755
 
23684
- async repaint() {
23685
- if (undefined === this.tile) {
23756
+
23757
+ repaint() {
23758
+ if (undefined === this.featureCache) {
23686
23759
  return;
23687
23760
  }
23688
23761
 
23689
23762
  let {
23690
23763
  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
23764
+ roiFeatures
23765
+ } = this.featureCache; //this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
23766
+ // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23708
23767
 
23768
+ const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr); // Canvas dimensions. There is no left-right panning for WGV so canvas width is viewport width.
23769
+ // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
23709
23770
 
23771
+ const pixelWidth = isWGV ? this.$viewport.width() : 3 * this.$viewport.width();
23710
23772
  const viewportHeight = this.$viewport.height();
23711
23773
  const contentHeight = this.getContentHeight();
23712
23774
  const minHeight = roiFeatures ? Math.max(contentHeight, viewportHeight) : contentHeight; // Need to fill viewport for ROIs.
23713
23775
 
23714
- let pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23776
+ const pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23715
23777
 
23716
23778
  if (0 === pixelWidth || 0 === pixelHeight) {
23717
23779
  if (this.canvas) {
@@ -23721,26 +23783,22 @@
23721
23783
  return;
23722
23784
  }
23723
23785
 
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);
23786
+ const canvasTop = Math.max(0, -this.$content.position().top - viewportHeight);
23787
+ const bpPerPixel = this.referenceFrame.bpPerPixel;
23788
+ const startBP = this.referenceFrame.start - (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23789
+ const endBP = this.referenceFrame.end + (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23790
+ const pixelXOffset = Math.round((startBP - this.referenceFrame.start) / bpPerPixel);
23735
23791
  const newCanvas = $$1('<canvas class="igv-canvas">').get(0);
23736
- const ctx = newCanvas.getContext("2d");
23737
23792
  newCanvas.style.width = pixelWidth + "px";
23738
23793
  newCanvas.style.height = pixelHeight + "px";
23794
+ newCanvas.style.left = pixelXOffset + "px";
23795
+ newCanvas.style.top = canvasTop + "px"; // Always use high DPI if in "FILL" display mode, otherwise use track setting;
23796
+
23797
+ const devicePixelRatio = "FILL" === this.trackView.track.displayMode || this.trackView.track.supportHiDPI !== false ? window.devicePixelRatio : 1;
23739
23798
  newCanvas.width = devicePixelRatio * pixelWidth;
23740
23799
  newCanvas.height = devicePixelRatio * pixelHeight;
23800
+ const ctx = newCanvas.getContext("2d");
23741
23801
  ctx.scale(devicePixelRatio, devicePixelRatio);
23742
- newCanvas.style.left = pixelXOffset + "px";
23743
- newCanvas.style.top = canvasTop + "px";
23744
23802
  ctx.translate(0, -canvasTop);
23745
23803
  const drawConfiguration = {
23746
23804
  context: ctx,
@@ -23757,20 +23815,32 @@
23757
23815
  viewportWidth: this.$viewport.width()
23758
23816
  };
23759
23817
  this.draw(drawConfiguration, features, roiFeatures);
23760
- this.canvasVerticalRange = {
23761
- top: canvasTop,
23762
- bottom: canvasTop + pixelHeight
23763
- };
23818
+ this.featureCache.canvasTop = canvasTop;
23819
+ this.featureCache.height = pixelHeight;
23764
23820
 
23765
- if (this.$canvas) {
23766
- this.$canvas.remove();
23821
+ if (this.canvas) {
23822
+ $$1(this.canvas).remove();
23767
23823
  }
23768
23824
 
23769
- this.$canvas = $$1(newCanvas);
23770
- this.$content.append(this.$canvas);
23825
+ newCanvas._data = {
23826
+ chr: this.featureCache.chr,
23827
+ bpPerPixel,
23828
+ startBP,
23829
+ endBP,
23830
+ pixelHeight,
23831
+ pixelTop: canvasTop
23832
+ };
23771
23833
  this.canvas = newCanvas;
23772
- this.ctx = ctx;
23834
+ this.$content.append($$1(newCanvas));
23773
23835
  }
23836
+ /**
23837
+ * Draw the associated track.
23838
+ *
23839
+ * @param drawConfiguration
23840
+ * @param features
23841
+ * @param roiFeatures
23842
+ */
23843
+
23774
23844
 
23775
23845
  draw(drawConfiguration, features, roiFeatures) {
23776
23846
  // console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
@@ -23785,51 +23855,6 @@
23785
23855
  r.track.draw(drawConfiguration);
23786
23856
  }
23787
23857
  }
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
23858
  }
23834
23859
 
23835
23860
  containsPosition(chr, position) {
@@ -23844,15 +23869,17 @@
23844
23869
  return this.loading;
23845
23870
  }
23846
23871
 
23847
- saveImage() {
23848
- if (!this.ctx) return;
23849
- const canvasTop = this.canvasVerticalRange ? this.canvasVerticalRange.top : 0;
23872
+ savePNG() {
23873
+ if (!this.canvas) return;
23874
+ const canvasMetadata = this.featureCache;
23875
+ const canvasTop = canvasMetadata ? canvasMetadata.canvasTop : 0;
23850
23876
  const devicePixelRatio = window.devicePixelRatio;
23851
23877
  const w = this.$viewport.width() * devicePixelRatio;
23852
23878
  const h = this.$viewport.height() * devicePixelRatio;
23853
23879
  const x = -$$1(this.canvas).position().left * devicePixelRatio;
23854
23880
  const y = (-this.$content.position().top - canvasTop) * devicePixelRatio;
23855
- const imageData = this.ctx.getImageData(x, y, w, h);
23881
+ const ctx = this.canvas.getContext("2d");
23882
+ const imageData = ctx.getImageData(x, y, w, h);
23856
23883
  const exportCanvas = document.createElement('canvas');
23857
23884
  const exportCtx = exportCanvas.getContext('2d');
23858
23885
  exportCanvas.width = imageData.width;
@@ -23968,29 +23995,44 @@
23968
23995
  viewportWidth: width,
23969
23996
  selection: this.selection
23970
23997
  };
23971
- const features = this.tile ? this.tile.features : [];
23972
- const roiFeatures = this.tile ? this.tile.roiFeatures : undefined;
23998
+ const features = this.featureCache ? this.featureCache.features : [];
23999
+ const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
23973
24000
  this.draw(config, features, roiFeatures);
23974
24001
  context.restore();
23975
24002
  }
23976
24003
 
23977
- getCachedFeatures() {
23978
- return this.tile ? this.tile.features : [];
24004
+ get cachedFeatures() {
24005
+ return this.featureCache ? this.featureCache.features : [];
23979
24006
  }
23980
24007
 
23981
24008
  async getFeatures(track, chr, start, end, bpPerPixel) {
23982
- if (this.tile && this.tile.containsRange(chr, start, end, bpPerPixel)) {
23983
- return this.tile.features;
24009
+ if (this.featureCache && this.featureCache.containsRange(chr, start, end, bpPerPixel)) {
24010
+ return this.featureCache.features;
23984
24011
  } else if (typeof track.getFeatures === "function") {
23985
24012
  const features = await track.getFeatures(chr, start, end, bpPerPixel, this);
23986
- this.cachedFeatures = features;
23987
- this.checkContentHeight();
24013
+ this.checkContentHeight(features);
23988
24014
  return features;
23989
24015
  } else {
23990
24016
  return undefined;
23991
24017
  }
23992
24018
  }
23993
24019
 
24020
+ needsRepaint() {
24021
+ if (!this.canvas) return true;
24022
+ const data = this.canvas._data;
24023
+ return !data || this.referenceFrame.start < data.startBP || this.referenceFrame.end > data.endBP || this.referenceFrame.chr !== data.chr || this.referenceFrame.bpPerPixel != data.bpPerPixel;
24024
+ }
24025
+
24026
+ needsReload() {
24027
+ if (!this.featureCache) return true;
24028
+ const referenceFrame = this.referenceFrame;
24029
+ const chr = this.referenceFrame.chr;
24030
+ const start = referenceFrame.start;
24031
+ const end = start + referenceFrame.toBP($$1(this.contentDiv).width());
24032
+ const bpPerPixel = referenceFrame.bpPerPixel;
24033
+ return !this.featureCache.containsRange(chr, start, end, bpPerPixel);
24034
+ }
24035
+
23994
24036
  createZoomInNotice($parent) {
23995
24037
  const $container = $$1('<div>', {
23996
24038
  class: 'igv-zoom-in-notice-container'
@@ -24008,36 +24050,42 @@
24008
24050
  }
24009
24051
 
24010
24052
  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));
24053
+ const viewport = this.$viewport.get(0);
24054
+ this.addViewportContextMenuHandler(viewport);
24017
24055
 
24018
- if (this.trackView.track.name && "sequence" !== this.trackView.track.config.type) {
24019
- this.addTrackLabelClickHandler(this.$trackLabel.get(0));
24020
- }
24021
- }
24056
+ const md = event => {
24057
+ this.enableClick = true;
24058
+ this.browser.mouseDownOnViewport(event, this);
24059
+ pageCoordinates$1(event);
24060
+ };
24022
24061
 
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));
24062
+ viewport.addEventListener('mousedown', md);
24063
+ viewport.addEventListener('touchstart', md);
24064
+
24065
+ const mu = event => {
24066
+ // Any mouse up cancels drag and scrolling
24067
+ if (this.browser.dragObject || this.browser.isScrolling) {
24068
+ this.browser.cancelTrackPan(); // event.preventDefault();
24069
+ // event.stopPropagation();
24070
+
24071
+ this.enableClick = false; // Until next mouse down
24072
+ } else {
24073
+ this.browser.cancelTrackPan();
24074
+ this.browser.endTrackDrag();
24075
+ }
24076
+ };
24077
+
24078
+ viewport.addEventListener('mouseup', mu);
24079
+ viewport.addEventListener('touchend', mu);
24080
+ this.addViewportClickHandler(this.$viewport.get(0));
24030
24081
 
24031
24082
  if (this.trackView.track.name && "sequence" !== this.trackView.track.config.type) {
24032
- this.removeTrackLabelClickHandler(this.$trackLabel.get(0));
24083
+ this.addTrackLabelClickHandler(this.$trackLabel.get(0));
24033
24084
  }
24034
24085
  }
24035
24086
 
24036
24087
  addViewportContextMenuHandler(viewport) {
24037
- this.boundContextMenuHandler = contextMenuHandler.bind(this);
24038
- viewport.addEventListener('contextmenu', this.boundContextMenuHandler);
24039
-
24040
- function contextMenuHandler(event) {
24088
+ viewport.addEventListener('contextmenu', event => {
24041
24089
  // Ignore if we are doing a drag. This can happen with touch events.
24042
24090
  if (this.browser.dragObject) {
24043
24091
  return false;
@@ -24077,55 +24125,12 @@
24077
24125
  click: () => this.saveSVG()
24078
24126
  });
24079
24127
  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);
24128
+ });
24121
24129
  }
24122
24130
 
24123
24131
  addViewportClickHandler(viewport) {
24124
- this.boundClickHandler = clickHandler.bind(this);
24125
- viewport.addEventListener('click', this.boundClickHandler);
24126
-
24127
- function clickHandler(event) {
24128
- if (this.enableClick) {
24132
+ viewport.addEventListener('click', event => {
24133
+ if (this.enableClick && this.canvas) {
24129
24134
  if (3 === event.which || event.ctrlKey) {
24130
24135
  return;
24131
24136
  } // Close any currently open popups
@@ -24199,18 +24204,11 @@
24199
24204
 
24200
24205
  lastClickTime = time;
24201
24206
  }
24202
- }
24203
- }
24204
-
24205
- removeViewportClickHandler(viewport) {
24206
- viewport.removeEventListener('click', this.boundClickHandler);
24207
+ });
24207
24208
  }
24208
24209
 
24209
24210
  addTrackLabelClickHandler(trackLabel) {
24210
- this.boundTrackLabelClickHandler = clickHandler.bind(this);
24211
- trackLabel.addEventListener('click', this.boundTrackLabelClickHandler);
24212
-
24213
- function clickHandler(event) {
24211
+ trackLabel.addEventListener('click', event => {
24214
24212
  event.stopPropagation();
24215
24213
  const {
24216
24214
  track
@@ -24231,44 +24229,16 @@
24231
24229
  this.popover = new Popover(this.browser.columnContainer, track.name || '');
24232
24230
  this.popover.presentContentWithEvent(event, str);
24233
24231
  }
24234
- }
24235
- }
24236
-
24237
- removeTrackLabelClickHandler(trackLabel) {
24238
- trackLabel.removeEventListener('click', this.boundTrackLabelClickHandler);
24232
+ });
24239
24233
  }
24240
24234
 
24241
24235
  }
24242
24236
 
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
24237
  function createClickState(event, viewport) {
24263
24238
  const referenceFrame = viewport.referenceFrame;
24264
24239
  const viewportCoords = translateMouseCoordinates$1(event, viewport.contentDiv);
24265
24240
  const canvasCoords = translateMouseCoordinates$1(event, viewport.canvas);
24266
24241
  const genomicLocation = referenceFrame.start + referenceFrame.toBP(viewportCoords.x);
24267
-
24268
- if (undefined === genomicLocation || null === viewport.tile) {
24269
- return undefined;
24270
- }
24271
-
24272
24242
  return {
24273
24243
  event,
24274
24244
  viewport,
@@ -24322,22 +24292,28 @@
24322
24292
  return rows.join('');
24323
24293
  }
24324
24294
 
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
- };
24295
+ class FeatureCache {
24296
+ constructor(chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures, multiresolution) {
24297
+ this.chr = chr;
24298
+ this.startBP = tileStart;
24299
+ this.endBP = tileEnd;
24300
+ this.bpPerPixel = bpPerPixel;
24301
+ this.features = features;
24302
+ this.roiFeatures = roiFeatures;
24303
+ this.multiresolution = multiresolution;
24304
+ }
24333
24305
 
24334
- Tile.prototype.containsRange = function (chr, start, end, bpPerPixel) {
24335
- return this.bpPerPixel === bpPerPixel && start >= this.startBP && end <= this.endBP && chr === this.chr;
24336
- };
24306
+ containsRange(chr, start, end, bpPerPixel) {
24307
+ // For multi-resolution tracks allow for a 2X change in bpPerPixel
24308
+ const r = this.multiresolution ? this.bpPerPixel / bpPerPixel : 1;
24309
+ return start >= this.startBP && end <= this.endBP && chr === this.chr && r > 0.5 && r < 2;
24310
+ }
24337
24311
 
24338
- Tile.prototype.overlapsRange = function (chr, start, end) {
24339
- return this.chr === chr && end >= this.startBP && start <= this.endBP;
24340
- };
24312
+ overlapsRange(chr, start, end) {
24313
+ return this.chr === chr && end >= this.startBP && start <= this.endBP;
24314
+ }
24315
+
24316
+ }
24341
24317
  /**
24342
24318
  * Merge 2 arrays. a and/or b can be undefined. If both are undefined, return undefined
24343
24319
  * @param a An array or undefined
@@ -24476,10 +24452,12 @@
24476
24452
  end: bp(this.rulerViewport.referenceFrame, left + width)
24477
24453
  };
24478
24454
  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);
24455
+ const newStart = Math.round(extent.start);
24456
+ const newEnd = Math.round(extent.end);
24457
+ this.rulerViewport.referenceFrame.bpPerPixel = (newEnd - newStart) / this.rulerViewport.contentDiv.clientWidth;
24458
+ this.rulerViewport.referenceFrame.start = newStart;
24459
+ this.rulerViewport.referenceFrame.end = newEnd;
24460
+ this.rulerViewport.browser.updateViews();
24483
24461
  }
24484
24462
  }
24485
24463
  }
@@ -25481,6 +25459,18 @@
25481
25459
  return true; // By definition
25482
25460
  }
25483
25461
 
25462
+ isMateMapped() {
25463
+ return true; // By definition
25464
+ }
25465
+
25466
+ isProperPair() {
25467
+ return this.firstAlignment.isProperPair();
25468
+ }
25469
+
25470
+ get fragmentLength() {
25471
+ return Math.abs(this.firstAlignment.fragmentLength);
25472
+ }
25473
+
25484
25474
  firstOfPairStrand() {
25485
25475
  if (this.firstAlignment.isFirstOfPair()) {
25486
25476
  return this.firstAlignment.strand;
@@ -25491,6 +25481,10 @@
25491
25481
  }
25492
25482
  }
25493
25483
 
25484
+ hasTag(str) {
25485
+ return this.firstAlignment.hasTag(str) || this.secondAlignment && this.secondAlignment.hasTag(str);
25486
+ }
25487
+
25494
25488
  }
25495
25489
 
25496
25490
  /*
@@ -25847,7 +25841,15 @@
25847
25841
  */
25848
25842
 
25849
25843
  class AlignmentContainer {
25850
- constructor(chr, start, end, samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold) {
25844
+ // this.config.samplingWindowSize, this.config.samplingDepth,
25845
+ // this.config.pairsSupported, this.config.alleleFreqThreshold)
25846
+ constructor(chr, start, end, _ref) {
25847
+ let {
25848
+ samplingWindowSize,
25849
+ samplingDepth,
25850
+ pairsSupported,
25851
+ alleleFreqThreshold
25852
+ } = _ref;
25851
25853
  this.chr = chr;
25852
25854
  this.start = Math.floor(start);
25853
25855
  this.end = Math.ceil(end);
@@ -25870,17 +25872,10 @@
25870
25872
  // TODO -- pass this in
25871
25873
  return alignment.isMapped() && !alignment.isFailsVendorQualityCheck();
25872
25874
  };
25873
-
25874
- this.pairedEndStats = new PairedEndStats();
25875
25875
  }
25876
25876
 
25877
25877
  push(alignment) {
25878
25878
  if (this.filter(alignment) === false) return;
25879
-
25880
- if (alignment.isPaired()) {
25881
- this.pairedEndStats.push(alignment);
25882
- }
25883
-
25884
25879
  this.coverageMap.incCounts(alignment); // Count coverage before any downsampling
25885
25880
 
25886
25881
  if (this.pairsSupported && this.downsampledReads.has(alignment.readName)) {
@@ -25909,7 +25904,6 @@
25909
25904
  });
25910
25905
  this.pairsCache = undefined;
25911
25906
  this.downsampledReads = undefined;
25912
- this.pairedEndStats.compute();
25913
25907
  }
25914
25908
 
25915
25909
  contains(chr, start, end) {
@@ -26204,62 +26198,6 @@
26204
26198
 
26205
26199
  }
26206
26200
 
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
26201
  class SupplementaryAlignment {
26264
26202
  constructor(rec) {
26265
26203
  const tokens = rec.split(',');
@@ -27022,7 +26960,7 @@
27022
26960
  const lseq = readInt(ba, offset + 20);
27023
26961
  const mateChrIdx = readInt(ba, offset + 24);
27024
26962
  const matePos = readInt(ba, offset + 28);
27025
- const tlen = readInt(ba, offset + 32);
26963
+ const fragmentLength = readInt(ba, offset + 32);
27026
26964
  let readName = [];
27027
26965
 
27028
26966
  for (let j = 0; j < nl - 1; ++j) {
@@ -27063,7 +27001,7 @@
27063
27001
  alignment.readName = readName;
27064
27002
  alignment.cigar = cigar;
27065
27003
  alignment.lengthOnRef = lengthOnRef;
27066
- alignment.fragmentLength = tlen;
27004
+ alignment.fragmentLength = fragmentLength;
27067
27005
  alignment.mq = mq;
27068
27006
  BamUtils.bam_tag2cigar(ba, blockEnd, p, lseq, alignment, cigarArray);
27069
27007
  alignment.end = alignment.start + alignment.lengthOnRef;
@@ -27463,7 +27401,7 @@
27463
27401
  const header = this.header;
27464
27402
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
27465
27403
  const qAlignments = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
27466
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27404
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27467
27405
 
27468
27406
  for (let a of qAlignments) {
27469
27407
  alignmentContainer.push(a);
@@ -27490,13 +27428,13 @@
27490
27428
  const alignments = [];
27491
27429
  this.header = BamUtils.decodeBamHeader(data);
27492
27430
  BamUtils.decodeBamRecords(data, this.header.size, alignments, this.header.chrNames);
27493
- this.alignmentCache = new FeatureCache(alignments, this.genome);
27431
+ this.alignmentCache = new FeatureCache$1(alignments, this.genome);
27494
27432
  }
27495
27433
 
27496
27434
  fetchAlignments(chr, bpStart, bpEnd) {
27497
27435
  const queryChr = this.header.chrAliasTable.hasOwnProperty(chr) ? this.header.chrAliasTable[chr] : chr;
27498
27436
  const features = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
27499
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported);
27437
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27500
27438
 
27501
27439
  for (let feature of features) {
27502
27440
  alignmentContainer.push(feature);
@@ -28496,7 +28434,7 @@
28496
28434
  const chrToIndex = await this.getChrIndex();
28497
28435
  const queryChr = this.chrAliasTable.hasOwnProperty(chr) ? this.chrAliasTable[chr] : chr;
28498
28436
  const chrId = chrToIndex[queryChr];
28499
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config.samplingWindowSize, this.config.samplingDepth, this.config.pairsSupported, this.config.alleleFreqThreshold);
28437
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
28500
28438
 
28501
28439
  if (chrId === undefined) {
28502
28440
  return alignmentContainer;
@@ -28645,7 +28583,7 @@
28645
28583
 
28646
28584
  async readAlignments(chr, start, end) {
28647
28585
  if (!this.bamReaders.hasOwnProperty(chr)) {
28648
- return new AlignmentContainer(chr, start, end);
28586
+ return new AlignmentContainer(chr, start, end, this.config);
28649
28587
  } else {
28650
28588
  let reader = this.bamReaders[chr];
28651
28589
  const a = await reader.readAlignments(chr, start, end);
@@ -28717,7 +28655,7 @@
28717
28655
  return igvxhr.loadString(url, buildOptions(self.config)).then(function (sam) {
28718
28656
  var alignmentContainer;
28719
28657
  header.chrToIndex[queryChr];
28720
- alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold);
28658
+ alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.config);
28721
28659
  BamUtils.decodeSamRecords(sam, alignmentContainer, queryChr, bpStart, bpEnd, self.filter);
28722
28660
  return alignmentContainer;
28723
28661
  });
@@ -28927,7 +28865,7 @@
28927
28865
 
28928
28866
  const ba = unbgzf(compressedData.buffer);
28929
28867
  const chrIdx = this.header.chrToIndex[chr];
28930
- const alignmentContainer = new AlignmentContainer(chr, start, end, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
28868
+ const alignmentContainer = new AlignmentContainer(chr, start, end, this.config);
28931
28869
  BamUtils.decodeBamRecords(ba, this.header.size, alignmentContainer, this.header.chrNames, chrIdx, start, end);
28932
28870
  alignmentContainer.finish();
28933
28871
  return alignmentContainer;
@@ -44465,7 +44403,7 @@
44465
44403
  const header = await this.getHeader();
44466
44404
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
44467
44405
  const chrIdx = header.chrToIndex[queryChr];
44468
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
44406
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
44469
44407
 
44470
44408
  if (chrIdx === undefined) {
44471
44409
  return alignmentContainer;
@@ -45074,7 +45012,7 @@
45074
45012
  "pageSize": "10000"
45075
45013
  },
45076
45014
  decode: decodeGa4ghReads,
45077
- results: new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold)
45015
+ results: new AlignmentContainer(chr, bpStart, bpEnd, self.config)
45078
45016
  });
45079
45017
  });
45080
45018
 
@@ -45381,7 +45319,6 @@
45381
45319
  const genome = browser.genome;
45382
45320
  this.config = config;
45383
45321
  this.genome = genome;
45384
- this.alignmentContainer = undefined;
45385
45322
 
45386
45323
  if (isDataURL(config.url)) {
45387
45324
  if ("cram" === config.format) {
@@ -45432,58 +45369,44 @@
45432
45369
  }
45433
45370
 
45434
45371
  setViewAsPairs(bool) {
45435
-
45436
- if (this.viewAsPairs !== bool) {
45437
- this.viewAsPairs = bool; // if (this.alignmentContainer) {
45438
- // this.alignmentContainer.setViewAsPairs(bool);
45439
- // }
45440
- }
45372
+ this.viewAsPairs = bool;
45441
45373
  }
45442
45374
 
45443
45375
  setShowSoftClips(bool) {
45444
- if (this.showSoftClips !== bool) {
45445
- this.showSoftClips = bool;
45446
- }
45376
+ this.showSoftClips = bool;
45447
45377
  }
45448
45378
 
45449
45379
  async getAlignments(chr, bpStart, bpEnd) {
45450
45380
  const genome = this.genome;
45451
45381
  const showSoftClips = this.showSoftClips;
45382
+ const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
45383
+ let alignments = alignmentContainer.alignments;
45452
45384
 
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
45385
+ if (!this.viewAsPairs) {
45386
+ alignments = unpairAlignments([{
45387
+ alignments: alignments
45388
+ }]);
45389
+ }
45468
45390
 
45469
- this.alignmentContainer = alignmentContainer;
45391
+ const hasAlignments = alignments.length > 0;
45392
+ alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
45393
+ this.alignmentContainer = alignmentContainer;
45470
45394
 
45471
- if (hasAlignments) {
45472
- const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
45395
+ if (hasAlignments) {
45396
+ const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
45473
45397
 
45474
- if (sequence) {
45475
- alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
45398
+ if (sequence) {
45399
+ alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
45476
45400
 
45477
- alignmentContainer.sequence = sequence; // TODO -- fix this
45401
+ alignmentContainer.sequence = sequence; // TODO -- fix this
45478
45402
 
45479
- return alignmentContainer;
45480
- } else {
45481
- console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
45482
- }
45403
+ return alignmentContainer;
45404
+ } else {
45405
+ console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
45483
45406
  }
45484
-
45485
- return alignmentContainer;
45486
45407
  }
45408
+
45409
+ return alignmentContainer;
45487
45410
  }
45488
45411
 
45489
45412
  }
@@ -45512,6 +45435,10 @@
45512
45435
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
45513
45436
  * THE SOFTWARE.
45514
45437
  */
45438
+
45439
+ const fixColor = colorString => {
45440
+ return colorString.indexOf(",") > 0 && !colorString.startsWith("rgb") ? `rgb(${colorString})` : colorString;
45441
+ };
45515
45442
  /**
45516
45443
  * A collection of properties and methods shared by all (or most) track types.
45517
45444
  *
@@ -45520,6 +45447,7 @@
45520
45447
  * @constructor
45521
45448
  */
45522
45449
 
45450
+
45523
45451
  class TrackBase {
45524
45452
  constructor(config, browser) {
45525
45453
  this.browser = browser;
@@ -45553,8 +45481,8 @@
45553
45481
 
45554
45482
  this.id = this.config.id === undefined ? this.name : this.config.id;
45555
45483
  this.order = config.order;
45556
- this.color = config.color;
45557
- this.altColor = config.altColor;
45484
+ if (config.color) this.color = fixColor(config.color);
45485
+ if (config.altColor) this.altColor = fixColor(config.altColor);
45558
45486
 
45559
45487
  if ("civic-ws" === config.sourceType) {
45560
45488
  // Ugly proxy for specialized track type
@@ -45667,7 +45595,7 @@
45667
45595
  return state;
45668
45596
  }
45669
45597
 
45670
- supportsWholeGenome() {
45598
+ get supportsWholeGenome() {
45671
45599
  return false;
45672
45600
  }
45673
45601
  /**
@@ -45828,7 +45756,7 @@
45828
45756
  clickedFeatures(clickState, features) {
45829
45757
  // We use the cached features rather than method to avoid async load. If the
45830
45758
  // 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();
45759
+ if (!features) features = clickState.viewport.cachedFeatures;
45832
45760
 
45833
45761
  if (!features || features.length === 0) {
45834
45762
  return [];
@@ -47758,7 +47686,7 @@
47758
47686
  return this.tracks.find(t => name === t.name);
47759
47687
  }
47760
47688
 
47761
- getChordset(name) {
47689
+ getChordSet(name) {
47762
47690
  return this.chordSets.find(cs => name === cs.name);
47763
47691
  }
47764
47692
 
@@ -47868,9 +47796,9 @@
47868
47796
  buttonContainer.appendChild(this.showControlsButton);
47869
47797
  this.showControlsButton.innerText = 'none' === this.controlPanel.style.display ? 'Show Controls' : 'Hide Controls';
47870
47798
  this.showControlsButton.addEventListener('click', event => {
47871
- const trackPanelRows = this.controlPanel.querySelectorAll('div');
47799
+ const panelRows = this.controlPanel.querySelectorAll('div');
47872
47800
 
47873
- if (trackPanelRows.length > 0) {
47801
+ if (panelRows.length > 0) {
47874
47802
  if ('none' === this.controlPanel.style.display) {
47875
47803
  this.controlPanel.style.display = 'flex';
47876
47804
  event.target.innerText = 'Hide Controls';
@@ -47950,10 +47878,10 @@
47950
47878
  hideShowButton.innerText = true === chordSet.visible ? 'Hide' : 'Show';
47951
47879
  hideShowButton.addEventListener('click', event => {
47952
47880
  if (true === chordSet.visible) {
47953
- this.hideTrack(chordSet.name);
47881
+ this.hideChordSet(chordSet.name);
47954
47882
  event.target.innerText = "Show";
47955
47883
  } else {
47956
- this.showTrack(chordSet.name);
47884
+ this.showChordSet(chordSet.name);
47957
47885
  event.target.innerText = "Hide";
47958
47886
  }
47959
47887
  }); // The alpha range slider. Create this here so we can reference it from the color picker
@@ -47980,7 +47908,7 @@
47980
47908
  rgbaString
47981
47909
  }) => {
47982
47910
  colorPickerButton.style.backgroundColor = setAlpha(rgbaString, 1);
47983
- this.setTrackColor(chordSet.name, rgbaString);
47911
+ this.setColor(chordSet.name, rgbaString);
47984
47912
  alphaSlider.value = alphaToValue(getAlpha(chordSet.color));
47985
47913
  }
47986
47914
  };
@@ -47998,7 +47926,7 @@
47998
47926
 
47999
47927
  alphaSlider.oninput = () => {
48000
47928
  const v = valueToAlpha(alphaSlider.value);
48001
- this.setTrackColor(chordSet.name, setAlpha(chordSet.color, v));
47929
+ this.setColor(chordSet.name, setAlpha(chordSet.color, v));
48002
47930
  picker.setColor(chordSet.color);
48003
47931
  };
48004
47932
 
@@ -48017,12 +47945,14 @@
48017
47945
 
48018
47946
 
48019
47947
  setAssembly(igvGenome) {
48020
- if (this.genomeId === igvGenome.id) {
47948
+ const id = this.genomeId || guid();
47949
+
47950
+ if (this.genomeId === id) {
48021
47951
  return;
48022
47952
  }
48023
47953
 
48024
47954
  this.chordManager.clearChords();
48025
- this.genomeId = igvGenome.id;
47955
+ this.genomeId = id;
48026
47956
  this.chrNames = new Set(igvGenome.chromosomes.map(chr => shortChrName$1(chr.name)));
48027
47957
  const regions = [];
48028
47958
  const colors = [];
@@ -48041,7 +47971,7 @@
48041
47971
  this.assembly = {
48042
47972
  name: igvGenome.name,
48043
47973
  sequence: {
48044
- trackId: igvGenome.id,
47974
+ trackId: id,
48045
47975
  type: 'ReferenceSequenceTrack',
48046
47976
  adapter: {
48047
47977
  type: 'FromConfigSequenceAdapter',
@@ -48079,7 +48009,7 @@
48079
48009
 
48080
48010
 
48081
48011
  addChords(newChords, options = {}) {
48082
- const tmp = options.track || options.name || "*";
48012
+ const tmp = options.name || options.track || "*";
48083
48013
  const trackName = tmp.split(' ')[0].replaceAll("%20", " ");
48084
48014
  const chordSetName = tmp.replaceAll("%20", " ");
48085
48015
  const chordSet = {
@@ -48129,20 +48059,6 @@
48129
48059
  clearSelection() {
48130
48060
  this.viewState.pluginManager.rootModel.session.clearSelection();
48131
48061
  }
48132
-
48133
- getFeature(featureId) {
48134
- // TODO -- broken
48135
- // const display = this.viewState.pluginManager.rootModel.session.view.tracks[0].displays[0]
48136
- // const feature = display.data.features.get(featureId)
48137
- // return feature;
48138
- const features = [...this.viewState.config.tracks[0].adapter.features.value];
48139
-
48140
- for (let f of features) {
48141
- if (featureId === f.uniqueId) {
48142
- return f;
48143
- }
48144
- }
48145
- }
48146
48062
  /**
48147
48063
  * Deprecated, use "visible" property
48148
48064
  */
@@ -48168,25 +48084,25 @@
48168
48084
  this.parent.style.display = isVisible ? 'block' : 'none';
48169
48085
  }
48170
48086
 
48171
- hideTrack(trackName) {
48172
- let track = this.getTrack(trackName);
48087
+ hideChordSet(trackName) {
48088
+ let cs = this.getChordSet(trackName);
48173
48089
 
48174
- if (track) {
48175
- track.visible = false;
48090
+ if (cs) {
48091
+ cs.visible = false;
48176
48092
  this.render();
48177
48093
  } else {
48178
48094
  console.warn(`No track with name: ${name}`);
48179
48095
  }
48180
48096
  }
48181
48097
 
48182
- showTrack(trackName) {
48183
- let track = this.getTrack(trackName);
48098
+ showChordSet(name) {
48099
+ let cs = this.getChordSet(name);
48184
48100
 
48185
- if (track) {
48186
- track.visible = true;
48101
+ if (cs) {
48102
+ cs.visible = true;
48187
48103
  this.render();
48188
48104
  } else {
48189
- console.warn(`No track with name: ${trackName}`);
48105
+ console.warn(`No track with name: ${name}`);
48190
48106
  }
48191
48107
  } // showTrack(trackID) {
48192
48108
  // let idx = this.tracks.findIndex(t => trackID === t.id)
@@ -48213,12 +48129,12 @@
48213
48129
  this.render();
48214
48130
  }
48215
48131
 
48216
- getTrack(name) {
48217
- return this.groupByTrack ? this.chordManager.getTrack(name) : this.chordManager.getChordset(name);
48132
+ getChordSet(name) {
48133
+ return this.groupByTrack ? this.chordManager.getTrack(name) : this.chordManager.getChordSet(name);
48218
48134
  }
48219
48135
 
48220
- setTrackColor(name, color) {
48221
- const t = this.getTrack(name);
48136
+ setColor(name, color) {
48137
+ const t = this.getChordSet(name);
48222
48138
 
48223
48139
  if (t) {
48224
48140
  t.color = color;
@@ -48319,7 +48235,7 @@
48319
48235
  }
48320
48236
 
48321
48237
  function embedCSS$1() {
48322
- const css = '.igv-circview-container {\n z-index: 2048;\n position: absolute;\n width: fit-content;\n height: fit-content;\n box-sizing: content-box;\n color: dimgray;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n background-color: white;\n border-color: dimgray;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-circview-toolbar {\n position: relative;\n width: 100%;\n height: 32px;\n background-color: lightgrey;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n\n.igv-circview-toolbar-button-container {\n height: 100%;\n width: fit-content;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-toolbar-button-container > div {\n margin: 4px;\n}\n\n.igv-circview-track-panel {\n z-index: 1024;\n position: absolute;\n top: 33px;\n left: 0;\n width: 100%;\n height: fit-content;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n.igv-circview-track-panel > div {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-track-panel > div > div {\n margin: 4px;\n}\n\n.igv-circview-swatch-button {\n cursor: pointer;\n padding: 5px;\n width: 8px;\n height: 8px;\n border: 1px solid #8d8b8b;\n border-radius: 16px;\n}\n\n.igv-circview-button {\n cursor: pointer;\n padding: 5px;\n color: #444;\n vertical-align: middle;\n text-align: center;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n border: 1px solid #8d8b8b;\n border-radius: 4px;\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.2);\n}\n\n.igv-circview-button:hover {\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n.igv-circview-button:active {\n color: #007bff;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n/*# sourceMappingURL=circular-view.css.map */\n';
48238
+ const css = '.igv-circview-container {\n z-index: 2048;\n width: fit-content;\n height: fit-content;\n box-sizing: content-box;\n color: dimgray;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n background-color: white;\n border-color: dimgray;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-circview-toolbar {\n position: relative;\n width: 100%;\n height: 32px;\n background-color: lightgrey;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n\n.igv-circview-toolbar-button-container {\n height: 100%;\n width: fit-content;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-toolbar-button-container > div {\n margin: 4px;\n}\n\n.igv-circview-track-panel {\n z-index: 1024;\n position: absolute;\n top: 33px;\n left: 0;\n width: 100%;\n height: fit-content;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n.igv-circview-track-panel > div {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-track-panel > div > div {\n margin: 4px;\n}\n\n.igv-circview-swatch-button {\n cursor: pointer;\n padding: 5px;\n width: 8px;\n height: 8px;\n border: 1px solid #8d8b8b;\n border-radius: 16px;\n}\n\n.igv-circview-button {\n cursor: pointer;\n padding: 5px;\n color: #444;\n vertical-align: middle;\n text-align: center;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n border: 1px solid #8d8b8b;\n border-radius: 4px;\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.2);\n}\n\n.igv-circview-button:hover {\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n.igv-circview-button:active {\n color: #007bff;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n/*# sourceMappingURL=circular-view.css.map */\n';
48323
48239
  const style = document.createElement('style');
48324
48240
  style.setAttribute('type', 'text/css');
48325
48241
  style.innerHTML = css;
@@ -48359,20 +48275,36 @@
48359
48275
  const chords = [];
48360
48276
 
48361
48277
  for (let a of alignments) {
48362
- const mate = a.mate;
48363
-
48364
- if (mate && mate.chr && mate.position) {
48365
- chords.push({
48366
- uniqueId: a.readName,
48367
- refName: shortChrName(a.chr),
48368
- start: a.start,
48369
- end: a.end,
48370
- mate: {
48371
- refName: shortChrName(mate.chr),
48372
- start: mate.position - 1,
48373
- end: mate.position
48374
- }
48375
- });
48278
+ if (a.paired) {
48279
+ if (a.firstAlignment && a.secondAlignment) {
48280
+ chords.push({
48281
+ uniqueId: a.readName,
48282
+ refName: shortChrName(a.firstAlignment.chr),
48283
+ start: a.firstAlignment.start,
48284
+ end: a.firstAlignment.end,
48285
+ mate: {
48286
+ refName: shortChrName(a.secondAlignment.chr),
48287
+ start: a.secondAlignment.start,
48288
+ end: a.secondAlignment.end
48289
+ }
48290
+ });
48291
+ }
48292
+ } else {
48293
+ const mate = a.mate;
48294
+
48295
+ if (mate && mate.chr && mate.position) {
48296
+ chords.push({
48297
+ uniqueId: a.readName,
48298
+ refName: shortChrName(a.chr),
48299
+ start: a.start,
48300
+ end: a.end,
48301
+ mate: {
48302
+ refName: shortChrName(mate.chr),
48303
+ start: mate.position - 1,
48304
+ end: mate.position
48305
+ }
48306
+ });
48307
+ }
48376
48308
  }
48377
48309
  }
48378
48310
 
@@ -48380,9 +48312,7 @@
48380
48312
  };
48381
48313
 
48382
48314
  const makeSupplementalAlignmentChords = alignments => {
48383
- const chords = [];
48384
-
48385
- for (let a of alignments) {
48315
+ const makeChords = a => {
48386
48316
  const sa = a.tags()['SA'];
48387
48317
  const supAl = createSupplementaryAlignments(sa);
48388
48318
  let n = 0;
@@ -48402,6 +48332,20 @@
48402
48332
  });
48403
48333
  }
48404
48334
  }
48335
+ };
48336
+
48337
+ const chords = [];
48338
+
48339
+ for (let a of alignments) {
48340
+ if (a.paired) {
48341
+ makeChords(a.firstAlignment);
48342
+
48343
+ if (a.secondAlignment) {
48344
+ makeChords(a.secondAlignment);
48345
+ }
48346
+ } else {
48347
+ makeChords(a);
48348
+ }
48405
48349
  }
48406
48350
 
48407
48351
  return chords;
@@ -48467,52 +48411,125 @@
48467
48411
  return regions;
48468
48412
  }
48469
48413
 
48414
+ function sendChords(chords, track, refFrame, alpha) {
48415
+ const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? track.color : getChrColor(refFrame.chr), alpha);
48416
+ const trackColor = IGVColor.addAlpha(track.color || 'rgb(0,0,255)', alpha); // name the chord set to include locus and filtering information
48417
+
48418
+ const encodedName = track.name.replaceAll(' ', '%20');
48419
+ const chordSetName = "all" === refFrame.chr ? encodedName : `${encodedName} ${refFrame.chr}:${refFrame.start}-${refFrame.end}`;
48420
+ track.browser.circularView.addChords(chords, {
48421
+ track: chordSetName,
48422
+ color: chordSetColor,
48423
+ trackColor: trackColor
48424
+ }); // show circular view if hidden
48425
+
48426
+ if (!track.browser.circularViewVisible) track.browser.circularViewVisible = true;
48427
+ }
48428
+
48470
48429
  function createCircularView(el, browser) {
48471
48430
  const circularView = new CircularView(el, {
48472
48431
  onChordClick: (feature, chordTrack, pluginManager) => {
48473
48432
  const f1 = feature.data;
48474
48433
  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);
48434
+ addFrameForFeature(f1);
48435
+ addFrameForFeature(f2);
48436
+
48437
+ function addFrameForFeature(feature) {
48438
+ feature.chr = browser.genome.getChromosomeName(feature.refName);
48439
+ let frameFound = false;
48440
+
48441
+ for (let referenceFrame of browser.referenceFrameList) {
48442
+ const l = Locus.fromLocusString(referenceFrame.getLocusString());
48443
+
48444
+ if (l.contains(feature)) {
48445
+ frameFound = true;
48446
+ break;
48447
+ } else if (l.overlaps(feature)) {
48448
+ referenceFrame.extend(feature);
48449
+ frameFound = true;
48450
+ break;
48499
48451
  }
48500
48452
  }
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
48453
 
48509
- const searchString = loci.map(l => l.getLocusString()).join(" ");
48510
- browser.search(searchString);
48454
+ if (!frameFound) {
48455
+ const flanking = 2000;
48456
+ const center = (feature.start + feature.end) / 2;
48457
+ browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
48458
+ }
48459
+ }
48511
48460
  }
48512
48461
  });
48513
48462
  return circularView;
48514
48463
  }
48515
48464
 
48465
+ class PairedEndStats {
48466
+ constructor(alignments, _ref) {
48467
+ let {
48468
+ minTLENPercentile,
48469
+ maxTLENPercentile
48470
+ } = _ref;
48471
+ this.totalCount = 0;
48472
+ this.frCount = 0;
48473
+ this.rfCount = 0;
48474
+ this.ffCount = 0;
48475
+ this.sumF = 0;
48476
+ this.sumF2 = 0;
48477
+ this.lp = minTLENPercentile === undefined ? 0.1 : minTLENPercentile;
48478
+ this.up = maxTLENPercentile === undefined ? 99.5 : maxTLENPercentile;
48479
+ this.isizes = [];
48480
+ this.compute(alignments);
48481
+ }
48482
+
48483
+ compute(alignments) {
48484
+ for (let alignment of alignments) {
48485
+ if (alignment.isProperPair()) {
48486
+ var tlen = Math.abs(alignment.fragmentLength);
48487
+ this.sumF += tlen;
48488
+ this.sumF2 += tlen * tlen;
48489
+ this.isizes.push(tlen);
48490
+ var po = alignment.pairOrientation;
48491
+
48492
+ if (typeof po === "string" && po.length === 4) {
48493
+ var tmp = '' + po.charAt(0) + po.charAt(2);
48494
+
48495
+ switch (tmp) {
48496
+ case 'FF':
48497
+ case 'RR':
48498
+ this.ffCount++;
48499
+ break;
48500
+
48501
+ case "FR":
48502
+ this.frCount++;
48503
+ break;
48504
+
48505
+ case "RF":
48506
+ this.rfCount++;
48507
+ }
48508
+ }
48509
+
48510
+ this.totalCount++;
48511
+ }
48512
+ }
48513
+
48514
+ 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";
48515
+ this.minTLEN = this.lp === 0 ? 0 : percentile(this.isizes, this.lp);
48516
+ this.maxTLEN = percentile(this.isizes, this.up); // var fMean = this.sumF / this.totalCount
48517
+ // var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount))
48518
+ // this.minTLEN = fMean - 3 * stdDev
48519
+ // this.maxTLEN = fMean + 3 * stdDev
48520
+ }
48521
+
48522
+ }
48523
+
48524
+ function percentile(array, p) {
48525
+ if (array.length === 0) return undefined;
48526
+ var k = Math.floor(array.length * (p / 100));
48527
+ array.sort(function (a, b) {
48528
+ return a - b;
48529
+ });
48530
+ return array[k];
48531
+ }
48532
+
48516
48533
  /*
48517
48534
  * The MIT License (MIT)
48518
48535
  *
@@ -48572,10 +48589,7 @@
48572
48589
  this.showInsertions = false !== config.showInsertions;
48573
48590
  this.showMismatches = false !== config.showMismatches;
48574
48591
  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
48592
+ this.coverageColor = config.coverageColor; // The sort object can be an array in the case of multi-locus view, however if multiple sort positions
48579
48593
  // are present for a given reference frame the last one will take precedence
48580
48594
 
48581
48595
  if (config.sort) {
@@ -48603,12 +48617,22 @@
48603
48617
  return this._height;
48604
48618
  }
48605
48619
 
48620
+ get minTemplateLength() {
48621
+ const configMinTLEN = this.config.minTLEN !== undefined ? this.config.minTLEN : this.config.minFragmentLength;
48622
+ return configMinTLEN !== undefined ? configMinTLEN : this._pairedEndStats ? this._pairedEndStats.minTLEN : 0;
48623
+ }
48624
+
48625
+ get maxTemplateLength() {
48626
+ const configMaxTLEN = this.config.maxTLEN !== undefined ? this.config.maxTLEN : this.config.maxFragmentLength;
48627
+ return configMaxTLEN !== undefined ? configMaxTLEN : this._pairedEndStats ? this._pairedEndStats.maxTLEN : 1000;
48628
+ }
48629
+
48606
48630
  sort(options) {
48607
48631
  options = this.assignSort(options);
48608
48632
 
48609
48633
  for (let vp of this.trackView.viewports) {
48610
48634
  if (vp.containsPosition(options.chr, options.position)) {
48611
- const alignmentContainer = vp.getCachedFeatures();
48635
+ const alignmentContainer = vp.cachedFeatures;
48612
48636
 
48613
48637
  if (alignmentContainer) {
48614
48638
  sortAlignmentRows(options, alignmentContainer);
@@ -48643,16 +48667,16 @@
48643
48667
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel, viewport) {
48644
48668
  const alignmentContainer = await this.featureSource.getAlignments(chr, bpStart, bpEnd);
48645
48669
 
48646
- if (alignmentContainer.alignments && alignmentContainer.alignments.length > 99) {
48647
- if (undefined === this.minFragmentLength) {
48648
- this.minFragmentLength = alignmentContainer.pairedEndStats.lowerFragmentLength;
48649
- }
48670
+ if (alignmentContainer.paired && !this._pairedEndStats && !this.config.maxFragmentLength) {
48671
+ const pairedEndStats = new PairedEndStats(alignmentContainer.alignments, this.config);
48650
48672
 
48651
- if (undefined === this.maxFragmentLength) {
48652
- this.maxFragmentLength = alignmentContainer.pairedEndStats.upperFragmentLength;
48673
+ if (pairedEndStats.totalCount > 99) {
48674
+ this._pairedEndStats = pairedEndStats;
48653
48675
  }
48654
48676
  }
48655
48677
 
48678
+ alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
48679
+
48656
48680
  const sort = this.sortObject;
48657
48681
 
48658
48682
  if (sort) {
@@ -48757,7 +48781,7 @@
48757
48781
  label: 'pair orientation'
48758
48782
  });
48759
48783
  colorByMenuItems.push({
48760
- key: 'fragmentLength',
48784
+ key: 'tlen',
48761
48785
  label: 'insert size (TLEN)'
48762
48786
  });
48763
48787
  colorByMenuItems.push({
@@ -48868,36 +48892,19 @@
48868
48892
  this.trackView.repaintViews();
48869
48893
  }
48870
48894
  });
48871
- } // Experimental JBrowse feature
48895
+ } // Add chords to JBrowse circular view, if present
48872
48896
 
48873
48897
 
48874
- if (this.browser.circularView && true === this.browser.circularViewVisible && (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
48898
+ if (this.browser.circularView && (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
48875
48899
  menuItems.push('<hr/>');
48876
48900
 
48877
48901
  if (this.alignmentTrack.hasPairs) {
48878
48902
  menuItems.push({
48879
48903
  label: 'Add discordant pairs to circular view',
48880
48904
  click: () => {
48881
- const maxFragmentLength = this.maxFragmentLength;
48882
- const inView = [];
48883
-
48884
48905
  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
- }
48906
+ this.addPairedChordsForViewport(viewport);
48892
48907
  }
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
48908
  }
48902
48909
  });
48903
48910
  }
@@ -48906,25 +48913,9 @@
48906
48913
  menuItems.push({
48907
48914
  label: 'Add split reads to circular view',
48908
48915
  click: () => {
48909
- const inView = [];
48910
-
48911
48916
  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
- }
48917
+ this.addSplitChordsForViewport(viewport);
48920
48918
  }
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
48919
  }
48929
48920
  });
48930
48921
  }
@@ -49043,7 +49034,7 @@
49043
49034
  }
49044
49035
 
49045
49036
  getCachedAlignmentContainers() {
49046
- return this.trackView.viewports.map(vp => vp.getCachedFeatures());
49037
+ return this.trackView.viewports.map(vp => vp.cachedFeatures);
49047
49038
  }
49048
49039
 
49049
49040
  get dataRange() {
@@ -49069,6 +49060,64 @@
49069
49060
  set autoscale(autoscale) {
49070
49061
  this.coverageTrack.autoscale = autoscale;
49071
49062
  }
49063
+ /**
49064
+ * Add chords to the circular view for the given viewport, represented by its reference frame
49065
+ * @param refFrame
49066
+ */
49067
+
49068
+
49069
+ addPairedChordsForViewport(viewport) {
49070
+ const maxTemplateLength = this.maxTemplateLength;
49071
+ const inView = [];
49072
+ const refFrame = viewport.referenceFrame;
49073
+
49074
+ for (let a of viewport.cachedFeatures.allAlignments()) {
49075
+ if (a.end >= refFrame.start && a.start <= refFrame.end) {
49076
+ if (a.paired) {
49077
+ if (a.end - a.start > maxTemplateLength) {
49078
+ inView.push(a);
49079
+ }
49080
+ } else {
49081
+ if (a.mate && a.mate.chr && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxTemplateLength)) {
49082
+ inView.push(a);
49083
+ }
49084
+ }
49085
+ }
49086
+ }
49087
+
49088
+ const chords = makePairedAlignmentChords(inView);
49089
+ sendChords(chords, this, refFrame, 0.02); // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
49090
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
49091
+ //
49092
+ // // name the chord set to include track name and locus
49093
+ // const encodedName = this.name.replaceAll(' ', '%20')
49094
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
49095
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
49096
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
49097
+ }
49098
+
49099
+ addSplitChordsForViewport(viewport) {
49100
+ const inView = [];
49101
+ const refFrame = viewport.referenceFrame;
49102
+
49103
+ for (let a of viewport.cachedFeatures.allAlignments()) {
49104
+ const sa = a.hasTag('SA');
49105
+
49106
+ if (a.end >= refFrame.start && a.start <= refFrame.end && sa) {
49107
+ inView.push(a);
49108
+ }
49109
+ }
49110
+
49111
+ const chords = makeSupplementalAlignmentChords(inView);
49112
+ sendChords(chords, this, refFrame, 0.02); // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
49113
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
49114
+ //
49115
+ // // name the chord set to include track name and locus
49116
+ // const encodedName = this.name.replaceAll(' ', '%20')
49117
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
49118
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
49119
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
49120
+ }
49072
49121
 
49073
49122
  }
49074
49123
 
@@ -49179,7 +49228,7 @@
49179
49228
  }
49180
49229
 
49181
49230
  getClickedObject(clickState) {
49182
- let features = clickState.viewport.getCachedFeatures();
49231
+ let features = clickState.viewport.cachedFeatures;
49183
49232
  if (!features || features.length === 0) return;
49184
49233
  const genomicLocation = Math.floor(clickState.genomicLocation);
49185
49234
  const coverageMap = features.coverageMap;
@@ -49265,14 +49314,14 @@
49265
49314
  this.deletionColor = config.deletionColor || "black";
49266
49315
  this.skippedColor = config.skippedColor || "rgb(150, 170, 170)";
49267
49316
  this.pairConnectorColor = config.pairConnectorColor;
49268
- this.smallFragmentLengthColor = config.smallFragmentLengthColor || "rgb(0, 0, 150)";
49269
- this.largeFragmentLengthColor = config.largeFragmentLengthColor || "rgb(200, 0, 0)";
49317
+ this.smallTLENColor = config.smallTLENColor || config.smallFragmentLengthColor || "rgb(0, 0, 150)";
49318
+ this.largeTLENColor = config.largeTLENColor || config.largeFragmentLengthColor || "rgb(200, 0, 0)";
49270
49319
  this.pairOrientation = config.pairOrienation || 'fr';
49271
49320
  this.pairColors = {};
49272
49321
  this.pairColors["RL"] = config.rlColor || "rgb(0, 150, 0)";
49273
49322
  this.pairColors["RR"] = config.rrColor || "rgb(20, 50, 200)";
49274
49323
  this.pairColors["LL"] = config.llColor || "rgb(0, 150, 150)";
49275
- this.colorBy = config.colorBy || "pairOrientation";
49324
+ this.colorBy = config.colorBy || "unexpectedPair";
49276
49325
  this.colorByTag = config.colorByTag ? config.colorByTag.toUpperCase() : undefined;
49277
49326
  this.bamColorTag = config.bamColorTag === undefined ? "YC" : config.bamColorTag;
49278
49327
  this.hideSmallIndels = config.hideSmallIndels;
@@ -49371,7 +49420,7 @@
49371
49420
  for (let alignment of alignmentRow.alignments) {
49372
49421
  this.hasPairs = this.hasPairs || alignment.isPaired();
49373
49422
 
49374
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
49423
+ if (this.browser.circularView) {
49375
49424
  // This is an expensive check, only do it if needed
49376
49425
  this.hasSupplemental = this.hasSupplemental || alignment.hasTag('SA');
49377
49426
  }
@@ -49627,7 +49676,7 @@
49627
49676
  direction: direction
49628
49677
  };
49629
49678
  this.parent.sortObject = newSortObject;
49630
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
49679
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
49631
49680
  viewport.repaint();
49632
49681
  };
49633
49682
 
@@ -49679,7 +49728,7 @@
49679
49728
  };
49680
49729
  this.sortByTag = tag;
49681
49730
  this.parent.sortObject = newSortObject;
49682
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
49731
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
49683
49732
  viewport.repaint();
49684
49733
  }
49685
49734
  }
@@ -49703,8 +49752,12 @@
49703
49752
  const referenceFrame = clickState.viewport.referenceFrame;
49704
49753
 
49705
49754
  if (this.browser.genome.getChromosome(clickedAlignment.mate.chr)) {
49706
- this.highlightedAlignmentReadNamed = clickedAlignment.readName;
49707
- this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame);
49755
+ this.highlightedAlignmentReadNamed = clickedAlignment.readName; //this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame)
49756
+
49757
+ const bpWidth = referenceFrame.end - referenceFrame.start;
49758
+ const frameStart = clickedAlignment.mate.position - bpWidth / 2;
49759
+ const frameEnd = clickedAlignment.mate.position + bpWidth / 2;
49760
+ this.browser.addMultiLocusPanel(clickedAlignment.mate.chr, frameStart, frameEnd, referenceFrame);
49708
49761
  } else {
49709
49762
  Alert.presentAlert(`Reference does not contain chromosome: ${clickedAlignment.mate.chr}`);
49710
49763
  }
@@ -49717,9 +49770,7 @@
49717
49770
  list.push({
49718
49771
  label: 'View read sequence',
49719
49772
  click: () => {
49720
- const alignment = clickedAlignment;
49721
- if (!alignment) return;
49722
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
49773
+ const seqstring = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
49723
49774
 
49724
49775
  if (!seqstring || "*" === seqstring) {
49725
49776
  Alert.presentAlert("Read sequence: *");
@@ -49732,12 +49783,16 @@
49732
49783
  if (isSecureContext()) {
49733
49784
  list.push({
49734
49785
  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("");
49786
+ click: async () => {
49787
+ const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
49739
49788
 
49740
- navigator.clipboard.writeText(seqstring);
49789
+ try {
49790
+ //console.log(`seq: ${seq}`)
49791
+ await navigator.clipboard.writeText(seq);
49792
+ } catch (e) {
49793
+ console.error(e);
49794
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
49795
+ }
49741
49796
  }
49742
49797
  });
49743
49798
  }
@@ -49747,25 +49802,12 @@
49747
49802
  } // Experimental JBrowse feature
49748
49803
 
49749
49804
 
49750
- if (this.browser.circularView && true === this.browser.circularViewVisible && (this.hasPairs || this.hasSupplemental)) {
49805
+ if (this.browser.circularView && (this.hasPairs || this.hasSupplemental)) {
49751
49806
  if (this.hasPairs) {
49752
49807
  list.push({
49753
49808
  label: 'Add discordant pairs to circular view',
49754
49809
  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
- });
49810
+ this.parent.addPairedChordsForViewport(viewport);
49769
49811
  }
49770
49812
  });
49771
49813
  }
@@ -49774,23 +49816,7 @@
49774
49816
  list.push({
49775
49817
  label: 'Add split reads to circular view',
49776
49818
  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
- });
49819
+ this.parent.addSplitChordsForViewport(viewport);
49794
49820
  }
49795
49821
  });
49796
49822
  }
@@ -49806,7 +49832,7 @@
49806
49832
  const y = clickState.y;
49807
49833
  const genomicLocation = clickState.genomicLocation;
49808
49834
  const showSoftClips = this.parent.showSoftClips;
49809
- let features = viewport.getCachedFeatures();
49835
+ let features = viewport.cachedFeatures;
49810
49836
  if (!features || features.length === 0) return;
49811
49837
  let packedAlignmentRows = features.packedAlignmentRows;
49812
49838
  let downsampledIntervals = features.downsampledIntervals;
@@ -49880,11 +49906,15 @@
49880
49906
  case "unexpectedPair":
49881
49907
  case "pairOrientation":
49882
49908
  if (this.pairOrientation && alignment.pairOrientation) {
49883
- var oTypes = orientationTypes[this.pairOrientation];
49909
+ const oTypes = orientationTypes[this.pairOrientation];
49884
49910
 
49885
49911
  if (oTypes) {
49886
- var pairColor = this.pairColors[oTypes[alignment.pairOrientation]];
49887
- if (pairColor) color = pairColor;
49912
+ const pairColor = this.pairColors[oTypes[alignment.pairOrientation]];
49913
+
49914
+ if (pairColor) {
49915
+ color = pairColor;
49916
+ break;
49917
+ }
49888
49918
  }
49889
49919
  }
49890
49920
 
@@ -49892,13 +49922,16 @@
49892
49922
  break;
49893
49923
  }
49894
49924
 
49925
+ case "tlen":
49895
49926
  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;
49927
+ if (alignment.mate && alignment.isMateMapped()) {
49928
+ if (alignment.mate.chr !== alignment.chr) {
49929
+ color = getChrColor(alignment.mate.chr);
49930
+ } else if (this.parent.minTemplateLength && Math.abs(alignment.fragmentLength) < this.parent.minTemplateLength) {
49931
+ color = this.smallTLENColor;
49932
+ } else if (this.parent.maxTemplateLength && Math.abs(alignment.fragmentLength) > this.parent.maxTemplateLength) {
49933
+ color = this.largeTLENColor;
49934
+ }
49902
49935
  }
49903
49936
 
49904
49937
  break;
@@ -49940,10 +49973,7 @@
49940
49973
  alignmentContainer.packedAlignmentRows.sort(function (rowA, rowB) {
49941
49974
  const i = rowA.score > rowB.score ? 1 : rowA.score < rowB.score ? -1 : 0;
49942
49975
  return true === direction ? i : -i;
49943
- }); // For debugging
49944
- // for(let r of alignmentContainer.packedAlignmentRows) {
49945
- // console.log(r.score);
49946
- // }
49976
+ });
49947
49977
  }
49948
49978
 
49949
49979
  function shadedBaseColor(qual, baseColor) {
@@ -50098,7 +50128,7 @@
50098
50128
  });
50099
50129
  this.$viewport.append(this.$rulerLabel);
50100
50130
  this.$rulerLabel.click(async () => {
50101
- await this.browser.selectMultiLocusPanel(this.referenceFrame); // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
50131
+ await this.browser.gotoMultilocusPanel(this.referenceFrame); // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
50102
50132
  // for (let referenceFrame of removals) {
50103
50133
  // await this.browser.removeMultiLocusPanel(referenceFrame)
50104
50134
  // }
@@ -50204,7 +50234,10 @@
50204
50234
  currentViewport = this;
50205
50235
  this.$tooltip.show();
50206
50236
  } else if (currentViewport.guid !== this.guid) {
50207
- currentViewport.$tooltip.hide();
50237
+ if (currentViewport.$tooltip) {
50238
+ currentViewport.$tooltip.hide();
50239
+ }
50240
+
50208
50241
  this.$tooltip.show();
50209
50242
  currentViewport = this;
50210
50243
  } else {
@@ -50238,7 +50271,9 @@
50238
50271
  }); // hide tooltip when movement stops
50239
50272
 
50240
50273
  clearTimeout(timer);
50241
- timer = setTimeout(() => this.$tooltip.hide(), toolTipTimeout);
50274
+ timer = setTimeout(() => {
50275
+ if (this.$tooltip) this.$tooltip.hide();
50276
+ }, toolTipTimeout);
50242
50277
  }
50243
50278
  }
50244
50279
 
@@ -50253,65 +50288,6 @@
50253
50288
 
50254
50289
  }
50255
50290
 
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
50291
  /*
50316
50292
  * The MIT License (MIT)
50317
50293
  *
@@ -50343,60 +50319,30 @@
50343
50319
  }
50344
50320
 
50345
50321
  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;
50322
+ this.canvas = document.createElement('canvas');
50323
+ this.canvas.className = 'igv-ideogram-canvas';
50324
+ this.$content.append($$1(this.canvas));
50325
+ this.ideogram_ctx = this.canvas.getContext('2d');
50355
50326
  this.addMouseHandlers();
50356
50327
  }
50357
50328
 
50358
50329
  addMouseHandlers() {
50359
- this.addBrowserObserver();
50360
50330
  this.addViewportClickHandler(this.$viewport.get(0));
50361
50331
  }
50362
50332
 
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
50333
  addViewportClickHandler(viewport) {
50334
+ this.boundClickHandler = clickHandler.bind(this);
50335
+ viewport.addEventListener('click', this.boundClickHandler);
50336
+
50388
50337
  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
50338
  const {
50393
50339
  xNormalized,
50394
50340
  width
50395
50341
  } = translateMouseCoordinates$1(event, this.ideogram_ctx.canvas);
50396
50342
  const {
50397
50343
  bpLength
50398
- } = this.browser.genome.getChromosome(referenceFrame.chr);
50399
- const locusLength = referenceFrame.bpPerPixel * width;
50344
+ } = this.browser.genome.getChromosome(this.referenceFrame.chr);
50345
+ const locusLength = this.referenceFrame.bpPerPixel * width;
50400
50346
  const chrCoveragePercentage = locusLength / bpLength;
50401
50347
  let xPercentage = xNormalized;
50402
50348
 
@@ -50410,18 +50356,11 @@
50410
50356
 
50411
50357
  const ss = Math.round((xPercentage - chrCoveragePercentage / 2.0) * bpLength);
50412
50358
  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);
50359
+ this.referenceFrame.start = ss;
50360
+ this.referenceFrame.end = ee;
50361
+ this.referenceFrame.bpPerPixel = (ee - ss) / width;
50362
+ this.browser.updateViews(this.referenceFrame, this.browser.trackViews, true);
50417
50363
  }
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
50364
  }
50426
50365
 
50427
50366
  setWidth(width) {
@@ -50439,14 +50378,22 @@
50439
50378
  context.restore();
50440
50379
  }
50441
50380
 
50442
- update(context, pixelWidth, pixelHeight, referenceFrame) {
50443
- this.$canvas.hide();
50444
- IGVGraphics.configureHighDPICanvas(context, pixelWidth, pixelHeight);
50381
+ repaint() {
50382
+ this.draw({
50383
+ referenceFrame: this.referenceFrame
50384
+ });
50385
+ }
50386
+
50387
+ draw(_ref) {
50388
+ let {
50389
+ referenceFrame
50390
+ } = _ref;
50391
+ IGVGraphics.configureHighDPICanvas(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height());
50445
50392
  this.trackView.track.draw({
50446
- context,
50393
+ context: this.ideogram_ctx,
50447
50394
  referenceFrame,
50448
- pixelWidth,
50449
- pixelHeight
50395
+ pixelWidth: this.$viewport.width(),
50396
+ pixelHeight: this.$viewport.height()
50450
50397
  });
50451
50398
  }
50452
50399
 
@@ -50484,7 +50431,7 @@
50484
50431
  function createViewport(trackView, column, referenceFrame, width) {
50485
50432
  if ('ruler' === trackView.track.type) {
50486
50433
  return new RulerViewport(trackView, column, referenceFrame, width);
50487
- } else if ('ideogram' === trackView.track.type) {
50434
+ } else if ('ideogram' === trackView.track.id) {
50488
50435
  return new IdeogramViewport(trackView, column, referenceFrame, width);
50489
50436
  } else {
50490
50437
  return new TrackViewport(trackView, column, referenceFrame, width);
@@ -50659,7 +50606,7 @@
50659
50606
  sampleNameViewport.setWidth(this.browser.sampleNameViewportWidth);
50660
50607
  }
50661
50608
 
50662
- this.browser.resize();
50609
+ this.browser.layoutChange();
50663
50610
  }
50664
50611
  };
50665
50612
  this.browser.inputDialog.present(config, event);
@@ -51011,7 +50958,6 @@
51011
50958
 
51012
50959
  class TrackView {
51013
50960
  constructor(browser, columnContainer, track) {
51014
- this.namespace = `trackview-${guid$2()}`;
51015
50961
  this.browser = browser;
51016
50962
  this.track = track;
51017
50963
  track.trackView = this;
@@ -51037,7 +50983,7 @@
51037
50983
 
51038
50984
  addDOMToColumnContainer(browser, columnContainer, referenceFrameList) {
51039
50985
  // Axis
51040
- this.axis = this.createAxis(browser, this.track); // Track Viewports
50986
+ this.axis = this.createAxis(browser, this.track); // Create a viewport for each reference frame
51041
50987
 
51042
50988
  this.viewports = [];
51043
50989
  const viewportWidth = browser.calculateViewportWidth(referenceFrameList.length);
@@ -51104,7 +51050,6 @@
51104
51050
  this.axis.remove(); // Track Viewports
51105
51051
 
51106
51052
  for (let viewport of this.viewports) {
51107
- viewport.removeMouseHandlers();
51108
51053
  viewport.$viewport.remove();
51109
51054
  } // SampleName Viewport
51110
51055
 
@@ -51222,7 +51167,7 @@
51222
51167
  $viewport.height(newHeight);
51223
51168
  }
51224
51169
 
51225
- this.sampleNameViewport.viewport.style.height = `${newHeight}px`; // If the track does not manage its own content height set it here
51170
+ 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
51171
 
51227
51172
  if (typeof this.track.computePixelHeight !== "function") {
51228
51173
  for (let vp of this.viewports) {
@@ -51274,14 +51219,6 @@
51274
51219
  if (viewport.isLoading()) return true;
51275
51220
  }
51276
51221
  }
51277
-
51278
- resize(viewportWidth) {
51279
- for (let viewport of this.viewports) {
51280
- viewport.setWidth(viewportWidth);
51281
- }
51282
-
51283
- this.updateViews(true);
51284
- }
51285
51222
  /**
51286
51223
  * Repaint all viewports without loading any new data. Use this for events that change visual aspect of data,
51287
51224
  * e.g. color, sort order, etc, but do not change the genomic state.
@@ -51290,7 +51227,9 @@
51290
51227
 
51291
51228
  repaintViews() {
51292
51229
  for (let viewport of this.viewports) {
51293
- viewport.repaint();
51230
+ if (viewport.isVisible()) {
51231
+ viewport.repaint();
51232
+ }
51294
51233
  }
51295
51234
 
51296
51235
  if (typeof this.track.paintAxis === 'function') {
@@ -51312,41 +51251,60 @@
51312
51251
  setTrackLabelName(name) {
51313
51252
  this.viewports.forEach(viewport => viewport.setTrackLabel(name));
51314
51253
  }
51254
+ /**
51255
+ * Called in response to a window resize event, change in # of multilocus panels, or other event that changes
51256
+ * the width of the track view.
51257
+ *
51258
+ * @param viewportWidth The width of each viewport in this track view.
51259
+ */
51260
+
51261
+
51262
+ resize(viewportWidth) {
51263
+ for (let viewport of this.viewports) {
51264
+ viewport.setWidth(viewportWidth);
51265
+ }
51266
+ }
51315
51267
  /**
51316
51268
  * Update viewports to reflect current genomic state, possibly loading additional data.
51269
+ *
51270
+ * @param force - if true, force a repaint even if no new data is loaded
51271
+ * @returns {Promise<void>}
51317
51272
  */
51318
51273
 
51319
51274
 
51320
- async updateViews(force) {
51275
+ async updateViews() {
51321
51276
  if (!(this.browser && this.browser.referenceFrameList)) return;
51322
51277
  const visibleViewports = this.viewports.filter(viewport => viewport.isVisible()); // Shift viewports left/right to current genomic state (pans canvas)
51323
51278
 
51324
- visibleViewports.forEach(viewport => viewport.shift());
51325
- const isDragging = this.browser.dragObject;
51279
+ visibleViewports.forEach(viewport => viewport.shift()); // If dragging (panning) return
51326
51280
 
51327
- if (isDragging) {
51281
+ if (this.browser.dragObject) {
51328
51282
  return;
51329
- } // rpv: viewports whose image (canvas) does not fully cover current genomic range
51283
+ } // Get viewports to repaint
51330
51284
 
51331
51285
 
51332
- const reloadableViewports = this.viewportsToReload(force); // Trigger viewport to load features needed to cover current genomic range
51286
+ 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
51287
+
51288
+ viewportsToRepaint = viewportsToRepaint.filter(viewport => viewport.checkZoomIn()); // Get viewports that require a data load
51289
+
51290
+ const viewportsToReload = viewportsToRepaint.filter(viewport => viewport.needsReload()); // Trigger viewport to load features needed to cover current genomic range
51333
51291
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
51334
51292
 
51335
- for (let viewport of reloadableViewports) {
51293
+ for (let viewport of viewportsToReload) {
51336
51294
  await viewport.loadFeatures();
51337
51295
  }
51338
51296
 
51339
51297
  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)
51298
+ // Special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
51341
51299
  // section depends on data from all the views. We only need to adjust this however if any data was loaded
51342
51300
  // (i.e. reloadableViewports.length > 0)
51343
51301
 
51344
- if (this.track && typeof this.track.variantRowCount === 'function' && reloadableViewports.length > 0) {
51302
+ if (this.track && typeof this.track.variantRowCount === 'function' && viewportsToReload.length > 0) {
51345
51303
  let maxRow = 0;
51346
51304
 
51347
51305
  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));
51306
+ if (viewport.featureCache && viewport.featureCache.features) {
51307
+ maxRow = Math.max(maxRow, viewport.featureCache.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
51350
51308
  }
51351
51309
  }
51352
51310
 
@@ -51369,14 +51327,14 @@
51369
51327
  const start = referenceFrame.start;
51370
51328
  const end = start + referenceFrame.toBP($$1(visibleViewport.contentDiv).width());
51371
51329
 
51372
- if (visibleViewport.tile && visibleViewport.tile.features) {
51373
- if (typeof visibleViewport.tile.features.getMax === 'function') {
51374
- const max = visibleViewport.tile.features.getMax(start, end);
51330
+ if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
51331
+ if (typeof visibleViewport.featureCache.features.getMax === 'function') {
51332
+ const max = visibleViewport.featureCache.features.getMax(start, end);
51375
51333
  allFeatures.push({
51376
51334
  value: max
51377
51335
  });
51378
51336
  } else {
51379
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.tile.features, start, end));
51337
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end));
51380
51338
  }
51381
51339
  }
51382
51340
  }
@@ -51386,17 +51344,10 @@
51386
51344
  } else {
51387
51345
  this.track.dataRange = doAutoscale(allFeatures);
51388
51346
  }
51389
- } // Must repaint all viewports if autoscaling
51390
-
51347
+ }
51391
51348
 
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
- }
51349
+ for (let vp of viewportsToRepaint) {
51350
+ vp.repaint();
51400
51351
  }
51401
51352
 
51402
51353
  this.adjustTrackHeight(); // Repaint sample names last
@@ -51419,36 +51370,34 @@
51419
51370
  }
51420
51371
  }
51421
51372
  /**
51422
- * Return a promise to get all in-view features. Used for group autoscaling.
51373
+ * Return a promise to get all in-view features across all viewports. Used for group autoscaling.
51423
51374
  */
51424
51375
 
51425
51376
 
51426
- async getInViewFeatures(force) {
51377
+ async getInViewFeatures() {
51427
51378
  if (!(this.browser && this.browser.referenceFrameList)) {
51428
51379
  return [];
51429
- } // List of viewports that need reloading
51430
-
51380
+ }
51431
51381
 
51432
- const rpV = this.viewportsToReload(force);
51433
- const promises = rpV.map(function (vp) {
51434
- return vp.loadFeatures();
51435
- });
51436
- await Promise.all(promises);
51437
51382
  let allFeatures = [];
51438
51383
 
51439
51384
  for (let vp of this.viewports) {
51440
- if (vp.tile && vp.tile.features) {
51385
+ if (vp.needsReload()) {
51386
+ await vp.loadFeatures();
51387
+ }
51388
+
51389
+ if (vp.featureCache && vp.featureCache.features) {
51441
51390
  const referenceFrame = vp.referenceFrame;
51442
51391
  const start = referenceFrame.start;
51443
51392
  const end = start + referenceFrame.toBP($$1(vp.contentDiv).width());
51444
51393
 
51445
- if (typeof vp.tile.features.getMax === 'function') {
51446
- const max = vp.tile.features.getMax(start, end);
51394
+ if (typeof vp.featureCache.features.getMax === 'function') {
51395
+ const max = vp.featureCache.features.getMax(start, end);
51447
51396
  allFeatures.push({
51448
51397
  value: max
51449
51398
  });
51450
51399
  } else {
51451
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.tile.features, start, end));
51400
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.featureCache.features, start, end));
51452
51401
  }
51453
51402
  }
51454
51403
  }
@@ -51490,27 +51439,6 @@
51490
51439
  }
51491
51440
  }
51492
51441
 
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
51442
  createTrackScrollbar(browser) {
51515
51443
  const outerScroll = div$1();
51516
51444
  browser.columnContainer.querySelector('.igv-scrollbar-column').appendChild(outerScroll);
@@ -51604,7 +51532,7 @@
51604
51532
  }
51605
51533
 
51606
51534
  addTrackDragMouseHandlers(browser) {
51607
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
51535
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
51608
51536
  let currentDragHandle = undefined; // Mouse Down
51609
51537
 
51610
51538
  this.boundTrackDragMouseDownHandler = trackDragMouseDownHandler.bind(this);
@@ -51668,7 +51596,7 @@
51668
51596
  }
51669
51597
 
51670
51598
  removeTrackDragMouseHandlers() {
51671
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
51599
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
51672
51600
  this.dragHandle.removeEventListener('mousedown', this.boundTrackDragMouseDownHandler);
51673
51601
  document.removeEventListener('mouseup', this.boundDocumentTrackDragMouseUpHandler);
51674
51602
  this.dragHandle.removeEventListener('mouseup', this.boundTrackDragMouseEnterHandler);
@@ -52563,7 +52491,7 @@
52563
52491
  });
52564
52492
  }
52565
52493
 
52566
- findUTRs(exons, feature.cdStart, feature.cdEnd);
52494
+ findUTRs$1(exons, feature.cdStart, feature.cdEnd);
52567
52495
  feature.exons = exons;
52568
52496
  } // Optional extra columns
52569
52497
 
@@ -52668,7 +52596,7 @@
52668
52596
  });
52669
52597
  }
52670
52598
 
52671
- findUTRs(exons, cdStart, cdEnd);
52599
+ findUTRs$1(exons, cdStart, cdEnd);
52672
52600
  feature.exons = exons;
52673
52601
  return feature;
52674
52602
  }
@@ -52710,7 +52638,7 @@
52710
52638
  });
52711
52639
  }
52712
52640
 
52713
- findUTRs(exons, cdStart, cdEnd);
52641
+ findUTRs$1(exons, cdStart, cdEnd);
52714
52642
  feature.exons = exons;
52715
52643
  return feature;
52716
52644
  }
@@ -52751,12 +52679,12 @@
52751
52679
  });
52752
52680
  }
52753
52681
 
52754
- findUTRs(exons, cdStart, cdEnd);
52682
+ findUTRs$1(exons, cdStart, cdEnd);
52755
52683
  feature.exons = exons;
52756
52684
  return feature;
52757
52685
  }
52758
52686
 
52759
- function findUTRs(exons, cdStart, cdEnd) {
52687
+ function findUTRs$1(exons, cdStart, cdEnd) {
52760
52688
  for (let exon of exons) {
52761
52689
  const end = exon.end;
52762
52690
  const start = exon.start;
@@ -56315,7 +56243,7 @@
56315
56243
  this.genome = genome;
56316
56244
  this.sourceType = config.sourceType === undefined ? "file" : config.sourceType;
56317
56245
  this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
56318
- const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "tdf"]);
56246
+ const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
56319
56247
 
56320
56248
  if (config.features && Array.isArray(config.features)) {
56321
56249
  // Explicit array of features
@@ -56327,7 +56255,7 @@
56327
56255
  }
56328
56256
 
56329
56257
  this.queryable = false;
56330
- this.featureCache = new FeatureCache(features, genome);
56258
+ this.featureCache = new FeatureCache$1(features, genome);
56331
56259
  } else if (config.reader) {
56332
56260
  // Explicit reader implementation
56333
56261
  this.reader = config.reader;
@@ -56498,13 +56426,13 @@
56498
56426
  } // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
56499
56427
 
56500
56428
 
56501
- this.featureCache = new FeatureCache(features, this.genome, genomicInterval); // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
56429
+ 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
56430
 
56503
56431
  if (this.config.searchable || this.config.searchableFields) {
56504
56432
  this.addFeaturesToDB(features);
56505
56433
  }
56506
56434
  } else {
56507
- this.featureCache = new FeatureCache([], genomicInterval); // Empty cache
56435
+ this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
56508
56436
  }
56509
56437
  }
56510
56438
 
@@ -56726,10 +56654,8 @@
56726
56654
 
56727
56655
  }
56728
56656
 
56729
- //table chromatinInteract
56730
-
56731
56657
  function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
56732
- if (autoSql && 'chromatinInteract' === autoSql.table || "biginteract" === format) {
56658
+ if ("biginteract" === format || autoSql && 'chromatinInteract' === autoSql.table || 'interact' === autoSql.table) {
56733
56659
  return decodeInteract;
56734
56660
  } else {
56735
56661
  const standardFieldCount = definedFieldCount - 3;
@@ -56776,6 +56702,7 @@
56776
56702
  });
56777
56703
  }
56778
56704
 
56705
+ findUTRs(exons, feature.cdStart, feature.cdEnd);
56779
56706
  feature.exons = exons;
56780
56707
  }
56781
56708
 
@@ -56792,7 +56719,29 @@
56792
56719
  }
56793
56720
  }
56794
56721
  };
56795
- }
56722
+ } //table chromatinInteract
56723
+ // "Chromatin interaction between two regions"
56724
+ // (
56725
+ // string chrom; "Chromosome (or contig, scaffold, etc.). For interchromosomal, use 2 records"
56726
+ // uint chromStart; "Start position of lower region. For interchromosomal, set to chromStart of this region"
56727
+ // uint chromEnd; "End position of upper region. For interchromosomal, set to chromEnd of this region"
56728
+ // string name; "Name of item, for display"
56729
+ // uint score; "Score from 0-1000"
56730
+ // double value; "Strength of interaction or other data value. Typically basis for score"
56731
+ // string exp; "Experiment name (metadata for filtering). Use . if not applicable"
56732
+ // string color; "Item color. Specified as r,g,b or hexadecimal #RRGGBB or html color name, as in //www.w3.org/TR/css3-color/#html4."
56733
+ // string region1Chrom; "Chromosome of lower region. For non-directional interchromosomal, chrom of this region."
56734
+ // uint region1Start; "Start position of lower/this region"
56735
+ // uint region1End; "End position in chromosome of lower/this region"
56736
+ // string region1Name; "Identifier of lower/this region"
56737
+ // string region1Strand; "Orientation of lower/this region: + or -. Use . if not applicable"
56738
+ // string region2Chrom; "Chromosome of upper region. For non-directional interchromosomal, chrom of other region"
56739
+ // uint region2Start; "Start position in chromosome of upper/this region"
56740
+ // uint region2End; "End position in chromosome of upper/this region"
56741
+ // string region2Name; "Identifier of upper/this region"
56742
+ // string region2Strand; "Orientation of upper/this region: + or -. Use . if not applicable"
56743
+ // )
56744
+
56796
56745
 
56797
56746
  function decodeInteract(feature, tokens) {
56798
56747
  feature.chr1 = tokens[5];
@@ -56809,6 +56758,25 @@
56809
56758
  }
56810
56759
  }
56811
56760
 
56761
+ function findUTRs(exons, cdStart, cdEnd) {
56762
+ for (let exon of exons) {
56763
+ const end = exon.end;
56764
+ const start = exon.start;
56765
+
56766
+ if (end < cdStart || start > cdEnd) {
56767
+ exon.utr = true;
56768
+ } else {
56769
+ if (cdStart >= start && cdStart <= end) {
56770
+ exon.cdStart = cdStart;
56771
+ }
56772
+
56773
+ if (cdEnd >= start && cdEnd <= end) {
56774
+ exon.cdEnd = cdEnd;
56775
+ }
56776
+ }
56777
+ }
56778
+ }
56779
+
56812
56780
  function scoreShade(score, color) {
56813
56781
  const alpha = Math.min(1, 0.11 + 0.89 * (score / 779));
56814
56782
  return alpha.toString();
@@ -58254,7 +58222,7 @@
58254
58222
  return features;
58255
58223
  }
58256
58224
 
58257
- supportsWholeGenome() {
58225
+ get supportsWholeGenome() {
58258
58226
  return true;
58259
58227
  }
58260
58228
 
@@ -58359,11 +58327,12 @@
58359
58327
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
58360
58328
  * THE SOFTWARE.
58361
58329
  */
58330
+ const bbFormats = new Set(['bigwig', 'bw', 'bigbed', 'bb', 'biginteract', 'biggenepred', 'bignarrowpeak']);
58362
58331
 
58363
58332
  function FeatureSource(config, genome) {
58364
58333
  const format = config.format ? config.format.toLowerCase() : undefined;
58365
58334
 
58366
- if ('bigwig' === format || 'bigbed' === format || 'bb' === format || "biginteract" === format) {
58335
+ if (bbFormats.has(format)) {
58367
58336
  return new BWSource(config, genome);
58368
58337
  } else if ("tdf" === format) {
58369
58338
  return new TDFSource(config, genome);
@@ -58372,30 +58341,6 @@
58372
58341
  }
58373
58342
  }
58374
58343
 
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
58344
  const GtexUtils = {
58400
58345
  getTissueInfo: function (datasetId, baseURL) {
58401
58346
  datasetId = datasetId || 'gtex_v8';
@@ -58636,8 +58581,8 @@
58636
58581
  const xleft = centerX - textBox.width / 2;
58637
58582
  const xright = centerX + textBox.width / 2;
58638
58583
 
58639
- if (options.labelAllFeatures || xleft > options.rowLastX[feature.row] || gtexSelection) {
58640
- options.rowLastX[feature.row] = xright;
58584
+ if (options.labelAllFeatures || xleft > options.rowLastLabelX[feature.row] || gtexSelection) {
58585
+ options.rowLastLabelX[feature.row] = xright;
58641
58586
  IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
58642
58587
  }
58643
58588
  } finally {
@@ -58918,7 +58863,7 @@
58918
58863
  return this;
58919
58864
  }
58920
58865
 
58921
- supportsWholeGenome() {
58866
+ get supportsWholeGenome() {
58922
58867
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false;
58923
58868
  }
58924
58869
 
@@ -58977,27 +58922,31 @@
58977
58922
  if (featureList) {
58978
58923
  const rowFeatureCount = [];
58979
58924
  options.rowLastX = [];
58925
+ options.rowLastLabelX = [];
58980
58926
 
58981
58927
  for (let feature of featureList) {
58982
- const row = feature.row || 0;
58928
+ if (feature.start > bpStart && feature.end < bpEnd) {
58929
+ const row = this.displayMode === "COLLAPSED" ? 0 : feature.row || 0;
58983
58930
 
58984
- if (rowFeatureCount[row] === undefined) {
58985
- rowFeatureCount[row] = 1;
58986
- } else {
58987
- rowFeatureCount[row]++;
58988
- }
58931
+ if (rowFeatureCount[row] === undefined) {
58932
+ rowFeatureCount[row] = 1;
58933
+ } else {
58934
+ rowFeatureCount[row]++;
58935
+ }
58989
58936
 
58990
- options.rowLastX[row] = -Number.MAX_SAFE_INTEGER;
58937
+ options.rowLastX[row] = -Number.MAX_SAFE_INTEGER;
58938
+ options.rowLastLabelX[row] = -Number.MAX_SAFE_INTEGER;
58939
+ }
58991
58940
  }
58992
58941
 
58942
+ const pixelsPerFeature = pixelWidth / Math.max(...rowFeatureCount);
58993
58943
  let lastPxEnd = [];
58994
58944
 
58995
58945
  for (let feature of featureList) {
58996
58946
  if (feature.end < bpStart) continue;
58997
58947
  if (feature.start > bpEnd) break;
58998
58948
  const row = this.displayMode === 'COLLAPSED' ? 0 : feature.row;
58999
- const featureDensity = pixelWidth / rowFeatureCount[row];
59000
- options.drawLabel = options.labelAllFeatures || featureDensity > 10;
58949
+ options.drawLabel = options.labelAllFeatures || pixelsPerFeature > 10;
59001
58950
  const pxEnd = Math.ceil((feature.end - bpStart) / bpPerPixel);
59002
58951
  const last = lastPxEnd[row];
59003
58952
 
@@ -59071,15 +59020,9 @@
59071
59020
  for (let fd of featureData) {
59072
59021
  data.push(fd);
59073
59022
 
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
- }
59023
+ if (infoURL && fd.name && fd.name.toLowerCase() === "name" && fd.value && isString$3(fd.value) && !fd.value.startsWith("<")) {
59024
+ const href = infoURL.replace("$$", feature.name);
59025
+ fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
59083
59026
  }
59084
59027
  } //Array.prototype.push.apply(data, featureData);
59085
59028
  // If we have clicked over an exon number it.
@@ -59153,17 +59096,31 @@
59153
59096
  }
59154
59097
 
59155
59098
  contextMenuItemList(clickState) {
59156
- if (isSecureContext()) {
59157
- const features = this.clickedFeatures(clickState);
59099
+ const features = this.clickedFeatures(clickState);
59158
59100
 
59159
- if (features.length > 1) {
59160
- features.sort((a, b) => a.end - a.start - (b.end - b.start));
59161
- }
59101
+ if (features.length > 1) {
59102
+ features.sort((a, b) => b.end - b.start - (a.end - a.start));
59103
+ }
59104
+
59105
+ const f = features[0]; // The shortest clicked feature
59106
+
59107
+ if (f.end - f.start <= 1000000) {
59108
+ const list = [{
59109
+ label: 'View feature sequence',
59110
+ click: async () => {
59111
+ let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
59162
59112
 
59163
- const f = features[0]; // The longest feature
59113
+ if (f.strand === '-') {
59114
+ seq = reverseComplementSequence(seq);
59115
+ }
59116
+
59117
+ if (!seq) seq = "Unknown sequence";
59118
+ Alert.presentAlert(seq);
59119
+ }
59120
+ }];
59164
59121
 
59165
- if (f.end - f.start <= 1000000) {
59166
- return [{
59122
+ if (isSecureContext() && navigator.clipboard !== undefined) {
59123
+ list.push({
59167
59124
  label: 'Copy feature sequence',
59168
59125
  click: async () => {
59169
59126
  let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
@@ -59172,14 +59129,21 @@
59172
59129
  seq = reverseComplementSequence(seq);
59173
59130
  }
59174
59131
 
59175
- navigator.clipboard.writeText(seq);
59132
+ try {
59133
+ await navigator.clipboard.writeText(seq);
59134
+ } catch (e) {
59135
+ console.error(e);
59136
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
59137
+ }
59176
59138
  }
59177
- }, '<hr/>'];
59139
+ });
59178
59140
  }
59179
- } // Either not a secure context (i.e. http: protocol), or feature is too long
59180
59141
 
59181
-
59182
- return undefined;
59142
+ list.push('<hr/>');
59143
+ return list;
59144
+ } else {
59145
+ return undefined;
59146
+ }
59183
59147
  }
59184
59148
 
59185
59149
  description() {
@@ -59275,14 +59239,12 @@
59275
59239
  this.featureType = 'numeric';
59276
59240
  this.paintAxis = paintAxis;
59277
59241
  const format = config.format ? config.format.toLowerCase() : config.format;
59242
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
59243
+ this.logScale = config.logScale ? config.logScale : false;
59278
59244
 
59279
59245
  if ("bigwig" === format) {
59280
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
59281
- this.logScale = config.logScale ? config.logScale : false;
59282
59246
  this.featureSource = new BWSource(config, this.browser.genome);
59283
59247
  } else if ("tdf" === format) {
59284
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
59285
- this.logScale = config.logScale ? config.logScale : false;
59286
59248
  this.featureSource = new TDFSource(config, this.browser.genome);
59287
59249
  } else {
59288
59250
  this.featureSource = FeatureSource(config, this.browser.genome);
@@ -59520,7 +59482,7 @@
59520
59482
  }
59521
59483
  }
59522
59484
 
59523
- supportsWholeGenome() {
59485
+ get supportsWholeGenome() {
59524
59486
  return !this.config.indexURL && this.config.supportsWholeGenome !== false;
59525
59487
  }
59526
59488
  /**
@@ -60059,7 +60021,7 @@
60059
60021
 
60060
60022
  const sortHandler = sort => {
60061
60023
  const viewport = clickState.viewport;
60062
- const features = viewport.getCachedFeatures();
60024
+ const features = viewport.cachedFeatures;
60063
60025
  this.sortSamples(sort.chr, sort.start, sort.end, sort.direction, features);
60064
60026
  };
60065
60027
 
@@ -60079,7 +60041,7 @@
60079
60041
  }];
60080
60042
  }
60081
60043
 
60082
- supportsWholeGenome() {
60044
+ get supportsWholeGenome() {
60083
60045
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false;
60084
60046
  }
60085
60047
 
@@ -60165,10 +60127,16 @@
60165
60127
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
60166
60128
  * THE SOFTWARE.
60167
60129
  */
60130
+ /**
60131
+ * Represents 2 or more wig tracks overlaid on a common viewport.
60132
+ */
60168
60133
 
60169
60134
  class MergedTrack extends TrackBase {
60170
60135
  constructor(config, browser) {
60171
60136
  super(config, browser);
60137
+ this.type = "merged";
60138
+ this.featureType = 'numeric';
60139
+ this.paintAxis = paintAxis;
60172
60140
  }
60173
60141
 
60174
60142
  init(config) {
@@ -60179,21 +60147,6 @@
60179
60147
  super.init(config);
60180
60148
  }
60181
60149
 
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
60150
  async postInit() {
60198
60151
  this.tracks = [];
60199
60152
  const p = [];
@@ -60215,45 +60168,83 @@
60215
60168
  }
60216
60169
  }
60217
60170
 
60218
- this.height = this.config.height || 100;
60171
+ this.flipAxis = this.config.flipAxis ? this.config.flipAxis : false;
60172
+ this.logScale = this.config.logScale ? this.config.logScale : false;
60173
+ this.autoscale = this.config.autoscale || this.config.max === undefined;
60174
+
60175
+ if (!this.autoscale) {
60176
+ this.dataRange = {
60177
+ min: this.config.min || 0,
60178
+ max: this.config.max
60179
+ };
60180
+ }
60181
+
60182
+ for (let t of this.tracks) {
60183
+ t.autoscale = false;
60184
+ t.dataRange = this.dataRange;
60185
+ }
60186
+
60187
+ this.height = this.config.height || 50;
60219
60188
  return Promise.all(p);
60220
60189
  }
60221
60190
 
60191
+ get height() {
60192
+ return this._height;
60193
+ }
60194
+
60195
+ set height(h) {
60196
+ this._height = h;
60197
+
60198
+ if (this.tracks) {
60199
+ for (let t of this.tracks) {
60200
+ t.height = h;
60201
+ t.config.height = h;
60202
+ }
60203
+ }
60204
+ }
60205
+
60206
+ menuItemList() {
60207
+ let items = [];
60208
+
60209
+ if (this.flipAxis !== undefined) {
60210
+ items.push({
60211
+ label: "Flip y-axis",
60212
+ click: () => {
60213
+ this.flipAxis = !this.flipAxis;
60214
+ this.trackView.repaintViews();
60215
+ }
60216
+ });
60217
+ }
60218
+
60219
+ items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
60220
+ return items;
60221
+ }
60222
+
60222
60223
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
60223
60224
  const promises = this.tracks.map(t => t.getFeatures(chr, bpStart, bpEnd, bpPerPixel));
60224
60225
  return Promise.all(promises);
60225
60226
  }
60226
60227
 
60227
60228
  draw(options) {
60228
- var i, len, mergedFeatures, trackOptions, dataRange;
60229
- mergedFeatures = options.features; // Array of feature arrays, 1 for each track
60229
+ const mergedFeatures = options.features; // Array of feature arrays, 1 for each track
60230
60230
 
60231
- dataRange = autoscale(options.referenceFrame.chr, mergedFeatures); //IGVGraphics.fillRect(options.context, 0, options.pixelTop, options.pixelWidth, options.pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
60231
+ if (this.autoscale) {
60232
+ this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
60233
+ }
60232
60234
 
60233
- for (i = 0, len = this.tracks.length; i < len; i++) {
60234
- trackOptions = Object.assign({}, options);
60235
+ for (let i = 0, len = this.tracks.length; i < len; i++) {
60236
+ const trackOptions = Object.assign({}, options);
60235
60237
  trackOptions.features = mergedFeatures[i];
60236
- this.tracks[i].dataRange = dataRange;
60238
+ this.tracks[i].dataRange = this.dataRange;
60239
+ this.tracks[i].flipAxis = this.flipAxis;
60240
+ this.tracks[i].logScale = this.logScale;
60241
+ this.tracks[i].graphType = this.graphType;
60237
60242
  this.tracks[i].draw(trackOptions);
60238
60243
  }
60239
60244
  }
60240
60245
 
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
60246
  popupData(clickState, features) {
60256
- const featuresArray = features || clickState.viewport.getCachedFeatures();
60247
+ const featuresArray = features || clickState.viewport.cachedFeatures;
60257
60248
 
60258
60249
  if (featuresArray && featuresArray.length === this.tracks.length) {
60259
60250
  // Array of feature arrays, 1 for each track
@@ -60270,40 +60261,24 @@
60270
60261
  }
60271
60262
  }
60272
60263
 
60273
- supportsWholeGenome() {
60274
- const b = this.tracks.every(track => track.supportsWholeGenome());
60275
- return b;
60264
+ get supportsWholeGenome() {
60265
+ return this.tracks.every(track => track.supportsWholeGenome());
60276
60266
  }
60277
60267
 
60278
60268
  }
60279
60269
 
60280
60270
  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 {
60271
+ let min = 0;
60272
+ let max = -Number.MAX_VALUE;
60298
60273
 
60299
- featureArrays.forEach(function (features, i) {
60300
- features.forEach(function (f) {
60274
+ for (let features of featureArrays) {
60275
+ for (let f of features) {
60301
60276
  if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
60302
60277
  min = Math.min(min, f.value);
60303
60278
  max = Math.max(max, f.value);
60304
60279
  }
60305
- });
60306
- }); // }
60280
+ }
60281
+ }
60307
60282
 
60308
60283
  return {
60309
60284
  min: min,
@@ -60424,7 +60399,7 @@
60424
60399
  return this;
60425
60400
  }
60426
60401
 
60427
- supportsWholeGenome() {
60402
+ get supportsWholeGenome() {
60428
60403
  return true;
60429
60404
  }
60430
60405
 
@@ -60845,7 +60820,7 @@
60845
60820
  items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
60846
60821
  }
60847
60822
 
60848
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
60823
+ if (this.browser.circularView) {
60849
60824
  items.push('<hr/>');
60850
60825
  items.push({
60851
60826
  label: 'Add interactions to circular view',
@@ -60862,7 +60837,7 @@
60862
60837
 
60863
60838
  contextMenuItemList(clickState) {
60864
60839
  // Experimental JBrowse feature
60865
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
60840
+ if (this.browser.circularView) {
60866
60841
  const viewport = clickState.viewport;
60867
60842
  const list = [];
60868
60843
  list.push({
@@ -60887,20 +60862,20 @@
60887
60862
  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
60863
 
60889
60864
  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
- });
60865
+ if (inView.length === 0) return;
60866
+ const chords = makeBedPEChords(inView);
60867
+ sendChords(chords, this, refFrame, 0.5); //
60868
+ //
60869
+ // // for filtered set, distinguishing the chromosomes is more critical than tracks
60870
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5)
60871
+ // const trackColor = IGVColor.addAlpha(this.color, 0.5)
60872
+ //
60873
+ // // name the chord set to include locus and filtering information
60874
+ // const encodedName = this.name.replaceAll(' ', '%20')
60875
+ // const chordSetName = "all" === refFrame.chr ?
60876
+ // encodedName :
60877
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`
60878
+ // this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor})
60904
60879
  }
60905
60880
 
60906
60881
  doAutoscale(features) {
@@ -60987,7 +60962,7 @@
60987
60962
  clickedFeatures(clickState, features) {
60988
60963
  // We use the cached features rather than method to avoid async load. If the
60989
60964
  // 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();
60965
+ const featureList = features || clickState.viewport.cachedFeatures;
60991
60966
  const candidates = [];
60992
60967
 
60993
60968
  if (featureList) {
@@ -61358,7 +61333,7 @@
61358
61333
  return this;
61359
61334
  }
61360
61335
 
61361
- supportsWholeGenome() {
61336
+ get supportsWholeGenome() {
61362
61337
  return this.config.indexed === false || this.config.supportsWholeGenome === true;
61363
61338
  }
61364
61339
 
@@ -61573,7 +61548,7 @@
61573
61548
  variantColor = "gray";
61574
61549
  }
61575
61550
  } else if (this._color) {
61576
- variantColor = typeof this._color === "function" ? this._color(v) : this._color;
61551
+ variantColor = this.color;
61577
61552
  } else if ("NONVARIANT" === v.type) {
61578
61553
  variantColor = this.nonRefColor;
61579
61554
  } else if ("MIXED" === v.type) {
@@ -61585,6 +61560,10 @@
61585
61560
  return variantColor;
61586
61561
  }
61587
61562
 
61563
+ get color() {
61564
+ return this._color ? typeof this._color === "function" ? this._color(v) : this._color : this.defaultColor;
61565
+ }
61566
+
61588
61567
  clickedFeatures(clickState, features) {
61589
61568
  let featureList = super.clickedFeatures(clickState, features);
61590
61569
  const vGap = this.displayMode === 'EXPANDED' ? this.expandedVGap : this.squishedVGap;
@@ -61845,29 +61824,15 @@
61845
61824
  } // Experimental JBrowse circular view integration
61846
61825
 
61847
61826
 
61848
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
61827
+ if (this.browser.circularView) {
61849
61828
  menuItems.push('<hr>');
61850
61829
  menuItems.push({
61851
61830
  label: 'Add SVs to circular view',
61852
61831
  click: () => {
61853
- const inView = [];
61854
61832
 
61855
61833
  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
- }
61834
+ this.sendChordsForViewport(viewport);
61863
61835
  }
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
61836
  }
61872
61837
  });
61873
61838
  }
@@ -61877,26 +61842,26 @@
61877
61842
 
61878
61843
  contextMenuItemList(clickState) {
61879
61844
  // Experimental JBrowse circular view integration
61880
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
61845
+ if (this.browser.circularView) {
61881
61846
  const viewport = clickState.viewport;
61882
61847
  const list = [];
61883
61848
  list.push({
61884
61849
  label: 'Add SVs to Circular View',
61885
61850
  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
- });
61851
+ this.sendChordsForViewport(viewport);
61894
61852
  }
61895
61853
  });
61896
61854
  list.push('<hr/>');
61897
61855
  return list;
61898
61856
  }
61899
61857
  }
61858
+
61859
+ sendChordsForViewport(viewport) {
61860
+ const refFrame = viewport.referenceFrame;
61861
+ const inView = "all" === refFrame.chr ? this.featureSource.getAllFeatures() : this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
61862
+ const chords = makeVCFChords(inView);
61863
+ sendChords(chords, this, refFrame, 0.5);
61864
+ }
61900
61865
  /**
61901
61866
  * Create a "color by" checkbox menu item, optionally initially checked
61902
61867
  * @param menuItem
@@ -62199,7 +62164,7 @@
62199
62164
 
62200
62165
 
62201
62166
  popupData(clickState) {
62202
- let features = clickState.viewport.getCachedFeatures();
62167
+ let features = clickState.viewport.cachedFeatures;
62203
62168
  if (!features || features.length === 0) return [];
62204
62169
  const tolerance = 3;
62205
62170
  const tissue = this.name;
@@ -62407,7 +62372,7 @@
62407
62372
  return this;
62408
62373
  }
62409
62374
 
62410
- supportsWholeGenome() {
62375
+ get supportsWholeGenome() {
62411
62376
  return true;
62412
62377
  }
62413
62378
 
@@ -62545,7 +62510,7 @@
62545
62510
  popupData(clickState) {
62546
62511
  let data = [];
62547
62512
  const track = clickState.viewport.trackView.track;
62548
- const features = clickState.viewport.getCachedFeatures();
62513
+ const features = clickState.viewport.cachedFeatures;
62549
62514
 
62550
62515
  if (features) {
62551
62516
  let count = 0;
@@ -62973,7 +62938,7 @@
62973
62938
  return items;
62974
62939
  }
62975
62940
 
62976
- supportsWholeGenome() {
62941
+ get supportsWholeGenome() {
62977
62942
  return false;
62978
62943
  }
62979
62944
 
@@ -63234,7 +63199,7 @@
63234
63199
  if (!this.featureCache) {
63235
63200
  const options = buildOptions(this.config);
63236
63201
  const data = await igvxhr.loadString(this.config.url, options);
63237
- this.featureCache = new FeatureCache(parseBP(data), genome);
63202
+ this.featureCache = new FeatureCache$1(parseBP(data), genome);
63238
63203
  return this.featureCache.queryFeatures(chr, start, end);
63239
63204
  } else {
63240
63205
  return this.featureCache.queryFeatures(chr, start, end);
@@ -63334,12 +63299,15 @@
63334
63299
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
63335
63300
  * THE SOFTWARE.
63336
63301
  */
63302
+ /**
63303
+ * Class represents an ideogram of a chromsome cytobands. It is used for the header of a track panel.
63304
+ *
63305
+ */
63337
63306
 
63338
63307
  class IdeogramTrack {
63339
63308
  constructor(browser) {
63340
63309
  this.browser = browser;
63341
63310
  this.type = 'ideogram';
63342
- this.id = this.type;
63343
63311
  this.height = 16;
63344
63312
  this.order = Number.MIN_SAFE_INTEGER;
63345
63313
  this.disableButtons = true;
@@ -63599,7 +63567,7 @@
63599
63567
  return this;
63600
63568
  }
63601
63569
 
63602
- supportsWholeGenome() {
63570
+ get supportsWholeGenome() {
63603
63571
  return false;
63604
63572
  }
63605
63573
 
@@ -64477,6 +64445,15 @@
64477
64445
  this.id = guid$2();
64478
64446
  }
64479
64447
 
64448
+ extend(locus) {
64449
+ const newStart = Math.min(locus.start, this.start);
64450
+ const newEnd = Math.max(locus.end, this.end);
64451
+ const ratio = (newEnd - newStart) / (this.end - this.start);
64452
+ this.start = newStart;
64453
+ this.end = newEnd;
64454
+ this.bpPerPixel *= ratio;
64455
+ }
64456
+
64480
64457
  calculateEnd(pixels) {
64481
64458
  return this.start + this.bpPerPixel * pixels;
64482
64459
  }
@@ -64561,7 +64538,7 @@
64561
64538
  const viewChanged = start !== this.start || bpPerPixel !== this.bpPerPixel;
64562
64539
 
64563
64540
  if (viewChanged) {
64564
- await browser.updateViews(this);
64541
+ await browser.updateViews(true);
64565
64542
  }
64566
64543
  }
64567
64544
 
@@ -64631,23 +64608,6 @@
64631
64608
  });
64632
64609
  }
64633
64610
 
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
64611
  const defaultNucleotideColors = {
64652
64612
  "A": "rgb( 0, 200, 0)",
64653
64613
  "C": "rgb( 0,0,200)",
@@ -65489,7 +65449,7 @@
65489
65449
  }
65490
65450
  }
65491
65451
 
65492
- browser.resize();
65452
+ browser.layoutChange();
65493
65453
  });
65494
65454
  }
65495
65455
 
@@ -65694,6 +65654,65 @@
65694
65654
  button.addEventListener('click', () => browser.saveSVGtoFile({}));
65695
65655
  };
65696
65656
 
65657
+ const viewportColumnManager = {
65658
+ createColumns: (columnContainer, count) => {
65659
+ for (let i = 0; i < count; i++) {
65660
+ if (0 === i) {
65661
+ createColumn(columnContainer, 'igv-column');
65662
+ } else {
65663
+ columnContainer.appendChild(div$1({
65664
+ class: 'igv-column-shim'
65665
+ }));
65666
+ createColumn(columnContainer, 'igv-column');
65667
+ }
65668
+ }
65669
+ },
65670
+ removeColumnAtIndex: (i, column) => {
65671
+ const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
65672
+ column.remove();
65673
+ shim.remove();
65674
+ },
65675
+ insertAfter: referenceElement => {
65676
+ const shim = div$1({
65677
+ class: 'igv-column-shim'
65678
+ });
65679
+ insertElementAfter(shim, referenceElement);
65680
+ const column = div$1({
65681
+ class: 'igv-column'
65682
+ });
65683
+ insertElementAfter(column, shim);
65684
+ return column;
65685
+ },
65686
+ insertBefore: (referenceElement, count) => {
65687
+ for (let i = 0; i < count; i++) {
65688
+ const column = div$1({
65689
+ class: 'igv-column'
65690
+ });
65691
+ insertElementBefore(column, referenceElement);
65692
+
65693
+ if (count > 1 && i > 0) {
65694
+ const columnShim = div$1({
65695
+ class: 'igv-column-shim'
65696
+ });
65697
+ insertElementBefore(columnShim, column);
65698
+ }
65699
+ }
65700
+ },
65701
+ indexOfColumn: (columnContainer, column) => {
65702
+ const allColumns = columnContainer.querySelectorAll('.igv-column');
65703
+
65704
+ for (let i = 0; i < allColumns.length; i++) {
65705
+ const c = allColumns[i];
65706
+
65707
+ if (c === column) {
65708
+ return i;
65709
+ }
65710
+ }
65711
+
65712
+ return undefined;
65713
+ }
65714
+ };
65715
+
65697
65716
  /*
65698
65717
  * The MIT License (MIT)
65699
65718
  *
@@ -65944,7 +65963,7 @@
65944
65963
  }
65945
65964
  }
65946
65965
 
65947
- supportsWholeGenome() {
65966
+ get supportsWholeGenome() {
65948
65967
  return true;
65949
65968
  }
65950
65969
 
@@ -66476,7 +66495,9 @@
66476
66495
  // deferred because ideogram and ruler are treated as "tracks", and tracks require a reference frame
66477
66496
 
66478
66497
  if (false !== session.showIdeogram) {
66479
- this.trackViews.push(new TrackView(this, this.columnContainer, new IdeogramTrack(this)));
66498
+ const ideogramTrack = new IdeogramTrack(this);
66499
+ ideogramTrack.id = 'ideogram';
66500
+ this.trackViews.push(new TrackView(this, this.columnContainer, ideogramTrack));
66480
66501
  }
66481
66502
 
66482
66503
  if (false !== session.showRuler) {
@@ -66526,7 +66547,12 @@
66526
66547
  }
66527
66548
  }
66528
66549
 
66529
- await this.loadTrackList(trackConfigurations);
66550
+ await this.loadTrackList(trackConfigurations); // The ruler and ideogram tracks are not explicitly loaded, but needs updated nonetheless.
66551
+
66552
+ for (let rtv of this.trackViews.filter(tv => tv.track.type === 'ruler' || tv.track.type === 'ideogram')) {
66553
+ rtv.updateViews();
66554
+ }
66555
+
66530
66556
  this.updateUIWithReferenceFrameList();
66531
66557
  }
66532
66558
 
@@ -66691,26 +66717,22 @@
66691
66717
  }
66692
66718
 
66693
66719
  async loadTrackList(configList) {
66694
- try {
66695
- const promises = [];
66696
-
66697
- for (let config of configList) {
66698
- promises.push(this.loadTrack(config, false));
66699
- }
66720
+ const promises = [];
66700
66721
 
66701
- const loadedTracks = await Promise.all(promises);
66702
- const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
66703
- return trackView.track.autoscaleGroup;
66704
- });
66722
+ for (let config of configList) {
66723
+ promises.push(this.loadTrack(config));
66724
+ }
66705
66725
 
66706
- if (groupAutoscaleViews.length > 0) {
66707
- this.updateViews(groupAutoscaleViews);
66708
- }
66726
+ const loadedTracks = await Promise.all(promises);
66727
+ const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
66728
+ return trackView.track.autoscaleGroup;
66729
+ });
66709
66730
 
66710
- return loadedTracks;
66711
- } finally {
66712
- await this.resize();
66731
+ if (groupAutoscaleViews.length > 0) {
66732
+ this.updateViews();
66713
66733
  }
66734
+
66735
+ return loadedTracks;
66714
66736
  }
66715
66737
 
66716
66738
  async loadROI(config) {
@@ -66724,7 +66746,9 @@
66724
66746
  }
66725
66747
  } else {
66726
66748
  this.roi.push(new ROI(config, this.genome));
66727
- }
66749
+ } // Force reload all views (force = true) to insure ROI features are loaded. Wasteful but this function is
66750
+ // rarely called.
66751
+
66728
66752
 
66729
66753
  await this.updateViews(true);
66730
66754
  }
@@ -66738,7 +66762,7 @@
66738
66762
  }
66739
66763
 
66740
66764
  for (let tv of this.trackViews) {
66741
- tv.updateViews(true);
66765
+ tv.repaintViews();
66742
66766
  }
66743
66767
  }
66744
66768
 
@@ -66746,7 +66770,7 @@
66746
66770
  this.roi = [];
66747
66771
 
66748
66772
  for (let tv of this.trackViews) {
66749
- tv.updateViews(true);
66773
+ tv.repaintViews();
66750
66774
  }
66751
66775
  }
66752
66776
 
@@ -66762,25 +66786,13 @@
66762
66786
  /**
66763
66787
  * Return a promise to load a track.
66764
66788
  *
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
66789
  * @param config
66778
66790
  * @param doResize - undefined by default
66779
66791
  * @returns {*}
66780
66792
  */
66781
66793
 
66782
66794
 
66783
- async loadTrack(config, doResize) {
66795
+ async loadTrack(config) {
66784
66796
  // config might be json
66785
66797
  if (isString$3(config)) {
66786
66798
  config = JSON.parse(config);
@@ -66845,11 +66857,6 @@
66845
66857
 
66846
66858
  msg += ": " + config.url;
66847
66859
  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
66860
  }
66854
66861
  }
66855
66862
  /**
@@ -67065,8 +67072,18 @@
67065
67072
  trackView.setTrackHeight(newHeight);
67066
67073
  });
67067
67074
  }
67075
+ /**
67076
+ * API function to signal that this browser visibility has changed, e.g. from hiding/showing in a tab interface.
67077
+ *
67078
+ * @returns {Promise<void>}
67079
+ */
67080
+
67068
67081
 
67069
67082
  async visibilityChange() {
67083
+ this.layoutChange();
67084
+ }
67085
+
67086
+ async layoutChange() {
67070
67087
  const status = this.referenceFrameList.find(referenceFrame => referenceFrame.bpPerPixel < 0);
67071
67088
 
67072
67089
  if (status) {
@@ -67082,43 +67099,11 @@
67082
67099
  this.navbarManager.navbarDidResize(this.$navigation.width(), isWGV);
67083
67100
  }
67084
67101
 
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();
67102
+ resize.call(this);
67103
+ await this.updateViews();
67119
67104
  }
67120
67105
 
67121
- async updateViews(force) {
67106
+ async updateViews() {
67122
67107
  const trackViews = this.trackViews;
67123
67108
  this.updateLocusSearchWidget();
67124
67109
 
@@ -67129,7 +67114,7 @@
67129
67114
 
67130
67115
  if (this.dragObject) {
67131
67116
  for (let trackView of trackViews) {
67132
- await trackView.updateViews(force);
67117
+ await trackView.updateViews();
67133
67118
  }
67134
67119
  } else {
67135
67120
  // Group autoscale
@@ -67178,19 +67163,25 @@
67178
67163
  for (let trackView of groupTrackViews) {
67179
67164
  trackView.track.dataRange = dataRange;
67180
67165
  trackView.track.autoscale = false;
67181
- p.push(trackView.updateViews(force));
67166
+ p.push(trackView.updateViews());
67182
67167
  }
67183
67168
 
67184
67169
  await Promise.all(p);
67185
67170
  }
67186
67171
  }
67187
67172
 
67188
- await Promise.all(otherTracks.map(tv => tv.updateViews(force))); // for (let trackView of otherTracks) {
67173
+ await Promise.all(otherTracks.map(tv => tv.updateViews())); // for (let trackView of otherTracks) {
67189
67174
  // await trackView.updateViews(force);
67190
67175
  // }
67191
67176
  }
67192
67177
  }
67193
67178
 
67179
+ repaintViews() {
67180
+ for (let trackView of this.trackViews) {
67181
+ trackView.repaintViews();
67182
+ }
67183
+ }
67184
+
67194
67185
  updateLocusSearchWidget() {
67195
67186
  const referenceFrameList = this.referenceFrameList; // Update end position of reference frames based on pixel widths. This is hacky, but its been done here
67196
67187
  // for a long time, although indirectly.
@@ -67248,41 +67239,53 @@
67248
67239
  referenceFrame.zoomWithScaleFactor(this, scaleFactor, viewportWidth, centerBPOrUndefined);
67249
67240
  }
67250
67241
  }
67242
+ /**
67243
+ * Add a new multi-locus panel for the specified region
67244
+ * @param chr
67245
+ * @param start
67246
+ * @param end
67247
+ * @param referenceFrameLeft - optional, if supplied new panel should be placed to the immediate right
67248
+ */
67249
+
67251
67250
 
67252
- async presentMultiLocusPanel(alignment, referenceFrameLeft) {
67251
+ async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
67253
67252
  // account for reduced viewport width as a result of adding right mate pair panel
67254
67253
  const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
67255
67254
  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
67255
 
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
67256
+ for (let refFrame of this.referenceFrameList) {
67257
+ refFrame.bpPerPixel *= scaleFactor;
67258
+ }
67259
+
67260
+ const bpp = (end - start) / viewportWidth;
67261
+ const newReferenceFrame = new ReferenceFrame(this.genome, chr, start, end, bpp);
67262
+ const indexLeft = referenceFrameLeft ? this.referenceFrameList.indexOf(referenceFrameLeft) : this.referenceFrameList.length - 1;
67263
+ const indexRight = 1 + indexLeft; // TODO -- this is really ugly
67260
67264
 
67261
- const indexLeft = this.referenceFrameList.indexOf(referenceFrameLeft);
67262
- const indexRight = 1 + this.referenceFrameList.indexOf(referenceFrameLeft);
67263
67265
  const {
67264
67266
  $viewport
67265
67267
  } = this.trackViews[0].viewports[indexLeft];
67266
67268
  const viewportColumn = viewportColumnManager.insertAfter($viewport.get(0).parentElement);
67267
67269
 
67268
67270
  if (indexRight === this.referenceFrameList.length) {
67269
- this.referenceFrameList.push(referenceFrameRight);
67271
+ this.referenceFrameList.push(newReferenceFrame);
67270
67272
 
67271
67273
  for (let trackView of this.trackViews) {
67272
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
67274
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
67273
67275
  trackView.viewports.push(viewport);
67274
67276
  }
67275
67277
  } else {
67276
- this.referenceFrameList.splice(indexRight, 0, referenceFrameRight);
67278
+ this.referenceFrameList.splice(indexRight, 0, newReferenceFrame);
67277
67279
 
67278
67280
  for (let trackView of this.trackViews) {
67279
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
67281
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
67280
67282
  trackView.viewports.splice(indexRight, 0, viewport);
67281
67283
  }
67282
67284
  }
67283
67285
 
67284
67286
  this.centerLineList = this.createCenterLineList(this.columnContainer);
67285
- await this.resize();
67287
+ resize.call(this);
67288
+ await this.updateViews(true);
67286
67289
  }
67287
67290
 
67288
67291
  async removeMultiLocusPanel(referenceFrame) {
@@ -67311,8 +67314,15 @@
67311
67314
  const scaleFactor = this.calculateViewportWidth(1 + this.referenceFrameList.length) / this.calculateViewportWidth(this.referenceFrameList.length);
67312
67315
  await this.rescaleForMultiLocus(scaleFactor);
67313
67316
  }
67317
+ /**
67318
+ * Goto the locus represented by the selected referenceFrame, discarding all other panels
67319
+ *
67320
+ * @param referenceFrame
67321
+ * @returns {Promise<void>}
67322
+ */
67323
+
67314
67324
 
67315
- async selectMultiLocusPanel(referenceFrame) {
67325
+ async gotoMultilocusPanel(referenceFrame) {
67316
67326
  const referenceFrameIndex = this.referenceFrameList.indexOf(referenceFrame); // Remove columns for unselected panels
67317
67327
 
67318
67328
  this.columnContainer.querySelectorAll('.igv-column').forEach((column, c) => {
@@ -67360,7 +67370,7 @@
67360
67370
 
67361
67371
  this.centerLineList = this.createCenterLineList(this.columnContainer);
67362
67372
  this.updateUIWithReferenceFrameList();
67363
- await this.updateViews(true);
67373
+ await this.updateViews();
67364
67374
  }
67365
67375
  /**
67366
67376
  * @deprecated This is a deprecated method with no known usages. To be removed in a future release.
@@ -67613,20 +67623,6 @@
67613
67623
  const surl = (idx > 0 ? path.substring(0, idx) : path) + "?sessionURL=blob:" + this.compressedSession();
67614
67624
  return surl;
67615
67625
  }
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
67626
  /**
67631
67627
  * Record a mouse click on a specific viewport. This might be the start of a drag operation. Dragging
67632
67628
  * (panning) is handled here so that the mouse can move out of a specific viewport (e.g. stray into another
@@ -67661,10 +67657,22 @@
67661
67657
  this.fireEvent('trackdragend');
67662
67658
  }
67663
67659
  }
67660
+ /**
67661
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
67662
+ *
67663
+ * @param trackView
67664
+ */
67665
+
67664
67666
 
67665
67667
  startTrackDrag(trackView) {
67666
67668
  this.dragTrack = trackView;
67667
67669
  }
67670
+ /**
67671
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
67672
+ *
67673
+ * @param dragDestination
67674
+ */
67675
+
67668
67676
 
67669
67677
  updateTrackDrag(dragDestination) {
67670
67678
  if (dragDestination && this.dragTrack) {
@@ -67738,12 +67746,9 @@
67738
67746
  }
67739
67747
 
67740
67748
  addWindowResizeHandler() {
67741
- this.boundWindowResizeHandler = windowResizeHandler.bind(this);
67749
+ // Create a copy of the prototype "resize" function bound to this instance. Neccessary to support removing.
67750
+ this.boundWindowResizeHandler = resize.bind(this);
67742
67751
  window.addEventListener('resize', this.boundWindowResizeHandler);
67743
-
67744
- function windowResizeHandler() {
67745
- this.resize();
67746
- }
67747
67752
  }
67748
67753
 
67749
67754
  removeWindowResizeHandler() {
@@ -67815,6 +67820,8 @@
67815
67820
  }
67816
67821
 
67817
67822
  createCircularView(container, show) {
67823
+ show = show === true; // convert undefined to boolean
67824
+
67818
67825
  this.circularView = createCircularView(container, this);
67819
67826
  this.circularViewControl = new CircularViewControl(this.$toggle_button_container.get(0), this);
67820
67827
  this.circularView.setAssembly({
@@ -67822,7 +67829,8 @@
67822
67829
  id: this.genome.id,
67823
67830
  chromosomes: makeCircViewChromosomes(this.genome)
67824
67831
  });
67825
- this.circularViewVisible = show === true;
67832
+ this.circularViewVisible = show;
67833
+ return this.circularView;
67826
67834
  }
67827
67835
 
67828
67836
  get circularViewVisible() {
@@ -67837,6 +67845,48 @@
67837
67845
  }
67838
67846
 
67839
67847
  }
67848
+ /**
67849
+ * Function called win window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
67850
+ * than class method because it needs to be copied and bound to specific instances of browser to support listener
67851
+ * removal
67852
+ *
67853
+ * @returns {Promise<void>}
67854
+ */
67855
+
67856
+
67857
+ async function resize() {
67858
+ const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
67859
+
67860
+ for (let referenceFrame of this.referenceFrameList) {
67861
+ const index = this.referenceFrameList.indexOf(referenceFrame);
67862
+ const {
67863
+ chr,
67864
+ genome
67865
+ } = referenceFrame;
67866
+ const {
67867
+ bpLength
67868
+ } = genome.getChromosome(referenceFrame.chr);
67869
+ const viewportWidthBP = referenceFrame.toBP(viewportWidth); // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
67870
+
67871
+ if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
67872
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
67873
+ referenceFrame.bpPerPixel = bpLength / viewportWidth;
67874
+ } else {
67875
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
67876
+ referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
67877
+ }
67878
+
67879
+ for (let {
67880
+ viewports
67881
+ } of this.trackViews) {
67882
+ viewports[index].setWidth(viewportWidth);
67883
+ }
67884
+ }
67885
+
67886
+ this.updateUIWithReferenceFrameList(); //TODO -- update view only if needed. Reducing size never needed. Increasing size maybe
67887
+
67888
+ await this.updateViews(true);
67889
+ }
67840
67890
 
67841
67891
  function handleMouseMove(e) {
67842
67892
  e.preventDefault();