genoverse 3.2.0
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/.eslintrc.js +197 -0
- package/.github/workflows/test.yml +24 -0
- package/LICENSE.TXT +24 -0
- package/README.md +11 -0
- package/css/controlPanel.css +200 -0
- package/css/fileDrop.css +22 -0
- package/css/font-awesome.css +3 -0
- package/css/fullscreen.css +19 -0
- package/css/genoverse.css +466 -0
- package/css/karyotype.css +85 -0
- package/css/resizer.css +36 -0
- package/css/tooltips.css +26 -0
- package/css/trackControls.css +111 -0
- package/expanded.html +120 -0
- package/fontawesome/css/fontawesome.min.css +5 -0
- package/fontawesome/css/regular.min.css +5 -0
- package/fontawesome/css/solid.min.css +5 -0
- package/fontawesome/webfonts/fa-brands-400.ttf +0 -0
- package/fontawesome/webfonts/fa-brands-400.woff +0 -0
- package/fontawesome/webfonts/fa-brands-400.woff2 +0 -0
- package/fontawesome/webfonts/fa-regular-400.ttf +0 -0
- package/fontawesome/webfonts/fa-regular-400.woff +0 -0
- package/fontawesome/webfonts/fa-regular-400.woff2 +0 -0
- package/fontawesome/webfonts/fa-solid-900.ttf +0 -0
- package/fontawesome/webfonts/fa-solid-900.woff +0 -0
- package/fontawesome/webfonts/fa-solid-900.woff2 +0 -0
- package/help.pdf +0 -0
- package/i/sort_handle.png +0 -0
- package/index.html +68 -0
- package/index.js +83 -0
- package/jest.config.js +4 -0
- package/js/Genoverse.js +1681 -0
- package/js/Track/Controller/Sequence.js +17 -0
- package/js/Track/Controller/Stranded.js +73 -0
- package/js/Track/Controller.js +620 -0
- package/js/Track/Model/File/BAM.js +44 -0
- package/js/Track/Model/File/BED.js +116 -0
- package/js/Track/Model/File/GFF.js +40 -0
- package/js/Track/Model/File/VCF.js +101 -0
- package/js/Track/Model/File/WIG.js +67 -0
- package/js/Track/Model/File.js +36 -0
- package/js/Track/Model/Gene/Ensembl.js +22 -0
- package/js/Track/Model/Gene.js +5 -0
- package/js/Track/Model/Sequence/Ensembl.js +4 -0
- package/js/Track/Model/Sequence/Fasta.js +60 -0
- package/js/Track/Model/Sequence.js +50 -0
- package/js/Track/Model/SequenceVariation.js +41 -0
- package/js/Track/Model/Stranded.js +28 -0
- package/js/Track/Model/Transcript/Ensembl.js +67 -0
- package/js/Track/Model/Transcript.js +5 -0
- package/js/Track/Model.js +303 -0
- package/js/Track/View/Gene/Ensembl.js +46 -0
- package/js/Track/View/Gene.js +6 -0
- package/js/Track/View/Sequence/Variation.js +115 -0
- package/js/Track/View/Sequence.js +63 -0
- package/js/Track/View/Transcript/Ensembl.js +12 -0
- package/js/Track/View/Transcript.js +28 -0
- package/js/Track/View.js +566 -0
- package/js/Track/library/Chromosome.js +145 -0
- package/js/Track/library/File/BAM.js +30 -0
- package/js/Track/library/File/BED.js +24 -0
- package/js/Track/library/File/BIGBED.js +47 -0
- package/js/Track/library/File/BIGWIG.js +52 -0
- package/js/Track/library/File/GFF.js +9 -0
- package/js/Track/library/File/VCF.js +71 -0
- package/js/Track/library/File/WIG.js +5 -0
- package/js/Track/library/File.js +10 -0
- package/js/Track/library/Gene.js +37 -0
- package/js/Track/library/Graph/Bar.js +235 -0
- package/js/Track/library/Graph/Line.js +296 -0
- package/js/Track/library/Graph.js +355 -0
- package/js/Track/library/HighlightRegion.js +292 -0
- package/js/Track/library/Legend.js +224 -0
- package/js/Track/library/Scalebar.js +227 -0
- package/js/Track/library/Scaleline.js +91 -0
- package/js/Track/library/Static.js +78 -0
- package/js/Track/library/dbSNP.js +142 -0
- package/js/Track.js +632 -0
- package/js/genomes/grch37.js +990 -0
- package/js/genomes/grch38.js +990 -0
- package/js/genoverse.min.js +2 -0
- package/js/genoverse.min.js.map +1 -0
- package/js/lib/BWReader.js +578 -0
- package/js/lib/Base.js +145 -0
- package/js/lib/VCFReader.js +286 -0
- package/js/lib/dalliance/js/bam.js +494 -0
- package/js/lib/dalliance/js/bin.js +185 -0
- package/js/lib/dalliance/js/das.js +749 -0
- package/js/lib/dalliance/js/utils.js +370 -0
- package/js/lib/dalliance-lib.js +3594 -0
- package/js/lib/dalliance-lib.min.js +68 -0
- package/js/lib/jDataView.js +2 -0
- package/js/lib/jParser.js +192 -0
- package/js/lib/jquery-ui.js +8 -0
- package/js/lib/jquery.js +2 -0
- package/js/lib/jquery.mousehold.js +53 -0
- package/js/lib/jquery.mousewheel.js +84 -0
- package/js/lib/jquery.tipsy.js +258 -0
- package/js/lib/rtree.js +1 -0
- package/js/plugins/controlPanel.js +395 -0
- package/js/plugins/fileDrop.js +62 -0
- package/js/plugins/focusRegion.js +12 -0
- package/js/plugins/fullscreen.js +77 -0
- package/js/plugins/karyotype.js +210 -0
- package/js/plugins/resizer.js +45 -0
- package/js/plugins/tooltips.js +94 -0
- package/js/plugins/trackControls.js +143 -0
- package/package.json +43 -0
- package/test/View/__snapshots__/render-bar-graph.test.js.snap +111 -0
- package/test/View/__snapshots__/render-blocks.test.js.snap +105 -0
- package/test/View/__snapshots__/render-chromosome.test.js.snap +5 -0
- package/test/View/__snapshots__/render-highlights.test.js.snap +73 -0
- package/test/View/__snapshots__/render-insert-variants.test.js.snap +9 -0
- package/test/View/__snapshots__/render-labels.test.js.snap +241 -0
- package/test/View/__snapshots__/render-legends.test.js.snap +13 -0
- package/test/View/__snapshots__/render-line-graph.test.js.snap +349 -0
- package/test/View/__snapshots__/render-scalebar.test.js.snap +49 -0
- package/test/View/__snapshots__/render-scaleline.test.js.snap +31 -0
- package/test/View/__snapshots__/render-sequence.test.js.snap +23 -0
- package/test/View/__snapshots__/render-stranded.test.js.snap +5 -0
- package/test/View/__snapshots__/render-transcripts.test.js.snap +193 -0
- package/test/View/render-bar-graph.test.js +87 -0
- package/test/View/render-blocks.test.js +171 -0
- package/test/View/render-chromosome.test.js +40 -0
- package/test/View/render-highlights.test.js +67 -0
- package/test/View/render-insert-variants.test.js +11 -0
- package/test/View/render-labels.test.js +266 -0
- package/test/View/render-legends.test.js +31 -0
- package/test/View/render-line-graph.test.js +169 -0
- package/test/View/render-scalebar.test.js +36 -0
- package/test/View/render-scaleline.test.js +28 -0
- package/test/View/render-sequence.test.js +49 -0
- package/test/View/render-stranded.test.js +10 -0
- package/test/View/render-transcripts.test.js +165 -0
- package/test/create-and-destroy.test.js +63 -0
- package/test/track-ordering.test.js +514 -0
- package/test/track_config/__snapshots__/config-settings.test.js.snap +23 -0
- package/test/track_config/config-settings.test.js +321 -0
- package/test/track_config/zoom-level-settings.test.js +98 -0
- package/test/utils.js +80 -0
- package/utils/createGenome.js +52 -0
- package/utils/devServer.js +36 -0
- package/utils/expandedTemplate.html +46 -0
- package/utils/git-hooks/post-commit +9 -0
- package/utils/git-hooks/pre-commit +7 -0
- package/utils/git-hooks/setup +6 -0
- package/utils/makeExpanded.js +19 -0
- package/webpack.config.js +39 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Genoverse.Track.Model.File.BED = Genoverse.Track.Model.File.extend({
|
|
2
|
+
parseData: function (data, chr) {
|
|
3
|
+
var lines = typeof data === 'string' ? data.split('\n') : data;
|
|
4
|
+
var thinHeight = this.prop('thinHeight');
|
|
5
|
+
var thickHeight = this.prop('thickHeight');
|
|
6
|
+
var fields, len, feature, subfeatures, subfeature, blockSizes, blockStarts, j, thinFeature, thinFeature1, thinFeature2, thickFeature;
|
|
7
|
+
|
|
8
|
+
function filterNumber(n) {
|
|
9
|
+
return !isNaN(n);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (var i = 0; i < lines.length; i++) {
|
|
13
|
+
fields = lines[i].split('\t').filter(function (f) { return f; });
|
|
14
|
+
|
|
15
|
+
if (fields.length < 3 || fields[0] === 'track' || fields[0] === 'browser') {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
len = fields.length;
|
|
20
|
+
|
|
21
|
+
if (fields[0] === String(chr) || fields[0].toLowerCase() === 'chr' + chr || fields[0].match('[^1-9]' + chr + '$')) {
|
|
22
|
+
feature = {
|
|
23
|
+
chr : chr,
|
|
24
|
+
start : parseInt(fields[1], 10) + 1,
|
|
25
|
+
end : parseInt(fields[2], 10),
|
|
26
|
+
name : fields[3],
|
|
27
|
+
color : '#000000',
|
|
28
|
+
originalFeature : fields
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (len > 3) { feature.score = parseFloat(fields[4], 10); }
|
|
32
|
+
if (len > 5) { feature.strand = fields[5]; }
|
|
33
|
+
|
|
34
|
+
if (len > 7) {
|
|
35
|
+
feature.thickStart = parseInt(fields[6], 10) + 1;
|
|
36
|
+
feature.thickEnd = parseInt(fields[7], 10);
|
|
37
|
+
feature.drawThick = fields[6] !== fields[7];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (fields[8]) {
|
|
41
|
+
feature.color = 'rgb(' + fields[8] + ')';
|
|
42
|
+
} else {
|
|
43
|
+
feature.color = this.scoreColor(isNaN(feature.score) ? 1000 : feature.score);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (len === 12) { // subfeatures present
|
|
47
|
+
feature.blockCount = parseInt(fields[9], 10);
|
|
48
|
+
|
|
49
|
+
subfeatures = [];
|
|
50
|
+
blockSizes = fields[10].split(',').filter(filterNumber);
|
|
51
|
+
blockStarts = fields[11].split(',').filter(filterNumber);
|
|
52
|
+
|
|
53
|
+
for (j = 0; j < blockSizes.length; j++) {
|
|
54
|
+
subfeature = {
|
|
55
|
+
start : feature.start + parseInt(blockStarts[j], 10),
|
|
56
|
+
height : thinHeight // if subfeature lies entirely left / right to [ thickStart, thickEnd ]
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
subfeature.end = subfeature.start + parseInt(blockSizes[j], 10) - 1;
|
|
60
|
+
|
|
61
|
+
if (feature.drawThick && subfeature.start <= feature.thickEnd && subfeature.end >= feature.thickStart) {
|
|
62
|
+
// some kind of an overlap for sure
|
|
63
|
+
if (subfeature.start >= feature.thickStart && subfeature.end <= feature.thickEnd) {
|
|
64
|
+
// subfeature within thickBlock, draw thick
|
|
65
|
+
subfeature.height = thickHeight;
|
|
66
|
+
subfeatures.push(subfeature);
|
|
67
|
+
} else if (subfeature.start < feature.thickStart && subfeature.end <= feature.thickEnd) {
|
|
68
|
+
// left overlap, split subfeature into 2 - thin | thick
|
|
69
|
+
thinFeature = $.extend({}, subfeature, { end: feature.thickStart });
|
|
70
|
+
thickFeature = $.extend({}, subfeature, { start: feature.thickStart, height: thickHeight });
|
|
71
|
+
|
|
72
|
+
subfeatures = subfeatures.concat([ thinFeature, thickFeature ]);
|
|
73
|
+
} else if (subfeature.start >= feature.thickStart && subfeature.end > feature.thickEnd) {
|
|
74
|
+
// right overlap, split subfeature into 2 - thick | thin
|
|
75
|
+
thinFeature = $.extend({}, subfeature, { start: feature.thickEnd });
|
|
76
|
+
thickFeature = $.extend({}, subfeature, { end: feature.thickEnd, height: thickHeight });
|
|
77
|
+
|
|
78
|
+
subfeatures = subfeatures.concat([ thickFeature, thinFeature ]);
|
|
79
|
+
} else {
|
|
80
|
+
// thickBlock lies within subfeature, split into 3 - thin | thick | thin
|
|
81
|
+
// the least possible case but lets be prepared for the outliers
|
|
82
|
+
thinFeature1 = $.extend({}, subfeature, { end: feature.thickStart });
|
|
83
|
+
thinFeature2 = $.extend({}, subfeature, { start: feature.thickEnd });
|
|
84
|
+
thickFeature = { start: feature.thickStart, end: feature.thickEnd, height: thickHeight };
|
|
85
|
+
|
|
86
|
+
subfeatures = subfeatures.concat([ thinFeature1, thickFeature, thinFeature2 ]);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// no thick block
|
|
90
|
+
subfeatures.push(subfeature);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (subfeatures.length) {
|
|
95
|
+
feature.subFeatures = subfeatures;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.insertFeature(feature);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// As per https://genome.ucsc.edu/FAQ/FAQformat.html#format1 specification
|
|
105
|
+
scoreColor: function (score) {
|
|
106
|
+
if (score <= 166) { return 'rgb(219,219,219)'; }
|
|
107
|
+
if (score <= 277) { return 'rgb(186,186,186)'; }
|
|
108
|
+
if (score <= 388) { return 'rgb(154,154,154)'; }
|
|
109
|
+
if (score <= 499) { return 'rgb(122,122,122)'; }
|
|
110
|
+
if (score <= 611) { return 'rgb(94,94,94)'; }
|
|
111
|
+
if (score <= 722) { return 'rgb(67,67,67)'; }
|
|
112
|
+
if (score <= 833) { return 'rgb(42,42,42)'; }
|
|
113
|
+
if (score <= 944) { return 'rgb(21,21,21)'; }
|
|
114
|
+
return '#000000';
|
|
115
|
+
}
|
|
116
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Genoverse.Track.Model.File.GFF = Genoverse.Track.Model.File.extend({
|
|
2
|
+
parseData: function (text, chr) {
|
|
3
|
+
var lines = text.split('\n');
|
|
4
|
+
|
|
5
|
+
for (var i = 0; i < lines.length; i++) {
|
|
6
|
+
if (!lines[i].length || lines[i].indexOf('#') === 0) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
var fields = lines[i].split('\t');
|
|
11
|
+
|
|
12
|
+
if (fields.length < 5) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var seqId = fields[0].toLowerCase();
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
seqId === String(chr) ||
|
|
20
|
+
seqId === 'chr' + chr ||
|
|
21
|
+
seqId.match('[^1-9]' + chr + '$') ||
|
|
22
|
+
seqId.match('^' + chr + '\\b')
|
|
23
|
+
) {
|
|
24
|
+
this.insertFeature({
|
|
25
|
+
id : fields.slice(0, 5).join('|'),
|
|
26
|
+
chr : chr,
|
|
27
|
+
start : parseInt(fields[3], 10),
|
|
28
|
+
end : parseInt(fields[4], 10),
|
|
29
|
+
source : fields[1],
|
|
30
|
+
type : fields[2],
|
|
31
|
+
score : fields[5],
|
|
32
|
+
strand : fields[6] === '-' ? -1 : 1,
|
|
33
|
+
label : fields[1] + ' ' + fields[2] + ' ' + fields[3] + '-' + fields[4]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
Genoverse.Track.Model.File.GTF = Genoverse.Track.Model.File.GFF;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Genoverse.Track.Model.File.VCF = Genoverse.Track.Model.File.extend({
|
|
2
|
+
getData: function (chr, start, end) {
|
|
3
|
+
var deferred = $.Deferred();
|
|
4
|
+
var model = this;
|
|
5
|
+
|
|
6
|
+
if (!this.prop('gz')) {
|
|
7
|
+
return this.base.apply(this, arguments);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!this.vcfFile) {
|
|
11
|
+
if (this.url) {
|
|
12
|
+
this.vcfFile = new dallianceLib.URLFetchable(this.url);
|
|
13
|
+
this.tbiFile = new dallianceLib.URLFetchable(this.url + this.prop('indexExt'));
|
|
14
|
+
} else if (this.dataFile && this.indexFile) {
|
|
15
|
+
this.vcfFile = new dallianceLib.BlobFetchable(this.dataFile);
|
|
16
|
+
this.tbiFile = new dallianceLib.BlobFetchable(this.indexFile);
|
|
17
|
+
} else {
|
|
18
|
+
return deferred.rejectWith(model, [ 'GZipped VCF files must be accompanied by a .tbi index file' ]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.makeVCF(this.vcfFile, this.tbiFile).then(function (vcf) {
|
|
23
|
+
model.cachedVCF = vcf;
|
|
24
|
+
|
|
25
|
+
vcf.getRecords(chr, start, end, function (records) {
|
|
26
|
+
model.receiveData(records, chr, start, end);
|
|
27
|
+
deferred.resolveWith(model);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return deferred;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
makeVCF: function (vcfFile, tbiFile) {
|
|
35
|
+
var deferred = $.Deferred();
|
|
36
|
+
|
|
37
|
+
if (this.cachedVCF) {
|
|
38
|
+
deferred.resolve(this.cachedVCF);
|
|
39
|
+
} else {
|
|
40
|
+
var vcf = new VCFReader(vcfFile, tbiFile);
|
|
41
|
+
|
|
42
|
+
vcf.readTabix(function (tabix) {
|
|
43
|
+
vcf.tabix = tabix;
|
|
44
|
+
deferred.resolve(vcf);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return deferred;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
parseData: function (text, chr) {
|
|
52
|
+
var lines = text.split('\n');
|
|
53
|
+
var maxQual = this.allData ? this.prop('maxQual') || 0 : false;
|
|
54
|
+
|
|
55
|
+
for (var i = 0; i < lines.length; i++) {
|
|
56
|
+
if (!lines[i].length || lines[i].indexOf('#') === 0) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
var fields = lines[i].split('\t');
|
|
61
|
+
|
|
62
|
+
if (fields.length < 5) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (fields[0] === String(chr) || fields[0] === 'chr' + chr) {
|
|
67
|
+
var id = fields.slice(0, 3).join('|');
|
|
68
|
+
var start = parseInt(fields[1], 10);
|
|
69
|
+
var alleles = fields[4].split(',');
|
|
70
|
+
|
|
71
|
+
alleles.unshift(fields[3]);
|
|
72
|
+
|
|
73
|
+
for (var j = 0; j < alleles.length; j++) {
|
|
74
|
+
var end = start + alleles[j].length - 1;
|
|
75
|
+
|
|
76
|
+
this.insertFeature({
|
|
77
|
+
id : id + '|' + alleles[j],
|
|
78
|
+
sort : j,
|
|
79
|
+
chr : chr,
|
|
80
|
+
start : start,
|
|
81
|
+
end : end,
|
|
82
|
+
width : end - start,
|
|
83
|
+
allele : j === 0 ? 'REF' : 'ALT',
|
|
84
|
+
sequence : alleles[j],
|
|
85
|
+
label : alleles[j],
|
|
86
|
+
labelColor : '#FFFFFF',
|
|
87
|
+
originalFeature : fields
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (maxQual !== false) {
|
|
92
|
+
maxQual = Math.max(maxQual, fields[5]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (maxQual) {
|
|
98
|
+
this.prop('maxQual', maxQual);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Genoverse.Track.Model.File.WIG = Genoverse.Track.Model.Graph.Bar.extend({
|
|
2
|
+
dataType: 'text',
|
|
3
|
+
|
|
4
|
+
getData: function () {
|
|
5
|
+
if (!this.url) {
|
|
6
|
+
this.isLocal = true;
|
|
7
|
+
this.dataFile = this.track.dataFile;
|
|
8
|
+
|
|
9
|
+
return Genoverse.Track.Model.File.prototype.getData.apply(this, arguments);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return this.base.apply(this, arguments);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
parseData: function (text, chr, s, e) {
|
|
16
|
+
var lines = text.split('\n');
|
|
17
|
+
var features = [];
|
|
18
|
+
var fields, chrom, start, step, span, line, feature, i;
|
|
19
|
+
|
|
20
|
+
while (lines.length && (line = lines.shift())) {
|
|
21
|
+
if (line.indexOf('#') !== -1 || line.indexOf('browser') !== -1 || line.indexOf('track') !== -1) {
|
|
22
|
+
continue;
|
|
23
|
+
} else {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (line) {
|
|
29
|
+
fields = line.split(/\s+/);
|
|
30
|
+
chrom = parseInt(fields[1].split('=')[1].replace('chr', ''), 10);
|
|
31
|
+
|
|
32
|
+
if (fields[0] === 'fixedStep') {
|
|
33
|
+
start = parseInt(fields[2].split('=')[1], 10);
|
|
34
|
+
step = parseInt(fields[3].split('=')[1], 10);
|
|
35
|
+
span = fields[4] ? parseInt(fields[4].split('=')[1], 10) : 1;
|
|
36
|
+
|
|
37
|
+
for (i = 0; i < lines.length; i++) {
|
|
38
|
+
features.push({
|
|
39
|
+
chr : chrom,
|
|
40
|
+
start : start,
|
|
41
|
+
end : start + span,
|
|
42
|
+
height : parseFloat(lines[i])
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
start += step;
|
|
46
|
+
}
|
|
47
|
+
} else if (fields[0] === 'variableStep') {
|
|
48
|
+
span = fields[2] ? parseInt(fields[2].split('=')[1], 10) : 1;
|
|
49
|
+
|
|
50
|
+
for (i = 0; i < lines.length; i++) {
|
|
51
|
+
fields = lines[i].split(/\s+/);
|
|
52
|
+
feature = {
|
|
53
|
+
chr : chrom,
|
|
54
|
+
start : parseInt(fields[0], 10),
|
|
55
|
+
height : parseFloat(fields[1])
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
feature.end = feature.start + span;
|
|
59
|
+
|
|
60
|
+
features.push(feature);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return this.base.call(this, features, chr, s, e);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Genoverse.Track.Model.File = Genoverse.Track.Model.extend({
|
|
2
|
+
dataType: 'text',
|
|
3
|
+
|
|
4
|
+
init: function () {
|
|
5
|
+
if (this.isLocal) {
|
|
6
|
+
this.url = false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!(this.largeFile || this.indexFile)) {
|
|
10
|
+
this.allData = true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.base.apply(this, arguments);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
getData: function (chr) {
|
|
17
|
+
var model = this;
|
|
18
|
+
|
|
19
|
+
if (this.isLocal && this.dataFile) {
|
|
20
|
+
var reader = new FileReader();
|
|
21
|
+
var deferred = $.Deferred();
|
|
22
|
+
|
|
23
|
+
reader.onload = function (e) {
|
|
24
|
+
deferred.done(function () {
|
|
25
|
+
this.receiveData(e.target.result, chr, 1, this.browser.getChromosomeSize(chr));
|
|
26
|
+
}).resolveWith(model);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
reader.readAsText(this.dataFile);
|
|
30
|
+
|
|
31
|
+
return deferred;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return this.base.apply(this, arguments);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Ensembl REST API Gene model
|
|
2
|
+
Genoverse.Track.Model.Gene.Ensembl = Genoverse.Track.Model.Gene.extend({
|
|
3
|
+
url : '//rest.ensembl.org/overlap/region/human/__CHR__:__START__-__END__?feature=gene;content-type=application/json',
|
|
4
|
+
dataRequestLimit : 5000000, // As per e! REST API restrictions
|
|
5
|
+
|
|
6
|
+
// The url above responds in json format, data is an array
|
|
7
|
+
// We assume that parents always preceed children in data array, gene -> transcript -> exon
|
|
8
|
+
// See rest.ensembl.org/documentation/info/feature_region for more details
|
|
9
|
+
parseData: function (data, chr) {
|
|
10
|
+
for (var i = 0; i < data.length; i++) {
|
|
11
|
+
var feature = data[i];
|
|
12
|
+
|
|
13
|
+
if (feature.feature_type === 'gene' && !this.featuresById[feature.id]) {
|
|
14
|
+
feature.chr = feature.chr || chr;
|
|
15
|
+
feature.label = parseInt(feature.strand, 10) === 1 ? (feature.external_name || feature.id) + ' >' : '< ' + (feature.external_name || feature.id);
|
|
16
|
+
feature.transcripts = [];
|
|
17
|
+
|
|
18
|
+
this.insertFeature(feature);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Genoverse.Track.Model.Sequence.Fasta = Genoverse.Track.Model.Sequence.extend({
|
|
2
|
+
url: 'https://wtsi-web.github.io/Genoverse/data/Homo_sapiens.GRCh37.72.dna.chromosome.1.fa', // Example url
|
|
3
|
+
|
|
4
|
+
// Following settings could be left undefined and will be detected automatically via .getStartByte()
|
|
5
|
+
startByte : undefined, // Byte in the file where the sequence actually starts
|
|
6
|
+
lineLength : undefined, // Length of the sequence line in the file
|
|
7
|
+
|
|
8
|
+
// TODO: Check if URL provided
|
|
9
|
+
|
|
10
|
+
getData: function (chr, start, end) {
|
|
11
|
+
var deferred = $.Deferred();
|
|
12
|
+
|
|
13
|
+
$.when(this.getStartByte()).done(function () {
|
|
14
|
+
start = start - (start % this.chunkSize) + 1;
|
|
15
|
+
end = end + this.chunkSize - (end % this.chunkSize);
|
|
16
|
+
|
|
17
|
+
var startByte = start - 1 + Math.floor((start - 1) / this.lineLength) + this.startByte;
|
|
18
|
+
var endByte = end - 1 + Math.floor((end - 1) / this.lineLength) + this.startByte;
|
|
19
|
+
|
|
20
|
+
$.ajax({
|
|
21
|
+
url : this.parseURL(),
|
|
22
|
+
dataType : this.dataType,
|
|
23
|
+
context : this,
|
|
24
|
+
headers : { Range: 'bytes=' + startByte + '-' + endByte },
|
|
25
|
+
xhrFields : this.xhrFields,
|
|
26
|
+
success : function (data) { this.receiveData(data, chr, start, end); },
|
|
27
|
+
error : this.track.controller.showError
|
|
28
|
+
}).done(function () { deferred.resolveWith(this); }).fail(function () { deferred.rejectWith(this); });
|
|
29
|
+
}).fail(function () { deferred.rejectWith(this); });
|
|
30
|
+
|
|
31
|
+
return deferred;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
getStartByte: function () {
|
|
35
|
+
if (this.startByteRequest) {
|
|
36
|
+
return this.startByteRequest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.startByte === undefined || this.lineLength === undefined) {
|
|
40
|
+
this.startByteRequest = $.ajax({
|
|
41
|
+
url : this.parseURL(),
|
|
42
|
+
dataType : 'text',
|
|
43
|
+
context : this,
|
|
44
|
+
headers : { 'Range': 'bytes=0-300' },
|
|
45
|
+
xhrFields : this.xhrFields,
|
|
46
|
+
success : function (data) {
|
|
47
|
+
if (data.indexOf('>') === 0) {
|
|
48
|
+
this.startByte = data.indexOf('\n') + 1;
|
|
49
|
+
} else {
|
|
50
|
+
this.startByte = 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.lineLength = data.indexOf('\n', this.startByte) - this.startByte;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return this.startByteRequest;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Abstract Sequence model
|
|
2
|
+
// assumes that the data source responds with raw sequence text
|
|
3
|
+
// see Fasta model for more specific example
|
|
4
|
+
Genoverse.Track.Model.Sequence = Genoverse.Track.Model.extend({
|
|
5
|
+
threshold : 100000,
|
|
6
|
+
chunkSize : 1000,
|
|
7
|
+
buffer : 0,
|
|
8
|
+
dataType : 'text',
|
|
9
|
+
|
|
10
|
+
setChrProps: function () {
|
|
11
|
+
var chr = this.browser.chr;
|
|
12
|
+
|
|
13
|
+
this.base();
|
|
14
|
+
|
|
15
|
+
this.chunksByChr = this.chunksByChr || {};
|
|
16
|
+
this.chunksByChr[chr] = this.chunksByChr[chr] || {};
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
getData: function (chr, start, end) {
|
|
20
|
+
start = start - (start % this.chunkSize) + 1;
|
|
21
|
+
end = end + this.chunkSize - (end % this.chunkSize);
|
|
22
|
+
return this.base(chr, start, end);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
parseData: function (data, chr, start) {
|
|
26
|
+
data = data.replace(/\n/g, '');
|
|
27
|
+
|
|
28
|
+
if (this.prop('lowerCase')) {
|
|
29
|
+
data = data.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (var i = 0; i < data.length; i += this.chunkSize) {
|
|
33
|
+
if (this.chunksByChr[chr][start + i]) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var feature = {
|
|
38
|
+
id : chr + ':' + start + ':' + i,
|
|
39
|
+
chr : chr,
|
|
40
|
+
start : start + i,
|
|
41
|
+
end : start + i + this.chunkSize - 1,
|
|
42
|
+
sequence : data.substr(i, this.chunkSize),
|
|
43
|
+
sort : start + i
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
this.chunksByChr[chr][feature.start] = feature;
|
|
47
|
+
this.insertFeature(feature);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Genoverse.Track.Model.SequenceVariation = Genoverse.Track.Model.extend({
|
|
2
|
+
seqModel: Genoverse.Track.Model.Sequence.Ensembl,
|
|
3
|
+
|
|
4
|
+
getSeqModel: function () {
|
|
5
|
+
var models = this.prop('models');
|
|
6
|
+
models.seq = models.seq || this.track.newMVC(this.seqModel);
|
|
7
|
+
return models.seq;
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
getData: function (chr, start, end) {
|
|
11
|
+
var model = this;
|
|
12
|
+
var deferred = $.Deferred();
|
|
13
|
+
var seqData = this.getSeqModel().checkDataRange(chr, start, end);
|
|
14
|
+
|
|
15
|
+
this.base(chr, start, end).done(function () {
|
|
16
|
+
if (seqData) {
|
|
17
|
+
deferred.resolve();
|
|
18
|
+
} else {
|
|
19
|
+
model.getSeqModel().getData(chr, start, end).done(deferred.resolve);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return deferred;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
insertFeature: function (feature) {
|
|
27
|
+
return this.base($.extend(feature, {
|
|
28
|
+
end : feature.start + feature.alt_allele.length - 1,
|
|
29
|
+
length : feature.alt_allele.length,
|
|
30
|
+
sequence : feature.alt_allele
|
|
31
|
+
}));
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
checkDataRange: function (chr, start, end) {
|
|
35
|
+
return this.base(chr, start, end) && this.getSeqModel().checkDataRange(chr, start, end);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
findFeatures: function (chr, start, end) {
|
|
39
|
+
return this.getSeqModel().findFeatures(chr, start, end).concat(this.base(chr, start, end));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Genoverse.Track.Model.Stranded = Genoverse.Track.Model.extend({
|
|
2
|
+
init: function (reset) {
|
|
3
|
+
this.base(reset);
|
|
4
|
+
|
|
5
|
+
if (!reset) {
|
|
6
|
+
var otherTrack = this.prop('forwardTrack');
|
|
7
|
+
|
|
8
|
+
if (otherTrack) {
|
|
9
|
+
this.featuresByChr = otherTrack.prop('featuresByChr');
|
|
10
|
+
this.features = otherTrack.prop('features');
|
|
11
|
+
this.featuresById = otherTrack.prop('featuresById');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
parseURL: function () {
|
|
17
|
+
if (!this.urlParams.strand) {
|
|
18
|
+
this.urlParams.strand = this.prop('featureStrand');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return this.base.apply(this, arguments);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
findFeatures: function () {
|
|
25
|
+
var strand = this.track.featureStrand;
|
|
26
|
+
return $.grep(this.base.apply(this, arguments), function (feature) { return feature.strand === strand; });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Ensembl REST API Transcript model
|
|
2
|
+
Genoverse.Track.Model.Transcript.Ensembl = Genoverse.Track.Model.Transcript.extend({
|
|
3
|
+
url : '//rest.ensembl.org/overlap/region/human/__CHR__:__START__-__END__?feature=transcript;feature=exon;feature=cds;content-type=application/json',
|
|
4
|
+
dataRequestLimit : 5000000, // As per e! REST API restrictions
|
|
5
|
+
|
|
6
|
+
setDefaults: function () {
|
|
7
|
+
this.geneIds = {};
|
|
8
|
+
this.seenGenes = 0;
|
|
9
|
+
|
|
10
|
+
this.base.apply(this, arguments);
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
// The url above responds in json format, data is an array
|
|
14
|
+
// See rest.ensembl.org/documentation/info/overlap_region for more details
|
|
15
|
+
parseData: function (data, chr) {
|
|
16
|
+
var model = this;
|
|
17
|
+
var featuresById = this.featuresById;
|
|
18
|
+
var ids = [];
|
|
19
|
+
|
|
20
|
+
data.filter(function (d) { return d.feature_type === 'transcript'; }).forEach(function (feature, i) {
|
|
21
|
+
if (!featuresById[feature.id]) {
|
|
22
|
+
model.geneIds[feature.Parent] = model.geneIds[feature.Parent] || ++model.seenGenes;
|
|
23
|
+
|
|
24
|
+
feature.chr = feature.chr || chr;
|
|
25
|
+
feature.label = parseInt(feature.strand, 10) === 1 ? (feature.external_name || feature.id) + ' >' : '< ' + (feature.external_name || feature.id);
|
|
26
|
+
feature.sort = (model.geneIds[feature.Parent] * 1e10) + (feature.logic_name.indexOf('ensembl_havana') === 0 ? 0 : 2e9) + (feature.biotype === 'protein_coding' ? 0 : 1e9) + feature.start + i;
|
|
27
|
+
feature.cdsStart = Infinity;
|
|
28
|
+
feature.cdsEnd = -Infinity;
|
|
29
|
+
feature.exons = {};
|
|
30
|
+
feature.subFeatures = [];
|
|
31
|
+
|
|
32
|
+
model.insertFeature(feature);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ids.push(feature.id);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
data.filter(function (d) { return d.feature_type === 'cds' && featuresById[d.Parent]; }).forEach(function (cds) {
|
|
39
|
+
featuresById[cds.Parent].cdsStart = Math.min(featuresById[cds.Parent].cdsStart, cds.start);
|
|
40
|
+
featuresById[cds.Parent].cdsEnd = Math.max(featuresById[cds.Parent].cdsEnd, cds.end);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
data.filter(function (d) { return d.feature_type === 'exon' && featuresById[d.Parent] && !featuresById[d.Parent].exons[d.id]; }).forEach(function (exon) {
|
|
44
|
+
if (exon.end < featuresById[exon.Parent].cdsStart || exon.start > featuresById[exon.Parent].cdsEnd) {
|
|
45
|
+
featuresById[exon.Parent].subFeatures.push($.extend({ utr: true }, exon));
|
|
46
|
+
} else {
|
|
47
|
+
if (exon.start < featuresById[exon.Parent].cdsStart) {
|
|
48
|
+
featuresById[exon.Parent].subFeatures.push($.extend({ utr: true }, exon, { end: featuresById[exon.Parent].cdsStart }));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
featuresById[exon.Parent].subFeatures.push($.extend({}, exon, {
|
|
52
|
+
start : Math.max(exon.start, featuresById[exon.Parent].cdsStart),
|
|
53
|
+
end : Math.min(exon.end, featuresById[exon.Parent].cdsEnd),
|
|
54
|
+
strand : featuresById[exon.Parent].strand
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
if (exon.end > featuresById[exon.Parent].cdsEnd) {
|
|
58
|
+
featuresById[exon.Parent].subFeatures.push($.extend({ utr: true }, exon, { start: featuresById[exon.Parent].cdsEnd }));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
ids.forEach(function (id) {
|
|
64
|
+
featuresById[id].subFeatures.sort(function (a, b) { return a.start - b.start; });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|