igv 2.11.1 → 2.11.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$1 {
17946
+ class FeatureCache {
17947
17947
  constructor(featureList, genome, range) {
17948
17948
  featureList = featureList || [];
17949
17949
  this.treeMap = this.buildTreeMap(featureList, genome);
@@ -20733,13 +20733,14 @@
20733
20733
  console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
20734
20734
  }
20735
20735
 
20736
- checkContentHeight(features) {
20736
+ checkContentHeight() {
20737
20737
  let track = this.trackView.track;
20738
- features = features || this.cachedFeatures;
20739
20738
 
20740
20739
  if ("FILL" === track.displayMode) {
20741
20740
  this.setContentHeight(this.$viewport.height());
20742
20741
  } else if (typeof track.computePixelHeight === 'function') {
20742
+ let features = this.cachedFeatures;
20743
+
20743
20744
  if (features && features.length > 0) {
20744
20745
  let requiredContentHeight = track.computePixelHeight(features);
20745
20746
  let currentContentHeight = this.$content.height();
@@ -20759,7 +20760,7 @@
20759
20760
  // Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
20760
20761
  contentHeight = Math.min(contentHeight, 32000);
20761
20762
  this.$content.height(contentHeight);
20762
- if (this.canvas._data) this.canvas._data.invalidate = true;
20763
+ if (this.tile) this.tile.invalidate = true;
20763
20764
  }
20764
20765
 
20765
20766
  isLoading() {
@@ -20774,6 +20775,8 @@
20774
20775
 
20775
20776
  setWidth(width) {
20776
20777
  this.$viewport.width(width);
20778
+ this.canvas.style.width = `${width}px`;
20779
+ this.canvas.setAttribute('width', width);
20777
20780
  }
20778
20781
 
20779
20782
  getWidth() {
@@ -23058,7 +23061,7 @@
23058
23061
  }
23059
23062
  };
23060
23063
 
23061
- const _version = "2.11.1";
23064
+ const _version = "2.11.2";
23062
23065
 
23063
23066
  function version$1() {
23064
23067
  return _version;
@@ -23575,20 +23578,16 @@
23575
23578
 
23576
23579
  checkZoomIn() {
23577
23580
  const showZoomInNotice = () => {
23581
+ const referenceFrame = this.referenceFrame;
23582
+
23578
23583
  if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome()) {
23579
23584
  return true;
23580
23585
  } else {
23581
23586
  const visibilityWindow = this.trackView.track.visibilityWindow;
23582
- return visibilityWindow !== undefined && visibilityWindow > 0 && this.referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow;
23587
+ return visibilityWindow !== undefined && visibilityWindow > 0 && referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow;
23583
23588
  }
23584
23589
  };
23585
23590
 
23586
- if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
23587
- if (this.canvas) ;
23588
-
23589
- return false;
23590
- }
23591
-
23592
23591
  if (!this.viewIsReady()) {
23593
23592
  return false;
23594
23593
  }
@@ -23598,8 +23597,7 @@
23598
23597
  // Out of visibility window
23599
23598
  if (this.canvas) {
23600
23599
  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
23601
- this.canvas._data = undefined;
23602
- this.featureCache = undefined;
23600
+ this.tile = undefined;
23603
23601
  }
23604
23602
 
23605
23603
  this.$zoomInNotice.show();
@@ -23618,17 +23616,14 @@
23618
23616
 
23619
23617
  return true;
23620
23618
  }
23621
- /**
23622
- * Adjust the canvas to the current genomic state.
23623
- */
23624
-
23625
23619
 
23626
23620
  shift() {
23627
- const referenceFrame = this.referenceFrame;
23621
+ const self = this;
23622
+ const referenceFrame = self.referenceFrame;
23628
23623
 
23629
- if (this.canvas && this.canvas._data && this.canvas._data.chr === this.referenceFrame.chr && this.canvas._data.bpPerPixel === referenceFrame.bpPerPixel) {
23630
- const pixelOffset = Math.round((this.canvas._data.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23631
- this.canvas.style.left = pixelOffset + "px";
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";
23632
23627
  }
23633
23628
  }
23634
23629
 
@@ -23654,10 +23649,9 @@
23654
23649
  this.startSpinner();
23655
23650
 
23656
23651
  try {
23657
- const track = this.trackView.track;
23658
- const features = await this.getFeatures(track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23652
+ const features = await this.getFeatures(this.trackView.track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23659
23653
  let roiFeatures = [];
23660
- const roi = mergeArrays(this.browser.roi, track.roi);
23654
+ const roi = mergeArrays(this.browser.roi, this.trackView.track.roi);
23661
23655
 
23662
23656
  if (roi) {
23663
23657
  for (let r of roi) {
@@ -23669,13 +23663,11 @@
23669
23663
  }
23670
23664
  }
23671
23665
 
23672
- const mr = track && ("wig" === track.type || "merged" === track.type); // wig tracks are potentially multiresolution (e.g. bigwig)
23673
-
23674
- this.featureCache = new FeatureCache(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures, mr);
23666
+ this.tile = new Tile(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures);
23675
23667
  this.loading = false;
23676
23668
  this.hideMessage();
23677
23669
  this.stopSpinner();
23678
- return this.featureCache;
23670
+ return this.tile;
23679
23671
  } catch (error) {
23680
23672
  // Track might have been removed during load
23681
23673
  if (this.trackView && this.trackView.disposed !== true) {
@@ -23688,34 +23680,30 @@
23688
23680
  this.stopSpinner();
23689
23681
  }
23690
23682
  }
23691
- /**
23692
- * Repaint the canvas for the current genomic state.
23693
- *
23694
- * @returns {Promise<void>}
23695
- */
23696
23683
 
23697
-
23698
- repaint() {
23699
- if (undefined === this.featureCache) {
23684
+ async repaint() {
23685
+ if (undefined === this.tile) {
23700
23686
  return;
23701
23687
  }
23702
23688
 
23703
23689
  let {
23704
23690
  features,
23705
- roiFeatures
23706
- } = this.featureCache; //this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
23707
- // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23691
+ roiFeatures,
23692
+ bpPerPixel,
23693
+ startBP,
23694
+ endBP
23695
+ } = this.tile; // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23708
23696
 
23709
23697
  const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
23710
23698
  let pixelWidth;
23711
- const startBP = this.featureCache.startBP;
23712
- const endBP = this.featureCache.endBP;
23713
- let bpPerPixel = this.referenceFrame.bpPerPixel;
23714
23699
 
23715
23700
  if (isWGV) {
23701
+ bpPerPixel = this.referenceFrame.end / this.$viewport.width();
23702
+ startBP = 0;
23703
+ endBP = this.referenceFrame.end;
23716
23704
  pixelWidth = this.$viewport.width();
23717
23705
  } else {
23718
- pixelWidth = 3 * this.$viewport.width();
23706
+ pixelWidth = Math.ceil((endBP - startBP) / bpPerPixel);
23719
23707
  } // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
23720
23708
 
23721
23709
 
@@ -23768,33 +23756,21 @@
23768
23756
  viewport: this,
23769
23757
  viewportWidth: this.$viewport.width()
23770
23758
  };
23771
- this.draw(drawConfiguration, features, roiFeatures); // Attach metadata to canvas
23772
-
23773
- newCanvas._data = {
23774
- chr: this.referenceFrame.chr,
23775
- startBP,
23776
- endBP,
23777
- bpPerPixel,
23759
+ this.draw(drawConfiguration, features, roiFeatures);
23760
+ this.canvasVerticalRange = {
23778
23761
  top: canvasTop,
23779
23762
  bottom: canvasTop + pixelHeight
23780
23763
  };
23781
23764
 
23782
- if (this.canvas) {
23783
- $$1(this.canvas).remove();
23765
+ if (this.$canvas) {
23766
+ this.$canvas.remove();
23784
23767
  }
23785
23768
 
23769
+ this.$canvas = $$1(newCanvas);
23770
+ this.$content.append(this.$canvas);
23786
23771
  this.canvas = newCanvas;
23787
23772
  this.ctx = ctx;
23788
- this.$content.append($$1(newCanvas));
23789
23773
  }
23790
- /**
23791
- * Draw the associated track.
23792
- *
23793
- * @param drawConfiguration
23794
- * @param features
23795
- * @param roiFeatures
23796
- */
23797
-
23798
23774
 
23799
23775
  draw(drawConfiguration, features, roiFeatures) {
23800
23776
  // console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
@@ -23809,6 +23785,51 @@
23809
23785
  r.track.draw(drawConfiguration);
23810
23786
  }
23811
23787
  }
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);
23812
23833
  }
23813
23834
 
23814
23835
  containsPosition(chr, position) {
@@ -23823,10 +23844,9 @@
23823
23844
  return this.loading;
23824
23845
  }
23825
23846
 
23826
- savePNG() {
23847
+ saveImage() {
23827
23848
  if (!this.ctx) return;
23828
- const canvasMetadata = this.canvas._data;
23829
- const canvasTop = canvasMetadata ? canvasMetadata.top : 0;
23849
+ const canvasTop = this.canvasVerticalRange ? this.canvasVerticalRange.top : 0;
23830
23850
  const devicePixelRatio = window.devicePixelRatio;
23831
23851
  const w = this.$viewport.width() * devicePixelRatio;
23832
23852
  const h = this.$viewport.height() * devicePixelRatio;
@@ -23948,22 +23968,23 @@
23948
23968
  viewportWidth: width,
23949
23969
  selection: this.selection
23950
23970
  };
23951
- const features = this.featureCache ? this.featureCache.features : [];
23952
- const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
23971
+ const features = this.tile ? this.tile.features : [];
23972
+ const roiFeatures = this.tile ? this.tile.roiFeatures : undefined;
23953
23973
  this.draw(config, features, roiFeatures);
23954
23974
  context.restore();
23955
23975
  }
23956
23976
 
23957
- get cachedFeatures() {
23958
- return this.featureCache ? this.featureCache.features : [];
23977
+ getCachedFeatures() {
23978
+ return this.tile ? this.tile.features : [];
23959
23979
  }
23960
23980
 
23961
23981
  async getFeatures(track, chr, start, end, bpPerPixel) {
23962
- if (this.featureCache && this.featureCache.containsRange(chr, start, end, bpPerPixel)) {
23963
- return this.featureCache.features;
23982
+ if (this.tile && this.tile.containsRange(chr, start, end, bpPerPixel)) {
23983
+ return this.tile.features;
23964
23984
  } else if (typeof track.getFeatures === "function") {
23965
23985
  const features = await track.getFeatures(chr, start, end, bpPerPixel, this);
23966
- this.checkContentHeight(features);
23986
+ this.cachedFeatures = features;
23987
+ this.checkContentHeight();
23967
23988
  return features;
23968
23989
  } else {
23969
23990
  return undefined;
@@ -24243,6 +24264,11 @@
24243
24264
  const viewportCoords = translateMouseCoordinates$1(event, viewport.contentDiv);
24244
24265
  const canvasCoords = translateMouseCoordinates$1(event, viewport.canvas);
24245
24266
  const genomicLocation = referenceFrame.start + referenceFrame.toBP(viewportCoords.x);
24267
+
24268
+ if (undefined === genomicLocation || null === viewport.tile) {
24269
+ return undefined;
24270
+ }
24271
+
24246
24272
  return {
24247
24273
  event,
24248
24274
  viewport,
@@ -24296,28 +24322,22 @@
24296
24322
  return rows.join('');
24297
24323
  }
24298
24324
 
24299
- class FeatureCache {
24300
- constructor(chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures, multiresolution) {
24301
- this.chr = chr;
24302
- this.startBP = tileStart;
24303
- this.endBP = tileEnd;
24304
- this.bpPerPixel = bpPerPixel;
24305
- this.features = features;
24306
- this.roiFeatures = roiFeatures;
24307
- this.multiresolution = multiresolution;
24308
- }
24309
-
24310
- containsRange(chr, start, end, bpPerPixel) {
24311
- // For multi-resolution tracks allow for a 2X change in bpPerPixel
24312
- const r = this.multiresolution ? this.bpPerPixel / bpPerPixel : 1;
24313
- return start >= this.startBP && end <= this.endBP && chr === this.chr && r > 0.5 && r < 2;
24314
- }
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
+ };
24315
24333
 
24316
- overlapsRange(chr, start, end) {
24317
- return this.chr === chr && end >= this.startBP && start <= this.endBP;
24318
- }
24334
+ Tile.prototype.containsRange = function (chr, start, end, bpPerPixel) {
24335
+ return this.bpPerPixel === bpPerPixel && start >= this.startBP && end <= this.endBP && chr === this.chr;
24336
+ };
24319
24337
 
24320
- }
24338
+ Tile.prototype.overlapsRange = function (chr, start, end) {
24339
+ return this.chr === chr && end >= this.startBP && start <= this.endBP;
24340
+ };
24321
24341
  /**
24322
24342
  * Merge 2 arrays. a and/or b can be undefined. If both are undefined, return undefined
24323
24343
  * @param a An array or undefined
@@ -25461,18 +25481,6 @@
25461
25481
  return true; // By definition
25462
25482
  }
25463
25483
 
25464
- isMateMapped() {
25465
- return true; // By definition
25466
- }
25467
-
25468
- isProperPair() {
25469
- return this.firstAlignment.isProperPair();
25470
- }
25471
-
25472
- get tlen() {
25473
- return Math.abs(this.firstAlignment.fragmentLength);
25474
- }
25475
-
25476
25484
  firstOfPairStrand() {
25477
25485
  if (this.firstAlignment.isFirstOfPair()) {
25478
25486
  return this.firstAlignment.strand;
@@ -25839,15 +25847,7 @@
25839
25847
  */
25840
25848
 
25841
25849
  class AlignmentContainer {
25842
- // this.config.samplingWindowSize, this.config.samplingDepth,
25843
- // this.config.pairsSupported, this.config.alleleFreqThreshold)
25844
- constructor(chr, start, end, _ref) {
25845
- let {
25846
- samplingWindowSize,
25847
- samplingDepth,
25848
- pairsSupported,
25849
- alleleFreqThreshold
25850
- } = _ref;
25850
+ constructor(chr, start, end, samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold) {
25851
25851
  this.chr = chr;
25852
25852
  this.start = Math.floor(start);
25853
25853
  this.end = Math.ceil(end);
@@ -25870,10 +25870,17 @@
25870
25870
  // TODO -- pass this in
25871
25871
  return alignment.isMapped() && !alignment.isFailsVendorQualityCheck();
25872
25872
  };
25873
+
25874
+ this.pairedEndStats = new PairedEndStats();
25873
25875
  }
25874
25876
 
25875
25877
  push(alignment) {
25876
25878
  if (this.filter(alignment) === false) return;
25879
+
25880
+ if (alignment.isPaired()) {
25881
+ this.pairedEndStats.push(alignment);
25882
+ }
25883
+
25877
25884
  this.coverageMap.incCounts(alignment); // Count coverage before any downsampling
25878
25885
 
25879
25886
  if (this.pairsSupported && this.downsampledReads.has(alignment.readName)) {
@@ -25902,6 +25909,7 @@
25902
25909
  });
25903
25910
  this.pairsCache = undefined;
25904
25911
  this.downsampledReads = undefined;
25912
+ this.pairedEndStats.compute();
25905
25913
  }
25906
25914
 
25907
25915
  contains(chr, start, end) {
@@ -26196,6 +26204,62 @@
26196
26204
 
26197
26205
  }
26198
26206
 
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
+
26199
26263
  class SupplementaryAlignment {
26200
26264
  constructor(rec) {
26201
26265
  const tokens = rec.split(',');
@@ -27399,7 +27463,7 @@
27399
27463
  const header = this.header;
27400
27464
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
27401
27465
  const qAlignments = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
27402
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27466
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27403
27467
 
27404
27468
  for (let a of qAlignments) {
27405
27469
  alignmentContainer.push(a);
@@ -27426,13 +27490,13 @@
27426
27490
  const alignments = [];
27427
27491
  this.header = BamUtils.decodeBamHeader(data);
27428
27492
  BamUtils.decodeBamRecords(data, this.header.size, alignments, this.header.chrNames);
27429
- this.alignmentCache = new FeatureCache$1(alignments, this.genome);
27493
+ this.alignmentCache = new FeatureCache(alignments, this.genome);
27430
27494
  }
27431
27495
 
27432
27496
  fetchAlignments(chr, bpStart, bpEnd) {
27433
27497
  const queryChr = this.header.chrAliasTable.hasOwnProperty(chr) ? this.header.chrAliasTable[chr] : chr;
27434
27498
  const features = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
27435
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27499
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported);
27436
27500
 
27437
27501
  for (let feature of features) {
27438
27502
  alignmentContainer.push(feature);
@@ -28432,7 +28496,7 @@
28432
28496
  const chrToIndex = await this.getChrIndex();
28433
28497
  const queryChr = this.chrAliasTable.hasOwnProperty(chr) ? this.chrAliasTable[chr] : chr;
28434
28498
  const chrId = chrToIndex[queryChr];
28435
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
28499
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config.samplingWindowSize, this.config.samplingDepth, this.config.pairsSupported, this.config.alleleFreqThreshold);
28436
28500
 
28437
28501
  if (chrId === undefined) {
28438
28502
  return alignmentContainer;
@@ -28581,7 +28645,7 @@
28581
28645
 
28582
28646
  async readAlignments(chr, start, end) {
28583
28647
  if (!this.bamReaders.hasOwnProperty(chr)) {
28584
- return new AlignmentContainer(chr, start, end, this.config);
28648
+ return new AlignmentContainer(chr, start, end);
28585
28649
  } else {
28586
28650
  let reader = this.bamReaders[chr];
28587
28651
  const a = await reader.readAlignments(chr, start, end);
@@ -28653,7 +28717,7 @@
28653
28717
  return igvxhr.loadString(url, buildOptions(self.config)).then(function (sam) {
28654
28718
  var alignmentContainer;
28655
28719
  header.chrToIndex[queryChr];
28656
- alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.config);
28720
+ alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold);
28657
28721
  BamUtils.decodeSamRecords(sam, alignmentContainer, queryChr, bpStart, bpEnd, self.filter);
28658
28722
  return alignmentContainer;
28659
28723
  });
@@ -28863,7 +28927,7 @@
28863
28927
 
28864
28928
  const ba = unbgzf(compressedData.buffer);
28865
28929
  const chrIdx = this.header.chrToIndex[chr];
28866
- const alignmentContainer = new AlignmentContainer(chr, start, end, this.config);
28930
+ const alignmentContainer = new AlignmentContainer(chr, start, end, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
28867
28931
  BamUtils.decodeBamRecords(ba, this.header.size, alignmentContainer, this.header.chrNames, chrIdx, start, end);
28868
28932
  alignmentContainer.finish();
28869
28933
  return alignmentContainer;
@@ -44401,7 +44465,7 @@
44401
44465
  const header = await this.getHeader();
44402
44466
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
44403
44467
  const chrIdx = header.chrToIndex[queryChr];
44404
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
44468
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
44405
44469
 
44406
44470
  if (chrIdx === undefined) {
44407
44471
  return alignmentContainer;
@@ -45010,7 +45074,7 @@
45010
45074
  "pageSize": "10000"
45011
45075
  },
45012
45076
  decode: decodeGa4ghReads,
45013
- results: new AlignmentContainer(chr, bpStart, bpEnd, self.config)
45077
+ results: new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold)
45014
45078
  });
45015
45079
  });
45016
45080
 
@@ -45317,6 +45381,7 @@
45317
45381
  const genome = browser.genome;
45318
45382
  this.config = config;
45319
45383
  this.genome = genome;
45384
+ this.alignmentContainer = undefined;
45320
45385
 
45321
45386
  if (isDataURL(config.url)) {
45322
45387
  if ("cram" === config.format) {
@@ -45367,44 +45432,58 @@
45367
45432
  }
45368
45433
 
45369
45434
  setViewAsPairs(bool) {
45370
- this.viewAsPairs = bool;
45435
+
45436
+ if (this.viewAsPairs !== bool) {
45437
+ this.viewAsPairs = bool; // if (this.alignmentContainer) {
45438
+ // this.alignmentContainer.setViewAsPairs(bool);
45439
+ // }
45440
+ }
45371
45441
  }
45372
45442
 
45373
45443
  setShowSoftClips(bool) {
45374
- this.showSoftClips = bool;
45444
+ if (this.showSoftClips !== bool) {
45445
+ this.showSoftClips = bool;
45446
+ }
45375
45447
  }
45376
45448
 
45377
45449
  async getAlignments(chr, bpStart, bpEnd) {
45378
45450
  const genome = this.genome;
45379
45451
  const showSoftClips = this.showSoftClips;
45380
- const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
45381
- let alignments = alignmentContainer.alignments;
45382
45452
 
45383
- if (!this.viewAsPairs) {
45384
- alignments = unpairAlignments([{
45385
- alignments: alignments
45386
- }]);
45387
- }
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;
45388
45458
 
45389
- const hasAlignments = alignments.length > 0;
45390
- alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
45391
- this.alignmentContainer = alignmentContainer;
45459
+ if (!this.viewAsPairs) {
45460
+ alignments = unpairAlignments([{
45461
+ alignments: alignments
45462
+ }]);
45463
+ }
45392
45464
 
45393
- if (hasAlignments) {
45394
- const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
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
45395
45468
 
45396
- if (sequence) {
45397
- alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
45469
+ this.alignmentContainer = alignmentContainer;
45398
45470
 
45399
- alignmentContainer.sequence = sequence; // TODO -- fix this
45471
+ if (hasAlignments) {
45472
+ const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
45400
45473
 
45401
- return alignmentContainer;
45402
- } else {
45403
- console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
45474
+ if (sequence) {
45475
+ alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
45476
+
45477
+ alignmentContainer.sequence = sequence; // TODO -- fix this
45478
+
45479
+ return alignmentContainer;
45480
+ } else {
45481
+ console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
45482
+ }
45404
45483
  }
45405
- }
45406
45484
 
45407
- return alignmentContainer;
45485
+ return alignmentContainer;
45486
+ }
45408
45487
  }
45409
45488
 
45410
45489
  }
@@ -45749,7 +45828,7 @@
45749
45828
  clickedFeatures(clickState, features) {
45750
45829
  // We use the cached features rather than method to avoid async load. If the
45751
45830
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
45752
- if (!features) features = clickState.viewport.cachedFeatures;
45831
+ if (!features) features = clickState.viewport.getCachedFeatures();
45753
45832
 
45754
45833
  if (!features || features.length === 0) {
45755
45834
  return [];
@@ -48388,117 +48467,50 @@
48388
48467
  return regions;
48389
48468
  }
48390
48469
 
48391
- function sendChords(chords, track, refFrame, alpha) {
48392
- const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? track.color : getChrColor(refFrame.chr), alpha);
48393
- const trackColor = IGVColor.addAlpha(track.color || 'rgb(0,0,255)', alpha); // name the chord set to include locus and filtering information
48394
-
48395
- const encodedName = track.name.replaceAll(' ', '%20');
48396
- const chordSetName = "all" === refFrame.chr ? encodedName : `${encodedName} ${refFrame.chr}:${refFrame.start}-${refFrame.end}`;
48397
- track.browser.circularView.addChords(chords, {
48398
- track: chordSetName,
48399
- color: chordSetColor,
48400
- trackColor: trackColor
48401
- });
48402
- }
48403
-
48404
48470
  function createCircularView(el, browser) {
48405
48471
  const circularView = new CircularView(el, {
48406
48472
  onChordClick: (feature, chordTrack, pluginManager) => {
48407
48473
  const f1 = feature.data;
48408
48474
  const f2 = f1.mate;
48409
- addFrameForFeature(f1);
48410
- addFrameForFeature(f2);
48411
-
48412
- function addFrameForFeature(feature) {
48413
- feature.chr = browser.genome.getChromosomeName(feature.refName);
48414
-
48415
- for (let referenceFrame of browser.currentReferenceFrames()) {
48416
- const l = Locus.fromLocusString(referenceFrame.getLocusString());
48417
-
48418
- if (l.contains(feature)) {
48419
- break;
48420
- } else if (l.overlaps(feature)) {
48421
- referenceFrame.extend(feature);
48422
- break;
48423
- } else {
48424
- const flanking = 2000;
48425
- const center = (feature.start + feature.end) / 2;
48426
- browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
48427
- break;
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);
48428
48499
  }
48429
48500
  }
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];
48430
48507
  }
48431
- }
48432
- });
48433
- return circularView;
48434
- }
48435
-
48436
- class PairedEndStats {
48437
- constructor(alignments, _ref) {
48438
- let {
48439
- minTLENPercentile,
48440
- maxTLENPercentile
48441
- } = _ref;
48442
- this.totalCount = 0;
48443
- this.frCount = 0;
48444
- this.rfCount = 0;
48445
- this.ffCount = 0;
48446
- this.sumF = 0;
48447
- this.sumF2 = 0;
48448
- this.lp = minTLENPercentile === undefined ? 0.1 : minTLENPercentile;
48449
- this.up = maxTLENPercentile === undefined ? 99.5 : maxTLENPercentile;
48450
- this.isizes = [];
48451
- this.compute(alignments);
48452
- }
48453
-
48454
- compute(alignments) {
48455
- for (let alignment of alignments) {
48456
- if (alignment.isProperPair()) {
48457
- var tlen = Math.abs(alignment.fragmentLength);
48458
- this.sumF += tlen;
48459
- this.sumF2 += tlen * tlen;
48460
- this.isizes.push(tlen);
48461
- var po = alignment.pairOrientation;
48462
-
48463
- if (typeof po === "string" && po.length === 4) {
48464
- var tmp = '' + po.charAt(0) + po.charAt(2);
48465
-
48466
- switch (tmp) {
48467
- case 'FF':
48468
- case 'RR':
48469
- this.ffCount++;
48470
- break;
48471
-
48472
- case "FR":
48473
- this.frCount++;
48474
- break;
48475
48508
 
48476
- case "RF":
48477
- this.rfCount++;
48478
- }
48479
- }
48480
-
48481
- this.totalCount++;
48482
- }
48509
+ const searchString = loci.map(l => l.getLocusString()).join(" ");
48510
+ browser.search(searchString);
48483
48511
  }
48484
-
48485
- 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";
48486
- this.minTLEN = this.lp === 0 ? 0 : percentile(this.isizes, this.lp);
48487
- this.maxTLEN = percentile(this.isizes, this.up); // var fMean = this.sumF / this.totalCount
48488
- // var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount))
48489
- // this.minTLEN = fMean - 3 * stdDev
48490
- // this.maxTLEN = fMean + 3 * stdDev
48491
- }
48492
-
48493
- }
48494
-
48495
- function percentile(array, p) {
48496
- if (array.length === 0) return undefined;
48497
- var k = Math.floor(array.length * (p / 100));
48498
- array.sort(function (a, b) {
48499
- return a - b;
48500
48512
  });
48501
- return array[k];
48513
+ return circularView;
48502
48514
  }
48503
48515
 
48504
48516
  /*
@@ -48560,7 +48572,10 @@
48560
48572
  this.showInsertions = false !== config.showInsertions;
48561
48573
  this.showMismatches = false !== config.showMismatches;
48562
48574
  this.color = config.color;
48563
- this.coverageColor = config.coverageColor; // The sort object can be an array in the case of multi-locus view, however if multiple sort positions
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
48564
48579
  // are present for a given reference frame the last one will take precedence
48565
48580
 
48566
48581
  if (config.sort) {
@@ -48588,22 +48603,12 @@
48588
48603
  return this._height;
48589
48604
  }
48590
48605
 
48591
- get minTemplateLength() {
48592
- const configMinTLEN = this.config.minTLEN !== undefined ? this.config.minTLEN : this.config.minFragmentLength;
48593
- return configMinTLEN !== undefined ? configMinTLEN : this._pairedEndStats ? this._pairedEndStats.minTLEN : 0;
48594
- }
48595
-
48596
- get maxTemplateLength() {
48597
- const configMaxTLEN = this.config.maxTLEN !== undefined ? this.config.maxTLEN : this.config.maxFragmentLength;
48598
- return configMaxTLEN !== undefined ? configMaxTLEN : this._pairedEndStats ? this._pairedEndStats.maxTLEN : 1000;
48599
- }
48600
-
48601
48606
  sort(options) {
48602
48607
  options = this.assignSort(options);
48603
48608
 
48604
48609
  for (let vp of this.trackView.viewports) {
48605
48610
  if (vp.containsPosition(options.chr, options.position)) {
48606
- const alignmentContainer = vp.cachedFeatures;
48611
+ const alignmentContainer = vp.getCachedFeatures();
48607
48612
 
48608
48613
  if (alignmentContainer) {
48609
48614
  sortAlignmentRows(options, alignmentContainer);
@@ -48638,16 +48643,16 @@
48638
48643
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel, viewport) {
48639
48644
  const alignmentContainer = await this.featureSource.getAlignments(chr, bpStart, bpEnd);
48640
48645
 
48641
- if (alignmentContainer.paired && !this._pairedEndStats && !this.config.maxFragmentLength) {
48642
- const pairedEndStats = new PairedEndStats(alignmentContainer.alignments, this.config);
48646
+ if (alignmentContainer.alignments && alignmentContainer.alignments.length > 99) {
48647
+ if (undefined === this.minFragmentLength) {
48648
+ this.minFragmentLength = alignmentContainer.pairedEndStats.lowerFragmentLength;
48649
+ }
48643
48650
 
48644
- if (pairedEndStats.totalCount > 99) {
48645
- this._pairedEndStats = pairedEndStats;
48651
+ if (undefined === this.maxFragmentLength) {
48652
+ this.maxFragmentLength = alignmentContainer.pairedEndStats.upperFragmentLength;
48646
48653
  }
48647
48654
  }
48648
48655
 
48649
- alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
48650
-
48651
48656
  const sort = this.sortObject;
48652
48657
 
48653
48658
  if (sort) {
@@ -48752,7 +48757,7 @@
48752
48757
  label: 'pair orientation'
48753
48758
  });
48754
48759
  colorByMenuItems.push({
48755
- key: 'tlen',
48760
+ key: 'fragmentLength',
48756
48761
  label: 'insert size (TLEN)'
48757
48762
  });
48758
48763
  colorByMenuItems.push({
@@ -48863,7 +48868,7 @@
48863
48868
  this.trackView.repaintViews();
48864
48869
  }
48865
48870
  });
48866
- } // Add chords to JBrowse circular view, if present
48871
+ } // Experimental JBrowse feature
48867
48872
 
48868
48873
 
48869
48874
  if (this.browser.circularView && true === this.browser.circularViewVisible && (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
@@ -48873,9 +48878,26 @@
48873
48878
  menuItems.push({
48874
48879
  label: 'Add discordant pairs to circular view',
48875
48880
  click: () => {
48881
+ const maxFragmentLength = this.maxFragmentLength;
48882
+ const inView = [];
48883
+
48876
48884
  for (let viewport of this.trackView.viewports) {
48877
- this.addPairedChordsForViewport(viewport);
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
+ }
48878
48892
  }
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
+ });
48879
48901
  }
48880
48902
  });
48881
48903
  }
@@ -48884,9 +48906,25 @@
48884
48906
  menuItems.push({
48885
48907
  label: 'Add split reads to circular view',
48886
48908
  click: () => {
48909
+ const inView = [];
48910
+
48887
48911
  for (let viewport of this.trackView.viewports) {
48888
- this.addSplitChordsForViewport(viewport);
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
+ }
48889
48920
  }
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
+ });
48890
48928
  }
48891
48929
  });
48892
48930
  }
@@ -49005,7 +49043,7 @@
49005
49043
  }
49006
49044
 
49007
49045
  getCachedAlignmentContainers() {
49008
- return this.trackView.viewports.map(vp => vp.cachedFeatures);
49046
+ return this.trackView.viewports.map(vp => vp.getCachedFeatures());
49009
49047
  }
49010
49048
 
49011
49049
  get dataRange() {
@@ -49031,56 +49069,6 @@
49031
49069
  set autoscale(autoscale) {
49032
49070
  this.coverageTrack.autoscale = autoscale;
49033
49071
  }
49034
- /**
49035
- * Add chords to the circular view for the given viewport, represented by its reference frame
49036
- * @param refFrame
49037
- */
49038
-
49039
-
49040
- addPairedChordsForViewport(viewport) {
49041
- const maxTemplateLength = this.maxTemplateLength;
49042
- const inView = [];
49043
- const refFrame = viewport.referenceFrame;
49044
-
49045
- for (let a of viewport.cachedFeatures.allAlignments()) {
49046
- if (a.end >= refFrame.start && a.start <= refFrame.end && a.mate && a.mate.chr && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxTemplateLength)) {
49047
- inView.push(a);
49048
- }
49049
- }
49050
-
49051
- const chords = makePairedAlignmentChords(inView);
49052
- sendChords(chords, this, refFrame, 0.02); // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
49053
- // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
49054
- //
49055
- // // name the chord set to include track name and locus
49056
- // const encodedName = this.name.replaceAll(' ', '%20')
49057
- // const chordSetName = "all" === refFrame.chr ? encodedName :
49058
- // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
49059
- // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
49060
- }
49061
-
49062
- addSplitChordsForViewport(viewport) {
49063
- const inView = [];
49064
- const refFrame = viewport.referenceFrame;
49065
-
49066
- for (let a of viewport.cachedFeatures.allAlignments()) {
49067
- const sa = a.hasTag('SA');
49068
-
49069
- if (a.end >= refFrame.start && a.start <= refFrame.end && sa) {
49070
- inView.push(a);
49071
- }
49072
- }
49073
-
49074
- const chords = makeSupplementalAlignmentChords(inView);
49075
- sendChords(chords, this, refFrame, 0.02); // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
49076
- // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
49077
- //
49078
- // // name the chord set to include track name and locus
49079
- // const encodedName = this.name.replaceAll(' ', '%20')
49080
- // const chordSetName = "all" === refFrame.chr ? encodedName :
49081
- // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
49082
- // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
49083
- }
49084
49072
 
49085
49073
  }
49086
49074
 
@@ -49191,7 +49179,7 @@
49191
49179
  }
49192
49180
 
49193
49181
  getClickedObject(clickState) {
49194
- let features = clickState.viewport.cachedFeatures;
49182
+ let features = clickState.viewport.getCachedFeatures();
49195
49183
  if (!features || features.length === 0) return;
49196
49184
  const genomicLocation = Math.floor(clickState.genomicLocation);
49197
49185
  const coverageMap = features.coverageMap;
@@ -49277,14 +49265,14 @@
49277
49265
  this.deletionColor = config.deletionColor || "black";
49278
49266
  this.skippedColor = config.skippedColor || "rgb(150, 170, 170)";
49279
49267
  this.pairConnectorColor = config.pairConnectorColor;
49280
- this.smallTLENColor = config.smallTLENColor || config.smallFragmentLengthColor || "rgb(0, 0, 150)";
49281
- this.largeTLENColor = config.largeTLENColor || config.largeFragmentLengthColor || "rgb(200, 0, 0)";
49268
+ this.smallFragmentLengthColor = config.smallFragmentLengthColor || "rgb(0, 0, 150)";
49269
+ this.largeFragmentLengthColor = config.largeFragmentLengthColor || "rgb(200, 0, 0)";
49282
49270
  this.pairOrientation = config.pairOrienation || 'fr';
49283
49271
  this.pairColors = {};
49284
49272
  this.pairColors["RL"] = config.rlColor || "rgb(0, 150, 0)";
49285
49273
  this.pairColors["RR"] = config.rrColor || "rgb(20, 50, 200)";
49286
49274
  this.pairColors["LL"] = config.llColor || "rgb(0, 150, 150)";
49287
- this.colorBy = config.colorBy || "unexpectedPair";
49275
+ this.colorBy = config.colorBy || "pairOrientation";
49288
49276
  this.colorByTag = config.colorByTag ? config.colorByTag.toUpperCase() : undefined;
49289
49277
  this.bamColorTag = config.bamColorTag === undefined ? "YC" : config.bamColorTag;
49290
49278
  this.hideSmallIndels = config.hideSmallIndels;
@@ -49639,7 +49627,7 @@
49639
49627
  direction: direction
49640
49628
  };
49641
49629
  this.parent.sortObject = newSortObject;
49642
- sortAlignmentRows(newSortObject, viewport.cachedFeatures);
49630
+ sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
49643
49631
  viewport.repaint();
49644
49632
  };
49645
49633
 
@@ -49691,7 +49679,7 @@
49691
49679
  };
49692
49680
  this.sortByTag = tag;
49693
49681
  this.parent.sortObject = newSortObject;
49694
- sortAlignmentRows(newSortObject, viewport.cachedFeatures);
49682
+ sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
49695
49683
  viewport.repaint();
49696
49684
  }
49697
49685
  }
