igv 3.2.5 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/igv.esm.js +1641 -919
- package/dist/igv.esm.min.js +9 -9
- package/dist/igv.esm.min.js.map +1 -1
- package/dist/igv.js +1641 -919
- package/dist/igv.min.js +9 -9
- package/dist/igv.min.js.map +1 -1
- package/package.json +3 -3
package/dist/igv.esm.js
CHANGED
|
@@ -10946,7 +10946,7 @@ function createMenuElements$1(itemList, popover) {
|
|
|
10946
10946
|
return list;
|
|
10947
10947
|
}
|
|
10948
10948
|
|
|
10949
|
-
/*! @license DOMPurify 3.2.
|
|
10949
|
+
/*! @license DOMPurify 3.2.5 | (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.5/LICENSE */
|
|
10950
10950
|
|
|
10951
10951
|
const {
|
|
10952
10952
|
entries,
|
|
@@ -11006,6 +11006,9 @@ const typeErrorCreate = unconstruct(TypeError);
|
|
|
11006
11006
|
*/
|
|
11007
11007
|
function unapply(func) {
|
|
11008
11008
|
return function (thisArg) {
|
|
11009
|
+
if (thisArg instanceof RegExp) {
|
|
11010
|
+
thisArg.lastIndex = 0;
|
|
11011
|
+
}
|
|
11009
11012
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
11010
11013
|
args[_key - 1] = arguments[_key];
|
|
11011
11014
|
}
|
|
@@ -11244,7 +11247,7 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
11244
11247
|
function createDOMPurify() {
|
|
11245
11248
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
11246
11249
|
const DOMPurify = root => createDOMPurify(root);
|
|
11247
|
-
DOMPurify.version = '3.2.
|
|
11250
|
+
DOMPurify.version = '3.2.5';
|
|
11248
11251
|
DOMPurify.removed = [];
|
|
11249
11252
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
11250
11253
|
// Not running in a browser, provide a factory function
|
|
@@ -11849,7 +11852,7 @@ function createDOMPurify() {
|
|
|
11849
11852
|
allowedTags: ALLOWED_TAGS
|
|
11850
11853
|
});
|
|
11851
11854
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
11852
|
-
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
|
|
11855
|
+
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
|
|
11853
11856
|
_forceRemove(currentNode);
|
|
11854
11857
|
return true;
|
|
11855
11858
|
}
|
|
@@ -12871,6 +12874,20 @@ class NonIndexedFasta {
|
|
|
12871
12874
|
|
|
12872
12875
|
async loadAll() {
|
|
12873
12876
|
|
|
12877
|
+
|
|
12878
|
+
const pushChromosome = (current, order) => {
|
|
12879
|
+
const length = current.length || (current.offset + current.seq.length);
|
|
12880
|
+
if (!chrNameSet.has(current.chr)) {
|
|
12881
|
+
this.sequences.set(current.chr, []);
|
|
12882
|
+
this.chromosomes.set(current.chr, new Chromosome(current.chr, order, length));
|
|
12883
|
+
chrNameSet.add(current.chr);
|
|
12884
|
+
} else {
|
|
12885
|
+
const c = this.chromosomes.get(current.chr);
|
|
12886
|
+
c.bpLength = Math.max(c.bpLength, length);
|
|
12887
|
+
}
|
|
12888
|
+
this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
|
|
12889
|
+
};
|
|
12890
|
+
|
|
12874
12891
|
let data;
|
|
12875
12892
|
if (isDataURL(this.fastaURL)) {
|
|
12876
12893
|
let bytes = decodeDataURI$1(this.fastaURL);
|
|
@@ -12884,70 +12901,62 @@ class NonIndexedFasta {
|
|
|
12884
12901
|
|
|
12885
12902
|
const chrNameSet = new Set();
|
|
12886
12903
|
const lines = splitLines$2(data);
|
|
12887
|
-
const len = lines.length;
|
|
12888
|
-
let lineNo = 0;
|
|
12889
12904
|
let order = 0;
|
|
12890
|
-
let nextLine;
|
|
12891
12905
|
let current = {};
|
|
12892
|
-
|
|
12893
|
-
nextLine = lines[lineNo++].trim();
|
|
12906
|
+
for (let nextLine of lines) {
|
|
12894
12907
|
if (nextLine.startsWith("#") || nextLine.length === 0) ; else if (nextLine.startsWith(">")) {
|
|
12895
12908
|
// Start the next sequence
|
|
12896
|
-
if (current && current.seq) {
|
|
12897
|
-
pushChromosome
|
|
12909
|
+
if (current.seq && current.seq.length > 0) {
|
|
12910
|
+
pushChromosome(current, order++);
|
|
12898
12911
|
}
|
|
12912
|
+
current.seq = "";
|
|
12899
12913
|
|
|
12900
12914
|
const parts = nextLine.substr(1).split(/\s+/);
|
|
12901
12915
|
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12916
|
+
|
|
12917
|
+
// Check for @len= token, which is a non-standard extension supporting igv-reports.
|
|
12918
|
+
if (nextLine.includes("@len=")) {
|
|
12919
|
+
const nameParts = parts[0].split(':');
|
|
12920
|
+
current.chr = nameParts[0];
|
|
12921
|
+
if (nameParts.length > 1 && nameParts[1].indexOf('-') > 0) {
|
|
12922
|
+
|
|
12923
|
+
const locusParts = nameParts[1].split('-');
|
|
12924
|
+
if (locusParts.length === 2 &&
|
|
12925
|
+
/^[0-9]+$/.test(locusParts[0]) &&
|
|
12926
|
+
/^[0-9]+$/.test(locusParts[1])) ;
|
|
12927
|
+
const from = Number.parseInt(locusParts[0]);
|
|
12928
|
+
Number.parseInt(locusParts[1]);
|
|
12915
12929
|
current.offset = from - 1;
|
|
12916
|
-
}
|
|
12917
12930
|
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12931
|
+
// Check for chromosome length token
|
|
12932
|
+
if (parts.length > 1 && parts[1].startsWith("@len=")) {
|
|
12933
|
+
try {
|
|
12934
|
+
current.length = parseInt(parts[1].trim().substring(5));
|
|
12935
|
+
} catch (e) {
|
|
12936
|
+
current.length = undefined;
|
|
12937
|
+
console.error(`Error parsing sequence length for ${nextLine}`);
|
|
12938
|
+
}
|
|
12939
|
+
} else {
|
|
12923
12940
|
current.length = undefined;
|
|
12924
|
-
console.error(`Error parsing sequence length for ${nextLine}`);
|
|
12925
12941
|
}
|
|
12926
|
-
} else {
|
|
12927
|
-
current.length = undefined;
|
|
12928
12942
|
}
|
|
12943
|
+
} else {
|
|
12944
|
+
// No special tokens, a standard FASTA header
|
|
12945
|
+
current.chr = parts[0];
|
|
12946
|
+
current.offset = 0;
|
|
12929
12947
|
}
|
|
12948
|
+
|
|
12930
12949
|
} else {
|
|
12950
|
+
// Not a header or comment, so it must be sequence data
|
|
12931
12951
|
current.seq += nextLine;
|
|
12932
12952
|
}
|
|
12933
|
-
// add last seq
|
|
12934
|
-
if (current && current.seq) {
|
|
12935
|
-
pushChromosome.call(this, current, order);
|
|
12936
|
-
}
|
|
12937
12953
|
}
|
|
12938
12954
|
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
this.sequences.set(current.chr, []);
|
|
12943
|
-
this.chromosomes.set(current.chr, new Chromosome(current.chr, order, length));
|
|
12944
|
-
chrNameSet.add(current.chr);
|
|
12945
|
-
} else {
|
|
12946
|
-
const c = this.chromosomes.get(current.chr);
|
|
12947
|
-
c.bpLength = Math.max(c.bpLength, length);
|
|
12948
|
-
}
|
|
12949
|
-
this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
|
|
12955
|
+
// Handle the last sequence
|
|
12956
|
+
if (current.seq && current.seq.length > 0) {
|
|
12957
|
+
pushChromosome(current, order);
|
|
12950
12958
|
}
|
|
12959
|
+
|
|
12951
12960
|
}
|
|
12952
12961
|
|
|
12953
12962
|
/**
|
|
@@ -13712,17 +13721,22 @@ class BPTree {
|
|
|
13712
13721
|
|
|
13713
13722
|
static magic = 2026540177
|
|
13714
13723
|
littleEndian = true
|
|
13724
|
+
type = 'BPTree' // Either BPTree or BPChromTree
|
|
13715
13725
|
nodeCache = new Map()
|
|
13716
13726
|
|
|
13717
|
-
static async loadBpTree(path, config, startOffset) {
|
|
13718
|
-
const bpTree = new BPTree(path, config, startOffset);
|
|
13727
|
+
static async loadBpTree(path, config, startOffset, type, loader) {
|
|
13728
|
+
const bpTree = new BPTree(path, config, startOffset, type, loader);
|
|
13719
13729
|
return bpTree.init()
|
|
13720
13730
|
}
|
|
13721
13731
|
|
|
13722
|
-
constructor(path, config, startOffset) {
|
|
13732
|
+
constructor(path, config, startOffset, type, loader) {
|
|
13723
13733
|
this.path = path;
|
|
13724
13734
|
this.config = config;
|
|
13725
13735
|
this.startOffset = startOffset;
|
|
13736
|
+
if(type) {
|
|
13737
|
+
this.type = type;
|
|
13738
|
+
}
|
|
13739
|
+
this.loader = loader || igvxhr;
|
|
13726
13740
|
}
|
|
13727
13741
|
|
|
13728
13742
|
async init() {
|
|
@@ -13748,69 +13762,22 @@ class BPTree {
|
|
|
13748
13762
|
return this
|
|
13749
13763
|
}
|
|
13750
13764
|
|
|
13751
|
-
|
|
13752
|
-
|
|
13765
|
+
getItemCount() {
|
|
13753
13766
|
if(!this.header) {
|
|
13754
|
-
|
|
13767
|
+
throw Error("Header not initialized")
|
|
13755
13768
|
}
|
|
13769
|
+
return this.header.itemCount
|
|
13770
|
+
}
|
|
13756
13771
|
|
|
13757
|
-
|
|
13772
|
+
async search(term) {
|
|
13758
13773
|
|
|
13759
|
-
if
|
|
13760
|
-
|
|
13774
|
+
if(!this.header) {
|
|
13775
|
+
await this.init();
|
|
13761
13776
|
}
|
|
13762
13777
|
|
|
13763
|
-
const readTreeNode = async (offset) => {
|
|
13764
|
-
|
|
13765
|
-
if (this.nodeCache.has(offset)) {
|
|
13766
|
-
return this.nodeCache.get(offset)
|
|
13767
|
-
} else {
|
|
13768
|
-
|
|
13769
|
-
let binaryParser = await this.#getParserFor(offset, 4);
|
|
13770
|
-
const type = binaryParser.getByte();
|
|
13771
|
-
binaryParser.getByte();
|
|
13772
|
-
const count = binaryParser.getUShort();
|
|
13773
|
-
const items = [];
|
|
13774
|
-
|
|
13775
|
-
if (type === 1) {
|
|
13776
|
-
// Leaf node
|
|
13777
|
-
const size = count * (keySize + valSize);
|
|
13778
|
-
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13779
|
-
for (let i = 0; i < count; i++) {
|
|
13780
|
-
const key = binaryParser.getFixedLengthString(keySize);
|
|
13781
|
-
const offset = binaryParser.getLong();
|
|
13782
|
-
|
|
13783
|
-
let value;
|
|
13784
|
-
if (valSize === 16) {
|
|
13785
|
-
const length = binaryParser.getInt();
|
|
13786
|
-
binaryParser.getInt();
|
|
13787
|
-
value = {offset, length};
|
|
13788
|
-
} else {
|
|
13789
|
-
value = {offset};
|
|
13790
|
-
}
|
|
13791
|
-
items.push({key, value});
|
|
13792
|
-
}
|
|
13793
|
-
} else {
|
|
13794
|
-
// Non leaf node
|
|
13795
|
-
const size = count * (keySize + 8);
|
|
13796
|
-
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13797
|
-
|
|
13798
|
-
for (let i = 0; i < count; i++) {
|
|
13799
|
-
const key = binaryParser.getFixedLengthString(keySize);
|
|
13800
|
-
const offset = binaryParser.getLong();
|
|
13801
|
-
items.push({key, offset});
|
|
13802
|
-
}
|
|
13803
|
-
}
|
|
13804
|
-
|
|
13805
|
-
const node = {type, count, items};
|
|
13806
|
-
this.nodeCache.set(offset, node);
|
|
13807
|
-
return node
|
|
13808
|
-
}
|
|
13809
|
-
};
|
|
13810
|
-
|
|
13811
13778
|
const walkTreeNode = async (offset) => {
|
|
13812
13779
|
|
|
13813
|
-
const node = await readTreeNode(offset);
|
|
13780
|
+
const node = await this.readTreeNode(offset);
|
|
13814
13781
|
|
|
13815
13782
|
if (node.type === 1) {
|
|
13816
13783
|
// Leaf node
|
|
@@ -13841,9 +13808,66 @@ class BPTree {
|
|
|
13841
13808
|
return walkTreeNode(this.header.nodeOffset)
|
|
13842
13809
|
}
|
|
13843
13810
|
|
|
13811
|
+
async readTreeNode (offset) {
|
|
13812
|
+
|
|
13813
|
+
if (this.nodeCache.has(offset)) {
|
|
13814
|
+
return this.nodeCache.get(offset)
|
|
13815
|
+
} else {
|
|
13816
|
+
let binaryParser = await this.#getParserFor(offset, 4);
|
|
13817
|
+
const type = binaryParser.getByte();
|
|
13818
|
+
binaryParser.getByte();
|
|
13819
|
+
const count = binaryParser.getUShort();
|
|
13820
|
+
const items = [];
|
|
13821
|
+
|
|
13822
|
+
const {keySize, valSize} = this.header;
|
|
13823
|
+
|
|
13824
|
+
if (type === 1) {
|
|
13825
|
+
// Leaf node
|
|
13826
|
+
const size = count * (keySize + valSize);
|
|
13827
|
+
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13828
|
+
for (let i = 0; i < count; i++) {
|
|
13829
|
+
const key = binaryParser.getFixedLengthString(keySize);
|
|
13830
|
+
let value;
|
|
13831
|
+
if(this.type === 'BPChromTree') {
|
|
13832
|
+
const id = binaryParser.getInt();
|
|
13833
|
+
const size = binaryParser.getInt();
|
|
13834
|
+
value = {id, size};
|
|
13835
|
+
} else {
|
|
13836
|
+
const offset = binaryParser.getLong();
|
|
13837
|
+
if (valSize === 16) {
|
|
13838
|
+
const length = binaryParser.getLong();
|
|
13839
|
+
value = {offset, length};
|
|
13840
|
+
} else {
|
|
13841
|
+
value = {offset};
|
|
13842
|
+
}
|
|
13843
|
+
}
|
|
13844
|
+
items.push({key, value});
|
|
13845
|
+
}
|
|
13846
|
+
} else {
|
|
13847
|
+
// Non leaf node
|
|
13848
|
+
const size = count * (keySize + 8);
|
|
13849
|
+
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13850
|
+
|
|
13851
|
+
for (let i = 0; i < count; i++) {
|
|
13852
|
+
const key = binaryParser.getFixedLengthString(keySize);
|
|
13853
|
+
const offset = binaryParser.getLong();
|
|
13854
|
+
items.push({key, offset});
|
|
13855
|
+
}
|
|
13856
|
+
}
|
|
13857
|
+
|
|
13858
|
+
const node = {type, count, items};
|
|
13859
|
+
this.nodeCache.set(offset, node);
|
|
13860
|
+
return node
|
|
13861
|
+
}
|
|
13862
|
+
}
|
|
13863
|
+
|
|
13844
13864
|
async #getParserFor(start, size) {
|
|
13845
|
-
|
|
13846
|
-
|
|
13865
|
+
try {
|
|
13866
|
+
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start, size}}));
|
|
13867
|
+
return new BinaryParser$1(new DataView(data), this.littleEndian)
|
|
13868
|
+
} catch (e) {
|
|
13869
|
+
console.error(e);
|
|
13870
|
+
}
|
|
13847
13871
|
}
|
|
13848
13872
|
|
|
13849
13873
|
}
|
|
@@ -13869,6 +13893,7 @@ class TwobitSequence {
|
|
|
13869
13893
|
|
|
13870
13894
|
littleEndian
|
|
13871
13895
|
metaIndex = new Map()
|
|
13896
|
+
chromosomeNames
|
|
13872
13897
|
|
|
13873
13898
|
constructor(config) {
|
|
13874
13899
|
this.url = config.twoBitURL || config.fastaURL;
|
|
@@ -13961,9 +13986,16 @@ class TwobitSequence {
|
|
|
13961
13986
|
return sequenceBases
|
|
13962
13987
|
}
|
|
13963
13988
|
|
|
13989
|
+
/**
|
|
13990
|
+
* Read the internal index of the 2bit file. This is a list of sequence names and their offsets in the file.
|
|
13991
|
+
*
|
|
13992
|
+
* @returns {Promise<Map<any, any>>}
|
|
13993
|
+
* @private
|
|
13994
|
+
*/
|
|
13964
13995
|
async _readIndex() {
|
|
13965
13996
|
|
|
13966
13997
|
const index = new Map();
|
|
13998
|
+
this.chromosomeNames = [];
|
|
13967
13999
|
|
|
13968
14000
|
const loadRange = {start: 0, size: 64};
|
|
13969
14001
|
let arrayBuffer = await igvxhr.loadArrayBuffer(this.url, {range: loadRange});
|
|
@@ -14016,6 +14048,8 @@ class TwobitSequence {
|
|
|
14016
14048
|
index.set(name, offset);
|
|
14017
14049
|
|
|
14018
14050
|
estNameLength = Math.floor(estNameLength * (i / (i + 1)) + name.length / (i + 1));
|
|
14051
|
+
|
|
14052
|
+
this.chromosomeNames.push(name);
|
|
14019
14053
|
}
|
|
14020
14054
|
return index
|
|
14021
14055
|
}
|
|
@@ -16343,7 +16377,10 @@ class FeatureParser {
|
|
|
16343
16377
|
}
|
|
16344
16378
|
} else {
|
|
16345
16379
|
// All directives that could change the format, and thus decoder, should have been read by now.
|
|
16346
|
-
|
|
16380
|
+
// Set the decoder, unless it is explicitly set in the track configuration (not common)
|
|
16381
|
+
if(!this.config.decode) {
|
|
16382
|
+
this.setDecoder(header.format);
|
|
16383
|
+
}
|
|
16347
16384
|
|
|
16348
16385
|
// If the line can be parsed as a feature assume we are beyond the header, if any
|
|
16349
16386
|
const tokens = line.split(this.delimiter || "\t");
|
|
@@ -22158,84 +22195,6 @@ function zoomLevelForScale$1(chr, bpPerPixel, genome) {
|
|
|
22158
22195
|
return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
|
|
22159
22196
|
}
|
|
22160
22197
|
|
|
22161
|
-
/**
|
|
22162
|
-
* A ChromTree parses a UCSC bigbed/bigwig "chromosomeTree" section of the header to produce 2 maps,
|
|
22163
|
-
* (1) ID -> chromosome names, and its
|
|
22164
|
-
* (2) chromsome name -> ID
|
|
22165
|
-
*
|
|
22166
|
-
* Both maps are needed by IGV
|
|
22167
|
-
*
|
|
22168
|
-
* The chromosome tree is a B+ index, but is located continguously in memory in the header section of the file. This
|
|
22169
|
-
* makes it feasible to parse the whole tree with data from a single fetch. In the end the tree is discarded
|
|
22170
|
-
* leaving only the mapps.
|
|
22171
|
-
*/
|
|
22172
|
-
class ChromTree {
|
|
22173
|
-
|
|
22174
|
-
constructor(header, nameToID, valueToKey, sumLengths) {
|
|
22175
|
-
this.header = header;
|
|
22176
|
-
this.nameToId = nameToID;
|
|
22177
|
-
this.idToName = valueToKey;
|
|
22178
|
-
this.sumLengths = sumLengths;
|
|
22179
|
-
}
|
|
22180
|
-
|
|
22181
|
-
static parseTree(binaryParser, startOffset, genome = false) {
|
|
22182
|
-
{
|
|
22183
|
-
const magic = binaryParser.getInt();
|
|
22184
|
-
const blockSize = binaryParser.getInt();
|
|
22185
|
-
const keySize = binaryParser.getInt();
|
|
22186
|
-
const valSize = binaryParser.getInt();
|
|
22187
|
-
const itemCount = binaryParser.getLong();
|
|
22188
|
-
const reserved = binaryParser.getLong();
|
|
22189
|
-
|
|
22190
|
-
const header = {magic, blockSize, keySize, valSize, itemCount, reserved};
|
|
22191
|
-
const nameToId = new Map();
|
|
22192
|
-
const idToName = [];
|
|
22193
|
-
let sumLengths = 0;
|
|
22194
|
-
const readTreeNode = (offset) => {
|
|
22195
|
-
if (offset >= 0) binaryParser.position = offset;
|
|
22196
|
-
const type = binaryParser.getByte();
|
|
22197
|
-
binaryParser.getByte();
|
|
22198
|
-
const count = binaryParser.getUShort();
|
|
22199
|
-
|
|
22200
|
-
if (type === 1) {
|
|
22201
|
-
// Leaf node
|
|
22202
|
-
for (let i = 0; i < count; i++) {
|
|
22203
|
-
let key = binaryParser.getFixedLengthString(keySize);
|
|
22204
|
-
let value;
|
|
22205
|
-
if (valSize === 8) {
|
|
22206
|
-
value = binaryParser.getInt();
|
|
22207
|
-
const chromSize = binaryParser.getInt();
|
|
22208
|
-
sumLengths += chromSize;
|
|
22209
|
-
if (genome) key = genome.getChromosomeName(key); // Translate to canonical chr name
|
|
22210
|
-
nameToId.set(key, value);
|
|
22211
|
-
idToName[value] = key;
|
|
22212
|
-
|
|
22213
|
-
} else {
|
|
22214
|
-
throw Error(`Unexpected "valSize" value in chromosome tree. Expected 8, actual value is ${valSize}`)
|
|
22215
|
-
}
|
|
22216
|
-
}
|
|
22217
|
-
} else {
|
|
22218
|
-
// non-leaf
|
|
22219
|
-
for (let i = 0; i < count; i++) {
|
|
22220
|
-
binaryParser.getFixedLengthString(keySize);
|
|
22221
|
-
const childOffset = binaryParser.getLong();
|
|
22222
|
-
const bufferOffset = childOffset - startOffset;
|
|
22223
|
-
const currOffset = binaryParser.position;
|
|
22224
|
-
readTreeNode(bufferOffset);
|
|
22225
|
-
binaryParser.position = currOffset;
|
|
22226
|
-
}
|
|
22227
|
-
}
|
|
22228
|
-
};
|
|
22229
|
-
|
|
22230
|
-
// Recursively walk tree to populate dictionary
|
|
22231
|
-
readTreeNode( -1);
|
|
22232
|
-
|
|
22233
|
-
return new ChromTree(header, nameToId, idToName, sumLengths)
|
|
22234
|
-
}
|
|
22235
|
-
}
|
|
22236
|
-
|
|
22237
|
-
}
|
|
22238
|
-
|
|
22239
22198
|
const RPTREE_HEADER_SIZE = 48;
|
|
22240
22199
|
const RPTREE_NODE_LEAF_ITEM_SIZE = 32; // leaf item size
|
|
22241
22200
|
const RPTREE_NODE_CHILD_ITEM_SIZE = 24; // child item size
|
|
@@ -22246,11 +22205,12 @@ class RPTree {
|
|
|
22246
22205
|
littleEndian = true
|
|
22247
22206
|
nodeCache = new Map()
|
|
22248
22207
|
|
|
22249
|
-
constructor(path, config, startOffset) {
|
|
22208
|
+
constructor(path, config, startOffset, loader) {
|
|
22250
22209
|
|
|
22251
22210
|
this.path = path;
|
|
22252
22211
|
this.config = config;
|
|
22253
22212
|
this.startOffset = startOffset;
|
|
22213
|
+
this.loader = loader || igvxhr;
|
|
22254
22214
|
}
|
|
22255
22215
|
|
|
22256
22216
|
|
|
@@ -22294,7 +22254,7 @@ class RPTree {
|
|
|
22294
22254
|
}
|
|
22295
22255
|
|
|
22296
22256
|
async #getParserFor(start, size) {
|
|
22297
|
-
const data = await
|
|
22257
|
+
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start, size}}));
|
|
22298
22258
|
return new BinaryParser$1(new DataView(data), this.littleEndian)
|
|
22299
22259
|
}
|
|
22300
22260
|
|
|
@@ -22651,6 +22611,165 @@ class Trix {
|
|
|
22651
22611
|
}
|
|
22652
22612
|
}
|
|
22653
22613
|
|
|
22614
|
+
class ChromTree {
|
|
22615
|
+
|
|
22616
|
+
nameToId = new Map()
|
|
22617
|
+
idToName = new Map()
|
|
22618
|
+
|
|
22619
|
+
constructor(path, config, startOffset, loader) {
|
|
22620
|
+
this.path = path;
|
|
22621
|
+
this.config = config;
|
|
22622
|
+
this.startOffset = startOffset;
|
|
22623
|
+
|
|
22624
|
+
this.bpTree = new BPTree(path, config, startOffset, 'BPChromTree', loader);
|
|
22625
|
+
}
|
|
22626
|
+
|
|
22627
|
+
async init() {
|
|
22628
|
+
return this.bpTree.init()
|
|
22629
|
+
}
|
|
22630
|
+
|
|
22631
|
+
getItemCount() {
|
|
22632
|
+
return this.bpTree.getItemCount()
|
|
22633
|
+
}
|
|
22634
|
+
|
|
22635
|
+
/**
|
|
22636
|
+
* Return the chromosome ID for the given name. This is the internal chromosome ID for the parent BB file only.
|
|
22637
|
+
* @param {string} chr - The chromosome name.
|
|
22638
|
+
* @returns {number|null} - The chromosome ID or null if not found.
|
|
22639
|
+
*/
|
|
22640
|
+
async getIdForName(chr) {
|
|
22641
|
+
if (this.nameToId.has(chr)) {
|
|
22642
|
+
return this.nameToId.get(chr)
|
|
22643
|
+
} else {
|
|
22644
|
+
try {
|
|
22645
|
+
const result = await this.bpTree.search(chr);
|
|
22646
|
+
if (result) {
|
|
22647
|
+
const id = result.id;
|
|
22648
|
+
this.nameToId.set(chr, id);
|
|
22649
|
+
return id
|
|
22650
|
+
} else {
|
|
22651
|
+
return
|
|
22652
|
+
}
|
|
22653
|
+
} catch (error) {
|
|
22654
|
+
throw new Error(error)
|
|
22655
|
+
}
|
|
22656
|
+
}
|
|
22657
|
+
}
|
|
22658
|
+
|
|
22659
|
+
/**
|
|
22660
|
+
* Return the chromosome name for the given ID. This is a potentially expensive operation as it involves
|
|
22661
|
+
* walking the tree until the leaf item for the given name is found. Currently it is used in only 2
|
|
22662
|
+
* situations:
|
|
22663
|
+
* (1) decoding features from a bigbed search-by-name query
|
|
22664
|
+
* (2) decoding bigwig data from the whole genome view
|
|
22665
|
+
* @param {number} id
|
|
22666
|
+
* @return {string|null}
|
|
22667
|
+
*/
|
|
22668
|
+
async getNameForId(id) {
|
|
22669
|
+
if (this.idToName.has(id)) {
|
|
22670
|
+
return this.idToName.get(id)
|
|
22671
|
+
} else {
|
|
22672
|
+
const name = await this.searchForName(id);
|
|
22673
|
+
if (name !== null) {
|
|
22674
|
+
this.idToName.set(id, name);
|
|
22675
|
+
return name
|
|
22676
|
+
}
|
|
22677
|
+
}
|
|
22678
|
+
return null
|
|
22679
|
+
}
|
|
22680
|
+
|
|
22681
|
+
/**
|
|
22682
|
+
* Perform a reverse search by traversing the tree starting at the given offset. This is potentially expensive
|
|
22683
|
+
* as it traverses the tree to find the name corresponding to the given ID. It shoud not be used for large trees.
|
|
22684
|
+
*
|
|
22685
|
+
* @param {number} id - The ID to search for.
|
|
22686
|
+
* @returns {string|null} - The name corresponding to the ID, or null if not found.
|
|
22687
|
+
*/
|
|
22688
|
+
async searchForName(id) {
|
|
22689
|
+
|
|
22690
|
+
const reverseSearch = async (offset, id) => {
|
|
22691
|
+
|
|
22692
|
+
const node = await this.bpTree.readTreeNode(offset);
|
|
22693
|
+
|
|
22694
|
+
let found = null;
|
|
22695
|
+
|
|
22696
|
+
if (node.type === 1) {
|
|
22697
|
+
// Leaf node
|
|
22698
|
+
for (const item of node.items) {
|
|
22699
|
+
const key = item.key;
|
|
22700
|
+
const itemId = item.value.id;
|
|
22701
|
+
if (itemId === id) {
|
|
22702
|
+
found = key;
|
|
22703
|
+
}
|
|
22704
|
+
// Cache the name and ID for future lookups
|
|
22705
|
+
this.nameToId.set(key, itemId);
|
|
22706
|
+
this.idToName.set(id, itemId);
|
|
22707
|
+
}
|
|
22708
|
+
return found
|
|
22709
|
+
} else {
|
|
22710
|
+
// Non-leaf node
|
|
22711
|
+
for (const item of node.items) {
|
|
22712
|
+
found = await reverseSearch.call(this, item.offset, id);
|
|
22713
|
+
if (found !== null) {
|
|
22714
|
+
break
|
|
22715
|
+
}
|
|
22716
|
+
}
|
|
22717
|
+
}
|
|
22718
|
+
return found
|
|
22719
|
+
};
|
|
22720
|
+
|
|
22721
|
+
try {
|
|
22722
|
+
return reverseSearch.call(this, this.startOffset + 32, id)
|
|
22723
|
+
} catch (error) {
|
|
22724
|
+
throw new Error(error)
|
|
22725
|
+
}
|
|
22726
|
+
}
|
|
22727
|
+
|
|
22728
|
+
/**
|
|
22729
|
+
* Return an estimated length of the genome, which might be the actual length if the number of contigs is small.
|
|
22730
|
+
* This is only used for calculating a default feature visibility window.
|
|
22731
|
+
*
|
|
22732
|
+
* @return {number}
|
|
22733
|
+
*/
|
|
22734
|
+
async estimateGenomeSize() {
|
|
22735
|
+
try {
|
|
22736
|
+
const runningTotal = {total: 0, count: 0};
|
|
22737
|
+
await this.accumulateSize(this.startOffset + 32, runningTotal, 10000);
|
|
22738
|
+
const itemCount = this.getItemCount();
|
|
22739
|
+
return (itemCount / runningTotal.count) * runningTotal.total
|
|
22740
|
+
|
|
22741
|
+
} catch (error) {
|
|
22742
|
+
console.error("Error estimating genome size", error);
|
|
22743
|
+
return -1
|
|
22744
|
+
}
|
|
22745
|
+
}
|
|
22746
|
+
|
|
22747
|
+
async accumulateSize(offset, runningTotal, maxCount) {
|
|
22748
|
+
|
|
22749
|
+
const node = await this.bpTree.readTreeNode(offset);
|
|
22750
|
+
|
|
22751
|
+
if (node.type === 1) {
|
|
22752
|
+
// Leaf node
|
|
22753
|
+
for (const item of node.items) {
|
|
22754
|
+
const value = item.value;
|
|
22755
|
+
runningTotal.total += value.size;
|
|
22756
|
+
runningTotal.count += 1;
|
|
22757
|
+
}
|
|
22758
|
+
} else {
|
|
22759
|
+
// Non-leaf node. Items are visited in random order to avoid biasing the estimate
|
|
22760
|
+
const shuffledItems = node.items.slice().sort(() => Math.random() - 0.5);
|
|
22761
|
+
for (const item of shuffledItems) {
|
|
22762
|
+
await this.accumulateSize(item.offset, runningTotal, maxCount);
|
|
22763
|
+
if (runningTotal.count > maxCount) {
|
|
22764
|
+
break
|
|
22765
|
+
}
|
|
22766
|
+
}
|
|
22767
|
+
}
|
|
22768
|
+
return runningTotal
|
|
22769
|
+
}
|
|
22770
|
+
|
|
22771
|
+
}
|
|
22772
|
+
|
|
22654
22773
|
/*
|
|
22655
22774
|
* The MIT License (MIT)
|
|
22656
22775
|
*
|
|
@@ -22714,14 +22833,40 @@ class BWReader {
|
|
|
22714
22833
|
async preload() {
|
|
22715
22834
|
const data = await igvxhr.loadArrayBuffer(this.path);
|
|
22716
22835
|
this.loader = new DataBuffer(data);
|
|
22836
|
+
for (let rpTree of this.rpTreeCache.values()) {
|
|
22837
|
+
rpTree.loader = this.loader;
|
|
22838
|
+
}
|
|
22839
|
+
if (this._searchTrees) {
|
|
22840
|
+
for (let bpTree of this._searchTrees) {
|
|
22841
|
+
bpTree.loader = this.loader;
|
|
22842
|
+
}
|
|
22843
|
+
}
|
|
22717
22844
|
}
|
|
22718
22845
|
|
|
22719
|
-
async readWGFeatures(bpPerPixel, windowFunction) {
|
|
22846
|
+
async readWGFeatures(wgChromosomeNames, bpPerPixel, windowFunction) {
|
|
22847
|
+
|
|
22720
22848
|
await this.loadHeader();
|
|
22721
|
-
|
|
22722
|
-
|
|
22723
|
-
|
|
22724
|
-
|
|
22849
|
+
// Convert the logic to JavaScript
|
|
22850
|
+
let minID = Number.MAX_SAFE_INTEGER;
|
|
22851
|
+
let maxID = -1;
|
|
22852
|
+
let chr1;
|
|
22853
|
+
let chr2;
|
|
22854
|
+
|
|
22855
|
+
for (const chr of wgChromosomeNames) {
|
|
22856
|
+
const id = await this.getIdForChr(chr);
|
|
22857
|
+
if (id === null || id === undefined) {
|
|
22858
|
+
continue
|
|
22859
|
+
}
|
|
22860
|
+
if (id < minID) {
|
|
22861
|
+
minID = id;
|
|
22862
|
+
chr1 = chr;
|
|
22863
|
+
}
|
|
22864
|
+
if (id > maxID) {
|
|
22865
|
+
maxID = id;
|
|
22866
|
+
chr2 = chr;
|
|
22867
|
+
}
|
|
22868
|
+
}
|
|
22869
|
+
|
|
22725
22870
|
return this.readFeatures(chr1, 0, chr2, Number.MAX_VALUE, bpPerPixel, windowFunction)
|
|
22726
22871
|
}
|
|
22727
22872
|
|
|
@@ -22732,8 +22877,8 @@ class BWReader {
|
|
|
22732
22877
|
|
|
22733
22878
|
await this.loadHeader();
|
|
22734
22879
|
|
|
22735
|
-
|
|
22736
|
-
|
|
22880
|
+
const chrIdx1 = await this.getIdForChr(chr1);
|
|
22881
|
+
const chrIdx2 = await this.getIdForChr(chr2);
|
|
22737
22882
|
|
|
22738
22883
|
if (chrIdx1 === undefined || chrIdx2 === undefined) {
|
|
22739
22884
|
return []
|
|
@@ -22792,7 +22937,7 @@ class BWReader {
|
|
|
22792
22937
|
} else {
|
|
22793
22938
|
plain = uint8Array;
|
|
22794
22939
|
}
|
|
22795
|
-
decodeFunction.call(this, new DataView(plain.buffer), chrIdx1, bpStart, chrIdx2, bpEnd, features,
|
|
22940
|
+
await decodeFunction.call(this, new DataView(plain.buffer), chrIdx1, bpStart, chrIdx2, bpEnd, features, windowFunction);
|
|
22796
22941
|
}
|
|
22797
22942
|
|
|
22798
22943
|
features.sort(function (a, b) {
|
|
@@ -22809,29 +22954,30 @@ class BWReader {
|
|
|
22809
22954
|
* @param chr
|
|
22810
22955
|
* @returns {Promise<*>}
|
|
22811
22956
|
*/
|
|
22812
|
-
async
|
|
22957
|
+
async getIdForChr(chr) {
|
|
22813
22958
|
|
|
22814
22959
|
if (this.chrAliasTable.has(chr)) {
|
|
22815
22960
|
chr = this.chrAliasTable.get(chr);
|
|
22816
|
-
if (chr
|
|
22961
|
+
if (!chr) {
|
|
22817
22962
|
return undefined
|
|
22818
22963
|
}
|
|
22819
22964
|
}
|
|
22820
22965
|
|
|
22821
|
-
let chrIdx = this.chromTree.
|
|
22966
|
+
let chrIdx = await this.chromTree.getIdForName(chr);
|
|
22822
22967
|
|
|
22823
22968
|
// Try alias
|
|
22824
22969
|
if (chrIdx === undefined && this.genome) {
|
|
22825
22970
|
const aliasRecord = await this.genome.getAliasRecord(chr);
|
|
22826
22971
|
let alias;
|
|
22827
22972
|
if (aliasRecord) {
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22973
|
+
for (let k of Object.keys(aliasRecord)) {
|
|
22974
|
+
if (k === "start" || k === "end") continue
|
|
22975
|
+
alias = aliasRecord[k];
|
|
22976
|
+
if (alias === chr) continue // Already tried this
|
|
22977
|
+
chrIdx = await this.chromTree.getIdForName(alias);
|
|
22978
|
+
if (chrIdx !== undefined) {
|
|
22979
|
+
break
|
|
22980
|
+
}
|
|
22835
22981
|
}
|
|
22836
22982
|
}
|
|
22837
22983
|
this.chrAliasTable.set(chr, alias); // alias may be undefined => no alias exists. Setting prevents repeated attempts
|
|
@@ -22918,7 +23064,8 @@ class BWReader {
|
|
|
22918
23064
|
this.header.extraIndexOffsets.length > 0) {
|
|
22919
23065
|
this._searchTrees = [];
|
|
22920
23066
|
for (let offset of this.header.extraIndexOffsets) {
|
|
22921
|
-
const
|
|
23067
|
+
const type = undefined;
|
|
23068
|
+
const bpTree = await BPTree.loadBpTree(this.path, this.config, offset, type, this.loader);
|
|
22922
23069
|
this._searchTrees.push(bpTree);
|
|
22923
23070
|
}
|
|
22924
23071
|
}
|
|
@@ -23035,15 +23182,13 @@ class BWReader {
|
|
|
23035
23182
|
this.totalSummary = new BWTotalSummary(extHeaderParser);
|
|
23036
23183
|
}
|
|
23037
23184
|
|
|
23038
|
-
|
|
23039
|
-
|
|
23040
|
-
this.chromTree = await this.#readChromTree(header.chromTreeOffset, bufferSize);
|
|
23041
|
-
this.chrNames = new Set(this.chromTree.idToName);
|
|
23185
|
+
this.chromTree = new ChromTree(this.path, this.config, header.chromTreeOffset, this.loader);
|
|
23186
|
+
await this.chromTree.init();
|
|
23042
23187
|
|
|
23043
23188
|
// Estimate feature density from dataCount (bigbed only)
|
|
23044
|
-
if("bigbed" === this.type) {
|
|
23189
|
+
if ("bigbed" === this.type) {
|
|
23045
23190
|
const dataCount = await this.#readDataCount(header.fullDataOffset);
|
|
23046
|
-
this.featureDensity = dataCount / this.chromTree.
|
|
23191
|
+
this.featureDensity = dataCount / await this.chromTree.estimateGenomeSize();
|
|
23047
23192
|
}
|
|
23048
23193
|
|
|
23049
23194
|
this.header = header;
|
|
@@ -23067,38 +23212,6 @@ class BWReader {
|
|
|
23067
23212
|
return binaryParser.getInt()
|
|
23068
23213
|
}
|
|
23069
23214
|
|
|
23070
|
-
/**
|
|
23071
|
-
* Used when the chromTreeOffset is > fullDataOffset, that is when the chrom tree is not in the initial chunk
|
|
23072
|
-
* read for parsing the header. We know the start position, but not the total size of the chrom tree
|
|
23073
|
-
*
|
|
23074
|
-
* @returns {Promise<void>}
|
|
23075
|
-
*/
|
|
23076
|
-
async #readChromTree(chromTreeOffset, bufferSize) {
|
|
23077
|
-
|
|
23078
|
-
let size = bufferSize;
|
|
23079
|
-
const load = async () => {
|
|
23080
|
-
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {
|
|
23081
|
-
range: {
|
|
23082
|
-
start: chromTreeOffset,
|
|
23083
|
-
size: size
|
|
23084
|
-
}
|
|
23085
|
-
}));
|
|
23086
|
-
const binaryParser = new BinaryParser$1(new DataView(data), this.littleEndian);
|
|
23087
|
-
return ChromTree.parseTree(binaryParser, chromTreeOffset, this.genome)
|
|
23088
|
-
};
|
|
23089
|
-
|
|
23090
|
-
let error;
|
|
23091
|
-
while (size < 1000000) {
|
|
23092
|
-
try {
|
|
23093
|
-
const chromTree = await load();
|
|
23094
|
-
return chromTree
|
|
23095
|
-
} catch (e) {
|
|
23096
|
-
error = e;
|
|
23097
|
-
size *= 2;
|
|
23098
|
-
}
|
|
23099
|
-
}
|
|
23100
|
-
throw (error)
|
|
23101
|
-
}
|
|
23102
23215
|
|
|
23103
23216
|
async loadExtendedHeader(offset) {
|
|
23104
23217
|
|
|
@@ -23154,7 +23267,7 @@ class BWReader {
|
|
|
23154
23267
|
if (rpTree) {
|
|
23155
23268
|
return rpTree
|
|
23156
23269
|
} else {
|
|
23157
|
-
rpTree = new RPTree(this.path, this.config, offset);
|
|
23270
|
+
rpTree = new RPTree(this.path, this.config, offset, this.loader);
|
|
23158
23271
|
await rpTree.init();
|
|
23159
23272
|
this.rpTreeCache.set(offset, rpTree);
|
|
23160
23273
|
return rpTree
|
|
@@ -23194,9 +23307,7 @@ class BWReader {
|
|
|
23194
23307
|
const plain = (this.header.uncompressBuffSize > 0) ? inflate_1$3(uint8Array) : uint8Array;
|
|
23195
23308
|
const decodeFunction = getBedDataDecoder.call(this);
|
|
23196
23309
|
const features = [];
|
|
23197
|
-
decodeFunction.call(this, new DataView(plain.buffer), 0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER,
|
|
23198
|
-
features, this.chromTree.idToName);
|
|
23199
|
-
|
|
23310
|
+
await decodeFunction.call(this, new DataView(plain.buffer), 0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, features);
|
|
23200
23311
|
return features
|
|
23201
23312
|
|
|
23202
23313
|
}
|
|
@@ -23264,7 +23375,7 @@ function zoomLevelForScale(bpPerPixel, zoomLevelHeaders) {
|
|
|
23264
23375
|
}
|
|
23265
23376
|
|
|
23266
23377
|
|
|
23267
|
-
function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray,
|
|
23378
|
+
async function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, windowFunction, littleEndian) {
|
|
23268
23379
|
|
|
23269
23380
|
const binaryParser = new BinaryParser$1(data, littleEndian);
|
|
23270
23381
|
const chromId = binaryParser.getInt();
|
|
@@ -23305,7 +23416,7 @@ function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, chr
|
|
|
23305
23416
|
else if (chromId > chrIdx2 || (chromId === chrIdx2 && chromStart >= bpEnd)) break
|
|
23306
23417
|
|
|
23307
23418
|
if (Number.isFinite(value)) {
|
|
23308
|
-
const chr =
|
|
23419
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23309
23420
|
featureArray.push({chr: chr, start: chromStart, end: chromEnd, value: value});
|
|
23310
23421
|
}
|
|
23311
23422
|
}
|
|
@@ -23316,12 +23427,13 @@ function getBedDataDecoder() {
|
|
|
23316
23427
|
|
|
23317
23428
|
const minSize = 3 * 4 + 1; // Minimum # of bytes required for a bed record
|
|
23318
23429
|
const decoder = getDecoder(this.header.definedFieldCount, this.header.fieldCount, this.autoSql, this.format);
|
|
23319
|
-
return function (data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray
|
|
23320
|
-
|
|
23430
|
+
return async function (data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray) {
|
|
23431
|
+
|
|
23432
|
+
const binaryParser = new BinaryParser$1(data, this.littleEndian);
|
|
23321
23433
|
while (binaryParser.remLength() >= minSize) {
|
|
23322
23434
|
|
|
23323
23435
|
const chromId = binaryParser.getInt();
|
|
23324
|
-
const chr =
|
|
23436
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23325
23437
|
const chromStart = binaryParser.getInt();
|
|
23326
23438
|
const chromEnd = binaryParser.getInt();
|
|
23327
23439
|
const rest = binaryParser.getString();
|
|
@@ -23338,8 +23450,7 @@ function getBedDataDecoder() {
|
|
|
23338
23450
|
}
|
|
23339
23451
|
}
|
|
23340
23452
|
|
|
23341
|
-
|
|
23342
|
-
function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, chrDict, windowFunction, littleEndian) {
|
|
23453
|
+
async function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, windowFunction, littleEndian) {
|
|
23343
23454
|
|
|
23344
23455
|
const binaryParser = new BinaryParser$1(data, littleEndian);
|
|
23345
23456
|
const minSize = 8 * 4; // Minimum # of bytes required for a zoom record
|
|
@@ -23371,7 +23482,7 @@ function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, ch
|
|
|
23371
23482
|
|
|
23372
23483
|
|
|
23373
23484
|
if (Number.isFinite(value)) {
|
|
23374
|
-
const chr =
|
|
23485
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23375
23486
|
featureArray.push({chr: chr, start: chromStart, end: chromEnd, value: value});
|
|
23376
23487
|
|
|
23377
23488
|
|
|
@@ -23456,7 +23567,8 @@ class BWSource extends BaseFeatureSource {
|
|
|
23456
23567
|
|
|
23457
23568
|
let features;
|
|
23458
23569
|
if ("all" === chr.toLowerCase()) {
|
|
23459
|
-
|
|
23570
|
+
const wgChromosomeNames = this.genome.wgChromosomeNames;
|
|
23571
|
+
features = isBigWig && wgChromosomeNames? await this.getWGValues(wgChromosomeNames, windowFunction, bpPerPixel) : [];
|
|
23460
23572
|
} else {
|
|
23461
23573
|
features = await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
|
|
23462
23574
|
}
|
|
@@ -23480,15 +23592,14 @@ class BWSource extends BaseFeatureSource {
|
|
|
23480
23592
|
|
|
23481
23593
|
}
|
|
23482
23594
|
|
|
23483
|
-
async getWGValues(windowFunction, bpPerPixel) {
|
|
23595
|
+
async getWGValues(wgChromosomeNames, windowFunction, bpPerPixel) {
|
|
23484
23596
|
|
|
23485
23597
|
const genome = this.genome;
|
|
23486
23598
|
const cached = this.#wgValues[windowFunction];
|
|
23487
23599
|
if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
|
|
23488
23600
|
return cached.values
|
|
23489
23601
|
} else {
|
|
23490
|
-
|
|
23491
|
-
const features = await this.reader.readWGFeatures(bpPerPixel, windowFunction);
|
|
23602
|
+
const features = await this.reader.readWGFeatures(wgChromosomeNames, bpPerPixel, windowFunction);
|
|
23492
23603
|
let wgValues = [];
|
|
23493
23604
|
for (let f of features) {
|
|
23494
23605
|
const chr = f.chr;
|
|
@@ -28283,7 +28394,7 @@ async function createBlatTrack({sequence, browser, name, title}) {
|
|
|
28283
28394
|
features
|
|
28284
28395
|
};
|
|
28285
28396
|
|
|
28286
|
-
const track = await browser.
|
|
28397
|
+
const track = (await browser.loadTrackList([trackConfig]))[0];
|
|
28287
28398
|
track.openTableView();
|
|
28288
28399
|
|
|
28289
28400
|
} catch (e) {
|
|
@@ -30335,109 +30446,578 @@ function convertToHubURL(accension) {
|
|
|
30335
30446
|
}
|
|
30336
30447
|
}
|
|
30337
30448
|
|
|
30338
|
-
|
|
30339
|
-
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
30340
|
-
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
30341
|
-
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
30342
|
-
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
30343
|
-
*/
|
|
30449
|
+
const parentOverrideProperties = new Set(["visibility", "priority", "group"]);
|
|
30344
30450
|
|
|
30451
|
+
const nonInheritableProperties = new Set([
|
|
30452
|
+
"track", "type", "shortLabel", "longLabel", "bigDataUrl",
|
|
30453
|
+
"parent", "superTrack", "priority", "view", "compositeContainer", "compositeTrack"
|
|
30454
|
+
]);
|
|
30345
30455
|
|
|
30346
|
-
class Hub {
|
|
30347
30456
|
|
|
30348
|
-
|
|
30349
|
-
|
|
30350
|
-
|
|
30457
|
+
class Stanza {
|
|
30458
|
+
|
|
30459
|
+
properties = new Map()
|
|
30460
|
+
|
|
30461
|
+
constructor(type, name) {
|
|
30462
|
+
this.type = type;
|
|
30463
|
+
this.name = name;
|
|
30464
|
+
}
|
|
30465
|
+
|
|
30466
|
+
setProperty(key, value) {
|
|
30467
|
+
this.properties.set(key, value);
|
|
30468
|
+
}
|
|
30469
|
+
|
|
30470
|
+
getProperty(key) {
|
|
30471
|
+
if (this.properties.has("noInherit")) {
|
|
30472
|
+
return this.properties.get(key)
|
|
30473
|
+
} else if (this.parent && parentOverrideProperties.has(key) && this.parent.hasProperty(key)) {
|
|
30474
|
+
return this.parent.getProperty(key)
|
|
30475
|
+
} else if (this.properties.has(key)) {
|
|
30476
|
+
return this.properties.get(key)
|
|
30477
|
+
} else if (this.parent && !nonInheritableProperties.has(key)) {
|
|
30478
|
+
return this.parent.getProperty(key)
|
|
30479
|
+
} else {
|
|
30480
|
+
return undefined
|
|
30481
|
+
}
|
|
30482
|
+
}
|
|
30483
|
+
|
|
30484
|
+
hasProperty(key) {
|
|
30485
|
+
return this.getProperty(key) !== null && this.getProperty(key) !== undefined
|
|
30486
|
+
}
|
|
30487
|
+
|
|
30488
|
+
hasOwnProperty(key) {
|
|
30489
|
+
return this.properties.has(key)
|
|
30490
|
+
}
|
|
30491
|
+
|
|
30492
|
+
getOwnProperty(key) {
|
|
30493
|
+
return this.properties.get(key)
|
|
30494
|
+
}
|
|
30495
|
+
|
|
30496
|
+
removeProperty(key) {
|
|
30497
|
+
this.properties.delete(key);
|
|
30498
|
+
}
|
|
30499
|
+
|
|
30500
|
+
get format() {
|
|
30501
|
+
const type = this.getProperty("type");
|
|
30502
|
+
if (type) {
|
|
30503
|
+
// Trim extra bed qualifiers (e.g. bigBed + 4)
|
|
30504
|
+
return firstWord$1(type)
|
|
30505
|
+
}
|
|
30506
|
+
return undefined // unknown type
|
|
30507
|
+
}
|
|
30508
|
+
|
|
30509
|
+
/**
|
|
30510
|
+
* IGV display mode
|
|
30511
|
+
*/
|
|
30512
|
+
get displayMode() {
|
|
30513
|
+
let viz = this.getProperty("visibility");
|
|
30514
|
+
if (!viz) {
|
|
30515
|
+
return "COLLAPSED"
|
|
30516
|
+
} else {
|
|
30517
|
+
viz = viz.toLowerCase();
|
|
30518
|
+
switch (viz) {
|
|
30519
|
+
case "dense":
|
|
30520
|
+
return "COLLAPSED"
|
|
30521
|
+
case "pack":
|
|
30522
|
+
return "EXPANDED"
|
|
30523
|
+
case "squish":
|
|
30524
|
+
return "SQUISHED"
|
|
30525
|
+
default:
|
|
30526
|
+
return "COLLAPSED"
|
|
30527
|
+
}
|
|
30528
|
+
}
|
|
30529
|
+
}
|
|
30530
|
+
}
|
|
30531
|
+
|
|
30532
|
+
|
|
30533
|
+
function firstWord$1(str) {
|
|
30534
|
+
const idx = str.indexOf(' ');
|
|
30535
|
+
return idx > 0 ? str.substring(0, idx) : str
|
|
30536
|
+
}
|
|
30537
|
+
|
|
30538
|
+
class TrackConfigContainer {
|
|
30539
|
+
constructor(name, label, priority, defaultOpen) {
|
|
30540
|
+
this.name = name;
|
|
30541
|
+
this.priority = priority;
|
|
30542
|
+
this.label = label;
|
|
30543
|
+
this.defaultOpen = defaultOpen;
|
|
30544
|
+
this.tracks = [];
|
|
30545
|
+
this.children = [];
|
|
30546
|
+
}
|
|
30547
|
+
|
|
30548
|
+
isEmpty() {
|
|
30549
|
+
return this.tracks.length === 0 &&
|
|
30550
|
+
(!this.children || this.children.length === 0 || this.children.every(child => child.isEmpty()));
|
|
30551
|
+
}
|
|
30552
|
+
|
|
30553
|
+
map(callback) {
|
|
30554
|
+
this.tracks.forEach(callback);
|
|
30555
|
+
this.children.forEach(child => child.map(callback));
|
|
30556
|
+
}
|
|
30557
|
+
|
|
30558
|
+
findTracks(filter) {
|
|
30559
|
+
const found = [];
|
|
30560
|
+
this._find(found, filter);
|
|
30561
|
+
return found;
|
|
30562
|
+
}
|
|
30563
|
+
|
|
30564
|
+
_find(found, filter) {
|
|
30565
|
+
this.tracks.forEach(track => {
|
|
30566
|
+
if (filter(track)) {
|
|
30567
|
+
found.push(track);
|
|
30568
|
+
}
|
|
30569
|
+
});
|
|
30570
|
+
this.children.forEach(child => child._find(found, filter));
|
|
30571
|
+
}
|
|
30572
|
+
|
|
30573
|
+
countTracks() {
|
|
30574
|
+
return this.tracks.length + this.children.reduce((count, child) => count + child.countTracks(), 0);
|
|
30575
|
+
}
|
|
30576
|
+
|
|
30577
|
+
countSelectedTracks() {
|
|
30578
|
+
const selectedCount = this.tracks.filter(track => track.visible).length;
|
|
30579
|
+
return selectedCount + this.children.reduce((count, child) => count + child.countSelectedTracks(), 0);
|
|
30580
|
+
}
|
|
30581
|
+
|
|
30582
|
+
trim() {
|
|
30583
|
+
this.children = this.children.filter(child => !child.isEmpty());
|
|
30584
|
+
this.children.forEach(child => child.trim());
|
|
30585
|
+
}
|
|
30586
|
+
|
|
30587
|
+
setTrackVisibility(loadedTrackPaths) {
|
|
30588
|
+
this.tracks.forEach(track => {
|
|
30589
|
+
track.visible = loadedTrackPaths.has(track.url);
|
|
30590
|
+
});
|
|
30591
|
+
this.children.forEach(child => child.setTrackVisibility(loadedTrackPaths));
|
|
30592
|
+
}
|
|
30593
|
+
}
|
|
30594
|
+
|
|
30595
|
+
const supportedTypes = new Set([
|
|
30596
|
+
"bigbed", "bigwig", "biggenepred", "vcftabix", "refgene",
|
|
30597
|
+
"bam", "sampleinfo", "vcf.list", "ucscsnp", "bed", "tdf", "gff", "gff3", "gtf", "vcf", "vcfphasedtrio",
|
|
30598
|
+
"bigdbsnp", "rmask", "genepred", "wig", "bedgraph", "interact", "broadpeak", "narrowpeak", "gappedpeak",
|
|
30599
|
+
"gistic", "seg", "mut, bigrmsk"
|
|
30600
|
+
]);
|
|
30601
|
+
|
|
30602
|
+
const filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps",
|
|
30603
|
+
"cpgIslandExtUnmasked", "windowMasker"]);
|
|
30604
|
+
|
|
30605
|
+
class TrackDbHub {
|
|
30606
|
+
|
|
30607
|
+
constructor(trackStanzas, groupStanzas) {
|
|
30608
|
+
this.groupStanzas = groupStanzas;
|
|
30609
|
+
this.trackStanzas = trackStanzas;
|
|
30610
|
+
}
|
|
30611
|
+
|
|
30612
|
+
findCytobandURL() {
|
|
30613
|
+
for (const t of this.trackStanzas) {
|
|
30614
|
+
if (t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) {
|
|
30615
|
+
return t.getProperty("bigDataUrl")
|
|
30616
|
+
}
|
|
30617
|
+
}
|
|
30618
|
+
}
|
|
30619
|
+
|
|
30620
|
+
getSupportedTrackCount() {
|
|
30621
|
+
let count = 0;
|
|
30622
|
+
for (const t of this.trackStanzas) {
|
|
30623
|
+
if (!filterTracks.has(t.name) &&
|
|
30624
|
+
t.hasProperty("bigDataUrl") &&
|
|
30625
|
+
t.format &&
|
|
30626
|
+
supportedTypes.has(t.format.toLowerCase())) {
|
|
30627
|
+
count++;
|
|
30628
|
+
}
|
|
30629
|
+
}
|
|
30630
|
+
return count
|
|
30631
|
+
}
|
|
30351
30632
|
|
|
30352
|
-
|
|
30633
|
+
getGroupedTrackConfigurations() {
|
|
30353
30634
|
|
|
30354
|
-
|
|
30355
|
-
|
|
30356
|
-
|
|
30357
|
-
|
|
30358
|
-
|
|
30359
|
-
const
|
|
30360
|
-
|
|
30361
|
-
|
|
30362
|
-
|
|
30635
|
+
if (!this.groupTrackConfigs) {
|
|
30636
|
+
this.groupTrackConfigs = [];
|
|
30637
|
+
const trackContainers = new Map();
|
|
30638
|
+
|
|
30639
|
+
// create a container for tracks with no parent
|
|
30640
|
+
const nullContainer = new TrackConfigContainer('', '', 0, true);
|
|
30641
|
+
this.groupTrackConfigs.push(nullContainer);
|
|
30642
|
+
|
|
30643
|
+
const hasGroups = this.groupStanzas && this.groupStanzas.length > 0;
|
|
30644
|
+
if (hasGroups) {
|
|
30645
|
+
for (const groupStanza of this.groupStanzas) {
|
|
30646
|
+
const name = groupStanza.getProperty("name");
|
|
30647
|
+
const defaultOpen = groupStanza.getProperty("defaultIsClosed") === "0";
|
|
30648
|
+
const priority = groupStanza.hasProperty("priority") ? getPriority(groupStanza.getProperty("priority")) : Number.MAX_SAFE_INTEGER - 1;
|
|
30649
|
+
const container = new TrackConfigContainer(name, groupStanza.getProperty("label"), priority, defaultOpen);
|
|
30650
|
+
trackContainers.set(name, container);
|
|
30651
|
+
this.groupTrackConfigs.push(container);
|
|
30652
|
+
}
|
|
30363
30653
|
}
|
|
30364
30654
|
|
|
30365
|
-
|
|
30366
|
-
|
|
30367
|
-
|
|
30368
|
-
|
|
30369
|
-
|
|
30370
|
-
|
|
30371
|
-
|
|
30655
|
+
for (let s of this.trackStanzas) {
|
|
30656
|
+
|
|
30657
|
+
const isContainer = (s.hasOwnProperty("superTrack") && !s.hasOwnProperty("bigDataUrl")) ||
|
|
30658
|
+
s.hasOwnProperty("compositeTrack") || s.hasOwnProperty("view") ||
|
|
30659
|
+
(s.hasOwnProperty("container") && s.getOwnProperty("container").equals("multiWig"));
|
|
30660
|
+
|
|
30661
|
+
// Find parent, if any. "group" containers can be implicit, all other types should be explicitly
|
|
30662
|
+
// defined before their children
|
|
30663
|
+
let parent;
|
|
30664
|
+
|
|
30665
|
+
if (s.hasOwnProperty("parent")) {
|
|
30666
|
+
parent = trackContainers.get(s.getOwnProperty("parent"));
|
|
30667
|
+
}
|
|
30668
|
+
|
|
30669
|
+
if (!parent && hasGroups && s.hasProperty("group")) {
|
|
30670
|
+
const groupName = s.getProperty("group");
|
|
30671
|
+
if (trackContainers.has(groupName)) {
|
|
30672
|
+
parent = trackContainers.get(groupName);
|
|
30673
|
+
} else {
|
|
30674
|
+
const container = new TrackConfigContainer(groupName, groupName, 1000, true);
|
|
30675
|
+
trackContainers.set(groupName, container);
|
|
30676
|
+
this.groupTrackConfigs.push(container);
|
|
30677
|
+
parent = container;
|
|
30678
|
+
}
|
|
30679
|
+
}
|
|
30680
|
+
|
|
30681
|
+
if (isContainer) {
|
|
30682
|
+
|
|
30683
|
+
const name = s.getProperty("track");
|
|
30684
|
+
const priority = s.hasProperty("priority") ? getPriority(s.getProperty("priority")) : Number.MAX_SAFE_INTEGER - 1;
|
|
30685
|
+
const defaultOpen = s.getProperty("defaultIsClosed") === "0";
|
|
30686
|
+
const longLabel = s.getOwnProperty("longLabel");
|
|
30687
|
+
const label = longLabel && longLabel.length < 50 ? longLabel : s.getOwnProperty("shortLabel");
|
|
30688
|
+
const container = new TrackConfigContainer(name, label, priority, defaultOpen);
|
|
30689
|
+
|
|
30690
|
+
if (trackContainers.has(name)) {
|
|
30691
|
+
throw new Error(`Duplicate track container: ${name}`)
|
|
30692
|
+
}
|
|
30693
|
+
trackContainers.set(name, container);
|
|
30694
|
+
|
|
30695
|
+
if (parent) {
|
|
30696
|
+
parent.children.push(container);
|
|
30697
|
+
} else {
|
|
30698
|
+
// No parent or a superTrack => promote to top level
|
|
30699
|
+
this.groupTrackConfigs.push(container);
|
|
30700
|
+
}
|
|
30701
|
+
} else if (!filterTracks.has(s.name) &&
|
|
30702
|
+
s.hasProperty("bigDataUrl") &&
|
|
30703
|
+
s.format &&
|
|
30704
|
+
supportedTypes.has(s.format.toLowerCase())) {
|
|
30705
|
+
|
|
30706
|
+
const trackConfig = this.#getTrackConfig(s);
|
|
30707
|
+
if (parent) {
|
|
30708
|
+
parent.tracks.push(trackConfig);
|
|
30709
|
+
} else {
|
|
30710
|
+
nullContainer.tracks.push(trackConfig);
|
|
30711
|
+
}
|
|
30372
30712
|
}
|
|
30373
30713
|
}
|
|
30714
|
+
|
|
30374
30715
|
}
|
|
30375
30716
|
|
|
30376
|
-
//
|
|
30377
|
-
|
|
30378
|
-
|
|
30379
|
-
// if ("include" === s.type) {
|
|
30380
|
-
// const includeStanzas = await loadStanzas(baseURL + s.getProperty("include"))
|
|
30381
|
-
// for (s of includeStanzas) {
|
|
30382
|
-
// s.setProperty("visibility", "hide")
|
|
30383
|
-
// stanzas.push(s)
|
|
30384
|
-
// }
|
|
30385
|
-
// }
|
|
30386
|
-
// }
|
|
30717
|
+
// Filter empty groups and sort
|
|
30718
|
+
this.groupTrackConfigs.forEach(c => c.trim());
|
|
30719
|
+
this.groupTrackConfigs = this.groupTrackConfigs.filter(t => !t.isEmpty());
|
|
30387
30720
|
|
|
30388
|
-
|
|
30721
|
+
this.groupTrackConfigs.sort((a, b) => a.priority - b.priority);
|
|
30722
|
+
return this.groupTrackConfigs
|
|
30389
30723
|
}
|
|
30390
30724
|
|
|
30391
|
-
|
|
30725
|
+
/**
|
|
30726
|
+
* Return an array of igv track config objects that satisfy the filter
|
|
30727
|
+
*/
|
|
30728
|
+
#getTracksConfigs(filter) {
|
|
30729
|
+
return this.trackStanzas.filter(t => {
|
|
30730
|
+
return supportedTypes.has(t.format) && t.hasProperty("bigDataUrl") && (!filter || filter(t))
|
|
30731
|
+
})
|
|
30732
|
+
.map(t => this.#getTrackConfig(t))
|
|
30733
|
+
}
|
|
30392
30734
|
|
|
30393
|
-
this.url = url;
|
|
30394
30735
|
|
|
30395
|
-
|
|
30396
|
-
|
|
30736
|
+
/** example
|
|
30737
|
+
* track gc5Base
|
|
30738
|
+
* shortLabel GC Percent
|
|
30739
|
+
* longLabel GC Percent in 5-Base Windows
|
|
30740
|
+
* group map
|
|
30741
|
+
* visibility full
|
|
30742
|
+
* autoScale Off
|
|
30743
|
+
* maxHeightPixels 128:36:16
|
|
30744
|
+
* graphTypeDefault Bar
|
|
30745
|
+
* gridDefault OFF
|
|
30746
|
+
* windowingFunction Mean
|
|
30747
|
+
* color 0,0,0
|
|
30748
|
+
* altColor 128,128,128
|
|
30749
|
+
* viewLimits 30:70
|
|
30750
|
+
* type bigWig 0 100
|
|
30751
|
+
* bigDataUrl bbi/GCA_011100615.1_Macaca_fascicularis_6.0.gc5Base.bw
|
|
30752
|
+
* html html/GCA_011100615.1_Macaca_fascicularis_6.0.gc5Base
|
|
30753
|
+
* @param t
|
|
30754
|
+
*/
|
|
30755
|
+
#getTrackConfig(t) {
|
|
30756
|
+
|
|
30757
|
+
const format = t.format;
|
|
30397
30758
|
|
|
30398
|
-
|
|
30399
|
-
|
|
30400
|
-
|
|
30401
|
-
|
|
30402
|
-
|
|
30759
|
+
const config = {
|
|
30760
|
+
"id": t.getProperty("track"),
|
|
30761
|
+
"name": t.getProperty("shortLabel"),
|
|
30762
|
+
"format": format,
|
|
30763
|
+
"url": t.getProperty("bigDataUrl"),
|
|
30764
|
+
"displayMode": t.displayMode,
|
|
30765
|
+
};
|
|
30766
|
+
|
|
30767
|
+
if ("vcfTabix" === format) {
|
|
30768
|
+
config.indexURL = config.url + ".tbi";
|
|
30403
30769
|
}
|
|
30404
|
-
|
|
30405
|
-
|
|
30770
|
+
|
|
30771
|
+
if (t.hasProperty("longLabel") && t.hasProperty("html")) {
|
|
30772
|
+
if (config.description) config.description += "<br/>";
|
|
30773
|
+
config.description =
|
|
30774
|
+
`<a target="_blank" href="${t.getProperty("html")}">${t.getProperty("longLabel")}</a>`;
|
|
30775
|
+
} else if (t.hasProperty("longLabel")) {
|
|
30776
|
+
config.description = t.getProperty("longLabel");
|
|
30406
30777
|
}
|
|
30407
|
-
|
|
30408
|
-
|
|
30778
|
+
|
|
30779
|
+
if (t.hasProperty("autoScale")) {
|
|
30780
|
+
config.autoscale = t.getProperty("autoScale").toLowerCase() === "on";
|
|
30409
30781
|
}
|
|
30782
|
+
if (t.hasProperty("maxHeightPixels")) {
|
|
30783
|
+
const tokens = t.getProperty("maxHeightPixels").split(":");
|
|
30784
|
+
config.maxHeight = Number.parseInt(tokens[0]);
|
|
30785
|
+
config.height = Number.parseInt(tokens[1]);
|
|
30786
|
+
config.minHeight = Number.parseInt(tokens[2]);
|
|
30787
|
+
}
|
|
30788
|
+
// TODO -- graphTypeDefault
|
|
30789
|
+
// TODO -- windowingFunction
|
|
30790
|
+
if (t.hasProperty("color")) {
|
|
30791
|
+
const c = t.getProperty("color");
|
|
30792
|
+
config.color = c.indexOf(",") > 0 ? `rgb(${c})` : c;
|
|
30793
|
+
}
|
|
30794
|
+
if (t.hasProperty("altColor")) {
|
|
30795
|
+
const c = t.getProperty("altColor");
|
|
30796
|
+
config.altColor = c.indexOf(",") > 0 ? `rgb(${c})` : c;
|
|
30797
|
+
}
|
|
30798
|
+
if (t.hasProperty("viewLimits")) {
|
|
30799
|
+
const tokens = t.getProperty("viewLimits").split(":");
|
|
30800
|
+
let min, max;
|
|
30801
|
+
if (tokens.length > 1) {
|
|
30802
|
+
min = Number.parseInt(tokens[0]);
|
|
30803
|
+
max = Number.parseInt(tokens[1]);
|
|
30804
|
+
}
|
|
30805
|
+
if (Number.isNaN(max) || Number.isNaN(min)) {
|
|
30806
|
+
console.warn(`Unexpected viewLimits value in track line: ${properties["viewLimits"]}`);
|
|
30807
|
+
} else {
|
|
30808
|
+
config.min = min;
|
|
30809
|
+
config.max = max;
|
|
30810
|
+
}
|
|
30410
30811
|
|
|
30411
|
-
|
|
30412
|
-
if ("
|
|
30413
|
-
|
|
30414
|
-
|
|
30415
|
-
|
|
30812
|
+
}
|
|
30813
|
+
if (t.hasProperty("itemRgb")) ;
|
|
30814
|
+
if ("hide" === t.getProperty("visibility")) {
|
|
30815
|
+
// TODO -- this not supported yet
|
|
30816
|
+
config.visible = false;
|
|
30817
|
+
}
|
|
30818
|
+
if (t.hasProperty("url")) {
|
|
30819
|
+
config.infoURL = t.getProperty("url");
|
|
30820
|
+
}
|
|
30821
|
+
if (t.hasProperty("searchIndex")) {
|
|
30822
|
+
config.searchIndex = t.getProperty("searchIndex");
|
|
30823
|
+
}
|
|
30824
|
+
if (t.hasProperty("searchTrix")) {
|
|
30825
|
+
config.trixURL = t.getProperty("searchTrix");
|
|
30826
|
+
}
|
|
30827
|
+
if (t.hasProperty("html")) {
|
|
30828
|
+
config.html = t.getProperty("html");
|
|
30829
|
+
}
|
|
30830
|
+
|
|
30831
|
+
if (t.hasProperty("group")) {
|
|
30832
|
+
config._group = t.getProperty("group");
|
|
30833
|
+
if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
|
|
30834
|
+
const nextPriority = this.groupPriorityMap.get(config._group) + 1;
|
|
30835
|
+
config.order = nextPriority;
|
|
30836
|
+
this.groupPriorityMap.set(config._group, nextPriority);
|
|
30837
|
+
}
|
|
30838
|
+
}
|
|
30839
|
+
|
|
30840
|
+
if (t.hasProperty("metadata")) {
|
|
30841
|
+
config.attributes = parseMetadata(t.getProperty("metadata"));
|
|
30842
|
+
}
|
|
30843
|
+
|
|
30844
|
+
if (t.hasProperty("maxWindowToDraw")) {
|
|
30845
|
+
let maxWindowToDraw = parseInt(t.getProperty("maxWindowToDraw"), 10);
|
|
30846
|
+
if (maxWindowToDraw > Number.MAX_SAFE_INTEGER) {
|
|
30847
|
+
maxWindowToDraw = Number.MAX_SAFE_INTEGER;
|
|
30848
|
+
}
|
|
30849
|
+
config.visibilityWindow = maxWindowToDraw;
|
|
30416
30850
|
}
|
|
30417
30851
|
|
|
30418
|
-
//
|
|
30419
|
-
|
|
30420
|
-
|
|
30421
|
-
if (
|
|
30422
|
-
|
|
30852
|
+
// IGV does not support "maxWindowCoverage" in the same way as UCSC. Use to limit visibility window
|
|
30853
|
+
if (t.hasProperty("maxWindowCoverage")) {
|
|
30854
|
+
let maxWindowToDraw = parseInt(t.getProperty("maxWindowCoverage"), 10);
|
|
30855
|
+
if (maxWindowToDraw > Number.MAX_SAFE_INTEGER) {
|
|
30856
|
+
maxWindowToDraw = Number.MAX_SAFE_INTEGER;
|
|
30423
30857
|
}
|
|
30858
|
+
config.visibilityWindow = maxWindowToDraw;
|
|
30424
30859
|
}
|
|
30425
30860
|
|
|
30426
|
-
|
|
30427
|
-
|
|
30428
|
-
|
|
30429
|
-
|
|
30430
|
-
|
|
30431
|
-
|
|
30861
|
+
return config
|
|
30862
|
+
}
|
|
30863
|
+
}
|
|
30864
|
+
|
|
30865
|
+
function htmlText(html) {
|
|
30866
|
+
// Assumes a pattern like <span style="color:#C58DAA">Digestive</span>
|
|
30867
|
+
const idx1 = html.indexOf('>');
|
|
30868
|
+
const idx2 = html.indexOf('<', idx1);
|
|
30869
|
+
if (idx1 > 0 && idx2 > idx1) {
|
|
30870
|
+
return html.substring(idx1 + 1, idx2)
|
|
30871
|
+
} else {
|
|
30872
|
+
return html
|
|
30873
|
+
}
|
|
30874
|
+
}
|
|
30875
|
+
|
|
30876
|
+
/**
|
|
30877
|
+
* Return the priority for the group. The priority format is uncertain, but extends to at least 2 levels (e.g. 3.4).
|
|
30878
|
+
* Ignore levels > 3
|
|
30879
|
+
*
|
|
30880
|
+
* @param {string} priorityString Priority as a string (e.g. 3.4)
|
|
30881
|
+
* @return {number} A priority as an integer
|
|
30882
|
+
*/
|
|
30883
|
+
function getPriority(priorityString) {
|
|
30884
|
+
try {
|
|
30885
|
+
const tokens = priorityString.trim().split(".");
|
|
30886
|
+
let p = parseInt(tokens[0], 10) * 100;
|
|
30887
|
+
if (tokens.length > 1) {
|
|
30888
|
+
p += parseInt(tokens[1], 10) * 10;
|
|
30889
|
+
}
|
|
30890
|
+
if (tokens.length > 2) {
|
|
30891
|
+
p += parseInt(tokens[2], 10);
|
|
30892
|
+
}
|
|
30893
|
+
return p
|
|
30894
|
+
} catch (e) {
|
|
30895
|
+
console.error(`Error parsing priority string: ${priorityString}`, e);
|
|
30896
|
+
return Number.MAX_SAFE_INTEGER
|
|
30897
|
+
}
|
|
30898
|
+
}
|
|
30899
|
+
|
|
30900
|
+
function parseMetadata(metadata) {
|
|
30901
|
+
const attrs = new Map();
|
|
30902
|
+
let lastMetdataLengh = -1;
|
|
30903
|
+
while (metadata && metadata.length > 0) {
|
|
30904
|
+
try {
|
|
30905
|
+
if (metadata.length === lastMetdataLengh) {
|
|
30906
|
+
break
|
|
30907
|
+
}
|
|
30908
|
+
lastMetdataLengh = metadata.length;
|
|
30909
|
+
let idx = metadata.indexOf("=");
|
|
30910
|
+
if (idx === -1 || idx === metadata.length - 1) {
|
|
30911
|
+
break
|
|
30912
|
+
}
|
|
30913
|
+
let idx2;
|
|
30914
|
+
const key = capitalize(stripQuotes$2(metadata.substring(0, idx)));
|
|
30915
|
+
let value;
|
|
30916
|
+
|
|
30917
|
+
if (metadata.charAt(idx + 1) === '"') {
|
|
30918
|
+
idx++;
|
|
30919
|
+
idx2 = metadata.indexOf('" ', idx + 1);
|
|
30920
|
+
value = idx2 > 0 ? metadata.substring(idx + 1, idx2) : metadata.substring(idx + 1);
|
|
30921
|
+
idx2++;
|
|
30922
|
+
} else {
|
|
30923
|
+
idx2 = metadata.indexOf(" ", idx + 1);
|
|
30924
|
+
if (idx2 === -1) {
|
|
30925
|
+
idx2 = metadata.length;
|
|
30432
30926
|
}
|
|
30927
|
+
value = metadata.substring(idx + 1, idx2);
|
|
30928
|
+
}
|
|
30929
|
+
value = stripQuotes$2(value);
|
|
30930
|
+
if (value.endsWith('"')) {
|
|
30931
|
+
value = value.substring(0, value.length - 1);
|
|
30932
|
+
}
|
|
30933
|
+
if (value.startsWith("<") && value.endsWith(">")) {
|
|
30934
|
+
value = htmlText(value);
|
|
30433
30935
|
}
|
|
30936
|
+
attrs.set(key, value);
|
|
30937
|
+
if (idx2 === metadata.length) {
|
|
30938
|
+
break
|
|
30939
|
+
}
|
|
30940
|
+
metadata = idx2 > 0 ? metadata.substring(idx2 + 1).trim() : "";
|
|
30941
|
+
} catch (e) {
|
|
30942
|
+
// We don't want to fail parsing the hub due to a failure parsing metadata. Also, we don't want to
|
|
30943
|
+
// overwhelm the log. Metadata is of marginal importance in IGV.
|
|
30944
|
+
}
|
|
30945
|
+
}
|
|
30946
|
+
return attrs
|
|
30947
|
+
}
|
|
30948
|
+
|
|
30949
|
+
/*
|
|
30950
|
+
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
30951
|
+
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
30952
|
+
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
30953
|
+
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
30954
|
+
*/
|
|
30955
|
+
|
|
30956
|
+
const idMappings = new Map([
|
|
30957
|
+
["hg38", "GCF_000001405.40"],
|
|
30958
|
+
["mm39", "GCF_000001635.27"],
|
|
30959
|
+
["mm10", "GCF_000001635.26"],
|
|
30960
|
+
["bosTau9", "GCF_002263795.1"],
|
|
30961
|
+
["canFam4", "GCF_011100685.1"],
|
|
30962
|
+
["canFam6", "GCF_000002285.5"],
|
|
30963
|
+
["ce11", "GCF_000002985.6"],
|
|
30964
|
+
["dm6", "GCF_000001215.4"],
|
|
30965
|
+
["galGal6", "GCF_000002315.6"],
|
|
30966
|
+
["gorGor6", "GCF_008122165.1"],
|
|
30967
|
+
["macFas5", "GCA_000364345.1"],
|
|
30968
|
+
["panTro6", "GCA_002880755.3"],
|
|
30969
|
+
["rn6", "GCF_000001895.5"],
|
|
30970
|
+
["rn7", "GCF_015227675.2"],
|
|
30971
|
+
["sacCer3", "GCF_000146045.2"],
|
|
30972
|
+
["sacCer2", "GCF_000146045.2"],
|
|
30973
|
+
["susScr11", "GCF_000003025.6"],
|
|
30974
|
+
["taeGut1", "GCF_000002275.3"],
|
|
30975
|
+
["tetNig2", "GCF_000002275.3"],
|
|
30976
|
+
["xenTro10", "GCF_000002035.6"],
|
|
30977
|
+
["xenTro9", "GCF_000002035.6"],
|
|
30978
|
+
["tair10", "GCF_000001735.4"],
|
|
30979
|
+
]);
|
|
30980
|
+
|
|
30981
|
+
class Hub {
|
|
30982
|
+
|
|
30983
|
+
static supportedTypes = new Set(["bigBed", "bigWig", "bigGenePred", "vcfTabix"])
|
|
30984
|
+
static filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps",
|
|
30985
|
+
"cpgIslandExtUnmasked", "windowMasker"])
|
|
30986
|
+
|
|
30987
|
+
constructor(url, hubStanza, genomeStanzas, trackStanzas, groupStanzas) {
|
|
30988
|
+
|
|
30989
|
+
this.url = url;
|
|
30990
|
+
this.hubStanza = hubStanza;
|
|
30991
|
+
this.genomeStanzas = genomeStanzas;
|
|
30992
|
+
this.trackStanzas = trackStanzas;
|
|
30993
|
+
this.groupStanzas = groupStanzas;
|
|
30994
|
+
this.trackHubMap = new Map();
|
|
30995
|
+
|
|
30996
|
+
// trackStanzas will not be null if this is a "onefile" hub
|
|
30997
|
+
if (trackStanzas) {
|
|
30998
|
+
const genomeId = genomeStanzas[0].getProperty("genome"); // Assumption here this is a single genome hub
|
|
30999
|
+
this.trackHubMap.set(genomeId, new TrackDbHub(trackStanzas, groupStanzas));
|
|
30434
31000
|
}
|
|
30435
31001
|
}
|
|
30436
31002
|
|
|
30437
|
-
|
|
30438
|
-
|
|
31003
|
+
|
|
31004
|
+
getName() {
|
|
31005
|
+
return this.hubStanza.getProperty("hub")
|
|
30439
31006
|
}
|
|
30440
31007
|
|
|
31008
|
+
getShortLabel() {
|
|
31009
|
+
return this.hubStanza.getProperty("shortLabel")
|
|
31010
|
+
}
|
|
31011
|
+
|
|
31012
|
+
getLongLabel() {
|
|
31013
|
+
return this.hubStanza.getProperty("longLabel")
|
|
31014
|
+
}
|
|
31015
|
+
|
|
31016
|
+
getDescriptionUrl() {
|
|
31017
|
+
return this.hubStanza.getProperty("descriptionUrl")
|
|
31018
|
+
}
|
|
31019
|
+
|
|
31020
|
+
|
|
30441
31021
|
/* Example genome stanza
|
|
30442
31022
|
genome GCF_000186305.1
|
|
30443
31023
|
taxId 176946
|
|
@@ -30456,133 +31036,126 @@ transBlat dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30456
31036
|
isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
30457
31037
|
*/
|
|
30458
31038
|
|
|
30459
|
-
getGenomeConfig(
|
|
30460
|
-
|
|
31039
|
+
getGenomeConfig(genomeId) {
|
|
31040
|
+
|
|
31041
|
+
const genomeStanza = genomeId ? this.genomeStanzas.find(s => s.getProperty("genome") === genomeId) : this.genomeStanzas[0];
|
|
31042
|
+
if (!genomeStanza) {
|
|
31043
|
+
throw new Error(`Genome not found in hub: ${genomeId}`)
|
|
31044
|
+
}
|
|
31045
|
+
return this.#getGenomeConfig(genomeStanza)
|
|
31046
|
+
}
|
|
31047
|
+
|
|
31048
|
+
#getGenomeConfig(genomeStanza) {
|
|
30461
31049
|
|
|
30462
|
-
const id =
|
|
31050
|
+
const id = genomeStanza.getProperty("genome");
|
|
30463
31051
|
const gsName =
|
|
30464
31052
|
this.hubStanza.getProperty("shortLabel") ||
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
31053
|
+
genomeStanza.getProperty("scientificName") ||
|
|
31054
|
+
genomeStanza.getProperty("organism") ||
|
|
31055
|
+
genomeStanza.getProperty("description");
|
|
30468
31056
|
const name = gsName + (gsName ? ` (${id})` : ` ${id}`);
|
|
30469
31057
|
|
|
30470
31058
|
const config = {
|
|
30471
|
-
|
|
31059
|
+
|
|
30472
31060
|
id: id,
|
|
30473
31061
|
name: name,
|
|
30474
|
-
twoBitURL:
|
|
31062
|
+
twoBitURL: genomeStanza.getProperty("twoBitPath"),
|
|
30475
31063
|
nameSet: "ucsc",
|
|
31064
|
+
hubs: [this.url]
|
|
30476
31065
|
};
|
|
30477
31066
|
|
|
30478
|
-
if (
|
|
30479
|
-
config.chromSizesURL =
|
|
31067
|
+
if (genomeStanza.hasProperty("chromSizes")) {
|
|
31068
|
+
config.chromSizesURL = genomeStanza.getProperty("chromSizes");
|
|
30480
31069
|
} else {
|
|
30481
31070
|
config.wholeGenomeView = false;
|
|
30482
31071
|
config.showChromosomeWidget = false;
|
|
30483
31072
|
}
|
|
30484
31073
|
|
|
30485
|
-
if (
|
|
30486
|
-
const hubLocus =
|
|
31074
|
+
if (genomeStanza.hasProperty("defaultPos")) {
|
|
31075
|
+
const hubLocus = genomeStanza.getProperty("defaultPos");
|
|
30487
31076
|
// Strip out coordinates => whole chromosome view
|
|
30488
|
-
if (hubLocus) {
|
|
30489
|
-
|
|
30490
|
-
|
|
30491
|
-
}
|
|
31077
|
+
// if (hubLocus) {
|
|
31078
|
+
// const idx = hubLocus.lastIndexOf(":")
|
|
31079
|
+
// config.locus = idx > 0 ? hubLocus.substring(0, idx) : hubLocus
|
|
31080
|
+
// }
|
|
31081
|
+
config.locus = hubLocus;
|
|
30492
31082
|
}
|
|
30493
31083
|
|
|
30494
|
-
if (
|
|
30495
|
-
config.blat =
|
|
30496
|
-
}
|
|
30497
|
-
if (this.genomeStanza.hasProperty("chromAliasBb")) {
|
|
30498
|
-
config.chromAliasBbURL = this.baseURL + this.genomeStanza.getProperty("chromAliasBb");
|
|
31084
|
+
if (genomeStanza.hasProperty("blat")) {
|
|
31085
|
+
config.blat = genomeStanza.getProperty("blat");
|
|
30499
31086
|
}
|
|
30500
|
-
if (
|
|
30501
|
-
config.
|
|
31087
|
+
if (genomeStanza.hasProperty("chromAliasBb")) {
|
|
31088
|
+
config.chromAliasBbURL = genomeStanza.getProperty("chromAliasBb");
|
|
30502
31089
|
}
|
|
30503
|
-
if (
|
|
30504
|
-
config.
|
|
31090
|
+
if (genomeStanza.hasProperty("chromAlias")) {
|
|
31091
|
+
config.aliasURL = genomeStanza.getProperty("chromAlias");
|
|
30505
31092
|
}
|
|
30506
|
-
|
|
30507
|
-
|
|
30508
|
-
config.twoBitBptURL = this.baseURL + this.genomeStanza.getProperty("twoBitBptUrl");
|
|
31093
|
+
if (genomeStanza.hasProperty("twoBitBptURL")) {
|
|
31094
|
+
config.twoBitBptURL = genomeStanza.getProperty("twoBitBptURL");
|
|
30509
31095
|
}
|
|
30510
31096
|
|
|
30511
|
-
|
|
30512
|
-
|
|
30513
|
-
config.chromSizesURL = this.baseURL + this.genomeStanza.getProperty("chromSizes");
|
|
31097
|
+
if (genomeStanza.hasProperty("twoBitBptUrl")) {
|
|
31098
|
+
config.twoBitBptURL = genomeStanza.getProperty("twoBitBptUrl");
|
|
30514
31099
|
}
|
|
30515
31100
|
|
|
30516
31101
|
if (this.hubStanza.hasProperty("longLabel")) {
|
|
30517
31102
|
config.description = this.hubStanza.getProperty("longLabel").replace("/", "\n");
|
|
30518
31103
|
} else {
|
|
30519
31104
|
config.description = config.id;
|
|
30520
|
-
if (
|
|
30521
|
-
config.description += `\n${
|
|
31105
|
+
if (genomeStanza.hasProperty("description")) {
|
|
31106
|
+
config.description += `\n${genomeStanza.getProperty("description")}`;
|
|
30522
31107
|
}
|
|
30523
|
-
if (
|
|
30524
|
-
config.description += `\n${
|
|
31108
|
+
if (genomeStanza.hasProperty("organism")) {
|
|
31109
|
+
config.description += `\n${genomeStanza.getProperty("organism")}`;
|
|
30525
31110
|
}
|
|
30526
|
-
if (
|
|
30527
|
-
config.description += `\n${
|
|
31111
|
+
if (genomeStanza.hasProperty("scientificName")) {
|
|
31112
|
+
config.description += `\n${genomeStanza.getProperty("scientificName")}`;
|
|
30528
31113
|
}
|
|
30529
31114
|
|
|
30530
|
-
if (
|
|
30531
|
-
config.infoURL =
|
|
31115
|
+
if (genomeStanza.hasProperty("htmlPath")) {
|
|
31116
|
+
config.infoURL = genomeStanza.getProperty("htmlPath");
|
|
30532
31117
|
}
|
|
30533
31118
|
}
|
|
30534
31119
|
|
|
30535
|
-
// Search for cytoband
|
|
30536
|
-
/*
|
|
30537
|
-
track cytoBandIdeo
|
|
30538
|
-
shortLabel Chromosome Band (Ideogram)
|
|
30539
|
-
longLabel Ideogram for Orientation
|
|
30540
|
-
group map
|
|
30541
|
-
visibility dense
|
|
30542
|
-
type bigBed 4 +
|
|
30543
|
-
bigDataUrl bbi/GCA_004027145.1_DauMad_v1_BIUU.cytoBand.bb
|
|
30544
|
-
*/
|
|
30545
|
-
const cytoStanza = this.trackStanzas.filter(t => "cytoBandIdeo" === t.name && t.hasProperty("bigDataUrl"));
|
|
30546
|
-
if (cytoStanza.length > 0) {
|
|
30547
|
-
config.cytobandBbURL = this.baseURL + cytoStanza[0].getProperty("bigDataUrl");
|
|
30548
|
-
}
|
|
30549
|
-
|
|
30550
31120
|
// Tracks.
|
|
30551
31121
|
const filter = (t) => !Hub.filterTracks.has(t.name) && "hide" !== t.getProperty("visibility");
|
|
30552
31122
|
config.tracks = this.#getTracksConfigs(filter);
|
|
30553
31123
|
|
|
30554
|
-
|
|
30555
31124
|
return config
|
|
30556
31125
|
}
|
|
30557
31126
|
|
|
30558
|
-
getGroupedTrackConfigurations() {
|
|
30559
|
-
|
|
30560
|
-
|
|
30561
|
-
|
|
30562
|
-
for (let c of this.#getTracksConfigs()) {
|
|
30563
|
-
if (c.name === "cytoBandIdeo") continue
|
|
30564
|
-
const groupName = c.group || "other";
|
|
30565
|
-
if (trackConfigMap.has(groupName)) {
|
|
30566
|
-
trackConfigMap.get(groupName).push(c);
|
|
30567
|
-
} else {
|
|
30568
|
-
trackConfigMap.set(groupName, [c]);
|
|
30569
|
-
}
|
|
31127
|
+
async getGroupedTrackConfigurations(genomeId) {
|
|
31128
|
+
let trackHub = await this.#getTrackDbHub(genomeId);
|
|
31129
|
+
if (!trackHub && idMappings.has(genomeId)) {
|
|
31130
|
+
trackHub = await this.#getTrackDbHub(idMappings.get(genomeId));
|
|
30570
31131
|
}
|
|
31132
|
+
if (!trackHub) {
|
|
31133
|
+
console.log(`Warning: no trackDB found for genomeId ${genomeId}.`);
|
|
31134
|
+
}
|
|
31135
|
+
return trackHub ? trackHub.getGroupedTrackConfigurations() : []
|
|
31136
|
+
}
|
|
30571
31137
|
|
|
30572
|
-
|
|
30573
|
-
|
|
30574
|
-
|
|
30575
|
-
|
|
30576
|
-
|
|
30577
|
-
|
|
30578
|
-
|
|
30579
|
-
|
|
30580
|
-
|
|
31138
|
+
async #getTrackDbHub(genomeId) {
|
|
31139
|
+
let trackHub = this.trackHubMap.get(genomeId);
|
|
31140
|
+
if (!trackHub) {
|
|
31141
|
+
for (let stanza of this.genomeStanzas) {
|
|
31142
|
+
if (genomeId === stanza.getProperty("genome")) {
|
|
31143
|
+
try {
|
|
31144
|
+
const trackDbURL = stanza.getProperty("trackDb");
|
|
31145
|
+
const trackStanzas = await loadStanzas(trackDbURL);
|
|
31146
|
+
trackHub = new TrackDbHub(trackStanzas, this.groupStanzas);
|
|
31147
|
+
this.trackHubMap.set(genomeId, trackHub);
|
|
31148
|
+
} catch (error) {
|
|
31149
|
+
console.error(`Error loading trackDb file: ${stanza.getProperty("trackDb")}`, error);
|
|
31150
|
+
}
|
|
31151
|
+
break
|
|
31152
|
+
}
|
|
30581
31153
|
}
|
|
30582
|
-
}
|
|
30583
|
-
|
|
31154
|
+
}
|
|
31155
|
+
return trackHub
|
|
30584
31156
|
}
|
|
30585
31157
|
|
|
31158
|
+
|
|
30586
31159
|
/**
|
|
30587
31160
|
* Return an array of igv track config objects that satisfy the filter
|
|
30588
31161
|
*/
|
|
@@ -30620,7 +31193,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30620
31193
|
"id": t.getProperty("track"),
|
|
30621
31194
|
"name": t.getProperty("shortLabel"),
|
|
30622
31195
|
"format": format,
|
|
30623
|
-
"url":
|
|
31196
|
+
"url": t.getProperty("bigDataUrl"),
|
|
30624
31197
|
"displayMode": t.displayMode,
|
|
30625
31198
|
};
|
|
30626
31199
|
|
|
@@ -30631,7 +31204,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30631
31204
|
if (t.hasProperty("longLabel") && t.hasProperty("html")) {
|
|
30632
31205
|
if (config.description) config.description += "<br/>";
|
|
30633
31206
|
config.description =
|
|
30634
|
-
`<a target="_blank" href="${
|
|
31207
|
+
`<a target="_blank" href="${t.getProperty("html")}">${t.getProperty("longLabel")}</a>`;
|
|
30635
31208
|
} else if (t.hasProperty("longLabel")) {
|
|
30636
31209
|
config.description = t.getProperty("longLabel");
|
|
30637
31210
|
}
|
|
@@ -30663,7 +31236,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30663
31236
|
max = Number.parseInt(tokens[1]);
|
|
30664
31237
|
}
|
|
30665
31238
|
if (Number.isNaN(max) || Number.isNaN(min)) {
|
|
30666
|
-
console.warn(`Unexpected viewLimits value in track line: ${
|
|
31239
|
+
console.warn(`Unexpected viewLimits value in track line: ${t.getProperty("viewLimits")}`);
|
|
30667
31240
|
} else {
|
|
30668
31241
|
config.min = min;
|
|
30669
31242
|
config.max = max;
|
|
@@ -30682,15 +31255,15 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30682
31255
|
config.searchIndex = t.getProperty("searchIndex");
|
|
30683
31256
|
}
|
|
30684
31257
|
if (t.hasProperty("searchTrix")) {
|
|
30685
|
-
config.trixURL =
|
|
31258
|
+
config.trixURL = t.getProperty("searchTrix");
|
|
30686
31259
|
}
|
|
30687
31260
|
|
|
30688
31261
|
if (t.hasProperty("group")) {
|
|
30689
|
-
config.
|
|
30690
|
-
if (this.groupPriorityMap && this.groupPriorityMap.has(config.
|
|
30691
|
-
const nextPriority = this.groupPriorityMap.get(config.
|
|
31262
|
+
config._group = t.getProperty("group");
|
|
31263
|
+
if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
|
|
31264
|
+
const nextPriority = this.groupPriorityMap.get(config._group) + 1;
|
|
30692
31265
|
config.order = nextPriority;
|
|
30693
|
-
this.groupPriorityMap.set(config.
|
|
31266
|
+
this.groupPriorityMap.set(config._group, nextPriority);
|
|
30694
31267
|
}
|
|
30695
31268
|
}
|
|
30696
31269
|
|
|
@@ -30699,96 +31272,92 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30699
31272
|
|
|
30700
31273
|
}
|
|
30701
31274
|
|
|
30702
|
-
|
|
30703
|
-
|
|
30704
|
-
|
|
30705
|
-
|
|
31275
|
+
/*
|
|
31276
|
+
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
31277
|
+
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
31278
|
+
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
31279
|
+
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
31280
|
+
*/
|
|
30706
31281
|
|
|
30707
|
-
|
|
31282
|
+
const urlProperties = new Set(["descriptionUrl", "desriptionUrl",
|
|
31283
|
+
"twoBitPath", "blat", "chromAliasBb", "twoBitBptURL", "twoBitBptUrl", "htmlPath", "bigDataUrl",
|
|
31284
|
+
"genomesFile", "trackDb", "groups", "include", "html", "searchTrix", "groups",
|
|
31285
|
+
"chromSizes"]);
|
|
30708
31286
|
|
|
30709
|
-
properties = new Map()
|
|
30710
31287
|
|
|
30711
|
-
|
|
30712
|
-
this.type = type;
|
|
30713
|
-
this.name = name;
|
|
30714
|
-
}
|
|
31288
|
+
const hubCache = new Map();
|
|
30715
31289
|
|
|
30716
|
-
|
|
30717
|
-
|
|
31290
|
+
async function loadHub(url) {
|
|
31291
|
+
if (hubCache.has(url)) {
|
|
31292
|
+
return hubCache.get(url)
|
|
30718
31293
|
}
|
|
30719
31294
|
|
|
30720
|
-
|
|
30721
|
-
|
|
30722
|
-
|
|
30723
|
-
} else if (this.parent) {
|
|
30724
|
-
return this.parent.getProperty(key)
|
|
30725
|
-
} else {
|
|
30726
|
-
return undefined
|
|
30727
|
-
}
|
|
31295
|
+
const stanzas = await loadStanzas(url);
|
|
31296
|
+
if (stanzas.length < 1) {
|
|
31297
|
+
throw new Error("Empty hub file")
|
|
30728
31298
|
}
|
|
30729
31299
|
|
|
30730
|
-
|
|
30731
|
-
|
|
30732
|
-
|
|
30733
|
-
} else if (this.parent) {
|
|
30734
|
-
return this.parent.hasProperty(key)
|
|
30735
|
-
} else {
|
|
30736
|
-
return false
|
|
30737
|
-
}
|
|
31300
|
+
const hubStanza = stanzas[0];
|
|
31301
|
+
if (hubStanza.type !== "hub") {
|
|
31302
|
+
throw new Error("First stanza must be a hub stanza")
|
|
30738
31303
|
}
|
|
30739
31304
|
|
|
30740
|
-
|
|
30741
|
-
|
|
30742
|
-
|
|
30743
|
-
|
|
30744
|
-
|
|
31305
|
+
let genomeStanzas;
|
|
31306
|
+
let trackStanzas;
|
|
31307
|
+
if (hubStanza.getProperty("useOneFile") === "on") {
|
|
31308
|
+
// This is a "onefile" hub, all stanzas are in the same file
|
|
31309
|
+
if (stanzas[1].type !== "genome") {
|
|
31310
|
+
throw new Error("Unexpected hub file -- expected 'genome' stanza but found " + stanzas[1].type)
|
|
30745
31311
|
}
|
|
30746
|
-
|
|
30747
|
-
|
|
31312
|
+
const genomeStanza = stanzas[1];
|
|
31313
|
+
genomeStanzas = [genomeStanza];
|
|
31314
|
+
trackStanzas = stanzas.slice(2);
|
|
30748
31315
|
|
|
30749
|
-
|
|
30750
|
-
|
|
30751
|
-
|
|
30752
|
-
|
|
30753
|
-
|
|
30754
|
-
|
|
30755
|
-
|
|
30756
|
-
|
|
30757
|
-
|
|
30758
|
-
|
|
30759
|
-
|
|
30760
|
-
return "COLLAPSED"
|
|
30761
|
-
case "pack":
|
|
30762
|
-
return "EXPANDED"
|
|
30763
|
-
case "squish":
|
|
30764
|
-
return "SQUISHED"
|
|
30765
|
-
default:
|
|
30766
|
-
return "COLLAPSED"
|
|
31316
|
+
// If this is an assembly check chromSizes. This file can be very large, and not needed if whole genome view
|
|
31317
|
+
// is not enabled. Remove it if > 100 kb
|
|
31318
|
+
if (genomeStanza.hasOwnProperty("chromSizes")) {
|
|
31319
|
+
const chromSizes = genomeStanza.getProperty("chromSizes");
|
|
31320
|
+
try {
|
|
31321
|
+
const contentLength = await igvxhr.getContentLength(chromSizes);
|
|
31322
|
+
if (contentLength > 100000) {
|
|
31323
|
+
genomeStanza.removeProperty("chromSizes");
|
|
31324
|
+
}
|
|
31325
|
+
} catch (e) {
|
|
31326
|
+
console.error(`Error getting content length for chromSizes ${chromSizes}`, e);
|
|
30767
31327
|
}
|
|
30768
|
-
}
|
|
30769
|
-
}
|
|
30770
|
-
}
|
|
30771
31328
|
|
|
31329
|
+
}
|
|
30772
31330
|
|
|
30773
|
-
|
|
30774
|
-
|
|
30775
|
-
|
|
30776
|
-
* @returns {Promise<number|string>}
|
|
30777
|
-
*/
|
|
30778
|
-
async function getContentLength(url) {
|
|
30779
|
-
try {
|
|
30780
|
-
const response = await fetch(url, {method: 'HEAD'});
|
|
30781
|
-
const headers = response.headers;
|
|
30782
|
-
if (headers.has("content-length")) {
|
|
30783
|
-
return headers.get("content-length")
|
|
30784
|
-
} else {
|
|
30785
|
-
return null
|
|
31331
|
+
} else {
|
|
31332
|
+
if (!hubStanza.hasProperty("genomesFile")) {
|
|
31333
|
+
throw new Error("hub.txt must specify 'genomesFile'")
|
|
30786
31334
|
}
|
|
30787
|
-
|
|
30788
|
-
return null
|
|
31335
|
+
genomeStanzas = await loadStanzas(hubStanza.getProperty("genomesFile"));
|
|
30789
31336
|
}
|
|
31337
|
+
|
|
31338
|
+
// Load group files for all genomes, if any.
|
|
31339
|
+
const uniqGroupURLs = new Set();
|
|
31340
|
+
genomeStanzas.forEach(s => {
|
|
31341
|
+
const groupURL = s.getProperty("groups");
|
|
31342
|
+
if (groupURL) uniqGroupURLs.add(groupURL);
|
|
31343
|
+
|
|
31344
|
+
});
|
|
31345
|
+
const groupStanzas = [];
|
|
31346
|
+
const groupPromises = Array.from(uniqGroupURLs).map(async url => {
|
|
31347
|
+
const stanza = await loadStanzas(url);
|
|
31348
|
+
return stanza
|
|
31349
|
+
});
|
|
31350
|
+
const groupResults = await Promise.all(groupPromises);
|
|
31351
|
+
groupResults.forEach(stanza => groupStanzas.push(...stanza));
|
|
31352
|
+
|
|
31353
|
+
const hub = new Hub(url, hubStanza, genomeStanzas, trackStanzas, groupStanzas);
|
|
31354
|
+
|
|
31355
|
+
hubCache.set(url, hub);
|
|
31356
|
+
|
|
31357
|
+
return hub
|
|
30790
31358
|
}
|
|
30791
31359
|
|
|
31360
|
+
|
|
30792
31361
|
/**
|
|
30793
31362
|
* Parse a UCSC file
|
|
30794
31363
|
* @param url
|
|
@@ -30796,38 +31365,92 @@ async function getContentLength(url) {
|
|
|
30796
31365
|
*/
|
|
30797
31366
|
async function loadStanzas(url) {
|
|
30798
31367
|
|
|
30799
|
-
const
|
|
30800
|
-
const
|
|
31368
|
+
const idx = url.lastIndexOf("/");
|
|
31369
|
+
const baseURL = url.substring(0, idx + 1);
|
|
31370
|
+
const host = getHost(url);
|
|
31371
|
+
|
|
31372
|
+
//const response = await fetch(url)
|
|
31373
|
+
const data = await igvxhr.loadString(url, {}); //await response.text()
|
|
30801
31374
|
const lines = data.split(/\n|\r\n|\r/g);
|
|
30802
31375
|
|
|
30803
31376
|
const nodes = [];
|
|
30804
31377
|
let currentNode;
|
|
30805
31378
|
let startNewNode = true;
|
|
30806
|
-
for (let
|
|
30807
|
-
|
|
30808
|
-
|
|
30809
|
-
|
|
31379
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31380
|
+
|
|
31381
|
+
let line = lines[i].trim();
|
|
31382
|
+
|
|
31383
|
+
if (line.length == 0) {
|
|
30810
31384
|
// Break - start a new node
|
|
30811
31385
|
startNewNode = true;
|
|
30812
31386
|
} else {
|
|
30813
|
-
|
|
30814
|
-
|
|
30815
|
-
|
|
31387
|
+
if (line.startsWith("#")) {
|
|
31388
|
+
continue
|
|
31389
|
+
}
|
|
31390
|
+
|
|
31391
|
+
while (line.endsWith('\\')) {
|
|
31392
|
+
i++;
|
|
31393
|
+
if (i >= lines.length) {
|
|
31394
|
+
break
|
|
31395
|
+
}
|
|
31396
|
+
line = line.substring(0, line.length - 1) + lines[i].trim();
|
|
31397
|
+
}
|
|
31398
|
+
|
|
31399
|
+
if (line.startsWith("include")) {
|
|
31400
|
+
const relativeURL = line.substring(8).trim();
|
|
31401
|
+
const includeURL = getDataURL(relativeURL, host, baseURL);
|
|
31402
|
+
const includeStanzas = await loadStanzas(includeURL);
|
|
31403
|
+
for (let s of includeStanzas) {
|
|
31404
|
+
nodes.push(s);
|
|
31405
|
+
}
|
|
31406
|
+
}
|
|
31407
|
+
|
|
31408
|
+
|
|
31409
|
+
const i = line.indexOf(' ');
|
|
31410
|
+
const key = line.substring(0, i).trim();
|
|
31411
|
+
let value = line.substring(i + 1).trim();
|
|
31412
|
+
|
|
31413
|
+
if (key === "type") {
|
|
31414
|
+
// The "type" property contains format and sometimes other information. For example, data range
|
|
31415
|
+
// on a bigwig "type bigWig 0 .5"
|
|
31416
|
+
const tokens = value.split(/\s+/);
|
|
31417
|
+
value = tokens[0];
|
|
31418
|
+
if (value === "bigWig" && tokens.length === 3) {
|
|
31419
|
+
// This is a bigWig with a range
|
|
31420
|
+
const min = tokens[1];
|
|
31421
|
+
const max = tokens[2];
|
|
31422
|
+
if (currentNode) {
|
|
31423
|
+
currentNode.setProperty("min", min);
|
|
31424
|
+
currentNode.setProperty("max", max);
|
|
31425
|
+
}
|
|
31426
|
+
}
|
|
31427
|
+
|
|
31428
|
+
} else if (!["shortLabel", "longLabel", "metadata", "label"].includes(key)) {
|
|
31429
|
+
const tokens = value.split(/\s+/);
|
|
31430
|
+
value = tokens[0];
|
|
31431
|
+
}
|
|
31432
|
+
|
|
31433
|
+
if (urlProperties.has(key) || value.endsWith("URL") || value.endsWith("Url")) {
|
|
31434
|
+
value = getDataURL(value, host, baseURL);
|
|
31435
|
+
}
|
|
31436
|
+
|
|
30816
31437
|
if (startNewNode) {
|
|
30817
|
-
|
|
30818
|
-
|
|
30819
|
-
const newNode = new Stanza(key, value);
|
|
30820
|
-
nodes.push(newNode);
|
|
30821
|
-
currentNode = newNode;
|
|
31438
|
+
currentNode = new Stanza(key, value);
|
|
31439
|
+
nodes.push(currentNode);
|
|
30822
31440
|
startNewNode = false;
|
|
30823
31441
|
}
|
|
31442
|
+
|
|
30824
31443
|
currentNode.setProperty(key, value);
|
|
30825
31444
|
}
|
|
30826
31445
|
}
|
|
30827
|
-
|
|
30828
31446
|
return resolveParents(nodes)
|
|
30829
31447
|
}
|
|
30830
31448
|
|
|
31449
|
+
function firstWord(str) {
|
|
31450
|
+
const idx = str.indexOf(' ');
|
|
31451
|
+
return idx > 0 ? str.substring(0, idx) : str
|
|
31452
|
+
}
|
|
31453
|
+
|
|
30831
31454
|
function resolveParents(nodes) {
|
|
30832
31455
|
const nodeMap = new Map();
|
|
30833
31456
|
for (let n of nodes) {
|
|
@@ -30842,17 +31465,34 @@ function resolveParents(nodes) {
|
|
|
30842
31465
|
return nodes
|
|
30843
31466
|
}
|
|
30844
31467
|
|
|
30845
|
-
function
|
|
30846
|
-
|
|
30847
|
-
|
|
30848
|
-
|
|
30849
|
-
|
|
31468
|
+
function getDataURL(url, host, baseURL) {
|
|
31469
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("gs://") || url.startsWith("s3://")) {
|
|
31470
|
+
return url
|
|
31471
|
+
} else if (url.startsWith("/")) {
|
|
31472
|
+
return host + url
|
|
31473
|
+
} else {
|
|
31474
|
+
return baseURL + url
|
|
30850
31475
|
}
|
|
30851
|
-
|
|
31476
|
+
}
|
|
31477
|
+
|
|
31478
|
+
function getHost(url) {
|
|
31479
|
+
let host;
|
|
31480
|
+
if (url.startsWith("https://") || url.startsWith("http://")) {
|
|
31481
|
+
try {
|
|
31482
|
+
const tmp = new URL(url);
|
|
31483
|
+
host = `${tmp.protocol}//${tmp.host}`;
|
|
31484
|
+
} catch (e) {
|
|
31485
|
+
console.error("Error parsing base URL host", e);
|
|
31486
|
+
throw e
|
|
31487
|
+
}
|
|
31488
|
+
} else {
|
|
31489
|
+
host = '';
|
|
31490
|
+
}
|
|
31491
|
+
return host
|
|
30852
31492
|
}
|
|
30853
31493
|
|
|
30854
31494
|
const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
|
|
30855
|
-
const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv
|
|
31495
|
+
const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-genomes/refs/heads/main/dist/genomes.json";
|
|
30856
31496
|
|
|
30857
31497
|
const GenomeUtils = {
|
|
30858
31498
|
|
|
@@ -30860,7 +31500,7 @@ const GenomeUtils = {
|
|
|
30860
31500
|
|
|
30861
31501
|
if (!GenomeUtils.KNOWN_GENOMES) {
|
|
30862
31502
|
|
|
30863
|
-
|
|
31503
|
+
let table = {};
|
|
30864
31504
|
|
|
30865
31505
|
const processJson = (jsonArray, table) => {
|
|
30866
31506
|
jsonArray.forEach(function (json) {
|
|
@@ -30873,12 +31513,12 @@ const GenomeUtils = {
|
|
|
30873
31513
|
if (config.loadDefaultGenomes !== false) {
|
|
30874
31514
|
try {
|
|
30875
31515
|
const jsonArray = await igvxhr.loadJson(DEFAULT_GENOMES_URL, {timeout: 2000});
|
|
30876
|
-
processJson(jsonArray,
|
|
31516
|
+
processJson(jsonArray, table);
|
|
30877
31517
|
} catch (error) {
|
|
30878
31518
|
try {
|
|
30879
31519
|
console.error("Error initializing default genomes:", error);
|
|
30880
31520
|
const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 2000});
|
|
30881
|
-
processJson(jsonArray,
|
|
31521
|
+
processJson(jsonArray, table);
|
|
30882
31522
|
} catch (e) {
|
|
30883
31523
|
console.error("Error initializing backup genomes:", error);
|
|
30884
31524
|
}
|
|
@@ -30890,11 +31530,12 @@ const GenomeUtils = {
|
|
|
30890
31530
|
if (genomeList) {
|
|
30891
31531
|
if (typeof genomeList === 'string') {
|
|
30892
31532
|
const jsonArray = await igvxhr.loadJson(genomeList, {});
|
|
30893
|
-
processJson(jsonArray,
|
|
31533
|
+
processJson(jsonArray, table);
|
|
30894
31534
|
} else {
|
|
30895
|
-
processJson(genomeList,
|
|
31535
|
+
processJson(genomeList, table);
|
|
30896
31536
|
}
|
|
30897
31537
|
}
|
|
31538
|
+
GenomeUtils.KNOWN_GENOMES = table;
|
|
30898
31539
|
}
|
|
30899
31540
|
},
|
|
30900
31541
|
|
|
@@ -30931,8 +31572,8 @@ const GenomeUtils = {
|
|
|
30931
31572
|
if ((genomeID.startsWith("GCA_") || genomeID.startsWith("GCF_")) && genomeID.length >= 13) {
|
|
30932
31573
|
try {
|
|
30933
31574
|
const hubURL = convertToHubURL(genomeID);
|
|
30934
|
-
const hub = await
|
|
30935
|
-
reference = hub.getGenomeConfig();
|
|
31575
|
+
const hub = await loadHub(hubURL);
|
|
31576
|
+
reference = hub.getGenomeConfig(genomeID);
|
|
30936
31577
|
} catch (e) {
|
|
30937
31578
|
console.error(e);
|
|
30938
31579
|
}
|
|
@@ -32894,27 +33535,18 @@ const distinctColorsPalette = _18_DistinctColorsViaChatGPT4.map(str => {
|
|
|
32894
33535
|
return [ r, g, b ]
|
|
32895
33536
|
});
|
|
32896
33537
|
|
|
32897
|
-
const colorForNA = appleCrayonRGB('magnesium');
|
|
32898
|
-
const sampleInfoFileHeaders = ['#sampleTable', '#sampleMapping', '#colors'];
|
|
32899
|
-
|
|
32900
33538
|
class SampleInfo {
|
|
32901
33539
|
|
|
32902
33540
|
static emptySpaceReplacement = '|'
|
|
32903
|
-
|
|
32904
|
-
|
|
32905
|
-
attributeNames = []
|
|
32906
|
-
sampleMappingDictionary = {}
|
|
32907
|
-
colorDictionary = {}
|
|
32908
|
-
attributeRangeLUT = {}
|
|
33541
|
+
static colorForNA = appleCrayonRGB('magnesium')
|
|
33542
|
+
static sampleInfoFileHeaders = ['#sampleTable', '#sampleMapping', '#colors']
|
|
32909
33543
|
|
|
32910
33544
|
constructor(browser) {
|
|
32911
|
-
|
|
32912
33545
|
const found = browser.tracks.some(t => typeof t.getSamples === 'function');
|
|
32913
33546
|
if (found.length > 0) {
|
|
32914
33547
|
browser.sampleInfoControl.setButtonVisibility(true);
|
|
32915
33548
|
}
|
|
32916
33549
|
this.initialize();
|
|
32917
|
-
|
|
32918
33550
|
}
|
|
32919
33551
|
|
|
32920
33552
|
initialize() {
|
|
@@ -32941,47 +33573,60 @@ class SampleInfo {
|
|
|
32941
33573
|
|
|
32942
33574
|
getAttributes(sampleName) {
|
|
32943
33575
|
|
|
32944
|
-
const key =
|
|
33576
|
+
const key = this.sampleMappingDictionary[sampleName] || sampleName;
|
|
32945
33577
|
return this.sampleDictionary[key]
|
|
32946
33578
|
}
|
|
32947
33579
|
|
|
32948
|
-
async
|
|
32949
|
-
|
|
32950
|
-
|
|
32951
|
-
this
|
|
32952
|
-
|
|
32953
|
-
|
|
32954
|
-
|
|
33580
|
+
async loadSampleInfo(config) {
|
|
33581
|
+
|
|
33582
|
+
if (config.url) {
|
|
33583
|
+
await this.loadSampleInfoFile(config.url);
|
|
33584
|
+
} else {
|
|
33585
|
+
|
|
33586
|
+
const samples = { ...config };
|
|
33587
|
+
for (const [key, record] of Object.entries(samples)) {
|
|
33588
|
+
samples[key] = SampleInfo.toNumericalRepresentation(record);
|
|
33589
|
+
}
|
|
33590
|
+
|
|
33591
|
+
const [ value ] = Object.values(samples);
|
|
33592
|
+
const attributes = Object.keys(value);
|
|
33593
|
+
|
|
33594
|
+
this.loadSampleInfoHelper(attributes, samples);
|
|
33595
|
+
|
|
32955
33596
|
}
|
|
32956
|
-
}
|
|
32957
33597
|
|
|
32958
|
-
|
|
33598
|
+
this.initialized = true;
|
|
33599
|
+
}
|
|
32959
33600
|
|
|
32960
|
-
|
|
33601
|
+
loadSampleInfoHelper(attributes, samples){
|
|
32961
33602
|
|
|
32962
|
-
|
|
32963
|
-
|
|
32964
|
-
|
|
32965
|
-
this.#accumulateSampleTableDictionary(value);
|
|
32966
|
-
break
|
|
32967
|
-
case '#sampleMapping':
|
|
32968
|
-
this.#accumulateSampleMappingDictionary(value);
|
|
32969
|
-
break
|
|
32970
|
-
case '#colors':
|
|
32971
|
-
this.#accumulateColorScheme(value);
|
|
32972
|
-
break
|
|
33603
|
+
// Establish the range of values for each attribute
|
|
33604
|
+
const lut = createAttributeRangeLUT(attributes, samples);
|
|
33605
|
+
accumulateDictionary(this.attributeRangeLUT, lut);
|
|
32973
33606
|
|
|
33607
|
+
// Ensure unique attribute names list
|
|
33608
|
+
const currentAttributeNameSet = new Set(this.attributeNames);
|
|
33609
|
+
for (const name of attributes) {
|
|
33610
|
+
if (!currentAttributeNameSet.has(name)) {
|
|
33611
|
+
this.attributeNames.push(name);
|
|
32974
33612
|
}
|
|
32975
33613
|
}
|
|
32976
33614
|
|
|
32977
|
-
this.
|
|
33615
|
+
accumulateDictionary(this.sampleDictionary, samples);
|
|
33616
|
+
|
|
33617
|
+
}
|
|
32978
33618
|
|
|
33619
|
+
async loadSampleInfoFile(path) {
|
|
33620
|
+
try {
|
|
33621
|
+
const string = await igvxhr.loadString(path);
|
|
33622
|
+
this.#processSampleInfoFileAsString(string);
|
|
33623
|
+
this.sampleInfoFiles.push(path);
|
|
33624
|
+
} catch (e) {
|
|
33625
|
+
console.error(e.message);
|
|
33626
|
+
}
|
|
32979
33627
|
}
|
|
32980
33628
|
|
|
32981
33629
|
getAttributeColor(attribute, value) {
|
|
32982
|
-
// if (value === 'NA') {
|
|
32983
|
-
// console.log(`${ attribute } : ${ value }`)
|
|
32984
|
-
// }
|
|
32985
33630
|
|
|
32986
33631
|
let color;
|
|
32987
33632
|
|
|
@@ -32999,7 +33644,7 @@ class SampleInfo {
|
|
|
32999
33644
|
|
|
33000
33645
|
} else if (typeof value === "string") {
|
|
33001
33646
|
|
|
33002
|
-
color = 'NA' === value ? colorForNA : stringToRGBString(value);
|
|
33647
|
+
color = 'NA' === value ? SampleInfo.colorForNA : SampleInfo.stringToRGBString(value);
|
|
33003
33648
|
|
|
33004
33649
|
} else {
|
|
33005
33650
|
|
|
@@ -33067,6 +33712,27 @@ class SampleInfo {
|
|
|
33067
33712
|
return json
|
|
33068
33713
|
}
|
|
33069
33714
|
|
|
33715
|
+
#processSampleInfoFileAsString(string) {
|
|
33716
|
+
|
|
33717
|
+
const sectionDictionary = createSectionDictionary(string);
|
|
33718
|
+
|
|
33719
|
+
for (const [header, value] of Object.entries(sectionDictionary)) {
|
|
33720
|
+
switch (header) {
|
|
33721
|
+
case '#sampleTable':
|
|
33722
|
+
this.#accumulateSampleTableDictionary(value);
|
|
33723
|
+
break
|
|
33724
|
+
case '#sampleMapping':
|
|
33725
|
+
this.#accumulateSampleMappingDictionary(value);
|
|
33726
|
+
break
|
|
33727
|
+
case '#colors':
|
|
33728
|
+
this.#accumulateColorScheme(value);
|
|
33729
|
+
break
|
|
33730
|
+
|
|
33731
|
+
}
|
|
33732
|
+
}
|
|
33733
|
+
|
|
33734
|
+
}
|
|
33735
|
+
|
|
33070
33736
|
#accumulateSampleTableDictionary(lines) {
|
|
33071
33737
|
|
|
33072
33738
|
// shift array with first item that is 'sample' or 'Linking_id'. Remaining items are attribute names
|
|
@@ -33106,22 +33772,11 @@ class SampleInfo {
|
|
|
33106
33772
|
} // for (lines)
|
|
33107
33773
|
|
|
33108
33774
|
for (const [key, record] of Object.entries(samples)) {
|
|
33109
|
-
samples[key] = toNumericalRepresentation(record);
|
|
33775
|
+
samples[key] = SampleInfo.toNumericalRepresentation(record);
|
|
33110
33776
|
}
|
|
33111
33777
|
|
|
33112
|
-
|
|
33113
|
-
const lut = createAttributeRangeLUT(attributes, samples);
|
|
33114
|
-
accumulateDictionary(this.attributeRangeLUT, lut);
|
|
33115
|
-
|
|
33116
|
-
// Ensure unique attribute names list
|
|
33117
|
-
const currentAttributeNameSet = new Set(this.attributeNames);
|
|
33118
|
-
for (const name of attributes) {
|
|
33119
|
-
if (!currentAttributeNameSet.has(name)) {
|
|
33120
|
-
this.attributeNames.push(name);
|
|
33121
|
-
}
|
|
33122
|
-
}
|
|
33778
|
+
this.loadSampleInfoHelper(attributes, samples);
|
|
33123
33779
|
|
|
33124
|
-
accumulateDictionary(this.sampleDictionary, samples);
|
|
33125
33780
|
}
|
|
33126
33781
|
|
|
33127
33782
|
#accumulateSampleMappingDictionary(lines) {
|
|
@@ -33222,7 +33877,7 @@ class SampleInfo {
|
|
|
33222
33877
|
this.colorDictionary[attribute] = attributeValue => {
|
|
33223
33878
|
|
|
33224
33879
|
if ('NA' === attributeValue) {
|
|
33225
|
-
return colorForNA
|
|
33880
|
+
return SampleInfo.colorForNA
|
|
33226
33881
|
} else {
|
|
33227
33882
|
const [min, max] = this.attributeRangeLUT[attribute];
|
|
33228
33883
|
const interpolant = (attributeValue - min) / (max - min);
|
|
@@ -33242,8 +33897,34 @@ class SampleInfo {
|
|
|
33242
33897
|
|
|
33243
33898
|
}
|
|
33244
33899
|
|
|
33245
|
-
|
|
33900
|
+
static toNumericalRepresentation(obj) {
|
|
33901
|
+
const result = Object.assign({}, obj);
|
|
33902
|
+
|
|
33903
|
+
for (const [key, value] of Object.entries(result)) {
|
|
33904
|
+
if (typeof value === 'string' && !isNaN(value)) {
|
|
33905
|
+
result[key] = Number(value);
|
|
33906
|
+
}
|
|
33907
|
+
}
|
|
33908
|
+
|
|
33909
|
+
return result
|
|
33910
|
+
}
|
|
33911
|
+
|
|
33912
|
+
static stringToRGBString(str) {
|
|
33913
|
+
let hash = 0;
|
|
33914
|
+
for (let i = 0; i < str.length; i++) {
|
|
33915
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
33916
|
+
}
|
|
33246
33917
|
|
|
33918
|
+
let color = [];
|
|
33919
|
+
for (let i = 0; i < 3; i++) {
|
|
33920
|
+
const value = (hash >> (i * 8)) & 0xff;
|
|
33921
|
+
color.push(value);
|
|
33922
|
+
}
|
|
33923
|
+
|
|
33924
|
+
return `rgb(${color.join(', ')})`
|
|
33925
|
+
}
|
|
33926
|
+
|
|
33927
|
+
}
|
|
33247
33928
|
|
|
33248
33929
|
function createSectionDictionary(string) {
|
|
33249
33930
|
|
|
@@ -33254,14 +33935,14 @@ function createSectionDictionary(string) {
|
|
|
33254
33935
|
let currentHeader;
|
|
33255
33936
|
|
|
33256
33937
|
// If the first line does not start with a section header an initial #sampleTable is implied
|
|
33257
|
-
if (!sampleInfoFileHeaders.includes(lines[0])) {
|
|
33938
|
+
if (!SampleInfo.sampleInfoFileHeaders.includes(lines[0])) {
|
|
33258
33939
|
currentHeader = '#sampleTable';
|
|
33259
33940
|
dictionary[currentHeader] = [];
|
|
33260
33941
|
}
|
|
33261
33942
|
|
|
33262
33943
|
for (const line of lines) {
|
|
33263
33944
|
|
|
33264
|
-
if (sampleInfoFileHeaders.includes(line)) {
|
|
33945
|
+
if (SampleInfo.sampleInfoFileHeaders.includes(line)) {
|
|
33265
33946
|
currentHeader = line;
|
|
33266
33947
|
dictionary[currentHeader] = [];
|
|
33267
33948
|
} else if (currentHeader && false === line.startsWith('#')) {
|
|
@@ -33280,7 +33961,6 @@ function accumulateDictionary(accumulator, dictionary) {
|
|
|
33280
33961
|
}
|
|
33281
33962
|
}
|
|
33282
33963
|
|
|
33283
|
-
|
|
33284
33964
|
function createAttributeRangeLUT(names, dictionary) {
|
|
33285
33965
|
|
|
33286
33966
|
const lut = {};
|
|
@@ -33326,35 +34006,6 @@ function createAttributeRangeLUT(names, dictionary) {
|
|
|
33326
34006
|
return lut
|
|
33327
34007
|
}
|
|
33328
34008
|
|
|
33329
|
-
function toNumericalRepresentation(obj) {
|
|
33330
|
-
|
|
33331
|
-
const result = Object.assign({}, obj);
|
|
33332
|
-
|
|
33333
|
-
for (const [key, value] of Object.entries(result)) {
|
|
33334
|
-
if (typeof value === 'string' && !isNaN(value)) {
|
|
33335
|
-
result[key] = Number(value);
|
|
33336
|
-
}
|
|
33337
|
-
}
|
|
33338
|
-
|
|
33339
|
-
return result
|
|
33340
|
-
}
|
|
33341
|
-
|
|
33342
|
-
function stringToRGBString(str) {
|
|
33343
|
-
|
|
33344
|
-
let hash = 0;
|
|
33345
|
-
for (let i = 0; i < str.length; i++) {
|
|
33346
|
-
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
33347
|
-
}
|
|
33348
|
-
|
|
33349
|
-
let color = [];
|
|
33350
|
-
for (let i = 0; i < 3; i++) {
|
|
33351
|
-
const value = (hash >> (i * 8)) & 0xff;
|
|
33352
|
-
color.push(value);
|
|
33353
|
-
}
|
|
33354
|
-
|
|
33355
|
-
return `rgb(${color.join(', ')})`
|
|
33356
|
-
}
|
|
33357
|
-
|
|
33358
34009
|
const sampleInfoTileXShim = 8;
|
|
33359
34010
|
const sampleInfoTileWidth = 16;
|
|
33360
34011
|
|
|
@@ -33963,7 +34614,11 @@ class SampleNameViewport {
|
|
|
33963
34614
|
|
|
33964
34615
|
checkCanvas() {
|
|
33965
34616
|
|
|
33966
|
-
|
|
34617
|
+
let width = 0;
|
|
34618
|
+
if (true === this.browser.showSampleNames) {
|
|
34619
|
+
width = undefined === this.browser.sampleNameViewportWidth ? 0 : this.browser.sampleNameViewportWidth;
|
|
34620
|
+
}
|
|
34621
|
+
|
|
33967
34622
|
this.ctx.canvas.width = width * window.devicePixelRatio;
|
|
33968
34623
|
this.ctx.canvas.style.width = `${width}px`;
|
|
33969
34624
|
|
|
@@ -35075,7 +35730,7 @@ class MergedTrack extends TrackBase {
|
|
|
35075
35730
|
let element = document.createElement('div');
|
|
35076
35731
|
element.textContent = 'Separate tracks';
|
|
35077
35732
|
|
|
35078
|
-
function click(e) {
|
|
35733
|
+
async function click(e) {
|
|
35079
35734
|
|
|
35080
35735
|
// Capture state which will be nulled when track is removed
|
|
35081
35736
|
const groupAutoscale = this.autoscale;
|
|
@@ -35091,9 +35746,10 @@ class MergedTrack extends TrackBase {
|
|
|
35091
35746
|
track.autoscaleGroup = name;
|
|
35092
35747
|
}
|
|
35093
35748
|
track.isMergedTrack = false;
|
|
35094
|
-
browser.addTrack(track
|
|
35749
|
+
browser.addTrack(track);
|
|
35095
35750
|
}
|
|
35096
|
-
browser.updateViews();
|
|
35751
|
+
await browser.updateViews();
|
|
35752
|
+
browser.reorderTracks();
|
|
35097
35753
|
}
|
|
35098
35754
|
|
|
35099
35755
|
return {element, click}
|
|
@@ -35188,7 +35844,7 @@ class OverlayTrackButton extends NavbarButton {
|
|
|
35188
35844
|
}
|
|
35189
35845
|
}
|
|
35190
35846
|
|
|
35191
|
-
function trackOverlayClickHandler(e) {
|
|
35847
|
+
async function trackOverlayClickHandler(e) {
|
|
35192
35848
|
|
|
35193
35849
|
if (true === isOverlayTrackCriteriaMet(this.browser)) {
|
|
35194
35850
|
|
|
@@ -35225,9 +35881,9 @@ function trackOverlayClickHandler(e) {
|
|
|
35225
35881
|
track.trackView.dispose();
|
|
35226
35882
|
}
|
|
35227
35883
|
|
|
35228
|
-
this.browser.addTrack(
|
|
35229
|
-
mergedTrack.trackView.updateViews();
|
|
35230
|
-
|
|
35884
|
+
this.browser.addTrack(mergedTrack);
|
|
35885
|
+
await mergedTrack.trackView.updateViews();
|
|
35886
|
+
this.browser.reorderTracks();
|
|
35231
35887
|
}
|
|
35232
35888
|
|
|
35233
35889
|
}
|
|
@@ -36834,14 +37490,45 @@ class InputDialog {
|
|
|
36834
37490
|
|
|
36835
37491
|
|
|
36836
37492
|
present(options, e) {
|
|
36837
|
-
|
|
36838
37493
|
this.label.textContent = options.label;
|
|
36839
37494
|
this._input.value = options.value;
|
|
36840
37495
|
this.callback = options.callback || options.click;
|
|
36841
37496
|
|
|
36842
|
-
|
|
36843
|
-
|
|
36844
|
-
|
|
37497
|
+
this.container.style.display = '';
|
|
37498
|
+
|
|
37499
|
+
// Get click coordinates
|
|
37500
|
+
const clickX = e.clientX;
|
|
37501
|
+
const clickY = e.clientY;
|
|
37502
|
+
|
|
37503
|
+
// Get dialog dimensions
|
|
37504
|
+
const dialogWidth = this.container.offsetWidth;
|
|
37505
|
+
const dialogHeight = this.container.offsetHeight;
|
|
37506
|
+
|
|
37507
|
+
// Calculate available space
|
|
37508
|
+
const windowWidth = window.innerWidth;
|
|
37509
|
+
const windowHeight = window.innerHeight;
|
|
37510
|
+
|
|
37511
|
+
// Calculate position to keep dialog on screen
|
|
37512
|
+
let left = clickX;
|
|
37513
|
+
let top = clickY;
|
|
37514
|
+
|
|
37515
|
+
// Adjust horizontal position if dialog would go off screen
|
|
37516
|
+
if (left + dialogWidth > windowWidth) {
|
|
37517
|
+
left = windowWidth - dialogWidth - 10; // 10px padding from edge
|
|
37518
|
+
}
|
|
37519
|
+
|
|
37520
|
+
// Adjust vertical position if dialog would go off screen
|
|
37521
|
+
if (top + dialogHeight > windowHeight) {
|
|
37522
|
+
top = windowHeight - dialogHeight - 10; // 10px padding from edge
|
|
37523
|
+
}
|
|
37524
|
+
|
|
37525
|
+
// Ensure minimum distance from edges
|
|
37526
|
+
left = Math.max(10, left);
|
|
37527
|
+
top = Math.max(10, top);
|
|
37528
|
+
|
|
37529
|
+
// Apply positions
|
|
37530
|
+
this.container.style.left = `${left}px`;
|
|
37531
|
+
this.container.style.top = `${top}px`;
|
|
36845
37532
|
}
|
|
36846
37533
|
}
|
|
36847
37534
|
|
|
@@ -47621,7 +48308,7 @@ class AlignmentTrack extends TrackBase {
|
|
|
47621
48308
|
if (!this.colorBy) {
|
|
47622
48309
|
this.colorBy = this.hasPairs ? "unexpectedPair" : "none";
|
|
47623
48310
|
}
|
|
47624
|
-
|
|
48311
|
+
|
|
47625
48312
|
let pixelTop = options.pixelTop - BAMTrack.coverageTrackHeight;
|
|
47626
48313
|
if (this.top) {
|
|
47627
48314
|
ctx.translate(0, this.top);
|
|
@@ -48328,7 +49015,12 @@ class AlignmentTrack extends TrackBase {
|
|
|
48328
49015
|
this.trackView.repaintViews();
|
|
48329
49016
|
}
|
|
48330
49017
|
|
|
48331
|
-
return {
|
|
49018
|
+
return {
|
|
49019
|
+
name: undefined,
|
|
49020
|
+
element: createCheckbox(menuItem.label, showCheck),
|
|
49021
|
+
click: clickHandler,
|
|
49022
|
+
init: undefined
|
|
49023
|
+
}
|
|
48332
49024
|
}
|
|
48333
49025
|
|
|
48334
49026
|
|
|
@@ -48375,7 +49067,12 @@ class AlignmentTrack extends TrackBase {
|
|
|
48375
49067
|
}
|
|
48376
49068
|
}
|
|
48377
49069
|
|
|
48378
|
-
return {
|
|
49070
|
+
return {
|
|
49071
|
+
name: undefined,
|
|
49072
|
+
element: createCheckbox(menuItem.label, showCheck),
|
|
49073
|
+
dialog: clickHandler,
|
|
49074
|
+
init: undefined
|
|
49075
|
+
}
|
|
48379
49076
|
|
|
48380
49077
|
}
|
|
48381
49078
|
|
|
@@ -48497,38 +49194,88 @@ class AlignmentTrack extends TrackBase {
|
|
|
48497
49194
|
});
|
|
48498
49195
|
}
|
|
48499
49196
|
|
|
49197
|
+
list.push('<hr/>');
|
|
49198
|
+
const softClips = clickedAlignment.softClippedBlocks();
|
|
48500
49199
|
list.push({
|
|
48501
49200
|
label: 'View read sequence',
|
|
48502
49201
|
click: () => {
|
|
48503
|
-
const seqstring = clickedAlignment.seq;
|
|
48504
|
-
|
|
48505
|
-
this.browser.alert.present("Read sequence: *");
|
|
48506
|
-
} else {
|
|
48507
|
-
this.browser.alert.present(seqstring);
|
|
48508
|
-
}
|
|
49202
|
+
const seqstring = clickedAlignment.seq;
|
|
49203
|
+
this.browser.alert.present(seqstring && seqstring !== "*" ? seqstring : "Read sequence: *");
|
|
48509
49204
|
}
|
|
48510
49205
|
});
|
|
48511
49206
|
|
|
49207
|
+
if (softClips.left && softClips.left.len > 0) {
|
|
49208
|
+
list.push({
|
|
49209
|
+
label: 'View left soft-clipped sequence',
|
|
49210
|
+
click: () => {
|
|
49211
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
|
|
49212
|
+
this.browser.alert.present(clippedSequence);
|
|
49213
|
+
}
|
|
49214
|
+
});
|
|
49215
|
+
}
|
|
49216
|
+
|
|
49217
|
+
if (softClips.right && softClips.right.len > 0) {
|
|
49218
|
+
list.push({
|
|
49219
|
+
label: 'View right soft-clipped sequence',
|
|
49220
|
+
click: () => {
|
|
49221
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
|
|
49222
|
+
this.browser.alert.present(clippedSequence);
|
|
49223
|
+
}
|
|
49224
|
+
});
|
|
49225
|
+
}
|
|
49226
|
+
|
|
49227
|
+
list.push('<hr/>');
|
|
49228
|
+
|
|
48512
49229
|
if (isSecureContext()) {
|
|
48513
49230
|
list.push({
|
|
48514
49231
|
label: 'Copy read sequence',
|
|
48515
49232
|
click: async () => {
|
|
48516
|
-
const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
|
|
48517
49233
|
try {
|
|
48518
|
-
await navigator.clipboard.writeText(seq);
|
|
49234
|
+
await navigator.clipboard.writeText(clickedAlignment.seq);
|
|
48519
49235
|
} catch (e) {
|
|
48520
49236
|
console.error(e);
|
|
48521
49237
|
this.browser.alert.present(`error copying sequence to clipboard ${e}`);
|
|
48522
49238
|
}
|
|
48523
|
-
|
|
48524
49239
|
}
|
|
48525
49240
|
});
|
|
49241
|
+
|
|
49242
|
+
if (softClips.left && softClips.left.len > 0) {
|
|
49243
|
+
list.push({
|
|
49244
|
+
label: 'Copy left soft-clipped sequence',
|
|
49245
|
+
click: async () => {
|
|
49246
|
+
try {
|
|
49247
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
|
|
49248
|
+
await navigator.clipboard.writeText(clippedSequence);
|
|
49249
|
+
} catch (e) {
|
|
49250
|
+
console.error(e);
|
|
49251
|
+
this.browser.alert.present(`error copying sequence to clipboard ${e}`);
|
|
49252
|
+
}
|
|
49253
|
+
}
|
|
49254
|
+
});
|
|
49255
|
+
}
|
|
49256
|
+
|
|
49257
|
+
if (softClips.right && softClips.right.len > 0) {
|
|
49258
|
+
list.push({
|
|
49259
|
+
label: 'Copy right soft-clipped sequence',
|
|
49260
|
+
click: async () => {
|
|
49261
|
+
try {
|
|
49262
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
|
|
49263
|
+
await navigator.clipboard.writeText(clippedSequence);
|
|
49264
|
+
} catch (e) {
|
|
49265
|
+
console.error(e);
|
|
49266
|
+
this.browser.alert.present(`error copying sequence to clipboard ${e}`);
|
|
49267
|
+
}
|
|
49268
|
+
}
|
|
49269
|
+
});
|
|
49270
|
+
}
|
|
48526
49271
|
}
|
|
48527
49272
|
|
|
48528
|
-
// TODO if genome supports blat
|
|
49273
|
+
// TODO test if genome supports blat
|
|
48529
49274
|
const seqstring = clickedAlignment.seq;
|
|
48530
49275
|
if (seqstring && "*" !== seqstring) {
|
|
48531
49276
|
|
|
49277
|
+
list.push('<hr/>');
|
|
49278
|
+
|
|
48532
49279
|
if (seqstring.length < maxSequenceSize$1) {
|
|
48533
49280
|
list.push({
|
|
48534
49281
|
label: 'BLAT read sequence',
|
|
@@ -48546,7 +49293,7 @@ class AlignmentTrack extends TrackBase {
|
|
|
48546
49293
|
list.push({
|
|
48547
49294
|
label: 'BLAT left soft-clipped sequence',
|
|
48548
49295
|
click: () => {
|
|
48549
|
-
const clippedSequence = seqstring.
|
|
49296
|
+
const clippedSequence = seqstring.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
|
|
48550
49297
|
const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
|
|
48551
49298
|
const name = `${clickedAlignment.readName} - blat left clip`;
|
|
48552
49299
|
const title = `${this.name} - ${name}`;
|
|
@@ -48558,7 +49305,7 @@ class AlignmentTrack extends TrackBase {
|
|
|
48558
49305
|
list.push({
|
|
48559
49306
|
label: 'BLAT right soft-clipped sequence',
|
|
48560
49307
|
click: () => {
|
|
48561
|
-
const clippedSequence = seqstring.
|
|
49308
|
+
const clippedSequence = seqstring.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
|
|
48562
49309
|
const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
|
|
48563
49310
|
const name = `${clickedAlignment.readName} - blat right clip`;
|
|
48564
49311
|
const title = `${this.name} - ${name}`;
|
|
@@ -48607,7 +49354,7 @@ class AlignmentTrack extends TrackBase {
|
|
|
48607
49354
|
const offsetY = y - this.top;
|
|
48608
49355
|
const genomicLocation = clickState.genomicLocation;
|
|
48609
49356
|
|
|
48610
|
-
if(features.packedGroups) {
|
|
49357
|
+
if (features.packedGroups) {
|
|
48611
49358
|
let minGroupY = Number.MAX_VALUE;
|
|
48612
49359
|
for (let group of features.packedGroups.values()) {
|
|
48613
49360
|
minGroupY = Math.min(minGroupY, group.pixelTop);
|
|
@@ -50429,7 +51176,7 @@ class RemoteFile {
|
|
|
50429
51176
|
|
|
50430
51177
|
let url = this.url.slice(); // slice => copy
|
|
50431
51178
|
if (this.config.oauthToken) {
|
|
50432
|
-
const token = resolveToken(this.config.oauthToken);
|
|
51179
|
+
const token = await resolveToken(this.config.oauthToken);
|
|
50433
51180
|
headers['Authorization'] = `Bearer ${token}`;
|
|
50434
51181
|
}
|
|
50435
51182
|
|
|
@@ -55194,7 +55941,7 @@ function create_nested_array(value, shape) {
|
|
|
55194
55941
|
async function openH5File(options) {
|
|
55195
55942
|
|
|
55196
55943
|
// Some clients (notably igv-webapp) pass a File reference in the url field. Fix this
|
|
55197
|
-
if(options.url && isBlobLike(options.url)) {
|
|
55944
|
+
if (options.url && isBlobLike(options.url)) {
|
|
55198
55945
|
options.file = options.url;
|
|
55199
55946
|
options.url = undefined;
|
|
55200
55947
|
}
|
|
@@ -55224,26 +55971,30 @@ async function openH5File(options) {
|
|
|
55224
55971
|
|
|
55225
55972
|
async function readExternalIndex(options) {
|
|
55226
55973
|
|
|
55227
|
-
|
|
55228
|
-
if(options.indexReader) {
|
|
55229
|
-
indexReader = options.indexReader;
|
|
55230
|
-
}
|
|
55231
|
-
else if(options.index) {
|
|
55974
|
+
if (options.index) {
|
|
55232
55975
|
return options.index
|
|
55233
|
-
} else
|
|
55234
|
-
indexReader
|
|
55235
|
-
|
|
55236
|
-
|
|
55237
|
-
|
|
55238
|
-
|
|
55239
|
-
|
|
55240
|
-
|
|
55976
|
+
} else {
|
|
55977
|
+
let indexReader;
|
|
55978
|
+
if (options.indexReader) {
|
|
55979
|
+
indexReader = options.indexReader;
|
|
55980
|
+
} else {
|
|
55981
|
+
let indexOptions;
|
|
55982
|
+
if (options.indexURL) {
|
|
55983
|
+
indexOptions = Object.assign({url: options.indexURL}, options);
|
|
55984
|
+
} else if (options.indexPath) {
|
|
55985
|
+
indexOptions = Object.assign({path: options.indexPath}, options);
|
|
55986
|
+
} else if (options.indexFile) {
|
|
55987
|
+
indexOptions = Object.assign({file: options.indexFile}, options);
|
|
55988
|
+
} else {
|
|
55989
|
+
return undefined
|
|
55990
|
+
}
|
|
55991
|
+
indexReader = getReaderFor(indexOptions);
|
|
55992
|
+
}
|
|
55241
55993
|
const indexFileContents = await indexReader.read();
|
|
55242
55994
|
const indexFileJson = new TextDecoder().decode(indexFileContents);
|
|
55243
55995
|
return JSON.parse(indexFileJson)
|
|
55244
|
-
} else {
|
|
55245
|
-
return undefined
|
|
55246
55996
|
}
|
|
55997
|
+
|
|
55247
55998
|
}
|
|
55248
55999
|
|
|
55249
56000
|
|
|
@@ -55321,9 +56072,9 @@ class HDF5Reader {
|
|
|
55321
56072
|
* @param {string} h5_file - path for the pytor file
|
|
55322
56073
|
* @param {integer} bin_size - bin size
|
|
55323
56074
|
*/
|
|
55324
|
-
constructor(
|
|
56075
|
+
constructor(config, bin_size=100000){
|
|
55325
56076
|
|
|
55326
|
-
this.
|
|
56077
|
+
this.config = config;
|
|
55327
56078
|
this.bin_size = bin_size;
|
|
55328
56079
|
this.h5_obj = undefined;
|
|
55329
56080
|
this.pytorKeys = [];
|
|
@@ -55333,11 +56084,8 @@ class HDF5Reader {
|
|
|
55333
56084
|
async fetch(){
|
|
55334
56085
|
|
|
55335
56086
|
if(!this.h5_obj) {
|
|
55336
|
-
this.
|
|
55337
|
-
|
|
55338
|
-
fetchSize: 1000000,
|
|
55339
|
-
maxSize: 200000000
|
|
55340
|
-
});
|
|
56087
|
+
const options = Object.assign(this.config, {fetchSize: 1000000, maxSize: 200000000});
|
|
56088
|
+
this.h5_obj = await openH5File(options);
|
|
55341
56089
|
}
|
|
55342
56090
|
return this.h5_obj
|
|
55343
56091
|
}
|
|
@@ -64373,7 +65121,7 @@ class CNVPytorTrack extends TrackBase {
|
|
|
64373
65121
|
this.set_available_callers();
|
|
64374
65122
|
|
|
64375
65123
|
} else {
|
|
64376
|
-
this.cnvpytor_obj = new HDF5Reader(this.config
|
|
65124
|
+
this.cnvpytor_obj = new HDF5Reader(this.config, this.bin_size);
|
|
64377
65125
|
// get chrom list that currently user viewing
|
|
64378
65126
|
let chroms = [ ...new Set(this.browser.referenceFrameList.map(val => val.chr))];
|
|
64379
65127
|
|
|
@@ -69361,6 +70109,23 @@ class ReferenceFrame {
|
|
|
69361
70109
|
return this.genome.getChromosome(this.chr)
|
|
69362
70110
|
}
|
|
69363
70111
|
|
|
70112
|
+
/**
|
|
70113
|
+
* Update reference frame based on new viewport width
|
|
70114
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
70115
|
+
*/
|
|
70116
|
+
updateForViewportWidth(viewportWidth) {
|
|
70117
|
+
const {chr} = this;
|
|
70118
|
+
const {bpLength} = this.getChromosome();
|
|
70119
|
+
const viewportWidthBP = this.toBP(viewportWidth);
|
|
70120
|
+
|
|
70121
|
+
// viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
|
|
70122
|
+
if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
|
|
70123
|
+
this.bpPerPixel = bpLength / viewportWidth;
|
|
70124
|
+
} else {
|
|
70125
|
+
this.end = this.start + this.toBP(viewportWidth);
|
|
70126
|
+
}
|
|
70127
|
+
}
|
|
70128
|
+
|
|
69364
70129
|
getMultiLocusLabelBPLengthOnly(pixels) {
|
|
69365
70130
|
const margin = ' ';
|
|
69366
70131
|
const ss = Math.floor(this.start) + 1;
|
|
@@ -69446,7 +70211,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
|
|
|
69446
70211
|
})
|
|
69447
70212
|
}
|
|
69448
70213
|
|
|
69449
|
-
const _version = "3.
|
|
70214
|
+
const _version = "3.3.0";
|
|
69450
70215
|
function version() {
|
|
69451
70216
|
return _version
|
|
69452
70217
|
}
|
|
@@ -69490,10 +70255,24 @@ class ChromosomeSelectWidget {
|
|
|
69490
70255
|
this.select.setAttribute('name', 'chromosome-select-widget');
|
|
69491
70256
|
this.container.appendChild(this.select);
|
|
69492
70257
|
|
|
69493
|
-
this.select.addEventListener('change', () => {
|
|
70258
|
+
this.select.addEventListener('change', async () => {
|
|
69494
70259
|
this.select.blur();
|
|
69495
70260
|
if (this.select.value !== '' && maximumSequenceCountExceeded !== this.select.value) {
|
|
69496
|
-
|
|
70261
|
+
|
|
70262
|
+
if (this.select.value.trim().toLowerCase() === "all" || this.select.value === "*") {
|
|
70263
|
+
if (browser.genome.wholeGenomeView) {
|
|
70264
|
+
const wgChr = browser.genome.getChromosome("all");
|
|
70265
|
+
return {chr: "all", start: 0, end: wgChr.bpLength}
|
|
70266
|
+
}
|
|
70267
|
+
} else {
|
|
70268
|
+
const chromosome = await browser.genome.loadChromosome(this.select.value);
|
|
70269
|
+
const locusObject = {chr: chromosome.name};
|
|
70270
|
+
if (locusObject.start === undefined && locusObject.end === undefined) {
|
|
70271
|
+
locusObject.start = 0;
|
|
70272
|
+
locusObject.end = chromosome.bpLength;
|
|
70273
|
+
}
|
|
70274
|
+
browser.updateLoci([locusObject]);
|
|
70275
|
+
}
|
|
69497
70276
|
}
|
|
69498
70277
|
});
|
|
69499
70278
|
|
|
@@ -69518,7 +70297,9 @@ class ChromosomeSelectWidget {
|
|
|
69518
70297
|
this.genome = genome;
|
|
69519
70298
|
|
|
69520
70299
|
// Start with explicit chromosome name list
|
|
69521
|
-
const list = genome.wgChromosomeNames
|
|
70300
|
+
const list = (genome.wgChromosomeNames) ?
|
|
70301
|
+
genome.wgChromosomeNames.map(nm => genome.getChromosomeDisplayName(nm)) :
|
|
70302
|
+
[];
|
|
69522
70303
|
|
|
69523
70304
|
if (this.showAllChromosomes && genome.chromosomeNames.length > 1) {
|
|
69524
70305
|
const exclude = new Set(list);
|
|
@@ -70744,7 +71525,7 @@ class ResponsiveNavbar {
|
|
|
70744
71525
|
});
|
|
70745
71526
|
|
|
70746
71527
|
this.searchInput.addEventListener('change', () => {
|
|
70747
|
-
|
|
71528
|
+
this.doSearch(this.searchInput.value);
|
|
70748
71529
|
});
|
|
70749
71530
|
|
|
70750
71531
|
const searchIconContainer = document.createElement('div');
|
|
@@ -70755,7 +71536,7 @@ class ResponsiveNavbar {
|
|
|
70755
71536
|
searchIconContainer.appendChild(searchIcon);
|
|
70756
71537
|
|
|
70757
71538
|
searchIconContainer.addEventListener('click', () => {
|
|
70758
|
-
|
|
71539
|
+
this.doSearch(this.searchInput.value);
|
|
70759
71540
|
});
|
|
70760
71541
|
|
|
70761
71542
|
this.windowSizePanel = new WindowSizePanel(locusSizeGroup, browser);
|
|
@@ -70903,6 +71684,22 @@ class ResponsiveNavbar {
|
|
|
70903
71684
|
this.navigation.style.display = 'flex';
|
|
70904
71685
|
}
|
|
70905
71686
|
|
|
71687
|
+
/**
|
|
71688
|
+
|
|
71689
|
+
* Search for the locus string -- this function is called from the navbar search box, and is not part of the API.
|
|
71690
|
+
* Wraps ```search``` and presents an error dialog if false.
|
|
71691
|
+
*
|
|
71692
|
+
* @param locus
|
|
71693
|
+
* @param init
|
|
71694
|
+
* @returns {Promise<void>}
|
|
71695
|
+
*/
|
|
71696
|
+
async doSearch(locus) {
|
|
71697
|
+
const success = await this.browser.search(locus);
|
|
71698
|
+
if (!success) {
|
|
71699
|
+
this.browser.alert.present(new Error(`Unrecognized locus: <b> ${locus} </b>`));
|
|
71700
|
+
}
|
|
71701
|
+
}
|
|
71702
|
+
|
|
70906
71703
|
}
|
|
70907
71704
|
|
|
70908
71705
|
function logo() {
|
|
@@ -72154,6 +72951,9 @@ class ChromAliasDefaults {
|
|
|
72154
72951
|
aliasRecord["_cap"] = cap;
|
|
72155
72952
|
}
|
|
72156
72953
|
|
|
72954
|
+
const chrPrefixAlias = aliasRecord.chr.startsWith("chr") ? aliasRecord.chr.substring(3) : "chr" + aliasRecord.chr;
|
|
72955
|
+
aliasRecord["_chrprefix_"] = chrPrefixAlias;
|
|
72956
|
+
|
|
72157
72957
|
}
|
|
72158
72958
|
|
|
72159
72959
|
}
|
|
@@ -72178,7 +72978,7 @@ class ChromAliasBB {
|
|
|
72178
72978
|
}
|
|
72179
72979
|
|
|
72180
72980
|
async preload(chrNames) {
|
|
72181
|
-
|
|
72981
|
+
await this.reader.preload();
|
|
72182
72982
|
for(let nm of chrNames) {
|
|
72183
72983
|
await this.search(nm);
|
|
72184
72984
|
}
|
|
@@ -72230,12 +73030,6 @@ class ChromAliasBB {
|
|
|
72230
73030
|
}
|
|
72231
73031
|
return this.aliasRecordCache.get(alias)
|
|
72232
73032
|
}
|
|
72233
|
-
|
|
72234
|
-
async getChromosomeNames() {
|
|
72235
|
-
await this.reader.loadHeader();
|
|
72236
|
-
return Array.from(this.reader.chrNames)
|
|
72237
|
-
}
|
|
72238
|
-
|
|
72239
73033
|
}
|
|
72240
73034
|
|
|
72241
73035
|
/**
|
|
@@ -72260,10 +73054,9 @@ class ChromAliasFile {
|
|
|
72260
73054
|
this.genome = genome;
|
|
72261
73055
|
}
|
|
72262
73056
|
|
|
72263
|
-
async preload() {
|
|
72264
|
-
|
|
73057
|
+
async preload(chrNames) {
|
|
73058
|
+
// A no-op, this is a text file, no need to preload
|
|
72265
73059
|
}
|
|
72266
|
-
|
|
72267
73060
|
/**
|
|
72268
73061
|
* Return the canonical chromosome name for the alias. If none found return the alias
|
|
72269
73062
|
*
|
|
@@ -72429,7 +73222,7 @@ class CytobandFile {
|
|
|
72429
73222
|
for (let line of lines) {
|
|
72430
73223
|
|
|
72431
73224
|
const tokens = line.split("\t");
|
|
72432
|
-
const chrName = tokens[0];
|
|
73225
|
+
const chrName = tokens[0];
|
|
72433
73226
|
if (!lastChr) lastChr = chrName;
|
|
72434
73227
|
|
|
72435
73228
|
if (chrName !== lastChr) {
|
|
@@ -72447,37 +73240,20 @@ class CytobandFile {
|
|
|
72447
73240
|
bands.push(new Cytoband(start, end, name, stain));
|
|
72448
73241
|
}
|
|
72449
73242
|
}
|
|
72450
|
-
|
|
72451
|
-
|
|
72452
|
-
|
|
72453
|
-
/**
|
|
72454
|
-
* Infer genome chromosome names from cytoband data. This should only be used as a last resort
|
|
72455
|
-
*/
|
|
72456
|
-
async getChromosomeNames() {
|
|
72457
|
-
if(this.cytobands.size === 0) {
|
|
72458
|
-
await this.#loadCytobands();
|
|
72459
|
-
}
|
|
72460
|
-
return Array.from(this.cytobands.keys())
|
|
72461
|
-
}
|
|
72462
|
-
|
|
72463
|
-
/**
|
|
72464
|
-
* Infer chromosome objects from cytoband data. This should only be used as last resort.
|
|
72465
|
-
*/
|
|
72466
|
-
async getChromosomes() {
|
|
72467
|
-
if(this.cytobands.size === 0) {
|
|
72468
|
-
await this.#loadCytobands();
|
|
73243
|
+
if(bands.length > 0) {
|
|
73244
|
+
this.cytobands.set(lastChr, bands);
|
|
72469
73245
|
}
|
|
72470
73246
|
|
|
72471
|
-
const chromosomes = [];
|
|
72472
|
-
let order = 0;
|
|
72473
|
-
for(let [chrName, cytoList] of this.cytobands.entries()) {
|
|
72474
|
-
chromosomes.push(new Chromosome(chrName, order++, cytoList[cytoList.length - 1].end));
|
|
72475
|
-
}
|
|
72476
|
-
return chromosomes
|
|
72477
73247
|
}
|
|
72478
73248
|
|
|
72479
73249
|
}
|
|
72480
73250
|
|
|
73251
|
+
const ucsdIDMap = new Map([
|
|
73252
|
+
["1kg_ref", "hg18"],
|
|
73253
|
+
["1kg_v37", "hg19"],
|
|
73254
|
+
["b37", "hg19"]
|
|
73255
|
+
]);
|
|
73256
|
+
|
|
72481
73257
|
/**
|
|
72482
73258
|
* The Genome class represents an assembly and consists of the following elements
|
|
72483
73259
|
* sequence - Object representing the DNA sequence
|
|
@@ -72502,8 +73278,10 @@ class Genome {
|
|
|
72502
73278
|
this.config = config;
|
|
72503
73279
|
this.browser = browser;
|
|
72504
73280
|
this.id = config.id || generateGenomeID(config);
|
|
73281
|
+
this.ucscID = config.ucscID || ucsdIDMap.get(this.id) || this.id;
|
|
73282
|
+
this.blatDB = config.blatDB || this.ucscID;
|
|
72505
73283
|
this.name = config.name;
|
|
72506
|
-
this.nameSet = config.nameSet;
|
|
73284
|
+
this.nameSet = config.nameSet || 'ucsc';
|
|
72507
73285
|
}
|
|
72508
73286
|
|
|
72509
73287
|
|
|
@@ -72511,49 +73289,45 @@ class Genome {
|
|
|
72511
73289
|
|
|
72512
73290
|
const config = this.config;
|
|
72513
73291
|
|
|
73292
|
+
// Load sequence
|
|
72514
73293
|
this.sequence = await loadSequence(config, this.browser);
|
|
72515
73294
|
|
|
72516
|
-
|
|
72517
|
-
|
|
73295
|
+
|
|
73296
|
+
// Load cytobands. This is optional but required to support the ideogram. Only needed for whole genome view
|
|
73297
|
+
if(false !== config.showIdeogram && false !== config.wholeGenomeView) {
|
|
73298
|
+
if (config.cytobandURL) {
|
|
73299
|
+
this.cytobandSource = new CytobandFile(config.cytobandURL, Object.assign({}, config));
|
|
73300
|
+
} else if (config.cytobandBbURL) {
|
|
73301
|
+
this.cytobandSource = new CytobandFileBB(config.cytobandBbURL, Object.assign({}, config), this);
|
|
73302
|
+
}
|
|
73303
|
+
}
|
|
73304
|
+
|
|
73305
|
+
// Search for chromosomes, that is an array of chromosome objects containing name and length. This is
|
|
73306
|
+
// optional but required to support whole genome view.
|
|
73307
|
+
if (this.sequence.chromosomes) {
|
|
73308
|
+
this.chromosomes = this.sequence.chromosomes;
|
|
73309
|
+
} else if (config.chromSizesURL) {
|
|
72518
73310
|
this.chromosomes = await loadChromSizes(config.chromSizesURL);
|
|
72519
73311
|
} else {
|
|
72520
|
-
|
|
72521
|
-
this.chromosomes = this.sequence.chromosomes || new Map();
|
|
73312
|
+
this.chromosomes = new Map(); // Cache, chromosome are added as they are loaded
|
|
72522
73313
|
}
|
|
72523
73314
|
|
|
72524
|
-
|
|
73315
|
+
// Search for chromosome names. This is optional but required to support the chromosome pulldown
|
|
73316
|
+
if (this.sequence.chromosomeNames) {
|
|
73317
|
+
this.chromosomeNames = this.sequence.chromosomeNames; // Twobit files can supply chromosome names unless they use an external index
|
|
73318
|
+
} else if (this.chromosomes.size > 0) {
|
|
72525
73319
|
this.chromosomeNames = Array.from(this.chromosomes.keys());
|
|
72526
73320
|
}
|
|
72527
73321
|
|
|
73322
|
+
// Chromosome alias
|
|
72528
73323
|
if (config.chromAliasBbURL) {
|
|
72529
73324
|
this.chromAlias = new ChromAliasBB(config.chromAliasBbURL, Object.assign({}, config), this);
|
|
72530
|
-
if (!this.chromosomeNames) {
|
|
72531
|
-
this.chromosomeNames = await this.chromAlias.getChromosomeNames();
|
|
72532
|
-
}
|
|
72533
73325
|
} else if (config.aliasURL) {
|
|
72534
73326
|
this.chromAlias = new ChromAliasFile(config.aliasURL, Object.assign({}, config), this);
|
|
72535
73327
|
} else if (this.chromosomeNames) {
|
|
72536
73328
|
this.chromAlias = new ChromAliasDefaults(this.id, this.chromosomeNames);
|
|
72537
73329
|
}
|
|
72538
73330
|
|
|
72539
|
-
if (config.cytobandBbURL) {
|
|
72540
|
-
this.cytobandSource = new CytobandFileBB(config.cytobandBbURL, Object.assign({}, config), this);
|
|
72541
|
-
} else if (config.cytobandURL) {
|
|
72542
|
-
this.cytobandSource = new CytobandFile(config.cytobandURL, Object.assign({}, config));
|
|
72543
|
-
}
|
|
72544
|
-
|
|
72545
|
-
// Last resort for chromosome information -- retrieve it from the cytoband source if supported
|
|
72546
|
-
if (!this.chromosomeNames && typeof this.cytobandSource.getChromosomeNames === 'function') {
|
|
72547
|
-
this.chromosomeNames = await this.cytobandSource.getChromosomeNames();
|
|
72548
|
-
}
|
|
72549
|
-
if (this.chromosomes.size === 0 && typeof this.cytobandSource.getChromosomes === 'function') {
|
|
72550
|
-
const c = await this.cytobandSource.getChromosomes();
|
|
72551
|
-
for (let chromosome of c) {
|
|
72552
|
-
this.chromosomes.set(c.name, c);
|
|
72553
|
-
}
|
|
72554
|
-
}
|
|
72555
|
-
|
|
72556
|
-
|
|
72557
73331
|
if (false !== config.wholeGenomeView && this.chromosomes.size > 0) {
|
|
72558
73332
|
// Set chromosome order for WG view and chromosome pulldown. If chromosome order is not specified sort
|
|
72559
73333
|
if (config.chromosomeOrder) {
|
|
@@ -72605,9 +73379,9 @@ class Genome {
|
|
|
72605
73379
|
getHomeChromosomeName() {
|
|
72606
73380
|
if (this.showWholeGenomeView() && this.chromosomes.has("all")) {
|
|
72607
73381
|
return "all"
|
|
72608
|
-
} else {
|
|
73382
|
+
} else if (this.chromosomeNames) {
|
|
72609
73383
|
return this.chromosomeNames[0]
|
|
72610
|
-
}
|
|
73384
|
+
} else ;
|
|
72611
73385
|
}
|
|
72612
73386
|
|
|
72613
73387
|
getChromosomeName(chr) {
|
|
@@ -72658,18 +73432,18 @@ class Genome {
|
|
|
72658
73432
|
if (!aliasRecord && chr !== chr.toLowerCase()) {
|
|
72659
73433
|
aliasRecord = await this.chromAlias.search(chr.toLowerCase());
|
|
72660
73434
|
}
|
|
72661
|
-
if(aliasRecord) {
|
|
73435
|
+
if (aliasRecord) {
|
|
72662
73436
|
// Add some aliases for case insensitivy
|
|
72663
73437
|
const upper = aliasRecord.chr.toUpperCase();
|
|
72664
73438
|
const lower = aliasRecord.chr.toLowerCase();
|
|
72665
73439
|
const cap = aliasRecord.chr.charAt(0).toUpperCase() + aliasRecord.chr.slice(1);
|
|
72666
|
-
if(aliasRecord.chr !== upper) {
|
|
73440
|
+
if (aliasRecord.chr !== upper) {
|
|
72667
73441
|
aliasRecord["_uppercase"] = upper;
|
|
72668
73442
|
}
|
|
72669
|
-
if(aliasRecord.chr !== lower) {
|
|
73443
|
+
if (aliasRecord.chr !== lower) {
|
|
72670
73444
|
aliasRecord["_lowercase"] = lower;
|
|
72671
73445
|
}
|
|
72672
|
-
if(aliasRecord.chr !== cap) {
|
|
73446
|
+
if (aliasRecord.chr !== cap) {
|
|
72673
73447
|
aliasRecord["_cap"] = cap;
|
|
72674
73448
|
}
|
|
72675
73449
|
}
|
|
@@ -72801,6 +73575,10 @@ class Genome {
|
|
|
72801
73575
|
return undefined
|
|
72802
73576
|
}
|
|
72803
73577
|
}
|
|
73578
|
+
|
|
73579
|
+
getHubURLs() {
|
|
73580
|
+
return this.config.hubs
|
|
73581
|
+
}
|
|
72804
73582
|
}
|
|
72805
73583
|
|
|
72806
73584
|
/**
|
|
@@ -72843,7 +73621,7 @@ function generateGenomeID(config) {
|
|
|
72843
73621
|
}
|
|
72844
73622
|
}
|
|
72845
73623
|
|
|
72846
|
-
var igvCss = '.igv-ui-dropdown {\n cursor: default;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n background-color: white;\n}\n.igv-ui-dropdown > div {\n overflow-y: auto;\n overflow-x: hidden;\n background-color: white;\n}\n.igv-ui-dropdown > div > div {\n padding: 4px;\n width: 100%;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: 1px;\n background-color: white;\n}\n.igv-ui-dropdown > div > div:last-child {\n border-bottom-color: transparent;\n border-bottom-width: 0;\n}\n.igv-ui-dropdown > div > div:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white;\n}\n.igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-width: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px;\n}\n.igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-popover > div:last-child {\n user-select: text;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white;\n border-bottom-width: 0;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-ui-popover > div:last-child > div {\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.igv-ui-popover > div:last-child > div > span {\n font-weight: bolder;\n}\n.igv-ui-popover > div:last-child hr {\n width: 100%;\n}\n\n.igv-ui-alert-dialog-container {\n position: fixed;\n top: 20%;\n left: 50%;\n transform: translateX(-50%);\n z-index: 2048;\n box-sizing: content-box;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:first-child {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n background-color: white;\n border: unset;\n}\n.igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n width: 300px;\n height: fit-content;\n padding-bottom: 16px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input > div {\n width: fit-content;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input[type=range] {\n width: 70%;\n -webkit-appearance: none;\n background: linear-gradient(90deg, white, black);\n outline: none;\n margin: 0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input output {\n display: block;\n height: 100%;\n width: 20%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n padding-top: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div {\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n\n.igv-ui-generic-container {\n position: fixed;\n cursor: pointer;\n background-color: white;\n z-index: 2048;\n box-sizing: content-box;\n width: 300px;\n height: 200px;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-ui-generic-container > div:nth-child(1) > div {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-ui-colorpicker-container {\n background-color: #eee;\n height: fit-content;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n.igv-ui-colorpicker-container > div:nth-child(2) {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-colorpicker-container > div:nth-child(3) {\n box-sizing: border-box;\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n padding: 0.5rem;\n font-family: \"Open Sans\", sans-serif;\n font-size: 1rem;\n font-weight: 400;\n width: 100%;\n border-top-style: solid;\n border-top-width: thin;\n border-top-color: #373737;\n}\n.igv-ui-colorpicker-container > div:nth-child(4) {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n margin: 2px;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 0;\n border-color: white;\n border-radius: 3px;\n}\n\n.igv-ui-color-swatch-shim {\n cursor: pointer;\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n}\n.igv-ui-dialog .igv-ui-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-ui-panel, .igv-ui-panel-row, .igv-ui-panel-column {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column;\n}\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row;\n}\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-table {\n background-color: white;\n}\n\n.igv-ui-table thead {\n position: sticky;\n top: 0;\n}\n\n.igv-ui-table th {\n text-align: left;\n}\n\n.igv-ui-table td {\n padding-right: 20px;\n}\n\n.igv-ui-table tr:hover {\n background-color: lightblue;\n}\n\n.igv-ui-center-fixed {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 240px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-hidden {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-icon-button {\n cursor: pointer;\n position: relative;\n width: 24px;\n height: 24px;\n margin-left: 4px;\n margin-right: 4px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:first-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: -18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:last-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.igv-navbar-text-button {\n cursor: pointer;\n position: relative;\n margin-left: 2px;\n margin-right: 2px;\n border: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar-text-button > div:nth-child(2) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 0;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-text-button > div:nth-child(3) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 42px;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n#igv-text-button-label {\n text-anchor: middle;\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-inactive rect {\n stroke: #737373;\n fill: white;\n}\n.igv-navbar-text-button-svg-inactive text {\n fill: #737373;\n}\n.igv-navbar-text-button-svg-inactive tspan {\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-hover rect {\n stroke: #737373;\n fill: #737373;\n}\n.igv-navbar-text-button-svg-hover text {\n fill: white;\n}\n.igv-navbar-text-button-svg-hover tspan {\n dominant-baseline: middle;\n}\n\n#igv-save-svg-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-svg-group text {\n fill: #737373;\n}\n\n#igv-save-svg-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-svg-group:hover text {\n fill: white;\n}\n\n#igv-save-png-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-png-group text {\n fill: #737373;\n}\n\n#igv-save-png-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-png-group:hover text {\n fill: white;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 256;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 12px;\n height: 12px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 12px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label > div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n.igv-multi-locus-ruler-label > div {\n cursor: pointer;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 50%;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 32;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 32;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container > div:nth-child(1) i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n max-width: 400px;\n z-index: 512;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n}\n.igv-roi-menu > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu > div:last-child > div:not(:first-child):hover {\n cursor: pointer;\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu > div:last-child div:first-child {\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n resize: both;\n overflow: hidden;\n width: min-content;\n max-width: 1600px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n cursor: default;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-description {\n padding: 4px;\n margin-left: 4px;\n word-break: break-all;\n overflow-y: auto;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-goto-explainer {\n margin-top: 5px;\n margin-left: 4px;\n color: #7F7F7F;\n font-style: italic;\n height: 24px;\n border-top: solid lightgray;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: #7f7f7f;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n overflow: auto;\n height: 360px;\n flex: 1 1 auto;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: transparent;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n min-height: 32px;\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n cursor: pointer;\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n pointer-events: auto;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.picker_wrapper.no_alpha .picker_alpha {\n display: none;\n}\n\n.picker_wrapper.no_editor .picker_editor {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.picker_wrapper.no_cancel .picker_cancel {\n display: none;\n}\n\n.layout_default.picker_wrapper {\n display: flex;\n flex-flow: row wrap;\n justify-content: space-between;\n align-items: stretch;\n font-size: 10px;\n width: 25em;\n padding: 0.5em;\n}\n\n.layout_default.picker_wrapper input, .layout_default.picker_wrapper button {\n font-size: 1rem;\n}\n\n.layout_default.picker_wrapper > * {\n margin: 0.5em;\n}\n\n.layout_default.picker_wrapper::before {\n content: \"\";\n display: block;\n width: 100%;\n height: 0;\n order: 1;\n}\n\n.layout_default .picker_slider, .layout_default .picker_selector {\n padding: 1em;\n}\n\n.layout_default .picker_hue {\n width: 100%;\n}\n\n.layout_default .picker_sl {\n flex: 1 1 auto;\n}\n\n.layout_default .picker_sl::before {\n content: \"\";\n display: block;\n padding-bottom: 100%;\n}\n\n.layout_default .picker_editor {\n order: 1;\n width: 6.5rem;\n}\n\n.layout_default .picker_editor input {\n width: 100%;\n height: 100%;\n}\n\n.layout_default .picker_sample {\n order: 1;\n flex: 1 1 auto;\n}\n\n.layout_default .picker_done, .layout_default .picker_cancel {\n order: 1;\n}\n\n.picker_wrapper {\n box-sizing: border-box;\n background: #f2f2f2;\n box-shadow: 0 0 0 1px silver;\n cursor: default;\n font-family: sans-serif;\n color: #444;\n pointer-events: auto;\n}\n\n.picker_wrapper:focus {\n outline: none;\n}\n\n.picker_wrapper button, .picker_wrapper input {\n box-sizing: border-box;\n border: none;\n box-shadow: 0 0 0 1px silver;\n outline: none;\n}\n\n.picker_wrapper button:focus, .picker_wrapper button:active, .picker_wrapper input:focus, .picker_wrapper input:active {\n box-shadow: 0 0 2px 1px #1e90ff;\n}\n\n.picker_wrapper button {\n padding: 0.4em 0.6em;\n cursor: pointer;\n background-color: #f5f5f5;\n background-image: linear-gradient(0deg, gainsboro, transparent);\n}\n\n.picker_wrapper button:active {\n background-image: linear-gradient(0deg, transparent, gainsboro);\n}\n\n.picker_wrapper button:hover {\n background-color: #fff;\n}\n\n.picker_selector {\n position: absolute;\n z-index: 1;\n display: block;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n border: 2px solid #fff;\n border-radius: 100%;\n box-shadow: 0 0 3px 1px #67b9ff;\n background: currentColor;\n cursor: pointer;\n}\n\n.picker_slider .picker_selector {\n border-radius: 2px;\n}\n\n.picker_hue {\n position: relative;\n background-image: linear-gradient(90deg, red, yellow, lime, cyan, blue, magenta, red);\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_sl {\n position: relative;\n box-shadow: 0 0 0 1px silver;\n background-image: linear-gradient(180deg, white, rgba(255, 255, 255, 0) 50%), linear-gradient(0deg, black, rgba(0, 0, 0, 0) 50%), linear-gradient(90deg, #808080, rgba(128, 128, 128, 0));\n}\n\n.picker_alpha, .picker_sample {\n position: relative;\n background: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%) 0 0/2em 2em, linear-gradient(45deg, lightgrey 25%, white 25%, white 75%, lightgrey 75%) 1em 1em/2em 2em;\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_alpha .picker_selector, .picker_sample .picker_selector {\n background: none;\n}\n\n.picker_editor input {\n font-family: monospace;\n padding: 0.2em 0.4em;\n}\n\n.picker_sample::before {\n content: \"\";\n position: absolute;\n display: block;\n width: 100%;\n height: 100%;\n background: currentColor;\n}\n\n.picker_arrow {\n position: absolute;\n z-index: -1;\n}\n\n.picker_wrapper.popup {\n position: absolute;\n z-index: 2;\n margin: 1.5em;\n}\n\n.picker_wrapper.popup, .picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n background: #f2f2f2;\n box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.4);\n}\n\n.picker_wrapper.popup .picker_arrow {\n width: 3em;\n height: 3em;\n margin: 0;\n}\n\n.picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n content: \"\";\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n z-index: -99;\n}\n\n.picker_wrapper.popup .picker_arrow::before {\n width: 100%;\n height: 100%;\n -webkit-transform: skew(45deg);\n transform: skew(45deg);\n -webkit-transform-origin: 0 100%;\n transform-origin: 0 100%;\n}\n\n.picker_wrapper.popup .picker_arrow::after {\n width: 150%;\n height: 150%;\n box-shadow: none;\n}\n\n.popup.popup_top {\n bottom: 100%;\n left: 0;\n}\n\n.popup.popup_top .picker_arrow {\n bottom: 0;\n left: 0;\n -webkit-transform: rotate(-90deg);\n transform: rotate(-90deg);\n}\n\n.popup.popup_bottom {\n top: 100%;\n left: 0;\n}\n\n.popup.popup_bottom .picker_arrow {\n top: 0;\n left: 0;\n -webkit-transform: rotate(90deg) scale(1, -1);\n transform: rotate(90deg) scale(1, -1);\n}\n\n.popup.popup_left {\n top: 0;\n right: 100%;\n}\n\n.popup.popup_left .picker_arrow {\n top: 0;\n right: 0;\n -webkit-transform: scale(-1, 1);\n transform: scale(-1, 1);\n}\n\n.popup.popup_right {\n top: 0;\n left: 100%;\n}\n\n.popup.popup_right .picker_arrow {\n top: 0;\n left: 0;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n min-height: 160px;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n line-height: 1;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n position: relative;\n margin-top: 5px;\n width: 100%;\n}\n.igv-axis-column > div > div {\n z-index: 512;\n position: absolute;\n top: 8px;\n left: 8px;\n width: fit-content;\n height: fit-content;\n background-color: transparent;\n display: grid;\n align-items: start;\n justify-items: center;\n}\n.igv-axis-column > div > div > input {\n display: block;\n margin: unset;\n cursor: pointer;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-info-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.igv-track-drag-column .igv-track-drag-handle-color {\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover-color {\n background-color: #787878;\n}\n.igv-track-drag-column .igv-track-drag-handle-selected-color {\n background-color: #0963fa;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-vertical-center {\n margin: 0 !important;\n top: 50% !important;\n -ms-transform: translateY(-50%) !important;\n transform: translateY(-50%) !important;\n}\n\n/*# sourceMappingURL=igv.css.map */\n';
|
|
73624
|
+
var igvCss = '.igv-ui-dropdown {\n cursor: default;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n background-color: white;\n}\n.igv-ui-dropdown > div {\n overflow-y: auto;\n overflow-x: hidden;\n background-color: white;\n}\n.igv-ui-dropdown > div > div {\n padding: 4px;\n width: 100%;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: 1px;\n background-color: white;\n}\n.igv-ui-dropdown > div > div:last-child {\n border-bottom-color: transparent;\n border-bottom-width: 0;\n}\n.igv-ui-dropdown > div > div:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white;\n}\n.igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-width: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px;\n}\n.igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-popover > div:last-child {\n user-select: text;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white;\n border-bottom-width: 0;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-ui-popover > div:last-child > div {\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.igv-ui-popover > div:last-child > div > span {\n font-weight: bolder;\n}\n.igv-ui-popover > div:last-child hr {\n width: 100%;\n}\n\n.igv-ui-alert-dialog-container {\n position: fixed;\n top: 20%;\n left: 50%;\n transform: translateX(-50%);\n z-index: 2048;\n box-sizing: content-box;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:first-child {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n background-color: white;\n border: unset;\n}\n.igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n width: 300px;\n height: fit-content;\n padding-bottom: 16px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input > div {\n width: fit-content;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input[type=range] {\n width: 70%;\n -webkit-appearance: none;\n background: linear-gradient(90deg, white, black);\n outline: none;\n margin: 0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input output {\n display: block;\n height: 100%;\n width: 20%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n padding-top: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div {\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n\n.igv-ui-generic-container {\n position: fixed;\n cursor: pointer;\n background-color: white;\n z-index: 2048;\n box-sizing: content-box;\n width: 300px;\n height: 200px;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-ui-generic-container > div:nth-child(1) > div {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-ui-colorpicker-container {\n background-color: #eee;\n height: fit-content;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n.igv-ui-colorpicker-container > div:nth-child(2) {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-colorpicker-container > div:nth-child(3) {\n box-sizing: border-box;\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n padding: 0.5rem;\n font-family: \"Open Sans\", sans-serif;\n font-size: 1rem;\n font-weight: 400;\n width: 100%;\n border-top-style: solid;\n border-top-width: thin;\n border-top-color: #373737;\n}\n.igv-ui-colorpicker-container > div:nth-child(4) {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n margin: 2px;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 0;\n border-color: white;\n border-radius: 3px;\n}\n\n.igv-ui-color-swatch-shim {\n cursor: pointer;\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n}\n.igv-ui-dialog .igv-ui-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-ui-panel, .igv-ui-panel-row, .igv-ui-panel-column {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column;\n}\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row;\n}\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-table {\n background-color: white;\n}\n\n.igv-ui-table thead {\n position: sticky;\n top: 0;\n}\n\n.igv-ui-table th {\n text-align: left;\n}\n\n.igv-ui-table td {\n padding-right: 20px;\n}\n\n.igv-ui-table tr:hover {\n background-color: lightblue;\n}\n\n.igv-ui-center-fixed {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 240px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-hidden {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-icon-button {\n cursor: pointer;\n position: relative;\n width: 24px;\n height: 24px;\n margin-left: 4px;\n margin-right: 4px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:first-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: -18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:last-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.igv-navbar-text-button {\n cursor: pointer;\n position: relative;\n margin-left: 2px;\n margin-right: 2px;\n border: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar-text-button > div:nth-child(2) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 0;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-text-button > div:nth-child(3) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 42px;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n#igv-text-button-label {\n text-anchor: middle;\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-inactive rect {\n stroke: #737373;\n fill: white;\n}\n.igv-navbar-text-button-svg-inactive text {\n fill: #737373;\n}\n.igv-navbar-text-button-svg-inactive tspan {\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-hover rect {\n stroke: #737373;\n fill: #737373;\n}\n.igv-navbar-text-button-svg-hover text {\n fill: white;\n}\n.igv-navbar-text-button-svg-hover tspan {\n dominant-baseline: middle;\n}\n\n#igv-save-svg-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-svg-group text {\n fill: #737373;\n}\n\n#igv-save-svg-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-svg-group:hover text {\n fill: white;\n}\n\n#igv-save-png-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-png-group text {\n fill: #737373;\n}\n\n#igv-save-png-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-png-group:hover text {\n fill: white;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 256;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 12px;\n height: 12px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 12px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label > div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n.igv-multi-locus-ruler-label > div {\n cursor: pointer;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 50%;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 32;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 32;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container > div:nth-child(1) i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n max-width: 400px;\n z-index: 2048;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n}\n.igv-roi-menu > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu > div:last-child > div:not(:first-child):hover {\n cursor: pointer;\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu > div:last-child div:first-child {\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n resize: both;\n overflow: hidden;\n width: min-content;\n max-width: 1600px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n cursor: default;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-description {\n padding: 4px;\n margin-left: 4px;\n word-break: break-all;\n overflow-y: auto;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-goto-explainer {\n margin-top: 5px;\n margin-left: 4px;\n color: #7F7F7F;\n font-style: italic;\n height: 24px;\n border-top: solid lightgray;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: #7f7f7f;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n overflow: auto;\n height: 360px;\n flex: 1 1 auto;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: transparent;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n min-height: 32px;\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n cursor: pointer;\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n pointer-events: auto;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.picker_wrapper.no_alpha .picker_alpha {\n display: none;\n}\n\n.picker_wrapper.no_editor .picker_editor {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.picker_wrapper.no_cancel .picker_cancel {\n display: none;\n}\n\n.layout_default.picker_wrapper {\n display: flex;\n flex-flow: row wrap;\n justify-content: space-between;\n align-items: stretch;\n font-size: 10px;\n width: 25em;\n padding: 0.5em;\n}\n\n.layout_default.picker_wrapper input, .layout_default.picker_wrapper button {\n font-size: 1rem;\n}\n\n.layout_default.picker_wrapper > * {\n margin: 0.5em;\n}\n\n.layout_default.picker_wrapper::before {\n content: \"\";\n display: block;\n width: 100%;\n height: 0;\n order: 1;\n}\n\n.layout_default .picker_slider, .layout_default .picker_selector {\n padding: 1em;\n}\n\n.layout_default .picker_hue {\n width: 100%;\n}\n\n.layout_default .picker_sl {\n flex: 1 1 auto;\n}\n\n.layout_default .picker_sl::before {\n content: \"\";\n display: block;\n padding-bottom: 100%;\n}\n\n.layout_default .picker_editor {\n order: 1;\n width: 6.5rem;\n}\n\n.layout_default .picker_editor input {\n width: 100%;\n height: 100%;\n}\n\n.layout_default .picker_sample {\n order: 1;\n flex: 1 1 auto;\n}\n\n.layout_default .picker_done, .layout_default .picker_cancel {\n order: 1;\n}\n\n.picker_wrapper {\n box-sizing: border-box;\n background: #f2f2f2;\n box-shadow: 0 0 0 1px silver;\n cursor: default;\n font-family: sans-serif;\n color: #444;\n pointer-events: auto;\n}\n\n.picker_wrapper:focus {\n outline: none;\n}\n\n.picker_wrapper button, .picker_wrapper input {\n box-sizing: border-box;\n border: none;\n box-shadow: 0 0 0 1px silver;\n outline: none;\n}\n\n.picker_wrapper button:focus, .picker_wrapper button:active, .picker_wrapper input:focus, .picker_wrapper input:active {\n box-shadow: 0 0 2px 1px #1e90ff;\n}\n\n.picker_wrapper button {\n padding: 0.4em 0.6em;\n cursor: pointer;\n background-color: #f5f5f5;\n background-image: linear-gradient(0deg, gainsboro, transparent);\n}\n\n.picker_wrapper button:active {\n background-image: linear-gradient(0deg, transparent, gainsboro);\n}\n\n.picker_wrapper button:hover {\n background-color: #fff;\n}\n\n.picker_selector {\n position: absolute;\n z-index: 1;\n display: block;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n border: 2px solid #fff;\n border-radius: 100%;\n box-shadow: 0 0 3px 1px #67b9ff;\n background: currentColor;\n cursor: pointer;\n}\n\n.picker_slider .picker_selector {\n border-radius: 2px;\n}\n\n.picker_hue {\n position: relative;\n background-image: linear-gradient(90deg, red, yellow, lime, cyan, blue, magenta, red);\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_sl {\n position: relative;\n box-shadow: 0 0 0 1px silver;\n background-image: linear-gradient(180deg, white, rgba(255, 255, 255, 0) 50%), linear-gradient(0deg, black, rgba(0, 0, 0, 0) 50%), linear-gradient(90deg, #808080, rgba(128, 128, 128, 0));\n}\n\n.picker_alpha, .picker_sample {\n position: relative;\n background: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%) 0 0/2em 2em, linear-gradient(45deg, lightgrey 25%, white 25%, white 75%, lightgrey 75%) 1em 1em/2em 2em;\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_alpha .picker_selector, .picker_sample .picker_selector {\n background: none;\n}\n\n.picker_editor input {\n font-family: monospace;\n padding: 0.2em 0.4em;\n}\n\n.picker_sample::before {\n content: \"\";\n position: absolute;\n display: block;\n width: 100%;\n height: 100%;\n background: currentColor;\n}\n\n.picker_arrow {\n position: absolute;\n z-index: -1;\n}\n\n.picker_wrapper.popup {\n position: absolute;\n z-index: 2;\n margin: 1.5em;\n}\n\n.picker_wrapper.popup, .picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n background: #f2f2f2;\n box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.4);\n}\n\n.picker_wrapper.popup .picker_arrow {\n width: 3em;\n height: 3em;\n margin: 0;\n}\n\n.picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n content: \"\";\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n z-index: -99;\n}\n\n.picker_wrapper.popup .picker_arrow::before {\n width: 100%;\n height: 100%;\n -webkit-transform: skew(45deg);\n transform: skew(45deg);\n -webkit-transform-origin: 0 100%;\n transform-origin: 0 100%;\n}\n\n.picker_wrapper.popup .picker_arrow::after {\n width: 150%;\n height: 150%;\n box-shadow: none;\n}\n\n.popup.popup_top {\n bottom: 100%;\n left: 0;\n}\n\n.popup.popup_top .picker_arrow {\n bottom: 0;\n left: 0;\n -webkit-transform: rotate(-90deg);\n transform: rotate(-90deg);\n}\n\n.popup.popup_bottom {\n top: 100%;\n left: 0;\n}\n\n.popup.popup_bottom .picker_arrow {\n top: 0;\n left: 0;\n -webkit-transform: rotate(90deg) scale(1, -1);\n transform: rotate(90deg) scale(1, -1);\n}\n\n.popup.popup_left {\n top: 0;\n right: 100%;\n}\n\n.popup.popup_left .picker_arrow {\n top: 0;\n right: 0;\n -webkit-transform: scale(-1, 1);\n transform: scale(-1, 1);\n}\n\n.popup.popup_right {\n top: 0;\n left: 100%;\n}\n\n.popup.popup_right .picker_arrow {\n top: 0;\n left: 0;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n min-height: 160px;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n line-height: 1;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n position: relative;\n margin-top: 5px;\n width: 100%;\n}\n.igv-axis-column > div > div {\n z-index: 512;\n position: absolute;\n top: 8px;\n left: 8px;\n width: fit-content;\n height: fit-content;\n background-color: transparent;\n display: grid;\n align-items: start;\n justify-items: center;\n}\n.igv-axis-column > div > div > input {\n display: block;\n margin: unset;\n cursor: pointer;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-info-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.igv-track-drag-column .igv-track-drag-handle-color {\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover-color {\n background-color: #787878;\n}\n.igv-track-drag-column .igv-track-drag-handle-selected-color {\n background-color: #0963fa;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-vertical-center {\n margin: 0 !important;\n top: 50% !important;\n -ms-transform: translateY(-50%) !important;\n transform: translateY(-50%) !important;\n}\n\n/*# sourceMappingURL=igv.css.map */\n';
|
|
72847
73625
|
|
|
72848
73626
|
/**
|
|
72849
73627
|
* Manages XQTL selections.
|
|
@@ -73377,7 +74155,7 @@ class Browser {
|
|
|
73377
74155
|
this.dataRangeDialog = new DataRangeDialog(this, this.root);
|
|
73378
74156
|
this.dataRangeDialog.container.id = `igv-data-range-dialog-${guid$2()}`;
|
|
73379
74157
|
|
|
73380
|
-
this.genericColorPicker = new GenericColorPicker({
|
|
74158
|
+
this.genericColorPicker = new GenericColorPicker({parent: this.root, width: 180});
|
|
73381
74159
|
this.genericColorPicker.container.id = `igv-track-color-picker-${guid$2()}`;
|
|
73382
74160
|
|
|
73383
74161
|
this.sliderDialog = new SliderDialog(this.root);
|
|
@@ -73387,10 +74165,10 @@ class Browser {
|
|
|
73387
74165
|
|
|
73388
74166
|
getSampleNameViewportWidth() {
|
|
73389
74167
|
|
|
73390
|
-
if (undefined === this.sampleNameViewportWidth) {
|
|
74168
|
+
if (false === this.showSampleNames || undefined === this.sampleNameViewportWidth) {
|
|
73391
74169
|
return 0
|
|
73392
74170
|
} else {
|
|
73393
|
-
return
|
|
74171
|
+
return this.sampleNameViewportWidth
|
|
73394
74172
|
}
|
|
73395
74173
|
|
|
73396
74174
|
}
|
|
@@ -73552,7 +74330,8 @@ class Browser {
|
|
|
73552
74330
|
} else {
|
|
73553
74331
|
session = options;
|
|
73554
74332
|
}
|
|
73555
|
-
|
|
74333
|
+
|
|
74334
|
+
await this.loadSessionObject(session);
|
|
73556
74335
|
}
|
|
73557
74336
|
|
|
73558
74337
|
/**
|
|
@@ -73581,8 +74360,7 @@ class Browser {
|
|
|
73581
74360
|
config = new XMLSession(string, knownGenomes);
|
|
73582
74361
|
|
|
73583
74362
|
} else if (filename.endsWith("hub.txt")) {
|
|
73584
|
-
|
|
73585
|
-
const hub = await Hub.loadHub(urlOrFile, options);
|
|
74363
|
+
const hub = await loadHub(urlOrFile);
|
|
73586
74364
|
const genomeConfig = hub.getGenomeConfig();
|
|
73587
74365
|
config = {
|
|
73588
74366
|
reference: genomeConfig
|
|
@@ -73673,7 +74451,7 @@ class Browser {
|
|
|
73673
74451
|
}
|
|
73674
74452
|
|
|
73675
74453
|
// ROIs
|
|
73676
|
-
if(session.showROIOverlays !== undefined) {
|
|
74454
|
+
if (session.showROIOverlays !== undefined) {
|
|
73677
74455
|
this.roiManager.showOverlays = session.showROIOverlays;
|
|
73678
74456
|
}
|
|
73679
74457
|
this.roiManager.clearROIs();
|
|
@@ -73687,12 +74465,16 @@ class Browser {
|
|
|
73687
74465
|
// Sample info
|
|
73688
74466
|
const localSampleInfoFiles = [];
|
|
73689
74467
|
if (session.sampleinfo) {
|
|
73690
|
-
for (const
|
|
73691
|
-
|
|
73692
|
-
|
|
73693
|
-
|
|
74468
|
+
for (const sampleInfoConfig of session.sampleinfo) {
|
|
74469
|
+
// The "file" property is recorded in the session when a local file is referenced. It can't be used
|
|
74470
|
+
// on reloading, its only purpose is to present an alert to the user. This could also be used
|
|
74471
|
+
// to prompt the user to load the file manually, but we don't currently do that.
|
|
74472
|
+
if (sampleInfoConfig.file) {
|
|
74473
|
+
localSampleInfoFiles.push(sampleInfoConfig.file);
|
|
73694
74474
|
} else {
|
|
73695
|
-
this.loadSampleInfo(
|
|
74475
|
+
// this.loadSampleInfo(sampleInfoConfig)
|
|
74476
|
+
await this.sampleInfo.loadSampleInfo(sampleInfoConfig);
|
|
74477
|
+
|
|
73696
74478
|
}
|
|
73697
74479
|
|
|
73698
74480
|
}
|
|
@@ -73733,23 +74515,14 @@ class Browser {
|
|
|
73733
74515
|
}
|
|
73734
74516
|
}
|
|
73735
74517
|
|
|
73736
|
-
|
|
73737
|
-
|
|
73738
|
-
|
|
73739
|
-
|
|
73740
|
-
await
|
|
74518
|
+
// Load a hidden track -- used to populate searchable database without creating a track
|
|
74519
|
+
const configHidden = nonLocalTrackConfigurations.filter(config => true === config.hidden);
|
|
74520
|
+
for (const config of configHidden) {
|
|
74521
|
+
const featureSource = FeatureSource(config, this.genome);
|
|
74522
|
+
await featureSource.getFeatures({chr: "1", start: 0, end: Number.MAX_SAFE_INTEGER});
|
|
73741
74523
|
}
|
|
73742
74524
|
|
|
73743
|
-
|
|
73744
|
-
if (this.trackViews.some(tv => tv.track.selected)) {
|
|
73745
|
-
this.navbar.setEnableTrackSelection(true);
|
|
73746
|
-
}
|
|
73747
|
-
|
|
73748
|
-
this.updateUIWithReferenceFrameList();
|
|
73749
|
-
|
|
73750
|
-
this.updateLocusSearchWidget();
|
|
73751
|
-
|
|
73752
|
-
return trackConfigurations
|
|
74525
|
+
await this.loadTrackList(nonLocalTrackConfigurations);
|
|
73753
74526
|
|
|
73754
74527
|
}
|
|
73755
74528
|
|
|
@@ -73810,13 +74583,8 @@ class Browser {
|
|
|
73810
74583
|
}
|
|
73811
74584
|
|
|
73812
74585
|
if (genomeChange) {
|
|
73813
|
-
|
|
73814
|
-
|
|
73815
|
-
// TODO -- refactor this so "hub" is not loaded twice
|
|
73816
|
-
const hub = await Hub.loadHub(genomeConfig.hubURL);
|
|
73817
|
-
trackConfigurations = hub.getGroupedTrackConfigurations();
|
|
73818
|
-
}
|
|
73819
|
-
this.fireEvent('genomechange', [{genome, trackConfigurations}]);
|
|
74586
|
+
|
|
74587
|
+
this.fireEvent('genomechange', [{genome}]);
|
|
73820
74588
|
|
|
73821
74589
|
if (this.circularView) {
|
|
73822
74590
|
this.circularView.setAssembly({
|
|
@@ -73855,7 +74623,7 @@ class Browser {
|
|
|
73855
74623
|
let genomeConfig;
|
|
73856
74624
|
const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
|
|
73857
74625
|
if (isHubGenome) {
|
|
73858
|
-
const hub = await
|
|
74626
|
+
const hub = await loadHub(idOrConfig.hubURL || idOrConfig.url);
|
|
73859
74627
|
genomeConfig = hub.getGenomeConfig();
|
|
73860
74628
|
} else if (isString$2(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
|
|
73861
74629
|
// Either an ID, a json string, or an object missing required properties.
|
|
@@ -73885,22 +74653,9 @@ class Browser {
|
|
|
73885
74653
|
|
|
73886
74654
|
await this.loadTrackList(tracks);
|
|
73887
74655
|
|
|
73888
|
-
await this.updateViews();
|
|
73889
|
-
|
|
73890
74656
|
return this.genome
|
|
73891
74657
|
}
|
|
73892
74658
|
|
|
73893
|
-
/**
|
|
73894
|
-
* Load a UCSC single-file genome assembly hub.
|
|
73895
|
-
* @param options
|
|
73896
|
-
* @returns {Promise<void>}
|
|
73897
|
-
*/
|
|
73898
|
-
async loadTrackHub(options) {
|
|
73899
|
-
const hub = await Hub.loadHub(options.url, options);
|
|
73900
|
-
const genomeConfig = setDefaults(hub.getGenomeConfig());
|
|
73901
|
-
return this.loadGenome(genomeConfig)
|
|
73902
|
-
}
|
|
73903
|
-
|
|
73904
74659
|
/**
|
|
73905
74660
|
* Called after a session load, search, pan (horizontal drag), or resize
|
|
73906
74661
|
*
|
|
@@ -73997,18 +74752,23 @@ class Browser {
|
|
|
73997
74752
|
}
|
|
73998
74753
|
|
|
73999
74754
|
const promises = [];
|
|
74000
|
-
for (
|
|
74001
|
-
promises.push(this
|
|
74755
|
+
for (const config of configList) {
|
|
74756
|
+
promises.push(this.#loadTrackHelper(config));
|
|
74002
74757
|
}
|
|
74003
74758
|
|
|
74004
74759
|
const loadedTracks = await Promise.all(promises);
|
|
74005
74760
|
|
|
74006
|
-
|
|
74007
|
-
|
|
74008
|
-
|
|
74009
|
-
if (groupAutoscaleViews.length > 0) {
|
|
74010
|
-
this.updateViews();
|
|
74761
|
+
// If any tracks are selected show the selection buttons
|
|
74762
|
+
if (this.trackViews.some(({track}) => track.selected)) {
|
|
74763
|
+
this.navbar.setEnableTrackSelection(true);
|
|
74011
74764
|
}
|
|
74765
|
+
|
|
74766
|
+
this.reorderTracks();
|
|
74767
|
+
|
|
74768
|
+
await resize.call(this);
|
|
74769
|
+
|
|
74770
|
+
this.fireEvent('trackorderchanged', [this.getTrackOrder()]);
|
|
74771
|
+
|
|
74012
74772
|
return loadedTracks
|
|
74013
74773
|
}
|
|
74014
74774
|
|
|
@@ -74022,54 +74782,23 @@ class Browser {
|
|
|
74022
74782
|
*/
|
|
74023
74783
|
async loadTrack(config) {
|
|
74024
74784
|
|
|
74025
|
-
|
|
74026
|
-
|
|
74027
|
-
|
|
74028
|
-
const newTrack = this._loadTrack(config);
|
|
74029
|
-
|
|
74030
|
-
if (newTrack && config.autoscaleGroup) {
|
|
74031
|
-
// Await newTrack load and update all views
|
|
74032
|
-
await newTrack;
|
|
74785
|
+
const loadedTracks = await this.loadTrackList([config]);
|
|
74786
|
+
if (config.autoscaleGroup) {
|
|
74033
74787
|
this.updateViews();
|
|
74034
74788
|
}
|
|
74035
|
-
|
|
74036
|
-
return newTrack
|
|
74789
|
+
return loadedTracks[0]
|
|
74037
74790
|
}
|
|
74038
74791
|
|
|
74039
|
-
|
|
74040
|
-
* Return a promise to load a track. Private function used by loadTrack() and loadTrackList()
|
|
74041
|
-
*
|
|
74042
|
-
* @param config
|
|
74043
|
-
* @returns {*}
|
|
74044
|
-
*/
|
|
74045
|
-
|
|
74046
|
-
async _loadTrack(config) {
|
|
74792
|
+
async #loadTrackHelper(config) {
|
|
74047
74793
|
|
|
74048
74794
|
// config might be json
|
|
74049
74795
|
if (isString$2(config)) {
|
|
74050
74796
|
config = JSON.parse(config);
|
|
74051
74797
|
}
|
|
74052
74798
|
|
|
74799
|
+
let track;
|
|
74053
74800
|
try {
|
|
74054
|
-
|
|
74055
|
-
// Load a hidden track -- used to populate searchable database without creating a track
|
|
74056
|
-
if (config.hidden) {
|
|
74057
|
-
const featureSource = FeatureSource(config, this.genome);
|
|
74058
|
-
await featureSource.getFeatures({chr: "1", start: 0, end: Number.MAX_SAFE_INTEGER});
|
|
74059
|
-
return
|
|
74060
|
-
}
|
|
74061
|
-
|
|
74062
|
-
const newTrack = await this.createTrack(config);
|
|
74063
|
-
|
|
74064
|
-
if ('sampleinfo' === config.type) {
|
|
74065
|
-
this.layoutChange();
|
|
74066
|
-
return
|
|
74067
|
-
} else if (undefined === newTrack) {
|
|
74068
|
-
return
|
|
74069
|
-
}
|
|
74070
|
-
|
|
74071
|
-
return this.addTrack(config, newTrack)
|
|
74072
|
-
|
|
74801
|
+
track = await this.createTrack(config);
|
|
74073
74802
|
} catch (error) {
|
|
74074
74803
|
|
|
74075
74804
|
let msg = error.message || error.error || error.toString();
|
|
@@ -74086,62 +74815,53 @@ class Browser {
|
|
|
74086
74815
|
}
|
|
74087
74816
|
|
|
74088
74817
|
msg = `${msg} : ${isFile(config.url) ? config.url.name : config.url}`;
|
|
74089
|
-
// msg += (": " + FileUtils.isFile(config.url) ? config.url.name : config.url)
|
|
74090
74818
|
const err = new Error(msg);
|
|
74091
74819
|
console.error(err);
|
|
74092
74820
|
throw err
|
|
74093
|
-
// this.alert.present(new Error(msg), undefined)
|
|
74094
74821
|
}
|
|
74095
|
-
}
|
|
74096
74822
|
|
|
74097
|
-
|
|
74823
|
+
if (track) {
|
|
74824
|
+
return await this.addTrack(track)
|
|
74825
|
+
} else {
|
|
74826
|
+
return undefined
|
|
74827
|
+
}
|
|
74098
74828
|
|
|
74829
|
+
}
|
|
74830
|
+
|
|
74831
|
+
async addTrack(track) {
|
|
74099
74832
|
|
|
74100
74833
|
// Set order field of track here, otherwise track order might get shuffled during asynchronous load
|
|
74101
|
-
if (undefined ===
|
|
74102
|
-
|
|
74834
|
+
if (undefined === track.order) {
|
|
74835
|
+
track.order = this.trackViews.length;
|
|
74103
74836
|
}
|
|
74104
74837
|
|
|
74105
|
-
const trackView = new TrackView(this, this.columnContainer,
|
|
74838
|
+
const trackView = new TrackView(this, this.columnContainer, track);
|
|
74106
74839
|
this.trackViews.push(trackView);
|
|
74107
74840
|
toggleTrackLabels(this.trackViews, this.doShowTrackLabels);
|
|
74108
74841
|
|
|
74109
|
-
if (typeof
|
|
74842
|
+
if (typeof track.postInit === 'function') {
|
|
74110
74843
|
try {
|
|
74111
74844
|
trackView.startSpinner();
|
|
74112
|
-
await
|
|
74845
|
+
await track.postInit();
|
|
74113
74846
|
} finally {
|
|
74114
74847
|
trackView.stopSpinner();
|
|
74115
74848
|
}
|
|
74116
74849
|
}
|
|
74117
74850
|
|
|
74118
|
-
if (
|
|
74119
|
-
// Group autoscale will get updated later (as a group)
|
|
74120
|
-
if (config.sync) {
|
|
74121
|
-
await trackView.updateViews();
|
|
74122
|
-
} else {
|
|
74123
|
-
trackView.updateViews();
|
|
74124
|
-
}
|
|
74125
|
-
}
|
|
74126
|
-
|
|
74127
|
-
if (typeof newTrack.hasSamples === 'function' && newTrack.hasSamples()) {
|
|
74851
|
+
if (typeof track.hasSamples === 'function' && track.hasSamples()) {
|
|
74128
74852
|
|
|
74129
74853
|
if (this.sampleInfo.hasAttributes()) {
|
|
74130
74854
|
this.sampleInfoControl.setButtonVisibility(true);
|
|
74131
74855
|
}
|
|
74132
74856
|
|
|
74133
74857
|
if (this.config.showSampleNameButton !== false) {
|
|
74134
|
-
this.sampleNameControl.show();
|
|
74858
|
+
this.sampleNameControl.show();
|
|
74135
74859
|
}
|
|
74136
74860
|
}
|
|
74137
74861
|
|
|
74138
|
-
|
|
74139
|
-
this.reorderTracks();
|
|
74140
|
-
this.fireEvent('trackorderchanged', [this.getTrackOrder()]);
|
|
74141
|
-
|
|
74142
|
-
newTrack.trackView.enableTrackSelection(this.navbar.getEnableTrackSelection());
|
|
74862
|
+
track.trackView.enableTrackSelection(this.navbar.getEnableTrackSelection());
|
|
74143
74863
|
|
|
74144
|
-
return
|
|
74864
|
+
return track
|
|
74145
74865
|
|
|
74146
74866
|
}
|
|
74147
74867
|
|
|
@@ -74286,7 +75006,6 @@ class Browser {
|
|
|
74286
75006
|
}
|
|
74287
75007
|
}
|
|
74288
75008
|
|
|
74289
|
-
|
|
74290
75009
|
reorderTracks() {
|
|
74291
75010
|
|
|
74292
75011
|
this.trackViews.sort(function (a, b) {
|
|
@@ -74523,19 +75242,19 @@ class Browser {
|
|
|
74523
75242
|
|
|
74524
75243
|
this.updateLocusSearchWidget();
|
|
74525
75244
|
|
|
74526
|
-
for (
|
|
74527
|
-
if (
|
|
74528
|
-
await this.genome.getSequence(
|
|
75245
|
+
for (const {bpPerPixel, chr, start} of this.referenceFrameList) {
|
|
75246
|
+
if (bpPerPixel <= bppSequenceThreshold) {
|
|
75247
|
+
await this.genome.getSequence(chr, start, start + 1);
|
|
74529
75248
|
}
|
|
74530
75249
|
}
|
|
74531
75250
|
|
|
74532
|
-
for (
|
|
75251
|
+
for (const centerGuide of this.centerLineList) {
|
|
74533
75252
|
centerGuide.repaint();
|
|
74534
75253
|
}
|
|
74535
75254
|
|
|
74536
75255
|
// Don't autoscale while dragging.
|
|
74537
75256
|
if (this.dragObject) {
|
|
74538
|
-
for (
|
|
75257
|
+
for (const trackView of trackViews) {
|
|
74539
75258
|
await trackView.updateViews();
|
|
74540
75259
|
}
|
|
74541
75260
|
} else {
|
|
@@ -74559,8 +75278,8 @@ class Browser {
|
|
|
74559
75278
|
// Calculate group autoscale dataRange
|
|
74560
75279
|
if (Object.entries(groupAutoscaleTrackViews).length > 0) {
|
|
74561
75280
|
for (const [group, trackViews] of Object.entries(groupAutoscaleTrackViews)) {
|
|
74562
|
-
const
|
|
74563
|
-
const dataRange = doAutoscale(
|
|
75281
|
+
const inViewFeatures = await Promise.all(trackViews.map(trackView => trackView.getInViewFeatures()));
|
|
75282
|
+
const dataRange = doAutoscale(inViewFeatures.flat());
|
|
74564
75283
|
for (const trackView of trackViews) {
|
|
74565
75284
|
trackView.track.dataRange = Object.assign({}, dataRange);
|
|
74566
75285
|
trackView.track.autoscale = false;
|
|
@@ -74569,7 +75288,7 @@ class Browser {
|
|
|
74569
75288
|
}
|
|
74570
75289
|
}
|
|
74571
75290
|
|
|
74572
|
-
await Promise.all(otherTrackViews.map(
|
|
75291
|
+
await Promise.all(otherTrackViews.map(trackView => trackView.updateViews()));
|
|
74573
75292
|
}
|
|
74574
75293
|
|
|
74575
75294
|
}
|
|
@@ -74592,10 +75311,10 @@ class Browser {
|
|
|
74592
75311
|
referenceFrame.end = referenceFrame.start + referenceFrame.bpPerPixel * width;
|
|
74593
75312
|
}
|
|
74594
75313
|
|
|
74595
|
-
const chrName = referenceFrameList.length === 1 ? this.referenceFrameList[0].chr : '';
|
|
74596
|
-
|
|
74597
75314
|
const loc = this.referenceFrameList.map(rf => rf.getLocusString()).join(' ');
|
|
74598
75315
|
|
|
75316
|
+
const chrName = referenceFrameList.length === 1 ? this.genome.getChromosomeDisplayName(this.referenceFrameList[0].chr) : '';
|
|
75317
|
+
|
|
74599
75318
|
this.navbar.updateLocus(loc, chrName);
|
|
74600
75319
|
|
|
74601
75320
|
this.fireEvent('locuschange', [this.referenceFrameList]);
|
|
@@ -74612,6 +75331,46 @@ class Browser {
|
|
|
74612
75331
|
return Math.floor(width / columnCount)
|
|
74613
75332
|
}
|
|
74614
75333
|
|
|
75334
|
+
/**
|
|
75335
|
+
* Update reference frames based on new viewport width
|
|
75336
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
75337
|
+
*/
|
|
75338
|
+
updateReferenceFrames(viewportWidth) {
|
|
75339
|
+
|
|
75340
|
+
for (const referenceFrame of this.referenceFrameList) {
|
|
75341
|
+
referenceFrame.updateForViewportWidth(viewportWidth);
|
|
75342
|
+
}
|
|
75343
|
+
}
|
|
75344
|
+
|
|
75345
|
+
/**
|
|
75346
|
+
* Update DOM viewport elements with new width
|
|
75347
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
75348
|
+
*/
|
|
75349
|
+
updateViewportElements(viewportWidth) {
|
|
75350
|
+
|
|
75351
|
+
for (let i = 0; i < this.referenceFrameList.length; i++) {
|
|
75352
|
+
|
|
75353
|
+
for (const {viewports} of this.trackViews) {
|
|
75354
|
+
viewports[i].setWidth(viewportWidth);
|
|
75355
|
+
}
|
|
75356
|
+
|
|
75357
|
+
for (const {sampleInfoViewport} of this.trackViews) {
|
|
75358
|
+
sampleInfoViewport.setWidth(this.getSampleInfoColumnWidth());
|
|
75359
|
+
sampleInfoViewport.repaint();
|
|
75360
|
+
}
|
|
75361
|
+
|
|
75362
|
+
}
|
|
75363
|
+
}
|
|
75364
|
+
|
|
75365
|
+
/**
|
|
75366
|
+
* Synchronize UI state after viewport updates
|
|
75367
|
+
* @returns {Promise<void>}
|
|
75368
|
+
*/
|
|
75369
|
+
async syncUIState() {
|
|
75370
|
+
this.updateUIWithReferenceFrameList();
|
|
75371
|
+
await this.updateViews(true);
|
|
75372
|
+
}
|
|
75373
|
+
|
|
74615
75374
|
minimumBases() {
|
|
74616
75375
|
return this.config.minimumBases
|
|
74617
75376
|
}
|
|
@@ -74797,29 +75556,12 @@ class Browser {
|
|
|
74797
75556
|
}
|
|
74798
75557
|
|
|
74799
75558
|
/**
|
|
74800
|
-
* @deprecated This is a deprecated method with no known usages.
|
|
75559
|
+
* @deprecated This is a deprecated method with no known usages.
|
|
74801
75560
|
*/
|
|
74802
75561
|
async goto(chr, start, end) {
|
|
74803
75562
|
await this.search(chr + ":" + start + "-" + end);
|
|
74804
75563
|
}
|
|
74805
75564
|
|
|
74806
|
-
/**
|
|
74807
|
-
|
|
74808
|
-
* Search for the locus string -- this function is called from various igv.js GUI elements, and is not part of the
|
|
74809
|
-
* API. Wraps ```search``` and presents an error dialog if false.
|
|
74810
|
-
*
|
|
74811
|
-
* @param string
|
|
74812
|
-
* @param init
|
|
74813
|
-
* @returns {Promise<void>}
|
|
74814
|
-
*/
|
|
74815
|
-
async doSearch(string, init) {
|
|
74816
|
-
const success = await this.search(string, init);
|
|
74817
|
-
if (!success) {
|
|
74818
|
-
this.alert.present(new Error(`Unrecognized locus: <b> ${string} </b>`));
|
|
74819
|
-
}
|
|
74820
|
-
return success
|
|
74821
|
-
}
|
|
74822
|
-
|
|
74823
75565
|
|
|
74824
75566
|
/**
|
|
74825
75567
|
* Search for the locus string
|
|
@@ -74832,6 +75574,10 @@ class Browser {
|
|
|
74832
75574
|
async search(stringOrArray, init) {
|
|
74833
75575
|
|
|
74834
75576
|
const loci = await search(this, stringOrArray);
|
|
75577
|
+
return this.updateLoci(loci, init)
|
|
75578
|
+
}
|
|
75579
|
+
|
|
75580
|
+
async updateLoci(loci, init) {
|
|
74835
75581
|
|
|
74836
75582
|
if (loci && loci.length > 0) {
|
|
74837
75583
|
|
|
@@ -74868,9 +75614,9 @@ class Browser {
|
|
|
74868
75614
|
}
|
|
74869
75615
|
}
|
|
74870
75616
|
|
|
74871
|
-
async loadSampleInfo(
|
|
75617
|
+
async loadSampleInfo(sampleInfoConfig) {
|
|
74872
75618
|
|
|
74873
|
-
await this.sampleInfo.
|
|
75619
|
+
await this.sampleInfo.loadSampleInfo(sampleInfoConfig);
|
|
74874
75620
|
|
|
74875
75621
|
for (const {sampleInfoViewport} of this.trackViews) {
|
|
74876
75622
|
sampleInfoViewport.setWidth(this.getSampleInfoColumnWidth());
|
|
@@ -75008,9 +75754,9 @@ class Browser {
|
|
|
75008
75754
|
json["locus"] = locus.length === 1 ? locus[0] : locus;
|
|
75009
75755
|
|
|
75010
75756
|
const roiSets = this.roiManager.toJSON();
|
|
75011
|
-
if(roiSets) {
|
|
75757
|
+
if (roiSets) {
|
|
75012
75758
|
json["roi"] = roiSets;
|
|
75013
|
-
if(!this.roiManager.showOverlays){
|
|
75759
|
+
if (!this.roiManager.showOverlays) {
|
|
75014
75760
|
json["showROIOverlays"] = false; // true is the default
|
|
75015
75761
|
}
|
|
75016
75762
|
}
|
|
@@ -75353,8 +76099,6 @@ class Browser {
|
|
|
75353
76099
|
}
|
|
75354
76100
|
}
|
|
75355
76101
|
|
|
75356
|
-
|
|
75357
|
-
|
|
75358
76102
|
// Navbar delegates
|
|
75359
76103
|
get sampleInfoControl() {
|
|
75360
76104
|
return this.navbar.sampleInfoControl
|
|
@@ -75376,6 +76120,9 @@ class Browser {
|
|
|
75376
76120
|
return this.navbar.sampleNameControl
|
|
75377
76121
|
}
|
|
75378
76122
|
|
|
76123
|
+
async blat(sequence) {
|
|
76124
|
+
return createBlatTrack({sequence, browser: this, name: 'Blat', title: 'Blat'})
|
|
76125
|
+
}
|
|
75379
76126
|
}
|
|
75380
76127
|
|
|
75381
76128
|
function getFileExtension(input) {
|
|
@@ -75401,7 +76148,7 @@ function getFileExtension(input) {
|
|
|
75401
76148
|
}
|
|
75402
76149
|
|
|
75403
76150
|
/**
|
|
75404
|
-
*
|
|
76151
|
+
* Called when window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
|
|
75405
76152
|
* than class method because it needs to be copied and bound to specific instances of browser to support listener
|
|
75406
76153
|
* removal
|
|
75407
76154
|
*
|
|
@@ -75409,40 +76156,14 @@ function getFileExtension(input) {
|
|
|
75409
76156
|
*/
|
|
75410
76157
|
async function resize() {
|
|
75411
76158
|
|
|
75412
|
-
if (
|
|
75413
|
-
|
|
75414
|
-
const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
|
|
75415
|
-
|
|
75416
|
-
for (let referenceFrame of this.referenceFrameList) {
|
|
75417
|
-
|
|
75418
|
-
const index = this.referenceFrameList.indexOf(referenceFrame);
|
|
75419
|
-
|
|
75420
|
-
const {chr, genome} = referenceFrame;
|
|
75421
|
-
|
|
75422
|
-
const {bpLength} = genome.getChromosome(referenceFrame.chr);
|
|
75423
|
-
|
|
75424
|
-
const viewportWidthBP = referenceFrame.toBP(viewportWidth);
|
|
75425
|
-
|
|
75426
|
-
// viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
|
|
75427
|
-
if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
|
|
75428
|
-
// console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
|
|
75429
|
-
referenceFrame.bpPerPixel = bpLength / viewportWidth;
|
|
75430
|
-
} else {
|
|
75431
|
-
// console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
|
|
75432
|
-
referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
|
|
75433
|
-
}
|
|
75434
|
-
|
|
75435
|
-
for (let {viewports} of this.trackViews) {
|
|
75436
|
-
viewports[index].setWidth(viewportWidth);
|
|
75437
|
-
}
|
|
75438
|
-
|
|
76159
|
+
if (undefined === this.referenceFrameList || 0 === this.referenceFrameList.length) {
|
|
76160
|
+
return
|
|
75439
76161
|
}
|
|
75440
76162
|
|
|
75441
|
-
this.
|
|
75442
|
-
|
|
75443
|
-
|
|
75444
|
-
|
|
75445
|
-
await this.updateViews(true);
|
|
76163
|
+
const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
|
|
76164
|
+
this.updateReferenceFrames(viewportWidth);
|
|
76165
|
+
this.updateViewportElements(viewportWidth);
|
|
76166
|
+
await this.syncUIState();
|
|
75446
76167
|
}
|
|
75447
76168
|
|
|
75448
76169
|
|
|
@@ -75911,7 +76632,8 @@ var index = {
|
|
|
75911
76632
|
registerTrackClass,
|
|
75912
76633
|
registerTrackCreatorFunction,
|
|
75913
76634
|
registerFileFormats,
|
|
75914
|
-
loadSessionFile: Browser.loadSessionFile
|
|
76635
|
+
loadSessionFile: Browser.loadSessionFile,
|
|
76636
|
+
loadHub
|
|
75915
76637
|
};
|
|
75916
76638
|
|
|
75917
76639
|
export { index as default };
|