igv 2.11.1 → 2.12.1

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
@@ -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
 
@@ -20614,7 +20656,6 @@ class Viewport {
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
20658
  this.$content.height(contentHeight);
20617
- if (this.canvas._data) this.canvas._data.invalidate = true;
20618
20659
  }
20619
20660
 
20620
20661
  isLoading() {
@@ -20660,8 +20701,6 @@ class Viewport {
20660
20701
  this.popover.dispose();
20661
20702
  }
20662
20703
 
20663
- this.removeMouseHandlers();
20664
-
20665
20704
  this.$viewport.get(0).remove();
20666
20705
 
20667
20706
  // Null out all properties -- this should not be neccessary, but just in case there is a
@@ -22757,7 +22796,7 @@ const Cytoband = function (start, end, name, typestain) {
22757
22796
  }
22758
22797
  };
22759
22798
 
22760
- const _version = "2.11.1";
22799
+ const _version = "2.12.1";
22761
22800
  function version() {
22762
22801
  return _version
22763
22802
  }
@@ -22905,7 +22944,7 @@ class Genome {
22905
22944
  constructor(config, sequence, ideograms, aliases) {
22906
22945
 
22907
22946
  this.config = config;
22908
- this.id = config.id;
22947
+ this.id = config.id || generateGenomeID(config);
22909
22948
  this.sequence = sequence;
22910
22949
  this.chromosomeNames = sequence.chromosomeNames;
22911
22950
  this.chromosomes = sequence.chromosomes; // An object (functions as a dictionary)
@@ -23111,6 +23150,7 @@ class Genome {
23111
23150
  }
23112
23151
 
23113
23152
  async getSequence(chr, start, end) {
23153
+ chr = this.getChromosomeName(chr);
23114
23154
  return this.sequence.getSequence(chr, start, end)
23115
23155
  }
23116
23156
  }
@@ -23226,6 +23266,18 @@ function constructWG(genome, config) {
23226
23266
 
23227
23267
  }
23228
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
+
23229
23281
  /**
23230
23282
  * Created by dat on 9/16/16.
23231
23283
  */
@@ -23246,28 +23298,27 @@ class TrackViewport extends Viewport {
23246
23298
  this.$viewport.append(this.$spinner);
23247
23299
  this.$spinner.append($$1('<div>'));
23248
23300
 
23249
- const {track} = this.trackView;
23250
-
23301
+ const track = this.trackView.track;
23251
23302
  if ('sequence' !== track.type) {
23252
23303
  this.$zoomInNotice = this.createZoomInNotice(this.$content);
23253
23304
  }
23254
23305
 
23255
- if (track.name && "sequence" !== track.config.type) {
23256
-
23306
+ if (track.name && "sequence" !== track.id) {
23257
23307
  this.$trackLabel = $$1('<div class="igv-track-label">');
23258
23308
  this.$viewport.append(this.$trackLabel);
23259
23309
  this.setTrackLabel(track.name);
23260
-
23261
23310
  if (false === this.browser.trackLabelsVisible) {
23262
23311
  this.$trackLabel.hide();
23263
23312
  }
23264
-
23265
23313
  }
23266
23314
 
23267
23315
  this.stopSpinner();
23268
-
23269
23316
  this.addMouseHandlers();
23317
+ }
23270
23318
 
23319
+ setContentHeight(contentHeight) {
23320
+ super.setContentHeight(contentHeight);
23321
+ if (this.featureCache) this.featureCache.redraw = true;
23271
23322
  }
23272
23323
 
23273
23324
  setTrackLabel(label) {
@@ -23289,10 +23340,17 @@ class TrackViewport extends Viewport {
23289
23340
  }
23290
23341
  }
23291
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
+ */
23292
23350
  checkZoomIn() {
23293
23351
 
23294
- const showZoomInNotice = () => {
23295
- if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome()) {
23352
+ const zoomedOutOfWindow = () => {
23353
+ if (this.referenceFrame.chr.toLowerCase() === "all" && !this.trackView.track.supportsWholeGenome) {
23296
23354
  return true
23297
23355
  } else {
23298
23356
  const visibilityWindow = this.trackView.track.visibilityWindow;
@@ -23303,7 +23361,9 @@ class TrackViewport extends Viewport {
23303
23361
  };
23304
23362
 
23305
23363
  if (this.trackView.track && "sequence" === this.trackView.track.type && this.referenceFrame.bpPerPixel > 1) {
23306
- if (this.canvas) ;
23364
+ $$1(this.canvas).remove();
23365
+ this.canvas = undefined;
23366
+ //this.featureCache = undefined
23307
23367
  return false
23308
23368
  }
23309
23369
 
@@ -23311,29 +23371,30 @@ class TrackViewport extends Viewport {
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.canvas._data = undefined;
23320
- this.featureCache = undefined;
23321
- }
23322
- this.$zoomInNotice.show();
23323
23374
 
23324
- if (this.trackView.track.autoHeight) {
23325
- const minHeight = this.trackView.minHeight || 0;
23326
- this.setContentHeight(minHeight);
23327
- }
23375
+ if (zoomedOutOfWindow()) {
23328
23376
 
23329
- return false
23330
- } 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) {
23331
23393
  this.$zoomInNotice.hide();
23332
- return true
23333
23394
  }
23395
+ return true
23334
23396
  }
23335
23397
 
23336
- return true
23337
23398
  }
23338
23399
 
23339
23400
  /**
@@ -23400,9 +23461,8 @@ class TrackViewport extends Viewport {
23400
23461
  }
23401
23462
 
23402
23463
  /**
23403
- * Repaint the canvas for the current genomic state.
23464
+ * Repaint the canvas using the cached features
23404
23465
  *
23405
- * @returns {Promise<void>}
23406
23466
  */
23407
23467
  repaint() {
23408
23468
 
@@ -23416,21 +23476,13 @@ class TrackViewport extends Viewport {
23416
23476
  // const isWGV = GenomeUtils.isWholeGenomeView(this.browser.referenceFrameList[0].chr)
23417
23477
  const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
23418
23478
 
23419
- let pixelWidth;
23420
- const startBP = this.featureCache.startBP;
23421
- const endBP = this.featureCache.endBP;
23422
- let bpPerPixel = this.referenceFrame.bpPerPixel;
23423
- if (isWGV) {
23424
- pixelWidth = this.$viewport.width();
23425
- } else {
23426
- pixelWidth = 3 * this.$viewport.width();
23427
- }
23428
-
23479
+ // Canvas dimensions. There is no left-right panning for WGV so canvas width is viewport width.
23429
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();
23430
23482
  const viewportHeight = this.$viewport.height();
23431
23483
  const contentHeight = this.getContentHeight();
23432
23484
  const minHeight = roiFeatures ? Math.max(contentHeight, viewportHeight) : contentHeight; // Need to fill viewport for ROIs.
23433
- let pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23485
+ const pixelHeight = Math.min(minHeight, 3 * viewportHeight);
23434
23486
  if (0 === pixelWidth || 0 === pixelHeight) {
23435
23487
  if (this.canvas) {
23436
23488
  $$1(this.canvas).remove();
@@ -23439,30 +23491,25 @@ class TrackViewport extends Viewport {
23439
23491
  }
23440
23492
  const canvasTop = Math.max(0, -(this.$content.position().top) - viewportHeight);
23441
23493
 
23442
- // Always use high DPI if in compressed display mode, otherwise use preference setting;
23443
- let devicePixelRatio;
23444
- if ("FILL" === this.trackView.track.displayMode) {
23445
- devicePixelRatio = window.devicePixelRatio;
23446
- } else {
23447
- devicePixelRatio = (this.trackView.track.supportHiDPI === false) ? 1 : window.devicePixelRatio;
23448
- }
23449
-
23450
- 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);
23451
23498
 
23452
23499
  const newCanvas = $$1('<canvas class="igv-canvas">').get(0);
23453
- const ctx = newCanvas.getContext("2d");
23454
-
23455
23500
  newCanvas.style.width = pixelWidth + "px";
23456
23501
  newCanvas.style.height = pixelHeight + "px";
23502
+ newCanvas.style.left = pixelXOffset + "px";
23503
+ newCanvas.style.top = canvasTop + "px";
23457
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;
23458
23508
  newCanvas.width = devicePixelRatio * pixelWidth;
23459
23509
  newCanvas.height = devicePixelRatio * pixelHeight;
23460
23510
 
23511
+ const ctx = newCanvas.getContext("2d");
23461
23512
  ctx.scale(devicePixelRatio, devicePixelRatio);
23462
-
23463
- newCanvas.style.left = pixelXOffset + "px";
23464
- newCanvas.style.top = canvasTop + "px";
23465
-
23466
23513
  ctx.translate(0, -canvasTop);
23467
23514
 
23468
23515
  const drawConfiguration =
@@ -23483,16 +23530,16 @@ class TrackViewport extends Viewport {
23483
23530
 
23484
23531
  this.draw(drawConfiguration, features, roiFeatures);
23485
23532
 
23486
- // Attach metadata to canvas
23487
- newCanvas._data = {
23488
- chr: this.referenceFrame.chr, startBP, endBP, bpPerPixel, top: canvasTop, bottom: canvasTop + pixelHeight
23489
- };
23533
+ this.featureCache.canvasTop = canvasTop;
23534
+ this.featureCache.height = pixelHeight;
23490
23535
 
23491
23536
  if (this.canvas) {
23492
23537
  $$1(this.canvas).remove();
23493
23538
  }
23539
+ newCanvas._data = {
23540
+ chr: this.featureCache.chr, bpPerPixel, startBP, endBP, pixelHeight, pixelTop: canvasTop
23541
+ };
23494
23542
  this.canvas = newCanvas;
23495
- this.ctx = ctx;
23496
23543
  this.$content.append($$1(newCanvas));
23497
23544
 
23498
23545
  }
@@ -23534,17 +23581,18 @@ class TrackViewport extends Viewport {
23534
23581
 
23535
23582
  savePNG() {
23536
23583
 
23537
- if (!this.ctx) return
23584
+ if (!this.canvas) return
23538
23585
 
23539
- const canvasMetadata = this.canvas._data;
23540
- const canvasTop = canvasMetadata ? canvasMetadata.top : 0;
23586
+ const canvasMetadata = this.featureCache;
23587
+ const canvasTop = canvasMetadata ? canvasMetadata.canvasTop : 0;
23541
23588
  const devicePixelRatio = window.devicePixelRatio;
23542
23589
  const w = this.$viewport.width() * devicePixelRatio;
23543
23590
  const h = this.$viewport.height() * devicePixelRatio;
23544
23591
  const x = -$$1(this.canvas).position().left * devicePixelRatio;
23545
23592
  const y = (-this.$content.position().top - canvasTop) * devicePixelRatio;
23546
23593
 
23547
- 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);
23548
23596
  const exportCanvas = document.createElement('canvas');
23549
23597
  const exportCtx = exportCanvas.getContext('2d');
23550
23598
  exportCanvas.width = imageData.width;
@@ -23692,6 +23740,28 @@ class TrackViewport extends Viewport {
23692
23740
  }
23693
23741
  }
23694
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
+
23695
23765
  createZoomInNotice($parent) {
23696
23766
 
23697
23767
  const $container = $$1('<div>', {class: 'igv-zoom-in-notice-container'});
@@ -23713,15 +23783,33 @@ class TrackViewport extends Viewport {
23713
23783
 
23714
23784
  addMouseHandlers() {
23715
23785
 
23716
- this.addViewportContextMenuHandler(this.$viewport.get(0));
23786
+ const viewport = this.$viewport.get(0);
23717
23787
 
23718
- this.addViewportMouseDownHandler(this.$viewport.get(0));
23788
+ this.addViewportContextMenuHandler(viewport);
23719
23789
 
23720
- this.addViewportTouchStartHandler(this.$viewport.get(0));
23721
-
23722
- 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
+ };
23723
23810
 
23724
- this.addViewportTouchEndHandler(this.$viewport.get(0));
23811
+ viewport.addEventListener('mouseup', mu);
23812
+ viewport.addEventListener('touchend', mu);
23725
23813
 
23726
23814
  this.addViewportClickHandler(this.$viewport.get(0));
23727
23815
 
@@ -23731,32 +23819,9 @@ class TrackViewport extends Viewport {
23731
23819
 
23732
23820
  }
23733
23821
 
23734
- removeMouseHandlers() {
23735
-
23736
- this.removeViewportContextMenuHandler(this.$viewport.get(0));
23737
-
23738
- this.removeViewportMouseDownHandler(this.$viewport.get(0));
23739
-
23740
- this.removeViewportTouchStartHandler(this.$viewport.get(0));
23741
-
23742
- this.removeViewportMouseUpHandler(this.$viewport.get(0));
23743
-
23744
- this.removeViewportTouchEndHandler(this.$viewport.get(0));
23745
-
23746
- this.removeViewportClickHandler(this.$viewport.get(0));
23747
-
23748
- if (this.trackView.track.name && "sequence" !== this.trackView.track.config.type) {
23749
- this.removeTrackLabelClickHandler(this.$trackLabel.get(0));
23750
- }
23751
-
23752
- }
23753
-
23754
23822
  addViewportContextMenuHandler(viewport) {
23755
23823
 
23756
- this.boundContextMenuHandler = contextMenuHandler.bind(this);
23757
- viewport.addEventListener('contextmenu', this.boundContextMenuHandler);
23758
-
23759
- function contextMenuHandler(event) {
23824
+ viewport.addEventListener('contextmenu', (event) => {
23760
23825
 
23761
23826
  // Ignore if we are doing a drag. This can happen with touch events.
23762
23827
  if (this.browser.dragObject) {
@@ -23789,58 +23854,16 @@ class TrackViewport extends Viewport {
23789
23854
  menuItems.push({label: 'Save Image (SVG)', click: () => this.saveSVG()});
23790
23855
 
23791
23856
  this.browser.menuPopup.presentTrackContextMenu(event, menuItems);
23792
- }
23793
-
23794
- }
23795
-
23796
- removeViewportContextMenuHandler(viewport) {
23797
- viewport.removeEventListener('contextmenu', this.boundContextMenuHandler);
23798
- }
23799
-
23800
- addViewportMouseDownHandler(viewport) {
23801
- this.boundMouseDownHandler = mouseDownHandler.bind(this);
23802
- viewport.addEventListener('mousedown', this.boundMouseDownHandler);
23803
- }
23804
-
23805
- removeViewportMouseDownHandler(viewport) {
23806
- viewport.removeEventListener('mousedown', this.boundMouseDownHandler);
23807
- }
23808
-
23809
- addViewportTouchStartHandler(viewport) {
23810
- this.boundTouchStartHandler = mouseDownHandler.bind(this);
23811
- viewport.addEventListener('touchstart', this.boundTouchStartHandler);
23812
- }
23813
-
23814
- removeViewportTouchStartHandler(viewport) {
23815
- viewport.removeEventListener('touchstart', this.boundTouchStartHandler);
23816
- }
23857
+ });
23817
23858
 
23818
- addViewportMouseUpHandler(viewport) {
23819
- this.boundMouseUpHandler = mouseUpHandler.bind(this);
23820
- viewport.addEventListener('mouseup', this.boundMouseUpHandler);
23821
23859
  }
23822
23860
 
23823
- removeViewportMouseUpHandler(viewport) {
23824
- viewport.removeEventListener('mouseup', this.boundMouseUpHandler);
23825
- }
23826
-
23827
- addViewportTouchEndHandler(viewport) {
23828
- this.boundTouchEndHandler = mouseUpHandler.bind(this);
23829
- viewport.addEventListener('touchend', this.boundTouchEndHandler);
23830
- }
23831
-
23832
- removeViewportTouchEndHandler(viewport) {
23833
- viewport.removeEventListener('touchend', this.boundTouchEndHandler);
23834
- }
23835
23861
 
23836
23862
  addViewportClickHandler(viewport) {
23837
23863
 
23838
- this.boundClickHandler = clickHandler.bind(this);
23839
- viewport.addEventListener('click', this.boundClickHandler);
23840
-
23841
- function clickHandler(event) {
23864
+ viewport.addEventListener('click', (event) => {
23842
23865
 
23843
- if (this.enableClick) {
23866
+ if (this.enableClick && this.canvas) {
23844
23867
  if (3 === event.which || event.ctrlKey) {
23845
23868
  return
23846
23869
  }
@@ -23923,19 +23946,12 @@ class TrackViewport extends Viewport {
23923
23946
  lastClickTime = time;
23924
23947
 
23925
23948
  }
23926
- }
23927
- }
23928
-
23929
- removeViewportClickHandler(viewport) {
23930
- viewport.removeEventListener('click', this.boundClickHandler);
23949
+ });
23931
23950
  }
23932
23951
 
23933
23952
  addTrackLabelClickHandler(trackLabel) {
23934
23953
 
23935
- this.boundTrackLabelClickHandler = clickHandler.bind(this);
23936
- trackLabel.addEventListener('click', this.boundTrackLabelClickHandler);
23937
-
23938
- function clickHandler(event) {
23954
+ trackLabel.addEventListener('click', (event) => {
23939
23955
 
23940
23956
  event.stopPropagation();
23941
23957
 
@@ -23955,35 +23971,11 @@ class TrackViewport extends Viewport {
23955
23971
  this.popover = new Popover(this.browser.columnContainer, (track.name || ''));
23956
23972
  this.popover.presentContentWithEvent(event, str);
23957
23973
  }
23958
- }
23959
- }
23960
-
23961
- removeTrackLabelClickHandler(trackLabel) {
23962
- trackLabel.removeEventListener('click', this.boundTrackLabelClickHandler);
23974
+ });
23963
23975
  }
23964
23976
 
23965
23977
  }
23966
23978
 
23967
- function mouseDownHandler(event) {
23968
- this.enableClick = true;
23969
- this.browser.mouseDownOnViewport(event, this);
23970
- pageCoordinates$1(event);
23971
- }
23972
-
23973
- function mouseUpHandler(event) {
23974
-
23975
- // Any mouse up cancels drag and scrolling
23976
- if (this.browser.dragObject || this.browser.isScrolling) {
23977
- this.browser.cancelTrackPan();
23978
- // event.preventDefault();
23979
- // event.stopPropagation();
23980
- this.enableClick = false; // Until next mouse down
23981
- } else {
23982
- this.browser.cancelTrackPan();
23983
- this.browser.endTrackDrag();
23984
- }
23985
- }
23986
-
23987
23979
  function createClickState(event, viewport) {
23988
23980
 
23989
23981
  const referenceFrame = viewport.referenceFrame;
@@ -24239,11 +24231,13 @@ class RulerSweeper {
24239
24231
 
24240
24232
  validateLocusExtent(this.rulerViewport.browser.genome.getChromosome(this.rulerViewport.referenceFrame.chr).bpLength, extent, this.rulerViewport.browser.minimumBases());
24241
24233
 
24242
- this.rulerViewport.referenceFrame.bpPerPixel = (Math.round(extent.end) - Math.round(extent.start)) / this.rulerViewport.contentDiv.clientWidth;
24243
- this.rulerViewport.referenceFrame.start = Math.round(extent.start);
24244
- 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;
24245
24239
 
24246
- this.rulerViewport.browser.updateViews(this.rulerViewport.referenceFrame);
24240
+ this.rulerViewport.browser.updateViews();
24247
24241
  }
24248
24242
 
24249
24243
  }
@@ -24389,7 +24383,7 @@ class PairedAlignment {
24389
24383
  return this.firstAlignment.isProperPair()
24390
24384
  }
24391
24385
 
24392
- get tlen() {
24386
+ get fragmentLength() {
24393
24387
  return Math.abs(this.firstAlignment.fragmentLength)
24394
24388
  }
24395
24389
 
@@ -25832,7 +25826,7 @@ const BamUtils = {
25832
25826
  const lseq = readInt(ba, offset + 20);
25833
25827
  const mateChrIdx = readInt(ba, offset + 24);
25834
25828
  const matePos = readInt(ba, offset + 28);
25835
- const tlen = readInt(ba, offset + 32);
25829
+ const fragmentLength = readInt(ba, offset + 32);
25836
25830
 
25837
25831
  let readName = [];
25838
25832
  for (let j = 0; j < nl - 1; ++j) {
@@ -25873,7 +25867,7 @@ const BamUtils = {
25873
25867
  alignment.readName = readName;
25874
25868
  alignment.cigar = cigar;
25875
25869
  alignment.lengthOnRef = lengthOnRef;
25876
- alignment.fragmentLength = tlen;
25870
+ alignment.fragmentLength = fragmentLength;
25877
25871
  alignment.mq = mq;
25878
25872
 
25879
25873
  BamUtils.bam_tag2cigar(ba, blockEnd, p, lseq, alignment, cigarArray);
@@ -29026,6 +29020,10 @@ class BamSource {
29026
29020
  * THE SOFTWARE.
29027
29021
  */
29028
29022
 
29023
+ const fixColor = (colorString) => {
29024
+ return (colorString.indexOf(",") > 0 && !colorString.startsWith("rgb")) ?
29025
+ `rgb(${colorString})` : colorString
29026
+ };
29029
29027
 
29030
29028
  /**
29031
29029
  * A collection of properties and methods shared by all (or most) track types.
@@ -29070,8 +29068,8 @@ class TrackBase {
29070
29068
 
29071
29069
  this.order = config.order;
29072
29070
 
29073
- this.color = config.color;
29074
- this.altColor = config.altColor;
29071
+ if(config.color) this.color = fixColor(config.color);
29072
+ if(config.altColor) this.altColor = fixColor(config.altColor);
29075
29073
  if ("civic-ws" === config.sourceType) { // Ugly proxy for specialized track type
29076
29074
  this.defaultColor = "rgb(155,20,20)";
29077
29075
  } else {
@@ -29177,7 +29175,7 @@ class TrackBase {
29177
29175
  return state
29178
29176
  }
29179
29177
 
29180
- supportsWholeGenome() {
29178
+ get supportsWholeGenome() {
29181
29179
  return false
29182
29180
  }
29183
29181
 
@@ -30966,7 +30964,7 @@ class ChordSetManager {
30966
30964
  return this.tracks.find(t => name === t.name)
30967
30965
  }
30968
30966
 
30969
- getChordset(name) {
30967
+ getChordSet(name) {
30970
30968
  return this.chordSets.find(cs => name === cs.name)
30971
30969
  }
30972
30970
 
@@ -31084,9 +31082,8 @@ class CircularView {
31084
31082
  buttonContainer.appendChild(this.showControlsButton);
31085
31083
  this.showControlsButton.innerText = 'none' === this.controlPanel.style.display ? 'Show Controls' : 'Hide Controls';
31086
31084
  this.showControlsButton.addEventListener('click', (event) => {
31087
- const trackPanelRows = this.controlPanel.querySelectorAll('div');
31088
- if (trackPanelRows.length > 0) {
31089
-
31085
+ const panelRows = this.controlPanel.querySelectorAll('div');
31086
+ if (panelRows.length > 0) {
31090
31087
  if ('none' === this.controlPanel.style.display) {
31091
31088
  this.controlPanel.style.display = 'flex';
31092
31089
  event.target.innerText = 'Hide Controls';
@@ -31170,10 +31167,10 @@ class CircularView {
31170
31167
  hideShowButton.innerText = true === chordSet.visible ? 'Hide' : 'Show';
31171
31168
  hideShowButton.addEventListener('click', event => {
31172
31169
  if (true === chordSet.visible) {
31173
- this.hideTrack(chordSet.name);
31170
+ this.hideChordSet(chordSet.name);
31174
31171
  event.target.innerText = "Show";
31175
31172
  } else {
31176
- this.showTrack(chordSet.name);
31173
+ this.showChordSet(chordSet.name);
31177
31174
  event.target.innerText = "Hide";
31178
31175
  }
31179
31176
  });
@@ -31197,7 +31194,7 @@ class CircularView {
31197
31194
  color: chordSet.color,
31198
31195
  onChange: ({rgbaString}) => {
31199
31196
  colorPickerButton.style.backgroundColor = setAlpha(rgbaString, 1);
31200
- this.setTrackColor(chordSet.name, rgbaString);
31197
+ this.setColor(chordSet.name, rgbaString);
31201
31198
  alphaSlider.value = alphaToValue(getAlpha(chordSet.color));
31202
31199
  }
31203
31200
  };
@@ -31215,7 +31212,7 @@ class CircularView {
31215
31212
  alphaSlider.value = alphaToValue(getAlpha(chordSet.color));
31216
31213
  alphaSlider.oninput = () => {
31217
31214
  const v = valueToAlpha(alphaSlider.value);
31218
- this.setTrackColor(chordSet.name, setAlpha(chordSet.color, v));
31215
+ this.setColor(chordSet.name, setAlpha(chordSet.color, v));
31219
31216
  picker.setColor(chordSet.color);
31220
31217
  };
31221
31218
  row.appendChild(alphaSlider);
@@ -31235,11 +31232,13 @@ class CircularView {
31235
31232
  */
31236
31233
  setAssembly(igvGenome) {
31237
31234
 
31238
- if (this.genomeId === igvGenome.id) {
31235
+ const id = this.genomeId || guid();
31236
+
31237
+ if (this.genomeId === id) {
31239
31238
  return
31240
31239
  }
31241
31240
  this.chordManager.clearChords();
31242
- this.genomeId = igvGenome.id;
31241
+ this.genomeId = id;
31243
31242
  this.chrNames = new Set(igvGenome.chromosomes.map(chr => shortChrName$1(chr.name)));
31244
31243
 
31245
31244
  const regions = [];
@@ -31260,7 +31259,7 @@ class CircularView {
31260
31259
  this.assembly = {
31261
31260
  name: igvGenome.name,
31262
31261
  sequence: {
31263
- trackId: igvGenome.id,
31262
+ trackId: id,
31264
31263
  type: 'ReferenceSequenceTrack',
31265
31264
  adapter: {
31266
31265
  type: 'FromConfigSequenceAdapter',
@@ -31301,7 +31300,7 @@ class CircularView {
31301
31300
 
31302
31301
  addChords(newChords, options = {}) {
31303
31302
 
31304
- const tmp = options.track || options.name || "*";
31303
+ const tmp = options.name || options.track || "*";
31305
31304
  const trackName = tmp.split(' ')[0].replaceAll("%20", " ");
31306
31305
  const chordSetName = tmp.replaceAll("%20", " ");
31307
31306
 
@@ -31354,21 +31353,6 @@ class CircularView {
31354
31353
  this.viewState.pluginManager.rootModel.session.clearSelection();
31355
31354
  }
31356
31355
 
31357
- getFeature(featureId) {
31358
-
31359
- // TODO -- broken
31360
- // const display = this.viewState.pluginManager.rootModel.session.view.tracks[0].displays[0]
31361
- // const feature = display.data.features.get(featureId)
31362
- // return feature;
31363
-
31364
- const features = [...this.viewState.config.tracks[0].adapter.features.value];
31365
- for (let f of features) {
31366
- if (featureId === f.uniqueId) {
31367
- return f
31368
- }
31369
- }
31370
- }
31371
-
31372
31356
  /**
31373
31357
  * Deprecated, use "visible" property
31374
31358
  */
@@ -31391,23 +31375,23 @@ class CircularView {
31391
31375
  this.parent.style.display = isVisible ? 'block' : 'none';
31392
31376
  }
31393
31377
 
31394
- hideTrack(trackName) {
31395
- let track = this.getTrack(trackName);
31396
- if (track) {
31397
- track.visible = false;
31378
+ hideChordSet(trackName) {
31379
+ let cs = this.getChordSet(trackName);
31380
+ if (cs) {
31381
+ cs.visible = false;
31398
31382
  this.render();
31399
31383
  } else {
31400
31384
  console.warn(`No track with name: ${name}`);
31401
31385
  }
31402
31386
  }
31403
31387
 
31404
- showTrack(trackName) {
31405
- let track = this.getTrack(trackName);
31406
- if (track) {
31407
- track.visible = true;
31388
+ showChordSet(name) {
31389
+ let cs = this.getChordSet(name);
31390
+ if (cs) {
31391
+ cs.visible = true;
31408
31392
  this.render();
31409
31393
  } else {
31410
- console.warn(`No track with name: ${trackName}`);
31394
+ console.warn(`No track with name: ${name}`);
31411
31395
  }
31412
31396
  }
31413
31397
 
@@ -31433,12 +31417,12 @@ class CircularView {
31433
31417
  this.render();
31434
31418
  }
31435
31419
 
31436
- getTrack(name) {
31437
- return this.groupByTrack ? this.chordManager.getTrack(name) : this.chordManager.getChordset(name)
31420
+ getChordSet(name) {
31421
+ return this.groupByTrack ? this.chordManager.getTrack(name) : this.chordManager.getChordSet(name)
31438
31422
  }
31439
31423
 
31440
- setTrackColor(name, color) {
31441
- const t = this.getTrack(name);
31424
+ setColor(name, color) {
31425
+ const t = this.getChordSet(name);
31442
31426
  if (t) {
31443
31427
  t.color = color;
31444
31428
  const trackID = t.id;
@@ -31465,8 +31449,6 @@ class CircularView {
31465
31449
  // Remove all children from possible previous renders. React might do this for us when we render, but just in case.
31466
31450
  ReactDOM.unmountComponentAtNode(this.container);
31467
31451
 
31468
-
31469
-
31470
31452
  const visibleChordSets =
31471
31453
  (this.groupByTrack ? this.chordManager.tracks : this.chordManager.chordSets).filter(t => t.visible);
31472
31454
 
@@ -31545,7 +31527,7 @@ function guid() {
31545
31527
 
31546
31528
  function embedCSS$1() {
31547
31529
 
31548
- 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';
31530
+ 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';
31549
31531
 
31550
31532
  const style = document.createElement('style');
31551
31533
  style.setAttribute('type', 'text/css');
@@ -31707,6 +31689,9 @@ function sendChords(chords, track, refFrame, alpha) {
31707
31689
  `${encodedName} ${refFrame.chr}:${refFrame.start}-${refFrame.end}`;
31708
31690
  track.browser.circularView.addChords(chords, {track: chordSetName, color: chordSetColor, trackColor: trackColor});
31709
31691
 
31692
+ // show circular view if hidden
31693
+ if(!track.browser.circularViewVisible) track.browser.circularViewVisible = true;
31694
+
31710
31695
  }
31711
31696
 
31712
31697
 
@@ -31724,21 +31709,24 @@ function createCircularView(el, browser) {
31724
31709
  function addFrameForFeature(feature) {
31725
31710
 
31726
31711
  feature.chr = browser.genome.getChromosomeName(feature.refName);
31727
-
31728
- for (let referenceFrame of browser.currentReferenceFrames()) {
31712
+ let frameFound = false;
31713
+ for (let referenceFrame of browser.referenceFrameList) {
31729
31714
  const l = Locus.fromLocusString(referenceFrame.getLocusString());
31730
31715
  if (l.contains(feature)) {
31716
+ frameFound = true;
31731
31717
  break
31732
31718
  } else if (l.overlaps(feature)) {
31733
31719
  referenceFrame.extend(feature);
31734
- break
31735
- } else {
31736
- const flanking = 2000;
31737
- const center = (feature.start + feature.end) / 2;
31738
- browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
31720
+ frameFound = true;
31739
31721
  break
31740
31722
  }
31741
31723
  }
31724
+ if (!frameFound) {
31725
+ const flanking = 2000;
31726
+ const center = (feature.start + feature.end) / 2;
31727
+ browser.addMultiLocusPanel(feature.chr, center - flanking, center + flanking);
31728
+
31729
+ }
31742
31730
  }
31743
31731
  }
31744
31732
  });
@@ -32169,7 +32157,7 @@ class BAMTrack extends TrackBase {
32169
32157
  }
32170
32158
 
32171
32159
  // Add chords to JBrowse circular view, if present
32172
- if (this.browser.circularView && true === this.browser.circularViewVisible &&
32160
+ if (this.browser.circularView &&
32173
32161
  (this.alignmentTrack.hasPairs || this.alignmentTrack.hasSupplemental)) {
32174
32162
  menuItems.push('<hr/>');
32175
32163
  if (this.alignmentTrack.hasPairs) {
@@ -32694,7 +32682,7 @@ class AlignmentTrack {
32694
32682
  for (let alignment of alignmentRow.alignments) {
32695
32683
 
32696
32684
  this.hasPairs = this.hasPairs || alignment.isPaired();
32697
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
32685
+ if (this.browser.circularView) {
32698
32686
  // This is an expensive check, only do it if needed
32699
32687
  this.hasSupplemental = this.hasSupplemental || alignment.hasTag('SA');
32700
32688
  }
@@ -33053,10 +33041,7 @@ class AlignmentTrack {
33053
33041
  list.push({
33054
33042
  label: 'View read sequence',
33055
33043
  click: () => {
33056
- const alignment = clickedAlignment;
33057
- if (!alignment) return
33058
-
33059
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
33044
+ const seqstring = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
33060
33045
  if (!seqstring || "*" === seqstring) {
33061
33046
  Alert.presentAlert("Read sequence: *");
33062
33047
  } else {
@@ -33068,11 +33053,16 @@ class AlignmentTrack {
33068
33053
  if (isSecureContext()) {
33069
33054
  list.push({
33070
33055
  label: 'Copy read sequence',
33071
- click: () => {
33072
- const alignment = clickedAlignment;
33073
- if (!alignment) return
33074
- const seqstring = alignment.seq; //.map(b => String.fromCharCode(b)).join("");
33075
- navigator.clipboard.writeText(seqstring);
33056
+ click: async () => {
33057
+ const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
33058
+ try {
33059
+ //console.log(`seq: ${seq}`)
33060
+ await navigator.clipboard.writeText(seq);
33061
+ } catch (e) {
33062
+ console.error(e);
33063
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
33064
+ }
33065
+
33076
33066
  }
33077
33067
  });
33078
33068
  }
@@ -33082,8 +33072,7 @@ class AlignmentTrack {
33082
33072
  }
33083
33073
 
33084
33074
  // Experimental JBrowse feature
33085
- if (this.browser.circularView && true === this.browser.circularViewVisible
33086
- && (this.hasPairs || this.hasSupplemental)) {
33075
+ if (this.browser.circularView && (this.hasPairs || this.hasSupplemental)) {
33087
33076
  if (this.hasPairs) {
33088
33077
  list.push({
33089
33078
  label: 'Add discordant pairs to circular view',
@@ -33196,10 +33185,13 @@ class AlignmentTrack {
33196
33185
  case "pairOrientation":
33197
33186
 
33198
33187
  if (this.pairOrientation && alignment.pairOrientation) {
33199
- var oTypes = orientationTypes[this.pairOrientation];
33188
+ const oTypes = orientationTypes[this.pairOrientation];
33200
33189
  if (oTypes) {
33201
- var pairColor = this.pairColors[oTypes[alignment.pairOrientation]];
33202
- if (pairColor) color = pairColor;
33190
+ const pairColor = this.pairColors[oTypes[alignment.pairOrientation]];
33191
+ if (pairColor) {
33192
+ color = pairColor;
33193
+ break
33194
+ }
33203
33195
  }
33204
33196
  }
33205
33197
  if ("pairOrientation" === option) {
@@ -33540,7 +33532,9 @@ class RulerViewport extends TrackViewport {
33540
33532
  currentViewport = this;
33541
33533
  this.$tooltip.show();
33542
33534
  } else if (currentViewport.guid !== this.guid) {
33543
- currentViewport.$tooltip.hide();
33535
+ if (currentViewport.$tooltip) {
33536
+ currentViewport.$tooltip.hide();
33537
+ }
33544
33538
  this.$tooltip.show();
33545
33539
  currentViewport = this;
33546
33540
  } else {
@@ -33567,7 +33561,9 @@ class RulerViewport extends TrackViewport {
33567
33561
 
33568
33562
  // hide tooltip when movement stops
33569
33563
  clearTimeout(timer);
33570
- timer = setTimeout(() => this.$tooltip.hide(), toolTipTimeout);
33564
+ timer = setTimeout(() => {
33565
+ if (this.$tooltip) this.$tooltip.hide();
33566
+ }, toolTipTimeout);
33571
33567
 
33572
33568
  }
33573
33569
 
@@ -33586,69 +33582,6 @@ class RulerViewport extends TrackViewport {
33586
33582
 
33587
33583
  }
33588
33584
 
33589
- const viewportColumnManager =
33590
- {
33591
- createColumns: (columnContainer, count) => {
33592
-
33593
- for (let i = 0; i < count; i++) {
33594
- if (0 === i) {
33595
- createColumn(columnContainer, 'igv-column');
33596
- } else {
33597
- columnContainer.appendChild(div$1({class: 'igv-column-shim'}));
33598
- createColumn(columnContainer, 'igv-column');
33599
- }
33600
- }
33601
-
33602
- },
33603
-
33604
- removeColumnAtIndex: (i, column) => {
33605
- const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
33606
- column.remove();
33607
- shim.remove();
33608
- },
33609
-
33610
- insertAfter: referenceElement => {
33611
-
33612
- const shim = div$1({class: 'igv-column-shim'});
33613
- insertElementAfter(shim, referenceElement);
33614
-
33615
- const column = div$1({class: 'igv-column'});
33616
- insertElementAfter(column, shim);
33617
-
33618
- return column
33619
- },
33620
-
33621
- insertBefore: (referenceElement, count) => {
33622
-
33623
- for (let i = 0; i < count; i++) {
33624
-
33625
- const column = div$1({class: 'igv-column'});
33626
- insertElementBefore(column, referenceElement);
33627
-
33628
- if (count > 1 && i > 0) {
33629
- const columnShim = div$1({class: 'igv-column-shim'});
33630
- insertElementBefore(columnShim, column);
33631
- }
33632
-
33633
- }
33634
-
33635
- },
33636
-
33637
- indexOfColumn: (columnContainer, column) => {
33638
-
33639
- const allColumns = columnContainer.querySelectorAll('.igv-column');
33640
-
33641
- for (let i = 0; i < allColumns.length; i++) {
33642
- const c = allColumns[ i ];
33643
- if (c === column) {
33644
- return i
33645
- }
33646
- }
33647
-
33648
- return undefined
33649
- },
33650
- };
33651
-
33652
33585
  /*
33653
33586
  * The MIT License (MIT)
33654
33587
  *
@@ -33682,61 +33615,28 @@ class IdeogramViewport extends TrackViewport {
33682
33615
 
33683
33616
  initializationHelper() {
33684
33617
 
33685
- this.$ideogramCanvas = $$1('<canvas>', {class: 'igv-ideogram-canvas'});
33686
- this.$ideogramCanvas.insertBefore(this.$canvas);
33687
-
33688
- const canvas = this.$ideogramCanvas.get(0);
33689
- this.ideogram_ctx = canvas.getContext('2d');
33690
-
33691
- this.$canvas.remove();
33692
- this.canvas = undefined;
33693
- this.ctx = undefined;
33618
+ this.canvas = document.createElement('canvas');
33619
+ this.canvas.className = 'igv-ideogram-canvas';
33620
+ this.$content.append($$1(this.canvas));
33621
+ this.ideogram_ctx = this.canvas.getContext('2d');
33694
33622
 
33695
33623
  this.addMouseHandlers();
33696
-
33697
33624
  }
33698
33625
 
33699
33626
  addMouseHandlers() {
33700
- this.addBrowserObserver();
33701
33627
  this.addViewportClickHandler(this.$viewport.get(0));
33702
33628
  }
33703
33629
 
33704
- removeMouseHandlers() {
33705
- this.removeBrowserObserver();
33706
- this.removeViewportClickHandler(this.$viewport.get(0));
33707
-
33708
- }
33709
-
33710
- addBrowserObserver() {
33711
-
33712
- function observerHandler(referenceFrameList) {
33713
- const column = this.$viewport.get(0).parentElement;
33714
- if (null !== column) {
33715
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column);
33716
- // console.log(`ideogram-viewport - locus-change-handler index(${ index }) ${ referenceFrameList[ index ].getLocusString() } ${ Date.now() } `)
33717
- this.update(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height(), referenceFrameList[ index ]);
33718
- }
33719
- }
33720
-
33721
- this.boundObserverHandler = observerHandler.bind(this);
33722
- this.browser.on('locuschange', this.boundObserverHandler);
33723
- }
33724
-
33725
- removeBrowserObserver() {
33726
- this.browser.off('locuschange', this.boundObserverHandler);
33727
- }
33728
-
33729
33630
  addViewportClickHandler(viewport) {
33730
33631
 
33731
- function clickHandler(event) {
33632
+ this.boundClickHandler = clickHandler.bind(this);
33633
+ viewport.addEventListener('click', this.boundClickHandler);
33732
33634
 
33733
- const column = viewport.parentElement;
33734
- const index = viewportColumnManager.indexOfColumn(this.browser.columnContainer, column);
33735
- const referenceFrame = this.browser.referenceFrameList[ index ];
33635
+ function clickHandler(event) {
33736
33636
 
33737
33637
  const {xNormalized, width} = translateMouseCoordinates$1(event, this.ideogram_ctx.canvas);
33738
- const {bpLength} = this.browser.genome.getChromosome(referenceFrame.chr);
33739
- const locusLength = referenceFrame.bpPerPixel * width;
33638
+ const {bpLength} = this.browser.genome.getChromosome(this.referenceFrame.chr);
33639
+ const locusLength = this.referenceFrame.bpPerPixel * width;
33740
33640
  const chrCoveragePercentage = locusLength / bpLength;
33741
33641
 
33742
33642
  let xPercentage = xNormalized;
@@ -33751,21 +33651,14 @@ class IdeogramViewport extends TrackViewport {
33751
33651
  const ss = Math.round((xPercentage - (chrCoveragePercentage / 2.0)) * bpLength);
33752
33652
  const ee = Math.round((xPercentage + (chrCoveragePercentage / 2.0)) * bpLength);
33753
33653
 
33754
- referenceFrame.start = ss;
33755
- referenceFrame.end = ee;
33756
- referenceFrame.bpPerPixel = (ee - ss) / width;
33654
+ this.referenceFrame.start = ss;
33655
+ this.referenceFrame.end = ee;
33656
+ this.referenceFrame.bpPerPixel = (ee - ss) / width;
33757
33657
 
33758
- this.browser.updateViews(true);
33658
+ this.browser.updateViews(this.referenceFrame, this.browser.trackViews, true);
33759
33659
 
33760
33660
  }
33761
33661
 
33762
- this.boundClickHandler = clickHandler.bind(this);
33763
- viewport.addEventListener('click', this.boundClickHandler);
33764
-
33765
- }
33766
-
33767
- removeViewportClickHandler(viewport) {
33768
- viewport.removeEventListener('click', this.boundClickHandler);
33769
33662
  }
33770
33663
 
33771
33664
  setWidth(width) {
@@ -33786,10 +33679,20 @@ class IdeogramViewport extends TrackViewport {
33786
33679
  context.restore();
33787
33680
  }
33788
33681
 
33789
- update(context, pixelWidth, pixelHeight, referenceFrame) {
33790
- this.$canvas.hide();
33791
- IGVGraphics.configureHighDPICanvas(context, pixelWidth, pixelHeight);
33792
- this.trackView.track.draw({ context, referenceFrame, pixelWidth, pixelHeight });
33682
+ repaint() {
33683
+ this.draw({referenceFrame: this.referenceFrame});
33684
+ }
33685
+
33686
+ draw({referenceFrame}) {
33687
+
33688
+ IGVGraphics.configureHighDPICanvas(this.ideogram_ctx, this.$viewport.width(), this.$viewport.height());
33689
+
33690
+ this.trackView.track.draw({
33691
+ context: this.ideogram_ctx,
33692
+ referenceFrame,
33693
+ pixelWidth: this.$viewport.width(),
33694
+ pixelHeight: this.$viewport.height()
33695
+ });
33793
33696
  }
33794
33697
 
33795
33698
  startSpinner() {
@@ -33830,7 +33733,7 @@ function createViewport(trackView, column, referenceFrame, width) {
33830
33733
 
33831
33734
  if ('ruler' === trackView.track.type) {
33832
33735
  return new RulerViewport(trackView, column, referenceFrame, width)
33833
- } else if ('ideogram' === trackView.track.type) {
33736
+ } else if ('ideogram' === trackView.track.id) {
33834
33737
  return new IdeogramViewport(trackView, column, referenceFrame, width)
33835
33738
  } else {
33836
33739
  return new TrackViewport(trackView, column, referenceFrame, width)
@@ -34353,7 +34256,6 @@ const colorPickerExclusionTypes = new Set(['ruler', 'sequence', 'ideogram']);
34353
34256
  class TrackView {
34354
34257
 
34355
34258
  constructor(browser, columnContainer, track) {
34356
- this.namespace = `trackview-${guid$2()}`;
34357
34259
  this.browser = browser;
34358
34260
  this.track = track;
34359
34261
  track.trackView = this;
@@ -34381,7 +34283,7 @@ class TrackView {
34381
34283
  // Axis
34382
34284
  this.axis = this.createAxis(browser, this.track);
34383
34285
 
34384
- // Track Viewports
34286
+ // Create a viewport for each reference frame
34385
34287
  this.viewports = [];
34386
34288
  const viewportWidth = browser.calculateViewportWidth(referenceFrameList.length);
34387
34289
  const viewportColumns = columnContainer.querySelectorAll('.igv-column');
@@ -34454,7 +34356,6 @@ class TrackView {
34454
34356
 
34455
34357
  // Track Viewports
34456
34358
  for (let viewport of this.viewports) {
34457
- viewport.removeMouseHandlers();
34458
34359
  viewport.$viewport.remove();
34459
34360
  }
34460
34361
 
@@ -34572,9 +34473,8 @@ class TrackView {
34572
34473
 
34573
34474
  this.sampleNameViewport.viewport.style.height = `${newHeight}px`;
34574
34475
 
34575
- // If the track does not manage its own content height set it here
34476
+ // If the track does not manage its own content height set it equal to the viewport height here
34576
34477
  if (typeof this.track.computePixelHeight !== "function") {
34577
-
34578
34478
  for (let vp of this.viewports) {
34579
34479
  vp.setContentHeight(newHeight);
34580
34480
  }
@@ -34627,17 +34527,8 @@ class TrackView {
34627
34527
 
34628
34528
  isLoading() {
34629
34529
  for (let viewport of this.viewports) {
34630
- if (viewport.isLoading()) return true
34631
- }
34632
- }
34633
-
34634
- resize(viewportWidth) {
34635
-
34636
- for (let viewport of this.viewports) {
34637
- viewport.setWidth(viewportWidth);
34530
+ if (viewport.isLoading()) return true
34638
34531
  }
34639
-
34640
- this.updateViews(true);
34641
34532
  }
34642
34533
 
34643
34534
  /**
@@ -34647,7 +34538,7 @@ class TrackView {
34647
34538
  repaintViews() {
34648
34539
 
34649
34540
  for (let viewport of this.viewports) {
34650
- if(viewport.isVisible()) {
34541
+ if (viewport.isVisible()) {
34651
34542
  viewport.repaint();
34652
34543
  }
34653
34544
  }
@@ -34658,7 +34549,6 @@ class TrackView {
34658
34549
 
34659
34550
  // Repaint sample names last
34660
34551
  this.repaintSamples();
34661
-
34662
34552
  }
34663
34553
 
34664
34554
  repaintSamples() {
@@ -34674,13 +34564,25 @@ class TrackView {
34674
34564
  this.viewports.forEach(viewport => viewport.setTrackLabel(name));
34675
34565
  }
34676
34566
 
34567
+ /**
34568
+ * Called in response to a window resize event, change in # of multilocus panels, or other event that changes
34569
+ * the width of the track view.
34570
+ *
34571
+ * @param viewportWidth The width of each viewport in this track view.
34572
+ */
34573
+ resize(viewportWidth) {
34574
+ for (let viewport of this.viewports) {
34575
+ viewport.setWidth(viewportWidth);
34576
+ }
34577
+ }
34578
+
34677
34579
  /**
34678
34580
  * Update viewports to reflect current genomic state, possibly loading additional data.
34679
34581
  *
34680
34582
  * @param force - if true, force a repaint even if no new data is loaded
34681
34583
  * @returns {Promise<void>}
34682
34584
  */
34683
- async updateViews(force) {
34585
+ async updateViews() {
34684
34586
 
34685
34587
  if (!(this.browser && this.browser.referenceFrameList)) return
34686
34588
 
@@ -34689,18 +34591,25 @@ class TrackView {
34689
34591
  // Shift viewports left/right to current genomic state (pans canvas)
34690
34592
  visibleViewports.forEach(viewport => viewport.shift());
34691
34593
 
34692
- const isDragging = this.browser.dragObject;
34693
-
34694
- if (isDragging) {
34594
+ // If dragging (panning) return
34595
+ if (this.browser.dragObject) {
34695
34596
  return
34696
34597
  }
34697
34598
 
34698
- // rpv: viewports whose image (canvas) does not fully cover current genomic range
34699
- const reloadableViewports = force ? visibleViewports : this.viewportsToReload();
34599
+ // Get viewports to repaint
34600
+ let viewportsToRepaint = (this.track.autoscale || this.track.autoscaleGroup || this.track.type === 'ruler') ?
34601
+ visibleViewports :
34602
+ visibleViewports.filter(vp => vp.needsRepaint());
34603
+
34604
+ // Filter zoomed out views. This has the side effect or turning off or no the zoomed out notice
34605
+ viewportsToRepaint = viewportsToRepaint.filter(viewport => viewport.checkZoomIn());
34606
+
34607
+ // Get viewports that require a data load
34608
+ const viewportsToReload = viewportsToRepaint.filter(viewport => viewport.needsReload());
34700
34609
 
34701
34610
  // Trigger viewport to load features needed to cover current genomic range
34702
34611
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
34703
- for (let viewport of reloadableViewports) {
34612
+ for (let viewport of viewportsToReload) {
34704
34613
  await viewport.loadFeatures();
34705
34614
  }
34706
34615
 
@@ -34709,7 +34618,7 @@ class TrackView {
34709
34618
  // Special case for variant tracks in multilocus view. The # of rows to allocate to the variant (site)
34710
34619
  // section depends on data from all the views. We only need to adjust this however if any data was loaded
34711
34620
  // (i.e. reloadableViewports.length > 0)
34712
- if (this.track && typeof this.track.variantRowCount === 'function' && reloadableViewports.length > 0) {
34621
+ if (this.track && typeof this.track.variantRowCount === 'function' && viewportsToReload.length > 0) {
34713
34622
  let maxRow = 0;
34714
34623
  for (let viewport of this.viewports) {
34715
34624
  if (viewport.featureCache && viewport.featureCache.features) {
@@ -34747,19 +34656,8 @@ class TrackView {
34747
34656
  }
34748
34657
  }
34749
34658
 
34750
- // Must repaint all viewports if autoscaling. Always repaint ruler.
34751
- if (this.track.autoscale || this.track.autoscaleGroup || this.track.type === 'ruler' || force) {
34752
- for (let vp of visibleViewports) {
34753
- vp.repaint();
34754
- }
34755
- } else {
34756
- const reloadedViewports = new Set(reloadableViewports);
34757
- for (let vp of visibleViewports) {
34758
- const invalid = vp.canvas && vp.canvas._data && vp.canvas._data.invalidate;
34759
- if (invalid || reloadedViewports.has(vp)) {
34760
- vp.repaint();
34761
- }
34762
- }
34659
+ for (let vp of viewportsToRepaint) {
34660
+ vp.repaint();
34763
34661
  }
34764
34662
 
34765
34663
  this.adjustTrackHeight();
@@ -34787,7 +34685,7 @@ class TrackView {
34787
34685
  }
34788
34686
 
34789
34687
  /**
34790
- * Return a promise to get all in-view features. Used for group autoscaling.
34688
+ * Return a promise to get all in-view features across all viewports. Used for group autoscaling.
34791
34689
  */
34792
34690
  async getInViewFeatures() {
34793
34691
 
@@ -34795,16 +34693,11 @@ class TrackView {
34795
34693
  return []
34796
34694
  }
34797
34695
 
34798
- // List of viewports that need reloading
34799
- const rpV = this.viewportsToReload();
34800
- const promises = rpV.map(function (vp) {
34801
- return vp.loadFeatures()
34802
- });
34803
-
34804
- await Promise.all(promises);
34805
-
34806
34696
  let allFeatures = [];
34807
34697
  for (let vp of this.viewports) {
34698
+ if (vp.needsReload()) {
34699
+ await vp.loadFeatures();
34700
+ }
34808
34701
  if (vp.featureCache && vp.featureCache.features) {
34809
34702
  const referenceFrame = vp.referenceFrame;
34810
34703
  const start = referenceFrame.start;
@@ -34855,27 +34748,6 @@ class TrackView {
34855
34748
  }
34856
34749
  }
34857
34750
 
34858
- viewportsToReload(force) {
34859
-
34860
- // List of viewports that need reloading
34861
- const viewports = this.viewports.filter(viewport => {
34862
- if (!viewport.isVisible()) {
34863
- return false
34864
- }
34865
- if (!viewport.checkZoomIn()) {
34866
- return false
34867
- } else {
34868
- const referenceFrame = viewport.referenceFrame;
34869
- const chr = viewport.referenceFrame.chr;
34870
- const start = referenceFrame.start;
34871
- const end = start + referenceFrame.toBP($$1(viewport.contentDiv).width());
34872
- const bpPerPixel = referenceFrame.bpPerPixel;
34873
- return (!viewport.featureCache || !viewport.featureCache.containsRange(chr, start, end, bpPerPixel))
34874
- }
34875
- });
34876
- return viewports
34877
- }
34878
-
34879
34751
  createTrackScrollbar(browser) {
34880
34752
 
34881
34753
  const outerScroll = div$1();
@@ -34988,7 +34860,7 @@ class TrackView {
34988
34860
 
34989
34861
  addTrackDragMouseHandlers(browser) {
34990
34862
 
34991
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
34863
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
34992
34864
 
34993
34865
  let currentDragHandle = undefined;
34994
34866
 
@@ -35065,7 +34937,7 @@ class TrackView {
35065
34937
 
35066
34938
  removeTrackDragMouseHandlers() {
35067
34939
 
35068
- if ('ideogram' === this.track.type || 'ruler' === this.track.type) ; else {
34940
+ if ('ideogram' === this.track.id || 'ruler' === this.track.id) ; else {
35069
34941
  this.dragHandle.removeEventListener('mousedown', this.boundTrackDragMouseDownHandler);
35070
34942
  document.removeEventListener('mouseup', this.boundDocumentTrackDragMouseUpHandler);
35071
34943
  this.dragHandle.removeEventListener('mouseup', this.boundTrackDragMouseEnterHandler);
@@ -35928,7 +35800,7 @@ function decodeBed(tokens, header) {
35928
35800
  const eEnd = eStart + parseInt(exonSizes[i]);
35929
35801
  exons.push({start: eStart, end: eEnd});
35930
35802
  }
35931
- findUTRs(exons, feature.cdStart, feature.cdEnd);
35803
+ findUTRs$1(exons, feature.cdStart, feature.cdEnd);
35932
35804
  feature.exons = exons;
35933
35805
  }
35934
35806
 
@@ -36036,7 +35908,7 @@ function decodeGenePred(tokens, header) {
36036
35908
  const end = parseInt(exonEnds[i]);
36037
35909
  exons.push({start: start, end: end});
36038
35910
  }
36039
- findUTRs(exons, cdStart, cdEnd);
35911
+ findUTRs$1(exons, cdStart, cdEnd);
36040
35912
 
36041
35913
  feature.exons = exons;
36042
35914
 
@@ -36079,7 +35951,7 @@ function decodeGenePredExt(tokens, header) {
36079
35951
  const end = parseInt(exonEnds[i]);
36080
35952
  exons.push({start: start, end: end});
36081
35953
  }
36082
- findUTRs(exons, cdStart, cdEnd);
35954
+ findUTRs$1(exons, cdStart, cdEnd);
36083
35955
 
36084
35956
  feature.exons = exons;
36085
35957
 
@@ -36120,14 +35992,14 @@ function decodeReflat(tokens, header) {
36120
35992
  const end = parseInt(exonEnds[i]);
36121
35993
  exons.push({start: start, end: end});
36122
35994
  }
36123
- findUTRs(exons, cdStart, cdEnd);
35995
+ findUTRs$1(exons, cdStart, cdEnd);
36124
35996
 
36125
35997
  feature.exons = exons;
36126
35998
 
36127
35999
  return feature
36128
36000
  }
36129
36001
 
36130
- function findUTRs(exons, cdStart, cdEnd) {
36002
+ function findUTRs$1(exons, cdStart, cdEnd) {
36131
36003
 
36132
36004
  for (let exon of exons) {
36133
36005
  const end = exon.end;
@@ -39643,7 +39515,7 @@ class TextFeatureSource {
39643
39515
  this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
39644
39516
  this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
39645
39517
 
39646
- const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "tdf"]);
39518
+ const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
39647
39519
 
39648
39520
  if (config.features && Array.isArray(config.features)) {
39649
39521
  // Explicit array of features
@@ -40063,11 +39935,9 @@ class BufferedReader {
40063
39935
  }
40064
39936
  }
40065
39937
 
40066
- //table chromatinInteract
40067
-
40068
39938
  function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40069
39939
 
40070
- if (autoSql && 'chromatinInteract' === autoSql.table || "biginteract" === format) {
39940
+ if ("biginteract" === format || (autoSql && ('chromatinInteract' === autoSql.table) || 'interact' === autoSql.table)) {
40071
39941
  return decodeInteract
40072
39942
  } else {
40073
39943
  const standardFieldCount = definedFieldCount - 3;
@@ -40104,6 +39974,7 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40104
39974
  const eEnd = eStart + parseInt(exonSizes[i]);
40105
39975
  exons.push({start: eStart, end: eEnd});
40106
39976
  }
39977
+ findUTRs(exons, feature.cdStart, feature.cdEnd);
40107
39978
  feature.exons = exons;
40108
39979
  }
40109
39980
 
@@ -40121,6 +39992,28 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40121
39992
  }
40122
39993
  }
40123
39994
 
39995
+ //table chromatinInteract
39996
+ // "Chromatin interaction between two regions"
39997
+ // (
39998
+ // string chrom; "Chromosome (or contig, scaffold, etc.). For interchromosomal, use 2 records"
39999
+ // uint chromStart; "Start position of lower region. For interchromosomal, set to chromStart of this region"
40000
+ // uint chromEnd; "End position of upper region. For interchromosomal, set to chromEnd of this region"
40001
+ // string name; "Name of item, for display"
40002
+ // uint score; "Score from 0-1000"
40003
+ // double value; "Strength of interaction or other data value. Typically basis for score"
40004
+ // string exp; "Experiment name (metadata for filtering). Use . if not applicable"
40005
+ // 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."
40006
+ // string region1Chrom; "Chromosome of lower region. For non-directional interchromosomal, chrom of this region."
40007
+ // uint region1Start; "Start position of lower/this region"
40008
+ // uint region1End; "End position in chromosome of lower/this region"
40009
+ // string region1Name; "Identifier of lower/this region"
40010
+ // string region1Strand; "Orientation of lower/this region: + or -. Use . if not applicable"
40011
+ // string region2Chrom; "Chromosome of upper region. For non-directional interchromosomal, chrom of other region"
40012
+ // uint region2Start; "Start position in chromosome of upper/this region"
40013
+ // uint region2End; "End position in chromosome of upper/this region"
40014
+ // string region2Name; "Identifier of upper/this region"
40015
+ // string region2Strand; "Orientation of upper/this region: + or -. Use . if not applicable"
40016
+ // )
40124
40017
  function decodeInteract(feature, tokens) {
40125
40018
 
40126
40019
  feature.chr1 = tokens[5];
@@ -40140,6 +40033,24 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
40140
40033
  }
40141
40034
  }
40142
40035
 
40036
+ function findUTRs(exons, cdStart, cdEnd) {
40037
+
40038
+ for (let exon of exons) {
40039
+ const end = exon.end;
40040
+ const start = exon.start;
40041
+ if (end < cdStart || start > cdEnd) {
40042
+ exon.utr = true;
40043
+ } else {
40044
+ if (cdStart >= start && cdStart <= end) {
40045
+ exon.cdStart = cdStart;
40046
+ }
40047
+ if (cdEnd >= start && cdEnd <= end) {
40048
+ exon.cdEnd = cdEnd;
40049
+ }
40050
+ }
40051
+ }
40052
+ }
40053
+
40143
40054
  function scoreShade(score, color) {
40144
40055
  const alpha = Math.min(1, 0.11 + 0.89 * (score / 779));
40145
40056
  return alpha.toString()
@@ -41571,7 +41482,7 @@ class TDFSource {
41571
41482
  return features
41572
41483
  }
41573
41484
 
41574
- supportsWholeGenome() {
41485
+ get supportsWholeGenome() {
41575
41486
  return true
41576
41487
  }
41577
41488
  }
@@ -41679,11 +41590,12 @@ function zoomLevelForScale(chr, bpPerPixel, genome) {
41679
41590
  * THE SOFTWARE.
41680
41591
  */
41681
41592
 
41593
+ const bbFormats = new Set(['bigwig', 'bw', 'bigbed', 'bb', 'biginteract', 'biggenepred', 'bignarrowpeak']);
41682
41594
 
41683
41595
  function FeatureSource(config, genome) {
41684
41596
 
41685
41597
  const format = config.format ? config.format.toLowerCase() : undefined;
41686
- if ('bigwig' === format || 'bigbed' === format || 'bb' === format || "biginteract" === format) {
41598
+ if (bbFormats.has(format)) {
41687
41599
  return new BWSource(config, genome)
41688
41600
  } else if ("tdf" === format) {
41689
41601
  return new TDFSource(config, genome)
@@ -41692,38 +41604,6 @@ function FeatureSource(config, genome) {
41692
41604
  }
41693
41605
  }
41694
41606
 
41695
- const pairs =
41696
- [
41697
- ['A', 'T'],
41698
- ['G', 'C'],
41699
- ['Y', 'R'],
41700
- ['W', 'S'],
41701
- ['K', 'M'],
41702
- ['D', 'H'],
41703
- ['B', 'V']
41704
- ];
41705
-
41706
- const complements = new Map();
41707
- for (let p of pairs) {
41708
- const p1 = p[0];
41709
- const p2 = p[1];
41710
- complements.set(p1, p2);
41711
- complements.set(p2, p1);
41712
- complements.set(p1.toLowerCase(), p2.toLowerCase());
41713
- complements.set(p2.toLowerCase(), p1.toLowerCase());
41714
- }
41715
-
41716
- function reverseComplementSequence(sequence) {
41717
-
41718
- let comp = '';
41719
- let idx = sequence.length;
41720
- while (idx-- > 0) {
41721
- const base = sequence[idx];
41722
- comp += complements.has(base) ? complements.get(base) : base;
41723
- }
41724
- return comp
41725
- }
41726
-
41727
41607
  const GtexUtils = {
41728
41608
 
41729
41609
  getTissueInfo: function (datasetId, baseURL) {
@@ -41824,7 +41704,7 @@ function renderFeature(feature, bpStart, xScale, pixelHeight, ctx, options) {
41824
41704
  // single-exon transcript
41825
41705
  const xLeft = Math.max(0, coord.px);
41826
41706
  const xRight = Math.min(pixelWidth, coord.px1);
41827
- const width = Math.max(coord.pw, xRight - xLeft);
41707
+ const width = xRight - xLeft;
41828
41708
  ctx.fillRect(xLeft, py, width, h);
41829
41709
 
41830
41710
  // Arrows
@@ -41963,10 +41843,9 @@ function renderFeatureLabel(ctx, feature, featureX, featureX1, featureY, referen
41963
41843
  const textBox = ctx.measureText(name);
41964
41844
  const xleft = centerX - textBox.width / 2;
41965
41845
  const xright = centerX + textBox.width / 2;
41966
- if (options.labelAllFeatures || xleft > options.rowLastX[feature.row] || gtexSelection) {
41967
- options.rowLastX[feature.row] = xright;
41846
+ if (options.labelAllFeatures || xleft > options.rowLastLabelX[feature.row] || gtexSelection) {
41847
+ options.rowLastLabelX[feature.row] = xright;
41968
41848
  IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
41969
-
41970
41849
  }
41971
41850
  } finally {
41972
41851
  ctx.restore();
@@ -42267,7 +42146,7 @@ class FeatureTrack extends TrackBase {
42267
42146
 
42268
42147
  }
42269
42148
 
42270
- supportsWholeGenome() {
42149
+ get supportsWholeGenome() {
42271
42150
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false
42272
42151
  }
42273
42152
 
@@ -42323,24 +42202,27 @@ class FeatureTrack extends TrackBase {
42323
42202
 
42324
42203
  const rowFeatureCount = [];
42325
42204
  options.rowLastX = [];
42205
+ options.rowLastLabelX = [];
42326
42206
  for (let feature of featureList) {
42327
- const row = feature.row || 0;
42328
- if (rowFeatureCount[row] === undefined) {
42329
- rowFeatureCount[row] = 1;
42330
- } else {
42331
- rowFeatureCount[row]++;
42207
+ if(feature.start > bpStart && feature.end < bpEnd) {
42208
+ const row = this.displayMode === "COLLAPSED" ? 0 : feature.row || 0;
42209
+ if (rowFeatureCount[row] === undefined) {
42210
+ rowFeatureCount[row] = 1;
42211
+ } else {
42212
+ rowFeatureCount[row]++;
42213
+ }
42214
+ options.rowLastX[row] = -Number.MAX_SAFE_INTEGER;
42215
+ options.rowLastLabelX[row] = -Number.MAX_SAFE_INTEGER;
42332
42216
  }
42333
- options.rowLastX[row] = -Number.MAX_SAFE_INTEGER;
42334
42217
  }
42218
+ const pixelsPerFeature = pixelWidth / Math.max(...rowFeatureCount);
42335
42219
 
42336
42220
  let lastPxEnd = [];
42337
42221
  for (let feature of featureList) {
42338
42222
  if (feature.end < bpStart) continue
42339
42223
  if (feature.start > bpEnd) break
42340
-
42341
42224
  const row = this.displayMode === 'COLLAPSED' ? 0 : feature.row;
42342
- const featureDensity = pixelWidth / rowFeatureCount[row];
42343
- options.drawLabel = options.labelAllFeatures || featureDensity > 10;
42225
+ options.drawLabel = options.labelAllFeatures || pixelsPerFeature > 10;
42344
42226
  const pxEnd = Math.ceil((feature.end - bpStart) / bpPerPixel);
42345
42227
  const last = lastPxEnd[row];
42346
42228
  if (!last || pxEnd > last) {
@@ -42354,7 +42236,6 @@ class FeatureTrack extends TrackBase {
42354
42236
  ctx.globalAlpha = 1.0;
42355
42237
  }
42356
42238
  lastPxEnd[row] = pxEnd;
42357
-
42358
42239
  }
42359
42240
  }
42360
42241
 
@@ -42416,21 +42297,18 @@ class FeatureTrack extends TrackBase {
42416
42297
  const infoURL = this.infoURL || this.config.infoURL;
42417
42298
  for (let fd of featureData) {
42418
42299
  data.push(fd);
42419
- if (infoURL) {
42420
- if (fd.name &&
42421
- fd.name.toLowerCase() === "name" &&
42422
- fd.value &&
42423
- isString$3(fd.value) &&
42424
- !fd.value.startsWith("<")) {
42425
-
42426
-
42427
- const url = this.infoURL || this.config.infoURL;
42428
- const href = url.replace("$$", feature.name);
42429
- data.push({name: "Info", value: `<a target="_blank" href=${href}>${fd.value}</a>`});
42430
- }
42300
+ if (infoURL &&
42301
+ fd.name &&
42302
+ fd.name.toLowerCase() === "name" &&
42303
+ fd.value &&
42304
+ isString$3(fd.value) &&
42305
+ !fd.value.startsWith("<")) {
42306
+ const href = infoURL.replace("$$", feature.name);
42307
+ fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
42431
42308
  }
42432
42309
  }
42433
42310
 
42311
+
42434
42312
  //Array.prototype.push.apply(data, featureData);
42435
42313
 
42436
42314
  // If we have clicked over an exon number it.
@@ -42459,7 +42337,7 @@ class FeatureTrack extends TrackBase {
42459
42337
  }
42460
42338
 
42461
42339
  menuItemList() {
42462
-
42340
+
42463
42341
  const menuItems = [];
42464
42342
 
42465
42343
  if (this.render === renderSnp) {
@@ -42487,7 +42365,7 @@ class FeatureTrack extends TrackBase {
42487
42365
  menuItems.push(
42488
42366
  {
42489
42367
  object: $$1(createCheckbox$1(lut[displayMode], displayMode === this.displayMode)),
42490
- click: () => {
42368
+ click: () => {
42491
42369
  this.displayMode = displayMode;
42492
42370
  this.config.displayMode = displayMode;
42493
42371
  this.trackView.checkContentHeight();
@@ -42503,14 +42381,28 @@ class FeatureTrack extends TrackBase {
42503
42381
 
42504
42382
  contextMenuItemList(clickState) {
42505
42383
 
42506
- if (isSecureContext()) {
42507
- const features = this.clickedFeatures(clickState);
42508
- if (features.length > 1) {
42509
- features.sort((a, b) => (a.end - a.start) - (b.end - b.start));
42510
- }
42511
- const f = features[0]; // The longest feature
42512
- if ((f.end - f.start) <= 1000000) {
42513
- return [
42384
+ const features = this.clickedFeatures(clickState);
42385
+ if (features.length > 1) {
42386
+ features.sort((a, b) => (b.end - b.start) - (a.end - a.start));
42387
+ }
42388
+ const f = features[0]; // The shortest clicked feature
42389
+
42390
+ if ((f.end - f.start) <= 1000000) {
42391
+ const list = [{
42392
+ label: 'View feature sequence',
42393
+ click: async () => {
42394
+ let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
42395
+ if (f.strand === '-') {
42396
+ seq = reverseComplementSequence(seq);
42397
+ }
42398
+ if (!seq) seq = "Unknown sequence";
42399
+ Alert.presentAlert(seq);
42400
+
42401
+ }
42402
+ }];
42403
+
42404
+ if (isSecureContext() && navigator.clipboard !== undefined) {
42405
+ list.push(
42514
42406
  {
42515
42407
  label: 'Copy feature sequence',
42516
42408
  click: async () => {
@@ -42518,17 +42410,23 @@ class FeatureTrack extends TrackBase {
42518
42410
  if (f.strand === '-') {
42519
42411
  seq = reverseComplementSequence(seq);
42520
42412
  }
42521
- navigator.clipboard.writeText(seq);
42413
+ try {
42414
+ await navigator.clipboard.writeText(seq);
42415
+ } catch (e) {
42416
+ console.error(e);
42417
+ Alert.presentAlert(`error copying sequence to clipboard ${e}`);
42418
+ }
42522
42419
  }
42523
- },
42524
- '<hr/>'
42525
- ]
42420
+ }
42421
+ );
42526
42422
  }
42527
- }
42423
+ list.push('<hr/>');
42424
+ return list
42425
+ } else {
42528
42426
 
42529
- // Either not a secure context (i.e. http: protocol), or feature is too long
42530
- return undefined
42427
+ return undefined
42531
42428
 
42429
+ }
42532
42430
  }
42533
42431
 
42534
42432
  description() {
@@ -42549,7 +42447,7 @@ class FeatureTrack extends TrackBase {
42549
42447
  desc += "</html>";
42550
42448
  return desc
42551
42449
  } else {
42552
- return super.description();
42450
+ return super.description()
42553
42451
  }
42554
42452
 
42555
42453
  };
@@ -42870,7 +42768,7 @@ class WigTrack extends TrackBase {
42870
42768
  }
42871
42769
  }
42872
42770
 
42873
- supportsWholeGenome() {
42771
+ get supportsWholeGenome() {
42874
42772
  return !this.config.indexURL && this.config.supportsWholeGenome !== false
42875
42773
  }
42876
42774
 
@@ -43442,7 +43340,7 @@ class SegTrack extends TrackBase {
43442
43340
 
43443
43341
  }
43444
43342
 
43445
- supportsWholeGenome() {
43343
+ get supportsWholeGenome() {
43446
43344
  return (this.config.indexed === false || !this.config.indexURL) && this.config.supportsWholeGenome !== false
43447
43345
  }
43448
43346
 
@@ -43665,7 +43563,7 @@ class MergedTrack extends TrackBase {
43665
43563
  }
43666
43564
 
43667
43565
 
43668
- supportsWholeGenome() {
43566
+ get supportsWholeGenome() {
43669
43567
  return this.tracks.every(track => track.supportsWholeGenome())
43670
43568
  }
43671
43569
  }
@@ -43790,7 +43688,7 @@ class InteractionTrack extends TrackBase {
43790
43688
  return this
43791
43689
  }
43792
43690
 
43793
- supportsWholeGenome() {
43691
+ get supportsWholeGenome() {
43794
43692
  return true
43795
43693
  }
43796
43694
 
@@ -44176,7 +44074,7 @@ class InteractionTrack extends TrackBase {
44176
44074
  items = items.concat(MenuUtils.numericDataMenuItems(this.trackView));
44177
44075
  }
44178
44076
 
44179
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44077
+ if (this.browser.circularView) {
44180
44078
  items.push('<hr/>');
44181
44079
  items.push({
44182
44080
  label: 'Add interactions to circular view',
@@ -44194,7 +44092,7 @@ class InteractionTrack extends TrackBase {
44194
44092
  contextMenuItemList(clickState) {
44195
44093
 
44196
44094
  // Experimental JBrowse feature
44197
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44095
+ if (this.browser.circularView ) {
44198
44096
  const viewport = clickState.viewport;
44199
44097
  const list = [];
44200
44098
 
@@ -44225,7 +44123,6 @@ class InteractionTrack extends TrackBase {
44225
44123
  const inView = cachedFeatures.filter(f => f.drawState);
44226
44124
  if(inView.length === 0) return;
44227
44125
 
44228
- this.browser.circularViewVisible = true;
44229
44126
  const chords = makeBedPEChords(inView);
44230
44127
  sendChords(chords, this, refFrame, 0.5);
44231
44128
  //
@@ -44638,7 +44535,7 @@ class VariantTrack extends TrackBase {
44638
44535
 
44639
44536
  }
44640
44537
 
44641
- supportsWholeGenome() {
44538
+ get supportsWholeGenome() {
44642
44539
  return this.config.indexed === false || this.config.supportsWholeGenome === true
44643
44540
  }
44644
44541
 
@@ -45071,7 +44968,7 @@ class VariantTrack extends TrackBase {
45071
44968
  }
45072
44969
 
45073
44970
  // Experimental JBrowse circular view integration
45074
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44971
+ if (this.browser.circularView) {
45075
44972
 
45076
44973
  menuItems.push('<hr>');
45077
44974
  menuItems.push({
@@ -45091,7 +44988,7 @@ class VariantTrack extends TrackBase {
45091
44988
  contextMenuItemList(clickState) {
45092
44989
 
45093
44990
  // Experimental JBrowse circular view integration
45094
- if (this.browser.circularView && true === this.browser.circularViewVisible) {
44991
+ if (this.browser.circularView) {
45095
44992
  const viewport = clickState.viewport;
45096
44993
  const list = [];
45097
44994
 
@@ -45632,7 +45529,7 @@ class GWASTrack extends TrackBase {
45632
45529
  }
45633
45530
 
45634
45531
 
45635
- supportsWholeGenome() {
45532
+ get supportsWholeGenome() {
45636
45533
  return true
45637
45534
  }
45638
45535
 
@@ -46146,7 +46043,7 @@ class GCNVTrack extends TrackBase {
46146
46043
  return items
46147
46044
  }
46148
46045
 
46149
- supportsWholeGenome() {
46046
+ get supportsWholeGenome() {
46150
46047
  return false
46151
46048
  }
46152
46049
  }
@@ -46533,21 +46430,18 @@ class RNAFeatureSource {
46533
46430
  * THE SOFTWARE.
46534
46431
  */
46535
46432
 
46433
+ /**
46434
+ * Class represents an ideogram of a chromsome cytobands. It is used for the header of a track panel.
46435
+ *
46436
+ */
46536
46437
  class IdeogramTrack {
46537
46438
  constructor(browser) {
46538
-
46539
46439
  this.browser = browser;
46540
-
46541
46440
  this.type = 'ideogram';
46542
- this.id = this.type;
46543
-
46544
46441
  this.height = 16;
46545
-
46546
46442
  this.order = Number.MIN_SAFE_INTEGER;
46547
-
46548
46443
  this.disableButtons = true;
46549
46444
  this.ignoreTrackMenu = true;
46550
-
46551
46445
  }
46552
46446
 
46553
46447
  async getFeatures(chr, start, end) {
@@ -46814,7 +46708,7 @@ class SpliceJunctionTrack extends TrackBase {
46814
46708
 
46815
46709
  }
46816
46710
 
46817
- supportsWholeGenome() {
46711
+ get supportsWholeGenome() {
46818
46712
  return false
46819
46713
  }
46820
46714
 
@@ -47879,30 +47773,6 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
47879
47773
  })
47880
47774
  }
47881
47775
 
47882
- function adjustReferenceFrame(scaleFactor, referenceFrame, viewportWidth, alignmentStart, alignmentLength) {
47883
-
47884
- referenceFrame.bpPerPixel *= scaleFactor;
47885
-
47886
- const alignmentEE = alignmentStart + alignmentLength;
47887
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
47888
-
47889
- referenceFrame.start = alignmentCC - (referenceFrame.bpPerPixel * (viewportWidth / 2));
47890
- referenceFrame.end = referenceFrame.start + (referenceFrame.bpPerPixel * viewportWidth);
47891
- referenceFrame.locusSearchString = referenceFrame.getLocusString();
47892
- }
47893
-
47894
- function createReferenceFrameWithAlignment(genome, chromosomeName, bpp, viewportWidth, alignmentStart, alignmentLength) {
47895
-
47896
- const alignmentEE = alignmentStart + alignmentLength;
47897
- const alignmentCC = (alignmentStart + alignmentEE) / 2;
47898
-
47899
- const ss = alignmentCC - (bpp * (viewportWidth / 2));
47900
- const ee = ss + (bpp * viewportWidth);
47901
-
47902
- return new ReferenceFrame(genome, chromosomeName, ss, ee, bpp)
47903
-
47904
- }
47905
-
47906
47776
  const defaultNucleotideColors = {
47907
47777
  "A": "rgb( 0, 200, 0)",
47908
47778
  "C": "rgb( 0,0,200)",
@@ -49002,6 +48872,69 @@ const SVGSaveControl = function (parent, browser) {
49002
48872
  button.addEventListener('click', () => browser.saveSVGtoFile({}));
49003
48873
  };
49004
48874
 
48875
+ const viewportColumnManager =
48876
+ {
48877
+ createColumns: (columnContainer, count) => {
48878
+
48879
+ for (let i = 0; i < count; i++) {
48880
+ if (0 === i) {
48881
+ createColumn(columnContainer, 'igv-column');
48882
+ } else {
48883
+ columnContainer.appendChild(div$1({class: 'igv-column-shim'}));
48884
+ createColumn(columnContainer, 'igv-column');
48885
+ }
48886
+ }
48887
+
48888
+ },
48889
+
48890
+ removeColumnAtIndex: (i, column) => {
48891
+ const shim = 0 === i ? column.nextElementSibling : column.previousElementSibling;
48892
+ column.remove();
48893
+ shim.remove();
48894
+ },
48895
+
48896
+ insertAfter: referenceElement => {
48897
+
48898
+ const shim = div$1({class: 'igv-column-shim'});
48899
+ insertElementAfter(shim, referenceElement);
48900
+
48901
+ const column = div$1({class: 'igv-column'});
48902
+ insertElementAfter(column, shim);
48903
+
48904
+ return column
48905
+ },
48906
+
48907
+ insertBefore: (referenceElement, count) => {
48908
+
48909
+ for (let i = 0; i < count; i++) {
48910
+
48911
+ const column = div$1({class: 'igv-column'});
48912
+ insertElementBefore(column, referenceElement);
48913
+
48914
+ if (count > 1 && i > 0) {
48915
+ const columnShim = div$1({class: 'igv-column-shim'});
48916
+ insertElementBefore(columnShim, column);
48917
+ }
48918
+
48919
+ }
48920
+
48921
+ },
48922
+
48923
+ indexOfColumn: (columnContainer, column) => {
48924
+
48925
+ const allColumns = columnContainer.querySelectorAll('.igv-column');
48926
+
48927
+ for (let i = 0; i < allColumns.length; i++) {
48928
+ const c = allColumns[ i ];
48929
+ if (c === column) {
48930
+ return i
48931
+ }
48932
+ }
48933
+
48934
+ return undefined
48935
+ },
48936
+ };
48937
+
49005
48938
  /*
49006
48939
  * The MIT License (MIT)
49007
48940
  *
@@ -49234,7 +49167,7 @@ class RulerTrack {
49234
49167
  }
49235
49168
  }
49236
49169
 
49237
- supportsWholeGenome() {
49170
+ get supportsWholeGenome() {
49238
49171
  return true
49239
49172
  };
49240
49173
 
@@ -49811,7 +49744,9 @@ class Browser {
49811
49744
  // Create ideogram and ruler track. Really this belongs in browser initialization, but creation is
49812
49745
  // deferred because ideogram and ruler are treated as "tracks", and tracks require a reference frame
49813
49746
  if (false !== session.showIdeogram) {
49814
- this.trackViews.push(new TrackView(this, this.columnContainer, new IdeogramTrack(this)));
49747
+ const ideogramTrack = new IdeogramTrack(this);
49748
+ ideogramTrack.id = 'ideogram';
49749
+ this.trackViews.push(new TrackView(this, this.columnContainer, ideogramTrack));
49815
49750
  }
49816
49751
 
49817
49752
  if (false !== session.showRuler) {
@@ -49856,8 +49791,8 @@ class Browser {
49856
49791
 
49857
49792
  await this.loadTrackList(trackConfigurations);
49858
49793
 
49859
- // The ruler track is not explicitly loaded, but needs updated nonetheless.
49860
- for (let rtv of this.trackViews.filter((tv) => tv.track.type === 'ruler')) {
49794
+ // The ruler and ideogram tracks are not explicitly loaded, but needs updated nonetheless.
49795
+ for (let rtv of this.trackViews.filter((tv) => tv.track.type === 'ruler' || tv.track.type === 'ideogram')) {
49861
49796
  rtv.updateViews();
49862
49797
  }
49863
49798
 
@@ -50393,10 +50328,11 @@ class Browser {
50393
50328
  this.navbarManager.navbarDidResize(this.$navigation.width(), isWGV);
50394
50329
  }
50395
50330
 
50396
- await resize.call(this);
50331
+ resize.call(this);
50332
+ await this.updateViews();
50397
50333
  }
50398
50334
 
50399
- async updateViews(force) {
50335
+ async updateViews() {
50400
50336
 
50401
50337
  const trackViews = this.trackViews;
50402
50338
 
@@ -50409,7 +50345,7 @@ class Browser {
50409
50345
  // Don't autoscale while dragging.
50410
50346
  if (this.dragObject) {
50411
50347
  for (let trackView of trackViews) {
50412
- await trackView.updateViews(force);
50348
+ await trackView.updateViews();
50413
50349
  }
50414
50350
  } else {
50415
50351
  // Group autoscale
@@ -50454,14 +50390,14 @@ class Browser {
50454
50390
  for (let trackView of groupTrackViews) {
50455
50391
  trackView.track.dataRange = dataRange;
50456
50392
  trackView.track.autoscale = false;
50457
- p.push(trackView.updateViews(force));
50393
+ p.push(trackView.updateViews());
50458
50394
  }
50459
50395
  await Promise.all(p);
50460
50396
  }
50461
50397
 
50462
50398
  }
50463
50399
 
50464
- await Promise.all(otherTracks.map(tv => tv.updateViews(force)));
50400
+ await Promise.all(otherTracks.map(tv => tv.updateViews()));
50465
50401
  // for (let trackView of otherTracks) {
50466
50402
  // await trackView.updateViews(force);
50467
50403
  // }
@@ -50539,50 +50475,6 @@ class Browser {
50539
50475
  }
50540
50476
  }
50541
50477
 
50542
- async presentMultiLocusPanel(alignment, referenceFrameLeft) {
50543
-
50544
- // account for reduced viewport width as a result of adding right mate pair panel
50545
- const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
50546
-
50547
- const scaleFactor = this.calculateViewportWidth(this.referenceFrameList.length) / this.calculateViewportWidth(1 + this.referenceFrameList.length);
50548
- adjustReferenceFrame(scaleFactor, referenceFrameLeft, viewportWidth, alignment.start, alignment.lengthOnRef);
50549
-
50550
- // create right mate pair reference frame
50551
- const mateChrName = this.genome.getChromosomeName(alignment.mate.chr);
50552
-
50553
- const referenceFrameRight = createReferenceFrameWithAlignment(this.genome, mateChrName, referenceFrameLeft.bpPerPixel, viewportWidth, alignment.mate.position, alignment.lengthOnRef);
50554
-
50555
- // add right mate panel beside left mate panel
50556
- const indexLeft = this.referenceFrameList.indexOf(referenceFrameLeft);
50557
- const indexRight = 1 + (this.referenceFrameList.indexOf(referenceFrameLeft));
50558
-
50559
- const {$viewport} = this.trackViews[0].viewports[indexLeft];
50560
- const viewportColumn = viewportColumnManager.insertAfter($viewport.get(0).parentElement);
50561
-
50562
- if (indexRight === this.referenceFrameList.length) {
50563
-
50564
- this.referenceFrameList.push(referenceFrameRight);
50565
-
50566
- for (let trackView of this.trackViews) {
50567
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
50568
- trackView.viewports.push(viewport);
50569
- }
50570
-
50571
- } else {
50572
-
50573
- this.referenceFrameList.splice(indexRight, 0, referenceFrameRight);
50574
-
50575
- for (let trackView of this.trackViews) {
50576
- const viewport = createViewport(trackView, viewportColumn, referenceFrameRight);
50577
- trackView.viewports.splice(indexRight, 0, viewport);
50578
- }
50579
- }
50580
-
50581
- this.centerLineList = this.createCenterLineList(this.columnContainer);
50582
-
50583
- await resize.call(this);
50584
- }
50585
-
50586
50478
  /**
50587
50479
  * Add a new multi-locus panel for the specified region
50588
50480
  * @param chr
@@ -50590,7 +50482,7 @@ class Browser {
50590
50482
  * @param end
50591
50483
  * @param referenceFrameLeft - optional, if supplied new panel should be placed to the immediate right
50592
50484
  */
50593
- async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
50485
+ async addMultiLocusPanel(chr, start, end, referenceFrameLeft) {
50594
50486
 
50595
50487
  // account for reduced viewport width as a result of adding right mate pair panel
50596
50488
  const viewportWidth = this.calculateViewportWidth(1 + this.referenceFrameList.length);
@@ -50626,6 +50518,7 @@ class Browser {
50626
50518
  this.centerLineList = this.createCenterLineList(this.columnContainer);
50627
50519
 
50628
50520
  resize.call(this);
50521
+ await this.updateViews(true);
50629
50522
  }
50630
50523
 
50631
50524
  async removeMultiLocusPanel(referenceFrame) {
@@ -50955,11 +50848,6 @@ class Browser {
50955
50848
  return surl
50956
50849
  }
50957
50850
 
50958
- currentReferenceFrames() {
50959
- const anyTrackView = this.trackViews[0];
50960
- return anyTrackView.viewports.map(vp => vp.referenceFrame)
50961
- }
50962
-
50963
50851
  /**
50964
50852
  * Record a mouse click on a specific viewport. This might be the start of a drag operation. Dragging
50965
50853
  * (panning) is handled here so that the mouse can move out of a specific viewport (e.g. stray into another
@@ -50997,12 +50885,22 @@ class Browser {
50997
50885
 
50998
50886
  }
50999
50887
 
50888
+ /**
50889
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
50890
+ *
50891
+ * @param trackView
50892
+ */
51000
50893
  startTrackDrag(trackView) {
51001
50894
 
51002
50895
  this.dragTrack = trackView;
51003
50896
 
51004
50897
  }
51005
50898
 
50899
+ /**
50900
+ * Track drag here refers to vertical dragging to reorder tracks, not horizontal panning.
50901
+ *
50902
+ * @param dragDestination
50903
+ */
51006
50904
  updateTrackDrag(dragDestination) {
51007
50905
 
51008
50906
  if (dragDestination && this.dragTrack) {
@@ -51155,6 +51053,7 @@ class Browser {
51155
51053
  }
51156
51054
 
51157
51055
  createCircularView(container, show) {
51056
+ show = show === true; // convert undefined to boolean
51158
51057
  this.circularView = createCircularView(container, this);
51159
51058
  this.circularViewControl = new CircularViewControl(this.$toggle_button_container.get(0), this);
51160
51059
  this.circularView.setAssembly({
@@ -51162,8 +51061,8 @@ class Browser {
51162
51061
  id: this.genome.id,
51163
51062
  chromosomes: makeCircViewChromosomes(this.genome)
51164
51063
  });
51165
- this.circularViewVisible = show === true;
51166
-
51064
+ this.circularViewVisible = show;
51065
+ return this.circularView
51167
51066
  }
51168
51067
 
51169
51068
  get circularViewVisible() {
@@ -51216,7 +51115,9 @@ async function resize() {
51216
51115
 
51217
51116
  this.updateUIWithReferenceFrameList();
51218
51117
 
51219
- await this.updateViews(true);
51118
+ //TODO -- update view only if needed. Reducing size never needed. Increasing size maybe
51119
+
51120
+ await this.updateViews(true);
51220
51121
  }
51221
51122
 
51222
51123