@@ -49715,12 +49703,8 @@
49715
49703
  const referenceFrame = clickState.viewport.referenceFrame;
49716
49704
 
49717
49705
  if (this.browser.genome.getChromosome(clickedAlignment.mate.chr)) {
49718
- this.highlightedAlignmentReadNamed = clickedAlignment.readName; //this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame)
49719
-
49720
- const bpWidth = referenceFrame.end - referenceFrame.start;
49721
- const frameStart = clickedAlignment.mate.position - bpWidth / 2;
49722
- const frameEnd = clickedAlignment.mate.position + bpWidth / 2;
49723
- this.browser.addMultiLocusPanel(clickedAlignment.mate.chr, frameStart, frameEnd, referenceFrame);
49706
+ this.highlightedAlignmentReadNamed = clickedAlignment.readName;
49707
+ this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame);
49724
49708
  } else {
49725
49709
  Alert.presentAlert(`Reference does not contain chromosome: ${clickedAlignment.mate.chr}`);
49726
49710
  }
@@ -49768,7 +49752,20 @@
49768
49752
  list.push({
49769
49753
  label: 'Add discordant pairs to circular view',
49770
49754
  click: () => {
49771
- this.parent.addPairedChordsForViewport(viewport);
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
+ });
49772
49769
  }
