igv 3.1.2 → 3.1.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/dist/igv.esm.js +702 -715
- package/dist/igv.esm.min.js +11 -11
- package/dist/igv.esm.min.js.map +1 -1
- package/dist/igv.js +702 -715
- package/dist/igv.min.js +11 -11
- package/dist/igv.min.js.map +1 -1
- package/package.json +1 -1
package/dist/igv.esm.js
CHANGED
|
@@ -19431,7 +19431,7 @@ function doPath(ctx, x, y) {
|
|
|
19431
19431
|
ctx.closePath();
|
|
19432
19432
|
}
|
|
19433
19433
|
|
|
19434
|
-
/*! @license DOMPurify 3.2.
|
|
19434
|
+
/*! @license DOMPurify 3.2.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.4/LICENSE */
|
|
19435
19435
|
|
|
19436
19436
|
const {
|
|
19437
19437
|
entries,
|
|
@@ -19470,8 +19470,10 @@ if (!construct) {
|
|
|
19470
19470
|
};
|
|
19471
19471
|
}
|
|
19472
19472
|
const arrayForEach = unapply(Array.prototype.forEach);
|
|
19473
|
+
const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
19473
19474
|
const arrayPop = unapply(Array.prototype.pop);
|
|
19474
19475
|
const arrayPush = unapply(Array.prototype.push);
|
|
19476
|
+
const arraySplice = unapply(Array.prototype.splice);
|
|
19475
19477
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
19476
19478
|
const stringToString = unapply(String.prototype.toString);
|
|
19477
19479
|
const stringMatch = unapply(String.prototype.match);
|
|
@@ -19627,7 +19629,7 @@ const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:x
|
|
|
19627
19629
|
// eslint-disable-next-line unicorn/better-regex
|
|
19628
19630
|
const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
|
|
19629
19631
|
const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
|
|
19630
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]
|
|
19632
|
+
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
19631
19633
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
19632
19634
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
19633
19635
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
@@ -19727,9 +19729,9 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
19727
19729
|
function createDOMPurify() {
|
|
19728
19730
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
19729
19731
|
const DOMPurify = root => createDOMPurify(root);
|
|
19730
|
-
DOMPurify.version = '3.2.
|
|
19732
|
+
DOMPurify.version = '3.2.4';
|
|
19731
19733
|
DOMPurify.removed = [];
|
|
19732
|
-
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
|
|
19734
|
+
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
19733
19735
|
// Not running in a browser, provide a factory function
|
|
19734
19736
|
// so that you can pass your own Window
|
|
19735
19737
|
DOMPurify.isSupported = false;
|
|
@@ -20748,7 +20750,11 @@ function createDOMPurify() {
|
|
|
20748
20750
|
}
|
|
20749
20751
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
20750
20752
|
};
|
|
20751
|
-
DOMPurify.removeHook = function (entryPoint) {
|
|
20753
|
+
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
20754
|
+
if (hookFunction !== undefined) {
|
|
20755
|
+
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
20756
|
+
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
20757
|
+
}
|
|
20752
20758
|
return arrayPop(hooks[entryPoint]);
|
|
20753
20759
|
};
|
|
20754
20760
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
@@ -24664,6 +24670,533 @@ function stripQuotes$1(value) {
|
|
|
24664
24670
|
return value
|
|
24665
24671
|
}
|
|
24666
24672
|
|
|
24673
|
+
/**
|
|
24674
|
+
* Some interpretations of the sequence ontology needed to assemble GFF transcripts.
|
|
24675
|
+
*
|
|
24676
|
+
*/
|
|
24677
|
+
|
|
24678
|
+
const transcriptTypes = new Set(['transcript', 'primary_transcript', 'processed_transcript', 'mRNA', 'mrna',
|
|
24679
|
+
'lnc_RNA', 'miRNA', 'ncRNA', 'rRNA', 'scRNA', 'snRNA', 'snoRNA', 'tRNA']);
|
|
24680
|
+
const cdsTypes = new Set(['CDS', 'cds']);
|
|
24681
|
+
const codonTypes = new Set(['start_codon', 'stop_codon']);
|
|
24682
|
+
const utrTypes = new Set(['5UTR', '3UTR', 'UTR', 'five_prime_UTR', 'three_prime_UTR', "3'-UTR", "5'-UTR"]);
|
|
24683
|
+
const exonTypes = new Set(['exon', 'coding-exon']);
|
|
24684
|
+
|
|
24685
|
+
const transcriptPartTypes = new Set();
|
|
24686
|
+
for (let cltn of [cdsTypes, codonTypes, utrTypes, exonTypes]) {
|
|
24687
|
+
for (let t of cltn) {
|
|
24688
|
+
transcriptPartTypes.add(t);
|
|
24689
|
+
}
|
|
24690
|
+
}
|
|
24691
|
+
|
|
24692
|
+
function isExon(type) {
|
|
24693
|
+
return exonTypes.has(type)
|
|
24694
|
+
}
|
|
24695
|
+
|
|
24696
|
+
function isIntron(type) {
|
|
24697
|
+
return type.includes("intron")
|
|
24698
|
+
}
|
|
24699
|
+
|
|
24700
|
+
function isCoding(type) {
|
|
24701
|
+
return cdsTypes.has(type) || codonTypes.has(type)
|
|
24702
|
+
}
|
|
24703
|
+
|
|
24704
|
+
function isUTR(type) {
|
|
24705
|
+
return utrTypes.has(type)
|
|
24706
|
+
}
|
|
24707
|
+
|
|
24708
|
+
function isTranscript(type) {
|
|
24709
|
+
return transcriptTypes.has(type) || type.endsWith("RNA") || type.endsWith("transcript")
|
|
24710
|
+
}
|
|
24711
|
+
|
|
24712
|
+
function isTranscriptPart(type) {
|
|
24713
|
+
return transcriptPartTypes.has(type) || type.endsWith("RNA") || isIntron(type)
|
|
24714
|
+
}
|
|
24715
|
+
|
|
24716
|
+
const filterPopupProperties = new Set(["id", "parent", "name"]);
|
|
24717
|
+
|
|
24718
|
+
class GFFFeature {
|
|
24719
|
+
|
|
24720
|
+
constructor(properties) {
|
|
24721
|
+
|
|
24722
|
+
Object.assign(this, properties);
|
|
24723
|
+
|
|
24724
|
+
if (properties.phase !== undefined && "." !== properties.phase) {
|
|
24725
|
+
this.readingFrame = (3 - parseInt(properties.phase)) % 3;
|
|
24726
|
+
}
|
|
24727
|
+
|
|
24728
|
+
}
|
|
24729
|
+
|
|
24730
|
+
|
|
24731
|
+
popupData(genomicLocation) {
|
|
24732
|
+
|
|
24733
|
+
const pd = this.geneObject ? this.geneObject.popupData() : [];
|
|
24734
|
+
|
|
24735
|
+
if (this.geneObject) {
|
|
24736
|
+
pd.push('<hr/>');
|
|
24737
|
+
}
|
|
24738
|
+
|
|
24739
|
+
if (this.name) {
|
|
24740
|
+
pd.push({name: 'Name', value: this.name});
|
|
24741
|
+
}
|
|
24742
|
+
|
|
24743
|
+
pd.push({name: 'Type', value: this.type});
|
|
24744
|
+
pd.push({name: 'Source', value: this.source});
|
|
24745
|
+
if (this.score !== undefined) {
|
|
24746
|
+
pd.push({name: 'Score', value: this.score});
|
|
24747
|
+
}
|
|
24748
|
+
pd.push({name: 'Phase', value: this.phase});
|
|
24749
|
+
|
|
24750
|
+
if (this.attributeString) {
|
|
24751
|
+
const atts = parseAttributeString(this.attributeString, this.delim);
|
|
24752
|
+
for (let [key, value] of atts) {
|
|
24753
|
+
if (value !== undefined && value.length > 0 && !filterPopupProperties.has(key.toLowerCase())) {
|
|
24754
|
+
pd.push({name: key + ":", value: value});
|
|
24755
|
+
}
|
|
24756
|
+
}
|
|
24757
|
+
}
|
|
24758
|
+
pd.push({
|
|
24759
|
+
name: 'Location',
|
|
24760
|
+
value: `${this.chr}:${numberFormatter$1(this.start + 1)}-${numberFormatter$1(this.end)}`
|
|
24761
|
+
});
|
|
24762
|
+
return pd
|
|
24763
|
+
}
|
|
24764
|
+
|
|
24765
|
+
getAttributeValue(attributeName) {
|
|
24766
|
+
if (this.hasOwnProperty(attributeName)) {
|
|
24767
|
+
return this[attributeName]
|
|
24768
|
+
} else {
|
|
24769
|
+
// TODO -- fetch from attribute string and cache
|
|
24770
|
+
if (!this._attributeCache) {
|
|
24771
|
+
this._attributeCache = new Map();
|
|
24772
|
+
}
|
|
24773
|
+
if (this._attributeCache.has(attributeName)) {
|
|
24774
|
+
return this._attributeCache.get(attributeName)
|
|
24775
|
+
} else {
|
|
24776
|
+
const atts = parseAttributeString(this.attributeString, this.delim);
|
|
24777
|
+
let v;
|
|
24778
|
+
for (let [key, value] of atts) {
|
|
24779
|
+
if (key === attributeName) {
|
|
24780
|
+
v = value;
|
|
24781
|
+
break
|
|
24782
|
+
}
|
|
24783
|
+
}
|
|
24784
|
+
this._attributeCache.set(attributeName, v);
|
|
24785
|
+
return v
|
|
24786
|
+
}
|
|
24787
|
+
}
|
|
24788
|
+
}
|
|
24789
|
+
}
|
|
24790
|
+
|
|
24791
|
+
class GFFTranscript extends GFFFeature {
|
|
24792
|
+
|
|
24793
|
+
constructor(feature) {
|
|
24794
|
+
super(feature);
|
|
24795
|
+
this.exons = [];
|
|
24796
|
+
this.parts = [];
|
|
24797
|
+
}
|
|
24798
|
+
|
|
24799
|
+
addExon(feature) {
|
|
24800
|
+
|
|
24801
|
+
this.exons.push(feature);
|
|
24802
|
+
|
|
24803
|
+
// Expand feature -- for transcripts not explicitly represented in the file (gtf)
|
|
24804
|
+
this.start = Math.min(this.start, feature.start);
|
|
24805
|
+
this.end = Math.max(this.end, feature.end);
|
|
24806
|
+
}
|
|
24807
|
+
|
|
24808
|
+
addPart(feature) {
|
|
24809
|
+
this.parts.push(feature);
|
|
24810
|
+
}
|
|
24811
|
+
|
|
24812
|
+
assembleParts() {
|
|
24813
|
+
|
|
24814
|
+
if (this.parts.length === 0) return
|
|
24815
|
+
|
|
24816
|
+
this.parts.sort(function (a, b) {
|
|
24817
|
+
return a.start - b.start
|
|
24818
|
+
});
|
|
24819
|
+
|
|
24820
|
+
// Create exons, if neccessary
|
|
24821
|
+
let lastStart = this.parts[0].start;
|
|
24822
|
+
let lastEnd = this.parts[0].end;
|
|
24823
|
+
for (let i = 1; i < this.parts.length; i++) {
|
|
24824
|
+
const part = this.parts[i];
|
|
24825
|
+
if (isIntron(part.type)) {
|
|
24826
|
+
continue
|
|
24827
|
+
}
|
|
24828
|
+
if (part.start <= lastEnd) {
|
|
24829
|
+
lastEnd = Math.max(lastEnd, part.end);
|
|
24830
|
+
} else {
|
|
24831
|
+
let exon = this.findExonContaining({start: lastStart, end: lastEnd});
|
|
24832
|
+
if (!exon) {
|
|
24833
|
+
this.exons.push({start: lastStart, end: lastEnd, psuedo: true});
|
|
24834
|
+
}
|
|
24835
|
+
lastStart = part.start;
|
|
24836
|
+
lastEnd = part.end;
|
|
24837
|
+
}
|
|
24838
|
+
}
|
|
24839
|
+
let exon = this.findExonContaining({start: lastStart, end: lastEnd});
|
|
24840
|
+
if (!exon) {
|
|
24841
|
+
this.exons.push({start: lastStart, end: lastEnd, psuedo: true});
|
|
24842
|
+
this.start = Math.min(this.start, lastStart);
|
|
24843
|
+
this.end = Math.max(this.end, lastEnd);
|
|
24844
|
+
}
|
|
24845
|
+
|
|
24846
|
+
|
|
24847
|
+
for (let part of this.parts) {
|
|
24848
|
+
const type = part.type;
|
|
24849
|
+
if (isCoding(type)) {
|
|
24850
|
+
this.addCDS(part);
|
|
24851
|
+
} else if (isUTR(type)) {
|
|
24852
|
+
this.addUTR(part);
|
|
24853
|
+
}
|
|
24854
|
+
}
|
|
24855
|
+
}
|
|
24856
|
+
|
|
24857
|
+
findExonContaining({start, end}) {
|
|
24858
|
+
for (let exon of this.exons) {
|
|
24859
|
+
if (exon.end >= end && exon.start <= start) {
|
|
24860
|
+
return exon
|
|
24861
|
+
}
|
|
24862
|
+
}
|
|
24863
|
+
return undefined
|
|
24864
|
+
}
|
|
24865
|
+
|
|
24866
|
+
addCDS(cds) {
|
|
24867
|
+
|
|
24868
|
+
let exon;
|
|
24869
|
+
const exons = this.exons;
|
|
24870
|
+
|
|
24871
|
+
for (let e of exons) {
|
|
24872
|
+
if (e.start <= cds.start && e.end >= cds.end) {
|
|
24873
|
+
exon = e;
|
|
24874
|
+
break
|
|
24875
|
+
}
|
|
24876
|
+
}
|
|
24877
|
+
|
|
24878
|
+
if (exon) {
|
|
24879
|
+
exon.cdStart = exon.cdStart ? Math.min(cds.start, exon.cdStart) : cds.start;
|
|
24880
|
+
exon.cdEnd = exon.cdEnd ? Math.max(cds.end, exon.cdEnd) : cds.end;
|
|
24881
|
+
exon.readingFrame = cds.readingFrame;
|
|
24882
|
+
// TODO -- merge attributes?
|
|
24883
|
+
} else {
|
|
24884
|
+
// cds.cdStart = cds.start
|
|
24885
|
+
// cds.cdEnd = cds.end
|
|
24886
|
+
// exons.push(cds)
|
|
24887
|
+
console.error("No exon found spanning " + cds.start + "-" + cds.end);
|
|
24888
|
+
}
|
|
24889
|
+
|
|
24890
|
+
// Expand feature -- for transcripts not explicitly represented in the file (gtf files)
|
|
24891
|
+
// this.start = Math.min(this.start, cds.start);
|
|
24892
|
+
// this.end = Math.max(this.end, cds.end);
|
|
24893
|
+
|
|
24894
|
+
this.cdStart = this.cdStart ? Math.min(cds.start, this.cdStart) : cds.start;
|
|
24895
|
+
this.cdEnd = this.cdEnd ? Math.max(cds.end, this.cdEnd) : cds.end;
|
|
24896
|
+
}
|
|
24897
|
+
|
|
24898
|
+
addUTR(utr) {
|
|
24899
|
+
|
|
24900
|
+
let exon;
|
|
24901
|
+
const exons = this.exons;
|
|
24902
|
+
|
|
24903
|
+
// Find exon containing CDS
|
|
24904
|
+
for (let i = 0; i < exons.length; i++) {
|
|
24905
|
+
if (exons[i].start <= utr.start && exons[i].end >= utr.end) {
|
|
24906
|
+
exon = exons[i];
|
|
24907
|
+
break
|
|
24908
|
+
}
|
|
24909
|
+
}
|
|
24910
|
+
|
|
24911
|
+
if (exon) {
|
|
24912
|
+
if (utr.start === exon.start && utr.end === exon.end) {
|
|
24913
|
+
exon.utr = true;
|
|
24914
|
+
} else {
|
|
24915
|
+
if (utr.end < exon.end) {
|
|
24916
|
+
exon.cdStart = utr.end;
|
|
24917
|
+
}
|
|
24918
|
+
if (utr.start > exon.start) {
|
|
24919
|
+
exon.cdEnd = utr.start;
|
|
24920
|
+
}
|
|
24921
|
+
}
|
|
24922
|
+
|
|
24923
|
+
} else {
|
|
24924
|
+
// utr.utr = true
|
|
24925
|
+
// exons.push(utr)
|
|
24926
|
+
console.error("No exon found spanning " + cds.start + "-" + cds.end);
|
|
24927
|
+
}
|
|
24928
|
+
|
|
24929
|
+
// Expand feature -- for transcripts not explicitly represented in the file
|
|
24930
|
+
// this.start = Math.min(this.start, utr.start);
|
|
24931
|
+
// this.end = Math.max(this.end, utr.end);
|
|
24932
|
+
|
|
24933
|
+
}
|
|
24934
|
+
|
|
24935
|
+
finish() {
|
|
24936
|
+
|
|
24937
|
+
this.assembleParts();
|
|
24938
|
+
|
|
24939
|
+
var cdStart = this.cdStart;
|
|
24940
|
+
var cdEnd = this.cdEnd;
|
|
24941
|
+
|
|
24942
|
+
this.exons.sort(function (a, b) {
|
|
24943
|
+
return a.start - b.start
|
|
24944
|
+
});
|
|
24945
|
+
|
|
24946
|
+
// Search for UTR exons that were not explicitly tagged
|
|
24947
|
+
if (cdStart) {
|
|
24948
|
+
this.exons.forEach(function (exon) {
|
|
24949
|
+
if (exon.end < cdStart || exon.start > cdEnd) exon.utr = true;
|
|
24950
|
+
});
|
|
24951
|
+
}
|
|
24952
|
+
}
|
|
24953
|
+
|
|
24954
|
+
popupData(genomicLocation) {
|
|
24955
|
+
|
|
24956
|
+
const pd = super.popupData(genomicLocation);
|
|
24957
|
+
|
|
24958
|
+
// If clicked over an exon add its attributes
|
|
24959
|
+
for (let exon of this.exons) {
|
|
24960
|
+
if (exon.pseudo) continue // An implicit exon
|
|
24961
|
+
if (genomicLocation >= exon.start && genomicLocation < exon.end && typeof exon.popupData === 'function') {
|
|
24962
|
+
pd.push('<hr/>');
|
|
24963
|
+
const exonData = exon.popupData(genomicLocation);
|
|
24964
|
+
for (let att of exonData) {
|
|
24965
|
+
pd.push(att);
|
|
24966
|
+
}
|
|
24967
|
+
}
|
|
24968
|
+
}
|
|
24969
|
+
|
|
24970
|
+
for (let part of this.parts) {
|
|
24971
|
+
if (genomicLocation >= part.start && genomicLocation < part.end && typeof part.popupData === 'function') {
|
|
24972
|
+
pd.push('<hr/>');
|
|
24973
|
+
const partData = part.popupData(genomicLocation);
|
|
24974
|
+
for (let att of partData) {
|
|
24975
|
+
pd.push(att);
|
|
24976
|
+
}
|
|
24977
|
+
}
|
|
24978
|
+
}
|
|
24979
|
+
|
|
24980
|
+
|
|
24981
|
+
return pd
|
|
24982
|
+
}
|
|
24983
|
+
}
|
|
24984
|
+
|
|
24985
|
+
class GFFHelper {
|
|
24986
|
+
|
|
24987
|
+
static gffNameFields = new Set(["Name", "transcript_id", "gene_name", "gene", "gene_id", "alias", "locus", "name" ])
|
|
24988
|
+
constructor(options) {
|
|
24989
|
+
this.format = options.format;
|
|
24990
|
+
this.nameField = options.nameField;
|
|
24991
|
+
this.filterTypes = options.filterTypes === undefined ?
|
|
24992
|
+
new Set(['chromosome']) :
|
|
24993
|
+
new Set(options.filterTypes);
|
|
24994
|
+
}
|
|
24995
|
+
|
|
24996
|
+
combineFeatures(features, genomicInterval) {
|
|
24997
|
+
|
|
24998
|
+
let combinedFeatures;
|
|
24999
|
+
|
|
25000
|
+
const filterTypes = this.filterTypes;
|
|
25001
|
+
features = features.filter(f => filterTypes === undefined || !filterTypes.has(f.type));
|
|
25002
|
+
|
|
25003
|
+
if ("gff3" === this.format) {
|
|
25004
|
+
const tmp = this.combineFeaturesById(features);
|
|
25005
|
+
combinedFeatures = this.combineFeaturesByType(tmp);
|
|
25006
|
+
} else {
|
|
25007
|
+
combinedFeatures = this.combineFeaturesByType(features);
|
|
25008
|
+
}
|
|
25009
|
+
|
|
25010
|
+
this.numberExons(combinedFeatures, genomicInterval);
|
|
25011
|
+
this.nameFeatures(combinedFeatures);
|
|
25012
|
+
return combinedFeatures
|
|
25013
|
+
}
|
|
25014
|
+
|
|
25015
|
+
/**
|
|
25016
|
+
* Combine multiple non-transcript model features with the same ID on the same chromosome into a single feature.
|
|
25017
|
+
* Features that are part of the transcript model (e.g. exon, mRNA, etc) are combined later.
|
|
25018
|
+
*
|
|
25019
|
+
* @param features
|
|
25020
|
+
* @returns {[]}
|
|
25021
|
+
*/
|
|
25022
|
+
combineFeaturesById(features) {
|
|
25023
|
+
|
|
25024
|
+
const chrIdMap = new Map();
|
|
25025
|
+
const combinedFeatures = [];
|
|
25026
|
+
|
|
25027
|
+
for (let f of features) {
|
|
25028
|
+
if (isTranscriptPart(f.type) || isTranscript(f.type) || !f.id) {
|
|
25029
|
+
combinedFeatures.push(f);
|
|
25030
|
+
} else {
|
|
25031
|
+
let idMap = chrIdMap.get(f.chr);
|
|
25032
|
+
if (!idMap) {
|
|
25033
|
+
idMap = new Map();
|
|
25034
|
+
chrIdMap.set(f.chr, idMap);
|
|
25035
|
+
}
|
|
25036
|
+
|
|
25037
|
+
let featureArray = idMap.get(f.id);
|
|
25038
|
+
if (featureArray) {
|
|
25039
|
+
featureArray.push(f);
|
|
25040
|
+
} else {
|
|
25041
|
+
idMap.set(f.id, [f]);
|
|
25042
|
+
}
|
|
25043
|
+
}
|
|
25044
|
+
}
|
|
25045
|
+
|
|
25046
|
+
for (let idMap of chrIdMap.values()) {
|
|
25047
|
+
for (let featureArray of idMap.values()) {
|
|
25048
|
+
if (featureArray.length > 1) {
|
|
25049
|
+
// Use the first feature as prototypical (for column 9 attributes), and adjust start/end
|
|
25050
|
+
// Parts are represented as "exons", as that is how they are presented visually
|
|
25051
|
+
const cf = featureArray[0];
|
|
25052
|
+
cf.exons = [];
|
|
25053
|
+
for (let f of featureArray) {
|
|
25054
|
+
cf.start = Math.min(cf.start, f.start);
|
|
25055
|
+
cf.end = Math.max(cf.end, f.end);
|
|
25056
|
+
cf.exons.push({
|
|
25057
|
+
start: f.start,
|
|
25058
|
+
end: f.end
|
|
25059
|
+
});
|
|
25060
|
+
}
|
|
25061
|
+
combinedFeatures.push(cf);
|
|
25062
|
+
} else {
|
|
25063
|
+
combinedFeatures.push(featureArray[0]);
|
|
25064
|
+
}
|
|
25065
|
+
}
|
|
25066
|
+
}
|
|
25067
|
+
|
|
25068
|
+
return combinedFeatures
|
|
25069
|
+
}
|
|
25070
|
+
|
|
25071
|
+
combineFeaturesByType(features) {
|
|
25072
|
+
|
|
25073
|
+
// Build dictionary of genes
|
|
25074
|
+
const genes = features.filter(f => "gene" === f.type || f.type.endsWith("_gene"));
|
|
25075
|
+
const geneMap = Object.create(null);
|
|
25076
|
+
for (let g of genes) {
|
|
25077
|
+
geneMap[g.id] = g;
|
|
25078
|
+
}
|
|
25079
|
+
|
|
25080
|
+
// 1. Build dictionary of transcripts
|
|
25081
|
+
const transcripts = Object.create(null);
|
|
25082
|
+
const combinedFeatures = [];
|
|
25083
|
+
const consumedFeatures = new Set();
|
|
25084
|
+
const filterTypes = this.filterTypes;
|
|
25085
|
+
|
|
25086
|
+
features = features.filter(f => filterTypes === undefined || !filterTypes.has(f.type));
|
|
25087
|
+
|
|
25088
|
+
for (let f of features) {
|
|
25089
|
+
if (isTranscript(f.type)) {
|
|
25090
|
+
const transcriptId = f.id; // getAttribute(f.attributeString, "transcript_id", /\s+/);
|
|
25091
|
+
if (undefined !== transcriptId) {
|
|
25092
|
+
const gffTranscript = new GFFTranscript(f);
|
|
25093
|
+
transcripts[transcriptId] = gffTranscript;
|
|
25094
|
+
combinedFeatures.push(gffTranscript);
|
|
25095
|
+
consumedFeatures.add(f);
|
|
25096
|
+
const g = geneMap[f.parent];
|
|
25097
|
+
if (g) {
|
|
25098
|
+
gffTranscript.geneObject = g;
|
|
25099
|
+
consumedFeatures.add(g);
|
|
25100
|
+
}
|
|
25101
|
+
}
|
|
25102
|
+
}
|
|
25103
|
+
}
|
|
25104
|
+
|
|
25105
|
+
// Add exons and transcript parts
|
|
25106
|
+
for (let f of features) {
|
|
25107
|
+
if (isTranscriptPart(f.type)) {
|
|
25108
|
+
const parents = getParents(f);
|
|
25109
|
+
if (parents) {
|
|
25110
|
+
for (let id of parents) {
|
|
25111
|
+
|
|
25112
|
+
let transcript = transcripts[id];
|
|
25113
|
+
if (!transcript && this.format === "gtf") {
|
|
25114
|
+
// GTF does not require explicit a transcript or mRNA record, start one with this feature.
|
|
25115
|
+
const psuedoTranscript = Object.assign({}, f);
|
|
25116
|
+
psuedoTranscript.type = "transcript";
|
|
25117
|
+
transcript = new GFFTranscript(psuedoTranscript);
|
|
25118
|
+
transcripts[id] = transcript;
|
|
25119
|
+
combinedFeatures.push(transcript);
|
|
25120
|
+
}
|
|
25121
|
+
if (transcript !== undefined) {
|
|
25122
|
+
|
|
25123
|
+
if (isExon(f.type)) {
|
|
25124
|
+
if (parents.length > 1) {
|
|
25125
|
+
// Multiple parents, this is unusual. Make a copy as exon can be modified
|
|
25126
|
+
// differently by CDS, etc, for each parent
|
|
25127
|
+
const e2 = new GFFFeature(f);
|
|
25128
|
+
transcript.addExon(e2);
|
|
25129
|
+
} else {
|
|
25130
|
+
transcript.addExon(f);
|
|
25131
|
+
}
|
|
25132
|
+
} else {
|
|
25133
|
+
transcript.addPart(f);
|
|
25134
|
+
}
|
|
25135
|
+
consumedFeatures.add(f);
|
|
25136
|
+
}
|
|
25137
|
+
}
|
|
25138
|
+
}
|
|
25139
|
+
}
|
|
25140
|
+
}
|
|
25141
|
+
|
|
25142
|
+
// Finish transcripts
|
|
25143
|
+
combinedFeatures.forEach(function (f) {
|
|
25144
|
+
if (typeof f.finish === "function") {
|
|
25145
|
+
f.finish();
|
|
25146
|
+
}
|
|
25147
|
+
});
|
|
25148
|
+
|
|
25149
|
+
// Add other features
|
|
25150
|
+
const others = features.filter(f => !consumedFeatures.has(f));
|
|
25151
|
+
for (let f of others) {
|
|
25152
|
+
combinedFeatures.push(f);
|
|
25153
|
+
}
|
|
25154
|
+
|
|
25155
|
+
return combinedFeatures
|
|
25156
|
+
|
|
25157
|
+
function getParents(f) {
|
|
25158
|
+
if (f.parent && f.parent.trim() !== "") {
|
|
25159
|
+
return f.parent.trim().split(",")
|
|
25160
|
+
} else {
|
|
25161
|
+
return null
|
|
25162
|
+
}
|
|
25163
|
+
}
|
|
25164
|
+
}
|
|
25165
|
+
|
|
25166
|
+
numberExons(features, genomicInterval) {
|
|
25167
|
+
|
|
25168
|
+
for (let f of features) {
|
|
25169
|
+
if (f.exons &&
|
|
25170
|
+
(!genomicInterval ||
|
|
25171
|
+
(f.end <= genomicInterval.end && f.start > genomicInterval.start))) {
|
|
25172
|
+
for (let i = 0; i < f.exons.length; i++) {
|
|
25173
|
+
const exon = f.exons[i];
|
|
25174
|
+
exon.number = f.strand === "-" ? f.exons.length - i : i + 1;
|
|
25175
|
+
}
|
|
25176
|
+
}
|
|
25177
|
+
}
|
|
25178
|
+
}
|
|
25179
|
+
|
|
25180
|
+
nameFeatures(features) {
|
|
25181
|
+
// Find name (label) property
|
|
25182
|
+
for (let f of features) {
|
|
25183
|
+
if(typeof f.getAttributeValue === 'function') {
|
|
25184
|
+
if (this.nameField) {
|
|
25185
|
+
f.name = f.getAttributeValue(this.nameField);
|
|
25186
|
+
} else {
|
|
25187
|
+
for (let nameField of GFFHelper.gffNameFields) {
|
|
25188
|
+
const v = f.getAttributeValue(nameField);
|
|
25189
|
+
if (v) {
|
|
25190
|
+
f.name = v;
|
|
25191
|
+
break
|
|
25192
|
+
}
|
|
25193
|
+
}
|
|
25194
|
+
}
|
|
25195
|
+
}
|
|
25196
|
+
}
|
|
25197
|
+
}
|
|
25198
|
+
}
|
|
25199
|
+
|
|
24667
25200
|
/**
|
|
24668
25201
|
* Decode the UCSC bed format. Only the first 3 columns (chr, start, end) are required. The remaining columns
|
|
24669
25202
|
* must follow standard bed order, but we will tolerate deviations after column 3.
|
|
@@ -24676,7 +25209,7 @@ function decodeBed(tokens, header, maxColumnCount = Number.MAX_SAFE_INTEGER) {
|
|
|
24676
25209
|
|
|
24677
25210
|
if (tokens.length < 3) return undefined
|
|
24678
25211
|
|
|
24679
|
-
header && header.gffTags;
|
|
25212
|
+
const gffTags = header && header.gffTags;
|
|
24680
25213
|
|
|
24681
25214
|
const chr = tokens[0];
|
|
24682
25215
|
const start = parseInt(tokens[1]);
|
|
@@ -24696,12 +25229,16 @@ function decodeBed(tokens, header, maxColumnCount = Number.MAX_SAFE_INTEGER) {
|
|
|
24696
25229
|
feature.attributes = {};
|
|
24697
25230
|
for (let kv of attributeKVs) {
|
|
24698
25231
|
feature.attributes[kv[0]] = kv[1];
|
|
24699
|
-
if
|
|
24700
|
-
|
|
25232
|
+
if(gffTags) {
|
|
25233
|
+
if (header.nameField != undefined && kv[0] === header.nameField) {
|
|
25234
|
+
feature.name = kv[1];
|
|
25235
|
+
} else if (!feature.name && GFFHelper.gffNameFields.has(kv[0])) {
|
|
25236
|
+
feature.name = kv[1];
|
|
25237
|
+
}
|
|
24701
25238
|
}
|
|
24702
25239
|
}
|
|
24703
25240
|
}
|
|
24704
|
-
if (!feature.name) {
|
|
25241
|
+
if (!feature.name && !gffTags) {
|
|
24705
25242
|
feature.name = tokens[3] === '.' ? '' : tokens[3];
|
|
24706
25243
|
}
|
|
24707
25244
|
}
|
|
@@ -25120,505 +25657,193 @@ function decodeBedGraph(tokens, header) {
|
|
|
25120
25657
|
if (colorColumn && colorColumn < tokens.length) {
|
|
25121
25658
|
feature.color = IGVColor.createColorString(tokens[colorColumn]);
|
|
25122
25659
|
}
|
|
25123
|
-
}
|
|
25124
|
-
|
|
25125
|
-
return feature
|
|
25126
|
-
}
|
|
25127
|
-
|
|
25128
|
-
function decodeWig(tokens, header) {
|
|
25129
|
-
|
|
25130
|
-
const wig = header.wig;
|
|
25131
|
-
|
|
25132
|
-
if (wig && wig.format === "fixedStep") {
|
|
25133
|
-
const ss = (wig.index * wig.step) + wig.start;
|
|
25134
|
-
const ee = ss + wig.span;
|
|
25135
|
-
const value = Number(tokens[0]);
|
|
25136
|
-
++(wig.index);
|
|
25137
|
-
return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value}
|
|
25138
|
-
} else if (wig && wig.format === "variableStep") {
|
|
25139
|
-
|
|
25140
|
-
if (tokens.length < 2) return null
|
|
25141
|
-
const ss = parseInt(tokens[0], 10) - 1;
|
|
25142
|
-
const ee = ss + wig.span;
|
|
25143
|
-
const value = Number(tokens[1]);
|
|
25144
|
-
return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value}
|
|
25145
|
-
|
|
25146
|
-
} else {
|
|
25147
|
-
return decodeBedGraph(tokens)
|
|
25148
|
-
}
|
|
25149
|
-
}
|
|
25150
|
-
|
|
25151
|
-
function decodeSNP(tokens, header) {
|
|
25152
|
-
|
|
25153
|
-
if (tokens.length < 6) return undefined
|
|
25154
|
-
|
|
25155
|
-
const autoSql = [
|
|
25156
|
-
'bin',
|
|
25157
|
-
'chr',
|
|
25158
|
-
'start',
|
|
25159
|
-
'end',
|
|
25160
|
-
'name',
|
|
25161
|
-
'score',
|
|
25162
|
-
'strand',
|
|
25163
|
-
'refNCBI',
|
|
25164
|
-
'refUCSC',
|
|
25165
|
-
'observed',
|
|
25166
|
-
'molType',
|
|
25167
|
-
'class',
|
|
25168
|
-
'valid',
|
|
25169
|
-
'avHet',
|
|
25170
|
-
'avHetSE',
|
|
25171
|
-
'func',
|
|
25172
|
-
'locType',
|
|
25173
|
-
'weight',
|
|
25174
|
-
'exceptions',
|
|
25175
|
-
'submitterCount',
|
|
25176
|
-
'submitters',
|
|
25177
|
-
'alleleFreqCount',
|
|
25178
|
-
'alleles',
|
|
25179
|
-
'alleleNs',
|
|
25180
|
-
'alleleFreqs',
|
|
25181
|
-
'bitfields'
|
|
25182
|
-
];
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
const feature = {
|
|
25186
|
-
chr: tokens[1],
|
|
25187
|
-
start: Number.parseInt(tokens[2]),
|
|
25188
|
-
end: Number.parseInt(tokens[3]),
|
|
25189
|
-
name: tokens[4],
|
|
25190
|
-
score: Number.parseInt(tokens[5])
|
|
25191
|
-
};
|
|
25192
|
-
|
|
25193
|
-
const n = Math.min(tokens.length, autoSql.length);
|
|
25194
|
-
for (let i = 6; i < n; i++) {
|
|
25195
|
-
feature[autoSql[i]] = tokens[i];
|
|
25196
|
-
}
|
|
25197
|
-
return feature
|
|
25198
|
-
|
|
25199
|
-
}
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
class UCSCBedFeature {
|
|
25203
|
-
|
|
25204
|
-
constructor(properties) {
|
|
25205
|
-
Object.assign(this, properties);
|
|
25206
|
-
}
|
|
25207
|
-
|
|
25208
|
-
getAttributeValue(attributeName) {
|
|
25209
|
-
if (this.hasOwnProperty(attributeName)) {
|
|
25210
|
-
return this[attributeName]
|
|
25211
|
-
} else if (this.attributes) {
|
|
25212
|
-
return this.attributes[attributeName]
|
|
25213
|
-
}
|
|
25214
|
-
}
|
|
25215
|
-
}
|
|
25216
|
-
|
|
25217
|
-
/*
|
|
25218
|
-
* 0 matches - Number of bases that match that aren't repeats
|
|
25219
|
-
* 1 misMatches - Number of bases that don't match
|
|
25220
|
-
* 2 repMatches - Number of bases that match but are part of repeats
|
|
25221
|
-
* 3 nCount - Number of "N" bases
|
|
25222
|
-
* 4 qNumInsert - Number of inserts in query
|
|
25223
|
-
* 5 qBaseInsert - Number of bases inserted in query
|
|
25224
|
-
* 6 tNumInsert - Number of inserts in target
|
|
25225
|
-
* 7 tBaseInsert - Number of bases inserted in target
|
|
25226
|
-
* 8 strand - "+" or "-" for query strand. For translated alignments, second "+"or "-" is for target genomic strand.
|
|
25227
|
-
* 9 qName - Query sequence name
|
|
25228
|
-
* 10 qSize - Query sequence size.
|
|
25229
|
-
* 11 qStart - Alignment start position in query
|
|
25230
|
-
* 12 qEnd - Alignment end position in query
|
|
25231
|
-
* 13 tName - Target sequence name
|
|
25232
|
-
* 14 tSize - Target sequence size
|
|
25233
|
-
* 15 tStart - Alignment start position in target
|
|
25234
|
-
* 16 tEnd - Alignment end position in target
|
|
25235
|
-
* 17 blockCount - Number of blocks in the alignment (a block contains no gaps)
|
|
25236
|
-
* 18 blockSizes - Comma-separated list of sizes of each block. If the query is a protein and the target the genome, blockSizes are in amino acids. See below for more information on protein query PSLs.
|
|
25237
|
-
* 19 qStarts - Comma-separated list of starting positions of each block in query
|
|
25238
|
-
* 20 tStarts - Comma-separated list of starting positions of each block in target
|
|
25239
|
-
*/
|
|
25240
|
-
|
|
25241
|
-
class PSLFeature {
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
constructor(properties) {
|
|
25245
|
-
Object.assign(this, properties);
|
|
25246
|
-
}
|
|
25247
|
-
|
|
25248
|
-
get score() {
|
|
25249
|
-
const tokens = this.tokens;
|
|
25250
|
-
const match = parseInt(tokens[0]);
|
|
25251
|
-
const repMatch = parseInt(tokens[2]);
|
|
25252
|
-
const misMatch = parseInt(tokens[1]);
|
|
25253
|
-
const qGapCount = parseInt(tokens[4]);
|
|
25254
|
-
const tGapCount = parseInt(tokens[6]);
|
|
25255
|
-
const qSize = parseInt(tokens[10]);
|
|
25256
|
-
return Math.floor((1000 * (match + repMatch - misMatch - qGapCount - tGapCount)) / qSize)
|
|
25257
|
-
}
|
|
25258
|
-
|
|
25259
|
-
get matches() {
|
|
25260
|
-
return this.tokens[0]
|
|
25261
|
-
}
|
|
25262
|
-
|
|
25263
|
-
get misMatches() {
|
|
25264
|
-
return this.tokens[1]
|
|
25265
|
-
}
|
|
25266
|
-
|
|
25267
|
-
get repMatches() {
|
|
25268
|
-
return this.tokens[2]
|
|
25269
|
-
}
|
|
25270
|
-
|
|
25271
|
-
get nCount() {
|
|
25272
|
-
return this.tokens[3]
|
|
25273
|
-
}
|
|
25274
|
-
|
|
25275
|
-
get qNumInsert() {
|
|
25276
|
-
return this.tokens[4]
|
|
25277
|
-
}
|
|
25278
|
-
|
|
25279
|
-
get qBaseInsert() {
|
|
25280
|
-
return this.tokens[5]
|
|
25281
|
-
}
|
|
25282
|
-
|
|
25283
|
-
get tNumInsert() {
|
|
25284
|
-
return this.tokens[6]
|
|
25285
|
-
}
|
|
25286
|
-
|
|
25287
|
-
get tBaseInsert() {
|
|
25288
|
-
return this.tokens[7]
|
|
25289
|
-
|
|
25290
|
-
}
|
|
25291
|
-
|
|
25292
|
-
popupData() {
|
|
25293
|
-
return [
|
|
25294
|
-
{name: 'chr', value: this.chr},
|
|
25295
|
-
{name: 'start', value: this.start + 1},
|
|
25296
|
-
{name: 'end', value: this.end},
|
|
25297
|
-
{name: 'strand', value: this.strand},
|
|
25298
|
-
{name: 'score', value: this.score},
|
|
25299
|
-
{name: 'match', value: this.matches},
|
|
25300
|
-
{name: "mis-match", value: this.misMatches},
|
|
25301
|
-
{name: "rep. match", value: this.repMatches},
|
|
25302
|
-
{name: "N's", value: this.nCount},
|
|
25303
|
-
{name: 'Q gap count', value: this.qNumInsert},
|
|
25304
|
-
{name: 'Q gap bases', value: this.qBaseInsert},
|
|
25305
|
-
{name: 'T gap count', value: this.tNumInsert},
|
|
25306
|
-
{name: 'T gap bases', value: this.tBaseInsert},
|
|
25307
|
-
]
|
|
25308
|
-
}
|
|
25309
|
-
|
|
25310
|
-
}
|
|
25311
|
-
|
|
25312
|
-
/**
|
|
25313
|
-
* Some interpretations of the sequence ontology needed to assemble GFF transcripts.
|
|
25314
|
-
*
|
|
25315
|
-
*/
|
|
25316
|
-
|
|
25317
|
-
const transcriptTypes = new Set(['transcript', 'primary_transcript', 'processed_transcript', 'mRNA', 'mrna',
|
|
25318
|
-
'lnc_RNA', 'miRNA', 'ncRNA', 'rRNA', 'scRNA', 'snRNA', 'snoRNA', 'tRNA']);
|
|
25319
|
-
const cdsTypes = new Set(['CDS', 'cds']);
|
|
25320
|
-
const codonTypes = new Set(['start_codon', 'stop_codon']);
|
|
25321
|
-
const utrTypes = new Set(['5UTR', '3UTR', 'UTR', 'five_prime_UTR', 'three_prime_UTR', "3'-UTR", "5'-UTR"]);
|
|
25322
|
-
const exonTypes = new Set(['exon', 'coding-exon']);
|
|
25323
|
-
|
|
25324
|
-
const transcriptPartTypes = new Set();
|
|
25325
|
-
for (let cltn of [cdsTypes, codonTypes, utrTypes, exonTypes]) {
|
|
25326
|
-
for (let t of cltn) {
|
|
25327
|
-
transcriptPartTypes.add(t);
|
|
25328
|
-
}
|
|
25329
|
-
}
|
|
25330
|
-
|
|
25331
|
-
function isExon(type) {
|
|
25332
|
-
return exonTypes.has(type)
|
|
25333
|
-
}
|
|
25334
|
-
|
|
25335
|
-
function isIntron(type) {
|
|
25336
|
-
return type.includes("intron")
|
|
25337
|
-
}
|
|
25338
|
-
|
|
25339
|
-
function isCoding(type) {
|
|
25340
|
-
return cdsTypes.has(type) || codonTypes.has(type)
|
|
25341
|
-
}
|
|
25342
|
-
|
|
25343
|
-
function isUTR(type) {
|
|
25344
|
-
return utrTypes.has(type)
|
|
25345
|
-
}
|
|
25346
|
-
|
|
25347
|
-
function isTranscript(type) {
|
|
25348
|
-
return transcriptTypes.has(type) || type.endsWith("RNA") || type.endsWith("transcript")
|
|
25349
|
-
}
|
|
25350
|
-
|
|
25351
|
-
function isTranscriptPart(type) {
|
|
25352
|
-
return transcriptPartTypes.has(type) || type.endsWith("RNA") || isIntron(type)
|
|
25353
|
-
}
|
|
25354
|
-
|
|
25355
|
-
const filterPopupProperties = new Set(["id", "parent", "name"]);
|
|
25356
|
-
|
|
25357
|
-
class GFFFeature {
|
|
25358
|
-
|
|
25359
|
-
constructor(properties) {
|
|
25360
|
-
|
|
25361
|
-
Object.assign(this, properties);
|
|
25362
|
-
|
|
25363
|
-
if (properties.phase !== undefined && "." !== properties.phase) {
|
|
25364
|
-
this.readingFrame = (3 - parseInt(properties.phase)) % 3;
|
|
25365
|
-
}
|
|
25366
|
-
|
|
25367
|
-
}
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
popupData(genomicLocation) {
|
|
25371
|
-
|
|
25372
|
-
const pd = this.geneObject ? this.geneObject.popupData() : [];
|
|
25373
|
-
|
|
25374
|
-
if (this.geneObject) {
|
|
25375
|
-
pd.push('<hr/>');
|
|
25376
|
-
}
|
|
25377
|
-
|
|
25378
|
-
if (this.name) {
|
|
25379
|
-
pd.push({name: 'Name', value: this.name});
|
|
25380
|
-
}
|
|
25381
|
-
|
|
25382
|
-
pd.push({name: 'Type', value: this.type});
|
|
25383
|
-
pd.push({name: 'Source', value: this.source});
|
|
25384
|
-
if (this.score !== undefined) {
|
|
25385
|
-
pd.push({name: 'Score', value: this.score});
|
|
25386
|
-
}
|
|
25387
|
-
pd.push({name: 'Phase', value: this.phase});
|
|
25388
|
-
|
|
25389
|
-
if (this.attributeString) {
|
|
25390
|
-
const atts = parseAttributeString(this.attributeString, this.delim);
|
|
25391
|
-
for (let [key, value] of atts) {
|
|
25392
|
-
if (value !== undefined && value.length > 0 && !filterPopupProperties.has(key.toLowerCase())) {
|
|
25393
|
-
pd.push({name: key + ":", value: value});
|
|
25394
|
-
}
|
|
25395
|
-
}
|
|
25396
|
-
}
|
|
25397
|
-
pd.push({
|
|
25398
|
-
name: 'Location',
|
|
25399
|
-
value: `${this.chr}:${numberFormatter$1(this.start + 1)}-${numberFormatter$1(this.end)}`
|
|
25400
|
-
});
|
|
25401
|
-
return pd
|
|
25402
|
-
}
|
|
25403
|
-
|
|
25404
|
-
getAttributeValue(attributeName) {
|
|
25405
|
-
if (this.hasOwnProperty(attributeName)) {
|
|
25406
|
-
return this[attributeName]
|
|
25407
|
-
} else {
|
|
25408
|
-
// TODO -- fetch from attribute string and cache
|
|
25409
|
-
if (!this._attributeCache) {
|
|
25410
|
-
this._attributeCache = new Map();
|
|
25411
|
-
}
|
|
25412
|
-
if (this._attributeCache.has(attributeName)) {
|
|
25413
|
-
return this._attributeCache.get(attributeName)
|
|
25414
|
-
} else {
|
|
25415
|
-
const atts = parseAttributeString(this.attributeString, this.delim);
|
|
25416
|
-
let v;
|
|
25417
|
-
for (let [key, value] of atts) {
|
|
25418
|
-
if (key === attributeName) {
|
|
25419
|
-
v = value;
|
|
25420
|
-
break
|
|
25421
|
-
}
|
|
25422
|
-
}
|
|
25423
|
-
this._attributeCache.set(attributeName, v);
|
|
25424
|
-
return v
|
|
25425
|
-
}
|
|
25426
|
-
}
|
|
25427
|
-
}
|
|
25428
|
-
}
|
|
25429
|
-
|
|
25430
|
-
class GFFTranscript extends GFFFeature {
|
|
25431
|
-
|
|
25432
|
-
constructor(feature) {
|
|
25433
|
-
super(feature);
|
|
25434
|
-
this.exons = [];
|
|
25435
|
-
this.parts = [];
|
|
25436
|
-
}
|
|
25437
|
-
|
|
25438
|
-
addExon(feature) {
|
|
25439
|
-
|
|
25440
|
-
this.exons.push(feature);
|
|
25441
|
-
|
|
25442
|
-
// Expand feature -- for transcripts not explicitly represented in the file (gtf)
|
|
25443
|
-
this.start = Math.min(this.start, feature.start);
|
|
25444
|
-
this.end = Math.max(this.end, feature.end);
|
|
25445
|
-
}
|
|
25446
|
-
|
|
25447
|
-
addPart(feature) {
|
|
25448
|
-
this.parts.push(feature);
|
|
25449
|
-
}
|
|
25450
|
-
|
|
25451
|
-
assembleParts() {
|
|
25660
|
+
}
|
|
25452
25661
|
|
|
25453
|
-
|
|
25662
|
+
return feature
|
|
25663
|
+
}
|
|
25454
25664
|
|
|
25455
|
-
|
|
25456
|
-
return a.start - b.start
|
|
25457
|
-
});
|
|
25665
|
+
function decodeWig(tokens, header) {
|
|
25458
25666
|
|
|
25459
|
-
|
|
25460
|
-
let lastStart = this.parts[0].start;
|
|
25461
|
-
let lastEnd = this.parts[0].end;
|
|
25462
|
-
for (let i = 1; i < this.parts.length; i++) {
|
|
25463
|
-
const part = this.parts[i];
|
|
25464
|
-
if (isIntron(part.type)) {
|
|
25465
|
-
continue
|
|
25466
|
-
}
|
|
25467
|
-
if (part.start <= lastEnd) {
|
|
25468
|
-
lastEnd = Math.max(lastEnd, part.end);
|
|
25469
|
-
} else {
|
|
25470
|
-
let exon = this.findExonContaining({start: lastStart, end: lastEnd});
|
|
25471
|
-
if (!exon) {
|
|
25472
|
-
this.exons.push({start: lastStart, end: lastEnd, psuedo: true});
|
|
25473
|
-
}
|
|
25474
|
-
lastStart = part.start;
|
|
25475
|
-
lastEnd = part.end;
|
|
25476
|
-
}
|
|
25477
|
-
}
|
|
25478
|
-
let exon = this.findExonContaining({start: lastStart, end: lastEnd});
|
|
25479
|
-
if (!exon) {
|
|
25480
|
-
this.exons.push({start: lastStart, end: lastEnd, psuedo: true});
|
|
25481
|
-
this.start = Math.min(this.start, lastStart);
|
|
25482
|
-
this.end = Math.max(this.end, lastEnd);
|
|
25483
|
-
}
|
|
25667
|
+
const wig = header.wig;
|
|
25484
25668
|
|
|
25669
|
+
if (wig && wig.format === "fixedStep") {
|
|
25670
|
+
const ss = (wig.index * wig.step) + wig.start;
|
|
25671
|
+
const ee = ss + wig.span;
|
|
25672
|
+
const value = Number(tokens[0]);
|
|
25673
|
+
++(wig.index);
|
|
25674
|
+
return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value}
|
|
25675
|
+
} else if (wig && wig.format === "variableStep") {
|
|
25485
25676
|
|
|
25486
|
-
|
|
25487
|
-
|
|
25488
|
-
|
|
25489
|
-
|
|
25490
|
-
|
|
25491
|
-
this.addUTR(part);
|
|
25492
|
-
}
|
|
25493
|
-
}
|
|
25494
|
-
}
|
|
25677
|
+
if (tokens.length < 2) return null
|
|
25678
|
+
const ss = parseInt(tokens[0], 10) - 1;
|
|
25679
|
+
const ee = ss + wig.span;
|
|
25680
|
+
const value = Number(tokens[1]);
|
|
25681
|
+
return isNaN(value) ? null : {chr: wig.chrom, start: ss, end: ee, value: value}
|
|
25495
25682
|
|
|
25496
|
-
|
|
25497
|
-
|
|
25498
|
-
if (exon.end >= end && exon.start <= start) {
|
|
25499
|
-
return exon
|
|
25500
|
-
}
|
|
25501
|
-
}
|
|
25502
|
-
return undefined
|
|
25683
|
+
} else {
|
|
25684
|
+
return decodeBedGraph(tokens)
|
|
25503
25685
|
}
|
|
25686
|
+
}
|
|
25504
25687
|
|
|
25505
|
-
|
|
25688
|
+
function decodeSNP(tokens, header) {
|
|
25506
25689
|
|
|
25507
|
-
|
|
25508
|
-
const exons = this.exons;
|
|
25690
|
+
if (tokens.length < 6) return undefined
|
|
25509
25691
|
|
|
25510
|
-
|
|
25511
|
-
|
|
25512
|
-
|
|
25513
|
-
|
|
25514
|
-
|
|
25515
|
-
|
|
25692
|
+
const autoSql = [
|
|
25693
|
+
'bin',
|
|
25694
|
+
'chr',
|
|
25695
|
+
'start',
|
|
25696
|
+
'end',
|
|
25697
|
+
'name',
|
|
25698
|
+
'score',
|
|
25699
|
+
'strand',
|
|
25700
|
+
'refNCBI',
|
|
25701
|
+
'refUCSC',
|
|
25702
|
+
'observed',
|
|
25703
|
+
'molType',
|
|
25704
|
+
'class',
|
|
25705
|
+
'valid',
|
|
25706
|
+
'avHet',
|
|
25707
|
+
'avHetSE',
|
|
25708
|
+
'func',
|
|
25709
|
+
'locType',
|
|
25710
|
+
'weight',
|
|
25711
|
+
'exceptions',
|
|
25712
|
+
'submitterCount',
|
|
25713
|
+
'submitters',
|
|
25714
|
+
'alleleFreqCount',
|
|
25715
|
+
'alleles',
|
|
25716
|
+
'alleleNs',
|
|
25717
|
+
'alleleFreqs',
|
|
25718
|
+
'bitfields'
|
|
25719
|
+
];
|
|
25516
25720
|
|
|
25517
|
-
if (exon) {
|
|
25518
|
-
exon.cdStart = exon.cdStart ? Math.min(cds.start, exon.cdStart) : cds.start;
|
|
25519
|
-
exon.cdEnd = exon.cdEnd ? Math.max(cds.end, exon.cdEnd) : cds.end;
|
|
25520
|
-
exon.readingFrame = cds.readingFrame;
|
|
25521
|
-
// TODO -- merge attributes?
|
|
25522
|
-
} else {
|
|
25523
|
-
// cds.cdStart = cds.start
|
|
25524
|
-
// cds.cdEnd = cds.end
|
|
25525
|
-
// exons.push(cds)
|
|
25526
|
-
console.error("No exon found spanning " + cds.start + "-" + cds.end);
|
|
25527
|
-
}
|
|
25528
25721
|
|
|
25529
|
-
|
|
25530
|
-
|
|
25531
|
-
|
|
25722
|
+
const feature = {
|
|
25723
|
+
chr: tokens[1],
|
|
25724
|
+
start: Number.parseInt(tokens[2]),
|
|
25725
|
+
end: Number.parseInt(tokens[3]),
|
|
25726
|
+
name: tokens[4],
|
|
25727
|
+
score: Number.parseInt(tokens[5])
|
|
25728
|
+
};
|
|
25532
25729
|
|
|
25533
|
-
|
|
25534
|
-
|
|
25730
|
+
const n = Math.min(tokens.length, autoSql.length);
|
|
25731
|
+
for (let i = 6; i < n; i++) {
|
|
25732
|
+
feature[autoSql[i]] = tokens[i];
|
|
25535
25733
|
}
|
|
25734
|
+
return feature
|
|
25536
25735
|
|
|
25537
|
-
|
|
25736
|
+
}
|
|
25538
25737
|
|
|
25539
|
-
let exon;
|
|
25540
|
-
const exons = this.exons;
|
|
25541
25738
|
|
|
25542
|
-
|
|
25543
|
-
for (let i = 0; i < exons.length; i++) {
|
|
25544
|
-
if (exons[i].start <= utr.start && exons[i].end >= utr.end) {
|
|
25545
|
-
exon = exons[i];
|
|
25546
|
-
break
|
|
25547
|
-
}
|
|
25548
|
-
}
|
|
25739
|
+
class UCSCBedFeature {
|
|
25549
25740
|
|
|
25550
|
-
|
|
25551
|
-
|
|
25552
|
-
|
|
25553
|
-
} else {
|
|
25554
|
-
if (utr.end < exon.end) {
|
|
25555
|
-
exon.cdStart = utr.end;
|
|
25556
|
-
}
|
|
25557
|
-
if (utr.start > exon.start) {
|
|
25558
|
-
exon.cdEnd = utr.start;
|
|
25559
|
-
}
|
|
25560
|
-
}
|
|
25741
|
+
constructor(properties) {
|
|
25742
|
+
Object.assign(this, properties);
|
|
25743
|
+
}
|
|
25561
25744
|
|
|
25562
|
-
|
|
25563
|
-
|
|
25564
|
-
|
|
25565
|
-
|
|
25745
|
+
getAttributeValue(attributeName) {
|
|
25746
|
+
if (this.hasOwnProperty(attributeName)) {
|
|
25747
|
+
return this[attributeName]
|
|
25748
|
+
} else if (this.attributes) {
|
|
25749
|
+
return this.attributes[attributeName]
|
|
25566
25750
|
}
|
|
25751
|
+
}
|
|
25752
|
+
}
|
|
25567
25753
|
|
|
25568
|
-
|
|
25569
|
-
|
|
25570
|
-
|
|
25754
|
+
/*
|
|
25755
|
+
* 0 matches - Number of bases that match that aren't repeats
|
|
25756
|
+
* 1 misMatches - Number of bases that don't match
|
|
25757
|
+
* 2 repMatches - Number of bases that match but are part of repeats
|
|
25758
|
+
* 3 nCount - Number of "N" bases
|
|
25759
|
+
* 4 qNumInsert - Number of inserts in query
|
|
25760
|
+
* 5 qBaseInsert - Number of bases inserted in query
|
|
25761
|
+
* 6 tNumInsert - Number of inserts in target
|
|
25762
|
+
* 7 tBaseInsert - Number of bases inserted in target
|
|
25763
|
+
* 8 strand - "+" or "-" for query strand. For translated alignments, second "+"or "-" is for target genomic strand.
|
|
25764
|
+
* 9 qName - Query sequence name
|
|
25765
|
+
* 10 qSize - Query sequence size.
|
|
25766
|
+
* 11 qStart - Alignment start position in query
|
|
25767
|
+
* 12 qEnd - Alignment end position in query
|
|
25768
|
+
* 13 tName - Target sequence name
|
|
25769
|
+
* 14 tSize - Target sequence size
|
|
25770
|
+
* 15 tStart - Alignment start position in target
|
|
25771
|
+
* 16 tEnd - Alignment end position in target
|
|
25772
|
+
* 17 blockCount - Number of blocks in the alignment (a block contains no gaps)
|
|
25773
|
+
* 18 blockSizes - Comma-separated list of sizes of each block. If the query is a protein and the target the genome, blockSizes are in amino acids. See below for more information on protein query PSLs.
|
|
25774
|
+
* 19 qStarts - Comma-separated list of starting positions of each block in query
|
|
25775
|
+
* 20 tStarts - Comma-separated list of starting positions of each block in target
|
|
25776
|
+
*/
|
|
25777
|
+
|
|
25778
|
+
class PSLFeature {
|
|
25571
25779
|
|
|
25780
|
+
|
|
25781
|
+
constructor(properties) {
|
|
25782
|
+
Object.assign(this, properties);
|
|
25572
25783
|
}
|
|
25573
25784
|
|
|
25574
|
-
|
|
25785
|
+
get score() {
|
|
25786
|
+
const tokens = this.tokens;
|
|
25787
|
+
const match = parseInt(tokens[0]);
|
|
25788
|
+
const repMatch = parseInt(tokens[2]);
|
|
25789
|
+
const misMatch = parseInt(tokens[1]);
|
|
25790
|
+
const qGapCount = parseInt(tokens[4]);
|
|
25791
|
+
const tGapCount = parseInt(tokens[6]);
|
|
25792
|
+
const qSize = parseInt(tokens[10]);
|
|
25793
|
+
return Math.floor((1000 * (match + repMatch - misMatch - qGapCount - tGapCount)) / qSize)
|
|
25794
|
+
}
|
|
25575
25795
|
|
|
25576
|
-
|
|
25796
|
+
get matches() {
|
|
25797
|
+
return this.tokens[0]
|
|
25798
|
+
}
|
|
25577
25799
|
|
|
25578
|
-
|
|
25579
|
-
|
|
25800
|
+
get misMatches() {
|
|
25801
|
+
return this.tokens[1]
|
|
25802
|
+
}
|
|
25580
25803
|
|
|
25581
|
-
|
|
25582
|
-
|
|
25583
|
-
|
|
25804
|
+
get repMatches() {
|
|
25805
|
+
return this.tokens[2]
|
|
25806
|
+
}
|
|
25584
25807
|
|
|
25585
|
-
|
|
25586
|
-
|
|
25587
|
-
this.exons.forEach(function (exon) {
|
|
25588
|
-
if (exon.end < cdStart || exon.start > cdEnd) exon.utr = true;
|
|
25589
|
-
});
|
|
25590
|
-
}
|
|
25808
|
+
get nCount() {
|
|
25809
|
+
return this.tokens[3]
|
|
25591
25810
|
}
|
|
25592
25811
|
|
|
25593
|
-
|
|
25812
|
+
get qNumInsert() {
|
|
25813
|
+
return this.tokens[4]
|
|
25814
|
+
}
|
|
25594
25815
|
|
|
25595
|
-
|
|
25816
|
+
get qBaseInsert() {
|
|
25817
|
+
return this.tokens[5]
|
|
25818
|
+
}
|
|
25596
25819
|
|
|
25597
|
-
|
|
25598
|
-
|
|
25599
|
-
|
|
25600
|
-
if (genomicLocation >= exon.start && genomicLocation < exon.end && typeof exon.popupData === 'function') {
|
|
25601
|
-
pd.push('<hr/>');
|
|
25602
|
-
const exonData = exon.popupData(genomicLocation);
|
|
25603
|
-
for (let att of exonData) {
|
|
25604
|
-
pd.push(att);
|
|
25605
|
-
}
|
|
25606
|
-
}
|
|
25607
|
-
}
|
|
25820
|
+
get tNumInsert() {
|
|
25821
|
+
return this.tokens[6]
|
|
25822
|
+
}
|
|
25608
25823
|
|
|
25609
|
-
|
|
25610
|
-
|
|
25611
|
-
pd.push('<hr/>');
|
|
25612
|
-
const partData = part.popupData(genomicLocation);
|
|
25613
|
-
for (let att of partData) {
|
|
25614
|
-
pd.push(att);
|
|
25615
|
-
}
|
|
25616
|
-
}
|
|
25617
|
-
}
|
|
25824
|
+
get tBaseInsert() {
|
|
25825
|
+
return this.tokens[7]
|
|
25618
25826
|
|
|
25827
|
+
}
|
|
25619
25828
|
|
|
25620
|
-
|
|
25829
|
+
popupData() {
|
|
25830
|
+
return [
|
|
25831
|
+
{name: 'chr', value: this.chr},
|
|
25832
|
+
{name: 'start', value: this.start + 1},
|
|
25833
|
+
{name: 'end', value: this.end},
|
|
25834
|
+
{name: 'strand', value: this.strand},
|
|
25835
|
+
{name: 'score', value: this.score},
|
|
25836
|
+
{name: 'match', value: this.matches},
|
|
25837
|
+
{name: "mis-match", value: this.misMatches},
|
|
25838
|
+
{name: "rep. match", value: this.repMatches},
|
|
25839
|
+
{name: "N's", value: this.nCount},
|
|
25840
|
+
{name: 'Q gap count', value: this.qNumInsert},
|
|
25841
|
+
{name: 'Q gap bases', value: this.qBaseInsert},
|
|
25842
|
+
{name: 'T gap count', value: this.tNumInsert},
|
|
25843
|
+
{name: 'T gap bases', value: this.tBaseInsert},
|
|
25844
|
+
]
|
|
25621
25845
|
}
|
|
25846
|
+
|
|
25622
25847
|
}
|
|
25623
25848
|
|
|
25624
25849
|
function decode(tokens, header) {
|
|
@@ -25920,223 +26145,6 @@ function decodeShoebox(tokens, header, maxColumnCount = Number.MAX_SAFE_INTEGER)
|
|
|
25920
26145
|
return feature
|
|
25921
26146
|
}
|
|
25922
26147
|
|
|
25923
|
-
const gffNameFields = ["Name", "transcript_id", "gene_name", "gene", "gene_id", "alias", "locus", "name" ];
|
|
25924
|
-
|
|
25925
|
-
class GFFHelper {
|
|
25926
|
-
|
|
25927
|
-
constructor(options) {
|
|
25928
|
-
this.format = options.format;
|
|
25929
|
-
this.nameField = options.nameField;
|
|
25930
|
-
this.filterTypes = options.filterTypes === undefined ?
|
|
25931
|
-
new Set(['chromosome']) :
|
|
25932
|
-
new Set(options.filterTypes);
|
|
25933
|
-
}
|
|
25934
|
-
|
|
25935
|
-
combineFeatures(features, genomicInterval) {
|
|
25936
|
-
|
|
25937
|
-
let combinedFeatures;
|
|
25938
|
-
|
|
25939
|
-
const filterTypes = this.filterTypes;
|
|
25940
|
-
features = features.filter(f => filterTypes === undefined || !filterTypes.has(f.type));
|
|
25941
|
-
|
|
25942
|
-
if ("gff3" === this.format) {
|
|
25943
|
-
const tmp = this.combineFeaturesById(features);
|
|
25944
|
-
combinedFeatures = this.combineFeaturesByType(tmp);
|
|
25945
|
-
} else {
|
|
25946
|
-
combinedFeatures = this.combineFeaturesByType(features);
|
|
25947
|
-
}
|
|
25948
|
-
|
|
25949
|
-
this.numberExons(combinedFeatures, genomicInterval);
|
|
25950
|
-
this.nameFeatures(combinedFeatures);
|
|
25951
|
-
return combinedFeatures
|
|
25952
|
-
}
|
|
25953
|
-
|
|
25954
|
-
/**
|
|
25955
|
-
* Combine multiple non-transcript model features with the same ID on the same chromosome into a single feature.
|
|
25956
|
-
* Features that are part of the transcript model (e.g. exon, mRNA, etc) are combined later.
|
|
25957
|
-
*
|
|
25958
|
-
* @param features
|
|
25959
|
-
* @returns {[]}
|
|
25960
|
-
*/
|
|
25961
|
-
combineFeaturesById(features) {
|
|
25962
|
-
|
|
25963
|
-
const chrIdMap = new Map();
|
|
25964
|
-
const combinedFeatures = [];
|
|
25965
|
-
|
|
25966
|
-
for (let f of features) {
|
|
25967
|
-
if (isTranscriptPart(f.type) || isTranscript(f.type) || !f.id) {
|
|
25968
|
-
combinedFeatures.push(f);
|
|
25969
|
-
} else {
|
|
25970
|
-
let idMap = chrIdMap.get(f.chr);
|
|
25971
|
-
if (!idMap) {
|
|
25972
|
-
idMap = new Map();
|
|
25973
|
-
chrIdMap.set(f.chr, idMap);
|
|
25974
|
-
}
|
|
25975
|
-
|
|
25976
|
-
let featureArray = idMap.get(f.id);
|
|
25977
|
-
if (featureArray) {
|
|
25978
|
-
featureArray.push(f);
|
|
25979
|
-
} else {
|
|
25980
|
-
idMap.set(f.id, [f]);
|
|
25981
|
-
}
|
|
25982
|
-
}
|
|
25983
|
-
}
|
|
25984
|
-
|
|
25985
|
-
for (let idMap of chrIdMap.values()) {
|
|
25986
|
-
for (let featureArray of idMap.values()) {
|
|
25987
|
-
if (featureArray.length > 1) {
|
|
25988
|
-
// Use the first feature as prototypical (for column 9 attributes), and adjust start/end
|
|
25989
|
-
// Parts are represented as "exons", as that is how they are presented visually
|
|
25990
|
-
const cf = featureArray[0];
|
|
25991
|
-
cf.exons = [];
|
|
25992
|
-
for (let f of featureArray) {
|
|
25993
|
-
cf.start = Math.min(cf.start, f.start);
|
|
25994
|
-
cf.end = Math.max(cf.end, f.end);
|
|
25995
|
-
cf.exons.push({
|
|
25996
|
-
start: f.start,
|
|
25997
|
-
end: f.end
|
|
25998
|
-
});
|
|
25999
|
-
}
|
|
26000
|
-
combinedFeatures.push(cf);
|
|
26001
|
-
} else {
|
|
26002
|
-
combinedFeatures.push(featureArray[0]);
|
|
26003
|
-
}
|
|
26004
|
-
}
|
|
26005
|
-
}
|
|
26006
|
-
|
|
26007
|
-
return combinedFeatures
|
|
26008
|
-
}
|
|
26009
|
-
|
|
26010
|
-
combineFeaturesByType(features) {
|
|
26011
|
-
|
|
26012
|
-
// Build dictionary of genes
|
|
26013
|
-
const genes = features.filter(f => "gene" === f.type || f.type.endsWith("_gene"));
|
|
26014
|
-
const geneMap = Object.create(null);
|
|
26015
|
-
for (let g of genes) {
|
|
26016
|
-
geneMap[g.id] = g;
|
|
26017
|
-
}
|
|
26018
|
-
|
|
26019
|
-
// 1. Build dictionary of transcripts
|
|
26020
|
-
const transcripts = Object.create(null);
|
|
26021
|
-
const combinedFeatures = [];
|
|
26022
|
-
const consumedFeatures = new Set();
|
|
26023
|
-
const filterTypes = this.filterTypes;
|
|
26024
|
-
|
|
26025
|
-
features = features.filter(f => filterTypes === undefined || !filterTypes.has(f.type));
|
|
26026
|
-
|
|
26027
|
-
for (let f of features) {
|
|
26028
|
-
if (isTranscript(f.type)) {
|
|
26029
|
-
const transcriptId = f.id; // getAttribute(f.attributeString, "transcript_id", /\s+/);
|
|
26030
|
-
if (undefined !== transcriptId) {
|
|
26031
|
-
const gffTranscript = new GFFTranscript(f);
|
|
26032
|
-
transcripts[transcriptId] = gffTranscript;
|
|
26033
|
-
combinedFeatures.push(gffTranscript);
|
|
26034
|
-
consumedFeatures.add(f);
|
|
26035
|
-
const g = geneMap[f.parent];
|
|
26036
|
-
if (g) {
|
|
26037
|
-
gffTranscript.geneObject = g;
|
|
26038
|
-
consumedFeatures.add(g);
|
|
26039
|
-
}
|
|
26040
|
-
}
|
|
26041
|
-
}
|
|
26042
|
-
}
|
|
26043
|
-
|
|
26044
|
-
// Add exons and transcript parts
|
|
26045
|
-
for (let f of features) {
|
|
26046
|
-
if (isTranscriptPart(f.type)) {
|
|
26047
|
-
const parents = getParents(f);
|
|
26048
|
-
if (parents) {
|
|
26049
|
-
for (let id of parents) {
|
|
26050
|
-
|
|
26051
|
-
let transcript = transcripts[id];
|
|
26052
|
-
if (!transcript && this.format === "gtf") {
|
|
26053
|
-
// GTF does not require explicit a transcript or mRNA record, start one with this feature.
|
|
26054
|
-
const psuedoTranscript = Object.assign({}, f);
|
|
26055
|
-
psuedoTranscript.type = "transcript";
|
|
26056
|
-
transcript = new GFFTranscript(psuedoTranscript);
|
|
26057
|
-
transcripts[id] = transcript;
|
|
26058
|
-
combinedFeatures.push(transcript);
|
|
26059
|
-
}
|
|
26060
|
-
if (transcript !== undefined) {
|
|
26061
|
-
|
|
26062
|
-
if (isExon(f.type)) {
|
|
26063
|
-
if (parents.length > 1) {
|
|
26064
|
-
// Multiple parents, this is unusual. Make a copy as exon can be modified
|
|
26065
|
-
// differently by CDS, etc, for each parent
|
|
26066
|
-
const e2 = new GFFFeature(f);
|
|
26067
|
-
transcript.addExon(e2);
|
|
26068
|
-
} else {
|
|
26069
|
-
transcript.addExon(f);
|
|
26070
|
-
}
|
|
26071
|
-
} else {
|
|
26072
|
-
transcript.addPart(f);
|
|
26073
|
-
}
|
|
26074
|
-
consumedFeatures.add(f);
|
|
26075
|
-
}
|
|
26076
|
-
}
|
|
26077
|
-
}
|
|
26078
|
-
}
|
|
26079
|
-
}
|
|
26080
|
-
|
|
26081
|
-
// Finish transcripts
|
|
26082
|
-
combinedFeatures.forEach(function (f) {
|
|
26083
|
-
if (typeof f.finish === "function") {
|
|
26084
|
-
f.finish();
|
|
26085
|
-
}
|
|
26086
|
-
});
|
|
26087
|
-
|
|
26088
|
-
// Add other features
|
|
26089
|
-
const others = features.filter(f => !consumedFeatures.has(f));
|
|
26090
|
-
for (let f of others) {
|
|
26091
|
-
combinedFeatures.push(f);
|
|
26092
|
-
}
|
|
26093
|
-
|
|
26094
|
-
return combinedFeatures
|
|
26095
|
-
|
|
26096
|
-
function getParents(f) {
|
|
26097
|
-
if (f.parent && f.parent.trim() !== "") {
|
|
26098
|
-
return f.parent.trim().split(",")
|
|
26099
|
-
} else {
|
|
26100
|
-
return null
|
|
26101
|
-
}
|
|
26102
|
-
}
|
|
26103
|
-
}
|
|
26104
|
-
|
|
26105
|
-
numberExons(features, genomicInterval) {
|
|
26106
|
-
|
|
26107
|
-
for (let f of features) {
|
|
26108
|
-
if (f.exons &&
|
|
26109
|
-
(!genomicInterval ||
|
|
26110
|
-
(f.end <= genomicInterval.end && f.start > genomicInterval.start))) {
|
|
26111
|
-
for (let i = 0; i < f.exons.length; i++) {
|
|
26112
|
-
const exon = f.exons[i];
|
|
26113
|
-
exon.number = f.strand === "-" ? f.exons.length - i : i + 1;
|
|
26114
|
-
}
|
|
26115
|
-
}
|
|
26116
|
-
}
|
|
26117
|
-
}
|
|
26118
|
-
|
|
26119
|
-
nameFeatures(features) {
|
|
26120
|
-
// Find name (label) property
|
|
26121
|
-
for (let f of features) {
|
|
26122
|
-
if(typeof f.getAttributeValue === 'function') {
|
|
26123
|
-
|
|
26124
|
-
if (this.nameField) {
|
|
26125
|
-
f.name = f.getAttributeValue(this.nameField);
|
|
26126
|
-
} else {
|
|
26127
|
-
for (let nameField of gffNameFields) {
|
|
26128
|
-
const v = f.getAttributeValue(nameField);
|
|
26129
|
-
if (v) {
|
|
26130
|
-
f.name = v;
|
|
26131
|
-
break
|
|
26132
|
-
}
|
|
26133
|
-
}
|
|
26134
|
-
}
|
|
26135
|
-
}
|
|
26136
|
-
}
|
|
26137
|
-
}
|
|
26138
|
-
}
|
|
26139
|
-
|
|
26140
26148
|
/**
|
|
26141
26149
|
* Utilities for creating and registering custom file formats for generic delimited files. A format definition consists
|
|
26142
26150
|
* of an ordered list of fields, and optional delimiter specified.
|
|
@@ -33157,7 +33165,7 @@ class BWReader {
|
|
|
33157
33165
|
if (this.type === "bigwig") {
|
|
33158
33166
|
return "wig"
|
|
33159
33167
|
} else {
|
|
33160
|
-
return this.autoSql && this.autoSql.table
|
|
33168
|
+
return this.autoSql && ("interact" === this.autoSql.table || "chromatinInteract" === this.autoSql.table) ? "interact" : "annotation"
|
|
33161
33169
|
}
|
|
33162
33170
|
}
|
|
33163
33171
|
|
|
@@ -40057,8 +40065,8 @@ class TrackViewport extends Viewport {
|
|
|
40057
40065
|
checkZoomIn() {
|
|
40058
40066
|
|
|
40059
40067
|
const zoomedOutOfWindow = () => {
|
|
40060
|
-
if (this.referenceFrame.chr.toLowerCase() === "all"
|
|
40061
|
-
return
|
|
40068
|
+
if (this.referenceFrame.chr.toLowerCase() === "all") {
|
|
40069
|
+
return !this.trackView.track.supportsWholeGenome
|
|
40062
40070
|
} else {
|
|
40063
40071
|
const visibilityWindow = this.trackView.track.visibilityWindow;
|
|
40064
40072
|
return (
|
|
@@ -60022,7 +60030,7 @@ class InteractionTrack extends TrackBase {
|
|
|
60022
60030
|
}
|
|
60023
60031
|
|
|
60024
60032
|
get supportsWholeGenome() {
|
|
60025
|
-
return true
|
|
60033
|
+
return typeof this.featureSource.supportsWholeGenome === 'function' ? this.featureSource.supportsWholeGenome() : true;
|
|
60026
60034
|
}
|
|
60027
60035
|
|
|
60028
60036
|
async getFeatures(chr, start, end) {
|
|
@@ -72568,7 +72576,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
|
|
|
72568
72576
|
})
|
|
72569
72577
|
}
|
|
72570
72578
|
|
|
72571
|
-
const _version = "3.1.
|
|
72579
|
+
const _version = "3.1.4";
|
|
72572
72580
|
function version() {
|
|
72573
72581
|
return _version
|
|
72574
72582
|
}
|
|
@@ -76330,15 +76338,6 @@ class Browser {
|
|
|
76330
76338
|
this.root = div({class: 'igv-container'});
|
|
76331
76339
|
shadowRoot.appendChild(this.root);
|
|
76332
76340
|
|
|
76333
|
-
// spinner
|
|
76334
|
-
this.spinner = div({class: 'igv-loading-spinner-container'});
|
|
76335
|
-
this.root.appendChild(this.spinner);
|
|
76336
|
-
|
|
76337
|
-
this.spinner.appendChild(div());
|
|
76338
|
-
this.spinner.style.width = '64px';
|
|
76339
|
-
this.spinner.style.height = '64px';
|
|
76340
|
-
this.stopSpinner();
|
|
76341
|
-
|
|
76342
76341
|
this.alert = new Alert(this.root);
|
|
76343
76342
|
|
|
76344
76343
|
this.columnContainer = div({class: 'igv-column-container'});
|
|
@@ -76414,14 +76413,6 @@ class Browser {
|
|
|
76414
76413
|
return this.roiManager.roiTableIsVisible()
|
|
76415
76414
|
}
|
|
76416
76415
|
|
|
76417
|
-
startSpinner() {
|
|
76418
|
-
this.spinner.style.display = 'flex';
|
|
76419
|
-
}
|
|
76420
|
-
|
|
76421
|
-
stopSpinner() {
|
|
76422
|
-
this.spinner.style.display = 'none';
|
|
76423
|
-
}
|
|
76424
|
-
|
|
76425
76416
|
initialize(config) {
|
|
76426
76417
|
|
|
76427
76418
|
this.flanking = config.flanking;
|
|
@@ -78770,9 +78761,6 @@ async function createBrowser(parentDiv, config) {
|
|
|
78770
78761
|
const browser = new Browser(config, parentDiv);
|
|
78771
78762
|
allBrowsers.push(browser);
|
|
78772
78763
|
|
|
78773
|
-
// Lod initial sessio
|
|
78774
|
-
browser.startSpinner();
|
|
78775
|
-
|
|
78776
78764
|
const sessionURL = config.sessionURL || config.session || config.hubURL;
|
|
78777
78765
|
if (sessionURL) {
|
|
78778
78766
|
await browser.loadSession({
|
|
@@ -78782,7 +78770,6 @@ async function createBrowser(parentDiv, config) {
|
|
|
78782
78770
|
await browser.loadSessionObject(config);
|
|
78783
78771
|
}
|
|
78784
78772
|
|
|
78785
|
-
browser.stopSpinner();
|
|
78786
78773
|
browser.navbar.navbarDidResize();
|
|
78787
78774
|
|
|
78788
78775
|
return browser
|