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.esm.js CHANGED
@@ -17624,7 +17624,7 @@ function Node(interval) {
17624
17624
  * @constructor
17625
17625
  */
17626
17626
 
17627
- class FeatureCache$1 {
17627
+ class FeatureCache {
17628
17628
 
17629
17629
  constructor(featureList, genome, range) {
17630
17630
 
@@ -20588,13 +20588,14 @@ class Viewport {
20588
20588
  console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
20589
20589
  }
20590
20590
 
20591
- checkContentHeight(features) {
20591
+ checkContentHeight() {
20592
20592
 
20593
20593
  let track = this.trackView.track;
20594
- features = features || this.cachedFeatures;
20594
+
20595
20595
  if ("FILL" === track.displayMode) {
20596
20596
  this.setContentHeight(this.$viewport.height());
20597
20597
  } else if (typeof track.computePixelHeight === 'function') {
20598
+ let features = this.cachedFeatures;
20598
20599
  if (features && features.length > 0) {
20599
20600
  let requiredContentHeight = track.computePixelHeight(features);
20600
20601
  let currentContentHeight = this.$content.height();
@@ -20610,11 +20611,12 @@ class Viewport {
20610
20611
  }
20611
20612
 
20612
20613
  setContentHeight(contentHeight) {
20613
-
20614
20614
  // Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
20615
20615
  contentHeight = Math.min(contentHeight, 32000);
20616
+
20616
20617
  this.$content.height(contentHeight);
20617
- if (this.canvas._data) this.canvas._data.invalidate = true;
20618
+
20619
+ if (this.tile) this.tile.invalidate = true;
20618
20620
  }
20619
20621
 
20620
20622
  isLoading() {
@@ -20631,6 +20633,8 @@ class Viewport {
20631
20633
 
20632
20634
  setWidth(width) {
20633
20635
  this.$viewport.width(width);
20636
+ this.canvas.style.width = (`${width}px`);
20637
+ this.canvas.setAttribute('width', width);
20634
20638
  }
20635
20639
 
20636
20640
  getWidth() {
@@ -22757,7 +22761,7 @@ const Cytoband = function (start, end, name, typestain) {
22757
22761
  }
22758
22762
  };
22759
22763
 
22760
- const _version = "2.11.1";
22764
+ const _version = "2.11.2";
22761
22765
  function version() {
22762
22766
  return _version
22763
22767
  }
@@ -23292,21 +23296,17 @@ class TrackViewport extends Viewport {
23292
23296
  checkZoomIn() {
23293
23297
 
23294
23298
  const showZoomInNotice = () => {
23299
+ const referenceFrame = this.referenceFrame;
23295
23300
  if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome()) {
23296
23301
  return true
23297
23302
  } else {
23298
23303
  const visibilityWindow = this.trackView.track.visibilityWindow;
23299
23304
  return (
23300
23305
  visibilityWindow !== undefined && visibilityWindow > 0 &&
23301
- (this.referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow))
23306
+ (referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow))
23302
23307
  }
23303
23308
  };
23304
23309
 
23305
- if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
23306
- if (this.canvas) ;
23307
- return false
23308
- }
23309
-
23310
23310
  if (!(this.viewIsReady())) {
23311
23311
  return false
23312
23312
  }
@@ -23316,8 +23316,7 @@ class TrackViewport extends Viewport {
23316
23316
  // Out of visibility window
23317
23317
  if (this.canvas) {
23318
23318
  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
23319
- this.canvas._data = undefined;
23320
- this.featureCache = undefined;
23319
+ this.tile = undefined;
23321
23320
  }
23322
23321
  this.$zoomInNotice.show();
23323
23322
 
@@ -23334,19 +23333,21 @@ class TrackViewport extends Viewport {
23334
23333
  }
23335
23334
 
23336
23335
  return true
23336
+
23337
+
23337
23338
  }
23338
23339
 
23339
- /**
23340
- * Adjust the canvas to the current genomic state.
23341
- */
23342
23340
  shift() {
23343
- const referenceFrame = this.referenceFrame;
23344
- if (this.canvas &&
23345
- this.canvas._data &&
23346
- this.canvas._data.chr === this.referenceFrame.chr &&
23347
- this.canvas._data.bpPerPixel === referenceFrame.bpPerPixel) {
23348
- const pixelOffset = Math.round((this.canvas._data.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23349
- this.canvas.style.left = pixelOffset + "px";
23341
+ const self = this;
23342
+ const referenceFrame = self.referenceFrame;
23343
+
23344
+ if (self.canvas &&
23345
+ self.tile &&
23346
+ self.tile.chr === self.referenceFrame.chr &&
23347
+ self.tile.bpPerPixel === referenceFrame.bpPerPixel) {
23348
+
23349
+ const pixelOffset = Math.round((self.tile.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23350
+ self.canvas.style.left = pixelOffset + "px";
23350
23351
  }
23351
23352
  }
23352
23353
 
@@ -23369,23 +23370,22 @@ class TrackViewport extends Viewport {
23369
23370
  this.startSpinner();
23370
23371
 
23371
23372
  try {
23372
- const track = this.trackView.track;
23373
- const features = await this.getFeatures(track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23373
+ const features = await this.getFeatures(this.trackView.track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23374
23374
  let roiFeatures = [];
23375
- const roi = mergeArrays(this.browser.roi, track.roi);
23375
+ const roi = mergeArrays(this.browser.roi, this.trackView.track.roi);
23376
23376
  if (roi) {
23377
23377
  for (let r of roi) {
23378
- const f = await r.getFeatures(chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23378
+ const f = await
23379
+ r.getFeatures(chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23379
23380
  roiFeatures.push({track: r, features: f});
23380
23381
  }
23381
23382
  }
23382
23383
 
23383
- const mr = track && ("wig" === track.type || "merged" === track.type); // wig tracks are potentially multiresolution (e.g. bigwig)
23384
- this.featureCache = new FeatureCache(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures, mr);
23384
+ this.tile = new Tile(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures);
23385
23385
  this.loading = false;
23386
23386
  this.hideMessage();
23387
23387
  this.stopSpinner();
23388
- return this.featureCache
23388
+ return this.tile
23389
23389
  } catch (error) {
23390
23390
  // Track might have been removed during load
23391
23391
  if (this.trackView && this.trackView.disposed !== true) {
@@ -23399,31 +23399,25 @@ class TrackViewport extends Viewport {
23399
23399
  }
23400
23400
  }
23401
23401
 
23402
- /**
23403
- * Repaint the canvas for the current genomic state.
23404
- *
23405
- * @returns {Promise<void>}
23406
- */
23407
- repaint() {
23402
+ async repaint() {
23408
23403
 
23409
- if (undefined === this.featureCache) {
23404
+ if (undefined === this.tile) {
23410
23405
  return
23411
23406
  }
23412
23407
 
23413
- let {features, roiFeatures} = this.featureCache;
23414
- //this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
23408
+ let {features, roiFeatures, bpPerPixel, startBP, endBP} = this.tile;
23415
23409
 
23416
23410
  // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23417
23411
  const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
23418
-
23419
23412
  let pixelWidth;
23420
- const startBP = this.featureCache.startBP;
23421
- const endBP = this.featureCache.endBP;
23422
- let bpPerPixel = this.referenceFrame.bpPerPixel;
23413
+
23423
23414
  if (isWGV) {
23415
+ bpPerPixel = this.referenceFrame.end / this.$viewport.width();
23416
+ startBP = 0;
23417
+ endBP = this.referenceFrame.end;
23424
23418
  pixelWidth = this.$viewport.width();
23425
23419
  } else {
23426
- pixelWidth = 3 * this.$viewport.width();
23420
+ pixelWidth = Math.ceil((endBP - startBP) / bpPerPixel);
23427
23421
  }
23428
23422
 
23429
23423
  // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
@@ -23483,27 +23477,17 @@ class TrackViewport extends Viewport {
23483
23477
 
23484
23478
  this.draw(drawConfiguration, features, roiFeatures);
23485
23479
 
23486
- // Attach metadata to canvas
23487
- newCanvas._data = {
23488
- chr: this.referenceFrame.chr, startBP, endBP, bpPerPixel, top: canvasTop, bottom: canvasTop + pixelHeight
23489
- };
23480
+ this.canvasVerticalRange = {top: canvasTop, bottom: canvasTop + pixelHeight};
23490
23481
 
23491
- if (this.canvas) {
23492
- $$1(this.canvas).remove();
23482
+ if (this.$canvas) {
23483
+ this.$canvas.remove();
23493
23484
  }
23485
+ this.$canvas = $$1(newCanvas);
23486
+ this.$content.append(this.$canvas);
23494
23487
  this.canvas = newCanvas;
23495
23488
  this.ctx = ctx;
23496
- this.$content.append($$1(newCanvas));
23497
-
23498
23489
  }
23499
23490
 
23500
- /**
23501
- * Draw the associated track.
23502
- *
23503
- * @param drawConfiguration
23504
- * @param features
23505
- * @param roiFeatures
23506
- */
23507
23491
  draw(drawConfiguration, features, roiFeatures) {
23508
23492
 
23509
23493
  // console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
@@ -23520,6 +23504,60 @@ class TrackViewport extends Viewport {
23520
23504
  }
23521
23505
  }
23522
23506
 
23507
+ // TODO: Nolonger used. Will discard
23508
+ async toSVG(tile) {
23509
+
23510
+ // Nothing to do if zoomInNotice is active
23511
+ if (this.$zoomInNotice && this.$zoomInNotice.is(":visible")) {
23512
+ return
23513
+ }
23514
+
23515
+ const referenceFrame = this.referenceFrame;
23516
+ const bpPerPixel = tile.bpPerPixel;
23517
+ const features = tile.features;
23518
+ const roiFeatures = tile.roiFeatures;
23519
+ const pixelWidth = this.$viewport.width();
23520
+ const pixelHeight = this.$viewport.height();
23521
+ const bpStart = referenceFrame.start;
23522
+ const bpEnd = referenceFrame.start + pixelWidth * referenceFrame.bpPerPixel;
23523
+
23524
+ const ctx$1 = new ctx(
23525
+ {
23526
+ // svg
23527
+ width: pixelWidth,
23528
+ height: pixelHeight,
23529
+ viewbox:
23530
+ {
23531
+ x: 0,
23532
+ y: -this.$content.position().top,
23533
+ width: pixelWidth,
23534
+ height: pixelHeight
23535
+ }
23536
+
23537
+ });
23538
+
23539
+ const drawConfiguration =
23540
+ {
23541
+ viewport: this,
23542
+ context: ctx$1,
23543
+ top: -this.$content.position().top,
23544
+ pixelTop: 0, // for compatibility with canvas draw
23545
+ pixelWidth,
23546
+ pixelHeight,
23547
+ bpStart,
23548
+ bpEnd,
23549
+ bpPerPixel,
23550
+ referenceFrame: this.referenceFrame,
23551
+ selection: this.selection,
23552
+ viewportWidth: pixelWidth,
23553
+ };
23554
+
23555
+ this.draw(drawConfiguration, features, roiFeatures);
23556
+
23557
+ return ctx$1.getSerializedSvg(true)
23558
+
23559
+ }
23560
+
23523
23561
  containsPosition(chr, position) {
23524
23562
  if (this.referenceFrame.chr === chr && position >= this.referenceFrame.start) {
23525
23563
  return position <= this.referenceFrame.calculateEnd(this.getWidth())
@@ -23532,12 +23570,11 @@ class TrackViewport extends Viewport {
23532
23570
  return this.loading
23533
23571
  }
23534
23572
 
23535
- savePNG() {
23573
+ saveImage() {
23536
23574
 
23537
23575
  if (!this.ctx) return
23538
23576
 
23539
- const canvasMetadata = this.canvas._data;
23540
- const canvasTop = canvasMetadata ? canvasMetadata.top : 0;
23577
+ const canvasTop = this.canvasVerticalRange ? this.canvasVerticalRange.top : 0;
23541
23578
  const devicePixelRatio = window.devicePixelRatio;
23542
23579
  const w = this.$viewport.width() * devicePixelRatio;
23543
23580
  const h = this.$viewport.height() * devicePixelRatio;
@@ -23667,25 +23704,26 @@ class TrackViewport extends Viewport {
23667
23704
  selection: this.selection
23668
23705
  };
23669
23706
 
23670
- const features = this.featureCache ? this.featureCache.features : [];
23671
- const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
23707
+ const features = this.tile ? this.tile.features : [];
23708
+ const roiFeatures = this.tile ? this.tile.roiFeatures : undefined;
23672
23709
  this.draw(config, features, roiFeatures);
23673
23710
 
23674
23711
  context.restore();
23675
23712
 
23676
23713
  }
23677
23714
 
23678
- get cachedFeatures() {
23679
- return this.featureCache ? this.featureCache.features : []
23715
+ getCachedFeatures() {
23716
+ return this.tile ? this.tile.features : []
23680
23717
  }
23681
23718
 
23682
23719
  async getFeatures(track, chr, start, end, bpPerPixel) {
23683
23720
 
23684
- if (this.featureCache && this.featureCache.containsRange(chr, start, end, bpPerPixel)) {
23685
- return this.featureCache.features
23721
+ if (this.tile && this.tile.containsRange(chr, start, end, bpPerPixel)) {
23722
+ return this.tile.features
23686
23723
  } else if (typeof track.getFeatures === "function") {
23687
23724
  const features = await track.getFeatures(chr, start, end, bpPerPixel, this);
23688
- this.checkContentHeight(features);
23725
+ this.cachedFeatures = features;
23726
+ this.checkContentHeight();
23689
23727
  return features
23690
23728
  } else {
23691
23729
  return undefined
@@ -23923,7 +23961,9 @@ class TrackViewport extends Viewport {
23923
23961
  lastClickTime = time;
23924
23962
 
23925
23963
  }
23964
+
23926
23965
  }
23966
+
23927
23967
  }
23928
23968
 
23929
23969
  removeViewportClickHandler(viewport) {
@@ -23987,10 +24027,16 @@ function mouseUpHandler(event) {
23987
24027
  function createClickState(event, viewport) {
23988
24028
 
23989
24029
  const referenceFrame = viewport.referenceFrame;
24030
+
23990
24031
  const viewportCoords = translateMouseCoordinates$1(event, viewport.contentDiv);
23991
24032
  const canvasCoords = translateMouseCoordinates$1(event, viewport.canvas);
24033
+
23992
24034
  const genomicLocation = ((referenceFrame.start) + referenceFrame.toBP(viewportCoords.x));
23993
24035
 
24036
+ if (undefined === genomicLocation || null === viewport.tile) {
24037
+ return undefined
24038
+ }
24039
+
23994
24040
  return {
23995
24041
  event,
23996
24042
  viewport,
@@ -24051,30 +24097,22 @@ function formatPopoverText(nameValues) {
24051
24097
  return rows.join('')
24052
24098
  }
24053
24099
 
24054
- class FeatureCache {
24055
-
24056
- constructor(chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures, multiresolution) {
24057
- this.chr = chr;
24058
- this.startBP = tileStart;
24059
- this.endBP = tileEnd;
24060
- this.bpPerPixel = bpPerPixel;
24061
- this.features = features;
24062
- this.roiFeatures = roiFeatures;
24063
- this.multiresolution = multiresolution;
24064
- }
24065
-
24066
- containsRange(chr, start, end, bpPerPixel) {
24067
-
24068
- // For multi-resolution tracks allow for a 2X change in bpPerPixel
24069
- const r = this.multiresolution ? this.bpPerPixel / bpPerPixel : 1;
24100
+ var Tile = function (chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures) {
24101
+ this.chr = chr;
24102
+ this.startBP = tileStart;
24103
+ this.endBP = tileEnd;
24104
+ this.bpPerPixel = bpPerPixel;
24105
+ this.features = features;
24106
+ this.roiFeatures = roiFeatures;
24107
+ };
24070
24108
 
24071
- return start >= this.startBP && end <= this.endBP && chr === this.chr && r > 0.5 && r < 2
24072
- }
24109
+ Tile.prototype.containsRange = function (chr, start, end, bpPerPixel) {
24110
+ return this.bpPerPixel === bpPerPixel && start >= this.startBP && end <= this.endBP && chr === this.chr
24111
+ };
24073
24112
 
24074
- overlapsRange(chr, start, end) {
24075
- return this.chr === chr && end >= this.startBP && start <= this.endBP
24076
- }
24077
- }
24113
+ Tile.prototype.overlapsRange = function (chr, start, end) {
24114
+ return this.chr === chr && end >= this.startBP && start <= this.endBP
24115
+ };
24078
24116
 
24079
24117
 
24080
24118
  /**
@@ -24381,18 +24419,6 @@ class PairedAlignment {
24381
24419
  return true // By definition
24382
24420
  }
24383
24421
 
24384
- isMateMapped() {
24385
- return true // By definition
24386
- }
24387
-
24388
- isProperPair() {
24389
- return this.firstAlignment.isProperPair()
24390
- }
24391
-
24392
- get tlen() {
24393
- return Math.abs(this.firstAlignment.fragmentLength)
24394
- }
24395
-
24396
24422
  firstOfPairStrand() {
24397
24423
 
24398
24424
  if (this.firstAlignment.isFirstOfPair()) {
@@ -24734,10 +24760,7 @@ function packAlignmentRows(alignments, start, end, showSoftClips) {
24734
24760
 
24735
24761
 
24736
24762
  class AlignmentContainer {
24737
-
24738
- // this.config.samplingWindowSize, this.config.samplingDepth,
24739
- // this.config.pairsSupported, this.config.alleleFreqThreshold)
24740
- constructor(chr, start, end, {samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold}) {
24763
+ constructor(chr, start, end, samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold) {
24741
24764
 
24742
24765
  this.chr = chr;
24743
24766
  this.start = Math.floor(start);
@@ -24765,12 +24788,17 @@ class AlignmentContainer {
24765
24788
  return alignment.isMapped() && !alignment.isFailsVendorQualityCheck()
24766
24789
  };
24767
24790
 
24791
+ this.pairedEndStats = new PairedEndStats();
24768
24792
  }
24769
24793
 
24770
24794
  push(alignment) {
24771
24795
 
24772
24796
  if (this.filter(alignment) === false) return
24773
24797
 
24798
+ if (alignment.isPaired()) {
24799
+ this.pairedEndStats.push(alignment);
24800
+ }
24801
+
24774
24802
  this.coverageMap.incCounts(alignment); // Count coverage before any downsampling
24775
24803
 
24776
24804
  if (this.pairsSupported && this.downsampledReads.has(alignment.readName)) {
@@ -24802,6 +24830,8 @@ class AlignmentContainer {
24802
24830
 
24803
24831
  this.pairsCache = undefined;
24804
24832
  this.downsampledReads = undefined;
24833
+
24834
+ this.pairedEndStats.compute();
24805
24835
  }
24806
24836
 
24807
24837
  contains(chr, start, end) {
@@ -25112,6 +25142,69 @@ class DownsampledInterval {
25112
25142
  }
25113
25143
  }
25114
25144
 
25145
+ class PairedEndStats {
25146
+
25147
+ constructor(lowerPercentile, upperPercentile) {
25148
+ this.totalCount = 0;
25149
+ this.frCount = 0;
25150
+ this.rfCount = 0;
25151
+ this.ffCount = 0;
25152
+ this.sumF = 0;
25153
+ this.sumF2 = 0;
25154
+ //this.lp = lowerPercentile === undefined ? 0.005 : lowerPercentile;
25155
+ //this.up = upperPercentile === undefined ? 0.995 : upperPercentile;
25156
+ //this.digest = new Digest();
25157
+ }
25158
+
25159
+ push(alignment) {
25160
+
25161
+ if (alignment.isProperPair()) {
25162
+
25163
+ var fragmentLength = Math.abs(alignment.fragmentLength);
25164
+ //this.digest.push(fragmentLength);
25165
+ this.sumF += fragmentLength;
25166
+ this.sumF2 += fragmentLength * fragmentLength;
25167
+
25168
+ var po = alignment.pairOrientation;
25169
+
25170
+ if (typeof po === "string" && po.length === 4) {
25171
+ var tmp = '' + po.charAt(0) + po.charAt(2);
25172
+ switch (tmp) {
25173
+ case 'FF':
25174
+ case 'RR':
25175
+ this.ffCount++;
25176
+ break
25177
+ case "FR":
25178
+ this.frCount++;
25179
+ break
25180
+ case"RF":
25181
+ this.rfCount++;
25182
+ }
25183
+ }
25184
+ this.totalCount++;
25185
+ }
25186
+ }
25187
+
25188
+ compute() {
25189
+
25190
+ if (this.totalCount > 100) {
25191
+ if (this.ffCount / this.totalCount > 0.9) this.orienation = "ff";
25192
+ else if (this.frCount / this.totalCount > 0.9) this.orienation = "fr";
25193
+ else if (this.rfCount / this.totalCount > 0.9) this.orienation = "rf";
25194
+
25195
+
25196
+ var fMean = this.sumF / this.totalCount;
25197
+ var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount));
25198
+ this.lowerFragmentLength = fMean - 3 * stdDev;
25199
+ this.upperFragmentLength = fMean + 3 * stdDev;
25200
+
25201
+ //this.lowerFragmentLength = this.digest.percentile(this.lp);
25202
+ //this.upperFragmentLength = this.digest.percentile(this.up);
25203
+ //this.digest = undefined;
25204
+ }
25205
+ }
25206
+ }
25207
+
25115
25208
  class SupplementaryAlignment {
25116
25209
 
25117
25210
  constructor(rec) {
@@ -26301,7 +26394,7 @@ class BamReaderNonIndexed {
26301
26394
  const header = this.header;
26302
26395
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
26303
26396
  const qAlignments = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
26304
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
26397
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
26305
26398
  for (let a of qAlignments) {
26306
26399
  alignmentContainer.push(a);
26307
26400
  }
@@ -26328,13 +26421,13 @@ class BamReaderNonIndexed {
26328
26421
  const alignments = [];
26329
26422
  this.header = BamUtils.decodeBamHeader(data);
26330
26423
  BamUtils.decodeBamRecords(data, this.header.size, alignments, this.header.chrNames);
26331
- this.alignmentCache = new FeatureCache$1(alignments, this.genome);
26424
+ this.alignmentCache = new FeatureCache(alignments, this.genome);
26332
26425
  }
26333
26426
 
26334
26427
  fetchAlignments(chr, bpStart, bpEnd) {
26335
26428
  const queryChr = this.header.chrAliasTable.hasOwnProperty(chr) ? this.header.chrAliasTable[chr] : chr;
26336
26429
  const features = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
26337
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
26430
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported);
26338
26431
  for (let feature of features) {
26339
26432
  alignmentContainer.push(feature);
26340
26433
  }
@@ -27325,7 +27418,9 @@ class BamReader {
27325
27418
  const chrToIndex = await this.getChrIndex();
27326
27419
  const queryChr = this.chrAliasTable.hasOwnProperty(chr) ? this.chrAliasTable[chr] : chr;
27327
27420
  const chrId = chrToIndex[queryChr];
27328
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27421
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd,
27422
+ this.config.samplingWindowSize, this.config.samplingDepth,
27423
+ this.config.pairsSupported, this.config.alleleFreqThreshold);
27329
27424
 
27330
27425
  if (chrId === undefined) {
27331
27426
  return alignmentContainer
@@ -27457,7 +27552,7 @@ class ShardedBamReader {
27457
27552
  async readAlignments(chr, start, end) {
27458
27553
 
27459
27554
  if (!this.bamReaders.hasOwnProperty(chr)) {
27460
- return new AlignmentContainer(chr, start, end, this.config)
27555
+ return new AlignmentContainer(chr, start, end)
27461
27556
  } else {
27462
27557
 
27463
27558
  let reader = this.bamReaders[chr];
@@ -27548,7 +27643,7 @@ BamWebserviceReader.prototype.readAlignments = function (chr, bpStart, bpEnd) {
27548
27643
 
27549
27644
  header.chrToIndex[queryChr];
27550
27645
 
27551
- alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.config);
27646
+ alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold);
27552
27647
 
27553
27648
  BamUtils.decodeSamRecords(sam, alignmentContainer, queryChr, bpStart, bpEnd, self.filter);
27554
27649
 
@@ -27802,7 +27897,7 @@ class HtsgetBamReader extends HtsgetReader {
27802
27897
  const ba = unbgzf(compressedData.buffer);
27803
27898
 
27804
27899
  const chrIdx = this.header.chrToIndex[chr];
27805
- const alignmentContainer = new AlignmentContainer(chr, start, end, this.config);
27900
+ const alignmentContainer = new AlignmentContainer(chr, start, end, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27806
27901
  BamUtils.decodeBamRecords(ba, this.header.size, alignmentContainer, this.header.chrNames, chrIdx, start, end);
27807
27902
  alignmentContainer.finish();
27808
27903
 
@@ -27968,7 +28063,8 @@ class CramReader {
27968
28063
  const header = await this.getHeader();
27969
28064
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
27970
28065
  const chrIdx = header.chrToIndex[queryChr];
27971
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
28066
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd,
28067
+ this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27972
28068
 
27973
28069
  if (chrIdx === undefined) {
27974
28070
  return alignmentContainer
@@ -28584,7 +28680,7 @@ Ga4ghAlignmentReader.prototype.readAlignments = function (chr, bpStart, bpEnd) {
28584
28680
  "pageSize": "10000"
28585
28681
  },
28586
28682
  decode: decodeGa4ghReads,
28587
- results: new AlignmentContainer(chr, bpStart, bpEnd, self.config)
28683
+ results: new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold)
28588
28684
  })
28589
28685
  })
28590
28686
 
@@ -28917,6 +29013,7 @@ class BamSource {
28917
29013
 
28918
29014
  this.config = config;
28919
29015
  this.genome = genome;
29016
+ this.alignmentContainer = undefined;
28920
29017
 
28921
29018
  if (isDataURL(config.url)) {
28922
29019
  if ("cram" === config.format) {
@@ -28964,11 +29061,19 @@ class BamSource {
28964
29061
  }
28965
29062
 
28966
29063
  setViewAsPairs(bool) {
28967
- this.viewAsPairs = bool;
29064
+
29065
+ if (this.viewAsPairs !== bool) {
29066
+ this.viewAsPairs = bool;
29067
+ // if (this.alignmentContainer) {
29068
+ // this.alignmentContainer.setViewAsPairs(bool);
29069
+ // }
29070
+ }
28968
29071
  }
28969
29072
 
28970
29073
  setShowSoftClips(bool) {
28971
- this.showSoftClips = bool;
29074
+ if (this.showSoftClips !== bool) {
29075
+ this.showSoftClips = bool;
29076
+ }
28972
29077
  }
28973
29078
 
28974
29079
  async getAlignments(chr, bpStart, bpEnd) {
@@ -28976,28 +29081,33 @@ class BamSource {
28976
29081
  const genome = this.genome;
28977
29082
  const showSoftClips = this.showSoftClips;
28978
29083
 
28979
- const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
28980
- let alignments = alignmentContainer.alignments;
28981
- if (!this.viewAsPairs) {
28982
- alignments = unpairAlignments([{alignments: alignments}]);
28983
- }
28984
- const hasAlignments = alignments.length > 0;
28985
- alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
28986
-
28987
- this.alignmentContainer = alignmentContainer;
29084
+ if (this.alignmentContainer && this.alignmentContainer.contains(chr, bpStart, bpEnd)) {
29085
+ return this.alignmentContainer
28988
29086
 
28989
- if (hasAlignments) {
28990
- const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
28991
- if (sequence) {
28992
- alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
28993
- alignmentContainer.sequence = sequence; // TODO -- fix this
28994
- return alignmentContainer
28995
- } else {
28996
- console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
29087
+ } else {
29088
+ const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
29089
+ let alignments = alignmentContainer.alignments;
29090
+ if (!this.viewAsPairs) {
29091
+ alignments = unpairAlignments([{alignments: alignments}]);
29092
+ }
29093
+ const hasAlignments = alignments.length > 0;
29094
+ alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
29095
+ alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
29096
+
29097
+ this.alignmentContainer = alignmentContainer;
29098
+
29099
+ if (hasAlignments) {
29100
+ const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
29101
+ if (sequence) {
29102
+ alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
29103
+ alignmentContainer.sequence = sequence; // TODO -- fix this
29104
+ return alignmentContainer
29105
+ } else {
29106
+ console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
29107
+ }
28997
29108
  }
29109
+ return alignmentContainer
28998
29110
  }
28999
- return alignmentContainer
29000
-
29001
29111
  }
29002
29112
  }
29003
29113
 
@@ -29315,7 +29425,7 @@ class TrackBase {
29315
29425
 
29316
29426
  // We use the cached features rather than method to avoid async load. If the
29317
29427
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
29318
- if (!features) features = clickState.viewport.cachedFeatures;
29428
+ if (!features) features = clickState.viewport.getCachedFeatures();
29319
29429
 
29320
29430
  if (!features || features.length === 0) {
29321
29431
  return []
@@ -29819,7 +29929,8 @@ class Locus {
29819
29929
  }
29820
29930
 
29821
29931
  overlaps(locus) {
29822
- return locus.chr === this.chr && !(locus.end < this.start || locus.start > this.end)
29932
+ return locus.chr === this.chr
29933
+ && !(locus.end < this.start || locus.start > this.end)
29823
29934
  }
29824
29935
 
29825
29936
  extend(l) {
@@ -31696,19 +31807,6 @@ function makeCircViewChromosomes(genome) {
31696
31807
  return regions
31697
31808
  }
31698
31809
 
31699
- function sendChords(chords, track, refFrame, alpha) {
31700
-
31701
- const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? track.color : getChrColor(refFrame.chr), alpha);
31702
- const trackColor = IGVColor.addAlpha(track.color || 'rgb(0,0,255)', alpha);
31703
-
31704
- // name the chord set to include locus and filtering information
31705
- const encodedName = track.name.replaceAll(' ', '%20');
31706
- const chordSetName = "all" === refFrame.chr ? encodedName :
31707
- `${encodedName} ${refFrame.chr}:${refFrame.start}-${refFrame.end}`;
31708
- track.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor});
31709
-
31710
- }
31711
-
31712
31810
 
31713
31811
  function createCircularView(el, browser) {
31714
31812
 
@@ -31718,102 +31816,42 @@ function createCircularView(el, browser) {
31718
31816
 
31719
31817
  const f1 = feature.data;
31720
31818
  const f2 = f1.mate;
31721
- addFrameForFeature(f1);
31722
- addFrameForFeature(f2);
31723
-
31724
- function addFrameForFeature(feature) {
31725
-
31726
- feature.chr = browser.genome.getChromosomeName(feature.refName);
31727
-
31728
- for (let referenceFrame of browser.currentReferenceFrames()) {
31729
- const l = Locus.fromLocusString(referenceFrame.getLocusString());
31730
- if (l.contains(feature)) {
31731
- break
31732
- } else if (l.overlaps(feature)) {
31733
- referenceFrame.extend(feature);
31734
- break
31735
- } else {
31736
- const flanking = 2000;
31737
- const center = (feature.start + feature.end) / 2;
31738
- browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
31739
- break
31740
- }
31741
- }
31742
- }
31743
- }
31744
- });
31819
+ const flanking = 2000;
31745
31820
 
31746
- return circularView
31747
- }
31821
+ const l1 = new Locus({chr: browser.genome.getChromosomeName(f1.refName), start: f1.start, end: f1.end});
31822
+ const l2 = new Locus({chr: browser.genome.getChromosomeName(f2.refName), start: f2.start, end: f2.end});
31748
31823
 
31749
- class PairedEndStats {
31824
+ let loci;
31750
31825
 
31751
- constructor(alignments, {minTLENPercentile, maxTLENPercentile}) {
31752
- this.totalCount = 0;
31753
- this.frCount = 0;
31754
- this.rfCount = 0;
31755
- this.ffCount = 0;
31756
- this.sumF = 0;
31757
- this.sumF2 = 0;
31758
- this.lp = minTLENPercentile === undefined ? 0.1 : minTLENPercentile;
31759
- this.up = maxTLENPercentile === undefined ? 99.5 : maxTLENPercentile;
31760
- this.isizes = [];
31761
- this.compute(alignments);
31762
- }
31826
+ // If there is overlap with current loci
31763
31827
 
31764
- compute(alignments) {
31828
+ loci = browser.currentLoci().map(str => Locus.fromLocusString(str));
31765
31829
 
31766
- for (let alignment of alignments) {
31767
- if (alignment.isProperPair()) {
31768
- var tlen = Math.abs(alignment.fragmentLength);
31769
- this.sumF += tlen;
31770
- this.sumF2 += tlen * tlen;
31771
- this.isizes.push(tlen);
31772
-
31773
- var po = alignment.pairOrientation;
31774
-
31775
- if (typeof po === "string" && po.length === 4) {
31776
- var tmp = '' + po.charAt(0) + po.charAt(2);
31777
- switch (tmp) {
31778
- case 'FF':
31779
- case 'RR':
31780
- this.ffCount++;
31781
- break
31782
- case "FR":
31783
- this.frCount++;
31784
- break
31785
- case"RF":
31786
- this.rfCount++;
31830
+ if (loci.some(locus => locus.contains(l1)) || loci.some(locus => locus.contains(l2))) {
31831
+ for (let l of [l1, l2]) {
31832
+ if (!loci.some(locus => {
31833
+ return locus.contains(l)
31834
+ })) {
31835
+ // add flanking
31836
+ l.start = Math.max(0, l.start - flanking);
31837
+ l.end += flanking;
31838
+ loci.push(l);
31787
31839
  }
31788
31840
  }
31789
- this.totalCount++;
31841
+ } else {
31842
+ l1.start = Math.max(0, l1.start - flanking);
31843
+ l1.end += flanking;
31844
+ l2.start = Math.max(0, l2.start - flanking);
31845
+ l2.end += flanking;
31846
+ loci = [l1, l2];
31790
31847
  }
31791
- }
31792
-
31793
- if (this.ffCount / this.totalCount > 0.9) this.orienation = "ff";
31794
- else if (this.frCount / this.totalCount > 0.9) this.orienation = "fr";
31795
- else if (this.rfCount / this.totalCount > 0.9) this.orienation = "rf";
31796
31848
 
31797
- this.minTLEN = this.lp === 0 ? 0 : percentile(this.isizes, this.lp);
31798
- this.maxTLEN = percentile(this.isizes, this.up);
31799
-
31800
- // var fMean = this.sumF / this.totalCount
31801
- // var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount))
31802
- // this.minTLEN = fMean - 3 * stdDev
31803
- // this.maxTLEN = fMean + 3 * stdDev
31804
-
31805
- }
31806
- }
31807
-
31808
- function percentile(array, p) {
31809
-
31810
- if (array.length === 0) return undefined
31811
- var k = Math.floor(array.length * (p / 100));
31812
- array.sort(function (a, b) {
31813
- return a - b
31849
+ const searchString = loci.map(l => l.getLocusString()).join(" ");
31850
+ browser.search(searchString);
31851
+ }
31814
31852
  });
31815
- return array[k]
31816
31853
 
31854
+ return circularView
31817
31855
  }
31818
31856
 
31819
31857
  /*
@@ -31880,6 +31918,8 @@ class BAMTrack extends TrackBase {
31880
31918
  this.showMismatches = false !== config.showMismatches;
31881
31919
  this.color = config.color;
31882
31920
  this.coverageColor = config.coverageColor;
31921
+ this.minFragmentLength = config.minFragmentLength; // Optional, might be undefined
31922
+ this.maxFragmentLength = config.maxFragmentLength || 1000;
31883
31923
 
31884
31924
  // The sort object can be an array in the case of multi-locus view, however if multiple sort positions
31885
31925
  // are present for a given reference frame the last one will take precedence
@@ -31907,24 +31947,12 @@ class BAMTrack extends TrackBase {
31907
31947
  return this._height
31908
31948
  }
31909
31949
 
31910
- get minTemplateLength() {
31911
- const configMinTLEN = this.config.minTLEN !== undefined ? this.config.minTLEN : this.config.minFragmentLength;
31912
- return (configMinTLEN !== undefined) ? configMinTLEN :
31913
- this._pairedEndStats ? this._pairedEndStats.minTLEN : 0
31914
- }
31915
-
31916
- get maxTemplateLength() {
31917
- const configMaxTLEN = this.config.maxTLEN !== undefined ? this.config.maxTLEN : this.config.maxFragmentLength;
31918
- return (configMaxTLEN !== undefined) ? configMaxTLEN :
31919
- this._pairedEndStats ? this._pairedEndStats.maxTLEN : 1000
31920
- }
31921
-
31922
31950
  sort(options) {
31923
31951
  options = this.assignSort(options);
31924
31952
 
31925
31953
  for (let vp of this.trackView.viewports) {
31926
31954
  if (vp.containsPosition(options.chr, options.position)) {
31927
- const alignmentContainer = vp.cachedFeatures;
31955
+ const alignmentContainer = vp.getCachedFeatures();
31928
31956
  if (alignmentContainer) {
31929
31957
  sortAlignmentRows(options, alignmentContainer);
31930
31958
  vp.repaint();
@@ -31959,13 +31987,14 @@ class BAMTrack extends TrackBase {
31959
31987
 
31960
31988
  const alignmentContainer = await this.featureSource.getAlignments(chr, bpStart, bpEnd);
31961
31989
 
31962
- if (alignmentContainer.paired && !this._pairedEndStats && !this.config.maxFragmentLength) {
31963
- const pairedEndStats = new PairedEndStats(alignmentContainer.alignments, this.config);
31964
- if (pairedEndStats.totalCount > 99) {
31965
- this._pairedEndStats = pairedEndStats;
31990
+ if (alignmentContainer.alignments && alignmentContainer.alignments.length > 99) {
31991
+ if (undefined === this.minFragmentLength) {
31992
+ this.minFragmentLength = alignmentContainer.pairedEndStats.lowerFragmentLength;
31993
+ }
31994
+ if (undefined === this.maxFragmentLength) {
31995
+ this.maxFragmentLength = alignmentContainer.pairedEndStats.upperFragmentLength;
31966
31996
  }
31967
31997
  }
31968
- alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
31969
31998
 
31970
31999
  const sort = this.sortObject;
31971
32000
  if (sort) {
@@ -32062,7 +32091,7 @@ class BAMTrack extends TrackBase {
32062
32091
  if (this.alignmentTrack.hasPairs) {
32063
32092
  colorByMenuItems.push({key: 'firstOfPairStrand', label: 'first-of-pair strand'});
32064
32093
  colorByMenuItems.push({key: 'pairOrientation', label: 'pair orientation'});
32065
- colorByMenuItems.push({key: 'tlen', label: 'insert size (TLEN)'});
32094
+ colorByMenuItems.push({key: 'fragmentLength', label: 'insert size (TLEN)'});
32066
32095
  colorByMenuItems.push({key: 'unexpectedPair', label: 'pair orientation & insert size (TLEN)'});
32067
32096
  }
32068
32097
  const tagLabel = 'tag' + (this.alignmentTrack.colorByTag ? ' (' + this.alignmentTrack.colorByTag + ')' : '');
@@ -32168,7 +32197,7 @@ class BAMTrack extends TrackBase {
32168
32197
  });
32169
32198
  }
32170
32199
 
32171
- // Add chords to JBrowse circular view, if present
32200
+ // Experimental JBrowse feature
32172
32201
  if (this.browser.circularView && true === this.browser.circularViewVisible &&
32173
32202
  (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
32174
32203
  menuItems.push('<hr/>');
@@ -32176,9 +32205,24 @@ class BAMTrack extends TrackBase {
32176
32205
  menuItems.push({
32177
32206
  label: 'Add discordant pairs to circular view',
32178
32207
  click: () => {
32208
+ const maxFragmentLength = this.maxFragmentLength;
32209
+ const inView = [];
32179
32210
  for (let viewport of this.trackView.viewports) {
32180
- this.addPairedChordsForViewport(viewport);
32211
+ for (let a of viewport.getCachedFeatures().allAlignments()) {
32212
+ const referenceFrame = viewport.referenceFrame;
32213
+ if (a.end >= referenceFrame.start
32214
+ && a.start <= referenceFrame.end
32215
+ && a.mate
32216
+ && a.mate.chr
32217
+ && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxFragmentLength)) {
32218
+ inView.push(a);
32219
+ }
32220
+ }
32181
32221
  }
32222
+ this.browser.circularViewVisible = true;
32223
+ const chords = makePairedAlignmentChords(inView);
32224
+ const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02);
32225
+ this.browser.circularView.addChords(chords, {track: this.name, color: color});
32182
32226
  }
32183
32227
  });
32184
32228
  }
@@ -32186,9 +32230,19 @@ class BAMTrack extends TrackBase {
32186
32230
  menuItems.push({
32187
32231
  label: 'Add split reads to circular view',
32188
32232
  click: () => {
32233
+ const inView = [];
32189
32234
  for (let viewport of this.trackView.viewports) {
32190
- this.addSplitChordsForViewport(viewport);
32235
+ for (let a of viewport.getCachedFeatures().allAlignments()) {
32236
+ const referenceFrame = viewport.referenceFrame;
32237
+ const sa = a.hasTag('SA');
32238
+ if (a.end >= referenceFrame.start && a.start <= referenceFrame.end && sa) {
32239
+ inView.push(a);
32240
+ }
32241
+ }
32191
32242
  }
32243
+ const chords = makeSupplementalAlignmentChords(inView);
32244
+ const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.1);
32245
+ this.browser.circularView.addChords(chords, {track: this.name, color: color});
32192
32246
  }
32193
32247
  });
32194
32248
  }
@@ -32300,7 +32354,7 @@ class BAMTrack extends TrackBase {
32300
32354
  }
32301
32355
 
32302
32356
  getCachedAlignmentContainers() {
32303
- return this.trackView.viewports.map(vp => vp.cachedFeatures)
32357
+ return this.trackView.viewports.map(vp => vp.getCachedFeatures())
32304
32358
  }
32305
32359
 
32306
32360
  get dataRange() {
@@ -32326,62 +32380,6 @@ class BAMTrack extends TrackBase {
32326
32380
  set autoscale(autoscale) {
32327
32381
  this.coverageTrack.autoscale = autoscale;
32328
32382
  }
32329
-
32330
- /**
32331
- * Add chords to the circular view for the given viewport, represented by its reference frame
32332
- * @param refFrame
32333
- */
32334
- addPairedChordsForViewport(viewport) {
32335
-
32336
- const maxTemplateLength = this.maxTemplateLength;
32337
- const inView = [];
32338
- const refFrame = viewport.referenceFrame;
32339
- for (let a of viewport.cachedFeatures.allAlignments()) {
32340
- if (a.end >= refFrame.start
32341
- && a.start <= refFrame.end
32342
- && a.mate
32343
- && a.mate.chr
32344
- && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxTemplateLength)) {
32345
- inView.push(a);
32346
- }
32347
- }
32348
- const chords = makePairedAlignmentChords(inView);
32349
- sendChords(chords, this, refFrame, 0.02);
32350
-
32351
- // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
32352
- // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
32353
- //
32354
- // // name the chord set to include track name and locus
32355
- // const encodedName = this.name.replaceAll(' ', '%20')
32356
- // const chordSetName = "all" === refFrame.chr ? encodedName :
32357
- // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
32358
- // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
32359
- }
32360
-
32361
- addSplitChordsForViewport(viewport) {
32362
-
32363
- const inView = [];
32364
- const refFrame = viewport.referenceFrame;
32365
- for (let a of viewport.cachedFeatures.allAlignments()) {
32366
-
32367
- const sa = a.hasTag('SA');
32368
- if (a.end >= refFrame.start && a.start <= refFrame.end && sa) {
32369
- inView.push(a);
32370
- }
32371
- }
32372
-
32373
- const chords = makeSupplementalAlignmentChords(inView);
32374
- sendChords(chords, this, refFrame, 0.02);
32375
-
32376
- // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
32377
- // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
32378
- //
32379
- // // name the chord set to include track name and locus
32380
- // const encodedName = this.name.replaceAll(' ', '%20')
32381
- // const chordSetName = "all" === refFrame.chr ? encodedName :
32382
- // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
32383
- // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
32384
- }
32385
32383
  }
32386
32384
 
32387
32385
 
@@ -32502,7 +32500,7 @@ class CoverageTrack {
32502
32500
 
32503
32501
  getClickedObject(clickState) {
32504
32502
 
32505
- let features = clickState.viewport.cachedFeatures;
32503
+ let features = clickState.viewport.getCachedFeatures();
32506
32504
  if (!features || features.length === 0) return
32507
32505
 
32508
32506
  const genomicLocation = Math.floor(clickState.genomicLocation);
@@ -32578,8 +32576,8 @@ class AlignmentTrack {
32578
32576
  this.skippedColor = config.skippedColor || "rgb(150, 170, 170)";
32579
32577
  this.pairConnectorColor = config.pairConnectorColor;
32580
32578
 
32581
- this.smallTLENColor = config.smallTLENColor || config.smallFragmentLengthColor || "rgb(0, 0, 150)";
32582
- this.largeTLENColor = config.largeTLENColor || config.largeFragmentLengthColor || "rgb(200, 0, 0)";
32579
+ this.smallFragmentLengthColor = config.smallFragmentLengthColor || "rgb(0, 0, 150)";
32580
+ this.largeFragmentLengthColor = config.largeFragmentLengthColor || "rgb(200, 0, 0)";
32583
32581
 
32584
32582
  this.pairOrientation = config.pairOrienation || 'fr';
32585
32583
  this.pairColors = {};
@@ -32587,7 +32585,7 @@ class AlignmentTrack {
32587
32585
  this.pairColors["RR"] = config.rrColor || "rgb(20, 50, 200)";
32588
32586
  this.pairColors["LL"] = config.llColor || "rgb(0, 150, 150)";
32589
32587
 
32590
- this.colorBy = config.colorBy || "unexpectedPair";
32588
+ this.colorBy = config.colorBy || "pairOrientation";
32591
32589
  this.colorByTag = config.colorByTag ? config.colorByTag.toUpperCase() : undefined;
32592
32590
  this.bamColorTag = config.bamColorTag === undefined ? "YC" : config.bamColorTag;
32593
32591
 
@@ -32979,7 +32977,7 @@ class AlignmentTrack {
32979
32977
  direction: direction
32980
32978
  };
32981
32979
  this.parent.sortObject = newSortObject;
32982
- sortAlignmentRows(newSortObject, viewport.cachedFeatures);
32980
+ sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
32983
32981
  viewport.repaint();
32984
32982
  };
32985
32983
  list.push('<b>Sort by...</b>');
@@ -33009,7 +33007,7 @@ class AlignmentTrack {
33009
33007
  };
33010
33008
  this.sortByTag = tag;
33011
33009
  this.parent.sortObject = newSortObject;
33012
- sortAlignmentRows(newSortObject, viewport.cachedFeatures);
33010
+ sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
33013
33011
  viewport.repaint();
33014
33012
  }
33015
33013
  }
@@ -33036,11 +33034,7 @@ class AlignmentTrack {
33036
33034
  const referenceFrame = clickState.viewport.referenceFrame;
33037
33035
  if (this.browser.genome.getChromosome(clickedAlignment.mate.chr)) {
33038
33036
  this.highlightedAlignmentReadNamed = clickedAlignment.readName;
33039
- //this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame)
33040
- const bpWidth = referenceFrame.end - referenceFrame.start;
33041
- const frameStart = clickedAlignment.mate.position - bpWidth / 2;
33042
- const frameEnd = clickedAlignment.mate.position + bpWidth / 2;
33043
- this.browser.addMultiLocusPanel(clickedAlignment.mate.chr, frameStart, frameEnd, referenceFrame);
33037
+ this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame);
33044
33038
  } else {
33045
33039
  Alert.presentAlert(`Reference does not contain chromosome: ${clickedAlignment.mate.chr}`);
33046
33040
  }
@@ -33088,7 +33082,19 @@ class AlignmentTrack {
33088
33082
  list.push({
33089
33083
  label: 'Add discordant pairs to circular view',
33090
33084
  click: () => {
33091
- this.parent.addPairedChordsForViewport(viewport);
33085
+ const maxFragmentLength = this.parent.maxFragmentLength;
33086
+ const {referenceFrame} = viewport;
33087
+ const inView = viewport.getCachedFeatures().allAlignments().filter(a => {
33088
+ return a.end >= referenceFrame.start
33089
+ && a.start <= referenceFrame.end
33090
+ && a.mate
33091
+ && a.mate.chr
33092
+ && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxFragmentLength)
33093
+ });
33094
+ this.browser.circularViewVisible = true;
33095
+ const chords = makePairedAlignmentChords(inView);
33096
+ const color = IGVColor.addAlpha(this.parent.color || 'rgb(0,0,255)', 0.02);
33097
+ this.browser.circularView.addChords(chords, {track: this.parent.name, color: color});
33092
33098
  }
33093
33099
  });
33094
33100
  }
@@ -33096,7 +33102,17 @@ class AlignmentTrack {
33096
33102
  list.push({
33097
33103
  label: 'Add split reads to circular view',
33098
33104
  click: () => {
33099
- this.parent.addSplitChordsForViewport(viewport);
33105
+ const inView = [];
33106
+ for (let a of viewport.getCachedFeatures().allAlignments()) {
33107
+ const referenceFrame = viewport.referenceFrame;
33108
+ const sa = a.hasTag('SA');
33109
+ if (a.end >= referenceFrame.start && a.start <= referenceFrame.end && sa) {
33110
+ inView.push(a);
33111
+ }
33112
+ }
33113
+ const chords = makeSupplementalAlignmentChords(inView);
33114
+ const color = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.1);
33115
+ this.browser.circularView.addChords(chords, {track: this.name, color: color});
33100
33116
  }
33101
33117
  });
33102
33118
  }
@@ -33115,7 +33131,7 @@ class AlignmentTrack {
33115
33131
 
33116
33132
  const showSoftClips = this.parent.showSoftClips;
33117
33133
 
33118
- let features = viewport.cachedFeatures;
33134
+ let features = viewport.getCachedFeatures();
33119
33135
  if (!features || features.length === 0) return
33120
33136
 
33121
33137
  let packedAlignmentRows = features.packedAlignmentRows;
@@ -33206,17 +33222,14 @@ class AlignmentTrack {
33206
33222
  break
33207
33223
  }
33208
33224
 
33209
- case "tlen":
33210
33225
  case "fragmentLength":
33211
33226
 
33212
- if (alignment.mate && alignment.isMateMapped()) {
33213
- if (alignment.mate.chr !== alignment.chr) {
33214
- color = getChrColor(alignment.mate.chr);
33215
- } else if (this.parent.minTemplateLength && Math.abs(alignment.fragmentLength) < this.parent.minTemplateLength) {
33216
- color = this.smallTLENColor;
33217
- } else if (this.parent.maxTemplateLength && Math.abs(alignment.fragmentLength) > this.parent.maxTemplateLength) {
33218
- color = this.largeTLENColor;
33219
- }
33227
+ if (alignment.mate && alignment.isMateMapped() && alignment.mate.chr !== alignment.chr) {
33228
+ color = getChrColor(alignment.mate.chr);
33229
+ } else if (this.parent.minFragmentLength && Math.abs(alignment.fragmentLength) < this.parent.minFragmentLength) {
33230
+ color = this.smallFragmentLengthColor;
33231
+ } else if (this.parent.maxFragmentLength && Math.abs(alignment.fragmentLength) > this.parent.maxFragmentLength) {
33232
+ color = this.largeFragmentLengthColor;
33220
33233
  }
33221
33234
  break
33222
33235
 
@@ -33258,6 +33271,11 @@ function sortAlignmentRows(options, alignmentContainer) {
33258
33271
  return true === direction ? i : -i
33259
33272
  });
33260
33273
 
33274
+ // For debugging
33275
+ // for(let r of alignmentContainer.packedAlignmentRows) {
33276
+ // console.log(r.score);
33277
+ // }
33278
+
33261
33279
  }
33262
33280
 
33263
33281
  function shadedBaseColor(qual, baseColor) {
@@ -33417,7 +33435,7 @@ class RulerViewport extends TrackViewport {
33417
33435
 
33418
33436
  this.$rulerLabel.click(async () => {
33419
33437
 
33420
- await this.browser.gotoMultilocusPanel(this.referenceFrame);
33438
+ await this.browser.selectMultiLocusPanel(this.referenceFrame);
33421
33439
 
33422
33440
  // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
33423
33441
  // for (let referenceFrame of removals) {
@@ -33755,7 +33773,7 @@ class IdeogramViewport extends TrackViewport {
33755
33773
  referenceFrame.end = ee;
33756
33774
  referenceFrame.bpPerPixel = (ee - ss) / width;
33757
33775
 
33758
- this.browser.updateViews(true);
33776
+ this.browser.updateViews(referenceFrame, this.browser.trackViews, true);
33759
33777
 
33760
33778
  }
33761
33779
 
@@ -34353,11 +34371,15 @@ const colorPickerExclusionTypes = new Set(['ruler', 'sequence', 'ideogram']);
34353
34371
  class TrackView {
34354
34372
 
34355
34373
  constructor(browser, columnContainer, track) {
34374
+
34356
34375
  this.namespace = `trackview-${guid$2()}`;
34376
+
34357
34377
  this.browser = browser;
34358
34378
  this.track = track;
34359
34379
  track.trackView = this;
34380
+
34360
34381
  this.addDOMToColumnContainer(browser, columnContainer, browser.referenceFrameList);
34382
+
34361
34383
  }
34362
34384
 
34363
34385
  /**
@@ -34520,14 +34542,19 @@ class TrackView {
34520
34542
  if (false === colorPickerExclusionTypes.has(this.track.type)) {
34521
34543
 
34522
34544
  const trackColors = [];
34545
+
34523
34546
  const color = this.track.color || this.track.defaultColor;
34547
+
34524
34548
  if (isString$3(color)) {
34525
34549
  trackColors.push(color);
34526
34550
  }
34551
+
34527
34552
  if (this.track.altColor && isString$3(this.track.altColor)) {
34528
34553
  trackColors.push(this.track.altColor);
34529
34554
  }
34555
+
34530
34556
  const defaultColors = trackColors.map(c => c.startsWith("#") ? c : c.startsWith("rgb(") ? IGVColor.rgbToHex(c) : IGVColor.colorNameToHex(c));
34557
+
34531
34558
  const colorHandlers =
34532
34559
  {
34533
34560
  color: color => {
@@ -34540,6 +34567,7 @@ class TrackView {
34540
34567
  }
34541
34568
 
34542
34569
  };
34570
+
34543
34571
  this.browser.genericColorPicker.configure(defaultColors, colorHandlers);
34544
34572
  this.browser.genericColorPicker.setActiveColorHandler(key);
34545
34573
  this.browser.genericColorPicker.show();
@@ -34553,6 +34581,7 @@ class TrackView {
34553
34581
  if (this.track.minHeight) {
34554
34582
  newHeight = Math.max(this.track.minHeight, newHeight);
34555
34583
  }
34584
+
34556
34585
  if (this.track.maxHeight) {
34557
34586
  newHeight = Math.min(this.track.maxHeight, newHeight);
34558
34587
  }
@@ -34647,9 +34676,7 @@ class TrackView {
34647
34676
  repaintViews() {
34648
34677
 
34649
34678
  for (let viewport of this.viewports) {
34650
- if(viewport.isVisible()) {
34651
- viewport.repaint();
34652
- }
34679
+ viewport.repaint();
34653
34680
  }
34654
34681
 
34655
34682
  if (typeof this.track.paintAxis === 'function') {
@@ -34676,9 +34703,6 @@ class TrackView {
34676
34703
 
34677
34704
  /**
34678
34705
  * Update viewports to reflect current genomic state, possibly loading additional data.
34679
- *
34680
- * @param force - if true, force a repaint even if no new data is loaded
34681
- * @returns {Promise<void>}
34682
34706
  */
34683
34707
  async updateViews(force) {
34684
34708
 
@@ -34696,24 +34720,24 @@ class TrackView {
34696
34720
  }
34697
34721
 
34698
34722
  // rpv: viewports whose image (canvas) does not fully cover current genomic range
34699
- const reloadableViewports = force ? visibleViewports : this.viewportsToReload();
34723
+ const reloadableViewports = this.viewportsToReload(force);
34700
34724
 
34701
34725
  // Trigger viewport to load features needed to cover current genomic range
34702
34726
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
34703
34727
  for (let viewport of reloadableViewports) {
34704
34728
  await viewport.loadFeatures();
34705
34729
  }
34706
-
34730
+
34707
34731
  if (this.disposed) return // Track was removed during load
34708
34732
 
34709
- // Special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
34733
+ // Very special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
34710
34734
  // section depends on data from all the views. We only need to adjust this however if any data was loaded
34711
34735
  // (i.e. reloadableViewports.length > 0)
34712
34736
  if (this.track && typeof this.track.variantRowCount === 'function' && reloadableViewports.length > 0) {
34713
34737
  let maxRow = 0;
34714
34738
  for (let viewport of this.viewports) {
34715
- if (viewport.featureCache && viewport.featureCache.features) {
34716
- maxRow = Math.max(maxRow, viewport.featureCache.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
34739
+ if (viewport.tile && viewport.tile.features) {
34740
+ maxRow = Math.max(maxRow, viewport.tile.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
34717
34741
  }
34718
34742
  }
34719
34743
  const current = this.track.nVariantRows;
@@ -34725,18 +34749,19 @@ class TrackView {
34725
34749
  }
34726
34750
  }
34727
34751
 
34752
+
34728
34753
  if (this.track.autoscale) {
34729
34754
  let allFeatures = [];
34730
34755
  for (let visibleViewport of visibleViewports) {
34731
34756
  const referenceFrame = visibleViewport.referenceFrame;
34732
34757
  const start = referenceFrame.start;
34733
34758
  const end = start + referenceFrame.toBP($$1(visibleViewport.contentDiv).width());
34734
- if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
34735
- if (typeof visibleViewport.featureCache.features.getMax === 'function') {
34736
- const max = visibleViewport.featureCache.features.getMax(start, end);
34759
+ if (visibleViewport.tile && visibleViewport.tile.features) {
34760
+ if (typeof visibleViewport.tile.features.getMax === 'function') {
34761
+ const max = visibleViewport.tile.features.getMax(start, end);
34737
34762
  allFeatures.push({value: max});
34738
34763
  } else {
34739
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end));
34764
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.tile.features, start, end));
34740
34765
  }
34741
34766
  }
34742
34767
  }
@@ -34747,18 +34772,14 @@ class TrackView {
34747
34772
  }
34748
34773
  }
34749
34774
 
34750
- // Must repaint all viewports if autoscaling. Always repaint ruler.
34751
- if (this.track.autoscale || this.track.autoscaleGroup || this.track.type === 'ruler' || force) {
34752
- for (let vp of visibleViewports) {
34753
- vp.repaint();
34775
+ // Must repaint all viewports if autoscaling
34776
+ if (!isDragging && (this.track.autoscale || this.track.autoscaleGroup)) {
34777
+ for (let visibleViewport of visibleViewports) {
34778
+ visibleViewport.repaint();
34754
34779
  }
34755
34780
  } else {
34756
- const reloadedViewports = new Set(reloadableViewports);
34757
- for (let vp of visibleViewports) {
34758
- const invalid = vp.canvas && vp.canvas._data && vp.canvas._data.invalidate;
34759
- if (invalid || reloadedViewports.has(vp)) {
34760
- vp.repaint();
34761
- }
34781
+ for (let vp of reloadableViewports) {
34782
+ vp.repaint();
34762
34783
  }
34763
34784
  }
34764
34785
 
@@ -34789,14 +34810,14 @@ class TrackView {
34789
34810
  /**
34790
34811
  * Return a promise to get all in-view features. Used for group autoscaling.
34791
34812
  */
34792
- async getInViewFeatures() {
34813
+ async getInViewFeatures(force) {
34793
34814
 
34794
34815
  if (!(this.browser && this.browser.referenceFrameList)) {
34795
34816
  return []
34796
34817
  }
34797
34818
 
34798
34819
  // List of viewports that need reloading
34799
- const rpV = this.viewportsToReload();
34820
+ const rpV = this.viewportsToReload(force);
34800
34821
  const promises = rpV.map(function (vp) {
34801
34822
  return vp.loadFeatures()
34802
34823
  });
@@ -34805,16 +34826,16 @@ class TrackView {
34805
34826
 
34806
34827
  let allFeatures = [];
34807
34828
  for (let vp of this.viewports) {
34808
- if (vp.featureCache && vp.featureCache.features) {
34829
+ if (vp.tile && vp.tile.features) {
34809
34830
  const referenceFrame = vp.referenceFrame;
34810
34831
  const start = referenceFrame.start;
34811
34832
  const end = start + referenceFrame.toBP($$1(vp.contentDiv).width());
34812
34833
 
34813
- if (typeof vp.featureCache.features.getMax === 'function') {
34814
- const max = vp.featureCache.features.getMax(start, end);
34834
+ if (typeof vp.tile.features.getMax === 'function') {
34835
+ const max = vp.tile.features.getMax(start, end);
34815
34836
  allFeatures.push({value: max});
34816
34837
  } else {
34817
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.featureCache.features, start, end));
34838
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.tile.features, start, end));
34818
34839
  }
34819
34840
  }
34820
34841
  }
@@ -34870,7 +34891,7 @@ class TrackView {
34870
34891
  const start = referenceFrame.start;
34871
34892
  const end = start + referenceFrame.toBP($$1(viewport.contentDiv).width());
34872
34893
  const bpPerPixel = referenceFrame.bpPerPixel;
34873
- return (!viewport.featureCache || !viewport.featureCache.containsRange(chr, start, end, bpPerPixel))
34894
+ return force || (!viewport.tile || viewport.tile.invalidate || !viewport.tile.containsRange(chr, start, end, bpPerPixel))
34874
34895
  }
34875
34896
  });
34876
34897
  return viewports
@@ -39653,7 +39674,7 @@ class TextFeatureSource {
39653
39674
  mapProperties(features, config.mappings);
39654
39675
  }
39655
39676
  this.queryable = false;
39656
- this.featureCache = new FeatureCache$1(features, genome);
39677
+ this.featureCache = new FeatureCache(features, genome);
39657
39678
  } else if (config.reader) {
39658
39679
  // Explicit reader implementation
39659
39680
  this.reader = config.reader;
@@ -39820,14 +39841,14 @@ class TextFeatureSource {
39820
39841
  }
39821
39842
 
39822
39843
  // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
39823
- this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
39844
+ this.featureCache = new FeatureCache(features, this.genome, genomicInterval);
39824
39845
 
39825
39846
  // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
39826
39847
  if (this.config.searchable || this.config.searchableFields) {
39827
39848
  this.addFeaturesToDB(features);
39828
39849
  }
39829
39850
  } else {
39830
- this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
39851
+ this.featureCache = new FeatureCache([], genomicInterval); // Empty cache
39831
39852
  }
39832
39853
  }
39833
39854
 
@@ -41824,7 +41845,7 @@ function renderFeature(feature, bpStart, xScale, pixelHeight, ctx, options) {
41824
41845
  // single-exon transcript
41825
41846
  const xLeft = Math.max(0, coord.px);
41826
41847
  const xRight = Math.min(pixelWidth, coord.px1);
41827
- const width = Math.max(coord.pw, xRight - xLeft);
41848
+ const width = xRight - xLeft;
41828
41849
  ctx.fillRect(xLeft, py, width, h);
41829
41850
 
41830
41851
  // Arrows
@@ -42630,11 +42651,13 @@ class WigTrack extends TrackBase {
42630
42651
  this.paintAxis = paintAxis;
42631
42652
 
42632
42653
  const format = config.format ? config.format.toLowerCase() : config.format;
42633
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
42634
- this.logScale = config.logScale ? config.logScale : false;
42635
42654
  if ("bigwig" === format) {
42655
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
42656
+ this.logScale = config.logScale ? config.logScale : false;
42636
42657
  this.featureSource = new BWSource(config, this.browser.genome);
42637
42658
  } else if ("tdf" === format) {
42659
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
42660
+ this.logScale = config.logScale ? config.logScale : false;
42638
42661
  this.featureSource = new TDFSource(config, this.browser.genome);
42639
42662
  } else {
42640
42663
  this.featureSource = FeatureSource(config, this.browser.genome);
@@ -42686,7 +42709,7 @@ class WigTrack extends TrackBase {
42686
42709
  let items = [];
42687
42710
  if (this.flipAxis !== undefined) {
42688
42711
  items.push({
42689
- label: "Flip y-axis",
42712
+ label:"Flip y-axis",
42690
42713
  click: () => {
42691
42714
  this.flipAxis = !this.flipAxis;
42692
42715
  this.trackView.repaintViews();
@@ -43414,7 +43437,7 @@ class SegTrack extends TrackBase {
43414
43437
 
43415
43438
  const sortHandler = (sort) => {
43416
43439
  const viewport = clickState.viewport;
43417
- const features = viewport.cachedFeatures;
43440
+ const features = viewport.getCachedFeatures();
43418
43441
  this.sortSamples(sort.chr, sort.start, sort.end, sort.direction, features);
43419
43442
  };
43420
43443
 
@@ -43532,16 +43555,10 @@ const MUT_COLORS = {
43532
43555
  * THE SOFTWARE.
43533
43556
  */
43534
43557
 
43535
- /**
43536
- * Represents 2 or more wig tracks overlaid on a common viewport.
43537
- */
43538
43558
  class MergedTrack extends TrackBase {
43539
43559
 
43540
43560
  constructor(config, browser) {
43541
43561
  super(config, browser);
43542
- this.type = "merged";
43543
- this.featureType = 'numeric';
43544
- this.paintAxis = paintAxis;
43545
43562
  }
43546
43563
 
43547
43564
  init(config) {
@@ -43552,6 +43569,21 @@ class MergedTrack extends TrackBase {
43552
43569
  super.init(config);
43553
43570
  }
43554
43571
 
43572
+ get height() {
43573
+ return this._height
43574
+ }
43575
+
43576
+ set height(h) {
43577
+ this._height = h;
43578
+ if (this.tracks) {
43579
+ for (let t of this.tracks) {
43580
+ t.height = h;
43581
+ t.config.height = h;
43582
+ }
43583
+ }
43584
+ }
43585
+
43586
+
43555
43587
  async postInit() {
43556
43588
 
43557
43589
  this.tracks = [];
@@ -43571,55 +43603,11 @@ class MergedTrack extends TrackBase {
43571
43603
  }
43572
43604
  }
43573
43605
 
43574
- this.flipAxis = this.config.flipAxis ? this.config.flipAxis : false;
43575
- this.logScale = this.config.logScale ? this.config.logScale : false;
43576
- this.autoscale = this.config.autoscale || this.config.max === undefined;
43577
- if (!this.autoscale) {
43578
- this.dataRange = {
43579
- min: this.config.min || 0,
43580
- max: this.config.max
43581
- };
43582
- }
43583
- for (let t of this.tracks) {
43584
- t.autoscale = false;
43585
- t.dataRange = this.dataRange;
43586
- }
43587
-
43588
- this.height = this.config.height || 50;
43606
+ this.height = this.config.height || 100;
43589
43607
 
43590
43608
  return Promise.all(p)
43591
43609
  }
43592
43610
 
43593
- get height() {
43594
- return this._height
43595
- }
43596
-
43597
- set height(h) {
43598
- this._height = h;
43599
- if (this.tracks) {
43600
- for (let t of this.tracks) {
43601
- t.height = h;
43602
- t.config.height = h;
43603
- }
43604
- }
43605
- }
43606
-
43607
- menuItemList() {
43608
- let items = [];
43609
- if (this.flipAxis !== undefined) {
43610
- items.push({
43611
- label: "Flip y-axis",
43612
- click: () => {
43613
- this.flipAxis = !this.flipAxis;
43614
- this.trackView.repaintViews();
43615
- }
43616
- });
43617
- }
43618
-
43619
- items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
43620
-
43621
- return items
43622
- }
43623
43611
 
43624
43612
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
43625
43613
 
@@ -43629,26 +43617,44 @@ class MergedTrack extends TrackBase {
43629
43617
 
43630
43618
  draw(options) {
43631
43619
 
43632
- const mergedFeatures = options.features; // Array of feature arrays, 1 for each track
43620
+ var i, len, mergedFeatures, trackOptions, dataRange;
43633
43621
 
43634
- if (this.autoscale) {
43635
- this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
43636
- }
43622
+ mergedFeatures = options.features; // Array of feature arrays, 1 for each track
43623
+
43624
+ dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
43637
43625
 
43638
- for (let i = 0, len = this.tracks.length; i < len; i++) {
43639
- const trackOptions = Object.assign({}, options);
43626
+ //IGVGraphics.fillRect(options.context, 0, options.pixelTop, options.pixelWidth, options.pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
43627
+
43628
+ for (i = 0, len = this.tracks.length; i < len; i++) {
43629
+
43630
+ trackOptions = Object.assign({}, options);
43640
43631
  trackOptions.features = mergedFeatures[i];
43641
- this.tracks[i].dataRange = this.dataRange;
43642
- this.tracks[i].flipAxis = this.flipAxis;
43643
- this.tracks[i].logScale = this.logScale;
43644
- this.tracks[i].graphType = this.graphType;
43632
+ this.tracks[i].dataRange = dataRange;
43645
43633
  this.tracks[i].draw(trackOptions);
43646
43634
  }
43635
+
43636
+ }
43637
+
43638
+ paintAxis(ctx, pixelWidth, pixelHeight) {
43639
+
43640
+ var i, len, autoscale, track;
43641
+
43642
+ autoscale = true; // Hardcoded for now
43643
+
43644
+ for (i = 0, len = this.tracks.length; i < len; i++) {
43645
+
43646
+ track = this.tracks[i];
43647
+
43648
+ if (typeof track.paintAxis === 'function') {
43649
+ track.paintAxis(ctx, pixelWidth, pixelHeight);
43650
+ if (autoscale) break
43651
+ }
43652
+ }
43647
43653
  }
43648
43654
 
43649
43655
  popupData(clickState, features) {
43650
43656
 
43651
- const featuresArray = features || clickState.viewport.cachedFeatures;
43657
+ const featuresArray = features || clickState.viewport.getCachedFeatures();
43652
43658
 
43653
43659
  if (featuresArray && featuresArray.length === this.tracks.length) {
43654
43660
  // Array of feature arrays, 1 for each track
@@ -43666,22 +43672,41 @@ class MergedTrack extends TrackBase {
43666
43672
 
43667
43673
 
43668
43674
  supportsWholeGenome() {
43669
- return this.tracks.every(track => track.supportsWholeGenome())
43675
+ const b = this.tracks.every(track => track.supportsWholeGenome());
43676
+ return b
43670
43677
  }
43671
43678
  }
43672
43679
 
43673
43680
  function autoscale(chr, featureArrays) {
43674
43681
 
43675
- let min = 0;
43676
- let max = -Number.MAX_VALUE;
43677
- for(let features of featureArrays) {
43678
- for(let f of features) {
43682
+
43683
+ var min = 0,
43684
+ max = -Number.MAX_VALUE;
43685
+
43686
+ // if (chr === 'all') {
43687
+ // allValues = [];
43688
+ // featureArrays.forEach(function (features) {
43689
+ // features.forEach(function (f) {
43690
+ // if (!Number.isNaN(f.value)) {
43691
+ // allValues.push(f.value);
43692
+ // }
43693
+ // });
43694
+ // });
43695
+ //
43696
+ // min = Math.min(0, IGVMath.percentile(allValues, .1));
43697
+ // max = IGVMath.percentile(allValues, 99.9);
43698
+ //
43699
+ // }
43700
+ // else {
43701
+ featureArrays.forEach(function (features, i) {
43702
+ features.forEach(function (f) {
43679
43703
  if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
43680
43704
  min = Math.min(min, f.value);
43681
43705
  max = Math.max(max, f.value);
43682
43706
  }
43683
- }
43684
- }
43707
+ });
43708
+ });
43709
+ // }
43685
43710
  return {min: min, max: max}
43686
43711
  }
43687
43712
 
@@ -44223,23 +44248,21 @@ class InteractionTrack extends TrackBase {
44223
44248
 
44224
44249
  // inView features are simply features that have been drawn, i.e. have a drawState
44225
44250
  const inView = cachedFeatures.filter(f => f.drawState);
44226
- if(inView.length === 0) return;
44251
+ if(inView.length === 0) erturn;
44227
44252
 
44228
44253
  this.browser.circularViewVisible = true;
44229
44254
  const chords = makeBedPEChords(inView);
44230
- sendChords(chords, this, refFrame, 0.5);
44231
- //
44232
- //
44233
- // // for filtered set, distinguishing the chromosomes is more critical than tracks
44234
- // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5)
44235
- // const trackColor = IGVColor.addAlpha(this.color, 0.5)
44236
- //
44237
- // // name the chord set to include locus and filtering information
44238
- // const encodedName = this.name.replaceAll(' ', '%20')
44239
- // const chordSetName = "all" === refFrame.chr ?
44240
- // encodedName :
44241
- // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`
44242
- // this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor})
44255
+
44256
+ // for filtered set, distinguishing the chromosomes is more critical than tracks
44257
+ const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5);
44258
+ const trackColor = IGVColor.addAlpha(this.color, 0.5);
44259
+
44260
+ // name the chord set to include filtering information
44261
+ const encodedName = this.name.replaceAll(' ', '%20');
44262
+ const chordSetName = "all" === refFrame.chr ?
44263
+ encodedName :
44264
+ `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`;
44265
+ this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor});
44243
44266
  }
44244
44267
 
44245
44268
  doAutoscale(features) {
@@ -44304,7 +44327,7 @@ class InteractionTrack extends TrackBase {
44304
44327
 
44305
44328
  // We use the cached features rather than method to avoid async load. If the
44306
44329
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
44307
- const featureList = features || clickState.viewport.cachedFeatures;
44330
+ const featureList = features || clickState.viewport.getCachedFeatures();
44308
44331
  const candidates = [];
44309
44332
  if (featureList) {
44310
44333
  const proportional = (this.arcType === "proportional" || this.arcType === "inView" || this.arcType === "partialInView");
@@ -44830,7 +44853,7 @@ class VariantTrack extends TrackBase {
44830
44853
  }
44831
44854
 
44832
44855
  } else if (this._color) {
44833
- variantColor = this.color;
44856
+ variantColor = (typeof this._color === "function") ? this._color(v) : this._color;
44834
44857
  } else if ("NONVARIANT" === v.type) {
44835
44858
  variantColor = this.nonRefColor;
44836
44859
  } else if ("MIXED" === v.type) {
@@ -44841,10 +44864,6 @@ class VariantTrack extends TrackBase {
44841
44864
  return variantColor
44842
44865
  }
44843
44866
 
44844
- get color() {
44845
- return this._color ? ((typeof this._color === "function") ? this._color(v) : this._color) : this.defaultColor
44846
- }
44847
-
44848
44867
  clickedFeatures(clickState, features) {
44849
44868
 
44850
44869
  let featureList = super.clickedFeatures(clickState, features);
@@ -45077,9 +45096,19 @@ class VariantTrack extends TrackBase {
45077
45096
  menuItems.push({
45078
45097
  label: 'Add SVs to circular view',
45079
45098
  click: () => {
45099
+ const inView = [];
45080
45100
  for (let viewport of this.trackView.viewports) {
45081
- this.sendChordsForViewport(viewport);
45101
+ const refFrame = viewport.referenceFrame;
45102
+ for (let f of viewport.getCachedFeatures()) {
45103
+ if (f.end >= refFrame.start && f.start <= refFrame.end) {
45104
+ inView.push(f);
45105
+ }
45106
+ }
45082
45107
  }
45108
+
45109
+ const chords = makeVCFChords(inView);
45110
+ const color = IGVColor.addAlpha(this._color || this.defaultColor, 0.5);
45111
+ this.browser.circularView.addChords(chords, {track: this.name, color: color});
45083
45112
  }
45084
45113
  });
45085
45114
  }
@@ -45098,7 +45127,13 @@ class VariantTrack extends TrackBase {
45098
45127
  list.push({
45099
45128
  label: 'Add SVs to Circular View',
45100
45129
  click: () => {
45101
- this.sendChordsForViewport(viewport);
45130
+ const refFrame = viewport.referenceFrame;
45131
+ const inView = "all" === refFrame.chr ?
45132
+ this.featureSource.getAllFeatures() :
45133
+ this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
45134
+ const chords = makeVCFChords(inView);
45135
+ const color = IGVColor.addAlpha(this._color || this.defaultColor, 0.5);
45136
+ this.browser.circularView.addChords(chords, {track: this.name, color: color});
45102
45137
  }
45103
45138
  });
45104
45139
 
@@ -45108,15 +45143,6 @@ class VariantTrack extends TrackBase {
45108
45143
  }
45109
45144
 
45110
45145
 
45111
- sendChordsForViewport(viewport) {
45112
- const refFrame = viewport.referenceFrame;
45113
- const inView = "all" === refFrame.chr ?
45114
- this.featureSource.getAllFeatures() :
45115
- this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
45116
- const chords = makeVCFChords(inView);
45117
- sendChords(chords, this, refFrame, 0.5);
45118
- }
45119
-
45120
45146
  /**
45121
45147
  * Create a "color by" checkbox menu item, optionally initially checked
45122
45148
  * @param menuItem
@@ -45413,7 +45439,7 @@ class EqtlTrack extends TrackBase {
45413
45439
  */
45414
45440
  popupData(clickState) {
45415
45441
 
45416
- let features = clickState.viewport.cachedFeatures;
45442
+ let features = clickState.viewport.getCachedFeatures();
45417
45443
  if (!features || features.length === 0) return []
45418
45444
 
45419
45445
  const tolerance = 3;
@@ -45743,7 +45769,7 @@ class GWASTrack extends TrackBase {
45743
45769
 
45744
45770
  let data = [];
45745
45771
  const track = clickState.viewport.trackView.track;
45746
- const features = clickState.viewport.cachedFeatures;
45772
+ const features = clickState.viewport.getCachedFeatures();
45747
45773
 
45748
45774
  if (features) {
45749
45775
  let count = 0;
@@ -46426,7 +46452,7 @@ class RNAFeatureSource {
46426
46452
 
46427
46453
  const data = await igvxhr.loadString(this.config.url, options);
46428
46454
 
46429
- this.featureCache = new FeatureCache$1(parseBP(data), genome);
46455
+ this.featureCache = new FeatureCache(parseBP(data), genome);
46430
46456
 
46431
46457
  return this.featureCache.queryFeatures(chr, start, end)
46432
46458
 
@@ -47708,15 +47734,6 @@ class ReferenceFrame {
47708
47734
  this.id = guid$2();
47709
47735
  }
47710
47736
 
47711
- extend(locus) {
47712
- const newStart = Math.min(locus.start, this.start);
47713
- const newEnd = Math.max(locus.end, this.end);
47714
- const ratio = (newEnd - newStart) / (this.end - this.start);
47715
- this.start = newStart;
47716
- this.end = newEnd;
47717
- this.bpPerPixel *= ratio;
47718
- }
47719
-
47720
47737
  calculateEnd(pixels) {
47721
47738
  return this.start + this.bpPerPixel * pixels
47722
47739
  }
@@ -47803,7 +47820,7 @@ class ReferenceFrame {
47803
47820
 
47804
47821
  const viewChanged = start !== this.start || bpPerPixel !== this.bpPerPixel;
47805
47822
  if (viewChanged) {
47806
- await browser.updateViews(true);
47823
+ await browser.updateViews(this);
47807
47824
  }
47808
47825
 
47809
47826
  }
@@ -49605,8 +49622,8 @@ class Browser {
49605
49622
  this.svgSaveControl = new SVGSaveControl($toggle_button_container.get(0), this);
49606
49623
  }
49607
49624
 
49608
- if (config.customButtons) {
49609
- for (let b of config.customButtons) {
49625
+ if(config.customButtons) {
49626
+ for(let b of config.customButtons) {
49610
49627
  new CustomButton($toggle_button_container.get(0), this, b);
49611
49628
  }
49612
49629
  }
@@ -49767,6 +49784,7 @@ class Browser {
49767
49784
  return undefined
49768
49785
  }
49769
49786
  }
49787
+
49770
49788
  }
49771
49789
  }
49772
49790
 
@@ -49856,11 +49874,6 @@ class Browser {
49856
49874
 
49857
49875
  await this.loadTrackList(trackConfigurations);
49858
49876
 
49859
- // The ruler track is not explicitly loaded, but needs updated nonetheless.
49860
- for (let rtv of this.trackViews.filter((tv) => tv.track.type === 'ruler')) {
49861
- rtv.updateViews();
49862
- }
49863
-
49864
49877
  this.updateUIWithReferenceFrameList();
49865
49878
 
49866
49879
  }
@@ -50039,19 +50052,23 @@ class Browser {
50039
50052
 
50040
50053
  async loadTrackList(configList) {
50041
50054
 
50042
- const promises = [];
50043
- for (let config of configList) {
50044
- promises.push(this.loadTrack(config));
50045
- }
50055
+ try {
50056
+ const promises = [];
50057
+ for (let config of configList) {
50058
+ promises.push(this.loadTrack(config, false));
50059
+ }
50046
50060
 
50047
- const loadedTracks = await Promise.all(promises);
50048
- const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
50049
- return trackView.track.autoscaleGroup
50050
- });
50051
- if (groupAutoscaleViews.length > 0) {
50052
- this.updateViews();
50061
+ const loadedTracks = await Promise.all(promises);
50062
+ const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
50063
+ return trackView.track.autoscaleGroup
50064
+ });
50065
+ if (groupAutoscaleViews.length > 0) {
50066
+ this.updateViews(groupAutoscaleViews);
50067
+ }
50068
+ return loadedTracks
50069
+ } finally {
50070
+ await this.resize();
50053
50071
  }
50054
- return loadedTracks
50055
50072
  }
50056
50073
 
50057
50074
  async loadROI(config) {
@@ -50065,8 +50082,6 @@ class Browser {
50065
50082
  } else {
50066
50083
  this.roi.push(new ROI(config, this.genome));
50067
50084
  }
50068
- // Force reload all views (force = true) to insure ROI features are loaded. Wasteful but this function is
50069
- // rarely called.
50070
50085
  await this.updateViews(true);
50071
50086
  }
50072
50087
 
@@ -50078,14 +50093,14 @@ class Browser {
50078
50093
  }
50079
50094
  }
50080
50095
  for (let tv of this.trackViews) {
50081
- tv.repaintViews();
50096
+ tv.updateViews(true);
50082
50097
  }
50083
50098
  }
50084
50099
 
50085
50100
  clearROIs() {
50086
50101
  this.roi = [];
50087
50102
  for (let tv of this.trackViews) {
50088
- tv.repaintViews();
50103
+ tv.updateViews(true);
50089
50104
  }
50090
50105
  }
50091
50106
 
@@ -50097,12 +50112,24 @@ class Browser {
50097
50112
  /**
50098
50113
  * Return a promise to load a track.
50099
50114
  *
50115
+ * Each track is associated with the following DOM elements
50116
+ *
50117
+ * leftHandGutter - div on the left for track controls and legend
50118
+ * contentDiv - a div element wrapping all the track content. Height can be > viewportDiv height
50119
+ * viewportDiv - a div element through which the track is viewed. This might have a vertical scrollbar
50120
+ * canvas - canvas element upon which the track is drawn. Child of contentDiv
50121
+ *
50122
+ * The width of all elements should be equal. Height of the viewportDiv is controlled by the user, but never
50123
+ * greater than the contentDiv height. Height of contentDiv and canvas are equal, and governed by the data
50124
+ * loaded.
50125
+ *
50126
+ *
50100
50127
  * @param config
50101
50128
  * @param doResize - undefined by default
50102
50129
  * @returns {*}
50103
50130
  */
50104
50131
 
50105
- async loadTrack(config) {
50132
+ async loadTrack(config, doResize) {
50106
50133
 
50107
50134
 
50108
50135
  // config might be json
@@ -50170,6 +50197,11 @@ class Browser {
50170
50197
  }
50171
50198
  msg += (": " + config.url);
50172
50199
  Alert.presentAlert(new Error(msg), undefined);
50200
+ } finally {
50201
+ // TODO: If loadTrack() is called individually - not via loadTrackList() - call this.resize()
50202
+ if (false === doResize) ; else {
50203
+ await this.resize();
50204
+ }
50173
50205
  }
50174
50206
  }
50175
50207
 
@@ -50393,7 +50425,41 @@ class Browser {
50393
50425
  this.navbarManager.navbarDidResize(this.$navigation.width(), isWGV);
50394
50426
  }
50395
50427
 
50396
- await resize.call(this);
50428
+ await this.resize();
50429
+ }
50430
+
50431
+ async resize() {
50432
+
50433
+ const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
50434
+
50435
+ for (let referenceFrame of this.referenceFrameList) {
50436
+
50437
+ const index = this.referenceFrameList.indexOf(referenceFrame);
50438
+
50439
+ const {chr, genome} = referenceFrame;
50440
+
50441
+ const {bpLength} = genome.getChromosome(referenceFrame.chr);
50442
+
50443
+ const viewportWidthBP = referenceFrame.toBP(viewportWidth);
50444
+
50445
+ // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
50446
+ if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
50447
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
50448
+ referenceFrame.bpPerPixel = bpLength / viewportWidth;
50449
+ } else {
50450
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
50451
+ referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
50452
+ }
50453
+
50454
+ for (let {viewports} of this.trackViews) {
50455
+ viewports[index].setWidth(viewportWidth);
50456
+ }
50457
+
50458
+ }
50459
+
50460
+ await this.updateViews(true);
50461
+
50462
+ this.updateUIWithReferenceFrameList();
50397
50463
  }
50398
50464
 
50399
50465
  async updateViews(force) {
@@ -50469,12 +50535,6 @@ class Browser {
50469
50535
 
50470
50536
  }
50471
50537
 
50472
- repaintViews() {
50473
- for (let trackView of this.trackViews) {
50474
- trackView.repaintViews();
50475
- }
50476
- }
50477
-
50478
50538
  updateLocusSearchWidget() {
50479
50539
 
50480
50540
  const referenceFrameList = this.referenceFrameList;
@@ -50576,56 +50636,12 @@ class Browser {
50576
50636
  const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
50577
50637
  trackView.viewports.splice(indexRight, 0, viewport);
50578
50638
  }
50579
- }
50580
-
50581
- this.centerLineList = this.createCenterLineList(this.columnContainer);
50582
-
50583
- await resize.call(this);
50584
- }
50585
-
50586
- /**
50587
- * Add a new multi-locus panel for the specified region
50588
- * @param chr
50589
- * @param start
50590
- * @param end
50591
- * @param referenceFrameLeft - optional, if supplied new panel should be placed to the immediate right
50592
- */
50593
- async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
50594
-
50595
- // account for reduced viewport width as a result of adding right mate pair panel
50596
- const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
50597
- const scaleFactor = this.calculateViewportWidth(this.referenceFrameList.length) / this.calculateViewportWidth(1 + this.referenceFrameList.length);
50598
- for (let refFrame of this.referenceFrameList) {
50599
- refFrame.bpPerPixel *= scaleFactor;
50600
- }
50601
-
50602
- const bpp = (end - start) / viewportWidth;
50603
- const newReferenceFrame = new ReferenceFrame(this.genome, chr, start, end, bpp);
50604
- const indexLeft = referenceFrameLeft ? this.referenceFrameList.indexOf(referenceFrameLeft) : this.referenceFrameList.length - 1;
50605
- const indexRight = 1 + indexLeft;
50606
50639
 
50607
- // TODO -- this is really ugly
50608
- const {$viewport} = this.trackViews[0].viewports[indexLeft];
50609
- const viewportColumn = viewportColumnManager.insertAfter($viewport.get(0).parentElement);
50610
-
50611
- if (indexRight === this.referenceFrameList.length) {
50612
- this.referenceFrameList.push(newReferenceFrame);
50613
- for (let trackView of this.trackViews) {
50614
- const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
50615
- trackView.viewports.push(viewport);
50616
- }
50617
- } else {
50618
- this.referenceFrameList.splice(indexRight, 0, newReferenceFrame);
50619
- for (let trackView of this.trackViews) {
50620
- const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
50621
- trackView.viewports.splice(indexRight, 0, viewport);
50622
- }
50623
50640
  }
50624
50641
 
50625
-
50626
50642
  this.centerLineList = this.createCenterLineList(this.columnContainer);
50627
50643
 
50628
- resize.call(this);
50644
+ await this.resize();
50629
50645
  }
50630
50646
 
50631
50647
  async removeMultiLocusPanel(referenceFrame) {
@@ -50654,13 +50670,7 @@ class Browser {
50654
50670
 
50655
50671
  }
50656
50672
 
50657
- /**
50658
- * Goto the locus represented by the selected referenceFrame, discarding all other panels
50659
- *
50660
- * @param referenceFrame
50661
- * @returns {Promise<void>}
50662
- */
50663
- async gotoMultilocusPanel(referenceFrame) {
50673
+ async selectMultiLocusPanel(referenceFrame) {
50664
50674
 
50665
50675
  const referenceFrameIndex = this.referenceFrameList.indexOf(referenceFrame);
50666
50676
 
@@ -50714,7 +50724,7 @@ class Browser {
50714
50724
 
50715
50725
  this.updateUIWithReferenceFrameList();
50716
50726
 
50717
- await this.updateViews();
50727
+ await this.updateViews(true);
50718
50728
 
50719
50729
  }
50720
50730
 
@@ -50937,6 +50947,7 @@ class Browser {
50937
50947
  }
50938
50948
 
50939
50949
 
50950
+
50940
50951
  json["tracks"] = trackJson;
50941
50952
 
50942
50953
  return json // This is an object, not a json string
@@ -50955,9 +50966,14 @@ class Browser {
50955
50966
  return surl
50956
50967
  }
50957
50968
 
50958
- currentReferenceFrames() {
50969
+ currentLoci() {
50970
+ const loci = [];
50959
50971
  const anyTrackView = this.trackViews[0];
50960
- return anyTrackView.viewports.map(vp => vp.referenceFrame)
50972
+ for (let {referenceFrame} of anyTrackView.viewports) {
50973
+ const locusString = referenceFrame.getLocusString();
50974
+ loci.push(locusString);
50975
+ }
50976
+ return loci
50961
50977
  }
50962
50978
 
50963
50979
  /**
@@ -51077,9 +51093,12 @@ class Browser {
51077
51093
  }
51078
51094
 
51079
51095
  addWindowResizeHandler() {
51080
- // Create a copy of the prototype "resize" function bound to this instance. Neccessary to support removing.
51081
- this.boundWindowResizeHandler = resize.bind(this);
51096
+ this.boundWindowResizeHandler = windowResizeHandler.bind(this);
51082
51097
  window.addEventListener('resize', this.boundWindowResizeHandler);
51098
+
51099
+ function windowResizeHandler() {
51100
+ this.resize();
51101
+ }
51083
51102
  }
51084
51103
 
51085
51104
  removeWindowResizeHandler() {
@@ -51178,48 +51197,6 @@ class Browser {
51178
51197
  }
51179
51198
  }
51180
51199
 
51181
- /**
51182
- * Function called win window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
51183
- * than class method because it needs to be copied and bound to specific instances of browser to support listener
51184
- * removal
51185
- *
51186
- * @returns {Promise<void>}
51187
- */
51188
- async function resize() {
51189
-
51190
- const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
51191
-
51192
- for (let referenceFrame of this.referenceFrameList) {
51193
-
51194
- const index = this.referenceFrameList.indexOf(referenceFrame);
51195
-
51196
- const {chr, genome} = referenceFrame;
51197
-
51198
- const {bpLength} = genome.getChromosome(referenceFrame.chr);
51199
-
51200
- const viewportWidthBP = referenceFrame.toBP(viewportWidth);
51201
-
51202
- // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
51203
- if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
51204
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
51205
- referenceFrame.bpPerPixel = bpLength / viewportWidth;
51206
- } else {
51207
- // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
51208
- referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
51209
- }
51210
-
51211
- for (let {viewports} of this.trackViews) {
51212
- viewports[index].setWidth(viewportWidth);
51213
- }
51214
-
51215
- }
51216
-
51217
- this.updateUIWithReferenceFrameList();
51218
-
51219
- await this.updateViews(true);
51220
- }
51221
-
51222
-
51223
51200
  function handleMouseMove(e) {
51224
51201
 
51225
51202
  e.preventDefault();