49773
49770
  });
49774
49771
  }
@@ -49777,7 +49774,23 @@
49777
49774
  list.push({
49778
49775
  label: 'Add split reads to circular view',
49779
49776
  click: () => {
49780
- this.parent.addSplitChordsForViewport(viewport);
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
+ });
49781
49794
  }
49782
49795
  });
49783
49796
  }
@@ -49793,7 +49806,7 @@
49793
49806
  const y = clickState.y;
49794
49807
  const genomicLocation = clickState.genomicLocation;
49795
49808
  const showSoftClips = this.parent.showSoftClips;
49796
- let features = viewport.cachedFeatures;
49809
+ let features = viewport.getCachedFeatures();
49797
49810
  if (!features || features.length === 0) return;
49798
49811
  let packedAlignmentRows = features.packedAlignmentRows;
49799
49812
  let downsampledIntervals = features.downsampledIntervals;
@@ -49879,16 +49892,13 @@
49879
49892
  break;
49880
49893
  }
49881
49894
 
49882
- case "tlen":
49883
49895
  case "fragmentLength":
49884
- if (alignment.mate && alignment.isMateMapped()) {
49885
- if (alignment.mate.chr !== alignment.chr) {
49886
- color = getChrColor(alignment.mate.chr);
49887
- } else if (this.parent.minTemplateLength && Math.abs(alignment.fragmentLength) < this.parent.minTemplateLength) {
49888
- color = this.smallTLENColor;
49889
- } else if (this.parent.maxTemplateLength && Math.abs(alignment.fragmentLength) > this.parent.maxTemplateLength) {
49890
- color = this.largeTLENColor;
49891
- }
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;
49892
49902
  }
