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