igv 2.11.2 → 2.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/igv.esm.js CHANGED
@@ -17624,7 +17624,7 @@ function Node(interval) {
17624
17624
  * @constructor
17625
17625
  */
17626
17626
 
17627
- class FeatureCache {
17627
+ class FeatureCache$1 {
17628
17628
 
17629
17629
  constructor(featureList, genome, range) {
17630
17630
 
@@ -19712,6 +19712,8 @@ const knownFileExtensions = new Set([
19712
19712
  "bb",
19713
19713
  "bigbed",
19714
19714
  "biginteract",
19715
+ "biggenepred",
19716
+ "bignarrowpeak",
19715
19717
  "bw",
19716
19718
  "bigwig",
19717
19719
  "bam",
@@ -19854,6 +19856,7 @@ function inferTrackType(config) {
19854
19856
  return "alignment"
19855
19857
  case "bedpe":
19856
19858
  case "bedpe-loop":
19859
+ case "biginteract":
19857
19860
  return "interact"
19858
19861
  case "bp":
19859
19862
  return "arc"
@@ -19862,7 +19865,8 @@ function inferTrackType(config) {
19862
19865
  case "bed":
19863
19866
  case "bigbed":
19864
19867
  case "bb":
19865
- case "biginteract":
19868
+ case "biggenepred":
19869
+ case "bignarrowpeak":
19866
19870
  return "bedtype"
19867
19871
  default:
19868
19872
  return "annotation"
@@ -20113,6 +20117,38 @@ function isSecureContext() {
20113
20117
  return window.location.protocol === "https:" || window.location.hostname === "localhost"
20114
20118
  }
20115
20119
 
20120
+ const pairs =
20121
+ [
20122
+ ['A', 'T'],
20123
+ ['G', 'C'],
20124
+ ['Y', 'R'],
20125
+ ['W', 'S'],
20126
+ ['K', 'M'],
20127
+ ['D', 'H'],
20128
+ ['B', 'V']
20129
+ ];
20130
+
20131
+ const complements = new Map();
20132
+ for (let p of pairs) {
20133
+ const p1 = p[0];
20134
+ const p2 = p[1];
20135
+ complements.set(p1, p2);
20136
+ complements.set(p2, p1);
20137
+ complements.set(p1.toLowerCase(), p2.toLowerCase());
20138
+ complements.set(p2.toLowerCase(), p1.toLowerCase());
20139
+ }
20140
+
20141
+ function reverseComplementSequence(sequence) {
20142
+
20143
+ let comp = '';
20144
+ let idx = sequence.length;
20145
+ while (idx-- > 0) {
20146
+ const base = sequence[idx];
20147
+ comp += complements.has(base) ? complements.get(base) : base;
20148
+ }
20149
+ return comp
20150
+ }
20151
+
20116
20152
  /*
20117
20153
  * The MIT License (MIT)
20118
20154
  *
@@ -20237,7 +20273,6 @@ class SequenceTrack {
20237
20273
  }
20238
20274
 
20239
20275
  menuItemList() {
20240
-
20241
20276
  return [
20242
20277
  {
20243
20278
  name: this.reversed ? "Forward" : "Reverse",
@@ -20272,17 +20307,20 @@ class SequenceTrack {
20272
20307
  contextMenuItemList(clickState) {
20273
20308
  const viewport = clickState.viewport;
20274
20309
  if (viewport.referenceFrame.bpPerPixel <= 1) {
20310
+ const pixelWidth = viewport.getWidth();
20311
+ const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20312
+ const chr = viewport.referenceFrame.chr;
20313
+ const start = Math.floor(viewport.referenceFrame.start);
20314
+ const end = Math.ceil(start + bpWindow);
20275
20315
  const items = [
20276
20316
  {
20277
- label: 'View visible sequence...',
20317
+ label: this.reversed ? 'View visible sequence (reversed)...' : 'View visible sequence...',
20278
20318
  click: async () => {
20279
- const pixelWidth = viewport.getWidth();
20280
- const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20281
- const chr = viewport.referenceFrame.chr;
20282
- const start = viewport.referenceFrame.start;
20283
- const end = start + bpWindow;
20284
- const sequence = await this.browser.genome.sequence.getSequence(chr, start, end);
20285
- Alert.presentAlert(sequence);
20319
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20320
+ if (this.reversed) {
20321
+ seq = reverseComplementSequence(seq);
20322
+ }
20323
+ Alert.presentAlert(seq);
20286
20324
  }
20287
20325
  }
20288
20326
  ];
@@ -20290,14 +20328,18 @@ class SequenceTrack {
20290
20328
  items.push({
20291
20329
  label: 'Copy visible sequence',
20292
20330
  click: async () => {
20293
- const pixelWidth = viewport.getWidth();
20294
- const bpWindow = pixelWidth * viewport.referenceFrame.bpPerPixel;
20295
- const chr = viewport.referenceFrame.chr;
20296
- const start = viewport.referenceFrame.start;
20297
- const end = start + bpWindow;
20298
- const sequence = await this.browser.genome.sequence.getSequence(chr, start, end);
20299
- navigator.clipboard.writeText(sequence);
20331
+ let seq = await this.browser.genome.sequence.getSequence(chr, start, end);
20332
+ if (this.reversed) {
20333
+ seq = reverseComplementSequence(seq);
20334
+ }
20335
+ try {
20336
+ await navigator.clipboard.writeText(seq);
20337
+ } catch (e) {
20338
+ console.error(e);
20339
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
20340
+ }
20300
20341
  }
20342
+
20301
20343
  });
20302
20344
  }
20303
20345
  items.push('<hr/>');
@@ -20441,7 +20483,7 @@ class SequenceTrack {
20441
20483
  }
20442
20484
  }
20443
20485
 
20444
- supportsWholeGenome() {
20486
+ get supportsWholeGenome() {
20445
20487
  return false
20446
20488
  }
20447
20489
 
@@ -20515,13 +20557,13 @@ class Viewport {
20515
20557
  this.$content.height(this.$viewport.height());
20516
20558
  this.contentDiv = this.$content.get(0);
20517
20559
 
20518
- this.$canvas = $$1('<canvas>');
20519
- this.$content.append(this.$canvas);
20520
-
20521
- this.canvas = this.$canvas.get(0);
20522
- this.ctx = this.canvas.getContext("2d");
20560
+ // this.$canvas = $('<canvas>')
20561
+ // this.$content.append(this.$canvas)
20562
+ //
20563
+ // this.canvas = this.$canvas.get(0)
20564
+ // this.ctx = this.canvas.getContext("2d")
20523
20565
 
20524
- this.setWidth(width);
20566
+ this.$viewport.width(width);
20525
20567
 
20526
20568
  this.initializationHelper();
20527
20569
 
@@ -20588,14 +20630,13 @@ class Viewport {
20588
20630
  console.log('Viewport - draw(drawConfiguration, features, roiFeatures)');
20589
20631
  }
20590
20632
 
20591
- checkContentHeight() {
20633
+ checkContentHeight(features) {
20592
20634
 
20593
20635
  let track = this.trackView.track;
20594
-
20636
+ features = features || this.cachedFeatures;
20595
20637
  if ("FILL" === track.displayMode) {
20596
20638
  this.setContentHeight(this.$viewport.height());
20597
20639
  } else if (typeof track.computePixelHeight === 'function') {
20598
- let features = this.cachedFeatures;
20599
20640
  if (features && features.length > 0) {
20600
20641
  let requiredContentHeight = track.computePixelHeight(features);
20601
20642
  let currentContentHeight = this.$content.height();
@@ -20611,12 +20652,10 @@ class Viewport {
20611
20652
  }
20612
20653
 
20613
20654
  setContentHeight(contentHeight) {
20655
+
20614
20656
  // Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
20615
20657
  contentHeight = Math.min(contentHeight, 32000);
20616
-
20617
20658
  this.$content.height(contentHeight);
20618
-
20619
- if (this.tile) this.tile.invalidate = true;
20620
20659
  }
20621
20660
 
20622
20661
  isLoading() {
@@ -20633,8 +20672,6 @@ class Viewport {
20633
20672
 
20634
20673
  setWidth(width) {
20635
20674
  this.$viewport.width(width);
20636
- this.canvas.style.width = (`${width}px`);
20637
- this.canvas.setAttribute('width', width);
20638
20675
  }
20639
20676
 
20640
20677
  getWidth() {
@@ -20664,8 +20701,6 @@ class Viewport {
20664
20701
  this.popover.dispose();
20665
20702
  }
20666
20703
 
20667
- this.removeMouseHandlers();
20668
-
20669
20704
  this.$viewport.get(0).remove();
20670
20705
 
20671
20706
  // Null out all properties -- this should not be neccessary, but just in case there is a
@@ -22761,7 +22796,7 @@ const Cytoband = function (start, end, name, typestain) {
22761
22796
  }
22762
22797
  };
22763
22798
 
22764
- const _version = "2.11.2";
22799
+ const _version = "2.12.2";
22765
22800
  function version() {
22766
22801
  return _version
22767
22802
  }
@@ -22909,7 +22944,7 @@ class Genome {
22909
22944
  constructor(config, sequence, ideograms, aliases) {
22910
22945
 
22911
22946
  this.config = config;
22912
- this.id = config.id;
22947
+ this.id = config.id || generateGenomeID(config);
22913
22948
  this.sequence = sequence;
22914
22949
  this.chromosomeNames = sequence.chromosomeNames;
22915
22950
  this.chromosomes = sequence.chromosomes; // An object (functions as a dictionary)
@@ -23115,6 +23150,7 @@ class Genome {
23115
23150
  }
23116
23151
 
23117
23152
  async getSequence(chr, start, end) {
23153
+ chr = this.getChromosomeName(chr);
23118
23154
  return this.sequence.getSequence(chr, start, end)
23119
23155
  }
23120
23156
  }
@@ -23230,6 +23266,18 @@ function constructWG(genome, config) {
23230
23266
 
23231
23267
  }
23232
23268
 
23269
+ function generateGenomeID(config) {
23270
+ if (config.id !== undefined) {
23271
+ return config.id
23272
+ } else if (config.fastaURL && isString$3(config.fastaURL)) {
23273
+ return config.fastaURL
23274
+ } else if (config.fastaURL && config.fastaURL.name) {
23275
+ return config.fastaURL.name
23276
+ } else {
23277
+ return ("0000" + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4)
23278
+ }
23279
+ }
23280
+
23233
23281
  /**
23234
23282
  * Created by dat on 9/16/16.
23235
23283
  */
@@ -23250,28 +23298,27 @@ class TrackViewport extends Viewport {
23250
23298
  this.$viewport.append(this.$spinner);
23251
23299
  this.$spinner.append($$1('<div>'));
23252
23300
 
23253
- const {track} = this.trackView;
23254
-
23301
+ const track = this.trackView.track;
23255
23302
  if ('sequence' !== track.type) {
23256
23303
  this.$zoomInNotice = this.createZoomInNotice(this.$content);
23257
23304
  }
23258
23305
 
23259
- if (track.name && "sequence" !== track.config.type) {
23260
-
23306
+ if (track.name && "sequence" !== track.id) {
23261
23307
  this.$trackLabel = $$1('<div class="igv-track-label">');
23262
23308
  this.$viewport.append(this.$trackLabel);
23263
23309
  this.setTrackLabel(track.name);
23264
-
23265
23310
  if (false === this.browser.trackLabelsVisible) {
23266
23311
  this.$trackLabel.hide();
23267
23312
  }
23268
-
23269
23313
  }
23270
23314
 
23271
23315
  this.stopSpinner();
23272
-
23273
23316
  this.addMouseHandlers();
23317
+ }
23274
23318
 
23319
+ setContentHeight(contentHeight) {
23320
+ super.setContentHeight(contentHeight);
23321
+ if (this.featureCache) this.featureCache.redraw = true;
23275
23322
  }
23276
23323
 
23277
23324
  setTrackLabel(label) {
@@ -23293,61 +23340,74 @@ class TrackViewport extends Viewport {
23293
23340
  }
23294
23341
  }
23295
23342
 
23343
+ /**
23344
+ * Test to determine if we are zoomed in far enough to see features. Applicable to tracks with visibility windows.
23345
+ *
23346
+ * As a side effect the viewports canvas is removed if zoomed out.
23347
+ *
23348
+ * @returns {boolean} true if we are zoomed in past visibility window, false otherwise
23349
+ */
23296
23350
  checkZoomIn() {
23297
23351
 
23298
- const showZoomInNotice = () => {
23299
- const referenceFrame = this.referenceFrame;
23300
- if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome()) {
23352
+ const zoomedOutOfWindow = () => {
23353
+ if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome) {
23301
23354
  return true
23302
23355
  } else {
23303
23356
  const visibilityWindow = this.trackView.track.visibilityWindow;
23304
23357
  return (
23305
23358
  visibilityWindow !== undefined && visibilityWindow > 0 &&
23306
- (referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow))
23359
+ (this.referenceFrame.bpPerPixel * this.$viewport.width() > visibilityWindow))
23307
23360
  }
23308
23361
  };
23309
23362
 
23363
+ if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
23364
+ $$1(this.canvas).remove();
23365
+ this.canvas = undefined;
23366
+ //this.featureCache = undefined
23367
+ return false
23368
+ }
23369
+
23310
23370
  if (!(this.viewIsReady())) {
23311
23371
  return false
23312
23372
  }
23313
23373
 
23314
- if (this.$zoomInNotice) {
23315
- if (showZoomInNotice()) {
23316
- // Out of visibility window
23317
- if (this.canvas) {
23318
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
23319
- this.tile = undefined;
23320
- }
23321
- this.$zoomInNotice.show();
23322
23374
 
23323
- if (this.trackView.track.autoHeight) {
23324
- const minHeight = this.trackView.minHeight || 0;
23325
- this.setContentHeight(minHeight);
23326
- }
23375
+ if (zoomedOutOfWindow()) {
23327
23376
 
23328
- return false
23329
- } else {
23377
+ // Out of visibility window
23378
+ if (this.canvas) {
23379
+ $$1(this.canvas).remove();
23380
+ this.canvas = undefined;
23381
+ //this.featureCache = undefined
23382
+ }
23383
+ if (this.trackView.track.autoHeight) {
23384
+ const minHeight = this.trackView.minHeight || 0;
23385
+ this.setContentHeight(minHeight);
23386
+ }
23387
+ if (this.$zoomInNotice) {
23388
+ this.$zoomInNotice.show();
23389
+ }
23390
+ return false
23391
+ } else {
23392
+ if (this.$zoomInNotice) {
23330
23393
  this.$zoomInNotice.hide();
23331
- return true
23332
23394
  }
23395
+ return true
23333
23396
  }
23334
23397
 
23335
- return true
23336
-
23337
-
23338
23398
  }
23339
23399
 
23400
+ /**
23401
+ * Adjust the canvas to the current genomic state.
23402
+ */
23340
23403
  shift() {
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";
23404
+ const referenceFrame = this.referenceFrame;
23405
+ if (this.canvas &&
23406
+ this.canvas._data &&
23407
+ this.canvas._data.chr === this.referenceFrame.chr &&
23408
+ this.canvas._data.bpPerPixel === referenceFrame.bpPerPixel) {
23409
+ const pixelOffset = Math.round((this.canvas._data.startBP - referenceFrame.start) / referenceFrame.bpPerPixel);
23410
+ this.canvas.style.left = pixelOffset + "px";
23351
23411
  }
23352
23412
  }
23353
23413
 
@@ -23370,22 +23430,23 @@ class TrackViewport extends Viewport {
23370
23430
  this.startSpinner();
23371
23431
 
23372
23432
  try {
23373
- const features = await this.getFeatures(this.trackView.track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23433
+ const track = this.trackView.track;
23434
+ const features = await this.getFeatures(track, chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23374
23435
  let roiFeatures = [];
23375
- const roi = mergeArrays(this.browser.roi, this.trackView.track.roi);
23436
+ const roi = mergeArrays(this.browser.roi, track.roi);
23376
23437
  if (roi) {
23377
23438
  for (let r of roi) {
23378
- const f = await
23379
- r.getFeatures(chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23439
+ const f = await r.getFeatures(chr, bpStart, bpEnd, referenceFrame.bpPerPixel);
23380
23440
  roiFeatures.push({track: r, features: f});
23381
23441
  }
23382
23442
  }
23383
23443
 
23384
- this.tile = new Tile(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures);
23444
+ const mr = track && ("wig" === track.type || "merged" === track.type); // wig tracks are potentially multiresolution (e.g. bigwig)
23445
+ this.featureCache = new FeatureCache(chr, bpStart, bpEnd, referenceFrame.bpPerPixel, features, roiFeatures, mr);
23385
23446
  this.loading = false;
23386
23447
  this.hideMessage();
23387
23448
  this.stopSpinner();
23388
- return this.tile
23449
+ return this.featureCache
23389
23450
  } catch (error) {
23390
23451
  // Track might have been removed during load
23391
23452
  if (this.trackView && this.trackView.disposed !== true) {
@@ -23399,32 +23460,29 @@ class TrackViewport extends Viewport {
23399
23460
  }
23400
23461
  }
23401
23462
 
23402
- async repaint() {
23463
+ /**
23464
+ * Repaint the canvas using the cached features
23465
+ *
23466
+ */
23467
+ repaint() {
23403
23468
 
23404
- if (undefined === this.tile) {
23469
+ if (undefined === this.featureCache) {
23405
23470
  return
23406
23471
  }
23407
23472
 
23408
- let {features, roiFeatures, bpPerPixel, startBP, endBP} = this.tile;
23473
+ let {features, roiFeatures} = this.featureCache;
23474
+ //this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
23409
23475
 
23410
23476
  // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23411
23477
  const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
23412
- let pixelWidth;
23413
-
23414
- if (isWGV) {
23415
- bpPerPixel = this.referenceFrame.end / this.$viewport.width();
23416
- startBP = 0;
23417
- endBP = this.referenceFrame.end;
23418
- pixelWidth = this.$viewport.width();
23419
- } else {
23420
- pixelWidth = Math.ceil((endBP - startBP) / bpPerPixel);
23421
- }
23422
23478
 
23479
+ // Canvas dimensions. There is no left-right panning for WGV so canvas width is viewport width.
23423
23480
  // For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
23481
+ const pixelWidth = isWGV ? this.$viewport.width() : 3 * this.$viewport.width();
23424
23482
  const viewportHeight = this.$viewport.height();
23425
23483
  const contentHeight = this.getContentHeight();
23426
23484
  const minHeight = roiFeatures ? Math.max(contentHeight, viewportHeight) : contentHeight; // Need to fill viewport for ROIs.
23427
- let pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23485
+ const pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23428
23486
  if (0 === pixelWidth || 0 === pixelHeight) {
23429
23487
  if (this.canvas) {
23430
23488
  $$1(this.canvas).remove();
@@ -23433,30 +23491,25 @@ class TrackViewport extends Viewport {
23433
23491
  }
23434
23492
  const canvasTop = Math.max(0, -(this.$content.position().top) - viewportHeight);
23435
23493
 
23436
- // Always use high DPI if in compressed display mode, otherwise use preference setting;
23437
- let devicePixelRatio;
23438
- if ("FILL" === this.trackView.track.displayMode) {
23439
- devicePixelRatio = window.devicePixelRatio;
23440
- } else {
23441
- devicePixelRatio = (this.trackView.track.supportHiDPI === false) ? 1 : window.devicePixelRatio;
23442
- }
23443
-
23444
- const pixelXOffset = Math.round((startBP - this.referenceFrame.start) / this.referenceFrame.bpPerPixel);
23494
+ const bpPerPixel = this.referenceFrame.bpPerPixel;
23495
+ const startBP = this.referenceFrame.start - (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23496
+ const endBP = this.referenceFrame.end + (isWGV ? 0 : pixelWidth / 3 * bpPerPixel);
23497
+ const pixelXOffset = Math.round((startBP - this.referenceFrame.start) / bpPerPixel);
23445
23498
 
23446
23499
  const newCanvas = $$1('<canvas class="igv-canvas">').get(0);
23447
- const ctx = newCanvas.getContext("2d");
23448
-
23449
23500
  newCanvas.style.width = pixelWidth + "px";
23450
23501
  newCanvas.style.height = pixelHeight + "px";
23502
+ newCanvas.style.left = pixelXOffset + "px";
23503
+ newCanvas.style.top = canvasTop + "px";
23451
23504
 
23505
+ // Always use high DPI if in "FILL" display mode, otherwise use track setting;
23506
+ const devicePixelRatio = ("FILL" === this.trackView.track.displayMode || this.trackView.track.supportHiDPI !== false) ?
23507
+ window.devicePixelRatio : 1;
23452
23508
  newCanvas.width = devicePixelRatio * pixelWidth;
23453
23509
  newCanvas.height = devicePixelRatio * pixelHeight;
23454
23510
 
23511
+ const ctx = newCanvas.getContext("2d");
23455
23512
  ctx.scale(devicePixelRatio, devicePixelRatio);
23456
-
23457
- newCanvas.style.left = pixelXOffset + "px";
23458
- newCanvas.style.top = canvasTop + "px";
23459
-
23460
23513
  ctx.translate(0, -canvasTop);
23461
23514
 
23462
23515
  const drawConfiguration =
@@ -23477,17 +23530,27 @@ class TrackViewport extends Viewport {
23477
23530
 
23478
23531
  this.draw(drawConfiguration, features, roiFeatures);
23479
23532
 
23480
- this.canvasVerticalRange = {top: canvasTop, bottom: canvasTop + pixelHeight};
23533
+ this.featureCache.canvasTop = canvasTop;
23534
+ this.featureCache.height = pixelHeight;
23481
23535
 
23482
- if (this.$canvas) {
23483
- this.$canvas.remove();
23536
+ if (this.canvas) {
23537
+ $$1(this.canvas).remove();
23484
23538
  }
23485
- this.$canvas = $$1(newCanvas);
23486
- this.$content.append(this.$canvas);
23539
+ newCanvas._data = {
23540
+ chr: this.featureCache.chr, bpPerPixel, startBP, endBP, pixelHeight, pixelTop: canvasTop
23541
+ };
23487
23542
  this.canvas = newCanvas;
23488
- this.ctx = ctx;
23543
+ this.$content.append($$1(newCanvas));
23544
+
23489
23545
  }
23490
23546
 
23547
+ /**
23548
+ * Draw the associated track.
23549
+ *
23550
+ * @param drawConfiguration
23551
+ * @param features
23552
+ * @param roiFeatures
23553
+ */
23491
23554
  draw(drawConfiguration, features, roiFeatures) {
23492
23555
 
23493
23556
  // console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
@@ -23504,60 +23567,6 @@ class TrackViewport extends Viewport {
23504
23567
  }
23505
23568
  }
23506
23569
 
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
-
23561
23570
  containsPosition(chr, position) {
23562
23571
  if (this.referenceFrame.chr === chr && position >= this.referenceFrame.start) {
23563
23572
  return position <= this.referenceFrame.calculateEnd(this.getWidth())
@@ -23570,18 +23579,20 @@ class TrackViewport extends Viewport {
23570
23579
  return this.loading
23571
23580
  }
23572
23581
 
23573
- saveImage() {
23582
+ savePNG() {
23574
23583
 
23575
- if (!this.ctx) return
23584
+ if (!this.canvas) return
23576
23585
 
23577
- const canvasTop = this.canvasVerticalRange ? this.canvasVerticalRange.top : 0;
23586
+ const canvasMetadata = this.featureCache;
23587
+ const canvasTop = canvasMetadata ? canvasMetadata.canvasTop : 0;
23578
23588
  const devicePixelRatio = window.devicePixelRatio;
23579
23589
  const w = this.$viewport.width() * devicePixelRatio;
23580
23590
  const h = this.$viewport.height() * devicePixelRatio;
23581
23591
  const x = -$$1(this.canvas).position().left * devicePixelRatio;
23582
23592
  const y = (-this.$content.position().top - canvasTop) * devicePixelRatio;
23583
23593
 
23584
- const imageData = this.ctx.getImageData(x, y, w, h);
23594
+ const ctx = this.canvas.getContext("2d");
23595
+ const imageData = ctx.getImageData(x, y, w, h);
23585
23596
  const exportCanvas = document.createElement('canvas');
23586
23597
  const exportCtx = exportCanvas.getContext('2d');
23587
23598
  exportCanvas.width = imageData.width;
@@ -23704,32 +23715,53 @@ class TrackViewport extends Viewport {
23704
23715
  selection: this.selection
23705
23716
  };
23706
23717
 
23707
- const features = this.tile ? this.tile.features : [];
23708
- const roiFeatures = this.tile ? this.tile.roiFeatures : undefined;
23718
+ const features = this.featureCache ? this.featureCache.features : [];
23719
+ const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
23709
23720
  this.draw(config, features, roiFeatures);
23710
23721
 
23711
23722
  context.restore();
23712
23723
 
23713
23724
  }
23714
23725
 
23715
- getCachedFeatures() {
23716
- return this.tile ? this.tile.features : []
23726
+ get cachedFeatures() {
23727
+ return this.featureCache ? this.featureCache.features : []
23717
23728
  }
23718
23729
 
23719
23730
  async getFeatures(track, chr, start, end, bpPerPixel) {
23720
23731
 
23721
- if (this.tile && this.tile.containsRange(chr, start, end, bpPerPixel)) {
23722
- return this.tile.features
23732
+ if (this.featureCache && this.featureCache.containsRange(chr, start, end, bpPerPixel)) {
23733
+ return this.featureCache.features
23723
23734
  } else if (typeof track.getFeatures === "function") {
23724
23735
  const features = await track.getFeatures(chr, start, end, bpPerPixel, this);
23725
- this.cachedFeatures = features;
23726
- this.checkContentHeight();
23736
+ this.checkContentHeight(features);
23727
23737
  return features
23728
23738
  } else {
23729
23739
  return undefined
23730
23740
  }
23731
23741
  }
23732
23742
 
23743
+ needsRepaint() {
23744
+
23745
+ if (!this.canvas) return true
23746
+
23747
+ const data = this.canvas._data;
23748
+ return !data ||
23749
+ this.referenceFrame.start < data.startBP ||
23750
+ this.referenceFrame.end > data.endBP ||
23751
+ this.referenceFrame.chr !== data.chr ||
23752
+ this.referenceFrame.bpPerPixel != data.bpPerPixel
23753
+ }
23754
+
23755
+ needsReload() {
23756
+ if (!this.featureCache) return true
23757
+ const referenceFrame = this.referenceFrame;
23758
+ const chr = this.referenceFrame.chr;
23759
+ const start = referenceFrame.start;
23760
+ const end = start + referenceFrame.toBP($$1(this.contentDiv).width());
23761
+ const bpPerPixel = referenceFrame.bpPerPixel;
23762
+ return (!this.featureCache.containsRange(chr, start, end, bpPerPixel))
23763
+ }
23764
+
23733
23765
  createZoomInNotice($parent) {
23734
23766
 
23735
23767
  const $container = $$1('<div>', {class: 'igv-zoom-in-notice-container'});
@@ -23751,15 +23783,33 @@ class TrackViewport extends Viewport {
23751
23783
 
23752
23784
  addMouseHandlers() {
23753
23785
 
23754
- this.addViewportContextMenuHandler(this.$viewport.get(0));
23786
+ const viewport = this.$viewport.get(0);
23755
23787
 
23756
- this.addViewportMouseDownHandler(this.$viewport.get(0));
23788
+ this.addViewportContextMenuHandler(viewport);
23757
23789
 
23758
- this.addViewportTouchStartHandler(this.$viewport.get(0));
23759
-
23760
- this.addViewportMouseUpHandler(this.$viewport.get(0));
23790
+ const md = (event) => {
23791
+ this.enableClick = true;
23792
+ this.browser.mouseDownOnViewport(event, this);
23793
+ pageCoordinates$1(event);
23794
+ };
23795
+ viewport.addEventListener('mousedown', md);
23796
+ viewport.addEventListener('touchstart', md);
23797
+
23798
+ const mu = (event) => {
23799
+ // Any mouse up cancels drag and scrolling
23800
+ if (this.browser.dragObject || this.browser.isScrolling) {
23801
+ this.browser.cancelTrackPan();
23802
+ // event.preventDefault();
23803
+ // event.stopPropagation();
23804
+ this.enableClick = false; // Until next mouse down
23805
+ } else {
23806
+ this.browser.cancelTrackPan();
23807
+ this.browser.endTrackDrag();
23808
+ }
23809
+ };
23761
23810
 
23762
- this.addViewportTouchEndHandler(this.$viewport.get(0));
23811
+ viewport.addEventListener('mouseup', mu);
23812
+ viewport.addEventListener('touchend', mu);
23763
23813
 
23764
23814
  this.addViewportClickHandler(this.$viewport.get(0));
23765
23815
 
@@ -23769,32 +23819,9 @@ class TrackViewport extends Viewport {
23769
23819
 
23770
23820
  }
23771
23821
 
23772
- removeMouseHandlers() {
23773
-
23774
- this.removeViewportContextMenuHandler(this.$viewport.get(0));
23775
-
23776
- this.removeViewportMouseDownHandler(this.$viewport.get(0));
23777
-
23778
- this.removeViewportTouchStartHandler(this.$viewport.get(0));
23779
-
23780
- this.removeViewportMouseUpHandler(this.$viewport.get(0));
23781
-
23782
- this.removeViewportTouchEndHandler(this.$viewport.get(0));
23783
-
23784
- this.removeViewportClickHandler(this.$viewport.get(0));
23785
-
23786
- if (this.trackView.track.name && "sequence" !== this.trackView.track.config.type) {
23787
- this.removeTrackLabelClickHandler(this.$trackLabel.get(0));
23788
- }
23789
-
23790
- }
23791
-
23792
23822
  addViewportContextMenuHandler(viewport) {
23793
23823
 
23794
- this.boundContextMenuHandler = contextMenuHandler.bind(this);
23795
- viewport.addEventListener('contextmenu', this.boundContextMenuHandler);
23796
-
23797
- function contextMenuHandler(event) {
23824
+ viewport.addEventListener('contextmenu', (event) => {
23798
23825
 
23799
23826
  // Ignore if we are doing a drag. This can happen with touch events.
23800
23827
  if (this.browser.dragObject) {
@@ -23827,58 +23854,16 @@ class TrackViewport extends Viewport {
23827
23854
  menuItems.push({label: 'Save Image (SVG)', click: () => this.saveSVG()});
23828
23855
 
23829
23856
  this.browser.menuPopup.presentTrackContextMenu(event, menuItems);
23830
- }
23831
-
23832
- }
23833
-
23834
- removeViewportContextMenuHandler(viewport) {
23835
- viewport.removeEventListener('contextmenu', this.boundContextMenuHandler);
23836
- }
23837
-
23838
- addViewportMouseDownHandler(viewport) {
23839
- this.boundMouseDownHandler = mouseDownHandler.bind(this);
23840
- viewport.addEventListener('mousedown', this.boundMouseDownHandler);
23841
- }
23842
-
23843
- removeViewportMouseDownHandler(viewport) {
23844
- viewport.removeEventListener('mousedown', this.boundMouseDownHandler);
23845
- }
23846
-
23847
- addViewportTouchStartHandler(viewport) {
23848
- this.boundTouchStartHandler = mouseDownHandler.bind(this);
23849
- viewport.addEventListener('touchstart', this.boundTouchStartHandler);
23850
- }
23851
-
23852
- removeViewportTouchStartHandler(viewport) {
23853
- viewport.removeEventListener('touchstart', this.boundTouchStartHandler);
23854
- }
23855
-
23856
- addViewportMouseUpHandler(viewport) {
23857
- this.boundMouseUpHandler = mouseUpHandler.bind(this);
23858
- viewport.addEventListener('mouseup', this.boundMouseUpHandler);
23859
- }
23860
-
23861
- removeViewportMouseUpHandler(viewport) {
23862
- viewport.removeEventListener('mouseup', this.boundMouseUpHandler);
23863
- }
23857
+ });
23864
23858
 
23865
- addViewportTouchEndHandler(viewport) {
23866
- this.boundTouchEndHandler = mouseUpHandler.bind(this);
23867
- viewport.addEventListener('touchend', this.boundTouchEndHandler);
23868
23859
  }
23869
23860
 
23870
- removeViewportTouchEndHandler(viewport) {
23871
- viewport.removeEventListener('touchend', this.boundTouchEndHandler);
23872
- }
23873
23861
 
23874
23862
  addViewportClickHandler(viewport) {
23875
23863
 
23876
- this.boundClickHandler = clickHandler.bind(this);
23877
- viewport.addEventListener('click', this.boundClickHandler);
23878
-
23879
- function clickHandler(event) {
23864
+ viewport.addEventListener('click', (event) => {
23880
23865
 
23881
- if (this.enableClick) {
23866
+ if (this.enableClick && this.canvas) {
23882
23867
  if (3 === event.which || event.ctrlKey) {
23883
23868
  return
23884
23869
  }
@@ -23961,21 +23946,12 @@ class TrackViewport extends Viewport {
23961
23946
  lastClickTime = time;
23962
23947
 
23963
23948
  }
23964
-
23965
- }
23966
-
23967
- }
23968
-
23969
- removeViewportClickHandler(viewport) {
23970
- viewport.removeEventListener('click', this.boundClickHandler);
23949
+ });
23971
23950
  }
23972
23951
 
23973
23952
  addTrackLabelClickHandler(trackLabel) {
23974
23953
 
23975
- this.boundTrackLabelClickHandler = clickHandler.bind(this);
23976
- trackLabel.addEventListener('click', this.boundTrackLabelClickHandler);
23977
-
23978
- function clickHandler(event) {
23954
+ trackLabel.addEventListener('click', (event) => {
23979
23955
 
23980
23956
  event.stopPropagation();
23981
23957
 
@@ -23995,48 +23971,18 @@ class TrackViewport extends Viewport {
23995
23971
  this.popover = new Popover(this.browser.columnContainer, (track.name || ''));
23996
23972
  this.popover.presentContentWithEvent(event, str);
23997
23973
  }
23998
- }
23999
- }
24000
-
24001
- removeTrackLabelClickHandler(trackLabel) {
24002
- trackLabel.removeEventListener('click', this.boundTrackLabelClickHandler);
23974
+ });
24003
23975
  }
24004
23976
 
24005
23977
  }
24006
23978
 
24007
- function mouseDownHandler(event) {
24008
- this.enableClick = true;
24009
- this.browser.mouseDownOnViewport(event, this);
24010
- pageCoordinates$1(event);
24011
- }
24012
-
24013
- function mouseUpHandler(event) {
24014
-
24015
- // Any mouse up cancels drag and scrolling
24016
- if (this.browser.dragObject || this.browser.isScrolling) {
24017
- this.browser.cancelTrackPan();
24018
- // event.preventDefault();
24019
- // event.stopPropagation();
24020
- this.enableClick = false; // Until next mouse down
24021
- } else {
24022
- this.browser.cancelTrackPan();
24023
- this.browser.endTrackDrag();
24024
- }
24025
- }
24026
-
24027
23979
  function createClickState(event, viewport) {
24028
23980
 
24029
23981
  const referenceFrame = viewport.referenceFrame;
24030
-
24031
23982
  const viewportCoords = translateMouseCoordinates$1(event, viewport.contentDiv);
24032
23983
  const canvasCoords = translateMouseCoordinates$1(event, viewport.canvas);
24033
-
24034
23984
  const genomicLocation = ((referenceFrame.start) + referenceFrame.toBP(viewportCoords.x));
24035
23985
 
24036
- if (undefined === genomicLocation || null === viewport.tile) {
24037
- return undefined
24038
- }
24039
-
24040
23986
  return {
24041
23987
  event,
24042
23988
  viewport,
@@ -24097,22 +24043,30 @@ function formatPopoverText(nameValues) {
24097
24043
  return rows.join('')
24098
24044
  }
24099
24045
 
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
- };
24046
+ class FeatureCache {
24108
24047
 
24109
- Tile.prototype.containsRange = function (chr, start, end, bpPerPixel) {
24110
- return this.bpPerPixel === bpPerPixel && start >= this.startBP && end <= this.endBP && chr === this.chr
24111
- };
24048
+ constructor(chr, tileStart, tileEnd, bpPerPixel, features, roiFeatures, multiresolution) {
24049
+ this.chr = chr;
24050
+ this.startBP = tileStart;
24051
+ this.endBP = tileEnd;
24052
+ this.bpPerPixel = bpPerPixel;
24053
+ this.features = features;
24054
+ this.roiFeatures = roiFeatures;
24055
+ this.multiresolution = multiresolution;
24056
+ }
24112
24057
 
24113
- Tile.prototype.overlapsRange = function (chr, start, end) {
24114
- return this.chr === chr && end >= this.startBP && start <= this.endBP
24115
- };
24058
+ containsRange(chr, start, end, bpPerPixel) {
24059
+
24060
+ // For multi-resolution tracks allow for a 2X change in bpPerPixel
24061
+ const r = this.multiresolution ? this.bpPerPixel / bpPerPixel : 1;
24062
+
24063
+ return start >= this.startBP && end <= this.endBP && chr === this.chr && r > 0.5 && r < 2
24064
+ }
24065
+
24066
+ overlapsRange(chr, start, end) {
24067
+ return this.chr === chr && end >= this.startBP && start <= this.endBP
24068
+ }
24069
+ }
24116
24070
 
24117
24071
 
24118
24072
  /**
@@ -24277,11 +24231,13 @@ class RulerSweeper {
24277
24231
 
24278
24232
  validateLocusExtent(this.rulerViewport.browser.genome.getChromosome(this.rulerViewport.referenceFrame.chr).bpLength, extent, this.rulerViewport.browser.minimumBases());
24279
24233
 
24280
- this.rulerViewport.referenceFrame.bpPerPixel = (Math.round(extent.end) - Math.round(extent.start)) / this.rulerViewport.contentDiv.clientWidth;
24281
- this.rulerViewport.referenceFrame.start = Math.round(extent.start);
24282
- this.rulerViewport.referenceFrame.end = Math.round(extent.end);
24234
+ const newStart = Math.round(extent.start);
24235
+ const newEnd = Math.round(extent.end);
24236
+ this.rulerViewport.referenceFrame.bpPerPixel = (newEnd - newStart) / this.rulerViewport.contentDiv.clientWidth;
24237
+ this.rulerViewport.referenceFrame.start = newStart;
24238
+ this.rulerViewport.referenceFrame.end = newEnd;
24283
24239
 
24284
- this.rulerViewport.browser.updateViews(this.rulerViewport.referenceFrame);
24240
+ this.rulerViewport.browser.updateViews();
24285
24241
  }
24286
24242
 
24287
24243
  }
@@ -24419,6 +24375,18 @@ class PairedAlignment {
24419
24375
  return true // By definition
24420
24376
  }
24421
24377
 
24378
+ isMateMapped() {
24379
+ return true // By definition
24380
+ }
24381
+
24382
+ isProperPair() {
24383
+ return this.firstAlignment.isProperPair()
24384
+ }
24385
+
24386
+ get fragmentLength() {
24387
+ return Math.abs(this.firstAlignment.fragmentLength)
24388
+ }
24389
+
24422
24390
  firstOfPairStrand() {
24423
24391
 
24424
24392
  if (this.firstAlignment.isFirstOfPair()) {
@@ -24429,6 +24397,10 @@ class PairedAlignment {
24429
24397
  return this.firstAlignment.mate.strand // Assumption is mate is first-of-pair
24430
24398
  }
24431
24399
  }
24400
+
24401
+ hasTag(str) {
24402
+ return this.firstAlignment.hasTag(str) || (this.secondAlignment && this.secondAlignment.hasTag(str))
24403
+ }
24432
24404
  }
24433
24405
 
24434
24406
  /*
@@ -24760,7 +24732,10 @@ function packAlignmentRows(alignments, start, end, showSoftClips) {
24760
24732
 
24761
24733
 
24762
24734
  class AlignmentContainer {
24763
- constructor(chr, start, end, samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold) {
24735
+
24736
+ // this.config.samplingWindowSize, this.config.samplingDepth,
24737
+ // this.config.pairsSupported, this.config.alleleFreqThreshold)
24738
+ constructor(chr, start, end, {samplingWindowSize, samplingDepth, pairsSupported, alleleFreqThreshold}) {
24764
24739
 
24765
24740
  this.chr = chr;
24766
24741
  this.start = Math.floor(start);
@@ -24788,17 +24763,12 @@ class AlignmentContainer {
24788
24763
  return alignment.isMapped() && !alignment.isFailsVendorQualityCheck()
24789
24764
  };
24790
24765
 
24791
- this.pairedEndStats = new PairedEndStats();
24792
24766
  }
24793
24767
 
24794
24768
  push(alignment) {
24795
24769
 
24796
24770
  if (this.filter(alignment) === false) return
24797
24771
 
24798
- if (alignment.isPaired()) {
24799
- this.pairedEndStats.push(alignment);
24800
- }
24801
-
24802
24772
  this.coverageMap.incCounts(alignment); // Count coverage before any downsampling
24803
24773
 
24804
24774
  if (this.pairsSupported && this.downsampledReads.has(alignment.readName)) {
@@ -24830,8 +24800,6 @@ class AlignmentContainer {
24830
24800
 
24831
24801
  this.pairsCache = undefined;
24832
24802
  this.downsampledReads = undefined;
24833
-
24834
- this.pairedEndStats.compute();
24835
24803
  }
24836
24804
 
24837
24805
  contains(chr, start, end) {
@@ -25142,69 +25110,6 @@ class DownsampledInterval {
25142
25110
  }
25143
25111
  }
25144
25112
 
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
-
25208
25113
  class SupplementaryAlignment {
25209
25114
 
25210
25115
  constructor(rec) {
@@ -25925,7 +25830,7 @@ const BamUtils = {
25925
25830
  const lseq = readInt(ba, offset + 20);
25926
25831
  const mateChrIdx = readInt(ba, offset + 24);
25927
25832
  const matePos = readInt(ba, offset + 28);
25928
- const tlen = readInt(ba, offset + 32);
25833
+ const fragmentLength = readInt(ba, offset + 32);
25929
25834
 
25930
25835
  let readName = [];
25931
25836
  for (let j = 0; j < nl - 1; ++j) {
@@ -25966,7 +25871,7 @@ const BamUtils = {
25966
25871
  alignment.readName = readName;
25967
25872
  alignment.cigar = cigar;
25968
25873
  alignment.lengthOnRef = lengthOnRef;
25969
- alignment.fragmentLength = tlen;
25874
+ alignment.fragmentLength = fragmentLength;
25970
25875
  alignment.mq = mq;
25971
25876
 
25972
25877
  BamUtils.bam_tag2cigar(ba, blockEnd, p, lseq, alignment, cigarArray);
@@ -26394,7 +26299,7 @@ class BamReaderNonIndexed {
26394
26299
  const header = this.header;
26395
26300
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
26396
26301
  const qAlignments = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
26397
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
26302
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
26398
26303
  for (let a of qAlignments) {
26399
26304
  alignmentContainer.push(a);
26400
26305
  }
@@ -26421,13 +26326,13 @@ class BamReaderNonIndexed {
26421
26326
  const alignments = [];
26422
26327
  this.header = BamUtils.decodeBamHeader(data);
26423
26328
  BamUtils.decodeBamRecords(data, this.header.size, alignments, this.header.chrNames);
26424
- this.alignmentCache = new FeatureCache(alignments, this.genome);
26329
+ this.alignmentCache = new FeatureCache$1(alignments, this.genome);
26425
26330
  }
26426
26331
 
26427
26332
  fetchAlignments(chr, bpStart, bpEnd) {
26428
26333
  const queryChr = this.header.chrAliasTable.hasOwnProperty(chr) ? this.header.chrAliasTable[chr] : chr;
26429
26334
  const features = this.alignmentCache.queryFeatures(queryChr, bpStart, bpEnd);
26430
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.samplingWindowSize, this.samplingDepth, this.pairsSupported);
26335
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
26431
26336
  for (let feature of features) {
26432
26337
  alignmentContainer.push(feature);
26433
26338
  }
@@ -27418,9 +27323,7 @@ class BamReader {
27418
27323
  const chrToIndex = await this.getChrIndex();
27419
27324
  const queryChr = this.chrAliasTable.hasOwnProperty(chr) ? this.chrAliasTable[chr] : chr;
27420
27325
  const chrId = chrToIndex[queryChr];
27421
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd,
27422
- this.config.samplingWindowSize, this.config.samplingDepth,
27423
- this.config.pairsSupported, this.config.alleleFreqThreshold);
27326
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
27424
27327
 
27425
27328
  if (chrId === undefined) {
27426
27329
  return alignmentContainer
@@ -27552,7 +27455,7 @@ class ShardedBamReader {
27552
27455
  async readAlignments(chr, start, end) {
27553
27456
 
27554
27457
  if (!this.bamReaders.hasOwnProperty(chr)) {
27555
- return new AlignmentContainer(chr, start, end)
27458
+ return new AlignmentContainer(chr, start, end, this.config)
27556
27459
  } else {
27557
27460
 
27558
27461
  let reader = this.bamReaders[chr];
@@ -27643,7 +27546,7 @@ BamWebserviceReader.prototype.readAlignments = function (chr, bpStart, bpEnd) {
27643
27546
 
27644
27547
  header.chrToIndex[queryChr];
27645
27548
 
27646
- alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold);
27549
+ alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, self.config);
27647
27550
 
27648
27551
  BamUtils.decodeSamRecords(sam, alignmentContainer, queryChr, bpStart, bpEnd, self.filter);
27649
27552
 
@@ -27897,7 +27800,7 @@ class HtsgetBamReader extends HtsgetReader {
27897
27800
  const ba = unbgzf(compressedData.buffer);
27898
27801
 
27899
27802
  const chrIdx = this.header.chrToIndex[chr];
27900
- const alignmentContainer = new AlignmentContainer(chr, start, end, this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27803
+ const alignmentContainer = new AlignmentContainer(chr, start, end, this.config);
27901
27804
  BamUtils.decodeBamRecords(ba, this.header.size, alignmentContainer, this.header.chrNames, chrIdx, start, end);
27902
27805
  alignmentContainer.finish();
27903
27806
 
@@ -28063,8 +27966,7 @@ class CramReader {
28063
27966
  const header = await this.getHeader();
28064
27967
  const queryChr = header.chrAliasTable.hasOwnProperty(chr) ? header.chrAliasTable[chr] : chr;
28065
27968
  const chrIdx = header.chrToIndex[queryChr];
28066
- const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd,
28067
- this.samplingWindowSize, this.samplingDepth, this.pairsSupported, this.alleleFreqThreshold);
27969
+ const alignmentContainer = new AlignmentContainer(chr, bpStart, bpEnd, this.config);
28068
27970
 
28069
27971
  if (chrIdx === undefined) {
28070
27972
  return alignmentContainer
@@ -28680,7 +28582,7 @@ Ga4ghAlignmentReader.prototype.readAlignments = function (chr, bpStart, bpEnd) {
28680
28582
  "pageSize": "10000"
28681
28583
  },
28682
28584
  decode: decodeGa4ghReads,
28683
- results: new AlignmentContainer(chr, bpStart, bpEnd, self.samplingWindowSize, self.samplingDepth, self.pairsSupported, self.alleleFreqThreshold)
28585
+ results: new AlignmentContainer(chr, bpStart, bpEnd, self.config)
28684
28586
  })
28685
28587
  })
28686
28588
 
@@ -29013,7 +28915,6 @@ class BamSource {
29013
28915
 
29014
28916
  this.config = config;
29015
28917
  this.genome = genome;
29016
- this.alignmentContainer = undefined;
29017
28918
 
29018
28919
  if (isDataURL(config.url)) {
29019
28920
  if ("cram" === config.format) {
@@ -29061,19 +28962,11 @@ class BamSource {
29061
28962
  }
29062
28963
 
29063
28964
  setViewAsPairs(bool) {
29064
-
29065
- if (this.viewAsPairs !== bool) {
29066
- this.viewAsPairs = bool;
29067
- // if (this.alignmentContainer) {
29068
- // this.alignmentContainer.setViewAsPairs(bool);
29069
- // }
29070
- }
28965
+ this.viewAsPairs = bool;
29071
28966
  }
29072
28967
 
29073
28968
  setShowSoftClips(bool) {
29074
- if (this.showSoftClips !== bool) {
29075
- this.showSoftClips = bool;
29076
- }
28969
+ this.showSoftClips = bool;
29077
28970
  }
29078
28971
 
29079
28972
  async getAlignments(chr, bpStart, bpEnd) {
@@ -29081,33 +28974,28 @@ class BamSource {
29081
28974
  const genome = this.genome;
29082
28975
  const showSoftClips = this.showSoftClips;
29083
28976
 
29084
- if (this.alignmentContainer && this.alignmentContainer.contains(chr, bpStart, bpEnd)) {
29085
- return this.alignmentContainer
28977
+ const alignmentContainer = await this.bamReader.readAlignments(chr, bpStart, bpEnd);
28978
+ let alignments = alignmentContainer.alignments;
28979
+ if (!this.viewAsPairs) {
28980
+ alignments = unpairAlignments([{alignments: alignments}]);
28981
+ }
28982
+ const hasAlignments = alignments.length > 0;
28983
+ alignmentContainer.packedAlignmentRows = packAlignmentRows(alignments, alignmentContainer.start, alignmentContainer.end, showSoftClips);
29086
28984
 
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
- }
28985
+ this.alignmentContainer = alignmentContainer;
28986
+
28987
+ if (hasAlignments) {
28988
+ const sequence = await genome.sequence.getSequence(chr, alignmentContainer.start, alignmentContainer.end);
28989
+ if (sequence) {
28990
+ alignmentContainer.coverageMap.refSeq = sequence; // TODO -- fix this
28991
+ alignmentContainer.sequence = sequence; // TODO -- fix this
28992
+ return alignmentContainer
28993
+ } else {
28994
+ console.error("No sequence for: " + chr + ":" + alignmentContainer.start + "-" + alignmentContainer.end);
29108
28995
  }
29109
- return alignmentContainer
29110
28996
  }
28997
+ return alignmentContainer
28998
+
29111
28999
  }
29112
29000
  }
29113
29001
 
@@ -29136,6 +29024,10 @@ class BamSource {
29136
29024
  * THE SOFTWARE.
29137
29025
  */
29138
29026
 
29027
+ const fixColor = (colorString) => {
29028
+ return (colorString.indexOf(",") > 0 && !colorString.startsWith("rgb")) ?
29029
+ `rgb(${colorString})` : colorString
29030
+ };
29139
29031
 
29140
29032
  /**
29141
29033
  * A collection of properties and methods shared by all (or most) track types.
@@ -29180,8 +29072,8 @@ class TrackBase {
29180
29072
 
29181
29073
  this.order = config.order;
29182
29074
 
29183
- this.color = config.color;
29184
- this.altColor = config.altColor;
29075
+ if(config.color) this.color = fixColor(config.color);
29076
+ if(config.altColor) this.altColor = fixColor(config.altColor);
29185
29077
  if ("civic-ws" === config.sourceType) { // Ugly proxy for specialized track type
29186
29078
  this.defaultColor = "rgb(155,20,20)";
29187
29079
  } else {
@@ -29287,7 +29179,7 @@ class TrackBase {
29287
29179
  return state
29288
29180
  }
29289
29181
 
29290
- supportsWholeGenome() {
29182
+ get supportsWholeGenome() {
29291
29183
  return false
29292
29184
  }
29293
29185
 
@@ -29425,7 +29317,7 @@ class TrackBase {
29425
29317
 
29426
29318
  // We use the cached features rather than method to avoid async load. If the
29427
29319
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
29428
- if (!features) features = clickState.viewport.getCachedFeatures();
29320
+ if (!features) features = clickState.viewport.cachedFeatures;
29429
29321
 
29430
29322
  if (!features || features.length === 0) {
29431
29323
  return []
@@ -29929,8 +29821,7 @@ class Locus {
29929
29821
  }
29930
29822
 
29931
29823
  overlaps(locus) {
29932
- return locus.chr === this.chr
29933
- && !(locus.end < this.start || locus.start > this.end)
29824
+ return locus.chr === this.chr && !(locus.end < this.start || locus.start > this.end)
29934
29825
  }
29935
29826
 
29936
29827
  extend(l) {
@@ -31077,7 +30968,7 @@ class ChordSetManager {
31077
30968
  return this.tracks.find(t => name === t.name)
31078
30969
  }
31079
30970
 
31080
- getChordset(name) {
30971
+ getChordSet(name) {
31081
30972
  return this.chordSets.find(cs => name === cs.name)
31082
30973
  }
31083
30974
 
@@ -31195,9 +31086,8 @@ class CircularView {
31195
31086
  buttonContainer.appendChild(this.showControlsButton);
31196
31087
  this.showControlsButton.innerText = 'none' === this.controlPanel.style.display ? 'Show Controls' : 'Hide Controls';
31197
31088
  this.showControlsButton.addEventListener('click', (event) => {
31198
- const trackPanelRows = this.controlPanel.querySelectorAll('div');
31199
- if (trackPanelRows.length > 0) {
31200
-
31089
+ const panelRows = this.controlPanel.querySelectorAll('div');
31090
+ if (panelRows.length > 0) {
31201
31091
  if ('none' === this.controlPanel.style.display) {
31202
31092
  this.controlPanel.style.display = 'flex';
31203
31093
  event.target.innerText = 'Hide Controls';
@@ -31281,10 +31171,10 @@ class CircularView {
31281
31171
  hideShowButton.innerText = true === chordSet.visible ? 'Hide' : 'Show';
31282
31172
  hideShowButton.addEventListener('click', event => {
31283
31173
  if (true === chordSet.visible) {
31284
- this.hideTrack(chordSet.name);
31174
+ this.hideChordSet(chordSet.name);
31285
31175
  event.target.innerText = "Show";
31286
31176
  } else {
31287
- this.showTrack(chordSet.name);
31177
+ this.showChordSet(chordSet.name);
31288
31178
  event.target.innerText = "Hide";
31289
31179
  }
31290
31180
  });
@@ -31308,7 +31198,7 @@ class CircularView {
31308
31198
  color: chordSet.color,
31309
31199
  onChange: ({rgbaString}) => {
31310
31200
  colorPickerButton.style.backgroundColor = setAlpha(rgbaString, 1);
31311
- this.setTrackColor(chordSet.name, rgbaString);
31201
+ this.setColor(chordSet.name, rgbaString);
31312
31202
  alphaSlider.value = alphaToValue(getAlpha(chordSet.color));
31313
31203
  }
31314
31204
  };
@@ -31326,7 +31216,7 @@ class CircularView {
31326
31216
  alphaSlider.value = alphaToValue(getAlpha(chordSet.color));
31327
31217
  alphaSlider.oninput = () => {
31328
31218
  const v = valueToAlpha(alphaSlider.value);
31329
- this.setTrackColor(chordSet.name, setAlpha(chordSet.color, v));
31219
+ this.setColor(chordSet.name, setAlpha(chordSet.color, v));
31330
31220
  picker.setColor(chordSet.color);
31331
31221
  };
31332
31222
  row.appendChild(alphaSlider);
@@ -31346,11 +31236,13 @@ class CircularView {
31346
31236
  */
31347
31237
  setAssembly(igvGenome) {
31348
31238
 
31349
- if (this.genomeId === igvGenome.id) {
31239
+ const id = this.genomeId || guid();
31240
+
31241
+ if (this.genomeId === id) {
31350
31242
  return
31351
31243
  }
31352
31244
  this.chordManager.clearChords();
31353
- this.genomeId = igvGenome.id;
31245
+ this.genomeId = id;
31354
31246
  this.chrNames = new Set(igvGenome.chromosomes.map(chr => shortChrName$1(chr.name)));
31355
31247
 
31356
31248
  const regions = [];
@@ -31371,7 +31263,7 @@ class CircularView {
31371
31263
  this.assembly = {
31372
31264
  name: igvGenome.name,
31373
31265
  sequence: {
31374
- trackId: igvGenome.id,
31266
+ trackId: id,
31375
31267
  type: 'ReferenceSequenceTrack',
31376
31268
  adapter: {
31377
31269
  type: 'FromConfigSequenceAdapter',
@@ -31412,7 +31304,7 @@ class CircularView {
31412
31304
 
31413
31305
  addChords(newChords, options = {}) {
31414
31306
 
31415
- const tmp = options.track || options.name || "*";
31307
+ const tmp = options.name || options.track || "*";
31416
31308
  const trackName = tmp.split(' ')[0].replaceAll("%20", " ");
31417
31309
  const chordSetName = tmp.replaceAll("%20", " ");
31418
31310
 
@@ -31465,21 +31357,6 @@ class CircularView {
31465
31357
  this.viewState.pluginManager.rootModel.session.clearSelection();
31466
31358
  }
31467
31359
 
31468
- getFeature(featureId) {
31469
-
31470
- // TODO -- broken
31471
- // const display = this.viewState.pluginManager.rootModel.session.view.tracks[0].displays[0]
31472
- // const feature = display.data.features.get(featureId)
31473
- // return feature;
31474
-
31475
- const features = [...this.viewState.config.tracks[0].adapter.features.value];
31476
- for (let f of features) {
31477
- if (featureId === f.uniqueId) {
31478
- return f
31479
- }
31480
- }
31481
- }
31482
-
31483
31360
  /**
31484
31361
  * Deprecated, use "visible" property
31485
31362
  */
@@ -31502,23 +31379,23 @@ class CircularView {
31502
31379
  this.parent.style.display = isVisible ? 'block' : 'none';
31503
31380
  }
31504
31381
 
31505
- hideTrack(trackName) {
31506
- let track = this.getTrack(trackName);
31507
- if (track) {
31508
- track.visible = false;
31382
+ hideChordSet(trackName) {
31383
+ let cs = this.getChordSet(trackName);
31384
+ if (cs) {
31385
+ cs.visible = false;
31509
31386
  this.render();
31510
31387
  } else {
31511
31388
  console.warn(`No track with name: ${name}`);
31512
31389
  }
31513
31390
  }
31514
31391
 
31515
- showTrack(trackName) {
31516
- let track = this.getTrack(trackName);
31517
- if (track) {
31518
- track.visible = true;
31392
+ showChordSet(name) {
31393
+ let cs = this.getChordSet(name);
31394
+ if (cs) {
31395
+ cs.visible = true;
31519
31396
  this.render();
31520
31397
  } else {
31521
- console.warn(`No track with name: ${trackName}`);
31398
+ console.warn(`No track with name: ${name}`);
31522
31399
  }
31523
31400
  }
31524
31401
 
@@ -31544,12 +31421,12 @@ class CircularView {
31544
31421
  this.render();
31545
31422
  }
31546
31423
 
31547
- getTrack(name) {
31548
- return this.groupByTrack ? this.chordManager.getTrack(name) : this.chordManager.getChordset(name)
31424
+ getChordSet(name) {
31425
+ return this.groupByTrack ? this.chordManager.getTrack(name) : this.chordManager.getChordSet(name)
31549
31426
  }
31550
31427
 
31551
- setTrackColor(name, color) {
31552
- const t = this.getTrack(name);
31428
+ setColor(name, color) {
31429
+ const t = this.getChordSet(name);
31553
31430
  if (t) {
31554
31431
  t.color = color;
31555
31432
  const trackID = t.id;
@@ -31576,8 +31453,6 @@ class CircularView {
31576
31453
  // Remove all children from possible previous renders. React might do this for us when we render, but just in case.
31577
31454
  ReactDOM.unmountComponentAtNode(this.container);
31578
31455
 
31579
-
31580
-
31581
31456
  const visibleChordSets =
31582
31457
  (this.groupByTrack ? this.chordManager.tracks : this.chordManager.chordSets).filter(t => t.visible);
31583
31458
 
@@ -31656,7 +31531,7 @@ function guid() {
31656
31531
 
31657
31532
  function embedCSS$1() {
31658
31533
 
31659
- const css = '.igv-circview-container {\n z-index: 2048;\n position: absolute;\n width: fit-content;\n height: fit-content;\n box-sizing: content-box;\n color: dimgray;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n background-color: white;\n border-color: dimgray;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-circview-toolbar {\n position: relative;\n width: 100%;\n height: 32px;\n background-color: lightgrey;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n\n.igv-circview-toolbar-button-container {\n height: 100%;\n width: fit-content;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-toolbar-button-container > div {\n margin: 4px;\n}\n\n.igv-circview-track-panel {\n z-index: 1024;\n position: absolute;\n top: 33px;\n left: 0;\n width: 100%;\n height: fit-content;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n.igv-circview-track-panel > div {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-track-panel > div > div {\n margin: 4px;\n}\n\n.igv-circview-swatch-button {\n cursor: pointer;\n padding: 5px;\n width: 8px;\n height: 8px;\n border: 1px solid #8d8b8b;\n border-radius: 16px;\n}\n\n.igv-circview-button {\n cursor: pointer;\n padding: 5px;\n color: #444;\n vertical-align: middle;\n text-align: center;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n border: 1px solid #8d8b8b;\n border-radius: 4px;\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.2);\n}\n\n.igv-circview-button:hover {\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n.igv-circview-button:active {\n color: #007bff;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n/*# sourceMappingURL=circular-view.css.map */\n';
31534
+ const css = '.igv-circview-container {\n z-index: 2048;\n width: fit-content;\n height: fit-content;\n box-sizing: content-box;\n color: dimgray;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n background-color: white;\n border-color: dimgray;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-circview-toolbar {\n position: relative;\n width: 100%;\n height: 32px;\n background-color: lightgrey;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n\n.igv-circview-toolbar-button-container {\n height: 100%;\n width: fit-content;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-toolbar-button-container > div {\n margin: 4px;\n}\n\n.igv-circview-track-panel {\n z-index: 1024;\n position: absolute;\n top: 33px;\n left: 0;\n width: 100%;\n height: fit-content;\n border-bottom-style: solid;\n border-bottom-color: dimgray;\n border-bottom-width: thin;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n}\n.igv-circview-track-panel > div {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-circview-track-panel > div > div {\n margin: 4px;\n}\n\n.igv-circview-swatch-button {\n cursor: pointer;\n padding: 5px;\n width: 8px;\n height: 8px;\n border: 1px solid #8d8b8b;\n border-radius: 16px;\n}\n\n.igv-circview-button {\n cursor: pointer;\n padding: 5px;\n color: #444;\n vertical-align: middle;\n text-align: center;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n border: 1px solid #8d8b8b;\n border-radius: 4px;\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.2);\n}\n\n.igv-circview-button:hover {\n background: #efefef;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n.igv-circview-button:active {\n color: #007bff;\n box-shadow: 0 0 5px -1px rgba(0, 0, 0, 0.6);\n}\n\n/*# sourceMappingURL=circular-view.css.map */\n';
31660
31535
 
31661
31536
  const style = document.createElement('style');
31662
31537
  style.setAttribute('type', 'text/css');
@@ -31695,27 +31570,45 @@ const makePairedAlignmentChords = (alignments) => {
31695
31570
 
31696
31571
  const chords = [];
31697
31572
  for (let a of alignments) {
31698
- const mate = a.mate;
31699
- if (mate && mate.chr && mate.position) {
31700
- chords.push({
31701
- uniqueId: a.readName,
31702
- refName: shortChrName(a.chr),
31703
- start: a.start,
31704
- end: a.end,
31705
- mate: {
31706
- refName: shortChrName(mate.chr),
31707
- start: mate.position - 1,
31708
- end: mate.position,
31709
- }
31710
- });
31573
+
31574
+ if(a.paired) {
31575
+ if(a.firstAlignment && a.secondAlignment) {
31576
+ chords.push({
31577
+ uniqueId: a.readName,
31578
+ refName: shortChrName(a.firstAlignment.chr),
31579
+ start: a.firstAlignment.start,
31580
+ end: a.firstAlignment.end,
31581
+ mate: {
31582
+ refName: shortChrName(a.secondAlignment.chr),
31583
+ start: a.secondAlignment.start,
31584
+ end: a.secondAlignment.end,
31585
+ }
31586
+ });
31587
+ }
31588
+ }
31589
+ else {
31590
+ const mate = a.mate;
31591
+ if (mate && mate.chr && mate.position) {
31592
+ chords.push({
31593
+ uniqueId: a.readName,
31594
+ refName: shortChrName(a.chr),
31595
+ start: a.start,
31596
+ end: a.end,
31597
+ mate: {
31598
+ refName: shortChrName(mate.chr),
31599
+ start: mate.position - 1,
31600
+ end: mate.position,
31601
+ }
31602
+ });
31603
+ }
31711
31604
  }
31712
31605
  }
31713
31606
  return chords
31714
31607
  };
31715
31608
 
31716
31609
  const makeSupplementalAlignmentChords = (alignments) => {
31717
- const chords = [];
31718
- for (let a of alignments) {
31610
+
31611
+ const makeChords = (a) => {
31719
31612
  const sa = a.tags()['SA'];
31720
31613
  const supAl = createSupplementaryAlignments(sa);
31721
31614
  let n = 0;
@@ -31734,6 +31627,18 @@ const makeSupplementalAlignmentChords = (alignments) => {
31734
31627
  });
31735
31628
  }
31736
31629
  }
31630
+ };
31631
+
31632
+ const chords = [];
31633
+ for (let a of alignments) {
31634
+ if(a.paired) {
31635
+ makeChords(a.firstAlignment);
31636
+ if(a.secondAlignment) {
31637
+ makeChords(a.secondAlignment);
31638
+ }
31639
+ } else {
31640
+ makeChords(a);
31641
+ }
31737
31642
  }
31738
31643
  return chords
31739
31644
  };
@@ -31807,6 +31712,22 @@ function makeCircViewChromosomes(genome) {
31807
31712
  return regions
31808
31713
  }
31809
31714
 
31715
+ function sendChords(chords, track, refFrame, alpha) {
31716
+
31717
+ const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? track.color : getChrColor(refFrame.chr), alpha);
31718
+ const trackColor = IGVColor.addAlpha(track.color || 'rgb(0,0,255)', alpha);
31719
+
31720
+ // name the chord set to include locus and filtering information
31721
+ const encodedName = track.name.replaceAll(' ', '%20');
31722
+ const chordSetName = "all" === refFrame.chr ? encodedName :
31723
+ `${encodedName} ${refFrame.chr}:${refFrame.start}-${refFrame.end}`;
31724
+ track.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor});
31725
+
31726
+ // show circular view if hidden
31727
+ if(!track.browser.circularViewVisible) track.browser.circularViewVisible = true;
31728
+
31729
+ }
31730
+
31810
31731
 
31811
31732
  function createCircularView(el, browser) {
31812
31733
 
@@ -31816,42 +31737,105 @@ function createCircularView(el, browser) {
31816
31737
 
31817
31738
  const f1 = feature.data;
31818
31739
  const f2 = f1.mate;
31819
- const flanking = 2000;
31740
+ addFrameForFeature(f1);
31741
+ addFrameForFeature(f2);
31820
31742
 
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});
31743
+ function addFrameForFeature(feature) {
31823
31744
 
31824
- let loci;
31745
+ feature.chr = browser.genome.getChromosomeName(feature.refName);
31746
+ let frameFound = false;
31747
+ for (let referenceFrame of browser.referenceFrameList) {
31748
+ const l = Locus.fromLocusString(referenceFrame.getLocusString());
31749
+ if (l.contains(feature)) {
31750
+ frameFound = true;
31751
+ break
31752
+ } else if (l.overlaps(feature)) {
31753
+ referenceFrame.extend(feature);
31754
+ frameFound = true;
31755
+ break
31756
+ }
31757
+ }
31758
+ if (!frameFound) {
31759
+ const flanking = 2000;
31760
+ const center = (feature.start + feature.end) / 2;
31761
+ browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
31825
31762
 
31826
- // If there is overlap with current loci
31763
+ }
31764
+ }
31765
+ }
31766
+ });
31827
31767
 
31828
- loci = browser.currentLoci().map(str => Locus.fromLocusString(str));
31768
+ return circularView
31769
+ }
31770
+
31771
+ class PairedEndStats {
31772
+
31773
+ constructor(alignments, {minTLENPercentile, maxTLENPercentile}) {
31774
+ this.totalCount = 0;
31775
+ this.frCount = 0;
31776
+ this.rfCount = 0;
31777
+ this.ffCount = 0;
31778
+ this.sumF = 0;
31779
+ this.sumF2 = 0;
31780
+ this.lp = minTLENPercentile === undefined ? 0.1 : minTLENPercentile;
31781
+ this.up = maxTLENPercentile === undefined ? 99.5 : maxTLENPercentile;
31782
+ this.isizes = [];
31783
+ this.compute(alignments);
31784
+ }
31829
31785
 
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);
31786
+ compute(alignments) {
31787
+
31788
+ for (let alignment of alignments) {
31789
+ if (alignment.isProperPair()) {
31790
+ var tlen = Math.abs(alignment.fragmentLength);
31791
+ this.sumF += tlen;
31792
+ this.sumF2 += tlen * tlen;
31793
+ this.isizes.push(tlen);
31794
+
31795
+ var po = alignment.pairOrientation;
31796
+
31797
+ if (typeof po === "string" && po.length === 4) {
31798
+ var tmp = '' + po.charAt(0) + po.charAt(2);
31799
+ switch (tmp) {
31800
+ case 'FF':
31801
+ case 'RR':
31802
+ this.ffCount++;
31803
+ break
31804
+ case "FR":
31805
+ this.frCount++;
31806
+ break
31807
+ case"RF":
31808
+ this.rfCount++;
31839
31809
  }
31840
31810
  }
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];
31811
+ this.totalCount++;
31847
31812
  }
31848
-
31849
- const searchString = loci.map(l => l.getLocusString()).join(" ");
31850
- browser.search(searchString);
31851
31813
  }
31814
+
31815
+ if (this.ffCount / this.totalCount > 0.9) this.orienation = "ff";
31816
+ else if (this.frCount / this.totalCount > 0.9) this.orienation = "fr";
31817
+ else if (this.rfCount / this.totalCount > 0.9) this.orienation = "rf";
31818
+
31819
+ this.minTLEN = this.lp === 0 ? 0 : percentile(this.isizes, this.lp);
31820
+ this.maxTLEN = percentile(this.isizes, this.up);
31821
+
31822
+ // var fMean = this.sumF / this.totalCount
31823
+ // var stdDev = Math.sqrt((this.totalCount * this.sumF2 - this.sumF * this.sumF) / (this.totalCount * this.totalCount))
31824
+ // this.minTLEN = fMean - 3 * stdDev
31825
+ // this.maxTLEN = fMean + 3 * stdDev
31826
+
31827
+ }
31828
+ }
31829
+
31830
+ function percentile(array, p) {
31831
+
31832
+ if (array.length === 0) return undefined
31833
+ var k = Math.floor(array.length * (p / 100));
31834
+ array.sort(function (a, b) {
31835
+ return a - b
31852
31836
  });
31837
+ return array[k]
31853
31838
 
31854
- return circularView
31855
31839
  }
31856
31840
 
31857
31841
  /*
@@ -31918,8 +31902,6 @@ class BAMTrack extends TrackBase {
31918
31902
  this.showMismatches = false !== config.showMismatches;
31919
31903
  this.color = config.color;
31920
31904
  this.coverageColor = config.coverageColor;
31921
- this.minFragmentLength = config.minFragmentLength; // Optional, might be undefined
31922
- this.maxFragmentLength = config.maxFragmentLength || 1000;
31923
31905
 
31924
31906
  // The sort object can be an array in the case of multi-locus view, however if multiple sort positions
31925
31907
  // are present for a given reference frame the last one will take precedence
@@ -31947,12 +31929,24 @@ class BAMTrack extends TrackBase {
31947
31929
  return this._height
31948
31930
  }
31949
31931
 
31932
+ get minTemplateLength() {
31933
+ const configMinTLEN = this.config.minTLEN !== undefined ? this.config.minTLEN : this.config.minFragmentLength;
31934
+ return (configMinTLEN !== undefined) ? configMinTLEN :
31935
+ this._pairedEndStats ? this._pairedEndStats.minTLEN : 0
31936
+ }
31937
+
31938
+ get maxTemplateLength() {
31939
+ const configMaxTLEN = this.config.maxTLEN !== undefined ? this.config.maxTLEN : this.config.maxFragmentLength;
31940
+ return (configMaxTLEN !== undefined) ? configMaxTLEN :
31941
+ this._pairedEndStats ? this._pairedEndStats.maxTLEN : 1000
31942
+ }
31943
+
31950
31944
  sort(options) {
31951
31945
  options = this.assignSort(options);
31952
31946
 
31953
31947
  for (let vp of this.trackView.viewports) {
31954
31948
  if (vp.containsPosition(options.chr, options.position)) {
31955
- const alignmentContainer = vp.getCachedFeatures();
31949
+ const alignmentContainer = vp.cachedFeatures;
31956
31950
  if (alignmentContainer) {
31957
31951
  sortAlignmentRows(options, alignmentContainer);
31958
31952
  vp.repaint();
@@ -31987,14 +31981,13 @@ class BAMTrack extends TrackBase {
31987
31981
 
31988
31982
  const alignmentContainer = await this.featureSource.getAlignments(chr, bpStart, bpEnd);
31989
31983
 
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;
31984
+ if (alignmentContainer.paired && !this._pairedEndStats && !this.config.maxFragmentLength) {
31985
+ const pairedEndStats = new PairedEndStats(alignmentContainer.alignments, this.config);
31986
+ if (pairedEndStats.totalCount > 99) {
31987
+ this._pairedEndStats = pairedEndStats;
31996
31988
  }
31997
31989
  }
31990
+ alignmentContainer.alignments = undefined; // Don't need to hold onto these anymore
31998
31991
 
31999
31992
  const sort = this.sortObject;
32000
31993
  if (sort) {
@@ -32091,7 +32084,7 @@ class BAMTrack extends TrackBase {
32091
32084
  if (this.alignmentTrack.hasPairs) {
32092
32085
  colorByMenuItems.push({key: 'firstOfPairStrand', label: 'first-of-pair strand'});
32093
32086
  colorByMenuItems.push({key: 'pairOrientation', label: 'pair orientation'});
32094
- colorByMenuItems.push({key: 'fragmentLength', label: 'insert size (TLEN)'});
32087
+ colorByMenuItems.push({key: 'tlen', label: 'insert size (TLEN)'});
32095
32088
  colorByMenuItems.push({key: 'unexpectedPair', label: 'pair orientation & insert size (TLEN)'});
32096
32089
  }
32097
32090
  const tagLabel = 'tag' + (this.alignmentTrack.colorByTag ? ' (' + this.alignmentTrack.colorByTag + ')' : '');
@@ -32197,32 +32190,17 @@ class BAMTrack extends TrackBase {
32197
32190
  });
32198
32191
  }
32199
32192
 
32200
- // Experimental JBrowse feature
32201
- if (this.browser.circularView && true === this.browser.circularViewVisible &&
32193
+ // Add chords to JBrowse circular view, if present
32194
+ if (this.browser.circularView &&
32202
32195
  (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
32203
32196
  menuItems.push('<hr/>');
32204
32197
  if (this.alignmentTrack.hasPairs) {
32205
32198
  menuItems.push({
32206
32199
  label: 'Add discordant pairs to circular view',
32207
32200
  click: () => {
32208
- const maxFragmentLength = this.maxFragmentLength;
32209
- const inView = [];
32210
32201
  for (let viewport of this.trackView.viewports) {
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
- }
32202
+ this.addPairedChordsForViewport(viewport);
32221
32203
  }
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});
32226
32204
  }
32227
32205
  });
32228
32206
  }
@@ -32230,19 +32208,9 @@ class BAMTrack extends TrackBase {
32230
32208
  menuItems.push({
32231
32209
  label: 'Add split reads to circular view',
32232
32210
  click: () => {
32233
- const inView = [];
32234
32211
  for (let viewport of this.trackView.viewports) {
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
- }
32212
+ this.addSplitChordsForViewport(viewport);
32242
32213
  }
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});
32246
32214
  }
32247
32215
  });
32248
32216
  }
@@ -32354,7 +32322,7 @@ class BAMTrack extends TrackBase {
32354
32322
  }
32355
32323
 
32356
32324
  getCachedAlignmentContainers() {
32357
- return this.trackView.viewports.map(vp => vp.getCachedFeatures())
32325
+ return this.trackView.viewports.map(vp => vp.cachedFeatures)
32358
32326
  }
32359
32327
 
32360
32328
  get dataRange() {
@@ -32380,6 +32348,69 @@ class BAMTrack extends TrackBase {
32380
32348
  set autoscale(autoscale) {
32381
32349
  this.coverageTrack.autoscale = autoscale;
32382
32350
  }
32351
+
32352
+ /**
32353
+ * Add chords to the circular view for the given viewport, represented by its reference frame
32354
+ * @param refFrame
32355
+ */
32356
+ addPairedChordsForViewport(viewport) {
32357
+
32358
+ const maxTemplateLength = this.maxTemplateLength;
32359
+ const inView = [];
32360
+ const refFrame = viewport.referenceFrame;
32361
+ for (let a of viewport.cachedFeatures.allAlignments()) {
32362
+ if (a.end >= refFrame.start
32363
+ && a.start <= refFrame.end) {
32364
+ if (a.paired) {
32365
+ if (a.end - a.start > maxTemplateLength) {
32366
+ inView.push(a);
32367
+ }
32368
+ } else {
32369
+ if (a.mate
32370
+ && a.mate.chr
32371
+ && (a.mate.chr !== a.chr || Math.max(a.fragmentLength) > maxTemplateLength)) {
32372
+ inView.push(a);
32373
+ }
32374
+ }
32375
+ }
32376
+ }
32377
+ const chords = makePairedAlignmentChords(inView);
32378
+ sendChords(chords, this, refFrame, 0.02);
32379
+
32380
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
32381
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
32382
+ //
32383
+ // // name the chord set to include track name and locus
32384
+ // const encodedName = this.name.replaceAll(' ', '%20')
32385
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
32386
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
32387
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
32388
+ }
32389
+
32390
+ addSplitChordsForViewport(viewport) {
32391
+
32392
+ const inView = [];
32393
+ const refFrame = viewport.referenceFrame;
32394
+ for (let a of viewport.cachedFeatures.allAlignments()) {
32395
+
32396
+ const sa = a.hasTag('SA');
32397
+ if (a.end >= refFrame.start && a.start <= refFrame.end && sa) {
32398
+ inView.push(a);
32399
+ }
32400
+ }
32401
+
32402
+ const chords = makeSupplementalAlignmentChords(inView);
32403
+ sendChords(chords, this, refFrame, 0.02);
32404
+
32405
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.02)
32406
+ // const trackColor = IGVColor.addAlpha(this.color || 'rgb(0,0,255)', 0.02)
32407
+ //
32408
+ // // name the chord set to include track name and locus
32409
+ // const encodedName = this.name.replaceAll(' ', '%20')
32410
+ // const chordSetName = "all" === refFrame.chr ? encodedName :
32411
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end}`
32412
+ // this.browser.circularView.addChords(chords, {name: chordSetName, color: chordSetColor, trackColor: trackColor})
32413
+ }
32383
32414
  }
32384
32415
 
32385
32416
 
@@ -32500,7 +32531,7 @@ class CoverageTrack {
32500
32531
 
32501
32532
  getClickedObject(clickState) {
32502
32533
 
32503
- let features = clickState.viewport.getCachedFeatures();
32534
+ let features = clickState.viewport.cachedFeatures;
32504
32535
  if (!features || features.length === 0) return
32505
32536
 
32506
32537
  const genomicLocation = Math.floor(clickState.genomicLocation);
@@ -32576,8 +32607,8 @@ class AlignmentTrack {
32576
32607
  this.skippedColor = config.skippedColor || "rgb(150, 170, 170)";
32577
32608
  this.pairConnectorColor = config.pairConnectorColor;
32578
32609
 
32579
- this.smallFragmentLengthColor = config.smallFragmentLengthColor || "rgb(0, 0, 150)";
32580
- this.largeFragmentLengthColor = config.largeFragmentLengthColor || "rgb(200, 0, 0)";
32610
+ this.smallTLENColor = config.smallTLENColor || config.smallFragmentLengthColor || "rgb(0, 0, 150)";
32611
+ this.largeTLENColor = config.largeTLENColor || config.largeFragmentLengthColor || "rgb(200, 0, 0)";
32581
32612
 
32582
32613
  this.pairOrientation = config.pairOrienation || 'fr';
32583
32614
  this.pairColors = {};
@@ -32585,7 +32616,7 @@ class AlignmentTrack {
32585
32616
  this.pairColors["RR"] = config.rrColor || "rgb(20, 50, 200)";
32586
32617
  this.pairColors["LL"] = config.llColor || "rgb(0, 150, 150)";
32587
32618
 
32588
- this.colorBy = config.colorBy || "pairOrientation";
32619
+ this.colorBy = config.colorBy || "unexpectedPair";
32589
32620
  this.colorByTag = config.colorByTag ? config.colorByTag.toUpperCase() : undefined;
32590
32621
  this.bamColorTag = config.bamColorTag === undefined ? "YC" : config.bamColorTag;
32591
32622
 
@@ -32692,7 +32723,7 @@ class AlignmentTrack {
32692
32723
  for (let alignment of alignmentRow.alignments) {
32693
32724
 
32694
32725
  this.hasPairs = this.hasPairs || alignment.isPaired();
32695
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
32726
+ if (this.browser.circularView) {
32696
32727
  // This is an expensive check, only do it if needed
32697
32728
  this.hasSupplemental = this.hasSupplemental || alignment.hasTag('SA');
32698
32729
  }
@@ -32977,7 +33008,7 @@ class AlignmentTrack {
32977
33008
  direction: direction
32978
33009
  };
32979
33010
  this.parent.sortObject = newSortObject;
32980
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
33011
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
32981
33012
  viewport.repaint();
32982
33013
  };
32983
33014
  list.push('<b>Sort by...</b>');
@@ -33007,7 +33038,7 @@ class AlignmentTrack {
33007
33038
  };
33008
33039
  this.sortByTag = tag;
33009
33040
  this.parent.sortObject = newSortObject;
33010
- sortAlignmentRows(newSortObject, viewport.getCachedFeatures());
33041
+ sortAlignmentRows(newSortObject, viewport.cachedFeatures);
33011
33042
  viewport.repaint();
33012
33043
  }
33013
33044
  }
@@ -33034,7 +33065,11 @@ class AlignmentTrack {
33034
33065
  const referenceFrame = clickState.viewport.referenceFrame;
33035
33066
  if (this.browser.genome.getChromosome(clickedAlignment.mate.chr)) {
33036
33067
  this.highlightedAlignmentReadNamed = clickedAlignment.readName;
33037
- this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame);
33068
+ //this.browser.presentMultiLocusPanel(clickedAlignment, referenceFrame)
33069
+ const bpWidth = referenceFrame.end - referenceFrame.start;
33070
+ const frameStart = clickedAlignment.mate.position - bpWidth / 2;
33071
+ const frameEnd = clickedAlignment.mate.position + bpWidth / 2;
33072
+ this.browser.addMultiLocusPanel(clickedAlignment.mate.chr, frameStart, frameEnd, referenceFrame);
33038
33073
  } else {
33039
33074
  Alert.presentAlert(`Reference does not contain chromosome: ${clickedAlignment.mate.chr}`);
33040
33075
  }
@@ -33047,10 +33082,7 @@ class AlignmentTrack {
33047
33082
  list.push({
33048
33083
  label: 'View read sequence',
33049
33084
  click: () => {
33050
- const alignment = clickedAlignment;
33051
- if (!alignment) return
33052
-
33053
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
33085
+ const seqstring = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
33054
33086
  if (!seqstring || "*" === seqstring) {
33055
33087
  Alert.presentAlert("Read sequence: *");
33056
33088
  } else {
@@ -33062,11 +33094,16 @@ class AlignmentTrack {
33062
33094
  if (isSecureContext()) {
33063
33095
  list.push({
33064
33096
  label: 'Copy read sequence',
33065
- click: () => {
33066
- const alignment = clickedAlignment;
33067
- if (!alignment) return
33068
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
33069
- navigator.clipboard.writeText(seqstring);
33097
+ click: async () => {
33098
+ const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
33099
+ try {
33100
+ //console.log(`seq: ${seq}`)
33101
+ await navigator.clipboard.writeText(seq);
33102
+ } catch (e) {
33103
+ console.error(e);
33104
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
33105
+ }
33106
+
33070
33107
  }
33071
33108
  });
33072
33109
  }
@@ -33076,25 +33113,12 @@ class AlignmentTrack {
33076
33113
  }
33077
33114
 
33078
33115
  // Experimental JBrowse feature
33079
- if (this.browser.circularView && true === this.browser.circularViewVisible
33080
- && (this.hasPairs || this.hasSupplemental)) {
33116
+ if (this.browser.circularView && (this.hasPairs || this.hasSupplemental)) {
33081
33117
  if (this.hasPairs) {
33082
33118
  list.push({
33083
33119
  label: 'Add discordant pairs to circular view',
33084
33120
  click: () => {
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});
33121
+ this.parent.addPairedChordsForViewport(viewport);
33098
33122
  }
33099
33123
  });
33100
33124
  }
@@ -33102,17 +33126,7 @@ class AlignmentTrack {
33102
33126
  list.push({
33103
33127
  label: 'Add split reads to circular view',
33104
33128
  click: () => {
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});
33129
+ this.parent.addSplitChordsForViewport(viewport);
33116
33130
  }
33117
33131
  });
33118
33132
  }
@@ -33131,7 +33145,7 @@ class AlignmentTrack {
33131
33145
 
33132
33146
  const showSoftClips = this.parent.showSoftClips;
33133
33147
 
33134
- let features = viewport.getCachedFeatures();
33148
+ let features = viewport.cachedFeatures;
33135
33149
  if (!features || features.length === 0) return
33136
33150
 
33137
33151
  let packedAlignmentRows = features.packedAlignmentRows;
@@ -33212,24 +33226,30 @@ class AlignmentTrack {
33212
33226
  case "pairOrientation":
33213
33227
 
33214
33228
  if (this.pairOrientation && alignment.pairOrientation) {
33215
- var oTypes = orientationTypes[this.pairOrientation];
33229
+ const oTypes = orientationTypes[this.pairOrientation];
33216
33230
  if (oTypes) {
33217
- var pairColor = this.pairColors[oTypes[alignment.pairOrientation]];
33218
- if (pairColor) color = pairColor;
33231
+ const pairColor = this.pairColors[oTypes[alignment.pairOrientation]];
33232
+ if (pairColor) {
33233
+ color = pairColor;
33234
+ break
33235
+ }
33219
33236
  }
33220
33237
  }
33221
33238
  if ("pairOrientation" === option) {
33222
33239
  break
33223
33240
  }
33224
33241
 
33242
+ case "tlen":
33225
33243
  case "fragmentLength":
33226
33244
 
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;
33245
+ if (alignment.mate && alignment.isMateMapped()) {
33246
+ if (alignment.mate.chr !== alignment.chr) {
33247
+ color = getChrColor(alignment.mate.chr);
33248
+ } else if (this.parent.minTemplateLength && Math.abs(alignment.fragmentLength) < this.parent.minTemplateLength) {
33249
+ color = this.smallTLENColor;
33250
+ } else if (this.parent.maxTemplateLength && Math.abs(alignment.fragmentLength) > this.parent.maxTemplateLength) {
33251
+ color = this.largeTLENColor;
33252
+ }
33233
33253
  }
33234
33254
  break
33235
33255
 
@@ -33271,11 +33291,6 @@ function sortAlignmentRows(options, alignmentContainer) {
33271
33291
  return true === direction ? i : -i
33272
33292
  });
33273
33293
 
33274
- // For debugging
33275
- // for(let r of alignmentContainer.packedAlignmentRows) {
33276
- // console.log(r.score);
33277
- // }
33278
-
33279
33294
  }
33280
33295
 
33281
33296
  function shadedBaseColor(qual, baseColor) {
@@ -33435,7 +33450,7 @@ class RulerViewport extends TrackViewport {
33435
33450
 
33436
33451
  this.$rulerLabel.click(async () => {
33437
33452
 
33438
- await this.browser.selectMultiLocusPanel(this.referenceFrame);
33453
+ await this.browser.gotoMultilocusPanel(this.referenceFrame);
33439
33454
 
33440
33455
  // const removals = this.browser.referenceFrameList.filter(r => this.referenceFrame !== r)
33441
33456
  // for (let referenceFrame of removals) {
@@ -33558,7 +33573,9 @@ class RulerViewport extends TrackViewport {
33558
33573
  currentViewport = this;
33559
33574
  this.$tooltip.show();
33560
33575
  } else if (currentViewport.guid !== this.guid) {
33561
- currentViewport.$tooltip.hide();
33576
+ if (currentViewport.$tooltip) {
33577
+ currentViewport.$tooltip.hide();
33578
+ }
33562
33579
  this.$tooltip.show();
33563
33580
  currentViewport = this;
33564
33581
  } else {
@@ -33585,7 +33602,9 @@ class RulerViewport extends TrackViewport {
33585
33602
 
33586
33603
  // hide tooltip when movement stops
33587
33604
  clearTimeout(timer);
33588
- timer = setTimeout(() => this.$tooltip.hide(), toolTipTimeout);
33605
+ timer = setTimeout(() => {
33606
+ if (this.$tooltip) this.$tooltip.hide();
33607
+ }, toolTipTimeout);
33589
33608
 
33590
33609
  }
33591
33610
 
@@ -33604,69 +33623,6 @@ class RulerViewport extends TrackViewport {
33604
33623
 
33605
33624
  }
33606
33625
 
33607
- const viewportColumnManager =
33608
- {
33609
- createColumns: (columnContainer, count) => {
33610
-
33611
- for (let i = 0; i < count; i++) {
33612
- if (0 === i) {
33613
- createColumn(columnContainer, 'igv-column');
33614
- } else {
33615
- columnContainer.appendChild(div$1({class: 'igv-column-shim'}));
33616
- createColumn(columnContainer, 'igv-column');
33617
- }
33618
- }
33619
-
33620
- },
33621
-
33622
- removeColumnAtIndex: (i, column) => {
33623
- const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
33624
- column.remove();
33625
- shim.remove();
33626
- },
33627
-
33628
- insertAfter: referenceElement => {
33629
-
33630
- const shim = div$1({class: 'igv-column-shim'});
33631
- insertElementAfter(shim, referenceElement);
33632
-
33633
- const column = div$1({class: 'igv-column'});
33634
- insertElementAfter(column, shim);
33635
-
33636
- return column
33637
- },
33638
-
33639
- insertBefore: (referenceElement, count) => {
33640
-
33641
- for (let i = 0; i < count; i++) {
33642
-
33643
- const column = div$1({class: 'igv-column'});
33644
- insertElementBefore(column, referenceElement);
33645
-
33646
- if (count > 1 && i > 0) {
33647
- const columnShim = div$1({class: 'igv-column-shim'});
33648
- insertElementBefore(columnShim, column);
33649
- }
33650
-
33651
- }
33652
-
33653
- },
33654
-
33655
- indexOfColumn: (columnContainer, column) => {
33656
-
33657
- const allColumns = columnContainer.querySelectorAll('.igv-column');
33658
-
33659
- for (let i = 0; i < allColumns.length; i++) {
33660
- const c = allColumns[ i ];
33661
- if (c === column) {
33662
- return i
33663
- }
33664
- }
33665
-
33666
- return undefined
33667
- },
33668
- };
33669
-
33670
33626
  /*
33671
33627
  * The MIT License (MIT)
33672
33628
  *
@@ -33700,61 +33656,28 @@ class IdeogramViewport extends TrackViewport {
33700
33656
 
33701
33657
  initializationHelper() {
33702
33658
 
33703
- this.$ideogramCanvas = $$1('<canvas>', {class: 'igv-ideogram-canvas'});
33704
- this.$ideogramCanvas.insertBefore(this.$canvas);
33705
-
33706
- const canvas = this.$ideogramCanvas.get(0);
33707
- this.ideogram_ctx = canvas.getContext('2d');
33708
-
33709
- this.$canvas.remove();
33710
- this.canvas = undefined;
33711
- this.ctx = undefined;
33659
+ this.canvas = document.createElement('canvas');
33660
+ this.canvas.className = 'igv-ideogram-canvas';
33661
+ this.$content.append($$1(this.canvas));
33662
+ this.ideogram_ctx = this.canvas.getContext('2d');
33712
33663
 
33713
33664
  this.addMouseHandlers();
33714
-
33715
33665
  }
33716
33666
 
33717
33667
  addMouseHandlers() {
33718
- this.addBrowserObserver();
33719
33668
  this.addViewportClickHandler(this.$viewport.get(0));
33720
33669
  }
33721
33670
 
33722
- removeMouseHandlers() {
33723
- this.removeBrowserObserver();
33724
- this.removeViewportClickHandler(this.$viewport.get(0));
33725
-
33726
- }
33727
-
33728
- addBrowserObserver() {
33729
-
33730
- function observerHandler(referenceFrameList) {
33731
- const column = this.$viewport.get(0).parentElement;
33732
- if (null !== column) {
33733
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column);
33734
- // console.log(`ideogram-viewport - locus-change-handler index(${ index }) ${ referenceFrameList[ index ].getLocusString() } ${ Date.now() } `)
33735
- this.update(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height(), referenceFrameList[ index ]);
33736
- }
33737
- }
33738
-
33739
- this.boundObserverHandler = observerHandler.bind(this);
33740
- this.browser.on('locuschange', this.boundObserverHandler);
33741
- }
33742
-
33743
- removeBrowserObserver() {
33744
- this.browser.off('locuschange', this.boundObserverHandler);
33745
- }
33746
-
33747
33671
  addViewportClickHandler(viewport) {
33748
33672
 
33749
- function clickHandler(event) {
33673
+ this.boundClickHandler = clickHandler.bind(this);
33674
+ viewport.addEventListener('click', this.boundClickHandler);
33750
33675
 
33751
- const column = viewport.parentElement;
33752
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column);
33753
- const referenceFrame = this.browser.referenceFrameList[ index ];
33676
+ function clickHandler(event) {
33754
33677
 
33755
33678
  const {xNormalized, width} = translateMouseCoordinates$1(event, this.ideogram_ctx.canvas);
33756
- const {bpLength} = this.browser.genome.getChromosome(referenceFrame.chr);
33757
- const locusLength = referenceFrame.bpPerPixel * width;
33679
+ const {bpLength} = this.browser.genome.getChromosome(this.referenceFrame.chr);
33680
+ const locusLength = this.referenceFrame.bpPerPixel * width;
33758
33681
  const chrCoveragePercentage = locusLength / bpLength;
33759
33682
 
33760
33683
  let xPercentage = xNormalized;
@@ -33769,21 +33692,14 @@ class IdeogramViewport extends TrackViewport {
33769
33692
  const ss = Math.round((xPercentage - (chrCoveragePercentage / 2.0)) * bpLength);
33770
33693
  const ee = Math.round((xPercentage + (chrCoveragePercentage / 2.0)) * bpLength);
33771
33694
 
33772
- referenceFrame.start = ss;
33773
- referenceFrame.end = ee;
33774
- referenceFrame.bpPerPixel = (ee - ss) / width;
33695
+ this.referenceFrame.start = ss;
33696
+ this.referenceFrame.end = ee;
33697
+ this.referenceFrame.bpPerPixel = (ee - ss) / width;
33775
33698
 
33776
- this.browser.updateViews(referenceFrame, this.browser.trackViews, true);
33699
+ this.browser.updateViews(this.referenceFrame, this.browser.trackViews, true);
33777
33700
 
33778
33701
  }
33779
33702
 
33780
- this.boundClickHandler = clickHandler.bind(this);
33781
- viewport.addEventListener('click', this.boundClickHandler);
33782
-
33783
- }
33784
-
33785
- removeViewportClickHandler(viewport) {
33786
- viewport.removeEventListener('click', this.boundClickHandler);
33787
33703
  }
33788
33704
 
33789
33705
  setWidth(width) {
@@ -33804,10 +33720,20 @@ class IdeogramViewport extends TrackViewport {
33804
33720
  context.restore();
33805
33721
  }
33806
33722
 
33807
- update(context, pixelWidth, pixelHeight, referenceFrame) {
33808
- this.$canvas.hide();
33809
- IGVGraphics.configureHighDPICanvas(context, pixelWidth, pixelHeight);
33810
- this.trackView.track.draw({ context, referenceFrame, pixelWidth, pixelHeight });
33723
+ repaint() {
33724
+ this.draw({referenceFrame: this.referenceFrame});
33725
+ }
33726
+
33727
+ draw({referenceFrame}) {
33728
+
33729
+ IGVGraphics.configureHighDPICanvas(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height());
33730
+
33731
+ this.trackView.track.draw({
33732
+ context: this.ideogram_ctx,
33733
+ referenceFrame,
33734
+ pixelWidth: this.$viewport.width(),
33735
+ pixelHeight: this.$viewport.height()
33736
+ });
33811
33737
  }
33812
33738
 
33813
33739
  startSpinner() {
@@ -33848,7 +33774,7 @@ function createViewport(trackView, column, referenceFrame, width) {
33848
33774
 
33849
33775
  if ('ruler' === trackView.track.type) {
33850
33776
  return new RulerViewport(trackView, column, referenceFrame, width)
33851
- } else if ('ideogram' === trackView.track.type) {
33777
+ } else if ('ideogram' === trackView.track.id) {
33852
33778
  return new IdeogramViewport(trackView, column, referenceFrame, width)
33853
33779
  } else {
33854
33780
  return new TrackViewport(trackView, column, referenceFrame, width)
@@ -34024,7 +33950,7 @@ class SampleNameViewport {
34024
33950
  for (let {sampleNameViewport} of this.browser.trackViews) {
34025
33951
  sampleNameViewport.setWidth(this.browser.sampleNameViewportWidth);
34026
33952
  }
34027
- this.browser.resize();
33953
+ this.browser.layoutChange();
34028
33954
  }
34029
33955
  };
34030
33956
 
@@ -34371,15 +34297,10 @@ const colorPickerExclusionTypes = new Set(['ruler', 'sequence', 'ideogram']);
34371
34297
  class TrackView {
34372
34298
 
34373
34299
  constructor(browser, columnContainer, track) {
34374
-
34375
- this.namespace = `trackview-${guid$2()}`;
34376
-
34377
34300
  this.browser = browser;
34378
34301
  this.track = track;
34379
34302
  track.trackView = this;
34380
-
34381
34303
  this.addDOMToColumnContainer(browser, columnContainer, browser.referenceFrameList);
34382
-
34383
34304
  }
34384
34305
 
34385
34306
  /**
@@ -34403,7 +34324,7 @@ class TrackView {
34403
34324
  // Axis
34404
34325
  this.axis = this.createAxis(browser, this.track);
34405
34326
 
34406
- // Track Viewports
34327
+ // Create a viewport for each reference frame
34407
34328
  this.viewports = [];
34408
34329
  const viewportWidth = browser.calculateViewportWidth(referenceFrameList.length);
34409
34330
  const viewportColumns = columnContainer.querySelectorAll('.igv-column');
@@ -34476,7 +34397,6 @@ class TrackView {
34476
34397
 
34477
34398
  // Track Viewports
34478
34399
  for (let viewport of this.viewports) {
34479
- viewport.removeMouseHandlers();
34480
34400
  viewport.$viewport.remove();
34481
34401
  }
34482
34402
 
@@ -34542,19 +34462,14 @@ class TrackView {
34542
34462
  if (false === colorPickerExclusionTypes.has(this.track.type)) {
34543
34463
 
34544
34464
  const trackColors = [];
34545
-
34546
34465
  const color = this.track.color || this.track.defaultColor;
34547
-
34548
34466
  if (isString$3(color)) {
34549
34467
  trackColors.push(color);
34550
34468
  }
34551
-
34552
34469
  if (this.track.altColor && isString$3(this.track.altColor)) {
34553
34470
  trackColors.push(this.track.altColor);
34554
34471
  }
34555
-
34556
34472
  const defaultColors = trackColors.map(c => c.startsWith("#") ? c : c.startsWith("rgb(") ? IGVColor.rgbToHex(c) : IGVColor.colorNameToHex(c));
34557
-
34558
34473
  const colorHandlers =
34559
34474
  {
34560
34475
  color: color => {
@@ -34567,7 +34482,6 @@ class TrackView {
34567
34482
  }
34568
34483
 
34569
34484
  };
34570
-
34571
34485
  this.browser.genericColorPicker.configure(defaultColors, colorHandlers);
34572
34486
  this.browser.genericColorPicker.setActiveColorHandler(key);
34573
34487
  this.browser.genericColorPicker.show();
@@ -34581,7 +34495,6 @@ class TrackView {
34581
34495
  if (this.track.minHeight) {
34582
34496
  newHeight = Math.max(this.track.minHeight, newHeight);
34583
34497
  }
34584
-
34585
34498
  if (this.track.maxHeight) {
34586
34499
  newHeight = Math.min(this.track.maxHeight, newHeight);
34587
34500
  }
@@ -34601,9 +34514,8 @@ class TrackView {
34601
34514
 
34602
34515
  this.sampleNameViewport.viewport.style.height = `${newHeight}px`;
34603
34516
 
34604
- // If the track does not manage its own content height set it here
34517
+ // If the track does not manage its own content height set it equal to the viewport height here
34605
34518
  if (typeof this.track.computePixelHeight !== "function") {
34606
-
34607
34519
  for (let vp of this.viewports) {
34608
34520
  vp.setContentHeight(newHeight);
34609
34521
  }
@@ -34660,15 +34572,6 @@ class TrackView {
34660
34572
  }
34661
34573
  }
34662
34574
 
34663
- resize(viewportWidth) {
34664
-
34665
- for (let viewport of this.viewports) {
34666
- viewport.setWidth(viewportWidth);
34667
- }
34668
-
34669
- this.updateViews(true);
34670
- }
34671
-
34672
34575
  /**
34673
34576
  * Repaint all viewports without loading any new data. Use this for events that change visual aspect of data,
34674
34577
  * e.g. color, sort order, etc, but do not change the genomic state.
@@ -34676,7 +34579,9 @@ class TrackView {
34676
34579
  repaintViews() {
34677
34580
 
34678
34581
  for (let viewport of this.viewports) {
34679
- viewport.repaint();
34582
+ if (viewport.isVisible()) {
34583
+ viewport.repaint();
34584
+ }
34680
34585
  }
34681
34586
 
34682
34587
  if (typeof this.track.paintAxis === 'function') {
@@ -34685,7 +34590,6 @@ class TrackView {
34685
34590
 
34686
34591
  // Repaint sample names last
34687
34592
  this.repaintSamples();
34688
-
34689
34593
  }
34690
34594
 
34691
34595
  repaintSamples() {
@@ -34701,10 +34605,25 @@ class TrackView {
34701
34605
  this.viewports.forEach(viewport => viewport.setTrackLabel(name));
34702
34606
  }
34703
34607
 
34608
+ /**
34609
+ * Called in response to a window resize event, change in # of multilocus panels, or other event that changes
34610
+ * the width of the track view.
34611
+ *
34612
+ * @param viewportWidth The width of each viewport in this track view.
34613
+ */
34614
+ resize(viewportWidth) {
34615
+ for (let viewport of this.viewports) {
34616
+ viewport.setWidth(viewportWidth);
34617
+ }
34618
+ }
34619
+
34704
34620
  /**
34705
34621
  * Update viewports to reflect current genomic state, possibly loading additional data.
34622
+ *
34623
+ * @param force - if true, force a repaint even if no new data is loaded
34624
+ * @returns {Promise<void>}
34706
34625
  */
34707
- async updateViews(force) {
34626
+ async updateViews() {
34708
34627
 
34709
34628
  if (!(this.browser && this.browser.referenceFrameList)) return
34710
34629
 
@@ -34713,31 +34632,38 @@ class TrackView {
34713
34632
  // Shift viewports left/right to current genomic state (pans canvas)
34714
34633
  visibleViewports.forEach(viewport => viewport.shift());
34715
34634
 
34716
- const isDragging = this.browser.dragObject;
34717
-
34718
- if (isDragging) {
34635
+ // If dragging (panning) return
34636
+ if (this.browser.dragObject) {
34719
34637
  return
34720
34638
  }
34721
34639
 
34722
- // rpv: viewports whose image (canvas) does not fully cover current genomic range
34723
- const reloadableViewports = this.viewportsToReload(force);
34640
+ // Get viewports to repaint
34641
+ let viewportsToRepaint = (this.track.autoscale || this.track.autoscaleGroup || this.track.type === 'ruler') ?
34642
+ visibleViewports :
34643
+ visibleViewports.filter(vp => vp.needsRepaint());
34644
+
34645
+ // Filter zoomed out views. This has the side effect or turning off or no the zoomed out notice
34646
+ viewportsToRepaint = viewportsToRepaint.filter(viewport => viewport.checkZoomIn());
34647
+
34648
+ // Get viewports that require a data load
34649
+ const viewportsToReload = viewportsToRepaint.filter(viewport => viewport.needsReload());
34724
34650
 
34725
34651
  // Trigger viewport to load features needed to cover current genomic range
34726
34652
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
34727
- for (let viewport of reloadableViewports) {
34653
+ for (let viewport of viewportsToReload) {
34728
34654
  await viewport.loadFeatures();
34729
34655
  }
34730
-
34656
+
34731
34657
  if (this.disposed) return // Track was removed during load
34732
34658
 
34733
- // Very special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
34659
+ // Special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
34734
34660
  // section depends on data from all the views. We only need to adjust this however if any data was loaded
34735
34661
  // (i.e. reloadableViewports.length > 0)
34736
- if (this.track && typeof this.track.variantRowCount === 'function' && reloadableViewports.length > 0) {
34662
+ if (this.track && typeof this.track.variantRowCount === 'function' && viewportsToReload.length > 0) {
34737
34663
  let maxRow = 0;
34738
34664
  for (let viewport of this.viewports) {
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));
34665
+ if (viewport.featureCache && viewport.featureCache.features) {
34666
+ maxRow = Math.max(maxRow, viewport.featureCache.features.reduce((a, f) => Math.max(a, f.row || 0), 0));
34741
34667
  }
34742
34668
  }
34743
34669
  const current = this.track.nVariantRows;
@@ -34749,19 +34675,18 @@ class TrackView {
34749
34675
  }
34750
34676
  }
34751
34677
 
34752
-
34753
34678
  if (this.track.autoscale) {
34754
34679
  let allFeatures = [];
34755
34680
  for (let visibleViewport of visibleViewports) {
34756
34681
  const referenceFrame = visibleViewport.referenceFrame;
34757
34682
  const start = referenceFrame.start;
34758
34683
  const end = start + referenceFrame.toBP($$1(visibleViewport.contentDiv).width());
34759
- if (visibleViewport.tile && visibleViewport.tile.features) {
34760
- if (typeof visibleViewport.tile.features.getMax === 'function') {
34761
- const max = visibleViewport.tile.features.getMax(start, end);
34684
+ if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
34685
+ if (typeof visibleViewport.featureCache.features.getMax === 'function') {
34686
+ const max = visibleViewport.featureCache.features.getMax(start, end);
34762
34687
  allFeatures.push({value: max});
34763
34688
  } else {
34764
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.tile.features, start, end));
34689
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end));
34765
34690
  }
34766
34691
  }
34767
34692
  }
@@ -34772,15 +34697,8 @@ class TrackView {
34772
34697
  }
34773
34698
  }
34774
34699
 
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();
34779
- }
34780
- } else {
34781
- for (let vp of reloadableViewports) {
34782
- vp.repaint();
34783
- }
34700
+ for (let vp of viewportsToRepaint) {
34701
+ vp.repaint();
34784
34702
  }
34785
34703
 
34786
34704
  this.adjustTrackHeight();
@@ -34808,34 +34726,29 @@ class TrackView {
34808
34726
  }
34809
34727
 
34810
34728
  /**
34811
- * Return a promise to get all in-view features. Used for group autoscaling.
34729
+ * Return a promise to get all in-view features across all viewports. Used for group autoscaling.
34812
34730
  */
34813
- async getInViewFeatures(force) {
34731
+ async getInViewFeatures() {
34814
34732
 
34815
34733
  if (!(this.browser && this.browser.referenceFrameList)) {
34816
34734
  return []
34817
34735
  }
34818
34736
 
34819
- // List of viewports that need reloading
34820
- const rpV = this.viewportsToReload(force);
34821
- const promises = rpV.map(function (vp) {
34822
- return vp.loadFeatures()
34823
- });
34824
-
34825
- await Promise.all(promises);
34826
-
34827
34737
  let allFeatures = [];
34828
34738
  for (let vp of this.viewports) {
34829
- if (vp.tile && vp.tile.features) {
34739
+ if (vp.needsReload()) {
34740
+ await vp.loadFeatures();
34741
+ }
34742
+ if (vp.featureCache && vp.featureCache.features) {
34830
34743
  const referenceFrame = vp.referenceFrame;
34831
34744
  const start = referenceFrame.start;
34832
34745
  const end = start + referenceFrame.toBP($$1(vp.contentDiv).width());
34833
34746
 
34834
- if (typeof vp.tile.features.getMax === 'function') {
34835
- const max = vp.tile.features.getMax(start, end);
34747
+ if (typeof vp.featureCache.features.getMax === 'function') {
34748
+ const max = vp.featureCache.features.getMax(start, end);
34836
34749
  allFeatures.push({value: max});
34837
34750
  } else {
34838
- allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.tile.features, start, end));
34751
+ allFeatures = allFeatures.concat(FeatureUtils.findOverlapping(vp.featureCache.features, start, end));
34839
34752
  }
34840
34753
  }
34841
34754
  }
@@ -34876,27 +34789,6 @@ class TrackView {
34876
34789
  }
34877
34790
  }
34878
34791
 
34879
- viewportsToReload(force) {
34880
-
34881
- // List of viewports that need reloading
34882
- const viewports = this.viewports.filter(viewport => {
34883
- if (!viewport.isVisible()) {
34884
- return false
34885
- }
34886
- if (!viewport.checkZoomIn()) {
34887
- return false
34888
- } else {
34889
- const referenceFrame = viewport.referenceFrame;
34890
- const chr = viewport.referenceFrame.chr;
34891
- const start = referenceFrame.start;
34892
- const end = start + referenceFrame.toBP($$1(viewport.contentDiv).width());
34893
- const bpPerPixel = referenceFrame.bpPerPixel;
34894
- return force || (!viewport.tile || viewport.tile.invalidate || !viewport.tile.containsRange(chr, start, end, bpPerPixel))
34895
- }
34896
- });
34897
- return viewports
34898
- }
34899
-
34900
34792
  createTrackScrollbar(browser) {
34901
34793
 
34902
34794
  const outerScroll = div$1();
@@ -35009,7 +34901,7 @@ class TrackView {
35009
34901
 
35010
34902
  addTrackDragMouseHandlers(browser) {
35011
34903
 
35012
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
34904
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
35013
34905
 
35014
34906
  let currentDragHandle = undefined;
35015
34907
 
@@ -35086,7 +34978,7 @@ class TrackView {
35086
34978
 
35087
34979
  removeTrackDragMouseHandlers() {
35088
34980
 
35089
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
34981
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
35090
34982
  this.dragHandle.removeEventListener('mousedown', this.boundTrackDragMouseDownHandler);
35091
34983
  document.removeEventListener('mouseup', this.boundDocumentTrackDragMouseUpHandler);
35092
34984
  this.dragHandle.removeEventListener('mouseup', this.boundTrackDragMouseEnterHandler);
@@ -35949,7 +35841,7 @@ function decodeBed(tokens, header) {
35949
35841
  const eEnd = eStart + parseInt(exonSizes[i]);
35950
35842
  exons.push({start: eStart, end: eEnd});
35951
35843
  }
35952
- findUTRs(exons, feature.cdStart, feature.cdEnd);
35844
+ findUTRs$1(exons, feature.cdStart, feature.cdEnd);
35953
35845
  feature.exons = exons;
35954
35846
  }
35955
35847
 
@@ -36057,7 +35949,7 @@ function decodeGenePred(tokens, header) {
36057
35949
  const end = parseInt(exonEnds[i]);
36058
35950
  exons.push({start: start, end: end});
36059
35951
  }
36060
- findUTRs(exons, cdStart, cdEnd);
35952
+ findUTRs$1(exons, cdStart, cdEnd);
36061
35953
 
36062
35954
  feature.exons = exons;
36063
35955
 
@@ -36100,7 +35992,7 @@ function decodeGenePredExt(tokens, header) {
36100
35992
  const end = parseInt(exonEnds[i]);
36101
35993
  exons.push({start: start, end: end});
36102
35994
  }
36103
- findUTRs(exons, cdStart, cdEnd);
35995
+ findUTRs$1(exons, cdStart, cdEnd);
36104
35996
 
36105
35997
  feature.exons = exons;
36106
35998
 
@@ -36141,14 +36033,14 @@ function decodeReflat(tokens, header) {
36141
36033
  const end = parseInt(exonEnds[i]);
36142
36034
  exons.push({start: start, end: end});
36143
36035
  }
36144
- findUTRs(exons, cdStart, cdEnd);
36036
+ findUTRs$1(exons, cdStart, cdEnd);
36145
36037
 
36146
36038
  feature.exons = exons;
36147
36039
 
36148
36040
  return feature
36149
36041
  }
36150
36042
 
36151
- function findUTRs(exons, cdStart, cdEnd) {
36043
+ function findUTRs$1(exons, cdStart, cdEnd) {
36152
36044
 
36153
36045
  for (let exon of exons) {
36154
36046
  const end = exon.end;
@@ -39664,7 +39556,7 @@ class TextFeatureSource {
39664
39556
  this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
39665
39557
  this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
39666
39558
 
39667
- const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "tdf"]);
39559
+ const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
39668
39560
 
39669
39561
  if (config.features && Array.isArray(config.features)) {
39670
39562
  // Explicit array of features
@@ -39674,7 +39566,7 @@ class TextFeatureSource {
39674
39566
  mapProperties(features, config.mappings);
39675
39567
  }
39676
39568
  this.queryable = false;
39677
- this.featureCache = new FeatureCache(features, genome);
39569
+ this.featureCache = new FeatureCache$1(features, genome);
39678
39570
  } else if (config.reader) {
39679
39571
  // Explicit reader implementation
39680
39572
  this.reader = config.reader;
@@ -39841,14 +39733,14 @@ class TextFeatureSource {
39841
39733
  }
39842
39734
 
39843
39735
  // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
39844
- this.featureCache = new FeatureCache(features, this.genome, genomicInterval);
39736
+ this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
39845
39737
 
39846
39738
  // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
39847
39739
  if (this.config.searchable || this.config.searchableFields) {
39848
39740
  this.addFeaturesToDB(features);
39849
39741
  }
39850
39742
  } else {
39851
- this.featureCache = new FeatureCache([], genomicInterval); // Empty cache
39743
+ this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
39852
39744
  }
39853
39745
  }
39854
39746
 
@@ -40084,11 +39976,9 @@ class BufferedReader {
40084
39976
  }
40085
39977
  }
40086
39978
 
40087
- //table chromatinInteract
40088
-
40089
39979
  function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40090
39980
 
40091
- if (autoSql && 'chromatinInteract' === autoSql.table || "biginteract" === format) {
39981
+ if ("biginteract" === format || (autoSql && ('chromatinInteract' === autoSql.table) || 'interact' === autoSql.table)) {
40092
39982
  return decodeInteract
40093
39983
  } else {
40094
39984
  const standardFieldCount = definedFieldCount - 3;
@@ -40125,6 +40015,7 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40125
40015
  const eEnd = eStart + parseInt(exonSizes[i]);
40126
40016
  exons.push({start: eStart, end: eEnd});
40127
40017
  }
40018
+ findUTRs(exons, feature.cdStart, feature.cdEnd);
40128
40019
  feature.exons = exons;
40129
40020
  }
40130
40021
 
@@ -40142,6 +40033,28 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40142
40033
  }
40143
40034
  }
40144
40035
 
40036
+ //table chromatinInteract
40037
+ // "Chromatin interaction between two regions"
40038
+ // (
40039
+ // string chrom; "Chromosome (or contig, scaffold, etc.). For interchromosomal, use 2 records"
40040
+ // uint chromStart; "Start position of lower region. For interchromosomal, set to chromStart of this region"
40041
+ // uint chromEnd; "End position of upper region. For interchromosomal, set to chromEnd of this region"
40042
+ // string name; "Name of item, for display"
40043
+ // uint score; "Score from 0-1000"
40044
+ // double value; "Strength of interaction or other data value. Typically basis for score"
40045
+ // string exp; "Experiment name (metadata for filtering). Use . if not applicable"
40046
+ // string color; "Item color. Specified as r,g,b or hexadecimal #RRGGBB or html color name, as in //www.w3.org/TR/css3-color/#html4."
40047
+ // string region1Chrom; "Chromosome of lower region. For non-directional interchromosomal, chrom of this region."
40048
+ // uint region1Start; "Start position of lower/this region"
40049
+ // uint region1End; "End position in chromosome of lower/this region"
40050
+ // string region1Name; "Identifier of lower/this region"
40051
+ // string region1Strand; "Orientation of lower/this region: + or -. Use . if not applicable"
40052
+ // string region2Chrom; "Chromosome of upper region. For non-directional interchromosomal, chrom of other region"
40053
+ // uint region2Start; "Start position in chromosome of upper/this region"
40054
+ // uint region2End; "End position in chromosome of upper/this region"
40055
+ // string region2Name; "Identifier of upper/this region"
40056
+ // string region2Strand; "Orientation of upper/this region: + or -. Use . if not applicable"
40057
+ // )
40145
40058
  function decodeInteract(feature, tokens) {
40146
40059
 
40147
40060
  feature.chr1 = tokens[5];
@@ -40161,6 +40074,24 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40161
40074
  }
40162
40075
  }
40163
40076
 
40077
+ function findUTRs(exons, cdStart, cdEnd) {
40078
+
40079
+ for (let exon of exons) {
40080
+ const end = exon.end;
40081
+ const start = exon.start;
40082
+ if (end < cdStart || start > cdEnd) {
40083
+ exon.utr = true;
40084
+ } else {
40085
+ if (cdStart >= start && cdStart <= end) {
40086
+ exon.cdStart = cdStart;
40087
+ }
40088
+ if (cdEnd >= start && cdEnd <= end) {
40089
+ exon.cdEnd = cdEnd;
40090
+ }
40091
+ }
40092
+ }
40093
+ }
40094
+
40164
40095
  function scoreShade(score, color) {
40165
40096
  const alpha = Math.min(1, 0.11 + 0.89 * (score / 779));
40166
40097
  return alpha.toString()
@@ -41592,7 +41523,7 @@ class TDFSource {
41592
41523
  return features
41593
41524
  }
41594
41525
 
41595
- supportsWholeGenome() {
41526
+ get supportsWholeGenome() {
41596
41527
  return true
41597
41528
  }
41598
41529
  }
@@ -41700,11 +41631,12 @@ function zoomLevelForScale(chr, bpPerPixel, genome) {
41700
41631
  * THE SOFTWARE.
41701
41632
  */
41702
41633
 
41634
+ const bbFormats = new Set(['bigwig', 'bw', 'bigbed', 'bb', 'biginteract', 'biggenepred', 'bignarrowpeak']);
41703
41635
 
41704
41636
  function FeatureSource(config, genome) {
41705
41637
 
41706
41638
  const format = config.format ? config.format.toLowerCase() : undefined;
41707
- if ('bigwig' === format || 'bigbed' === format || 'bb' === format || "biginteract" === format) {
41639
+ if (bbFormats.has(format)) {
41708
41640
  return new BWSource(config, genome)
41709
41641
  } else if ("tdf" === format) {
41710
41642
  return new TDFSource(config, genome)
@@ -41713,38 +41645,6 @@ function FeatureSource(config, genome) {
41713
41645
  }
41714
41646
  }
41715
41647
 
41716
- const pairs =
41717
- [
41718
- ['A', 'T'],
41719
- ['G', 'C'],
41720
- ['Y', 'R'],
41721
- ['W', 'S'],
41722
- ['K', 'M'],
41723
- ['D', 'H'],
41724
- ['B', 'V']
41725
- ];
41726
-
41727
- const complements = new Map();
41728
- for (let p of pairs) {
41729
- const p1 = p[0];
41730
- const p2 = p[1];
41731
- complements.set(p1, p2);
41732
- complements.set(p2, p1);
41733
- complements.set(p1.toLowerCase(), p2.toLowerCase());
41734
- complements.set(p2.toLowerCase(), p1.toLowerCase());
41735
- }
41736
-
41737
- function reverseComplementSequence(sequence) {
41738
-
41739
- let comp = '';
41740
- let idx = sequence.length;
41741
- while (idx-- > 0) {
41742
- const base = sequence[idx];
41743
- comp += complements.has(base) ? complements.get(base) : base;
41744
- }
41745
- return comp
41746
- }
41747
-
41748
41648
  const GtexUtils = {
41749
41649
 
41750
41650
  getTissueInfo: function (datasetId, baseURL) {
@@ -41984,10 +41884,9 @@ function renderFeatureLabel(ctx, feature, featureX, featureX1, featureY, referen
41984
41884
  const textBox = ctx.measureText(name);
41985
41885
  const xleft = centerX - textBox.width / 2;
41986
41886
  const xright = centerX + textBox.width / 2;
41987
- if (options.labelAllFeatures || xleft > options.rowLastX[feature.row] || gtexSelection) {
41988
- options.rowLastX[feature.row] = xright;
41887
+ if (options.labelAllFeatures || xleft > options.rowLastLabelX[feature.row] || gtexSelection) {
41888
+ options.rowLastLabelX[feature.row] = xright;
41989
41889
  IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
41990
-
41991
41890
  }
41992
41891
  } finally {
41993
41892
  ctx.restore();
@@ -42288,7 +42187,7 @@ class FeatureTrack extends TrackBase {
42288
42187
 
42289
42188
  }
42290
42189
 
42291
- supportsWholeGenome() {
42190
+ get supportsWholeGenome() {
42292
42191
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false
42293
42192
  }
42294
42193
 
@@ -42344,24 +42243,27 @@ class FeatureTrack extends TrackBase {
42344
42243
 
42345
42244
  const rowFeatureCount = [];
42346
42245
  options.rowLastX = [];
42246
+ options.rowLastLabelX = [];
42347
42247
  for (let feature of featureList) {
42348
- const row = feature.row || 0;
42349
- if (rowFeatureCount[row] === undefined) {
42350
- rowFeatureCount[row] = 1;
42351
- } else {
42352
- rowFeatureCount[row]++;
42248
+ if(feature.start > bpStart && feature.end < bpEnd) {
42249
+ const row = this.displayMode === "COLLAPSED" ? 0 : feature.row || 0;
42250
+ if (rowFeatureCount[row] === undefined) {
42251
+ rowFeatureCount[row] = 1;
42252
+ } else {
42253
+ rowFeatureCount[row]++;
42254
+ }
42255
+ options.rowLastX[row] = -Number.MAX_SAFE_INTEGER;
42256
+ options.rowLastLabelX[row] = -Number.MAX_SAFE_INTEGER;
42353
42257
  }
42354
- options.rowLastX[row] = -Number.MAX_SAFE_INTEGER;
42355
42258
  }
42259
+ const pixelsPerFeature = pixelWidth / Math.max(...rowFeatureCount);
42356
42260
 
42357
42261
  let lastPxEnd = [];
42358
42262
  for (let feature of featureList) {
42359
42263
  if (feature.end < bpStart) continue
42360
42264
  if (feature.start > bpEnd) break
42361
-
42362
42265
  const row = this.displayMode === 'COLLAPSED' ? 0 : feature.row;
42363
- const featureDensity = pixelWidth / rowFeatureCount[row];
42364
- options.drawLabel = options.labelAllFeatures || featureDensity > 10;
42266
+ options.drawLabel = options.labelAllFeatures || pixelsPerFeature > 10;
42365
42267
  const pxEnd = Math.ceil((feature.end - bpStart) / bpPerPixel);
42366
42268
  const last = lastPxEnd[row];
42367
42269
  if (!last || pxEnd > last) {
@@ -42375,7 +42277,6 @@ class FeatureTrack extends TrackBase {
42375
42277
  ctx.globalAlpha = 1.0;
42376
42278
  }
42377
42279
  lastPxEnd[row] = pxEnd;
42378
-
42379
42280
  }
42380
42281
  }
42381
42282
 
@@ -42437,21 +42338,18 @@ class FeatureTrack extends TrackBase {
42437
42338
  const infoURL = this.infoURL || this.config.infoURL;
42438
42339
  for (let fd of featureData) {
42439
42340
  data.push(fd);
42440
- if (infoURL) {
42441
- if (fd.name &&
42442
- fd.name.toLowerCase() === "name" &&
42443
- fd.value &&
42444
- isString$3(fd.value) &&
42445
- !fd.value.startsWith("<")) {
42446
-
42447
-
42448
- const url = this.infoURL || this.config.infoURL;
42449
- const href = url.replace("$$", feature.name);
42450
- data.push({name: "Info", value: `<a target="_blank" href=${href}>${fd.value}</a>`});
42451
- }
42341
+ if (infoURL &&
42342
+ fd.name &&
42343
+ fd.name.toLowerCase() === "name" &&
42344
+ fd.value &&
42345
+ isString$3(fd.value) &&
42346
+ !fd.value.startsWith("<")) {
42347
+ const href = infoURL.replace("$$", feature.name);
42348
+ fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
42452
42349
  }
42453
42350
  }
42454
42351
 
42352
+
42455
42353
  //Array.prototype.push.apply(data, featureData);
42456
42354
 
42457
42355
  // If we have clicked over an exon number it.
@@ -42480,7 +42378,7 @@ class FeatureTrack extends TrackBase {
42480
42378
  }
42481
42379
 
42482
42380
  menuItemList() {
42483
-
42381
+
42484
42382
  const menuItems = [];
42485
42383
 
42486
42384
  if (this.render === renderSnp) {
@@ -42508,7 +42406,7 @@ class FeatureTrack extends TrackBase {
42508
42406
  menuItems.push(
42509
42407
  {
42510
42408
  object: $$1(createCheckbox$1(lut[displayMode], displayMode === this.displayMode)),
42511
- click: () => {
42409
+ click: () => {
42512
42410
  this.displayMode = displayMode;
42513
42411
  this.config.displayMode = displayMode;
42514
42412
  this.trackView.checkContentHeight();
@@ -42524,14 +42422,28 @@ class FeatureTrack extends TrackBase {
42524
42422
 
42525
42423
  contextMenuItemList(clickState) {
42526
42424
 
42527
- if (isSecureContext()) {
42528
- const features = this.clickedFeatures(clickState);
42529
- if (features.length > 1) {
42530
- features.sort((a, b) => (a.end - a.start) - (b.end - b.start));
42531
- }
42532
- const f = features[0]; // The longest feature
42533
- if ((f.end - f.start) <= 1000000) {
42534
- return [
42425
+ const features = this.clickedFeatures(clickState);
42426
+ if (features.length > 1) {
42427
+ features.sort((a, b) => (b.end - b.start) - (a.end - a.start));
42428
+ }
42429
+ const f = features[0]; // The shortest clicked feature
42430
+
42431
+ if ((f.end - f.start) <= 1000000) {
42432
+ const list = [{
42433
+ label: 'View feature sequence',
42434
+ click: async () => {
42435
+ let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
42436
+ if (f.strand === '-') {
42437
+ seq = reverseComplementSequence(seq);
42438
+ }
42439
+ if (!seq) seq = "Unknown sequence";
42440
+ Alert.presentAlert(seq);
42441
+
42442
+ }
42443
+ }];
42444
+
42445
+ if (isSecureContext() && navigator.clipboard !== undefined) {
42446
+ list.push(
42535
42447
  {
42536
42448
  label: 'Copy feature sequence',
42537
42449
  click: async () => {
@@ -42539,17 +42451,23 @@ class FeatureTrack extends TrackBase {
42539
42451
  if (f.strand === '-') {
42540
42452
  seq = reverseComplementSequence(seq);
42541
42453
  }
42542
- navigator.clipboard.writeText(seq);
42454
+ try {
42455
+ await navigator.clipboard.writeText(seq);
42456
+ } catch (e) {
42457
+ console.error(e);
42458
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
42459
+ }
42543
42460
  }
42544
- },
42545
- '<hr/>'
42546
- ]
42461
+ }
42462
+ );
42547
42463
  }
42548
- }
42464
+ list.push('<hr/>');
42465
+ return list
42466
+ } else {
42549
42467
 
42550
- // Either not a secure context (i.e. http: protocol), or feature is too long
42551
- return undefined
42468
+ return undefined
42552
42469
 
42470
+ }
42553
42471
  }
42554
42472
 
42555
42473
  description() {
@@ -42570,7 +42488,7 @@ class FeatureTrack extends TrackBase {
42570
42488
  desc += "</html>";
42571
42489
  return desc
42572
42490
  } else {
42573
- return super.description();
42491
+ return super.description()
42574
42492
  }
42575
42493
 
42576
42494
  };
@@ -42651,13 +42569,11 @@ class WigTrack extends TrackBase {
42651
42569
  this.paintAxis = paintAxis;
42652
42570
 
42653
42571
  const format = config.format ? config.format.toLowerCase() : config.format;
42572
+ this.flipAxis = config.flipAxis ? config.flipAxis : false;
42573
+ this.logScale = config.logScale ? config.logScale : false;
42654
42574
  if ("bigwig" === format) {
42655
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
42656
- this.logScale = config.logScale ? config.logScale : false;
42657
42575
  this.featureSource = new BWSource(config, this.browser.genome);
42658
42576
  } else if ("tdf" === format) {
42659
- this.flipAxis = config.flipAxis ? config.flipAxis : false;
42660
- this.logScale = config.logScale ? config.logScale : false;
42661
42577
  this.featureSource = new TDFSource(config, this.browser.genome);
42662
42578
  } else {
42663
42579
  this.featureSource = FeatureSource(config, this.browser.genome);
@@ -42709,7 +42625,7 @@ class WigTrack extends TrackBase {
42709
42625
  let items = [];
42710
42626
  if (this.flipAxis !== undefined) {
42711
42627
  items.push({
42712
- label:"Flip y-axis",
42628
+ label: "Flip y-axis",
42713
42629
  click: () => {
42714
42630
  this.flipAxis = !this.flipAxis;
42715
42631
  this.trackView.repaintViews();
@@ -42893,7 +42809,7 @@ class WigTrack extends TrackBase {
42893
42809
  }
42894
42810
  }
42895
42811
 
42896
- supportsWholeGenome() {
42812
+ get supportsWholeGenome() {
42897
42813
  return !this.config.indexURL && this.config.supportsWholeGenome !== false
42898
42814
  }
42899
42815
 
@@ -43437,7 +43353,7 @@ class SegTrack extends TrackBase {
43437
43353
 
43438
43354
  const sortHandler = (sort) => {
43439
43355
  const viewport = clickState.viewport;
43440
- const features = viewport.getCachedFeatures();
43356
+ const features = viewport.cachedFeatures;
43441
43357
  this.sortSamples(sort.chr, sort.start, sort.end, sort.direction, features);
43442
43358
  };
43443
43359
 
@@ -43465,7 +43381,7 @@ class SegTrack extends TrackBase {
43465
43381
 
43466
43382
  }
43467
43383
 
43468
- supportsWholeGenome() {
43384
+ get supportsWholeGenome() {
43469
43385
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false
43470
43386
  }
43471
43387
 
@@ -43555,10 +43471,16 @@ const MUT_COLORS = {
43555
43471
  * THE SOFTWARE.
43556
43472
  */
43557
43473
 
43474
+ /**
43475
+ * Represents 2 or more wig tracks overlaid on a common viewport.
43476
+ */
43558
43477
  class MergedTrack extends TrackBase {
43559
43478
 
43560
43479
  constructor(config, browser) {
43561
43480
  super(config, browser);
43481
+ this.type = "merged";
43482
+ this.featureType = 'numeric';
43483
+ this.paintAxis = paintAxis;
43562
43484
  }
43563
43485
 
43564
43486
  init(config) {
@@ -43569,21 +43491,6 @@ class MergedTrack extends TrackBase {
43569
43491
  super.init(config);
43570
43492
  }
43571
43493
 
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
-
43587
43494
  async postInit() {
43588
43495
 
43589
43496
  this.tracks = [];
@@ -43603,11 +43510,55 @@ class MergedTrack extends TrackBase {
43603
43510
  }
43604
43511
  }
43605
43512
 
43606
- this.height = this.config.height || 100;
43513
+ this.flipAxis = this.config.flipAxis ? this.config.flipAxis : false;
43514
+ this.logScale = this.config.logScale ? this.config.logScale : false;
43515
+ this.autoscale = this.config.autoscale || this.config.max === undefined;
43516
+ if (!this.autoscale) {
43517
+ this.dataRange = {
43518
+ min: this.config.min || 0,
43519
+ max: this.config.max
43520
+ };
43521
+ }
43522
+ for (let t of this.tracks) {
43523
+ t.autoscale = false;
43524
+ t.dataRange = this.dataRange;
43525
+ }
43526
+
43527
+ this.height = this.config.height || 50;
43607
43528
 
43608
43529
  return Promise.all(p)
43609
43530
  }
43610
43531
 
43532
+ get height() {
43533
+ return this._height
43534
+ }
43535
+
43536
+ set height(h) {
43537
+ this._height = h;
43538
+ if (this.tracks) {
43539
+ for (let t of this.tracks) {
43540
+ t.height = h;
43541
+ t.config.height = h;
43542
+ }
43543
+ }
43544
+ }
43545
+
43546
+ menuItemList() {
43547
+ let items = [];
43548
+ if (this.flipAxis !== undefined) {
43549
+ items.push({
43550
+ label: "Flip y-axis",
43551
+ click: () => {
43552
+ this.flipAxis = !this.flipAxis;
43553
+ this.trackView.repaintViews();
43554
+ }
43555
+ });
43556
+ }
43557
+
43558
+ items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
43559
+
43560
+ return items
43561
+ }
43611
43562
 
43612
43563
  async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
43613
43564
 
@@ -43617,44 +43568,26 @@ class MergedTrack extends TrackBase {
43617
43568
 
43618
43569
  draw(options) {
43619
43570
 
43620
- var i, len, mergedFeatures, trackOptions, dataRange;
43621
-
43622
- mergedFeatures = options.features; // Array of feature arrays, 1 for each track
43623
-
43624
- dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
43571
+ const mergedFeatures = options.features; // Array of feature arrays, 1 for each track
43625
43572
 
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++) {
43573
+ if (this.autoscale) {
43574
+ this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
43575
+ }
43629
43576
 
43630
- trackOptions = Object.assign({}, options);
43577
+ for (let i = 0, len = this.tracks.length; i < len; i++) {
43578
+ const trackOptions = Object.assign({}, options);
43631
43579
  trackOptions.features = mergedFeatures[i];
43632
- this.tracks[i].dataRange = dataRange;
43580
+ this.tracks[i].dataRange = this.dataRange;
43581
+ this.tracks[i].flipAxis = this.flipAxis;
43582
+ this.tracks[i].logScale = this.logScale;
43583
+ this.tracks[i].graphType = this.graphType;
43633
43584
  this.tracks[i].draw(trackOptions);
43634
43585
  }
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
- }
43653
43586
  }
43654
43587
 
43655
43588
  popupData(clickState, features) {
43656
43589
 
43657
- const featuresArray = features || clickState.viewport.getCachedFeatures();
43590
+ const featuresArray = features || clickState.viewport.cachedFeatures;
43658
43591
 
43659
43592
  if (featuresArray && featuresArray.length === this.tracks.length) {
43660
43593
  // Array of feature arrays, 1 for each track
@@ -43671,42 +43604,23 @@ class MergedTrack extends TrackBase {
43671
43604
  }
43672
43605
 
43673
43606
 
43674
- supportsWholeGenome() {
43675
- const b = this.tracks.every(track => track.supportsWholeGenome());
43676
- return b
43607
+ get supportsWholeGenome() {
43608
+ return this.tracks.every(track => track.supportsWholeGenome())
43677
43609
  }
43678
43610
  }
43679
43611
 
43680
43612
  function autoscale(chr, featureArrays) {
43681
43613
 
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) {
43614
+ let min = 0;
43615
+ let max = -Number.MAX_VALUE;
43616
+ for(let features of featureArrays) {
43617
+ for(let f of features) {
43703
43618
  if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
43704
43619
  min = Math.min(min, f.value);
43705
43620
  max = Math.max(max, f.value);
43706
43621
  }
43707
- });
43708
- });
43709
- // }
43622
+ }
43623
+ }
43710
43624
  return {min: min, max: max}
43711
43625
  }
43712
43626
 
@@ -43815,7 +43729,7 @@ class InteractionTrack extends TrackBase {
43815
43729
  return this
43816
43730
  }
43817
43731
 
43818
- supportsWholeGenome() {
43732
+ get supportsWholeGenome() {
43819
43733
  return true
43820
43734
  }
43821
43735
 
@@ -44201,7 +44115,7 @@ class InteractionTrack extends TrackBase {
44201
44115
  items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
44202
44116
  }
44203
44117
 
44204
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44118
+ if (this.browser.circularView) {
44205
44119
  items.push('<hr/>');
44206
44120
  items.push({
44207
44121
  label: 'Add interactions to circular view',
@@ -44219,7 +44133,7 @@ class InteractionTrack extends TrackBase {
44219
44133
  contextMenuItemList(clickState) {
44220
44134
 
44221
44135
  // Experimental JBrowse feature
44222
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44136
+ if (this.browser.circularView ) {
44223
44137
  const viewport = clickState.viewport;
44224
44138
  const list = [];
44225
44139
 
@@ -44248,21 +44162,22 @@ class InteractionTrack extends TrackBase {
44248
44162
 
44249
44163
  // inView features are simply features that have been drawn, i.e. have a drawState
44250
44164
  const inView = cachedFeatures.filter(f => f.drawState);
44251
- if(inView.length === 0) erturn;
44165
+ if(inView.length === 0) return;
44252
44166
 
44253
- this.browser.circularViewVisible = true;
44254
44167
  const chords = makeBedPEChords(inView);
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});
44168
+ sendChords(chords, this, refFrame, 0.5);
44169
+ //
44170
+ //
44171
+ // // for filtered set, distinguishing the chromosomes is more critical than tracks
44172
+ // const chordSetColor = IGVColor.addAlpha("all" === refFrame.chr ? this.color : getChrColor(refFrame.chr), 0.5)
44173
+ // const trackColor = IGVColor.addAlpha(this.color, 0.5)
44174
+ //
44175
+ // // name the chord set to include locus and filtering information
44176
+ // const encodedName = this.name.replaceAll(' ', '%20')
44177
+ // const chordSetName = "all" === refFrame.chr ?
44178
+ // encodedName :
44179
+ // `${encodedName} (${refFrame.chr}:${refFrame.start}-${refFrame.end} ; range:${this.dataRange.min}-${this.dataRange.max})`
44180
+ // this.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor})
44266
44181
  }
44267
44182
 
44268
44183
  doAutoscale(features) {
@@ -44327,7 +44242,7 @@ class InteractionTrack extends TrackBase {
44327
44242
 
44328
44243
  // We use the cached features rather than method to avoid async load. If the
44329
44244
  // feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
44330
- const featureList = features || clickState.viewport.getCachedFeatures();
44245
+ const featureList = features || clickState.viewport.cachedFeatures;
44331
44246
  const candidates = [];
44332
44247
  if (featureList) {
44333
44248
  const proportional = (this.arcType === "proportional" || this.arcType === "inView" || this.arcType === "partialInView");
@@ -44661,7 +44576,7 @@ class VariantTrack extends TrackBase {
44661
44576
 
44662
44577
  }
44663
44578
 
44664
- supportsWholeGenome() {
44579
+ get supportsWholeGenome() {
44665
44580
  return this.config.indexed === false || this.config.supportsWholeGenome === true
44666
44581
  }
44667
44582
 
@@ -44853,7 +44768,7 @@ class VariantTrack extends TrackBase {
44853
44768
  }
44854
44769
 
44855
44770
  } else if (this._color) {
44856
- variantColor = (typeof this._color === "function") ? this._color(v) : this._color;
44771
+ variantColor = this.color;
44857
44772
  } else if ("NONVARIANT" === v.type) {
44858
44773
  variantColor = this.nonRefColor;
44859
44774
  } else if ("MIXED" === v.type) {
@@ -44864,6 +44779,10 @@ class VariantTrack extends TrackBase {
44864
44779
  return variantColor
44865
44780
  }
44866
44781
 
44782
+ get color() {
44783
+ return this._color ? ((typeof this._color === "function") ? this._color(v) : this._color) : this.defaultColor
44784
+ }
44785
+
44867
44786
  clickedFeatures(clickState, features) {
44868
44787
 
44869
44788
  let featureList = super.clickedFeatures(clickState, features);
@@ -45090,25 +45009,15 @@ class VariantTrack extends TrackBase {
45090
45009
  }
45091
45010
 
45092
45011
  // Experimental JBrowse circular view integration
45093
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
45012
+ if (this.browser.circularView) {
45094
45013
 
45095
45014
  menuItems.push('<hr>');
45096
45015
  menuItems.push({
45097
45016
  label: 'Add SVs to circular view',
45098
45017
  click: () => {
45099
- const inView = [];
45100
45018
  for (let viewport of this.trackView.viewports) {
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
- }
45019
+ this.sendChordsForViewport(viewport);
45107
45020
  }
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});
45112
45021
  }
45113
45022
  });
45114
45023
  }
@@ -45120,20 +45029,14 @@ class VariantTrack extends TrackBase {
45120
45029
  contextMenuItemList(clickState) {
45121
45030
 
45122
45031
  // Experimental JBrowse circular view integration
45123
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
45032
+ if (this.browser.circularView) {
45124
45033
  const viewport = clickState.viewport;
45125
45034
  const list = [];
45126
45035
 
45127
45036
  list.push({
45128
45037
  label: 'Add SVs to Circular View',
45129
45038
  click: () => {
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});
45039
+ this.sendChordsForViewport(viewport);
45137
45040
  }
45138
45041
  });
45139
45042
 
@@ -45143,6 +45046,15 @@ class VariantTrack extends TrackBase {
45143
45046
  }
45144
45047
 
45145
45048
 
45049
+ sendChordsForViewport(viewport) {
45050
+ const refFrame = viewport.referenceFrame;
45051
+ const inView = "all" === refFrame.chr ?
45052
+ this.featureSource.getAllFeatures() :
45053
+ this.featureSource.featureCache.queryFeatures(refFrame.chr, refFrame.start, refFrame.end);
45054
+ const chords = makeVCFChords(inView);
45055
+ sendChords(chords, this, refFrame, 0.5);
45056
+ }
45057
+
45146
45058
  /**
45147
45059
  * Create a "color by" checkbox menu item, optionally initially checked
45148
45060
  * @param menuItem
@@ -45439,7 +45351,7 @@ class EqtlTrack extends TrackBase {
45439
45351
  */
45440
45352
  popupData(clickState) {
45441
45353
 
45442
- let features = clickState.viewport.getCachedFeatures();
45354
+ let features = clickState.viewport.cachedFeatures;
45443
45355
  if (!features || features.length === 0) return []
45444
45356
 
45445
45357
  const tolerance = 3;
@@ -45658,7 +45570,7 @@ class GWASTrack extends TrackBase {
45658
45570
  }
45659
45571
 
45660
45572
 
45661
- supportsWholeGenome() {
45573
+ get supportsWholeGenome() {
45662
45574
  return true
45663
45575
  }
45664
45576
 
@@ -45769,7 +45681,7 @@ class GWASTrack extends TrackBase {
45769
45681
 
45770
45682
  let data = [];
45771
45683
  const track = clickState.viewport.trackView.track;
45772
- const features = clickState.viewport.getCachedFeatures();
45684
+ const features = clickState.viewport.cachedFeatures;
45773
45685
 
45774
45686
  if (features) {
45775
45687
  let count = 0;
@@ -46172,7 +46084,7 @@ class GCNVTrack extends TrackBase {
46172
46084
  return items
46173
46085
  }
46174
46086
 
46175
- supportsWholeGenome() {
46087
+ get supportsWholeGenome() {
46176
46088
  return false
46177
46089
  }
46178
46090
  }
@@ -46452,7 +46364,7 @@ class RNAFeatureSource {
46452
46364
 
46453
46365
  const data = await igvxhr.loadString(this.config.url, options);
46454
46366
 
46455
- this.featureCache = new FeatureCache(parseBP(data), genome);
46367
+ this.featureCache = new FeatureCache$1(parseBP(data), genome);
46456
46368
 
46457
46369
  return this.featureCache.queryFeatures(chr, start, end)
46458
46370
 
@@ -46559,21 +46471,18 @@ class RNAFeatureSource {
46559
46471
  * THE SOFTWARE.
46560
46472
  */
46561
46473
 
46474
+ /**
46475
+ * Class represents an ideogram of a chromsome cytobands. It is used for the header of a track panel.
46476
+ *
46477
+ */
46562
46478
  class IdeogramTrack {
46563
46479
  constructor(browser) {
46564
-
46565
46480
  this.browser = browser;
46566
-
46567
46481
  this.type = 'ideogram';
46568
- this.id = this.type;
46569
-
46570
46482
  this.height = 16;
46571
-
46572
46483
  this.order = Number.MIN_SAFE_INTEGER;
46573
-
46574
46484
  this.disableButtons = true;
46575
46485
  this.ignoreTrackMenu = true;
46576
-
46577
46486
  }
46578
46487
 
46579
46488
  async getFeatures(chr, start, end) {
@@ -46840,7 +46749,7 @@ class SpliceJunctionTrack extends TrackBase {
46840
46749
 
46841
46750
  }
46842
46751
 
46843
- supportsWholeGenome() {
46752
+ get supportsWholeGenome() {
46844
46753
  return false
46845
46754
  }
46846
46755
 
@@ -47734,6 +47643,15 @@ class ReferenceFrame {
47734
47643
  this.id = guid$2();
47735
47644
  }
47736
47645
 
47646
+ extend(locus) {
47647
+ const newStart = Math.min(locus.start, this.start);
47648
+ const newEnd = Math.max(locus.end, this.end);
47649
+ const ratio = (newEnd - newStart) / (this.end - this.start);
47650
+ this.start = newStart;
47651
+ this.end = newEnd;
47652
+ this.bpPerPixel *= ratio;
47653
+ }
47654
+
47737
47655
  calculateEnd(pixels) {
47738
47656
  return this.start + this.bpPerPixel * pixels
47739
47657
  }
@@ -47820,7 +47738,7 @@ class ReferenceFrame {
47820
47738
 
47821
47739
  const viewChanged = start !== this.start || bpPerPixel !== this.bpPerPixel;
47822
47740
  if (viewChanged) {
47823
- await browser.updateViews(this);
47741
+ await browser.updateViews(true);
47824
47742
  }
47825
47743
 
47826
47744
  }
@@ -47896,30 +47814,6 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
47896
47814
  })
47897
47815
  }
47898
47816
 
47899
- function adjustReferenceFrame(scaleFactor, referenceFrame, viewportWidth, alignmentStart, alignmentLength) {
47900
-
47901
- referenceFrame.bpPerPixel *= scaleFactor;
47902
-
47903
- const alignmentEE = alignmentStart + alignmentLength;
47904
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
47905
-
47906
- referenceFrame.start = alignmentCC - (referenceFrame.bpPerPixel * (viewportWidth / 2));
47907
- referenceFrame.end = referenceFrame.start + (referenceFrame.bpPerPixel * viewportWidth);
47908
- referenceFrame.locusSearchString = referenceFrame.getLocusString();
47909
- }
47910
-
47911
- function createReferenceFrameWithAlignment(genome, chromosomeName, bpp, viewportWidth, alignmentStart, alignmentLength) {
47912
-
47913
- const alignmentEE = alignmentStart + alignmentLength;
47914
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
47915
-
47916
- const ss = alignmentCC - (bpp * (viewportWidth / 2));
47917
- const ee = ss + (bpp * viewportWidth);
47918
-
47919
- return new ReferenceFrame(genome, chromosomeName, ss, ee, bpp)
47920
-
47921
- }
47922
-
47923
47817
  const defaultNucleotideColors = {
47924
47818
  "A": "rgb( 0, 200, 0)",
47925
47819
  "C": "rgb( 0,0,200)",
@@ -48789,7 +48683,7 @@ class SampleNameControl {
48789
48683
  }
48790
48684
  }
48791
48685
 
48792
- browser.resize();
48686
+ browser.layoutChange();
48793
48687
 
48794
48688
 
48795
48689
  });
@@ -49019,6 +48913,69 @@ const SVGSaveControl = function (parent, browser) {
49019
48913
  button.addEventListener('click', () => browser.saveSVGtoFile({}));
49020
48914
  };
49021
48915
 
48916
+ const viewportColumnManager =
48917
+ {
48918
+ createColumns: (columnContainer, count) => {
48919
+
48920
+ for (let i = 0; i < count; i++) {
48921
+ if (0 === i) {
48922
+ createColumn(columnContainer, 'igv-column');
48923
+ } else {
48924
+ columnContainer.appendChild(div$1({class: 'igv-column-shim'}));
48925
+ createColumn(columnContainer, 'igv-column');
48926
+ }
48927
+ }
48928
+
48929
+ },
48930
+
48931
+ removeColumnAtIndex: (i, column) => {
48932
+ const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
48933
+ column.remove();
48934
+ shim.remove();
48935
+ },
48936
+
48937
+ insertAfter: referenceElement => {
48938
+
48939
+ const shim = div$1({class: 'igv-column-shim'});
48940
+ insertElementAfter(shim, referenceElement);
48941
+
48942
+ const column = div$1({class: 'igv-column'});
48943
+ insertElementAfter(column, shim);
48944
+
48945
+ return column
48946
+ },
48947
+
48948
+ insertBefore: (referenceElement, count) => {
48949
+
48950
+ for (let i = 0; i < count; i++) {
48951
+
48952
+ const column = div$1({class: 'igv-column'});
48953
+ insertElementBefore(column, referenceElement);
48954
+
48955
+ if (count > 1 && i > 0) {
48956
+ const columnShim = div$1({class: 'igv-column-shim'});
48957
+ insertElementBefore(columnShim, column);
48958
+ }
48959
+
48960
+ }
48961
+
48962
+ },
48963
+
48964
+ indexOfColumn: (columnContainer, column) => {
48965
+
48966
+ const allColumns = columnContainer.querySelectorAll('.igv-column');
48967
+
48968
+ for (let i = 0; i < allColumns.length; i++) {
48969
+ const c = allColumns[ i ];
48970
+ if (c === column) {
48971
+ return i
48972
+ }
48973
+ }
48974
+
48975
+ return undefined
48976
+ },
48977
+ };
48978
+
49022
48979
  /*
49023
48980
  * The MIT License (MIT)
49024
48981
  *
@@ -49251,7 +49208,7 @@ class RulerTrack {
49251
49208
  }
49252
49209
  }
49253
49210
 
49254
- supportsWholeGenome() {
49211
+ get supportsWholeGenome() {
49255
49212
  return true
49256
49213
  };
49257
49214
 
@@ -49622,8 +49579,8 @@ class Browser {
49622
49579
  this.svgSaveControl = new SVGSaveControl($toggle_button_container.get(0), this);
49623
49580
  }
49624
49581
 
49625
- if(config.customButtons) {
49626
- for(let b of config.customButtons) {
49582
+ if (config.customButtons) {
49583
+ for (let b of config.customButtons) {
49627
49584
  new CustomButton($toggle_button_container.get(0), this, b);
49628
49585
  }
49629
49586
  }
@@ -49784,7 +49741,6 @@ class Browser {
49784
49741
  return undefined
49785
49742
  }
49786
49743
  }
49787
-
49788
49744
  }
49789
49745
  }
49790
49746
 
@@ -49829,7 +49785,9 @@ class Browser {
49829
49785
  // Create ideogram and ruler track. Really this belongs in browser initialization, but creation is
49830
49786
  // deferred because ideogram and ruler are treated as "tracks", and tracks require a reference frame
49831
49787
  if (false !== session.showIdeogram) {
49832
- this.trackViews.push(new TrackView(this, this.columnContainer, new IdeogramTrack(this)));
49788
+ const ideogramTrack = new IdeogramTrack(this);
49789
+ ideogramTrack.id = 'ideogram';
49790
+ this.trackViews.push(new TrackView(this, this.columnContainer, ideogramTrack));
49833
49791
  }
49834
49792
 
49835
49793
  if (false !== session.showRuler) {
@@ -49874,6 +49832,11 @@ class Browser {
49874
49832
 
49875
49833
  await this.loadTrackList(trackConfigurations);
49876
49834
 
49835
+ // The ruler and ideogram tracks are not explicitly loaded, but needs updated nonetheless.
49836
+ for (let rtv of this.trackViews.filter((tv) => tv.track.type === 'ruler' || tv.track.type === 'ideogram')) {
49837
+ rtv.updateViews();
49838
+ }
49839
+
49877
49840
  this.updateUIWithReferenceFrameList();
49878
49841
 
49879
49842
  }
@@ -50052,23 +50015,19 @@ class Browser {
50052
50015
 
50053
50016
  async loadTrackList(configList) {
50054
50017
 
50055
- try {
50056
- const promises = [];
50057
- for (let config of configList) {
50058
- promises.push(this.loadTrack(config, false));
50059
- }
50018
+ const promises = [];
50019
+ for (let config of configList) {
50020
+ promises.push(this.loadTrack(config));
50021
+ }
50060
50022
 
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();
50023
+ const loadedTracks = await Promise.all(promises);
50024
+ const groupAutoscaleViews = this.trackViews.filter(function (trackView) {
50025
+ return trackView.track.autoscaleGroup
50026
+ });
50027
+ if (groupAutoscaleViews.length > 0) {
50028
+ this.updateViews();
50071
50029
  }
50030
+ return loadedTracks
50072
50031
  }
50073
50032
 
50074
50033
  async loadROI(config) {
@@ -50082,6 +50041,8 @@ class Browser {
50082
50041
  } else {
50083
50042
  this.roi.push(new ROI(config, this.genome));
50084
50043
  }
50044
+ // Force reload all views (force = true) to insure ROI features are loaded. Wasteful but this function is
50045
+ // rarely called.
50085
50046
  await this.updateViews(true);
50086
50047
  }
50087
50048
 
@@ -50093,14 +50054,14 @@ class Browser {
50093
50054
  }
50094
50055
  }
50095
50056
  for (let tv of this.trackViews) {
50096
- tv.updateViews(true);
50057
+ tv.repaintViews();
50097
50058
  }
50098
50059
  }
50099
50060
 
50100
50061
  clearROIs() {
50101
50062
  this.roi = [];
50102
50063
  for (let tv of this.trackViews) {
50103
- tv.updateViews(true);
50064
+ tv.repaintViews();
50104
50065
  }
50105
50066
  }
50106
50067
 
@@ -50112,24 +50073,12 @@ class Browser {
50112
50073
  /**
50113
50074
  * Return a promise to load a track.
50114
50075
  *
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
- *
50127
50076
  * @param config
50128
50077
  * @param doResize - undefined by default
50129
50078
  * @returns {*}
50130
50079
  */
50131
50080
 
50132
- async loadTrack(config, doResize) {
50081
+ async loadTrack(config) {
50133
50082
 
50134
50083
 
50135
50084
  // config might be json
@@ -50197,11 +50146,6 @@ class Browser {
50197
50146
  }
50198
50147
  msg += (": " + config.url);
50199
50148
  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
- }
50205
50149
  }
50206
50150
  }
50207
50151
 
@@ -50409,7 +50353,16 @@ class Browser {
50409
50353
 
50410
50354
  }
50411
50355
 
50356
+ /**
50357
+ * API function to signal that this browser visibility has changed, e.g. from hiding/showing in a tab interface.
50358
+ *
50359
+ * @returns {Promise<void>}
50360
+ */
50412
50361
  async visibilityChange() {
50362
+ this.layoutChange();
50363
+ }
50364
+
50365
+ async layoutChange() {
50413
50366
 
50414
50367
  const status = this.referenceFrameList.find(referenceFrame => referenceFrame.bpPerPixel < 0);
50415
50368
 
@@ -50425,44 +50378,11 @@ class Browser {
50425
50378
  this.navbarManager.navbarDidResize(this.$navigation.width(), isWGV);
50426
50379
  }
50427
50380
 
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();
50381
+ resize.call(this);
50382
+ await this.updateViews();
50463
50383
  }
50464
50384
 
50465
- async updateViews(force) {
50385
+ async updateViews() {
50466
50386
 
50467
50387
  const trackViews = this.trackViews;
50468
50388
 
@@ -50475,7 +50395,7 @@ class Browser {
50475
50395
  // Don't autoscale while dragging.
50476
50396
  if (this.dragObject) {
50477
50397
  for (let trackView of trackViews) {
50478
- await trackView.updateViews(force);
50398
+ await trackView.updateViews();
50479
50399
  }
50480
50400
  } else {
50481
50401
  // Group autoscale
@@ -50520,14 +50440,14 @@ class Browser {
50520
50440
  for (let trackView of groupTrackViews) {
50521
50441
  trackView.track.dataRange = dataRange;
50522
50442
  trackView.track.autoscale = false;
50523
- p.push(trackView.updateViews(force));
50443
+ p.push(trackView.updateViews());
50524
50444
  }
50525
50445
  await Promise.all(p);
50526
50446
  }
50527
50447
 
50528
50448
  }
50529
50449
 
50530
- await Promise.all(otherTracks.map(tv => tv.updateViews(force)));
50450
+ await Promise.all(otherTracks.map(tv => tv.updateViews()));
50531
50451
  // for (let trackView of otherTracks) {
50532
50452
  // await trackView.updateViews(force);
50533
50453
  // }
@@ -50535,6 +50455,12 @@ class Browser {
50535
50455
 
50536
50456
  }
50537
50457
 
50458
+ repaintViews() {
50459
+ for (let trackView of this.trackViews) {
50460
+ trackView.repaintViews();
50461
+ }
50462
+ }
50463
+
50538
50464
  updateLocusSearchWidget() {
50539
50465
 
50540
50466
  const referenceFrameList = this.referenceFrameList;
@@ -50599,49 +50525,50 @@ class Browser {
50599
50525
  }
50600
50526
  }
50601
50527
 
50602
- async presentMultiLocusPanel(alignment, referenceFrameLeft) {
50528
+ /**
50529
+ * Add a new multi-locus panel for the specified region
50530
+ * @param chr
50531
+ * @param start
50532
+ * @param end
50533
+ * @param referenceFrameLeft - optional, if supplied new panel should be placed to the immediate right
50534
+ */
50535
+ async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
50603
50536
 
50604
50537
  // account for reduced viewport width as a result of adding right mate pair panel
50605
50538
  const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
50606
-
50607
50539
  const scaleFactor = this.calculateViewportWidth(this.referenceFrameList.length) / this.calculateViewportWidth(1 + this.referenceFrameList.length);
50608
- adjustReferenceFrame(scaleFactor, referenceFrameLeft, viewportWidth, alignment.start, alignment.lengthOnRef);
50609
-
50610
- // create right mate pair reference frame
50611
- const mateChrName = this.genome.getChromosomeName(alignment.mate.chr);
50612
-
50613
- const referenceFrameRight = createReferenceFrameWithAlignment(this.genome, mateChrName, referenceFrameLeft.bpPerPixel, viewportWidth, alignment.mate.position, alignment.lengthOnRef);
50540
+ for (let refFrame of this.referenceFrameList) {
50541
+ refFrame.bpPerPixel *= scaleFactor;
50542
+ }
50614
50543
 
50615
- // add right mate panel beside left mate panel
50616
- const indexLeft = this.referenceFrameList.indexOf(referenceFrameLeft);
50617
- const indexRight = 1 + (this.referenceFrameList.indexOf(referenceFrameLeft));
50544
+ const bpp = (end - start) / viewportWidth;
50545
+ const newReferenceFrame = new ReferenceFrame(this.genome, chr, start, end, bpp);
50546
+ const indexLeft = referenceFrameLeft ? this.referenceFrameList.indexOf(referenceFrameLeft) : this.referenceFrameList.length - 1;
50547
+ const indexRight = 1 + indexLeft;
50618
50548
 
50549
+ // TODO -- this is really ugly
50619
50550
  const {$viewport} = this.trackViews[0].viewports[indexLeft];
50620
50551
  const viewportColumn = viewportColumnManager.insertAfter($viewport.get(0).parentElement);
50621
50552
 
50622
50553
  if (indexRight === this.referenceFrameList.length) {
50623
-
50624
- this.referenceFrameList.push(referenceFrameRight);
50625
-
50554
+ this.referenceFrameList.push(newReferenceFrame);
50626
50555
  for (let trackView of this.trackViews) {
50627
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
50556
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
50628
50557
  trackView.viewports.push(viewport);
50629
50558
  }
50630
-
50631
50559
  } else {
50632
-
50633
- this.referenceFrameList.splice(indexRight, 0, referenceFrameRight);
50634
-
50560
+ this.referenceFrameList.splice(indexRight, 0, newReferenceFrame);
50635
50561
  for (let trackView of this.trackViews) {
50636
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
50562
+ const viewport = createViewport(trackView, viewportColumn, newReferenceFrame);
50637
50563
  trackView.viewports.splice(indexRight, 0, viewport);
50638
50564
  }
50639
-
50640
50565
  }
50641
50566
 
50567
+
50642
50568
  this.centerLineList = this.createCenterLineList(this.columnContainer);
50643
50569
 
50644
- await this.resize();
50570
+ resize.call(this);
50571
+ await this.updateViews(true);
50645
50572
  }
50646
50573
 
50647
50574
  async removeMultiLocusPanel(referenceFrame) {
@@ -50670,7 +50597,13 @@ class Browser {
50670
50597
 
50671
50598
  }
50672
50599
 
50673
- async selectMultiLocusPanel(referenceFrame) {
50600
+ /**
50601
+ * Goto the locus represented by the selected referenceFrame, discarding all other panels
50602
+ *
50603
+ * @param referenceFrame
50604
+ * @returns {Promise<void>}
50605
+ */
50606
+ async gotoMultilocusPanel(referenceFrame) {
50674
50607
 
50675
50608
  const referenceFrameIndex = this.referenceFrameList.indexOf(referenceFrame);
50676
50609
 
@@ -50724,7 +50657,7 @@ class Browser {
50724
50657
 
50725
50658
  this.updateUIWithReferenceFrameList();
50726
50659
 
50727
- await this.updateViews(true);
50660
+ await this.updateViews();
50728
50661
 
50729
50662
  }
50730
50663
 
@@ -50947,7 +50880,6 @@ class Browser {
50947
50880
  }
50948
50881
 
50949
50882
 
50950
-
50951
50883
  json["tracks"] = trackJson;
50952
50884
 
50953
50885
  return json // This is an object, not a json string
@@ -50966,16 +50898,6 @@ class Browser {
50966
50898
  return surl
50967
50899
  }
50968
50900
 
50969
- currentLoci() {
50970
- const loci = [];
50971
- const anyTrackView = this.trackViews[0];
50972
- for (let {referenceFrame} of anyTrackView.viewports) {
50973
- const locusString = referenceFrame.getLocusString();
50974
- loci.push(locusString);
50975
- }
50976
- return loci
50977
- }
50978
-
50979
50901
  /**
50980
50902
  * Record a mouse click on a specific viewport. This might be the start of a drag operation. Dragging
50981
50903
  * (panning) is handled here so that the mouse can move out of a specific viewport (e.g. stray into another
@@ -51013,12 +50935,22 @@ class Browser {
51013
50935
 
51014
50936
  }
51015
50937
 
50938
+ /**
50939
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
50940
+ *
50941
+ * @param trackView
50942
+ */
51016
50943
  startTrackDrag(trackView) {
51017
50944
 
51018
50945
  this.dragTrack = trackView;
51019
50946
 
51020
50947
  }
51021
50948
 
50949
+ /**
50950
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
50951
+ *
50952
+ * @param dragDestination
50953
+ */
51022
50954
  updateTrackDrag(dragDestination) {
51023
50955
 
51024
50956
  if (dragDestination && this.dragTrack) {
@@ -51093,12 +51025,9 @@ class Browser {
51093
51025
  }
51094
51026
 
51095
51027
  addWindowResizeHandler() {
51096
- this.boundWindowResizeHandler = windowResizeHandler.bind(this);
51028
+ // Create a copy of the prototype "resize" function bound to this instance. Neccessary to support removing.
51029
+ this.boundWindowResizeHandler = resize.bind(this);
51097
51030
  window.addEventListener('resize', this.boundWindowResizeHandler);
51098
-
51099
- function windowResizeHandler() {
51100
- this.resize();
51101
- }
51102
51031
  }
51103
51032
 
51104
51033
  removeWindowResizeHandler() {
@@ -51174,6 +51103,7 @@ class Browser {
51174
51103
  }
51175
51104
 
51176
51105
  createCircularView(container, show) {
51106
+ show = show === true; // convert undefined to boolean
51177
51107
  this.circularView = createCircularView(container, this);
51178
51108
  this.circularViewControl = new CircularViewControl(this.$toggle_button_container.get(0), this);
51179
51109
  this.circularView.setAssembly({
@@ -51181,8 +51111,8 @@ class Browser {
51181
51111
  id: this.genome.id,
51182
51112
  chromosomes: makeCircViewChromosomes(this.genome)
51183
51113
  });
51184
- this.circularViewVisible = show === true;
51185
-
51114
+ this.circularViewVisible = show;
51115
+ return this.circularView
51186
51116
  }
51187
51117
 
51188
51118
  get circularViewVisible() {
@@ -51197,6 +51127,50 @@ class Browser {
51197
51127
  }
51198
51128
  }
51199
51129
 
51130
+ /**
51131
+ * Function called win window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
51132
+ * than class method because it needs to be copied and bound to specific instances of browser to support listener
51133
+ * removal
51134
+ *
51135
+ * @returns {Promise<void>}
51136
+ */
51137
+ async function resize() {
51138
+
51139
+ const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
51140
+
51141
+ for (let referenceFrame of this.referenceFrameList) {
51142
+
51143
+ const index = this.referenceFrameList.indexOf(referenceFrame);
51144
+
51145
+ const {chr, genome} = referenceFrame;
51146
+
51147
+ const {bpLength} = genome.getChromosome(referenceFrame.chr);
51148
+
51149
+ const viewportWidthBP = referenceFrame.toBP(viewportWidth);
51150
+
51151
+ // viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
51152
+ if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
51153
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
51154
+ referenceFrame.bpPerPixel = bpLength / viewportWidth;
51155
+ } else {
51156
+ // console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
51157
+ referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
51158
+ }
51159
+
51160
+ for (let {viewports} of this.trackViews) {
51161
+ viewports[index].setWidth(viewportWidth);
51162
+ }
51163
+
51164
+ }
51165
+
51166
+ this.updateUIWithReferenceFrameList();
51167
+
51168
+ //TODO -- update view only if needed. Reducing size never needed. Increasing size maybe
51169
+
51170
+ await this.updateViews(true);
51171
+ }
51172
+
51173
+
51200
51174
  function handleMouseMove(e) {
51201
51175
 
51202
51176
  e.preventDefault();