49893
49903
 
49894
49904
  break;
@@ -49930,7 +49940,10 @@
49930
49940
  alignmentContainer.packedAlignmentRows.sort(function (rowA, rowB) {
49931
49941
  const i = rowA.score > rowB.score ? 1 : rowA.score < rowB.score ? -1 : 0;
49932
49942
  return true === direction ? i : -i;
49933
- });
49943
+ }); // For debugging
49944
+ // for(let r of alignmentContainer.packedAlignmentRows) {
49945
+ // console.log(r.score);
49946
+ // }
49934
49947
  }
49935
49948
 
49936
49949
  function shadedBaseColor(qual, baseColor) {
@@ -50085,7 +50098,7 @@
50085
50098
  });
50086
50099
  this.$viewport.append(this.$rulerLabel);
50087
50100
  this.$rulerLabel.click(async () => {
50088
- await this.browser.gotoMultilocusPanel(this.referenceFrame); // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
50101
+ await this.browser.selectMultiLocusPanel(this.referenceFrame); // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
50089
50102
  // for (let referenceFrame of removals) {
50090
50103
  // await this.browser.removeMultiLocusPanel(referenceFrame)
50091
50104
  // }
@@ -50400,7 +50413,7 @@
50400
50413
  referenceFrame.start = ss;
50401
50414
  referenceFrame.end = ee;
50402
50415
  referenceFrame.bpPerPixel = (ee - ss) / width;
50403
- this.browser.updateViews(true);
50416
+ this.browser.updateViews(referenceFrame, this.browser.trackViews, true);
50404
50417
  }
50405
50418
 
50406
50419
  this.boundClickHandler = clickHandler.bind(this);
@@ -51277,9 +51290,7 @@
51277
51290
 
51278
51291
  repaintViews() {
51279
51292
  for (let viewport of this.viewports) {
51280
- if (viewport.isVisible()) {
51281
- viewport.repaint();
51282
- }
51293
+ viewport.repaint();
51283
51294
  }
51284
51295
 
51285
51296
  if (typeof this.track.paintAxis === 'function') {
@@ -51303,9 +51314,6 @@
51303
51314
  }
51304
51315
  /**
51305
51316
  * Update viewports to reflect current genomic state, possibly loading additional data.
51306
- *
51307
- * @param force - if true, force a repaint even if no new data is loaded
51308
- * @returns {Promise<void>}
51309
51317
  */
51310
51318
 
51311
51319
 
@@ -51321,7 +51329,7 @@
51321
51329
  } // rpv: viewports whose image (canvas) does not fully cover current genomic range
51322
51330
 
51323
51331
 
51324
- const reloadableViewports = force ? visibleViewports : this.viewportsToReload(); // Trigger viewport to load features needed to cover current genomic range
51332
+ const reloadableViewports = this.viewportsToReload(force); // Trigger viewport to load features needed to cover current genomic range
51325
51333
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
51326
51334
 
51327
51335
  for (let viewport of reloadableViewports) {
@@ -51329,7 +51337,7 @@
51329
51337
  }
51330
51338
 
51331
51339
  if (this.disposed) return; // Track was removed during load
51332
- // Special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
51340
+ // Very special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
51333
51341
  // section depends on data from all the views. We only need to adjust this however if any data was loaded
51334
51342
  // (i.e. reloadableViewports.length > 0)
51335
51343
 
@@ -51337,8 +51345,8 @@
51337
51345
  let maxRow = 0;
51338
51346
 
51339
51347
  for (let viewport of this.viewports) {
51340
- if (viewport.featureCache && viewport.featureCache.features) {
51341
- maxRow = Math.max(maxRow, viewport.featureCache.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
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));
51342
51350
  }
51343
51351
  }
