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