igv 2.13.3 → 2.13.4
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 +346 -291
- package/dist/igv.esm.min.js +8 -9
- package/dist/igv.esm.min.js.map +1 -1
- package/dist/igv.js +9622 -17439
- package/dist/igv.min.js +5 -7
- package/dist/igv.min.js.map +1 -1
- package/package.json +1 -1
package/dist/igv.esm.js
CHANGED
|
@@ -21388,7 +21388,6 @@ class SequenceTrack {
|
|
|
21388
21388
|
|
|
21389
21389
|
let sequence = options.features.sequence;
|
|
21390
21390
|
if(!sequence) {
|
|
21391
|
-
console.error("No sequence");
|
|
21392
21391
|
return
|
|
21393
21392
|
}
|
|
21394
21393
|
|
|
@@ -21555,17 +21554,9 @@ class Viewport {
|
|
|
21555
21554
|
this.alert = new AlertDialog(this.$viewport.get(0));
|
|
21556
21555
|
}
|
|
21557
21556
|
|
|
21558
|
-
this
|
|
21559
|
-
this.$viewport.
|
|
21560
|
-
|
|
21561
|
-
this.$content.height(this.$viewport.height());
|
|
21562
|
-
this.contentDiv = this.$content.get(0);
|
|
21557
|
+
this.contentTop = 0;
|
|
21558
|
+
this.contentHeight = this.$viewport.height();
|
|
21563
21559
|
|
|
21564
|
-
// this.$canvas = $('<canvas>')
|
|
21565
|
-
// this.$content.append(this.$canvas)
|
|
21566
|
-
//
|
|
21567
|
-
// this.canvas = this.$canvas.get(0)
|
|
21568
|
-
// this.ctx = this.canvas.getContext("2d")
|
|
21569
21560
|
|
|
21570
21561
|
this.$viewport.width(width);
|
|
21571
21562
|
|
|
@@ -21581,7 +21572,8 @@ class Viewport {
|
|
|
21581
21572
|
if (!this.messageDiv) {
|
|
21582
21573
|
this.messageDiv = document.createElement('div');
|
|
21583
21574
|
this.messageDiv.className = 'igv-viewport-message';
|
|
21584
|
-
this.contentDiv.append(this.messageDiv)
|
|
21575
|
+
//this.contentDiv.append(this.messageDiv)
|
|
21576
|
+
this.$viewport.append($$1(this.messageDiv));
|
|
21585
21577
|
}
|
|
21586
21578
|
this.messageDiv.textContent = message;
|
|
21587
21579
|
this.messageDiv.style.display = 'inline-block';
|
|
@@ -21610,15 +21602,15 @@ class Viewport {
|
|
|
21610
21602
|
|
|
21611
21603
|
setTop(contentTop) {
|
|
21612
21604
|
|
|
21613
|
-
|
|
21614
|
-
|
|
21615
|
-
const viewBottom = viewTop + viewportHeight;
|
|
21616
|
-
|
|
21617
|
-
this.$content.css('top', `${contentTop}px`);
|
|
21605
|
+
this.contentTop = contentTop;
|
|
21606
|
+
this.$viewport.height();
|
|
21618
21607
|
|
|
21619
|
-
|
|
21620
|
-
|
|
21621
|
-
|
|
21608
|
+
//this.$content.css('top', `${contentTop}px`)
|
|
21609
|
+
//
|
|
21610
|
+
// if (undefined === this.canvasVerticalRange || this.canvasVerticalRange.bottom < viewBottom || this.canvasVerticalRange.top > viewTop) {
|
|
21611
|
+
// console.log("Repaint " + this.canvasVerticalRange)
|
|
21612
|
+
// this.repaint()
|
|
21613
|
+
// }
|
|
21622
21614
|
|
|
21623
21615
|
}
|
|
21624
21616
|
|
|
@@ -21646,7 +21638,8 @@ class Viewport {
|
|
|
21646
21638
|
} else if (typeof track.computePixelHeight === 'function') {
|
|
21647
21639
|
if (features && features.length > 0) {
|
|
21648
21640
|
let requiredContentHeight = track.computePixelHeight(features);
|
|
21649
|
-
let currentContentHeight = this.$content.height()
|
|
21641
|
+
//let currentContentHeight = this.$content.height()
|
|
21642
|
+
let currentContentHeight = this.contentHeight;
|
|
21650
21643
|
if (requiredContentHeight !== currentContentHeight) {
|
|
21651
21644
|
this.setContentHeight(requiredContentHeight);
|
|
21652
21645
|
}
|
|
@@ -21655,14 +21648,12 @@ class Viewport {
|
|
|
21655
21648
|
}
|
|
21656
21649
|
|
|
21657
21650
|
getContentHeight() {
|
|
21658
|
-
return this.$content.height()
|
|
21651
|
+
//return this.$content.height()
|
|
21652
|
+
return this.contentHeight
|
|
21659
21653
|
}
|
|
21660
21654
|
|
|
21661
21655
|
setContentHeight(contentHeight) {
|
|
21662
|
-
|
|
21663
|
-
// Maximum height of a canvas is ~32,000 pixels on Chrome, possibly smaller on other platforms
|
|
21664
|
-
contentHeight = Math.min(contentHeight, 32000);
|
|
21665
|
-
this.$content.height(contentHeight);
|
|
21656
|
+
this.contentHeight = contentHeight;
|
|
21666
21657
|
}
|
|
21667
21658
|
|
|
21668
21659
|
isLoading() {
|
|
@@ -21686,7 +21677,7 @@ class Viewport {
|
|
|
21686
21677
|
}
|
|
21687
21678
|
|
|
21688
21679
|
getContentTop() {
|
|
21689
|
-
return this.
|
|
21680
|
+
return this.contentTop
|
|
21690
21681
|
}
|
|
21691
21682
|
|
|
21692
21683
|
containsPosition(chr, position) {
|
|
@@ -23852,7 +23843,7 @@ const Cytoband = function (start, end, name, typestain) {
|
|
|
23852
23843
|
}
|
|
23853
23844
|
};
|
|
23854
23845
|
|
|
23855
|
-
const _version = "2.13.
|
|
23846
|
+
const _version = "2.13.4";
|
|
23856
23847
|
function version() {
|
|
23857
23848
|
return _version
|
|
23858
23849
|
}
|
|
@@ -24358,7 +24349,7 @@ class TrackViewport extends Viewport {
|
|
|
24358
24349
|
|
|
24359
24350
|
const track = this.trackView.track;
|
|
24360
24351
|
if ('sequence' !== track.type) {
|
|
24361
|
-
this.$zoomInNotice = this.createZoomInNotice(this.$
|
|
24352
|
+
this.$zoomInNotice = this.createZoomInNotice(this.$viewport);
|
|
24362
24353
|
}
|
|
24363
24354
|
|
|
24364
24355
|
if (track.name && "sequence" !== track.id) {
|
|
@@ -24469,6 +24460,34 @@ class TrackViewport extends Viewport {
|
|
|
24469
24460
|
}
|
|
24470
24461
|
}
|
|
24471
24462
|
|
|
24463
|
+
/**
|
|
24464
|
+
* Set the content top of the current view. This is triggered by scrolling. If the current canvas extent is not
|
|
24465
|
+
* sufficient to cover the new vertical range repaint.
|
|
24466
|
+
*
|
|
24467
|
+
* @param contentTop - the "top" property of the virtual content div, 0 unless track is scrolled vertically
|
|
24468
|
+
*
|
|
24469
|
+
*
|
|
24470
|
+
*/
|
|
24471
|
+
setTop(contentTop) {
|
|
24472
|
+
super.setTop(contentTop);
|
|
24473
|
+
|
|
24474
|
+
if (!this.canvas) {
|
|
24475
|
+
this.repaint();
|
|
24476
|
+
} else {
|
|
24477
|
+
// See if currently painted canvas covers the vertical range of the viewport. If not repaint
|
|
24478
|
+
const h = this.$viewport.height();
|
|
24479
|
+
const vt = contentTop + this.canvas._data.canvasTop;
|
|
24480
|
+
const vb = contentTop + this.canvas._data.canvasBottom;
|
|
24481
|
+
if (vt > 0 || vb < h) {
|
|
24482
|
+
this.repaint();
|
|
24483
|
+
}
|
|
24484
|
+
}
|
|
24485
|
+
|
|
24486
|
+
// Now offset backing canvas to align with the contentTop visual offset.
|
|
24487
|
+
let offset = contentTop + this.canvas._data.canvasTop;
|
|
24488
|
+
this.canvas.style.top = `${offset}px`;
|
|
24489
|
+
}
|
|
24490
|
+
|
|
24472
24491
|
async loadFeatures() {
|
|
24473
24492
|
|
|
24474
24493
|
const referenceFrame = this.referenceFrame;
|
|
@@ -24476,7 +24495,7 @@ class TrackViewport extends Viewport {
|
|
|
24476
24495
|
|
|
24477
24496
|
// Expand the requested range so we can pan a bit without reloading. But not beyond chromosome bounds
|
|
24478
24497
|
const chrLength = this.browser.genome.getChromosome(chr).bpLength;
|
|
24479
|
-
const pixelWidth = this.$
|
|
24498
|
+
const pixelWidth = this.$viewport.width();// * 3;
|
|
24480
24499
|
const bpWidth = pixelWidth * referenceFrame.bpPerPixel;
|
|
24481
24500
|
const bpStart = Math.floor(Math.max(0, referenceFrame.start - bpWidth));
|
|
24482
24501
|
const bpEnd = Math.ceil(Math.min(chrLength, referenceFrame.start + bpWidth + bpWidth)); // Add one screen width to end
|
|
@@ -24517,6 +24536,12 @@ class TrackViewport extends Viewport {
|
|
|
24517
24536
|
}
|
|
24518
24537
|
}
|
|
24519
24538
|
|
|
24539
|
+
/**
|
|
24540
|
+
* Compute the genomic extent and needed pixelWidth to repaint the canvas for the current genomic state.
|
|
24541
|
+
* Normally the canvas is size 3X the width of the viewport, however there is no left-right panning for WGV so
|
|
24542
|
+
* canvas width is viewport width.
|
|
24543
|
+
* @returns {{bpEnd: *, pixelWidth: (*|number), bpStart: number}}
|
|
24544
|
+
*/
|
|
24520
24545
|
repaintDimensions() {
|
|
24521
24546
|
const isWGV = GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
|
|
24522
24547
|
const pixelWidth = isWGV ? this.$viewport.width() : 3 * this.$viewport.width();
|
|
@@ -24539,12 +24564,8 @@ class TrackViewport extends Viewport {
|
|
|
24539
24564
|
}
|
|
24540
24565
|
|
|
24541
24566
|
const {features, roiFeatures} = this.featureCache;
|
|
24542
|
-
//this.tile.bpPerPixel = this.referenceFrame.bpPerPixel
|
|
24543
24567
|
|
|
24544
|
-
//
|
|
24545
|
-
GenomeUtils.isWholeGenomeView(this.referenceFrame.chr);
|
|
24546
|
-
|
|
24547
|
-
// Canvas dimensions. There is no left-right panning for WGV so canvas width is viewport width.
|
|
24568
|
+
// Canvas dimensions.
|
|
24548
24569
|
// For deep tracks we paint a canvas == 3*viewportHeight centered on the current vertical scroll position
|
|
24549
24570
|
const {bpStart, bpEnd, pixelWidth} = this.repaintDimensions();
|
|
24550
24571
|
const viewportHeight = this.$viewport.height();
|
|
@@ -24557,14 +24578,15 @@ class TrackViewport extends Viewport {
|
|
|
24557
24578
|
}
|
|
24558
24579
|
return
|
|
24559
24580
|
}
|
|
24560
|
-
const canvasTop = Math.max(0, -
|
|
24581
|
+
const canvasTop = Math.max(0, -this.contentTop - viewportHeight);
|
|
24582
|
+
|
|
24561
24583
|
|
|
24562
24584
|
const bpPerPixel = this.referenceFrame.bpPerPixel;
|
|
24563
|
-
//const bpStart = this.referenceFrame.start - (isWGV ? 0 : pixelWidth / 3 * bpPerPixel)
|
|
24564
|
-
//const bpEnd = this.referenceFrame.end + (isWGV ? 0 : pixelWidth / 3 * bpPerPixel)
|
|
24565
24585
|
const pixelXOffset = Math.round((bpStart - this.referenceFrame.start) / bpPerPixel);
|
|
24566
24586
|
|
|
24567
|
-
const newCanvas =
|
|
24587
|
+
const newCanvas = document.createElement('canvas');// $('<canvas class="igv-canvas">').get(0)
|
|
24588
|
+
newCanvas.style.position = 'relative';
|
|
24589
|
+
newCanvas.style.display = 'block';
|
|
24568
24590
|
newCanvas.style.width = pixelWidth + "px";
|
|
24569
24591
|
newCanvas.style.height = pixelHeight + "px";
|
|
24570
24592
|
newCanvas.style.left = pixelXOffset + "px";
|
|
@@ -24590,6 +24612,8 @@ class TrackViewport extends Viewport {
|
|
|
24590
24612
|
bpStart: bpStart,
|
|
24591
24613
|
bpEnd: bpEnd,
|
|
24592
24614
|
bpPerPixel,
|
|
24615
|
+
canvasTop,
|
|
24616
|
+
canvasBottom: canvasTop + pixelHeight,
|
|
24593
24617
|
referenceFrame: this.referenceFrame,
|
|
24594
24618
|
selection: this.selection,
|
|
24595
24619
|
viewport: this,
|
|
@@ -24603,7 +24627,7 @@ class TrackViewport extends Viewport {
|
|
|
24603
24627
|
}
|
|
24604
24628
|
newCanvas._data = drawConfiguration;
|
|
24605
24629
|
this.canvas = newCanvas;
|
|
24606
|
-
this.$
|
|
24630
|
+
this.$viewport.append($$1(newCanvas));
|
|
24607
24631
|
|
|
24608
24632
|
}
|
|
24609
24633
|
|
|
@@ -24625,13 +24649,11 @@ class TrackViewport extends Viewport {
|
|
|
24625
24649
|
*/
|
|
24626
24650
|
draw(drawConfiguration, features, roiFeatures) {
|
|
24627
24651
|
|
|
24628
|
-
// console.log(`${ Date.now() } viewport draw(). track ${ this.trackView.track.type }. content-css-top ${ this.$content.css('top') }. canvas-top ${ drawConfiguration.pixelTop }.`)
|
|
24629
|
-
|
|
24630
24652
|
if (features) {
|
|
24631
24653
|
drawConfiguration.features = features;
|
|
24632
24654
|
this.trackView.track.draw(drawConfiguration);
|
|
24633
24655
|
}
|
|
24634
|
-
if (roiFeatures) {
|
|
24656
|
+
if (roiFeatures && roiFeatures.length > 0) {
|
|
24635
24657
|
for (let r of roiFeatures) {
|
|
24636
24658
|
drawConfiguration.features = r.features;
|
|
24637
24659
|
r.track.draw(drawConfiguration);
|
|
@@ -24655,13 +24677,13 @@ class TrackViewport extends Viewport {
|
|
|
24655
24677
|
|
|
24656
24678
|
if (!this.canvas) return
|
|
24657
24679
|
|
|
24658
|
-
const canvasMetadata = this.
|
|
24680
|
+
const canvasMetadata = this.canvas._data;
|
|
24659
24681
|
const canvasTop = canvasMetadata ? canvasMetadata.canvasTop : 0;
|
|
24660
24682
|
const devicePixelRatio = window.devicePixelRatio;
|
|
24661
24683
|
const w = this.$viewport.width() * devicePixelRatio;
|
|
24662
24684
|
const h = this.$viewport.height() * devicePixelRatio;
|
|
24663
24685
|
const x = -$$1(this.canvas).position().left * devicePixelRatio;
|
|
24664
|
-
const y = (-this
|
|
24686
|
+
const y = (-this.contentTop - canvasTop) * devicePixelRatio;
|
|
24665
24687
|
|
|
24666
24688
|
const ctx = this.canvas.getContext("2d");
|
|
24667
24689
|
const imageData = ctx.getImageData(x, y, w, h);
|
|
@@ -24679,57 +24701,82 @@ class TrackViewport extends Viewport {
|
|
|
24679
24701
|
|
|
24680
24702
|
saveSVG() {
|
|
24681
24703
|
|
|
24682
|
-
const
|
|
24704
|
+
const marginTop = 32;
|
|
24705
|
+
const marginLeft = 32;
|
|
24706
|
+
|
|
24707
|
+
let {width, height} = this.browser.columnContainer.getBoundingClientRect();
|
|
24708
|
+
|
|
24709
|
+
const h_render = 8000;
|
|
24683
24710
|
|
|
24684
24711
|
const config =
|
|
24685
24712
|
{
|
|
24713
|
+
|
|
24686
24714
|
width,
|
|
24687
|
-
height,
|
|
24715
|
+
height: h_render,
|
|
24716
|
+
|
|
24717
|
+
backdropColor: 'white',
|
|
24718
|
+
|
|
24719
|
+
multiLocusGap: 0,
|
|
24720
|
+
|
|
24688
24721
|
viewbox:
|
|
24689
24722
|
{
|
|
24690
24723
|
x: 0,
|
|
24691
|
-
y:
|
|
24724
|
+
y: 0,
|
|
24692
24725
|
width,
|
|
24693
|
-
height
|
|
24726
|
+
height: h_render
|
|
24694
24727
|
}
|
|
24695
24728
|
|
|
24696
24729
|
};
|
|
24697
24730
|
|
|
24698
24731
|
const context = new ctx(config);
|
|
24699
24732
|
|
|
24700
|
-
const
|
|
24733
|
+
const delta =
|
|
24734
|
+
{
|
|
24735
|
+
deltaX: marginLeft,
|
|
24736
|
+
deltaY: marginTop
|
|
24737
|
+
};
|
|
24701
24738
|
|
|
24702
|
-
|
|
24703
|
-
|
|
24739
|
+
this.renderViewportToSVG(context, delta);
|
|
24740
|
+
|
|
24741
|
+
// reset height to trim away unneeded svg canvas real estate. Yes, a bit of a hack.
|
|
24742
|
+
context.setHeight(height);
|
|
24704
24743
|
|
|
24705
|
-
this.
|
|
24744
|
+
const str = (this.trackView.track.name || this.trackView.track.id).replace(/\W/g, '');
|
|
24745
|
+
const index = this.browser.referenceFrameList.indexOf(this.referenceFrame);
|
|
24706
24746
|
|
|
24707
24747
|
const svg = context.getSerializedSvg(true);
|
|
24708
24748
|
const data = URL.createObjectURL(new Blob([svg], {type: "application/octet-stream"}));
|
|
24709
24749
|
|
|
24750
|
+
const id = `${str}_referenceFrame_${index}_guid_${guid$2()}`;
|
|
24710
24751
|
download(`${id}.svg`, data);
|
|
24752
|
+
|
|
24711
24753
|
}
|
|
24712
24754
|
|
|
24713
24755
|
// called by trackView.renderSVGContext() when rendering
|
|
24714
24756
|
// entire browser as SVG
|
|
24715
24757
|
|
|
24716
|
-
|
|
24758
|
+
renderViewportToSVG(context, {deltaX, deltaY}) {
|
|
24717
24759
|
|
|
24718
|
-
// Nothing to do if zoomInNotice is active
|
|
24719
24760
|
if (this.$zoomInNotice && this.$zoomInNotice.is(":visible")) {
|
|
24720
24761
|
return
|
|
24721
24762
|
}
|
|
24722
24763
|
|
|
24723
|
-
const
|
|
24764
|
+
const {width, height} = this.$viewport.get(0).getBoundingClientRect();
|
|
24724
24765
|
|
|
24766
|
+
const str = (this.trackView.track.name || this.trackView.track.id).replace(/\W/g, '');
|
|
24725
24767
|
const index = this.browser.referenceFrameList.indexOf(this.referenceFrame);
|
|
24726
24768
|
const id = `${str}_referenceFrame_${index}_guid_${guid$2()}`;
|
|
24769
|
+
this.drawSVGWithContext(context, width, height, id, deltaX, deltaY + this.contentTop, -this.contentTop);
|
|
24727
24770
|
|
|
24728
|
-
|
|
24771
|
+
}
|
|
24729
24772
|
|
|
24730
|
-
|
|
24773
|
+
renderSVGContext(context, {deltaX, deltaY}) {
|
|
24731
24774
|
|
|
24732
|
-
this.
|
|
24775
|
+
this.renderViewportToSVG(context, {deltaX, deltaY});
|
|
24776
|
+
|
|
24777
|
+
if (this.$zoomInNotice && this.$zoomInNotice.is(":visible")) {
|
|
24778
|
+
return
|
|
24779
|
+
}
|
|
24733
24780
|
|
|
24734
24781
|
if (this.$trackLabel && true === this.browser.trackLabelsVisible) {
|
|
24735
24782
|
const {x, y, width, height} = relativeDOMBBox(this.$viewport.get(0), this.$trackLabel.get(0));
|
|
@@ -24787,7 +24834,7 @@ class TrackViewport extends Viewport {
|
|
|
24787
24834
|
selection: this.selection
|
|
24788
24835
|
};
|
|
24789
24836
|
|
|
24790
|
-
const features = this.featureCache ? this.featureCache.features :
|
|
24837
|
+
const features = this.featureCache ? this.featureCache.features : undefined;
|
|
24791
24838
|
const roiFeatures = this.featureCache ? this.featureCache.roiFeatures : undefined;
|
|
24792
24839
|
this.draw(config, features, roiFeatures);
|
|
24793
24840
|
|
|
@@ -24887,22 +24934,21 @@ class TrackViewport extends Viewport {
|
|
|
24887
24934
|
viewport.addEventListener('touchend', mu);
|
|
24888
24935
|
|
|
24889
24936
|
// Mouse move
|
|
24890
|
-
if (typeof this.trackView.track.
|
|
24937
|
+
if (typeof this.trackView.track.hoverText === 'function') {
|
|
24891
24938
|
viewport.addEventListener('mousemove', (event => {
|
|
24892
|
-
if (Date.now() - lastHoverUpdateTime > 100) {
|
|
24939
|
+
if (event.buttons === 0 && (Date.now() - lastHoverUpdateTime > 100)) {
|
|
24893
24940
|
lastHoverUpdateTime = Date.now();
|
|
24894
|
-
const clickState = createClickState(event
|
|
24941
|
+
const clickState = this.createClickState(event);
|
|
24895
24942
|
if (clickState) {
|
|
24896
|
-
const
|
|
24897
|
-
if (
|
|
24898
|
-
this.$viewport[0].setAttribute("title",
|
|
24943
|
+
const tooltip = this.trackView.track.hoverText(clickState);
|
|
24944
|
+
if (tooltip) {
|
|
24945
|
+
this.$viewport[0].setAttribute("title", tooltip);
|
|
24899
24946
|
} else {
|
|
24900
24947
|
this.$viewport[0].removeAttribute("title");
|
|
24901
24948
|
}
|
|
24902
24949
|
}
|
|
24903
24950
|
}
|
|
24904
24951
|
}));
|
|
24905
|
-
|
|
24906
24952
|
}
|
|
24907
24953
|
|
|
24908
24954
|
this.addViewportClickHandler(this.$viewport.get(0));
|
|
@@ -24922,7 +24968,7 @@ class TrackViewport extends Viewport {
|
|
|
24922
24968
|
return false
|
|
24923
24969
|
}
|
|
24924
24970
|
|
|
24925
|
-
const clickState = createClickState(event
|
|
24971
|
+
const clickState = this.createClickState(event);
|
|
24926
24972
|
|
|
24927
24973
|
if (undefined === clickState) {
|
|
24928
24974
|
return false
|
|
@@ -25024,7 +25070,7 @@ class TrackViewport extends Viewport {
|
|
|
25024
25070
|
|
|
25025
25071
|
popupTimerID = setTimeout(() => {
|
|
25026
25072
|
|
|
25027
|
-
const content = getPopupContent(event
|
|
25073
|
+
const content = this.getPopupContent(event);
|
|
25028
25074
|
if (content) {
|
|
25029
25075
|
if (this.popover) this.popover.dispose();
|
|
25030
25076
|
this.popover = new Popover(this.browser.columnContainer);
|
|
@@ -25068,57 +25114,58 @@ class TrackViewport extends Viewport {
|
|
|
25068
25114
|
});
|
|
25069
25115
|
}
|
|
25070
25116
|
|
|
25071
|
-
|
|
25117
|
+
createClickState(event) {
|
|
25072
25118
|
|
|
25073
|
-
|
|
25119
|
+
if (!this.canvas) return // Can happen during initialization
|
|
25074
25120
|
|
|
25075
|
-
|
|
25121
|
+
const referenceFrame = this.referenceFrame;
|
|
25122
|
+
const viewportCoords = translateMouseCoordinates$1(event, this.$viewport.get(0));
|
|
25123
|
+
const canvasCoords = translateMouseCoordinates$1(event, this.canvas);
|
|
25124
|
+
const genomicLocation = ((referenceFrame.start) + referenceFrame.toBP(viewportCoords.x));
|
|
25076
25125
|
|
|
25077
|
-
|
|
25078
|
-
|
|
25079
|
-
|
|
25080
|
-
|
|
25126
|
+
return {
|
|
25127
|
+
event,
|
|
25128
|
+
viewport: this,
|
|
25129
|
+
referenceFrame,
|
|
25130
|
+
genomicLocation,
|
|
25131
|
+
y: viewportCoords.y - this.contentTop,
|
|
25132
|
+
canvasX: canvasCoords.x,
|
|
25133
|
+
canvasY: canvasCoords.y
|
|
25134
|
+
}
|
|
25081
25135
|
|
|
25082
|
-
return {
|
|
25083
|
-
event,
|
|
25084
|
-
viewport,
|
|
25085
|
-
referenceFrame,
|
|
25086
|
-
genomicLocation,
|
|
25087
|
-
x: viewportCoords.x,
|
|
25088
|
-
y: viewportCoords.y,
|
|
25089
|
-
canvasX: canvasCoords.x,
|
|
25090
|
-
canvasY: canvasCoords.y
|
|
25091
25136
|
}
|
|
25092
25137
|
|
|
25093
|
-
|
|
25138
|
+
getPopupContent(event) {
|
|
25094
25139
|
|
|
25095
|
-
|
|
25140
|
+
const clickState = this.createClickState(event);
|
|
25096
25141
|
|
|
25097
|
-
|
|
25142
|
+
if (undefined === clickState) {
|
|
25143
|
+
return
|
|
25144
|
+
}
|
|
25098
25145
|
|
|
25099
|
-
|
|
25100
|
-
|
|
25101
|
-
}
|
|
25146
|
+
let track = this.trackView.track;
|
|
25147
|
+
const dataList = track.popupData(clickState);
|
|
25102
25148
|
|
|
25103
|
-
|
|
25104
|
-
const dataList = track.popupData(clickState);
|
|
25149
|
+
const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList]);
|
|
25105
25150
|
|
|
25106
|
-
|
|
25151
|
+
let content;
|
|
25152
|
+
if (undefined === popupClickHandlerResult || true === popupClickHandlerResult) {
|
|
25153
|
+
// Indicates handler did not handle the result, or the handler wishes default behavior to occur
|
|
25154
|
+
if (dataList && dataList.length > 0) {
|
|
25155
|
+
content = formatPopoverText(dataList);
|
|
25156
|
+
}
|
|
25107
25157
|
|
|
25108
|
-
|
|
25109
|
-
|
|
25110
|
-
// Indicates handler did not handle the result, or the handler wishes default behavior to occur
|
|
25111
|
-
if (dataList && dataList.length > 0) {
|
|
25112
|
-
content = formatPopoverText(dataList);
|
|
25158
|
+
} else if (typeof popupClickHandlerResult === 'string') {
|
|
25159
|
+
content = popupClickHandlerResult;
|
|
25113
25160
|
}
|
|
25114
25161
|
|
|
25115
|
-
|
|
25116
|
-
content = popupClickHandlerResult;
|
|
25162
|
+
return content
|
|
25117
25163
|
}
|
|
25118
25164
|
|
|
25119
|
-
|
|
25165
|
+
|
|
25120
25166
|
}
|
|
25121
25167
|
|
|
25168
|
+
|
|
25122
25169
|
function formatPopoverText(nameValues) {
|
|
25123
25170
|
|
|
25124
25171
|
const rows = nameValues.map(nameValue => {
|
|
@@ -26717,6 +26764,7 @@ class TrackBase {
|
|
|
26717
26764
|
* @param config
|
|
26718
26765
|
*/
|
|
26719
26766
|
init(config) {
|
|
26767
|
+
|
|
26720
26768
|
if (config.displayMode) {
|
|
26721
26769
|
config.displayMode = config.displayMode.toUpperCase();
|
|
26722
26770
|
}
|
|
@@ -26772,8 +26820,12 @@ class TrackBase {
|
|
|
26772
26820
|
}
|
|
26773
26821
|
}
|
|
26774
26822
|
|
|
26775
|
-
|
|
26776
|
-
|
|
26823
|
+
// Support for mouse hover text. This can be expensive, off by default.
|
|
26824
|
+
// this.hoverText = function(clickState) => return tool tip text
|
|
26825
|
+
if (config.hoverTextFields) {
|
|
26826
|
+
this.hoverText = hoverText.bind(this);
|
|
26827
|
+
} else if (typeof this.config.hoverText === 'function') {
|
|
26828
|
+
this.hoverText = this.config.hoverText;
|
|
26777
26829
|
}
|
|
26778
26830
|
}
|
|
26779
26831
|
|
|
@@ -26888,7 +26940,7 @@ class TrackBase {
|
|
|
26888
26940
|
*/
|
|
26889
26941
|
setTrackProperties(properties) {
|
|
26890
26942
|
|
|
26891
|
-
if(this.disposed) return
|
|
26943
|
+
if (this.disposed) return // This track was removed during async load
|
|
26892
26944
|
|
|
26893
26945
|
const tracklineConfg = {};
|
|
26894
26946
|
let tokens;
|
|
@@ -26991,20 +27043,20 @@ class TrackBase {
|
|
|
26991
27043
|
}
|
|
26992
27044
|
|
|
26993
27045
|
/**
|
|
26994
|
-
* Return the features clicked over. Default implementation assumes
|
|
27046
|
+
* Return the features clicked over. Default implementation assumes an array of features and only considers
|
|
26995
27047
|
* the genomic location. Overriden by most subclasses.
|
|
26996
27048
|
*
|
|
26997
27049
|
* @param clickState
|
|
26998
27050
|
* @param features
|
|
26999
27051
|
* @returns {[]|*[]}
|
|
27000
27052
|
*/
|
|
27001
|
-
clickedFeatures(clickState
|
|
27053
|
+
clickedFeatures(clickState) {
|
|
27002
27054
|
|
|
27003
27055
|
// We use the cached features rather than method to avoid async load. If the
|
|
27004
27056
|
// feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
|
|
27005
|
-
|
|
27057
|
+
const features = clickState.viewport.cachedFeatures;
|
|
27006
27058
|
|
|
27007
|
-
if (!features || features.length === 0) {
|
|
27059
|
+
if (!features || !Array.isArray(features) || features.length === 0) {
|
|
27008
27060
|
return []
|
|
27009
27061
|
}
|
|
27010
27062
|
|
|
@@ -27102,65 +27154,6 @@ class TrackBase {
|
|
|
27102
27154
|
|
|
27103
27155
|
}
|
|
27104
27156
|
|
|
27105
|
-
/**
|
|
27106
|
-
* Return a plain string for descriptive text given the list of features. The string is used to set a "title"
|
|
27107
|
-
* attribute and cannot contain any html.
|
|
27108
|
-
*
|
|
27109
|
-
* @returns {undefined}
|
|
27110
|
-
*/
|
|
27111
|
-
_hoverText(clickState) {
|
|
27112
|
-
|
|
27113
|
-
const features = this.clickedFeatures(clickState);
|
|
27114
|
-
|
|
27115
|
-
if (features && features.length > 0) {
|
|
27116
|
-
let str = "";
|
|
27117
|
-
for (let i = 0; i < features.length; i++) {
|
|
27118
|
-
if (i === 10) {
|
|
27119
|
-
str += "; ...";
|
|
27120
|
-
break
|
|
27121
|
-
}
|
|
27122
|
-
if (!features[i]) continue
|
|
27123
|
-
|
|
27124
|
-
const f = features[i]._f || features[i];
|
|
27125
|
-
if (str.length > 0) str += "\n";
|
|
27126
|
-
|
|
27127
|
-
if(this.hoverTextFields) {
|
|
27128
|
-
str = "";
|
|
27129
|
-
for(let field of this.hoverTextFields) {
|
|
27130
|
-
if(str.length > 0) str += "\n";
|
|
27131
|
-
if(f.hasOwnProperty(field)) {
|
|
27132
|
-
str += f[field];
|
|
27133
|
-
} else if(typeof f.getAttribute === "function") {
|
|
27134
|
-
str += f.getAttribute(field);
|
|
27135
|
-
}
|
|
27136
|
-
}
|
|
27137
|
-
}
|
|
27138
|
-
else if (typeof this.config.hoverText === 'function') {
|
|
27139
|
-
str += this.config.hoverText(f);
|
|
27140
|
-
} else if (typeof f.hoverText === 'function') {
|
|
27141
|
-
str += f.hoverText();
|
|
27142
|
-
} else {
|
|
27143
|
-
str += this.hoverText(f);
|
|
27144
|
-
}
|
|
27145
|
-
}
|
|
27146
|
-
return str
|
|
27147
|
-
}
|
|
27148
|
-
}
|
|
27149
|
-
|
|
27150
|
-
/**
|
|
27151
|
-
* Default hoverText function. Can be overridden in track configuration or subclasses
|
|
27152
|
-
*
|
|
27153
|
-
* @param f - feature hovered over
|
|
27154
|
-
*/
|
|
27155
|
-
hoverText(f) {
|
|
27156
|
-
let str = "";
|
|
27157
|
-
if (f.name) {
|
|
27158
|
-
str += f.name;
|
|
27159
|
-
if (f.value) str += ": ";
|
|
27160
|
-
}
|
|
27161
|
-
if (f.value) str += f.value; // TODO format value if number
|
|
27162
|
-
return str;
|
|
27163
|
-
}
|
|
27164
27157
|
|
|
27165
27158
|
/**
|
|
27166
27159
|
* Default track description -- displayed on click of track label. This can be overriden in the track
|
|
@@ -27233,6 +27226,38 @@ class TrackBase {
|
|
|
27233
27226
|
}
|
|
27234
27227
|
}
|
|
27235
27228
|
|
|
27229
|
+
function hoverText(clickState) {
|
|
27230
|
+
|
|
27231
|
+
if (!this.hoverTextFields) return
|
|
27232
|
+
|
|
27233
|
+
const features = this.clickedFeatures(clickState);
|
|
27234
|
+
|
|
27235
|
+
if (features && features.length > 0) {
|
|
27236
|
+
let str = "";
|
|
27237
|
+
for (let i = 0; i < features.length; i++) {
|
|
27238
|
+
if (i === 10) {
|
|
27239
|
+
str += "; ...";
|
|
27240
|
+
break
|
|
27241
|
+
}
|
|
27242
|
+
if (!features[i]) continue
|
|
27243
|
+
|
|
27244
|
+
const f = features[i]._f || features[i];
|
|
27245
|
+
if (str.length > 0) str += "\n";
|
|
27246
|
+
|
|
27247
|
+
str = "";
|
|
27248
|
+
for (let field of this.hoverTextFields) {
|
|
27249
|
+
if (str.length > 0) str += "\n";
|
|
27250
|
+
if (f.hasOwnProperty(field)) {
|
|
27251
|
+
str += f[field];
|
|
27252
|
+
} else if (typeof f.getAttribute === "function") {
|
|
27253
|
+
str += f.getAttribute(field);
|
|
27254
|
+
}
|
|
27255
|
+
}
|
|
27256
|
+
|
|
27257
|
+
}
|
|
27258
|
+
return str
|
|
27259
|
+
}
|
|
27260
|
+
}
|
|
27236
27261
|
|
|
27237
27262
|
/**
|
|
27238
27263
|
* Map UCSC track line "type" setting to file format. In igv.js "type" refers to the track type, not the input file format
|
|
@@ -27431,12 +27456,6 @@ class SegFeature {
|
|
|
27431
27456
|
return pd
|
|
27432
27457
|
}
|
|
27433
27458
|
|
|
27434
|
-
hoverText() {
|
|
27435
|
-
|
|
27436
|
-
return `${this.sample}: ${this.value}`
|
|
27437
|
-
}
|
|
27438
|
-
|
|
27439
|
-
|
|
27440
27459
|
extractCravatLink(genomeId) {
|
|
27441
27460
|
|
|
27442
27461
|
let ref, alt;
|
|
@@ -27720,10 +27739,6 @@ class Variant {
|
|
|
27720
27739
|
return fields
|
|
27721
27740
|
}
|
|
27722
27741
|
|
|
27723
|
-
hoverText() {
|
|
27724
|
-
return `${this.names} ${this.referenceBases}->${this.alternateBases}`
|
|
27725
|
-
}
|
|
27726
|
-
|
|
27727
27742
|
getInfo(tag) {
|
|
27728
27743
|
return this.info ? this.info[tag] : undefined;
|
|
27729
27744
|
}
|
|
@@ -28942,10 +28957,9 @@ class CSIIndex {
|
|
|
28942
28957
|
for (let bin = binRange[0]; bin <= binRange[1]; bin++) {
|
|
28943
28958
|
if (ba.binIndex[bin]) {
|
|
28944
28959
|
const binChunks = ba.binIndex[bin];
|
|
28945
|
-
|
|
28946
|
-
|
|
28947
|
-
const
|
|
28948
|
-
const ce = binChunks[c][1];
|
|
28960
|
+
for (let c of binChunks) {
|
|
28961
|
+
const cs = c[0];
|
|
28962
|
+
const ce = c[1];
|
|
28949
28963
|
chunks.push({minv: cs, maxv: ce, bin: bin});
|
|
28950
28964
|
}
|
|
28951
28965
|
}
|
|
@@ -28963,7 +28977,7 @@ class CSIIndex {
|
|
|
28963
28977
|
reg2bins(beg, end) {
|
|
28964
28978
|
beg -= 1; // < convert to 1-based closed
|
|
28965
28979
|
if (beg < 1) beg = 1;
|
|
28966
|
-
if (end > 2 **
|
|
28980
|
+
if (end > 2 ** 34) end = 2 ** 34; // 17 GiB ought to be enough for anybody
|
|
28967
28981
|
end -= 1;
|
|
28968
28982
|
let l = 0;
|
|
28969
28983
|
let t = 0;
|
|
@@ -28972,25 +28986,18 @@ class CSIIndex {
|
|
|
28972
28986
|
for (; l <= this.depth; s -= 3, t += (1 << l * 3), l += 1) {
|
|
28973
28987
|
const b = t + (beg >> s);
|
|
28974
28988
|
const e = t + (end >> s);
|
|
28975
|
-
|
|
28976
|
-
|
|
28977
|
-
|
|
28978
|
-
|
|
28979
|
-
//for (
|
|
28989
|
+
//
|
|
28990
|
+
// ITS NOT CLEAR WHERE THIS TEST CAME FROM, but maxBinNumber is never set, and its not clear what it represents.
|
|
28991
|
+
// if (e - b + bins.length > this.maxBinNumber)
|
|
28992
|
+
// throw new Error(
|
|
28993
|
+
// `query ${beg}-${end} is too large for current binning scheme (shift ${this.minShift}, depth ${this.depth}), try a smaller query or a coarser index binning scheme`,
|
|
28994
|
+
// )
|
|
28995
|
+
//
|
|
28980
28996
|
bins.push([b, e]);
|
|
28981
28997
|
}
|
|
28982
28998
|
return bins
|
|
28983
28999
|
}
|
|
28984
29000
|
|
|
28985
|
-
// function reg2bins(beg, end, min_shift, depth) {
|
|
28986
|
-
// let l, t, n, s = min_shift + depth * 3;
|
|
28987
|
-
// const bins = [];
|
|
28988
|
-
// for (--end, l = n = t = 0; l <= depth; s -= 3, t += 1 << l * 3, ++l) {
|
|
28989
|
-
// let b = t + (beg >> s), e = t + (end >> s), i;
|
|
28990
|
-
// for (i = b; i <= e; ++i) bins[n++] = i;
|
|
28991
|
-
// }
|
|
28992
|
-
// return bins;
|
|
28993
|
-
// }
|
|
28994
29001
|
|
|
28995
29002
|
bin_limit() {
|
|
28996
29003
|
return ((1 << (this.depth + 1) * 3) - 1) / 7
|
|
@@ -29193,11 +29200,10 @@ class BamIndex {
|
|
|
29193
29200
|
for (let binRange of overlappingBins) {
|
|
29194
29201
|
for (let bin = binRange[0]; bin <= binRange[1]; bin++) {
|
|
29195
29202
|
if (ba.binIndex[bin]) {
|
|
29196
|
-
const binChunks = ba.binIndex[bin]
|
|
29197
|
-
|
|
29198
|
-
|
|
29199
|
-
const
|
|
29200
|
-
const ce = binChunks[c][1];
|
|
29203
|
+
const binChunks = ba.binIndex[bin];
|
|
29204
|
+
for (let c of binChunks) {
|
|
29205
|
+
const cs = c[0];
|
|
29206
|
+
const ce = c[1];
|
|
29201
29207
|
chunks.push({minv: cs, maxv: ce, bin: bin});
|
|
29202
29208
|
}
|
|
29203
29209
|
}
|
|
@@ -29209,7 +29215,7 @@ class BamIndex {
|
|
|
29209
29215
|
let lowest = null;
|
|
29210
29216
|
const minLin = Math.min(min >> 14, nintv - 1);
|
|
29211
29217
|
const maxLin = Math.min(max >> 14, nintv - 1);
|
|
29212
|
-
for (let i = minLin; i
|
|
29218
|
+
for (let i = minLin; i < maxLin; i++) {
|
|
29213
29219
|
const vp = ba.linearIndex[i];
|
|
29214
29220
|
if (vp) {
|
|
29215
29221
|
// todo -- I think, but am not sure, that the values in the linear index have to be in increasing order. So the first non-null should be minimum
|
|
@@ -35129,10 +35135,6 @@ class BamAlignment {
|
|
|
35129
35135
|
return (genomicLocation >= s && genomicLocation <= (s + l))
|
|
35130
35136
|
}
|
|
35131
35137
|
|
|
35132
|
-
hoverText() {
|
|
35133
|
-
return this.readName;
|
|
35134
|
-
}
|
|
35135
|
-
|
|
35136
35138
|
popupData(genomicLocation) {
|
|
35137
35139
|
|
|
35138
35140
|
// if the user clicks on a base next to an insertion, show just the
|
|
@@ -39984,6 +39986,14 @@ class BAMTrack extends TrackBase {
|
|
|
39984
39986
|
return clickedObject ? [clickedObject] : undefined
|
|
39985
39987
|
}
|
|
39986
39988
|
|
|
39989
|
+
hoverText(clickState) {
|
|
39990
|
+
if (true === this.showCoverage && clickState.y >= this.coverageTrack.top && clickState.y < this.coverageTrack.height) {
|
|
39991
|
+
const clickedObject = this.coverageTrack.getClickedObject(clickState);
|
|
39992
|
+
return clickedObject.hoverText()
|
|
39993
|
+
}
|
|
39994
|
+
|
|
39995
|
+
}
|
|
39996
|
+
|
|
39987
39997
|
menuItemList() {
|
|
39988
39998
|
|
|
39989
39999
|
|
|
@@ -41401,6 +41411,10 @@ class RulerViewport extends TrackViewport {
|
|
|
41401
41411
|
super(trackView, $viewportColumn, referenceFrame, width);
|
|
41402
41412
|
}
|
|
41403
41413
|
|
|
41414
|
+
get contentDiv() {
|
|
41415
|
+
return this.$viewport.get(0)
|
|
41416
|
+
}
|
|
41417
|
+
|
|
41404
41418
|
initializationHelper() {
|
|
41405
41419
|
|
|
41406
41420
|
this.$multiLocusCloseButton = $$1('<div>', {class: 'igv-multi-locus-close-button'});
|
|
@@ -41628,7 +41642,8 @@ class IdeogramViewport extends TrackViewport {
|
|
|
41628
41642
|
|
|
41629
41643
|
this.canvas = document.createElement('canvas');
|
|
41630
41644
|
this.canvas.className = 'igv-ideogram-canvas';
|
|
41631
|
-
this.$content.append(
|
|
41645
|
+
//this.$content.append($(this.canvas))
|
|
41646
|
+
this.$viewport.append($$1(this.canvas));
|
|
41632
41647
|
this.ideogram_ctx = this.canvas.getContext('2d');
|
|
41633
41648
|
|
|
41634
41649
|
this.addMouseHandlers();
|
|
@@ -42202,7 +42217,7 @@ const hideAllMenuPopups = () => {
|
|
|
42202
42217
|
* THE SOFTWARE.
|
|
42203
42218
|
*/
|
|
42204
42219
|
|
|
42205
|
-
const scrollbarExclusionTypes = new Set(['ruler', '
|
|
42220
|
+
const scrollbarExclusionTypes = new Set(['ruler', 'ideogram']);
|
|
42206
42221
|
const colorPickerExclusionTypes = new Set(['ruler', 'sequence', 'ideogram']);
|
|
42207
42222
|
|
|
42208
42223
|
class TrackView {
|
|
@@ -42582,17 +42597,18 @@ class TrackView {
|
|
|
42582
42597
|
}
|
|
42583
42598
|
|
|
42584
42599
|
if (this.track.autoscale) {
|
|
42585
|
-
let allFeatures
|
|
42600
|
+
let allFeatures;
|
|
42586
42601
|
for (let visibleViewport of visibleViewports) {
|
|
42587
42602
|
const referenceFrame = visibleViewport.referenceFrame;
|
|
42588
42603
|
const start = referenceFrame.start;
|
|
42589
|
-
const end = start + referenceFrame.toBP(
|
|
42604
|
+
const end = start + referenceFrame.toBP(visibleViewport.getWidth());
|
|
42590
42605
|
if (visibleViewport.featureCache && visibleViewport.featureCache.features) {
|
|
42606
|
+
// If the "features" object has a getMax function use it. Currently only alignmentContainer implements this, for coverage.
|
|
42591
42607
|
if (typeof visibleViewport.featureCache.features.getMax === 'function') {
|
|
42592
42608
|
const max = visibleViewport.featureCache.features.getMax(start, end);
|
|
42593
|
-
allFeatures
|
|
42609
|
+
allFeatures = [{value: max}];
|
|
42594
42610
|
} else {
|
|
42595
|
-
allFeatures =
|
|
42611
|
+
allFeatures = FeatureUtils.findOverlapping(visibleViewport.featureCache.features, start, end);
|
|
42596
42612
|
}
|
|
42597
42613
|
}
|
|
42598
42614
|
}
|
|
@@ -42657,7 +42673,7 @@ class TrackView {
|
|
|
42657
42673
|
|
|
42658
42674
|
const referenceFrame = vp.referenceFrame;
|
|
42659
42675
|
const {chr, start, bpPerPixel} = vp.referenceFrame;
|
|
42660
|
-
const end = start + referenceFrame.toBP(
|
|
42676
|
+
const end = start + referenceFrame.toBP(vp.getWidth());
|
|
42661
42677
|
const needsReload = !vp.featureCache || !vp.featureCache.containsRange(chr, start, end, bpPerPixel);
|
|
42662
42678
|
|
|
42663
42679
|
if (needsReload) {
|
|
@@ -43549,7 +43565,7 @@ class FeatureTrack extends TrackBase {
|
|
|
43549
43565
|
|
|
43550
43566
|
if (typeof this.featureSource.getHeader === "function") {
|
|
43551
43567
|
this.header = await this.featureSource.getHeader();
|
|
43552
|
-
if(this.disposed) return
|
|
43568
|
+
if (this.disposed) return // This track was removed during async load
|
|
43553
43569
|
}
|
|
43554
43570
|
|
|
43555
43571
|
// Set properties from track line
|
|
@@ -43642,7 +43658,7 @@ class FeatureTrack extends TrackBase {
|
|
|
43642
43658
|
options.rowLastLabelX[row] = -Number.MAX_SAFE_INTEGER;
|
|
43643
43659
|
}
|
|
43644
43660
|
}
|
|
43645
|
-
const maxFeatureCount = Math.max(1,
|
|
43661
|
+
const maxFeatureCount = Math.max(1, Math.max(...rowFeatureCount));
|
|
43646
43662
|
const pixelsPerFeature = pixelWidth / maxFeatureCount;
|
|
43647
43663
|
|
|
43648
43664
|
let lastPxEnd = [];
|
|
@@ -43673,10 +43689,10 @@ class FeatureTrack extends TrackBase {
|
|
|
43673
43689
|
|
|
43674
43690
|
};
|
|
43675
43691
|
|
|
43676
|
-
clickedFeatures(clickState
|
|
43692
|
+
clickedFeatures(clickState) {
|
|
43677
43693
|
|
|
43678
43694
|
const y = clickState.y - this.margin;
|
|
43679
|
-
const allFeatures = super.clickedFeatures(clickState
|
|
43695
|
+
const allFeatures = super.clickedFeatures(clickState);
|
|
43680
43696
|
|
|
43681
43697
|
let row;
|
|
43682
43698
|
switch (this.displayMode) {
|
|
@@ -43700,9 +43716,8 @@ class FeatureTrack extends TrackBase {
|
|
|
43700
43716
|
*/
|
|
43701
43717
|
popupData(clickState, features) {
|
|
43702
43718
|
|
|
43703
|
-
features = this.clickedFeatures(clickState
|
|
43719
|
+
if (features === undefined) features = this.clickedFeatures(clickState);
|
|
43704
43720
|
const genomicLocation = clickState.genomicLocation;
|
|
43705
|
-
|
|
43706
43721
|
const data = [];
|
|
43707
43722
|
for (let feature of features) {
|
|
43708
43723
|
|
|
@@ -43827,8 +43842,7 @@ class FeatureTrack extends TrackBase {
|
|
|
43827
43842
|
let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
|
|
43828
43843
|
if (!seq) {
|
|
43829
43844
|
seq = "Unknown sequence";
|
|
43830
|
-
}
|
|
43831
|
-
else if (f.strand === '-') {
|
|
43845
|
+
} else if (f.strand === '-') {
|
|
43832
43846
|
seq = reverseComplementSequence(seq);
|
|
43833
43847
|
}
|
|
43834
43848
|
this.browser.alert.present(seq);
|
|
@@ -43844,8 +43858,7 @@ class FeatureTrack extends TrackBase {
|
|
|
43844
43858
|
let seq = await this.browser.genome.getSequence(f.chr, f.start, f.end);
|
|
43845
43859
|
if (!seq) {
|
|
43846
43860
|
seq = "Unknown sequence";
|
|
43847
|
-
}
|
|
43848
|
-
else if (f.strand === '-') {
|
|
43861
|
+
} else if (f.strand === '-') {
|
|
43849
43862
|
seq = reverseComplementSequence(seq);
|
|
43850
43863
|
}
|
|
43851
43864
|
try {
|
|
@@ -44162,10 +44175,7 @@ class WigTrack extends TrackBase {
|
|
|
44162
44175
|
|
|
44163
44176
|
popupData(clickState, features) {
|
|
44164
44177
|
|
|
44165
|
-
|
|
44166
|
-
// feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
|
|
44167
|
-
|
|
44168
|
-
features = this.clickedFeatures(clickState, features);
|
|
44178
|
+
if(features === undefined) features = this.clickedFeatures(clickState);
|
|
44169
44179
|
|
|
44170
44180
|
if (features && features.length > 0) {
|
|
44171
44181
|
|
|
@@ -44483,7 +44493,7 @@ class SegTrack extends TrackBase {
|
|
|
44483
44493
|
|
|
44484
44494
|
draw({context, renderSVG, pixelTop, pixelWidth, pixelHeight, features, bpPerPixel, bpStart}) {
|
|
44485
44495
|
|
|
44486
|
-
IGVGraphics.fillRect(context, 0,
|
|
44496
|
+
IGVGraphics.fillRect(context, 0, pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
|
|
44487
44497
|
|
|
44488
44498
|
if (features && features.length > 0) {
|
|
44489
44499
|
|
|
@@ -44707,9 +44717,9 @@ class SegTrack extends TrackBase {
|
|
|
44707
44717
|
|
|
44708
44718
|
}
|
|
44709
44719
|
|
|
44710
|
-
clickedFeatures(clickState
|
|
44720
|
+
clickedFeatures(clickState) {
|
|
44711
44721
|
|
|
44712
|
-
const allFeatures = super.clickedFeatures(clickState
|
|
44722
|
+
const allFeatures = super.clickedFeatures(clickState);
|
|
44713
44723
|
const y = clickState.y;
|
|
44714
44724
|
return allFeatures.filter(function (feature) {
|
|
44715
44725
|
const rect = feature.pixelRect;
|
|
@@ -44718,13 +44728,16 @@ class SegTrack extends TrackBase {
|
|
|
44718
44728
|
|
|
44719
44729
|
}
|
|
44720
44730
|
|
|
44721
|
-
hoverText(
|
|
44722
|
-
|
|
44731
|
+
hoverText(clickState) {
|
|
44732
|
+
const features = this.clickedFeatures(clickState);
|
|
44733
|
+
if(features && features.length > 0) {
|
|
44734
|
+
return `${features[0].sample}: ${features[0].value}`
|
|
44735
|
+
}
|
|
44723
44736
|
}
|
|
44724
44737
|
|
|
44725
44738
|
popupData(clickState, featureList) {
|
|
44726
44739
|
|
|
44727
|
-
featureList = this.clickedFeatures(clickState);
|
|
44740
|
+
if(featureList === undefined) featureList = this.clickedFeatures(clickState);
|
|
44728
44741
|
|
|
44729
44742
|
const items = [];
|
|
44730
44743
|
|
|
@@ -44880,6 +44893,7 @@ const MUT_COLORS = {
|
|
|
44880
44893
|
* THE SOFTWARE.
|
|
44881
44894
|
*/
|
|
44882
44895
|
|
|
44896
|
+
|
|
44883
44897
|
/**
|
|
44884
44898
|
* Represents 2 or more wig tracks overlaid on a common viewport.
|
|
44885
44899
|
*/
|
|
@@ -44970,49 +44984,78 @@ class MergedTrack extends TrackBase {
|
|
|
44970
44984
|
return items
|
|
44971
44985
|
}
|
|
44972
44986
|
|
|
44987
|
+
/**
|
|
44988
|
+
* Returns a MergedFeatureCollection containing an array of features for the specified range, 1 for each track.
|
|
44989
|
+
*/
|
|
44973
44990
|
async getFeatures(chr, bpStart, bpEnd, bpPerPixel) {
|
|
44974
44991
|
|
|
44975
44992
|
const promises = this.tracks.map((t) => t.getFeatures(chr, bpStart, bpEnd, bpPerPixel));
|
|
44976
|
-
|
|
44993
|
+
const featureArrays = await Promise.all(promises);
|
|
44994
|
+
return new MergedFeatureCollection(featureArrays)
|
|
44977
44995
|
}
|
|
44978
44996
|
|
|
44979
44997
|
draw(options) {
|
|
44980
44998
|
|
|
44981
|
-
const mergedFeatures = options.features; //
|
|
44982
|
-
|
|
44983
|
-
if (this.autoscale) {
|
|
44984
|
-
this.dataRange = autoscale(options.referenceFrame.chr, mergedFeatures);
|
|
44985
|
-
}
|
|
44999
|
+
const mergedFeatures = options.features; // A MergedFeatureCollection
|
|
44986
45000
|
|
|
44987
45001
|
for (let i = 0, len = this.tracks.length; i < len; i++) {
|
|
44988
45002
|
const trackOptions = Object.assign({}, options);
|
|
44989
|
-
trackOptions.features = mergedFeatures[i];
|
|
45003
|
+
trackOptions.features = mergedFeatures.featureArrays[i];
|
|
44990
45004
|
this.tracks[i].dataRange = this.dataRange;
|
|
44991
45005
|
this.tracks[i].flipAxis = this.flipAxis;
|
|
44992
45006
|
this.tracks[i].logScale = this.logScale;
|
|
44993
|
-
if(this.graphType){
|
|
45007
|
+
if (this.graphType) {
|
|
44994
45008
|
this.tracks[i].graphType = this.graphType;
|
|
44995
45009
|
}
|
|
44996
45010
|
this.tracks[i].draw(trackOptions);
|
|
44997
45011
|
}
|
|
44998
45012
|
}
|
|
44999
45013
|
|
|
45000
|
-
popupData(clickState
|
|
45014
|
+
popupData(clickState) {
|
|
45001
45015
|
|
|
45002
|
-
|
|
45016
|
+
if(clickState.viewport && clickState.viewport.cachedFeatures) {
|
|
45003
45017
|
|
|
45004
|
-
|
|
45005
|
-
|
|
45006
|
-
|
|
45007
|
-
|
|
45008
|
-
|
|
45009
|
-
|
|
45010
|
-
|
|
45011
|
-
|
|
45018
|
+
const featuresArray = clickState.viewport.cachedFeatures.featureArrays;
|
|
45019
|
+
|
|
45020
|
+
if (featuresArray && featuresArray.length === this.tracks.length) {
|
|
45021
|
+
// Array of feature arrays, 1 for each track
|
|
45022
|
+
const popupData = [];
|
|
45023
|
+
for (let i = 0; i < this.tracks.length; i++) {
|
|
45024
|
+
if (i > 0) popupData.push('<hr/>');
|
|
45025
|
+
popupData.push(`<div style=background-color:rgb(245,245,245);border-bottom-style:dashed;border-bottom-width:1px;padding-bottom:5px;padding-top:10px;font-weight:bold;font-size:larger >${this.tracks[i].name}</div>`);
|
|
45026
|
+
const trackPopupData = this.tracks[i].popupData(clickState, featuresArray[i]);
|
|
45027
|
+
popupData.push(...trackPopupData);
|
|
45012
45028
|
|
|
45029
|
+
}
|
|
45030
|
+
return popupData
|
|
45031
|
+
}
|
|
45032
|
+
}
|
|
45033
|
+
}
|
|
45034
|
+
|
|
45035
|
+
clickedFeatures(clickState) {
|
|
45036
|
+
|
|
45037
|
+
|
|
45038
|
+
// We use the cached features rather than method to avoid async load. If the
|
|
45039
|
+
// feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
|
|
45040
|
+
const mergedFeaturesCollection = clickState.viewport.cachedFeatures;
|
|
45041
|
+
|
|
45042
|
+
if(!mergedFeaturesCollection) {
|
|
45043
|
+
return [];
|
|
45044
|
+
}
|
|
45045
|
+
|
|
45046
|
+
const genomicLocation = clickState.genomicLocation;
|
|
45047
|
+
const clickedFeatures = [];
|
|
45048
|
+
for(let features of mergedFeaturesCollection.featureArrays) {
|
|
45049
|
+
// When zoomed out we need some tolerance around genomicLocation
|
|
45050
|
+
const tolerance = (clickState.referenceFrame.bpPerPixel > 0.2) ? 3 * clickState.referenceFrame.bpPerPixel : 0.2;
|
|
45051
|
+
const ss = genomicLocation - tolerance;
|
|
45052
|
+
const ee = genomicLocation + tolerance;
|
|
45053
|
+
const tmp = (FeatureUtils.findOverlapping(features, ss, ee));
|
|
45054
|
+
for(let f of tmp) {
|
|
45055
|
+
clickedFeatures.push(f);
|
|
45013
45056
|
}
|
|
45014
|
-
return popupData
|
|
45015
45057
|
}
|
|
45058
|
+
return clickedFeatures;
|
|
45016
45059
|
}
|
|
45017
45060
|
|
|
45018
45061
|
|
|
@@ -45021,19 +45064,31 @@ class MergedTrack extends TrackBase {
|
|
|
45021
45064
|
}
|
|
45022
45065
|
}
|
|
45023
45066
|
|
|
45024
|
-
function autoscale(chr, featureArrays) {
|
|
45025
45067
|
|
|
45026
|
-
|
|
45027
|
-
|
|
45028
|
-
|
|
45029
|
-
|
|
45030
|
-
|
|
45031
|
-
|
|
45032
|
-
|
|
45068
|
+
class MergedFeatureCollection {
|
|
45069
|
+
|
|
45070
|
+
constructor(featureArrays) {
|
|
45071
|
+
this.featureArrays = featureArrays;
|
|
45072
|
+
}
|
|
45073
|
+
|
|
45074
|
+
getMax(start, end) {
|
|
45075
|
+
let max = -Number.MAX_VALUE;
|
|
45076
|
+
for (let a of this.featureArrays) {
|
|
45077
|
+
for (let f of a) {
|
|
45078
|
+
if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
|
|
45079
|
+
if (f.end < start) {
|
|
45080
|
+
continue
|
|
45081
|
+
}
|
|
45082
|
+
if (f.start > end) {
|
|
45083
|
+
break
|
|
45084
|
+
}
|
|
45085
|
+
max = Math.max(max, f.value);
|
|
45086
|
+
}
|
|
45033
45087
|
}
|
|
45034
45088
|
}
|
|
45089
|
+
return max
|
|
45035
45090
|
}
|
|
45036
|
-
|
|
45091
|
+
|
|
45037
45092
|
}
|
|
45038
45093
|
|
|
45039
45094
|
/*
|
|
@@ -45616,7 +45671,7 @@ class InteractionTrack extends TrackBase {
|
|
|
45616
45671
|
|
|
45617
45672
|
popupData(clickState, features) {
|
|
45618
45673
|
|
|
45619
|
-
features = this.clickedFeatures(clickState);
|
|
45674
|
+
if(features === undefined) features = this.clickedFeatures(clickState);
|
|
45620
45675
|
|
|
45621
45676
|
const data = [];
|
|
45622
45677
|
for (let feature of features) {
|
|
@@ -45658,11 +45713,11 @@ class InteractionTrack extends TrackBase {
|
|
|
45658
45713
|
return data
|
|
45659
45714
|
}
|
|
45660
45715
|
|
|
45661
|
-
clickedFeatures(clickState
|
|
45716
|
+
clickedFeatures(clickState) {
|
|
45662
45717
|
|
|
45663
45718
|
// We use the cached features rather than method to avoid async load. If the
|
|
45664
45719
|
// feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
|
|
45665
|
-
const featureList =
|
|
45720
|
+
const featureList = clickState.viewport.cachedFeatures;
|
|
45666
45721
|
const candidates = [];
|
|
45667
45722
|
if (featureList) {
|
|
45668
45723
|
const proportional = (this.arcType === "proportional" || this.arcType === "inView" || this.arcType === "partialInView");
|
|
@@ -46252,9 +46307,9 @@ class VariantTrack extends TrackBase {
|
|
|
46252
46307
|
}
|
|
46253
46308
|
}
|
|
46254
46309
|
|
|
46255
|
-
clickedFeatures(clickState
|
|
46310
|
+
clickedFeatures(clickState) {
|
|
46256
46311
|
|
|
46257
|
-
let featureList = super.clickedFeatures(clickState
|
|
46312
|
+
let featureList = super.clickedFeatures(clickState);
|
|
46258
46313
|
|
|
46259
46314
|
const vGap = (this.displayMode === 'EXPANDED') ? this.expandedVGap : this.squishedVGap;
|
|
46260
46315
|
const callHeight = vGap + ("SQUISHED" === this.displayMode ? this.squishedCallHeight : this.expandedCallHeight);
|
|
@@ -46290,9 +46345,9 @@ class VariantTrack extends TrackBase {
|
|
|
46290
46345
|
/**
|
|
46291
46346
|
* Return "popup data" for feature @ genomic location. Data is an array of key-value pairs
|
|
46292
46347
|
*/
|
|
46293
|
-
popupData(clickState,
|
|
46348
|
+
popupData(clickState, featureList) {
|
|
46294
46349
|
|
|
46295
|
-
|
|
46350
|
+
if(featureList === undefined) featureList = this.clickedFeatures(clickState);
|
|
46296
46351
|
const genomicLocation = clickState.genomicLocation;
|
|
46297
46352
|
const genomeID = this.browser.genome.id;
|
|
46298
46353
|
const sampleInformation = this.browser.sampleInformation;
|
|
@@ -46820,9 +46875,9 @@ class EqtlTrack extends TrackBase {
|
|
|
46820
46875
|
/**
|
|
46821
46876
|
* Return "popup data" for feature @ genomic location. Data is an array of key-value pairs
|
|
46822
46877
|
*/
|
|
46823
|
-
popupData(clickState) {
|
|
46878
|
+
popupData(clickState, features) {
|
|
46824
46879
|
|
|
46825
|
-
|
|
46880
|
+
if(features === undefined) features = clickState.viewport.cachedFeatures;
|
|
46826
46881
|
if (!features || features.length === 0) return []
|
|
46827
46882
|
|
|
46828
46883
|
const tolerance = 3;
|
|
@@ -47149,11 +47204,12 @@ class GWASTrack extends TrackBase {
|
|
|
47149
47204
|
}
|
|
47150
47205
|
}
|
|
47151
47206
|
|
|
47152
|
-
popupData(clickState) {
|
|
47207
|
+
popupData(clickState, features) {
|
|
47208
|
+
|
|
47209
|
+
if(features === undefined) features = clickState.viewport.cachedFeatures;
|
|
47153
47210
|
|
|
47154
47211
|
let data = [];
|
|
47155
47212
|
const track = clickState.viewport.trackView.track;
|
|
47156
|
-
const features = clickState.viewport.cachedFeatures;
|
|
47157
47213
|
|
|
47158
47214
|
if (features) {
|
|
47159
47215
|
let count = 0;
|
|
@@ -47541,12 +47597,12 @@ class GCNVTrack extends TrackBase {
|
|
|
47541
47597
|
return []
|
|
47542
47598
|
}
|
|
47543
47599
|
|
|
47544
|
-
popupData(clickState,
|
|
47600
|
+
popupData(clickState, features) {
|
|
47545
47601
|
|
|
47546
|
-
|
|
47602
|
+
if(features === undefined) features = this.clickedFeatures(clickState);
|
|
47547
47603
|
|
|
47548
47604
|
const items = [];
|
|
47549
|
-
|
|
47605
|
+
features.forEach(function (f) {
|
|
47550
47606
|
for (let property of Object.keys(f)) {
|
|
47551
47607
|
if (isSimpleType(f[property])) {
|
|
47552
47608
|
items.push({name: property, value: f[property]});
|
|
@@ -47732,9 +47788,9 @@ class RnaStructTrack extends TrackBase {
|
|
|
47732
47788
|
}
|
|
47733
47789
|
}
|
|
47734
47790
|
|
|
47735
|
-
clickedFeatures(clickState
|
|
47791
|
+
clickedFeatures(clickState) {
|
|
47736
47792
|
|
|
47737
|
-
features = super.clickedFeatures(clickState
|
|
47793
|
+
const features = super.clickedFeatures(clickState);
|
|
47738
47794
|
|
|
47739
47795
|
const clicked = [];
|
|
47740
47796
|
|
|
@@ -47776,10 +47832,7 @@ class RnaStructTrack extends TrackBase {
|
|
|
47776
47832
|
|
|
47777
47833
|
popupData(clickState, features) {
|
|
47778
47834
|
|
|
47779
|
-
|
|
47780
|
-
// feature is not already loaded this won't work, but the user wouldn't be mousing over it either.
|
|
47781
|
-
|
|
47782
|
-
features = this.clickedFeatures(clickState, features);
|
|
47835
|
+
if(features === undefined) features = this.clickedFeatures(clickState);
|
|
47783
47836
|
|
|
47784
47837
|
if (features && features.length > 0) {
|
|
47785
47838
|
|
|
@@ -48207,7 +48260,7 @@ class SpliceJunctionTrack extends TrackBase {
|
|
|
48207
48260
|
|
|
48208
48261
|
if (typeof this.featureSource.getHeader === "function") {
|
|
48209
48262
|
this.header = await this.featureSource.getHeader();
|
|
48210
|
-
if(this.disposed) return
|
|
48263
|
+
if (this.disposed) return // This track was removed during async load
|
|
48211
48264
|
}
|
|
48212
48265
|
|
|
48213
48266
|
// Set properties from track line
|
|
@@ -48264,7 +48317,8 @@ class SpliceJunctionTrack extends TrackBase {
|
|
|
48264
48317
|
|
|
48265
48318
|
junctionRenderingContext.referenceFrame = options.viewport.referenceFrame;
|
|
48266
48319
|
junctionRenderingContext.referenceFrameStart = junctionRenderingContext.referenceFrame.start;
|
|
48267
|
-
junctionRenderingContext.referenceFrameEnd = junctionRenderingContext.referenceFrameStart +
|
|
48320
|
+
junctionRenderingContext.referenceFrameEnd = junctionRenderingContext.referenceFrameStart +
|
|
48321
|
+
junctionRenderingContext.referenceFrame.toBP(options.viewport.getWidth());
|
|
48268
48322
|
|
|
48269
48323
|
// For a given viewport, records where features that are < 2px in width have been rendered already.
|
|
48270
48324
|
// This prevents wasteful rendering of multiple such features onto the same pixels.
|
|
@@ -48512,9 +48566,9 @@ class SpliceJunctionTrack extends TrackBase {
|
|
|
48512
48566
|
ctx.fillText(label, junctionMiddlePx - ctx.measureText(label).width / 2, (7 * topY + cy) / 8);
|
|
48513
48567
|
}
|
|
48514
48568
|
|
|
48515
|
-
clickedFeatures(clickState
|
|
48569
|
+
clickedFeatures(clickState) {
|
|
48516
48570
|
|
|
48517
|
-
const allFeatures = super.clickedFeatures(clickState
|
|
48571
|
+
const allFeatures = super.clickedFeatures(clickState);
|
|
48518
48572
|
|
|
48519
48573
|
return allFeatures.filter(function (feature) {
|
|
48520
48574
|
return (feature.isVisible && feature.attributes)
|
|
@@ -48526,7 +48580,7 @@ class SpliceJunctionTrack extends TrackBase {
|
|
|
48526
48580
|
*/
|
|
48527
48581
|
popupData(clickState, features) {
|
|
48528
48582
|
|
|
48529
|
-
features = this.clickedFeatures(clickState
|
|
48583
|
+
if (features === undefined) features = this.clickedFeatures(clickState);
|
|
48530
48584
|
const genomicLocation = clickState.genomicLocation;
|
|
48531
48585
|
|
|
48532
48586
|
const data = [];
|
|
@@ -51970,11 +52024,11 @@ class Browser {
|
|
|
51970
52024
|
*/
|
|
51971
52025
|
toSVG() {
|
|
51972
52026
|
|
|
51973
|
-
|
|
52027
|
+
const {y, width, height} = this.columnContainer.getBoundingClientRect();
|
|
51974
52028
|
|
|
51975
52029
|
const h_render = 8000;
|
|
51976
52030
|
|
|
51977
|
-
|
|
52031
|
+
const config =
|
|
51978
52032
|
{
|
|
51979
52033
|
|
|
51980
52034
|
width,
|
|
@@ -51992,13 +52046,14 @@ class Browser {
|
|
|
51992
52046
|
height: h_render
|
|
51993
52047
|
}
|
|
51994
52048
|
|
|
51995
|
-
}
|
|
52049
|
+
};
|
|
52050
|
+
|
|
52051
|
+
const context = new ctx(config);
|
|
51996
52052
|
|
|
51997
52053
|
// tracks -> SVG
|
|
51998
52054
|
for (let trackView of this.trackViews) {
|
|
51999
52055
|
trackView.renderSVGContext(context, {deltaX: 0, deltaY: -y});
|
|
52000
52056
|
}
|
|
52001
|
-
|
|
52002
52057
|
// reset height to trim away unneeded svg canvas real estate. Yes, a bit of a hack.
|
|
52003
52058
|
context.setHeight(height);
|
|
52004
52059
|
|