51344
51352
 
@@ -51361,14 +51369,14 @@
51361
51369
  const start = referenceFrame.start;
51362
51370
  const end = start + referenceFrame.toBP($$1(visibleViewport.contentDiv).width());
51363
51371
 
51364
- if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
51365
- if (typeof visibleViewport.featureCache.features.getMax === 'function') {
51366
- const max = visibleViewport.featureCache.features.getMax(start, end);
51372
+ if (visibleViewport.tile && visibleViewport.tile.features) {
51373
+ if (typeof visibleViewport.tile.features.getMax === 'function') {
51374
+ const max = visibleViewport.tile.features.getMax(start, end);
51367
51375
  allFeatures.push({
51368
51376
  value: max
51369
51377
  });
51370
51378
  } else {
51371
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end));
51379
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.tile.features, start, end));
51372
51380
  }
51373
51381
  }
51374
51382
  }
@@ -51378,22 +51386,16 @@
51378
51386
  } else {
51379
51387
  this.track.dataRange = doAutoscale(allFeatures);
51380
51388
  }
51381
- } // Must repaint all viewports if autoscaling. Always repaint ruler.
51389
+ } // Must repaint all viewports if autoscaling
51382
51390
 
51383
51391
 
51384
- if (this.track.autoscale || this.track.autoscaleGroup || this.track.type === 'ruler' || force) {
51385
- for (let vp of visibleViewports) {
51386
- vp.repaint();
51392
+ if (!isDragging && (this.track.autoscale || this.track.autoscaleGroup)) {
51393
+ for (let visibleViewport of visibleViewports) {
51394
+ visibleViewport.repaint();
51387
51395
  }
51388
51396
  } else {
51389
- const reloadedViewports = new Set(reloadableViewports);
51390
-
51391
- for (let vp of visibleViewports) {
51392
- const invalid = vp.canvas && vp.canvas._data && vp.canvas._data.invalidate;
51393
-
51394
- if (invalid || reloadedViewports.has(vp)) {
51395
- vp.repaint();
51396
- }
51397
+ for (let vp of reloadableViewports) {
51398
+ vp.repaint();
51397
51399
  }
51398
51400
  }
51399
51401
 
@@ -51421,13 +51423,13 @@
51421
51423
  */
51422
51424
 
51423
51425
 
51424
- async getInViewFeatures() {
51426
+ async getInViewFeatures(force) {
51425
51427
  if (!(this.browser && this.browser.referenceFrameList)) {
51426
51428
  return [];
51427
51429
  } // List of viewports that need reloading
51428
51430
 
51429
51431
 
51430
- const rpV = this.viewportsToReload();
51432
+ const rpV = this.viewportsToReload(force);
51431
51433
  const promises = rpV.map(function (vp) {
51432
51434
  return vp.loadFeatures();
51433
51435
  });
@@ -51435,18 +51437,18 @@
51435
51437
  let allFeatures = [];
51436
51438
 
51437
51439
  for (let vp of this.viewports) {
51438
- if (vp.featureCache && vp.featureCache.features) {
51440
+ if (vp.tile && vp.tile.features) {
51439
51441
  const referenceFrame = vp.referenceFrame;
51440
51442
  const start = referenceFrame.start;
51441
51443
  const end = start + referenceFrame.toBP($$1(vp.contentDiv).width());
51442
51444
 
51443
- if (typeof vp.featureCache.features.getMax === 'function') {
51444
- const max = vp.featureCache.features.getMax(start, end);
51445
+ if (typeof vp.tile.features.getMax === 'function') {
51446
+ const max = vp.tile.features.getMax(start, end);
51445
51447
  allFeatures.push({
51446
51448
  value: max
51447
51449
  });
51448
51450
  } else {
51449
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.featureCache.features, start, end));
51451
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.tile.features, start, end));
51450
51452
  }
51451
51453
  }
51452
51454
  }
@@ -51503,7 +51505,7 @@
51503
51505
  const start = referenceFrame.start;
51504
51506
  const end = start + referenceFrame.toBP($$1(viewport.contentDiv).width());
51505
51507
  const bpPerPixel = referenceFrame.bpPerPixel;
51506
- return !viewport.featureCache || !viewport.featureCache.containsRange(chr, start, end, bpPerPixel);
51508
+ return force || !viewport.tile || viewport.tile.invalidate || !viewport.tile.containsRange(chr, start, end, bpPerPixel);
51507
51509
  }
51508
51510
  });
51509
51511
  return viewports;
@@ -56325,7 +56327,7 @@
56325
56327
  }
56326
56328
 
56327
56329
  this.queryable = false;
56328
- this.featureCache = new FeatureCache$1(features, genome);
56330
+ this.featureCache = new FeatureCache(features, genome);
56329
56331
  } else if (config.reader) {
56330
56332
  // Explicit reader implementation
56331
56333
  this.reader = config.reader;
@@ -56496,13 +56498,13 @@
56496
56498
  } // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
56497
56499
 
56498
56500
 
56499
- this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval); // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
56501
+ this.featureCache = new FeatureCache(features, this.genome, genomicInterval); // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
56500
56502
 
56501
56503
  if (this.config.searchable || this.config.searchableFields) {
56502
56504
  this.addFeaturesToDB(features);
56503
56505
  }
56504
56506
  } else {
56505
- this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
56507
+ this.featureCache = new FeatureCache([], genomicInterval); // Empty cache
56506
56508
  }
56507
56509
  }
56508
56510
 
@@ -58489,7 +58491,7 @@
58489
58491
  // single-exon transcript
58490
58492
  const xLeft = Math.max(0, coord.px);
58491
58493
  const xRight = Math.min(pixelWidth, coord.px1);
58492
- const width = Math.max(coord.pw, xRight - xLeft);
58494
+ const width = xRight - xLeft;
58493
58495
  ctx.fillRect(xLeft, py, width, h); // Arrows
58494
58496
  // Do not draw if strand is not +/-
58495
58497
 
@@ -59273,12 +59275,14 @@
59273
59275
  this.featureType = 'numeric';
59274
59276
  this.paintAxis = paintAxis;
59275
59277
  const format = config.format ? config.format.toLowerCase() : config.format;
59276
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
59277
- this.logScale = config.logScale ? config.logScale : false;
59278
59278
 
59279
59279
  if ("bigwig" === format) {
59280
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
59281
+ this.logScale = config.logScale ? config.logScale : false;
59280
59282
  this.featureSource = new BWSource(config, this.browser.genome);
59281
59283
  } else if ("tdf" === format) {
59284
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
59285
+ this.logScale = config.logScale ? config.logScale : false;
59282
59286
  this.featureSource = new TDFSource(config, this.browser.genome);
59283
59287
  } else {
59284
59288
  this.featureSource = FeatureSource(config, this.browser.genome);
@@ -60055,7 +60059,7 @@
60055
60059
 
60056
60060
  const sortHandler = sort => {
60057
60061
  const viewport = clickState.viewport;
60058
- const features = viewport.cachedFeatures;
60062
+ const features = viewport.getCachedFeatures();
60059
60063
  this.sortSamples(sort.chr, sort.start, sort.end, sort.direction, features);
60060
60064
  };
60061
60065
 
@@ -60161,16 +60165,10 @@
60161
60165
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
60162
60166
  * THE SOFTWARE.
60163
60167
  */
60164
- /**
60165
- * Represents 2 or more wig tracks overlaid on a common viewport.
60166
- */
60167
60168
 
60168
60169
  class MergedTrack extends TrackBase {
60169
60170
  constructor(config, browser) {
60170
60171
  super(config, browser);
60171
- this.type = "merged";
60172
- this.featureType = 'numeric';
60173
- this.paintAxis = paintAxis;
60174
60172
  }
60175
60173
 
60176
60174
  init(config) {
@@ -60181,6 +60179,21 @@
60181
60179
  super.init(config);
60182
60180
  }
60183
60181
 
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
+
60184
60197
  async postInit() {
60185
60198
  this.tracks = [];
60186
60199
  const p = [];
@@ -60202,83 +60215,45 @@
60202
60215
  }
60203
60216
  }
60204
60217
 
60205
- this.flipAxis = this.config.flipAxis ? this.config.flipAxis : false;
60206
- this.logScale = this.config.logScale ? this.config.logScale : false;
60207
- this.autoscale = this.config.autoscale || this.config.max === undefined;
60208
-
60209
- if (!this.autoscale) {
60210
- this.dataRange = {
60211
- min: this.config.min || 0,
60212
- max: this.config.max
60213
- };
60214
- }
60215
-
60216
- for (let t of this.tracks) {
60217
- t.autoscale = false;
60218
- t.dataRange = this.dataRange;
60219
- }
60220
-
60221
- this.height = this.config.height || 50;
60218
+ this.height = this.config.height || 100;
60222
60219
  return Promise.all(p);
60223
60220
  }
60224
60221
 
60225
- get height() {
60226
- return this._height;
60227
- }
60228
-
60229
- set height(h) {
60230
- this._height = h;
60231
-
60232
- if (this.tracks) {
60233
- for (let t of this.tracks) {
60234
- t.height = h;
60235
- t.config.height = h;
60236
- }
60237
- }
60238
- }
60239
-
60240
- menuItemList() {
60241
- let items = [];
60242
-
60243
- if (this.flipAxis !== undefined) {
60244
- items.push({
60245
- label: "Flip y-axis",
60246
- click: () => {
60247
- this.flipAxis = !this.flipAxis;
60248
- this.trackView.repaintViews();
60249
- }
60250
- });
60251
- }
60252
-
60253
- items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
60254
- return items;
60255
- }
60256
-
60257
60222
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
60258
60223
  const promises = this.tracks.map(t => t.getFeatures(chr, bpStart, bpEnd, bpPerPixel));
60259
60224
  return Promise.all(promises);
60260
60225
  }
60261
60226
 
60262
60227
  draw(options) {
60263
- const mergedFeatures = options.features; // Array of feature arrays, 1 for each track
60228
+ var i, len, mergedFeatures, trackOptions, dataRange;
60229
+ mergedFeatures = options.features; // Array of feature arrays, 1 for each track
60264
60230
 
60265
- if (this.autoscale) {
60266
- this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
60267
- }
60231
+ dataRange = autoscale(options.referenceFrame.chr, mergedFeatures); //IGVGraphics.fillRect(options.context, 0, options.pixelTop, options.pixelWidth, options.pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
60268
60232
 
60269
- for (let i = 0, len = this.tracks.length; i < len; i++) {
60270
- const trackOptions = Object.assign({}, options);
60233
+ for (i = 0, len = this.tracks.length; i < len; i++) {
60234
+ trackOptions = Object.assign({}, options);
60271
60235
  trackOptions.features = mergedFeatures[i];
60272
- this.tracks[i].dataRange = this.dataRange;
60273
- this.tracks[i].flipAxis = this.flipAxis;
60274
- this.tracks[i].logScale = this.logScale;
60275
- this.tracks[i].graphType = this.graphType;
60236
+ this.tracks[i].dataRange = dataRange;
60276
60237
  this.tracks[i].draw(trackOptions);
60277
60238
  }
60278
60239
  }
60279
60240
 
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
+
60280
60255
  popupData(clickState, features) {
60281
- const featuresArray = features || clickState.viewport.cachedFeatures;
60256
+ const featuresArray = features || clickState.viewport.getCachedFeatures();
60282
60257
 
60283
60258
  if (featuresArray && featuresArray.length === this.tracks.length) {
60284
60259
  // Array of feature arrays, 1 for each track
@@ -60296,23 +60271,39 @@
60296
60271
  }
60297
60272
 
60298
60273
  supportsWholeGenome() {
60299
- return this.tracks.every(track => track.supportsWholeGenome());
60274
+ const b = this.tracks.every(track => track.supportsWholeGenome());
60275
+ return b;
60300
60276
  }
60301
60277
 
60302
60278
  }
60303
60279
 
60304
60280
  function autoscale(chr, featureArrays) {
60305
- let min = 0;
60306
- let max = -Number.MAX_VALUE;
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 {
60307
60298
 
60308
- for (let features of featureArrays) {
60309
- for (let f of features) {
60299
+ featureArrays.forEach(function (features, i) {
60300
+ features.forEach(function (f) {
60310
60301
  if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
60311
60302
  min = Math.min(min, f.value);
60312
60303
  max = Math.max(max, f.value);
60313
60304
  }
60314
- }
60315
- }
60305
+ });
60306
+ }); // }
60316
60307
 
60317
60308
  return {
60318
60309
  min: min,
@@ -60896,21 +60887,20 @@
60896
60887
  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
60897
60888
 
60898
60889
  const inView = cachedFeatures.filter(f => f.drawState);
60899
- if (inView.length === 0) return;
60890
+ if (inView.length === 0) erturn;
60900
60891
  this.browser.circularViewVisible = true;
60901
- const chords = makeBedPEChords(inView);
60902
- sendChords(chords, this, refFrame, 0.5); //
60903
- //
60904
- // // for filtered set, distinguishing the chromosomes is more critical than tracks
60905
- // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5)
60906
- // const trackColor = IGVColor.addAlpha(this.color, 0.5)
60907
- //
60908
- // // name the chord set to include locus and filtering information
60909
- // const encodedName = this.name.replaceAll(' ', '%20')
60910
- // const chordSetName = "all" === refFrame.chr ?
60911
- // encodedName :
60912
- // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`
60913
- // this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor})
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
+ });
60914
60904
  }
60915
60905
 
60916
60906
  doAutoscale(features) {
@@ -60997,7 +60987,7 @@
60997
60987
  clickedFeatures(clickState, features) {
60998
60988
  // We use the cached features rather than method to avoid async load. If the
60999
60989
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
61000
- const featureList = features || clickState.viewport.cachedFeatures;
60990
+ const featureList = features || clickState.viewport.getCachedFeatures();
61001
60991
  const candidates = [];
61002
60992
 
61003
60993
  if (featureList) {
@@ -61583,7 +61573,7 @@
61583
61573
  variantColor = "gray";
61584
61574
  }
61585
61575
  } else if (this._color) {
61586
- variantColor = this.color;
61576
+ variantColor = typeof this._color === "function" ? this._color(v) : this._color;
61587
61577
  } else if ("NONVARIANT" === v.type) {
61588
61578
  variantColor = this.nonRefColor;
61589
61579
  } else if ("MIXED" === v.type) {
@@ -61595,10 +61585,6 @@
61595
61585
  return variantColor;
61596
61586
  }
61597
61587
 
61598
- get color() {
61599
- return this._color ? typeof this._color === "function" ? this._color(v) : this._color : this.defaultColor;
61600
- }
61601
-
61602
61588
  clickedFeatures(clickState, features) {
61603
61589
  let featureList = super.clickedFeatures(clickState, features);
61604
61590
  const vGap = this.displayMode === 'EXPANDED' ? this.expandedVGap : this.squishedVGap;
@@ -61864,10 +61850,24 @@
61864
61850
  menuItems.push({
61865
61851
  label: 'Add SVs to circular view',
61866
61852
  click: () => {
61853
+ const inView = [];
61867
61854
 
61868
61855
  for (let viewport of this.trackView.viewports) {
61869
- this.sendChordsForViewport(viewport);
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
+ }
61870
61863
  }
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
61871
  }
61872
61872
  });
61873
61873
  }
@@ -61883,20 +61883,20 @@
61883
61883
  list.push({
61884
61884
  label: 'Add SVs to Circular View',
61885
61885
  click: () => {
61886
- this.sendChordsForViewport(viewport);
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
+ });
61887
61894
  }
61888
61895
  });
61889
61896
  list.push('<hr/>');
61890
61897
  return list;
61891
61898
  }
61892
61899
  }
61893
-
61894
- sendChordsForViewport(viewport) {
61895
- const refFrame = viewport.referenceFrame;
61896
- const inView = "all" === refFrame.chr ? this.featureSource.getAllFeatures() : this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
61897
- const chords = makeVCFChords(inView);
61898
- sendChords(chords, this, refFrame, 0.5);
61899
- }
61900
61900
  /**
61901
61901
  * Create a "color by" checkbox menu item, optionally initially checked
61902
61902
  * @param menuItem
@@ -62199,7 +62199,7 @@
62199
62199
 
62200
62200
 
62201
62201
  popupData(clickState) {
62202
- let features = clickState.viewport.cachedFeatures;
62202
+ let features = clickState.viewport.getCachedFeatures();
62203
62203
  if (!features || features.length === 0) return [];
62204
62204
  const tolerance = 3;
62205
62205
  const tissue = this.name;
@@ -62545,7 +62545,7 @@
62545
62545
  popupData(clickState) {
62546
62546
  let data = [];
62547
62547
  const track = clickState.viewport.trackView.track;
62548
- const features = clickState.viewport.cachedFeatures;
62548
+ const features = clickState.viewport.getCachedFeatures();
62549
62549
 
62550
62550
  if (features) {
62551
62551
  let count = 0;
@@ -63234,7 +63234,7 @@
63234
63234
  if (!this.featureCache) {
63235
63235
  const options = buildOptions(this.config);
63236
63236
  const data = await igvxhr.loadString(this.config.url, options);
63237
- this.featureCache = new FeatureCache$1(parseBP(data), genome);
63237
+ this.featureCache = new FeatureCache(parseBP(data), genome);
63238
63238
  return this.featureCache.queryFeatures(chr, start, end);
63239
63239
  } else {
63240
63240
  return this.featureCache.queryFeatures(chr, start, end);
@@ -64477,15 +64477,6 @@
64477
64477
  this.id = guid$2();
64478
64478
  }
64479
64479
 
64480
- extend(locus) {
64481
- const newStart = Math.min(locus.start, this.start);
64482
- const newEnd = Math.max(locus.end, this.end);
64483
- const ratio = (newEnd - newStart) / (this.end - this.start);
64484
- this.start = newStart;
64485
- this.end = newEnd;
64486
- this.bpPerPixel *= ratio;
64487
- }
64488
-
64489
64480
  calculateEnd(pixels) {
64490
64481
  return this.start + this.bpPerPixel * pixels;
64491
64482
  }
@@ -64570,7 +64561,7 @@
64570
64561
  const viewChanged = start !== this.start || bpPerPixel !== this.bpPerPixel;
64571
64562
 
64572
64563
  if (viewChanged) {
64573
- await browser.updateViews(true);
64564
+ await browser.updateViews(this);
64574
64565
  }
64575
64566
  }
64576
64567
 
@@ -66535,12 +66526,7 @@
66535
66526
  }
66536
66527
  }
66537
66528
 
66538
- await this.loadTrackList(trackConfigurations); // The ruler track is not explicitly loaded, but needs updated nonetheless.
66539
-
66540
- for (let rtv of this.trackViews.filter(tv => tv.track.type === 'ruler')) {
66541
- rtv.updateViews();
66542
- }
66543
-
66529
+ await this.loadTrackList(trackConfigurations);
66544
66530
  this.updateUIWithReferenceFrameList();
66545
66531
  }
66546
66532
 
@@ -66705,22 +66691,26 @@
66705
66691
  }
66706
66692
 
66707
66693
  async loadTrackList(configList) {
66708
- const promises = [];
66694
+ try {
66695
+ const promises = [];
66709
66696
 
66710
- for (let config of configList) {
66711
- promises.push(this.loadTrack(config));
66712
- }
66697
+ for (let config of configList) {
66698
+ promises.push(this.loadTrack(config, false));
66699
+ }
66713
66700
 
66714
- const loadedTracks = await Promise.all(promises);
66715
- const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
66716
- return trackView.track.autoscaleGroup;
66717
- });
66701
+ const loadedTracks = await Promise.all(promises);
66702
+ const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
66703
+ return trackView.track.autoscaleGroup;
66704
+ });
66718
66705
 
66719
- if (groupAutoscaleViews.length > 0) {
66720
- this.updateViews();
66721
- }
66706
+ if (groupAutoscaleViews.length > 0) {
66707
+ this.updateViews(groupAutoscaleViews);
66708
+ }
66722
66709
 
66723
- return loadedTracks;
66710
+ return loadedTracks;
66711
+ } finally {
66712
+ await this.resize();
66713
+ }
66724
66714
  }
66725
66715
 
66726
66716
  async loadROI(config) {
@@ -66734,9 +66724,7 @@
66734
66724
  }
66735
66725
  } else {
66736
66726
  this.roi.push(new ROI(config, this.genome));
66737
- } // Force reload all views (force = true) to insure ROI features are loaded. Wasteful but this function is
66738
- // rarely called.
66739
-
66727
+ }
66740
66728
 
66741
66729
  await this.updateViews(true);
66742
66730
  }
@@ -66750,7 +66738,7 @@
66750
66738
  }
66751
66739
 
66752
66740
  for (let tv of this.trackViews) {
66753
- tv.repaintViews();
66741
+ tv.updateViews(true);
66754
66742
  }
66755
66743
  }
66756
66744
 
@@ -66758,7 +66746,7 @@
66758
66746
  this.roi = [];
66759
66747
 
66760
66748
  for (let tv of this.trackViews) {
66761
- tv.repaintViews();
66749
+ tv.updateViews(true);
66762
66750
  }
66763
66751
  }
66764
66752
 
@@ -66774,13 +66762,25 @@
66774
66762
  /**
66775
66763
  * Return a promise to load a track.
66776
66764
  *
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
66777
  * @param config
66778
66778
  * @param doResize - undefined by default
66779
66779
  * @returns {*}
66780
66780
  */
66781
66781
 
66782
66782
 
66783
- async loadTrack(config) {
66783
+ async loadTrack(config, doResize) {
66784
66784
  // config might be json
66785
66785
  if (isString$3(config)) {
66786
66786
  config = JSON.parse(config);
@@ -66845,6 +66845,11 @@
66845
66845
 
66846
66846
  msg += ": " + config.url;
66847
66847
  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
+ }
66848
66853
  }
66849
66854
  }
66850
66855
  /**
@@ -67077,7 +67082,40 @@
67077
67082
  this.navbarManager.navbarDidResize(this.$navigation.width(), isWGV);
67078
67083
  }
67079
67084
 
67080
- await resize.call(this);
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();
67081
67119
  }
67082
67120
 
67083
67121
  async updateViews(force) {
@@ -67153,12 +67191,6 @@
67153
67191
  }
67154
67192
  }
67155
67193
 
67156
- repaintViews() {
67157
- for (let trackView of this.trackViews) {
67158
- trackView.repaintViews();
67159
- }
67160
- }
67161
-
67162
67194
  updateLocusSearchWidget() {
67163
67195
  const referenceFrameList = this.referenceFrameList; // Update end position of reference frames based on pixel widths. This is hacky, but its been done here
67164
67196
  // for a long time, although indirectly.
@@ -67250,54 +67282,7 @@
67250
67282
  }
67251
67283
 
67252
67284
  this.centerLineList = this.createCenterLineList(this.columnContainer);
67253
- await resize.call(this);
67254
- }
67255
- /**
67256
- * Add a new multi-locus panel for the specified region
67257
- * @param chr
67258
- * @param start
67259
- * @param end
67260
- * @param referenceFrameLeft - optional, if supplied new panel should be placed to the immediate right
67261
- */
67262
-
67263
-
67264
- async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
67265
- // account for reduced viewport width as a result of adding right mate pair panel
67266
- const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
67267
- const scaleFactor = this.calculateViewportWidth(this.referenceFrameList.length) / this.calculateViewportWidth(1 + this.referenceFrameList.length);
67268
-
67269
- for (let refFrame of this.referenceFrameList) {
67270
- refFrame.bpPerPixel *= scaleFactor;
67271
- }
67272
-
67273
- const bpp = (end - start) / viewportWidth;
67274
- const newReferenceFrame = new ReferenceFrame(this.genome, chr, start, end, bpp);
67275
- const indexLeft = referenceFrameLeft ? this.referenceFrameList.indexOf(referenceFrameLeft) : this.referenceFrameList.length - 1;
67276
- const indexRight = 1 + indexLeft; // TODO -- this is really ugly
67277
-
67278
- const {
67279
- $viewport
67280
- } = this.trackViews[0].viewports[indexLeft];
67281
- const viewportColumn = viewportColumnManager.insertAfter($viewport.get(0).parentElement);
67282
-
67283
- if (indexRight === this.referenceFrameList.length) {
67284
- this.referenceFrameList.push(newReferenceFrame);
67285
-
67286
- for (let trackView of this.trackViews) {
67287
- const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
67288
- trackView.viewports.push(viewport);
67289
- }
67290
- } else {
67291
- this.referenceFrameList.splice(indexRight, 0, newReferenceFrame);
67292
-
67293
- for (let trackView of this.trackViews) {
67294
- const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
67295
- trackView.viewports.splice(indexRight, 0, viewport);
67296
- }
67297
- }
67298
-
67299
- this.centerLineList = this.createCenterLineList(this.columnContainer);
67300
- resize.call(this);
67285
+ await this.resize();
67301
67286
  }
67302
67287
 
67303
67288
  async removeMultiLocusPanel(referenceFrame) {
@@ -67326,15 +67311,8 @@
67326
67311
  const scaleFactor = this.calculateViewportWidth(1 + this.referenceFrameList.length) / this.calculateViewportWidth(this.referenceFrameList.length);
67327
67312
  await this.rescaleForMultiLocus(scaleFactor);
67328
67313
  }
67329
- /**
67330
- * Goto the locus represented by the selected referenceFrame, discarding all other panels
67331
- *
67332
- * @param referenceFrame
67333
- * @returns {Promise<void>}
67334
- */
67335
67314
 
67336
-
67337
- async gotoMultilocusPanel(referenceFrame) {
67315
+ async selectMultiLocusPanel(referenceFrame) {
67338
67316
  const referenceFrameIndex = this.referenceFrameList.indexOf(referenceFrame); // Remove columns for unselected panels
67339
67317
 
67340
67318
  this.columnContainer.querySelectorAll('.igv-column').forEach((column, c) => {
@@ -67382,7 +67360,7 @@
67382
67360
 
67383
67361
  this.centerLineList = this.createCenterLineList(this.columnContainer);
67384
67362
  this.updateUIWithReferenceFrameList();
67385
- await this.updateViews();
67363
+ await this.updateViews(true);
67386
67364
  }
67387
67365
  /**
67388
67366
  * @deprecated This is a deprecated method with no known usages. To be removed in a future release.
@@ -67636,9 +67614,18 @@
67636
67614
  return surl;
67637
67615
  }
67638
67616
 
67639
- currentReferenceFrames() {
67617
+ currentLoci() {
67618
+ const loci = [];
67640
67619
  const anyTrackView = this.trackViews[0];
67641
- return anyTrackView.viewports.map(vp => vp.referenceFrame);
67620
+
67621
+ for (let {
67622
+ referenceFrame
67623
+ } of anyTrackView.viewports) {
67624
+ const locusString = referenceFrame.getLocusString();
67625
+ loci.push(locusString);
67626
+ }
67627
+
67628
+ return loci;
67642
67629
  }
67643
67630
  /**
67644
67631
  * Record a mouse click on a specific viewport. This might be the start of a drag operation. Dragging
@@ -67751,9 +67738,12 @@
67751
67738
  }
67752
67739
 
67753
67740
  addWindowResizeHandler() {
67754
- // Create a copy of the prototype "resize" function bound to this instance. Neccessary to support removing.
67755
- this.boundWindowResizeHandler = resize.bind(this);
67741
+ this.boundWindowResizeHandler = windowResizeHandler.bind(this);
67756
67742
  window.addEventListener('resize', this.boundWindowResizeHandler);
67743
+
67744
+ function windowResizeHandler() {
67745
+ this.resize();
67746
+ }
67757
67747
  }
67758
67748
 
67759
67749
  removeWindowResizeHandler() {
@@ -67847,47 +67837,6 @@
67847
67837
  }
67848
67838
 
67849
67839
  }
67850
- /**
67851
- * Function called win window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
67852
- * than class method because it needs to be copied and bound to specific instances of browser to support listener
67853
- * removal
67854
- *
67855
- * @returns {Promise<void>}
67856
- */
67857
-
67858
-
67859
- async function resize() {
67860
- const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
67861
-
67862
- for (let referenceFrame of this.referenceFrameList) {
67863
- const index = this.referenceFrameList.indexOf(referenceFrame);
67864
- const {
67865
- chr,
67866
- genome
67867
- } = referenceFrame;
67868
- const {
67869
- bpLength
67870
- } = genome.getChromosome(referenceFrame.chr);
67871
- const viewportWidthBP = referenceFrame.toBP(viewportWidth); // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
67872
-
67873
- if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
67874
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
67875
- referenceFrame.bpPerPixel = bpLength / viewportWidth;
67876
- } else {
67877
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
67878
- referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
67879
- }
67880
-
67881
- for (let {
67882
- viewports
67883
- } of this.trackViews) {
67884
- viewports[index].setWidth(viewportWidth);
67885
- }
67886
- }
67887
-
67888
- this.updateUIWithReferenceFrameList();
67889
- await this.updateViews(true);
67890
- }
67891
67840
 
67892
67841
  function handleMouseMove(e) {
67893
67842
  e.preventDefault();