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.js
CHANGED
|
@@ -10952,7 +10952,7 @@
|
|
|
10952
10952
|
return list;
|
|
10953
10953
|
}
|
|
10954
10954
|
|
|
10955
|
-
/*! @license DOMPurify 3.2.
|
|
10955
|
+
/*! @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 */
|
|
10956
10956
|
|
|
10957
10957
|
const {
|
|
10958
10958
|
entries,
|
|
@@ -11012,6 +11012,9 @@
|
|
|
11012
11012
|
*/
|
|
11013
11013
|
function unapply(func) {
|
|
11014
11014
|
return function (thisArg) {
|
|
11015
|
+
if (thisArg instanceof RegExp) {
|
|
11016
|
+
thisArg.lastIndex = 0;
|
|
11017
|
+
}
|
|
11015
11018
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
11016
11019
|
args[_key - 1] = arguments[_key];
|
|
11017
11020
|
}
|
|
@@ -11250,7 +11253,7 @@
|
|
|
11250
11253
|
function createDOMPurify() {
|
|
11251
11254
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
11252
11255
|
const DOMPurify = root => createDOMPurify(root);
|
|
11253
|
-
DOMPurify.version = '3.2.
|
|
11256
|
+
DOMPurify.version = '3.2.5';
|
|
11254
11257
|
DOMPurify.removed = [];
|
|
11255
11258
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
11256
11259
|
// Not running in a browser, provide a factory function
|
|
@@ -11855,7 +11858,7 @@
|
|
|
11855
11858
|
allowedTags: ALLOWED_TAGS
|
|
11856
11859
|
});
|
|
11857
11860
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
11858
|
-
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
|
|
11861
|
+
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
|
|
11859
11862
|
_forceRemove(currentNode);
|
|
11860
11863
|
return true;
|
|
11861
11864
|
}
|
|
@@ -12877,6 +12880,20 @@
|
|
|
12877
12880
|
|
|
12878
12881
|
async loadAll() {
|
|
12879
12882
|
|
|
12883
|
+
|
|
12884
|
+
const pushChromosome = (current, order) => {
|
|
12885
|
+
const length = current.length || (current.offset + current.seq.length);
|
|
12886
|
+
if (!chrNameSet.has(current.chr)) {
|
|
12887
|
+
this.sequences.set(current.chr, []);
|
|
12888
|
+
this.chromosomes.set(current.chr, new Chromosome(current.chr, order, length));
|
|
12889
|
+
chrNameSet.add(current.chr);
|
|
12890
|
+
} else {
|
|
12891
|
+
const c = this.chromosomes.get(current.chr);
|
|
12892
|
+
c.bpLength = Math.max(c.bpLength, length);
|
|
12893
|
+
}
|
|
12894
|
+
this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
|
|
12895
|
+
};
|
|
12896
|
+
|
|
12880
12897
|
let data;
|
|
12881
12898
|
if (isDataURL(this.fastaURL)) {
|
|
12882
12899
|
let bytes = decodeDataURI$1(this.fastaURL);
|
|
@@ -12890,70 +12907,62 @@
|
|
|
12890
12907
|
|
|
12891
12908
|
const chrNameSet = new Set();
|
|
12892
12909
|
const lines = splitLines$2(data);
|
|
12893
|
-
const len = lines.length;
|
|
12894
|
-
let lineNo = 0;
|
|
12895
12910
|
let order = 0;
|
|
12896
|
-
let nextLine;
|
|
12897
12911
|
let current = {};
|
|
12898
|
-
|
|
12899
|
-
nextLine = lines[lineNo++].trim();
|
|
12912
|
+
for (let nextLine of lines) {
|
|
12900
12913
|
if (nextLine.startsWith("#") || nextLine.length === 0) ; else if (nextLine.startsWith(">")) {
|
|
12901
12914
|
// Start the next sequence
|
|
12902
|
-
if (current && current.seq) {
|
|
12903
|
-
pushChromosome
|
|
12915
|
+
if (current.seq && current.seq.length > 0) {
|
|
12916
|
+
pushChromosome(current, order++);
|
|
12904
12917
|
}
|
|
12918
|
+
current.seq = "";
|
|
12905
12919
|
|
|
12906
12920
|
const parts = nextLine.substr(1).split(/\s+/);
|
|
12907
12921
|
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12922
|
+
|
|
12923
|
+
// Check for @len= token, which is a non-standard extension supporting igv-reports.
|
|
12924
|
+
if (nextLine.includes("@len=")) {
|
|
12925
|
+
const nameParts = parts[0].split(':');
|
|
12926
|
+
current.chr = nameParts[0];
|
|
12927
|
+
if (nameParts.length > 1 && nameParts[1].indexOf('-') > 0) {
|
|
12928
|
+
|
|
12929
|
+
const locusParts = nameParts[1].split('-');
|
|
12930
|
+
if (locusParts.length === 2 &&
|
|
12931
|
+
/^[0-9]+$/.test(locusParts[0]) &&
|
|
12932
|
+
/^[0-9]+$/.test(locusParts[1])) ;
|
|
12933
|
+
const from = Number.parseInt(locusParts[0]);
|
|
12934
|
+
Number.parseInt(locusParts[1]);
|
|
12921
12935
|
current.offset = from - 1;
|
|
12922
|
-
}
|
|
12923
12936
|
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12937
|
+
// Check for chromosome length token
|
|
12938
|
+
if (parts.length > 1 && parts[1].startsWith("@len=")) {
|
|
12939
|
+
try {
|
|
12940
|
+
current.length = parseInt(parts[1].trim().substring(5));
|
|
12941
|
+
} catch (e) {
|
|
12942
|
+
current.length = undefined;
|
|
12943
|
+
console.error(`Error parsing sequence length for ${nextLine}`);
|
|
12944
|
+
}
|
|
12945
|
+
} else {
|
|
12929
12946
|
current.length = undefined;
|
|
12930
|
-
console.error(`Error parsing sequence length for ${nextLine}`);
|
|
12931
12947
|
}
|
|
12932
|
-
} else {
|
|
12933
|
-
current.length = undefined;
|
|
12934
12948
|
}
|
|
12949
|
+
} else {
|
|
12950
|
+
// No special tokens, a standard FASTA header
|
|
12951
|
+
current.chr = parts[0];
|
|
12952
|
+
current.offset = 0;
|
|
12935
12953
|
}
|
|
12954
|
+
|
|
12936
12955
|
} else {
|
|
12956
|
+
// Not a header or comment, so it must be sequence data
|
|
12937
12957
|
current.seq += nextLine;
|
|
12938
12958
|
}
|
|
12939
|
-
// add last seq
|
|
12940
|
-
if (current && current.seq) {
|
|
12941
|
-
pushChromosome.call(this, current, order);
|
|
12942
|
-
}
|
|
12943
12959
|
}
|
|
12944
12960
|
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
this.sequences.set(current.chr, []);
|
|
12949
|
-
this.chromosomes.set(current.chr, new Chromosome(current.chr, order, length));
|
|
12950
|
-
chrNameSet.add(current.chr);
|
|
12951
|
-
} else {
|
|
12952
|
-
const c = this.chromosomes.get(current.chr);
|
|
12953
|
-
c.bpLength = Math.max(c.bpLength, length);
|
|
12954
|
-
}
|
|
12955
|
-
this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
|
|
12961
|
+
// Handle the last sequence
|
|
12962
|
+
if (current.seq && current.seq.length > 0) {
|
|
12963
|
+
pushChromosome(current, order);
|
|
12956
12964
|
}
|
|
12965
|
+
|
|
12957
12966
|
}
|
|
12958
12967
|
|
|
12959
12968
|
/**
|
|
@@ -13718,17 +13727,22 @@
|
|
|
13718
13727
|
|
|
13719
13728
|
static magic = 2026540177
|
|
13720
13729
|
littleEndian = true
|
|
13730
|
+
type = 'BPTree' // Either BPTree or BPChromTree
|
|
13721
13731
|
nodeCache = new Map()
|
|
13722
13732
|
|
|
13723
|
-
static async loadBpTree(path, config, startOffset) {
|
|
13724
|
-
const bpTree = new BPTree(path, config, startOffset);
|
|
13733
|
+
static async loadBpTree(path, config, startOffset, type, loader) {
|
|
13734
|
+
const bpTree = new BPTree(path, config, startOffset, type, loader);
|
|
13725
13735
|
return bpTree.init()
|
|
13726
13736
|
}
|
|
13727
13737
|
|
|
13728
|
-
constructor(path, config, startOffset) {
|
|
13738
|
+
constructor(path, config, startOffset, type, loader) {
|
|
13729
13739
|
this.path = path;
|
|
13730
13740
|
this.config = config;
|
|
13731
13741
|
this.startOffset = startOffset;
|
|
13742
|
+
if(type) {
|
|
13743
|
+
this.type = type;
|
|
13744
|
+
}
|
|
13745
|
+
this.loader = loader || igvxhr;
|
|
13732
13746
|
}
|
|
13733
13747
|
|
|
13734
13748
|
async init() {
|
|
@@ -13754,69 +13768,22 @@
|
|
|
13754
13768
|
return this
|
|
13755
13769
|
}
|
|
13756
13770
|
|
|
13757
|
-
|
|
13758
|
-
|
|
13771
|
+
getItemCount() {
|
|
13759
13772
|
if(!this.header) {
|
|
13760
|
-
|
|
13773
|
+
throw Error("Header not initialized")
|
|
13761
13774
|
}
|
|
13775
|
+
return this.header.itemCount
|
|
13776
|
+
}
|
|
13762
13777
|
|
|
13763
|
-
|
|
13778
|
+
async search(term) {
|
|
13764
13779
|
|
|
13765
|
-
if
|
|
13766
|
-
|
|
13780
|
+
if(!this.header) {
|
|
13781
|
+
await this.init();
|
|
13767
13782
|
}
|
|
13768
13783
|
|
|
13769
|
-
const readTreeNode = async (offset) => {
|
|
13770
|
-
|
|
13771
|
-
if (this.nodeCache.has(offset)) {
|
|
13772
|
-
return this.nodeCache.get(offset)
|
|
13773
|
-
} else {
|
|
13774
|
-
|
|
13775
|
-
let binaryParser = await this.#getParserFor(offset, 4);
|
|
13776
|
-
const type = binaryParser.getByte();
|
|
13777
|
-
binaryParser.getByte();
|
|
13778
|
-
const count = binaryParser.getUShort();
|
|
13779
|
-
const items = [];
|
|
13780
|
-
|
|
13781
|
-
if (type === 1) {
|
|
13782
|
-
// Leaf node
|
|
13783
|
-
const size = count * (keySize + valSize);
|
|
13784
|
-
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13785
|
-
for (let i = 0; i < count; i++) {
|
|
13786
|
-
const key = binaryParser.getFixedLengthString(keySize);
|
|
13787
|
-
const offset = binaryParser.getLong();
|
|
13788
|
-
|
|
13789
|
-
let value;
|
|
13790
|
-
if (valSize === 16) {
|
|
13791
|
-
const length = binaryParser.getInt();
|
|
13792
|
-
binaryParser.getInt();
|
|
13793
|
-
value = {offset, length};
|
|
13794
|
-
} else {
|
|
13795
|
-
value = {offset};
|
|
13796
|
-
}
|
|
13797
|
-
items.push({key, value});
|
|
13798
|
-
}
|
|
13799
|
-
} else {
|
|
13800
|
-
// Non leaf node
|
|
13801
|
-
const size = count * (keySize + 8);
|
|
13802
|
-
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13803
|
-
|
|
13804
|
-
for (let i = 0; i < count; i++) {
|
|
13805
|
-
const key = binaryParser.getFixedLengthString(keySize);
|
|
13806
|
-
const offset = binaryParser.getLong();
|
|
13807
|
-
items.push({key, offset});
|
|
13808
|
-
}
|
|
13809
|
-
}
|
|
13810
|
-
|
|
13811
|
-
const node = {type, count, items};
|
|
13812
|
-
this.nodeCache.set(offset, node);
|
|
13813
|
-
return node
|
|
13814
|
-
}
|
|
13815
|
-
};
|
|
13816
|
-
|
|
13817
13784
|
const walkTreeNode = async (offset) => {
|
|
13818
13785
|
|
|
13819
|
-
const node = await readTreeNode(offset);
|
|
13786
|
+
const node = await this.readTreeNode(offset);
|
|
13820
13787
|
|
|
13821
13788
|
if (node.type === 1) {
|
|
13822
13789
|
// Leaf node
|
|
@@ -13847,9 +13814,66 @@
|
|
|
13847
13814
|
return walkTreeNode(this.header.nodeOffset)
|
|
13848
13815
|
}
|
|
13849
13816
|
|
|
13817
|
+
async readTreeNode (offset) {
|
|
13818
|
+
|
|
13819
|
+
if (this.nodeCache.has(offset)) {
|
|
13820
|
+
return this.nodeCache.get(offset)
|
|
13821
|
+
} else {
|
|
13822
|
+
let binaryParser = await this.#getParserFor(offset, 4);
|
|
13823
|
+
const type = binaryParser.getByte();
|
|
13824
|
+
binaryParser.getByte();
|
|
13825
|
+
const count = binaryParser.getUShort();
|
|
13826
|
+
const items = [];
|
|
13827
|
+
|
|
13828
|
+
const {keySize, valSize} = this.header;
|
|
13829
|
+
|
|
13830
|
+
if (type === 1) {
|
|
13831
|
+
// Leaf node
|
|
13832
|
+
const size = count * (keySize + valSize);
|
|
13833
|
+
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13834
|
+
for (let i = 0; i < count; i++) {
|
|
13835
|
+
const key = binaryParser.getFixedLengthString(keySize);
|
|
13836
|
+
let value;
|
|
13837
|
+
if(this.type === 'BPChromTree') {
|
|
13838
|
+
const id = binaryParser.getInt();
|
|
13839
|
+
const size = binaryParser.getInt();
|
|
13840
|
+
value = {id, size};
|
|
13841
|
+
} else {
|
|
13842
|
+
const offset = binaryParser.getLong();
|
|
13843
|
+
if (valSize === 16) {
|
|
13844
|
+
const length = binaryParser.getLong();
|
|
13845
|
+
value = {offset, length};
|
|
13846
|
+
} else {
|
|
13847
|
+
value = {offset};
|
|
13848
|
+
}
|
|
13849
|
+
}
|
|
13850
|
+
items.push({key, value});
|
|
13851
|
+
}
|
|
13852
|
+
} else {
|
|
13853
|
+
// Non leaf node
|
|
13854
|
+
const size = count * (keySize + 8);
|
|
13855
|
+
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13856
|
+
|
|
13857
|
+
for (let i = 0; i < count; i++) {
|
|
13858
|
+
const key = binaryParser.getFixedLengthString(keySize);
|
|
13859
|
+
const offset = binaryParser.getLong();
|
|
13860
|
+
items.push({key, offset});
|
|
13861
|
+
}
|
|
13862
|
+
}
|
|
13863
|
+
|
|
13864
|
+
const node = {type, count, items};
|
|
13865
|
+
this.nodeCache.set(offset, node);
|
|
13866
|
+
return node
|
|
13867
|
+
}
|
|
13868
|
+
}
|
|
13869
|
+
|
|
13850
13870
|
async #getParserFor(start, size) {
|
|
13851
|
-
|
|
13852
|
-
|
|
13871
|
+
try {
|
|
13872
|
+
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start, size}}));
|
|
13873
|
+
return new BinaryParser$1(new DataView(data), this.littleEndian)
|
|
13874
|
+
} catch (e) {
|
|
13875
|
+
console.error(e);
|
|
13876
|
+
}
|
|
13853
13877
|
}
|
|
13854
13878
|
|
|
13855
13879
|
}
|
|
@@ -13875,6 +13899,7 @@
|
|
|
13875
13899
|
|
|
13876
13900
|
littleEndian
|
|
13877
13901
|
metaIndex = new Map()
|
|
13902
|
+
chromosomeNames
|
|
13878
13903
|
|
|
13879
13904
|
constructor(config) {
|
|
13880
13905
|
this.url = config.twoBitURL || config.fastaURL;
|
|
@@ -13967,9 +13992,16 @@
|
|
|
13967
13992
|
return sequenceBases
|
|
13968
13993
|
}
|
|
13969
13994
|
|
|
13995
|
+
/**
|
|
13996
|
+
* Read the internal index of the 2bit file. This is a list of sequence names and their offsets in the file.
|
|
13997
|
+
*
|
|
13998
|
+
* @returns {Promise<Map<any, any>>}
|
|
13999
|
+
* @private
|
|
14000
|
+
*/
|
|
13970
14001
|
async _readIndex() {
|
|
13971
14002
|
|
|
13972
14003
|
const index = new Map();
|
|
14004
|
+
this.chromosomeNames = [];
|
|
13973
14005
|
|
|
13974
14006
|
const loadRange = {start: 0, size: 64};
|
|
13975
14007
|
let arrayBuffer = await igvxhr.loadArrayBuffer(this.url, {range: loadRange});
|
|
@@ -14022,6 +14054,8 @@
|
|
|
14022
14054
|
index.set(name, offset);
|
|
14023
14055
|
|
|
14024
14056
|
estNameLength = Math.floor(estNameLength * (i / (i + 1)) + name.length / (i + 1));
|
|
14057
|
+
|
|
14058
|
+
this.chromosomeNames.push(name);
|
|
14025
14059
|
}
|
|
14026
14060
|
return index
|
|
14027
14061
|
}
|
|
@@ -16349,7 +16383,10 @@
|
|
|
16349
16383
|
}
|
|
16350
16384
|
} else {
|
|
16351
16385
|
// All directives that could change the format, and thus decoder, should have been read by now.
|
|
16352
|
-
|
|
16386
|
+
// Set the decoder, unless it is explicitly set in the track configuration (not common)
|
|
16387
|
+
if(!this.config.decode) {
|
|
16388
|
+
this.setDecoder(header.format);
|
|
16389
|
+
}
|
|
16353
16390
|
|
|
16354
16391
|
// If the line can be parsed as a feature assume we are beyond the header, if any
|
|
16355
16392
|
const tokens = line.split(this.delimiter || "\t");
|
|
@@ -22164,84 +22201,6 @@
|
|
|
22164
22201
|
return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
|
|
22165
22202
|
}
|
|
22166
22203
|
|
|
22167
|
-
/**
|
|
22168
|
-
* A ChromTree parses a UCSC bigbed/bigwig "chromosomeTree" section of the header to produce 2 maps,
|
|
22169
|
-
* (1) ID -> chromosome names, and its
|
|
22170
|
-
* (2) chromsome name -> ID
|
|
22171
|
-
*
|
|
22172
|
-
* Both maps are needed by IGV
|
|
22173
|
-
*
|
|
22174
|
-
* The chromosome tree is a B+ index, but is located continguously in memory in the header section of the file. This
|
|
22175
|
-
* makes it feasible to parse the whole tree with data from a single fetch. In the end the tree is discarded
|
|
22176
|
-
* leaving only the mapps.
|
|
22177
|
-
*/
|
|
22178
|
-
class ChromTree {
|
|
22179
|
-
|
|
22180
|
-
constructor(header, nameToID, valueToKey, sumLengths) {
|
|
22181
|
-
this.header = header;
|
|
22182
|
-
this.nameToId = nameToID;
|
|
22183
|
-
this.idToName = valueToKey;
|
|
22184
|
-
this.sumLengths = sumLengths;
|
|
22185
|
-
}
|
|
22186
|
-
|
|
22187
|
-
static parseTree(binaryParser, startOffset, genome = false) {
|
|
22188
|
-
{
|
|
22189
|
-
const magic = binaryParser.getInt();
|
|
22190
|
-
const blockSize = binaryParser.getInt();
|
|
22191
|
-
const keySize = binaryParser.getInt();
|
|
22192
|
-
const valSize = binaryParser.getInt();
|
|
22193
|
-
const itemCount = binaryParser.getLong();
|
|
22194
|
-
const reserved = binaryParser.getLong();
|
|
22195
|
-
|
|
22196
|
-
const header = {magic, blockSize, keySize, valSize, itemCount, reserved};
|
|
22197
|
-
const nameToId = new Map();
|
|
22198
|
-
const idToName = [];
|
|
22199
|
-
let sumLengths = 0;
|
|
22200
|
-
const readTreeNode = (offset) => {
|
|
22201
|
-
if (offset >= 0) binaryParser.position = offset;
|
|
22202
|
-
const type = binaryParser.getByte();
|
|
22203
|
-
binaryParser.getByte();
|
|
22204
|
-
const count = binaryParser.getUShort();
|
|
22205
|
-
|
|
22206
|
-
if (type === 1) {
|
|
22207
|
-
// Leaf node
|
|
22208
|
-
for (let i = 0; i < count; i++) {
|
|
22209
|
-
let key = binaryParser.getFixedLengthString(keySize);
|
|
22210
|
-
let value;
|
|
22211
|
-
if (valSize === 8) {
|
|
22212
|
-
value = binaryParser.getInt();
|
|
22213
|
-
const chromSize = binaryParser.getInt();
|
|
22214
|
-
sumLengths += chromSize;
|
|
22215
|
-
if (genome) key = genome.getChromosomeName(key); // Translate to canonical chr name
|
|
22216
|
-
nameToId.set(key, value);
|
|
22217
|
-
idToName[value] = key;
|
|
22218
|
-
|
|
22219
|
-
} else {
|
|
22220
|
-
throw Error(`Unexpected "valSize" value in chromosome tree. Expected 8, actual value is ${valSize}`)
|
|
22221
|
-
}
|
|
22222
|
-
}
|
|
22223
|
-
} else {
|
|
22224
|
-
// non-leaf
|
|
22225
|
-
for (let i = 0; i < count; i++) {
|
|
22226
|
-
binaryParser.getFixedLengthString(keySize);
|
|
22227
|
-
const childOffset = binaryParser.getLong();
|
|
22228
|
-
const bufferOffset = childOffset - startOffset;
|
|
22229
|
-
const currOffset = binaryParser.position;
|
|
22230
|
-
readTreeNode(bufferOffset);
|
|
22231
|
-
binaryParser.position = currOffset;
|
|
22232
|
-
}
|
|
22233
|
-
}
|
|
22234
|
-
};
|
|
22235
|
-
|
|
22236
|
-
// Recursively walk tree to populate dictionary
|
|
22237
|
-
readTreeNode( -1);
|
|
22238
|
-
|
|
22239
|
-
return new ChromTree(header, nameToId, idToName, sumLengths)
|
|
22240
|
-
}
|
|
22241
|
-
}
|
|
22242
|
-
|
|
22243
|
-
}
|
|
22244
|
-
|
|
22245
22204
|
const RPTREE_HEADER_SIZE = 48;
|
|
22246
22205
|
const RPTREE_NODE_LEAF_ITEM_SIZE = 32; // leaf item size
|
|
22247
22206
|
const RPTREE_NODE_CHILD_ITEM_SIZE = 24; // child item size
|
|
@@ -22252,11 +22211,12 @@
|
|
|
22252
22211
|
littleEndian = true
|
|
22253
22212
|
nodeCache = new Map()
|
|
22254
22213
|
|
|
22255
|
-
constructor(path, config, startOffset) {
|
|
22214
|
+
constructor(path, config, startOffset, loader) {
|
|
22256
22215
|
|
|
22257
22216
|
this.path = path;
|
|
22258
22217
|
this.config = config;
|
|
22259
22218
|
this.startOffset = startOffset;
|
|
22219
|
+
this.loader = loader || igvxhr;
|
|
22260
22220
|
}
|
|
22261
22221
|
|
|
22262
22222
|
|
|
@@ -22300,7 +22260,7 @@
|
|
|
22300
22260
|
}
|
|
22301
22261
|
|
|
22302
22262
|
async #getParserFor(start, size) {
|
|
22303
|
-
const data = await
|
|
22263
|
+
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start, size}}));
|
|
22304
22264
|
return new BinaryParser$1(new DataView(data), this.littleEndian)
|
|
22305
22265
|
}
|
|
22306
22266
|
|
|
@@ -22657,6 +22617,165 @@
|
|
|
22657
22617
|
}
|
|
22658
22618
|
}
|
|
22659
22619
|
|
|
22620
|
+
class ChromTree {
|
|
22621
|
+
|
|
22622
|
+
nameToId = new Map()
|
|
22623
|
+
idToName = new Map()
|
|
22624
|
+
|
|
22625
|
+
constructor(path, config, startOffset, loader) {
|
|
22626
|
+
this.path = path;
|
|
22627
|
+
this.config = config;
|
|
22628
|
+
this.startOffset = startOffset;
|
|
22629
|
+
|
|
22630
|
+
this.bpTree = new BPTree(path, config, startOffset, 'BPChromTree', loader);
|
|
22631
|
+
}
|
|
22632
|
+
|
|
22633
|
+
async init() {
|
|
22634
|
+
return this.bpTree.init()
|
|
22635
|
+
}
|
|
22636
|
+
|
|
22637
|
+
getItemCount() {
|
|
22638
|
+
return this.bpTree.getItemCount()
|
|
22639
|
+
}
|
|
22640
|
+
|
|
22641
|
+
/**
|
|
22642
|
+
* Return the chromosome ID for the given name. This is the internal chromosome ID for the parent BB file only.
|
|
22643
|
+
* @param {string} chr - The chromosome name.
|
|
22644
|
+
* @returns {number|null} - The chromosome ID or null if not found.
|
|
22645
|
+
*/
|
|
22646
|
+
async getIdForName(chr) {
|
|
22647
|
+
if (this.nameToId.has(chr)) {
|
|
22648
|
+
return this.nameToId.get(chr)
|
|
22649
|
+
} else {
|
|
22650
|
+
try {
|
|
22651
|
+
const result = await this.bpTree.search(chr);
|
|
22652
|
+
if (result) {
|
|
22653
|
+
const id = result.id;
|
|
22654
|
+
this.nameToId.set(chr, id);
|
|
22655
|
+
return id
|
|
22656
|
+
} else {
|
|
22657
|
+
return
|
|
22658
|
+
}
|
|
22659
|
+
} catch (error) {
|
|
22660
|
+
throw new Error(error)
|
|
22661
|
+
}
|
|
22662
|
+
}
|
|
22663
|
+
}
|
|
22664
|
+
|
|
22665
|
+
/**
|
|
22666
|
+
* Return the chromosome name for the given ID. This is a potentially expensive operation as it involves
|
|
22667
|
+
* walking the tree until the leaf item for the given name is found. Currently it is used in only 2
|
|
22668
|
+
* situations:
|
|
22669
|
+
* (1) decoding features from a bigbed search-by-name query
|
|
22670
|
+
* (2) decoding bigwig data from the whole genome view
|
|
22671
|
+
* @param {number} id
|
|
22672
|
+
* @return {string|null}
|
|
22673
|
+
*/
|
|
22674
|
+
async getNameForId(id) {
|
|
22675
|
+
if (this.idToName.has(id)) {
|
|
22676
|
+
return this.idToName.get(id)
|
|
22677
|
+
} else {
|
|
22678
|
+
const name = await this.searchForName(id);
|
|
22679
|
+
if (name !== null) {
|
|
22680
|
+
this.idToName.set(id, name);
|
|
22681
|
+
return name
|
|
22682
|
+
}
|
|
22683
|
+
}
|
|
22684
|
+
return null
|
|
22685
|
+
}
|
|
22686
|
+
|
|
22687
|
+
/**
|
|
22688
|
+
* Perform a reverse search by traversing the tree starting at the given offset. This is potentially expensive
|
|
22689
|
+
* as it traverses the tree to find the name corresponding to the given ID. It shoud not be used for large trees.
|
|
22690
|
+
*
|
|
22691
|
+
* @param {number} id - The ID to search for.
|
|
22692
|
+
* @returns {string|null} - The name corresponding to the ID, or null if not found.
|
|
22693
|
+
*/
|
|
22694
|
+
async searchForName(id) {
|
|
22695
|
+
|
|
22696
|
+
const reverseSearch = async (offset, id) => {
|
|
22697
|
+
|
|
22698
|
+
const node = await this.bpTree.readTreeNode(offset);
|
|
22699
|
+
|
|
22700
|
+
let found = null;
|
|
22701
|
+
|
|
22702
|
+
if (node.type === 1) {
|
|
22703
|
+
// Leaf node
|
|
22704
|
+
for (const item of node.items) {
|
|
22705
|
+
const key = item.key;
|
|
22706
|
+
const itemId = item.value.id;
|
|
22707
|
+
if (itemId === id) {
|
|
22708
|
+
found = key;
|
|
22709
|
+
}
|
|
22710
|
+
// Cache the name and ID for future lookups
|
|
22711
|
+
this.nameToId.set(key, itemId);
|
|
22712
|
+
this.idToName.set(id, itemId);
|
|
22713
|
+
}
|
|
22714
|
+
return found
|
|
22715
|
+
} else {
|
|
22716
|
+
// Non-leaf node
|
|
22717
|
+
for (const item of node.items) {
|
|
22718
|
+
found = await reverseSearch.call(this, item.offset, id);
|
|
22719
|
+
if (found !== null) {
|
|
22720
|
+
break
|
|
22721
|
+
}
|
|
22722
|
+
}
|
|
22723
|
+
}
|
|
22724
|
+
return found
|
|
22725
|
+
};
|
|
22726
|
+
|
|
22727
|
+
try {
|
|
22728
|
+
return reverseSearch.call(this, this.startOffset + 32, id)
|
|
22729
|
+
} catch (error) {
|
|
22730
|
+
throw new Error(error)
|
|
22731
|
+
}
|
|
22732
|
+
}
|
|
22733
|
+
|
|
22734
|
+
/**
|
|
22735
|
+
* Return an estimated length of the genome, which might be the actual length if the number of contigs is small.
|
|
22736
|
+
* This is only used for calculating a default feature visibility window.
|
|
22737
|
+
*
|
|
22738
|
+
* @return {number}
|
|
22739
|
+
*/
|
|
22740
|
+
async estimateGenomeSize() {
|
|
22741
|
+
try {
|
|
22742
|
+
const runningTotal = {total: 0, count: 0};
|
|
22743
|
+
await this.accumulateSize(this.startOffset + 32, runningTotal, 10000);
|
|
22744
|
+
const itemCount = this.getItemCount();
|
|
22745
|
+
return (itemCount / runningTotal.count) * runningTotal.total
|
|
22746
|
+
|
|
22747
|
+
} catch (error) {
|
|
22748
|
+
console.error("Error estimating genome size", error);
|
|
22749
|
+
return -1
|
|
22750
|
+
}
|
|
22751
|
+
}
|
|
22752
|
+
|
|
22753
|
+
async accumulateSize(offset, runningTotal, maxCount) {
|
|
22754
|
+
|
|
22755
|
+
const node = await this.bpTree.readTreeNode(offset);
|
|
22756
|
+
|
|
22757
|
+
if (node.type === 1) {
|
|
22758
|
+
// Leaf node
|
|
22759
|
+
for (const item of node.items) {
|
|
22760
|
+
const value = item.value;
|
|
22761
|
+
runningTotal.total += value.size;
|
|
22762
|
+
runningTotal.count += 1;
|
|
22763
|
+
}
|
|
22764
|
+
} else {
|
|
22765
|
+
// Non-leaf node. Items are visited in random order to avoid biasing the estimate
|
|
22766
|
+
const shuffledItems = node.items.slice().sort(() => Math.random() - 0.5);
|
|
22767
|
+
for (const item of shuffledItems) {
|
|
22768
|
+
await this.accumulateSize(item.offset, runningTotal, maxCount);
|
|
22769
|
+
if (runningTotal.count > maxCount) {
|
|
22770
|
+
break
|
|
22771
|
+
}
|
|
22772
|
+
}
|
|
22773
|
+
}
|
|
22774
|
+
return runningTotal
|
|
22775
|
+
}
|
|
22776
|
+
|
|
22777
|
+
}
|
|
22778
|
+
|
|
22660
22779
|
/*
|
|
22661
22780
|
* The MIT License (MIT)
|
|
22662
22781
|
*
|
|
@@ -22720,14 +22839,40 @@
|
|
|
22720
22839
|
async preload() {
|
|
22721
22840
|
const data = await igvxhr.loadArrayBuffer(this.path);
|
|
22722
22841
|
this.loader = new DataBuffer(data);
|
|
22842
|
+
for (let rpTree of this.rpTreeCache.values()) {
|
|
22843
|
+
rpTree.loader = this.loader;
|
|
22844
|
+
}
|
|
22845
|
+
if (this._searchTrees) {
|
|
22846
|
+
for (let bpTree of this._searchTrees) {
|
|
22847
|
+
bpTree.loader = this.loader;
|
|
22848
|
+
}
|
|
22849
|
+
}
|
|
22723
22850
|
}
|
|
22724
22851
|
|
|
22725
|
-
async readWGFeatures(bpPerPixel, windowFunction) {
|
|
22852
|
+
async readWGFeatures(wgChromosomeNames, bpPerPixel, windowFunction) {
|
|
22853
|
+
|
|
22726
22854
|
await this.loadHeader();
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22855
|
+
// Convert the logic to JavaScript
|
|
22856
|
+
let minID = Number.MAX_SAFE_INTEGER;
|
|
22857
|
+
let maxID = -1;
|
|
22858
|
+
let chr1;
|
|
22859
|
+
let chr2;
|
|
22860
|
+
|
|
22861
|
+
for (const chr of wgChromosomeNames) {
|
|
22862
|
+
const id = await this.getIdForChr(chr);
|
|
22863
|
+
if (id === null || id === undefined) {
|
|
22864
|
+
continue
|
|
22865
|
+
}
|
|
22866
|
+
if (id < minID) {
|
|
22867
|
+
minID = id;
|
|
22868
|
+
chr1 = chr;
|
|
22869
|
+
}
|
|
22870
|
+
if (id > maxID) {
|
|
22871
|
+
maxID = id;
|
|
22872
|
+
chr2 = chr;
|
|
22873
|
+
}
|
|
22874
|
+
}
|
|
22875
|
+
|
|
22731
22876
|
return this.readFeatures(chr1, 0, chr2, Number.MAX_VALUE, bpPerPixel, windowFunction)
|
|
22732
22877
|
}
|
|
22733
22878
|
|
|
@@ -22738,8 +22883,8 @@
|
|
|
22738
22883
|
|
|
22739
22884
|
await this.loadHeader();
|
|
22740
22885
|
|
|
22741
|
-
|
|
22742
|
-
|
|
22886
|
+
const chrIdx1 = await this.getIdForChr(chr1);
|
|
22887
|
+
const chrIdx2 = await this.getIdForChr(chr2);
|
|
22743
22888
|
|
|
22744
22889
|
if (chrIdx1 === undefined || chrIdx2 === undefined) {
|
|
22745
22890
|
return []
|
|
@@ -22798,7 +22943,7 @@
|
|
|
22798
22943
|
} else {
|
|
22799
22944
|
plain = uint8Array;
|
|
22800
22945
|
}
|
|
22801
|
-
decodeFunction.call(this, new DataView(plain.buffer), chrIdx1, bpStart, chrIdx2, bpEnd, features,
|
|
22946
|
+
await decodeFunction.call(this, new DataView(plain.buffer), chrIdx1, bpStart, chrIdx2, bpEnd, features, windowFunction);
|
|
22802
22947
|
}
|
|
22803
22948
|
|
|
22804
22949
|
features.sort(function (a, b) {
|
|
@@ -22815,29 +22960,30 @@
|
|
|
22815
22960
|
* @param chr
|
|
22816
22961
|
* @returns {Promise<*>}
|
|
22817
22962
|
*/
|
|
22818
|
-
async
|
|
22963
|
+
async getIdForChr(chr) {
|
|
22819
22964
|
|
|
22820
22965
|
if (this.chrAliasTable.has(chr)) {
|
|
22821
22966
|
chr = this.chrAliasTable.get(chr);
|
|
22822
|
-
if (chr
|
|
22967
|
+
if (!chr) {
|
|
22823
22968
|
return undefined
|
|
22824
22969
|
}
|
|
22825
22970
|
}
|
|
22826
22971
|
|
|
22827
|
-
let chrIdx = this.chromTree.
|
|
22972
|
+
let chrIdx = await this.chromTree.getIdForName(chr);
|
|
22828
22973
|
|
|
22829
22974
|
// Try alias
|
|
22830
22975
|
if (chrIdx === undefined && this.genome) {
|
|
22831
22976
|
const aliasRecord = await this.genome.getAliasRecord(chr);
|
|
22832
22977
|
let alias;
|
|
22833
22978
|
if (aliasRecord) {
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
22979
|
+
for (let k of Object.keys(aliasRecord)) {
|
|
22980
|
+
if (k === "start" || k === "end") continue
|
|
22981
|
+
alias = aliasRecord[k];
|
|
22982
|
+
if (alias === chr) continue // Already tried this
|
|
22983
|
+
chrIdx = await this.chromTree.getIdForName(alias);
|
|
22984
|
+
if (chrIdx !== undefined) {
|
|
22985
|
+
break
|
|
22986
|
+
}
|
|
22841
22987
|
}
|
|
22842
22988
|
}
|
|
22843
22989
|
this.chrAliasTable.set(chr, alias); // alias may be undefined => no alias exists. Setting prevents repeated attempts
|
|
@@ -22924,7 +23070,8 @@
|
|
|
22924
23070
|
this.header.extraIndexOffsets.length > 0) {
|
|
22925
23071
|
this._searchTrees = [];
|
|
22926
23072
|
for (let offset of this.header.extraIndexOffsets) {
|
|
22927
|
-
const
|
|
23073
|
+
const type = undefined;
|
|
23074
|
+
const bpTree = await BPTree.loadBpTree(this.path, this.config, offset, type, this.loader);
|
|
22928
23075
|
this._searchTrees.push(bpTree);
|
|
22929
23076
|
}
|
|
22930
23077
|
}
|
|
@@ -23041,15 +23188,13 @@
|
|
|
23041
23188
|
this.totalSummary = new BWTotalSummary(extHeaderParser);
|
|
23042
23189
|
}
|
|
23043
23190
|
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
this.chromTree = await this.#readChromTree(header.chromTreeOffset, bufferSize);
|
|
23047
|
-
this.chrNames = new Set(this.chromTree.idToName);
|
|
23191
|
+
this.chromTree = new ChromTree(this.path, this.config, header.chromTreeOffset, this.loader);
|
|
23192
|
+
await this.chromTree.init();
|
|
23048
23193
|
|
|
23049
23194
|
// Estimate feature density from dataCount (bigbed only)
|
|
23050
|
-
if("bigbed" === this.type) {
|
|
23195
|
+
if ("bigbed" === this.type) {
|
|
23051
23196
|
const dataCount = await this.#readDataCount(header.fullDataOffset);
|
|
23052
|
-
this.featureDensity = dataCount / this.chromTree.
|
|
23197
|
+
this.featureDensity = dataCount / await this.chromTree.estimateGenomeSize();
|
|
23053
23198
|
}
|
|
23054
23199
|
|
|
23055
23200
|
this.header = header;
|
|
@@ -23073,38 +23218,6 @@
|
|
|
23073
23218
|
return binaryParser.getInt()
|
|
23074
23219
|
}
|
|
23075
23220
|
|
|
23076
|
-
/**
|
|
23077
|
-
* Used when the chromTreeOffset is > fullDataOffset, that is when the chrom tree is not in the initial chunk
|
|
23078
|
-
* read for parsing the header. We know the start position, but not the total size of the chrom tree
|
|
23079
|
-
*
|
|
23080
|
-
* @returns {Promise<void>}
|
|
23081
|
-
*/
|
|
23082
|
-
async #readChromTree(chromTreeOffset, bufferSize) {
|
|
23083
|
-
|
|
23084
|
-
let size = bufferSize;
|
|
23085
|
-
const load = async () => {
|
|
23086
|
-
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {
|
|
23087
|
-
range: {
|
|
23088
|
-
start: chromTreeOffset,
|
|
23089
|
-
size: size
|
|
23090
|
-
}
|
|
23091
|
-
}));
|
|
23092
|
-
const binaryParser = new BinaryParser$1(new DataView(data), this.littleEndian);
|
|
23093
|
-
return ChromTree.parseTree(binaryParser, chromTreeOffset, this.genome)
|
|
23094
|
-
};
|
|
23095
|
-
|
|
23096
|
-
let error;
|
|
23097
|
-
while (size < 1000000) {
|
|
23098
|
-
try {
|
|
23099
|
-
const chromTree = await load();
|
|
23100
|
-
return chromTree
|
|
23101
|
-
} catch (e) {
|
|
23102
|
-
error = e;
|
|
23103
|
-
size *= 2;
|
|
23104
|
-
}
|
|
23105
|
-
}
|
|
23106
|
-
throw (error)
|
|
23107
|
-
}
|
|
23108
23221
|
|
|
23109
23222
|
async loadExtendedHeader(offset) {
|
|
23110
23223
|
|
|
@@ -23160,7 +23273,7 @@
|
|
|
23160
23273
|
if (rpTree) {
|
|
23161
23274
|
return rpTree
|
|
23162
23275
|
} else {
|
|
23163
|
-
rpTree = new RPTree(this.path, this.config, offset);
|
|
23276
|
+
rpTree = new RPTree(this.path, this.config, offset, this.loader);
|
|
23164
23277
|
await rpTree.init();
|
|
23165
23278
|
this.rpTreeCache.set(offset, rpTree);
|
|
23166
23279
|
return rpTree
|
|
@@ -23200,9 +23313,7 @@
|
|
|
23200
23313
|
const plain = (this.header.uncompressBuffSize > 0) ? inflate_1$3(uint8Array) : uint8Array;
|
|
23201
23314
|
const decodeFunction = getBedDataDecoder.call(this);
|
|
23202
23315
|
const features = [];
|
|
23203
|
-
decodeFunction.call(this, new DataView(plain.buffer), 0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER,
|
|
23204
|
-
features, this.chromTree.idToName);
|
|
23205
|
-
|
|
23316
|
+
await decodeFunction.call(this, new DataView(plain.buffer), 0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, features);
|
|
23206
23317
|
return features
|
|
23207
23318
|
|
|
23208
23319
|
}
|
|
@@ -23270,7 +23381,7 @@
|
|
|
23270
23381
|
}
|
|
23271
23382
|
|
|
23272
23383
|
|
|
23273
|
-
function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray,
|
|
23384
|
+
async function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, windowFunction, littleEndian) {
|
|
23274
23385
|
|
|
23275
23386
|
const binaryParser = new BinaryParser$1(data, littleEndian);
|
|
23276
23387
|
const chromId = binaryParser.getInt();
|
|
@@ -23311,7 +23422,7 @@
|
|
|
23311
23422
|
else if (chromId > chrIdx2 || (chromId === chrIdx2 && chromStart >= bpEnd)) break
|
|
23312
23423
|
|
|
23313
23424
|
if (Number.isFinite(value)) {
|
|
23314
|
-
const chr =
|
|
23425
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23315
23426
|
featureArray.push({chr: chr, start: chromStart, end: chromEnd, value: value});
|
|
23316
23427
|
}
|
|
23317
23428
|
}
|
|
@@ -23322,12 +23433,13 @@
|
|
|
23322
23433
|
|
|
23323
23434
|
const minSize = 3 * 4 + 1; // Minimum # of bytes required for a bed record
|
|
23324
23435
|
const decoder = getDecoder(this.header.definedFieldCount, this.header.fieldCount, this.autoSql, this.format);
|
|
23325
|
-
return function (data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray
|
|
23326
|
-
|
|
23436
|
+
return async function (data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray) {
|
|
23437
|
+
|
|
23438
|
+
const binaryParser = new BinaryParser$1(data, this.littleEndian);
|
|
23327
23439
|
while (binaryParser.remLength() >= minSize) {
|
|
23328
23440
|
|
|
23329
23441
|
const chromId = binaryParser.getInt();
|
|
23330
|
-
const chr =
|
|
23442
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23331
23443
|
const chromStart = binaryParser.getInt();
|
|
23332
23444
|
const chromEnd = binaryParser.getInt();
|
|
23333
23445
|
const rest = binaryParser.getString();
|
|
@@ -23344,8 +23456,7 @@
|
|
|
23344
23456
|
}
|
|
23345
23457
|
}
|
|
23346
23458
|
|
|
23347
|
-
|
|
23348
|
-
function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, chrDict, windowFunction, littleEndian) {
|
|
23459
|
+
async function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, windowFunction, littleEndian) {
|
|
23349
23460
|
|
|
23350
23461
|
const binaryParser = new BinaryParser$1(data, littleEndian);
|
|
23351
23462
|
const minSize = 8 * 4; // Minimum # of bytes required for a zoom record
|
|
@@ -23377,7 +23488,7 @@
|
|
|
23377
23488
|
|
|
23378
23489
|
|
|
23379
23490
|
if (Number.isFinite(value)) {
|
|
23380
|
-
const chr =
|
|
23491
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23381
23492
|
featureArray.push({chr: chr, start: chromStart, end: chromEnd, value: value});
|
|
23382
23493
|
|
|
23383
23494
|
|
|
@@ -23462,7 +23573,8 @@
|
|
|
23462
23573
|
|
|
23463
23574
|
let features;
|
|
23464
23575
|
if ("all" === chr.toLowerCase()) {
|
|
23465
|
-
|
|
23576
|
+
const wgChromosomeNames = this.genome.wgChromosomeNames;
|
|
23577
|
+
features = isBigWig && wgChromosomeNames? await this.getWGValues(wgChromosomeNames, windowFunction, bpPerPixel) : [];
|
|
23466
23578
|
} else {
|
|
23467
23579
|
features = await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
|
|
23468
23580
|
}
|
|
@@ -23486,15 +23598,14 @@
|
|
|
23486
23598
|
|
|
23487
23599
|
}
|
|
23488
23600
|
|
|
23489
|
-
async getWGValues(windowFunction, bpPerPixel) {
|
|
23601
|
+
async getWGValues(wgChromosomeNames, windowFunction, bpPerPixel) {
|
|
23490
23602
|
|
|
23491
23603
|
const genome = this.genome;
|
|
23492
23604
|
const cached = this.#wgValues[windowFunction];
|
|
23493
23605
|
if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
|
|
23494
23606
|
return cached.values
|
|
23495
23607
|
} else {
|
|
23496
|
-
|
|
23497
|
-
const features = await this.reader.readWGFeatures(bpPerPixel, windowFunction);
|
|
23608
|
+
const features = await this.reader.readWGFeatures(wgChromosomeNames, bpPerPixel, windowFunction);
|
|
23498
23609
|
let wgValues = [];
|
|
23499
23610
|
for (let f of features) {
|
|
23500
23611
|
const chr = f.chr;
|
|
@@ -28289,7 +28400,7 @@
|
|
|
28289
28400
|
features
|
|
28290
28401
|
};
|
|
28291
28402
|
|
|
28292
|
-
const track = await browser.
|
|
28403
|
+
const track = (await browser.loadTrackList([trackConfig]))[0];
|
|
28293
28404
|
track.openTableView();
|
|
28294
28405
|
|
|
28295
28406
|
} catch (e) {
|
|
@@ -30341,109 +30452,578 @@
|
|
|
30341
30452
|
}
|
|
30342
30453
|
}
|
|
30343
30454
|
|
|
30344
|
-
|
|
30345
|
-
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
30346
|
-
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
30347
|
-
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
30348
|
-
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
30349
|
-
*/
|
|
30455
|
+
const parentOverrideProperties = new Set(["visibility", "priority", "group"]);
|
|
30350
30456
|
|
|
30457
|
+
const nonInheritableProperties = new Set([
|
|
30458
|
+
"track", "type", "shortLabel", "longLabel", "bigDataUrl",
|
|
30459
|
+
"parent", "superTrack", "priority", "view", "compositeContainer", "compositeTrack"
|
|
30460
|
+
]);
|
|
30351
30461
|
|
|
30352
|
-
class Hub {
|
|
30353
30462
|
|
|
30354
|
-
|
|
30355
|
-
|
|
30356
|
-
|
|
30463
|
+
class Stanza {
|
|
30464
|
+
|
|
30465
|
+
properties = new Map()
|
|
30466
|
+
|
|
30467
|
+
constructor(type, name) {
|
|
30468
|
+
this.type = type;
|
|
30469
|
+
this.name = name;
|
|
30470
|
+
}
|
|
30471
|
+
|
|
30472
|
+
setProperty(key, value) {
|
|
30473
|
+
this.properties.set(key, value);
|
|
30474
|
+
}
|
|
30475
|
+
|
|
30476
|
+
getProperty(key) {
|
|
30477
|
+
if (this.properties.has("noInherit")) {
|
|
30478
|
+
return this.properties.get(key)
|
|
30479
|
+
} else if (this.parent && parentOverrideProperties.has(key) && this.parent.hasProperty(key)) {
|
|
30480
|
+
return this.parent.getProperty(key)
|
|
30481
|
+
} else if (this.properties.has(key)) {
|
|
30482
|
+
return this.properties.get(key)
|
|
30483
|
+
} else if (this.parent && !nonInheritableProperties.has(key)) {
|
|
30484
|
+
return this.parent.getProperty(key)
|
|
30485
|
+
} else {
|
|
30486
|
+
return undefined
|
|
30487
|
+
}
|
|
30488
|
+
}
|
|
30489
|
+
|
|
30490
|
+
hasProperty(key) {
|
|
30491
|
+
return this.getProperty(key) !== null && this.getProperty(key) !== undefined
|
|
30492
|
+
}
|
|
30493
|
+
|
|
30494
|
+
hasOwnProperty(key) {
|
|
30495
|
+
return this.properties.has(key)
|
|
30496
|
+
}
|
|
30497
|
+
|
|
30498
|
+
getOwnProperty(key) {
|
|
30499
|
+
return this.properties.get(key)
|
|
30500
|
+
}
|
|
30501
|
+
|
|
30502
|
+
removeProperty(key) {
|
|
30503
|
+
this.properties.delete(key);
|
|
30504
|
+
}
|
|
30505
|
+
|
|
30506
|
+
get format() {
|
|
30507
|
+
const type = this.getProperty("type");
|
|
30508
|
+
if (type) {
|
|
30509
|
+
// Trim extra bed qualifiers (e.g. bigBed + 4)
|
|
30510
|
+
return firstWord$1(type)
|
|
30511
|
+
}
|
|
30512
|
+
return undefined // unknown type
|
|
30513
|
+
}
|
|
30514
|
+
|
|
30515
|
+
/**
|
|
30516
|
+
* IGV display mode
|
|
30517
|
+
*/
|
|
30518
|
+
get displayMode() {
|
|
30519
|
+
let viz = this.getProperty("visibility");
|
|
30520
|
+
if (!viz) {
|
|
30521
|
+
return "COLLAPSED"
|
|
30522
|
+
} else {
|
|
30523
|
+
viz = viz.toLowerCase();
|
|
30524
|
+
switch (viz) {
|
|
30525
|
+
case "dense":
|
|
30526
|
+
return "COLLAPSED"
|
|
30527
|
+
case "pack":
|
|
30528
|
+
return "EXPANDED"
|
|
30529
|
+
case "squish":
|
|
30530
|
+
return "SQUISHED"
|
|
30531
|
+
default:
|
|
30532
|
+
return "COLLAPSED"
|
|
30533
|
+
}
|
|
30534
|
+
}
|
|
30535
|
+
}
|
|
30536
|
+
}
|
|
30537
|
+
|
|
30538
|
+
|
|
30539
|
+
function firstWord$1(str) {
|
|
30540
|
+
const idx = str.indexOf(' ');
|
|
30541
|
+
return idx > 0 ? str.substring(0, idx) : str
|
|
30542
|
+
}
|
|
30543
|
+
|
|
30544
|
+
class TrackConfigContainer {
|
|
30545
|
+
constructor(name, label, priority, defaultOpen) {
|
|
30546
|
+
this.name = name;
|
|
30547
|
+
this.priority = priority;
|
|
30548
|
+
this.label = label;
|
|
30549
|
+
this.defaultOpen = defaultOpen;
|
|
30550
|
+
this.tracks = [];
|
|
30551
|
+
this.children = [];
|
|
30552
|
+
}
|
|
30553
|
+
|
|
30554
|
+
isEmpty() {
|
|
30555
|
+
return this.tracks.length === 0 &&
|
|
30556
|
+
(!this.children || this.children.length === 0 || this.children.every(child => child.isEmpty()));
|
|
30557
|
+
}
|
|
30558
|
+
|
|
30559
|
+
map(callback) {
|
|
30560
|
+
this.tracks.forEach(callback);
|
|
30561
|
+
this.children.forEach(child => child.map(callback));
|
|
30562
|
+
}
|
|
30563
|
+
|
|
30564
|
+
findTracks(filter) {
|
|
30565
|
+
const found = [];
|
|
30566
|
+
this._find(found, filter);
|
|
30567
|
+
return found;
|
|
30568
|
+
}
|
|
30569
|
+
|
|
30570
|
+
_find(found, filter) {
|
|
30571
|
+
this.tracks.forEach(track => {
|
|
30572
|
+
if (filter(track)) {
|
|
30573
|
+
found.push(track);
|
|
30574
|
+
}
|
|
30575
|
+
});
|
|
30576
|
+
this.children.forEach(child => child._find(found, filter));
|
|
30577
|
+
}
|
|
30578
|
+
|
|
30579
|
+
countTracks() {
|
|
30580
|
+
return this.tracks.length + this.children.reduce((count, child) => count + child.countTracks(), 0);
|
|
30581
|
+
}
|
|
30582
|
+
|
|
30583
|
+
countSelectedTracks() {
|
|
30584
|
+
const selectedCount = this.tracks.filter(track => track.visible).length;
|
|
30585
|
+
return selectedCount + this.children.reduce((count, child) => count + child.countSelectedTracks(), 0);
|
|
30586
|
+
}
|
|
30587
|
+
|
|
30588
|
+
trim() {
|
|
30589
|
+
this.children = this.children.filter(child => !child.isEmpty());
|
|
30590
|
+
this.children.forEach(child => child.trim());
|
|
30591
|
+
}
|
|
30592
|
+
|
|
30593
|
+
setTrackVisibility(loadedTrackPaths) {
|
|
30594
|
+
this.tracks.forEach(track => {
|
|
30595
|
+
track.visible = loadedTrackPaths.has(track.url);
|
|
30596
|
+
});
|
|
30597
|
+
this.children.forEach(child => child.setTrackVisibility(loadedTrackPaths));
|
|
30598
|
+
}
|
|
30599
|
+
}
|
|
30600
|
+
|
|
30601
|
+
const supportedTypes = new Set([
|
|
30602
|
+
"bigbed", "bigwig", "biggenepred", "vcftabix", "refgene",
|
|
30603
|
+
"bam", "sampleinfo", "vcf.list", "ucscsnp", "bed", "tdf", "gff", "gff3", "gtf", "vcf", "vcfphasedtrio",
|
|
30604
|
+
"bigdbsnp", "rmask", "genepred", "wig", "bedgraph", "interact", "broadpeak", "narrowpeak", "gappedpeak",
|
|
30605
|
+
"gistic", "seg", "mut, bigrmsk"
|
|
30606
|
+
]);
|
|
30607
|
+
|
|
30608
|
+
const filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps",
|
|
30609
|
+
"cpgIslandExtUnmasked", "windowMasker"]);
|
|
30610
|
+
|
|
30611
|
+
class TrackDbHub {
|
|
30612
|
+
|
|
30613
|
+
constructor(trackStanzas, groupStanzas) {
|
|
30614
|
+
this.groupStanzas = groupStanzas;
|
|
30615
|
+
this.trackStanzas = trackStanzas;
|
|
30616
|
+
}
|
|
30617
|
+
|
|
30618
|
+
findCytobandURL() {
|
|
30619
|
+
for (const t of this.trackStanzas) {
|
|
30620
|
+
if (t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) {
|
|
30621
|
+
return t.getProperty("bigDataUrl")
|
|
30622
|
+
}
|
|
30623
|
+
}
|
|
30624
|
+
}
|
|
30625
|
+
|
|
30626
|
+
getSupportedTrackCount() {
|
|
30627
|
+
let count = 0;
|
|
30628
|
+
for (const t of this.trackStanzas) {
|
|
30629
|
+
if (!filterTracks.has(t.name) &&
|
|
30630
|
+
t.hasProperty("bigDataUrl") &&
|
|
30631
|
+
t.format &&
|
|
30632
|
+
supportedTypes.has(t.format.toLowerCase())) {
|
|
30633
|
+
count++;
|
|
30634
|
+
}
|
|
30635
|
+
}
|
|
30636
|
+
return count
|
|
30637
|
+
}
|
|
30357
30638
|
|
|
30358
|
-
|
|
30639
|
+
getGroupedTrackConfigurations() {
|
|
30359
30640
|
|
|
30360
|
-
|
|
30361
|
-
|
|
30362
|
-
|
|
30363
|
-
|
|
30364
|
-
|
|
30365
|
-
const
|
|
30366
|
-
|
|
30367
|
-
|
|
30368
|
-
|
|
30641
|
+
if (!this.groupTrackConfigs) {
|
|
30642
|
+
this.groupTrackConfigs = [];
|
|
30643
|
+
const trackContainers = new Map();
|
|
30644
|
+
|
|
30645
|
+
// create a container for tracks with no parent
|
|
30646
|
+
const nullContainer = new TrackConfigContainer('', '', 0, true);
|
|
30647
|
+
this.groupTrackConfigs.push(nullContainer);
|
|
30648
|
+
|
|
30649
|
+
const hasGroups = this.groupStanzas && this.groupStanzas.length > 0;
|
|
30650
|
+
if (hasGroups) {
|
|
30651
|
+
for (const groupStanza of this.groupStanzas) {
|
|
30652
|
+
const name = groupStanza.getProperty("name");
|
|
30653
|
+
const defaultOpen = groupStanza.getProperty("defaultIsClosed") === "0";
|
|
30654
|
+
const priority = groupStanza.hasProperty("priority") ? getPriority(groupStanza.getProperty("priority")) : Number.MAX_SAFE_INTEGER - 1;
|
|
30655
|
+
const container = new TrackConfigContainer(name, groupStanza.getProperty("label"), priority, defaultOpen);
|
|
30656
|
+
trackContainers.set(name, container);
|
|
30657
|
+
this.groupTrackConfigs.push(container);
|
|
30658
|
+
}
|
|
30369
30659
|
}
|
|
30370
30660
|
|
|
30371
|
-
|
|
30372
|
-
|
|
30373
|
-
|
|
30374
|
-
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30661
|
+
for (let s of this.trackStanzas) {
|
|
30662
|
+
|
|
30663
|
+
const isContainer = (s.hasOwnProperty("superTrack") && !s.hasOwnProperty("bigDataUrl")) ||
|
|
30664
|
+
s.hasOwnProperty("compositeTrack") || s.hasOwnProperty("view") ||
|
|
30665
|
+
(s.hasOwnProperty("container") && s.getOwnProperty("container").equals("multiWig"));
|
|
30666
|
+
|
|
30667
|
+
// Find parent, if any. "group" containers can be implicit, all other types should be explicitly
|
|
30668
|
+
// defined before their children
|
|
30669
|
+
let parent;
|
|
30670
|
+
|
|
30671
|
+
if (s.hasOwnProperty("parent")) {
|
|
30672
|
+
parent = trackContainers.get(s.getOwnProperty("parent"));
|
|
30673
|
+
}
|
|
30674
|
+
|
|
30675
|
+
if (!parent && hasGroups && s.hasProperty("group")) {
|
|
30676
|
+
const groupName = s.getProperty("group");
|
|
30677
|
+
if (trackContainers.has(groupName)) {
|
|
30678
|
+
parent = trackContainers.get(groupName);
|
|
30679
|
+
} else {
|
|
30680
|
+
const container = new TrackConfigContainer(groupName, groupName, 1000, true);
|
|
30681
|
+
trackContainers.set(groupName, container);
|
|
30682
|
+
this.groupTrackConfigs.push(container);
|
|
30683
|
+
parent = container;
|
|
30684
|
+
}
|
|
30685
|
+
}
|
|
30686
|
+
|
|
30687
|
+
if (isContainer) {
|
|
30688
|
+
|
|
30689
|
+
const name = s.getProperty("track");
|
|
30690
|
+
const priority = s.hasProperty("priority") ? getPriority(s.getProperty("priority")) : Number.MAX_SAFE_INTEGER - 1;
|
|
30691
|
+
const defaultOpen = s.getProperty("defaultIsClosed") === "0";
|
|
30692
|
+
const longLabel = s.getOwnProperty("longLabel");
|
|
30693
|
+
const label = longLabel && longLabel.length < 50 ? longLabel : s.getOwnProperty("shortLabel");
|
|
30694
|
+
const container = new TrackConfigContainer(name, label, priority, defaultOpen);
|
|
30695
|
+
|
|
30696
|
+
if (trackContainers.has(name)) {
|
|
30697
|
+
throw new Error(`Duplicate track container: ${name}`)
|
|
30698
|
+
}
|
|
30699
|
+
trackContainers.set(name, container);
|
|
30700
|
+
|
|
30701
|
+
if (parent) {
|
|
30702
|
+
parent.children.push(container);
|
|
30703
|
+
} else {
|
|
30704
|
+
// No parent or a superTrack => promote to top level
|
|
30705
|
+
this.groupTrackConfigs.push(container);
|
|
30706
|
+
}
|
|
30707
|
+
} else if (!filterTracks.has(s.name) &&
|
|
30708
|
+
s.hasProperty("bigDataUrl") &&
|
|
30709
|
+
s.format &&
|
|
30710
|
+
supportedTypes.has(s.format.toLowerCase())) {
|
|
30711
|
+
|
|
30712
|
+
const trackConfig = this.#getTrackConfig(s);
|
|
30713
|
+
if (parent) {
|
|
30714
|
+
parent.tracks.push(trackConfig);
|
|
30715
|
+
} else {
|
|
30716
|
+
nullContainer.tracks.push(trackConfig);
|
|
30717
|
+
}
|
|
30378
30718
|
}
|
|
30379
30719
|
}
|
|
30720
|
+
|
|
30380
30721
|
}
|
|
30381
30722
|
|
|
30382
|
-
//
|
|
30383
|
-
|
|
30384
|
-
|
|
30385
|
-
// if ("include" === s.type) {
|
|
30386
|
-
// const includeStanzas = await loadStanzas(baseURL + s.getProperty("include"))
|
|
30387
|
-
// for (s of includeStanzas) {
|
|
30388
|
-
// s.setProperty("visibility", "hide")
|
|
30389
|
-
// stanzas.push(s)
|
|
30390
|
-
// }
|
|
30391
|
-
// }
|
|
30392
|
-
// }
|
|
30723
|
+
// Filter empty groups and sort
|
|
30724
|
+
this.groupTrackConfigs.forEach(c => c.trim());
|
|
30725
|
+
this.groupTrackConfigs = this.groupTrackConfigs.filter(t => !t.isEmpty());
|
|
30393
30726
|
|
|
30394
|
-
|
|
30727
|
+
this.groupTrackConfigs.sort((a, b) => a.priority - b.priority);
|
|
30728
|
+
return this.groupTrackConfigs
|
|
30395
30729
|
}
|
|
30396
30730
|
|
|
30397
|
-
|
|
30731
|
+
/**
|
|
30732
|
+
* Return an array of igv track config objects that satisfy the filter
|
|
30733
|
+
*/
|
|
30734
|
+
#getTracksConfigs(filter) {
|
|
30735
|
+
return this.trackStanzas.filter(t => {
|
|
30736
|
+
return supportedTypes.has(t.format) && t.hasProperty("bigDataUrl") && (!filter || filter(t))
|
|
30737
|
+
})
|
|
30738
|
+
.map(t => this.#getTrackConfig(t))
|
|
30739
|
+
}
|
|
30398
30740
|
|
|
30399
|
-
this.url = url;
|
|
30400
30741
|
|
|
30401
|
-
|
|
30402
|
-
|
|
30742
|
+
/** example
|
|
30743
|
+
* track gc5Base
|
|
30744
|
+
* shortLabel GC Percent
|
|
30745
|
+
* longLabel GC Percent in 5-Base Windows
|
|
30746
|
+
* group map
|
|
30747
|
+
* visibility full
|
|
30748
|
+
* autoScale Off
|
|
30749
|
+
* maxHeightPixels 128:36:16
|
|
30750
|
+
* graphTypeDefault Bar
|
|
30751
|
+
* gridDefault OFF
|
|
30752
|
+
* windowingFunction Mean
|
|
30753
|
+
* color 0,0,0
|
|
30754
|
+
* altColor 128,128,128
|
|
30755
|
+
* viewLimits 30:70
|
|
30756
|
+
* type bigWig 0 100
|
|
30757
|
+
* bigDataUrl bbi/GCA_011100615.1_Macaca_fascicularis_6.0.gc5Base.bw
|
|
30758
|
+
* html html/GCA_011100615.1_Macaca_fascicularis_6.0.gc5Base
|
|
30759
|
+
* @param t
|
|
30760
|
+
*/
|
|
30761
|
+
#getTrackConfig(t) {
|
|
30762
|
+
|
|
30763
|
+
const format = t.format;
|
|
30403
30764
|
|
|
30404
|
-
|
|
30405
|
-
|
|
30406
|
-
|
|
30407
|
-
|
|
30408
|
-
|
|
30765
|
+
const config = {
|
|
30766
|
+
"id": t.getProperty("track"),
|
|
30767
|
+
"name": t.getProperty("shortLabel"),
|
|
30768
|
+
"format": format,
|
|
30769
|
+
"url": t.getProperty("bigDataUrl"),
|
|
30770
|
+
"displayMode": t.displayMode,
|
|
30771
|
+
};
|
|
30772
|
+
|
|
30773
|
+
if ("vcfTabix" === format) {
|
|
30774
|
+
config.indexURL = config.url + ".tbi";
|
|
30409
30775
|
}
|
|
30410
|
-
|
|
30411
|
-
|
|
30776
|
+
|
|
30777
|
+
if (t.hasProperty("longLabel") && t.hasProperty("html")) {
|
|
30778
|
+
if (config.description) config.description += "<br/>";
|
|
30779
|
+
config.description =
|
|
30780
|
+
`<a target="_blank" href="${t.getProperty("html")}">${t.getProperty("longLabel")}</a>`;
|
|
30781
|
+
} else if (t.hasProperty("longLabel")) {
|
|
30782
|
+
config.description = t.getProperty("longLabel");
|
|
30412
30783
|
}
|
|
30413
|
-
|
|
30414
|
-
|
|
30784
|
+
|
|
30785
|
+
if (t.hasProperty("autoScale")) {
|
|
30786
|
+
config.autoscale = t.getProperty("autoScale").toLowerCase() === "on";
|
|
30415
30787
|
}
|
|
30788
|
+
if (t.hasProperty("maxHeightPixels")) {
|
|
30789
|
+
const tokens = t.getProperty("maxHeightPixels").split(":");
|
|
30790
|
+
config.maxHeight = Number.parseInt(tokens[0]);
|
|
30791
|
+
config.height = Number.parseInt(tokens[1]);
|
|
30792
|
+
config.minHeight = Number.parseInt(tokens[2]);
|
|
30793
|
+
}
|
|
30794
|
+
// TODO -- graphTypeDefault
|
|
30795
|
+
// TODO -- windowingFunction
|
|
30796
|
+
if (t.hasProperty("color")) {
|
|
30797
|
+
const c = t.getProperty("color");
|
|
30798
|
+
config.color = c.indexOf(",") > 0 ? `rgb(${c})` : c;
|
|
30799
|
+
}
|
|
30800
|
+
if (t.hasProperty("altColor")) {
|
|
30801
|
+
const c = t.getProperty("altColor");
|
|
30802
|
+
config.altColor = c.indexOf(",") > 0 ? `rgb(${c})` : c;
|
|
30803
|
+
}
|
|
30804
|
+
if (t.hasProperty("viewLimits")) {
|
|
30805
|
+
const tokens = t.getProperty("viewLimits").split(":");
|
|
30806
|
+
let min, max;
|
|
30807
|
+
if (tokens.length > 1) {
|
|
30808
|
+
min = Number.parseInt(tokens[0]);
|
|
30809
|
+
max = Number.parseInt(tokens[1]);
|
|
30810
|
+
}
|
|
30811
|
+
if (Number.isNaN(max) || Number.isNaN(min)) {
|
|
30812
|
+
console.warn(`Unexpected viewLimits value in track line: ${properties["viewLimits"]}`);
|
|
30813
|
+
} else {
|
|
30814
|
+
config.min = min;
|
|
30815
|
+
config.max = max;
|
|
30816
|
+
}
|
|
30416
30817
|
|
|
30417
|
-
|
|
30418
|
-
if ("
|
|
30419
|
-
|
|
30420
|
-
|
|
30421
|
-
|
|
30818
|
+
}
|
|
30819
|
+
if (t.hasProperty("itemRgb")) ;
|
|
30820
|
+
if ("hide" === t.getProperty("visibility")) {
|
|
30821
|
+
// TODO -- this not supported yet
|
|
30822
|
+
config.visible = false;
|
|
30823
|
+
}
|
|
30824
|
+
if (t.hasProperty("url")) {
|
|
30825
|
+
config.infoURL = t.getProperty("url");
|
|
30826
|
+
}
|
|
30827
|
+
if (t.hasProperty("searchIndex")) {
|
|
30828
|
+
config.searchIndex = t.getProperty("searchIndex");
|
|
30829
|
+
}
|
|
30830
|
+
if (t.hasProperty("searchTrix")) {
|
|
30831
|
+
config.trixURL = t.getProperty("searchTrix");
|
|
30832
|
+
}
|
|
30833
|
+
if (t.hasProperty("html")) {
|
|
30834
|
+
config.html = t.getProperty("html");
|
|
30835
|
+
}
|
|
30836
|
+
|
|
30837
|
+
if (t.hasProperty("group")) {
|
|
30838
|
+
config._group = t.getProperty("group");
|
|
30839
|
+
if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
|
|
30840
|
+
const nextPriority = this.groupPriorityMap.get(config._group) + 1;
|
|
30841
|
+
config.order = nextPriority;
|
|
30842
|
+
this.groupPriorityMap.set(config._group, nextPriority);
|
|
30843
|
+
}
|
|
30844
|
+
}
|
|
30845
|
+
|
|
30846
|
+
if (t.hasProperty("metadata")) {
|
|
30847
|
+
config.attributes = parseMetadata(t.getProperty("metadata"));
|
|
30848
|
+
}
|
|
30849
|
+
|
|
30850
|
+
if (t.hasProperty("maxWindowToDraw")) {
|
|
30851
|
+
let maxWindowToDraw = parseInt(t.getProperty("maxWindowToDraw"), 10);
|
|
30852
|
+
if (maxWindowToDraw > Number.MAX_SAFE_INTEGER) {
|
|
30853
|
+
maxWindowToDraw = Number.MAX_SAFE_INTEGER;
|
|
30854
|
+
}
|
|
30855
|
+
config.visibilityWindow = maxWindowToDraw;
|
|
30422
30856
|
}
|
|
30423
30857
|
|
|
30424
|
-
//
|
|
30425
|
-
|
|
30426
|
-
|
|
30427
|
-
if (
|
|
30428
|
-
|
|
30858
|
+
// IGV does not support "maxWindowCoverage" in the same way as UCSC. Use to limit visibility window
|
|
30859
|
+
if (t.hasProperty("maxWindowCoverage")) {
|
|
30860
|
+
let maxWindowToDraw = parseInt(t.getProperty("maxWindowCoverage"), 10);
|
|
30861
|
+
if (maxWindowToDraw > Number.MAX_SAFE_INTEGER) {
|
|
30862
|
+
maxWindowToDraw = Number.MAX_SAFE_INTEGER;
|
|
30429
30863
|
}
|
|
30864
|
+
config.visibilityWindow = maxWindowToDraw;
|
|
30430
30865
|
}
|
|
30431
30866
|
|
|
30432
|
-
|
|
30433
|
-
|
|
30434
|
-
|
|
30435
|
-
|
|
30436
|
-
|
|
30437
|
-
|
|
30867
|
+
return config
|
|
30868
|
+
}
|
|
30869
|
+
}
|
|
30870
|
+
|
|
30871
|
+
function htmlText(html) {
|
|
30872
|
+
// Assumes a pattern like <span style="color:#C58DAA">Digestive</span>
|
|
30873
|
+
const idx1 = html.indexOf('>');
|
|
30874
|
+
const idx2 = html.indexOf('<', idx1);
|
|
30875
|
+
if (idx1 > 0 && idx2 > idx1) {
|
|
30876
|
+
return html.substring(idx1 + 1, idx2)
|
|
30877
|
+
} else {
|
|
30878
|
+
return html
|
|
30879
|
+
}
|
|
30880
|
+
}
|
|
30881
|
+
|
|
30882
|
+
/**
|
|
30883
|
+
* Return the priority for the group. The priority format is uncertain, but extends to at least 2 levels (e.g. 3.4).
|
|
30884
|
+
* Ignore levels > 3
|
|
30885
|
+
*
|
|
30886
|
+
* @param {string} priorityString Priority as a string (e.g. 3.4)
|
|
30887
|
+
* @return {number} A priority as an integer
|
|
30888
|
+
*/
|
|
30889
|
+
function getPriority(priorityString) {
|
|
30890
|
+
try {
|
|
30891
|
+
const tokens = priorityString.trim().split(".");
|
|
30892
|
+
let p = parseInt(tokens[0], 10) * 100;
|
|
30893
|
+
if (tokens.length > 1) {
|
|
30894
|
+
p += parseInt(tokens[1], 10) * 10;
|
|
30895
|
+
}
|
|
30896
|
+
if (tokens.length > 2) {
|
|
30897
|
+
p += parseInt(tokens[2], 10);
|
|
30898
|
+
}
|
|
30899
|
+
return p
|
|
30900
|
+
} catch (e) {
|
|
30901
|
+
console.error(`Error parsing priority string: ${priorityString}`, e);
|
|
30902
|
+
return Number.MAX_SAFE_INTEGER
|
|
30903
|
+
}
|
|
30904
|
+
}
|
|
30905
|
+
|
|
30906
|
+
function parseMetadata(metadata) {
|
|
30907
|
+
const attrs = new Map();
|
|
30908
|
+
let lastMetdataLengh = -1;
|
|
30909
|
+
while (metadata && metadata.length > 0) {
|
|
30910
|
+
try {
|
|
30911
|
+
if (metadata.length === lastMetdataLengh) {
|
|
30912
|
+
break
|
|
30913
|
+
}
|
|
30914
|
+
lastMetdataLengh = metadata.length;
|
|
30915
|
+
let idx = metadata.indexOf("=");
|
|
30916
|
+
if (idx === -1 || idx === metadata.length - 1) {
|
|
30917
|
+
break
|
|
30918
|
+
}
|
|
30919
|
+
let idx2;
|
|
30920
|
+
const key = capitalize(stripQuotes$2(metadata.substring(0, idx)));
|
|
30921
|
+
let value;
|
|
30922
|
+
|
|
30923
|
+
if (metadata.charAt(idx + 1) === '"') {
|
|
30924
|
+
idx++;
|
|
30925
|
+
idx2 = metadata.indexOf('" ', idx + 1);
|
|
30926
|
+
value = idx2 > 0 ? metadata.substring(idx + 1, idx2) : metadata.substring(idx + 1);
|
|
30927
|
+
idx2++;
|
|
30928
|
+
} else {
|
|
30929
|
+
idx2 = metadata.indexOf(" ", idx + 1);
|
|
30930
|
+
if (idx2 === -1) {
|
|
30931
|
+
idx2 = metadata.length;
|
|
30438
30932
|
}
|
|
30933
|
+
value = metadata.substring(idx + 1, idx2);
|
|
30934
|
+
}
|
|
30935
|
+
value = stripQuotes$2(value);
|
|
30936
|
+
if (value.endsWith('"')) {
|
|
30937
|
+
value = value.substring(0, value.length - 1);
|
|
30938
|
+
}
|
|
30939
|
+
if (value.startsWith("<") && value.endsWith(">")) {
|
|
30940
|
+
value = htmlText(value);
|
|
30439
30941
|
}
|
|
30942
|
+
attrs.set(key, value);
|
|
30943
|
+
if (idx2 === metadata.length) {
|
|
30944
|
+
break
|
|
30945
|
+
}
|
|
30946
|
+
metadata = idx2 > 0 ? metadata.substring(idx2 + 1).trim() : "";
|
|
30947
|
+
} catch (e) {
|
|
30948
|
+
// We don't want to fail parsing the hub due to a failure parsing metadata. Also, we don't want to
|
|
30949
|
+
// overwhelm the log. Metadata is of marginal importance in IGV.
|
|
30950
|
+
}
|
|
30951
|
+
}
|
|
30952
|
+
return attrs
|
|
30953
|
+
}
|
|
30954
|
+
|
|
30955
|
+
/*
|
|
30956
|
+
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
30957
|
+
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
30958
|
+
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
30959
|
+
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
30960
|
+
*/
|
|
30961
|
+
|
|
30962
|
+
const idMappings = new Map([
|
|
30963
|
+
["hg38", "GCF_000001405.40"],
|
|
30964
|
+
["mm39", "GCF_000001635.27"],
|
|
30965
|
+
["mm10", "GCF_000001635.26"],
|
|
30966
|
+
["bosTau9", "GCF_002263795.1"],
|
|
30967
|
+
["canFam4", "GCF_011100685.1"],
|
|
30968
|
+
["canFam6", "GCF_000002285.5"],
|
|
30969
|
+
["ce11", "GCF_000002985.6"],
|
|
30970
|
+
["dm6", "GCF_000001215.4"],
|
|
30971
|
+
["galGal6", "GCF_000002315.6"],
|
|
30972
|
+
["gorGor6", "GCF_008122165.1"],
|
|
30973
|
+
["macFas5", "GCA_000364345.1"],
|
|
30974
|
+
["panTro6", "GCA_002880755.3"],
|
|
30975
|
+
["rn6", "GCF_000001895.5"],
|
|
30976
|
+
["rn7", "GCF_015227675.2"],
|
|
30977
|
+
["sacCer3", "GCF_000146045.2"],
|
|
30978
|
+
["sacCer2", "GCF_000146045.2"],
|
|
30979
|
+
["susScr11", "GCF_000003025.6"],
|
|
30980
|
+
["taeGut1", "GCF_000002275.3"],
|
|
30981
|
+
["tetNig2", "GCF_000002275.3"],
|
|
30982
|
+
["xenTro10", "GCF_000002035.6"],
|
|
30983
|
+
["xenTro9", "GCF_000002035.6"],
|
|
30984
|
+
["tair10", "GCF_000001735.4"],
|
|
30985
|
+
]);
|
|
30986
|
+
|
|
30987
|
+
class Hub {
|
|
30988
|
+
|
|
30989
|
+
static supportedTypes = new Set(["bigBed", "bigWig", "bigGenePred", "vcfTabix"])
|
|
30990
|
+
static filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps",
|
|
30991
|
+
"cpgIslandExtUnmasked", "windowMasker"])
|
|
30992
|
+
|
|
30993
|
+
constructor(url, hubStanza, genomeStanzas, trackStanzas, groupStanzas) {
|
|
30994
|
+
|
|
30995
|
+
this.url = url;
|
|
30996
|
+
this.hubStanza = hubStanza;
|
|
30997
|
+
this.genomeStanzas = genomeStanzas;
|
|
30998
|
+
this.trackStanzas = trackStanzas;
|
|
30999
|
+
this.groupStanzas = groupStanzas;
|
|
31000
|
+
this.trackHubMap = new Map();
|
|
31001
|
+
|
|
31002
|
+
// trackStanzas will not be null if this is a "onefile" hub
|
|
31003
|
+
if (trackStanzas) {
|
|
31004
|
+
const genomeId = genomeStanzas[0].getProperty("genome"); // Assumption here this is a single genome hub
|
|
31005
|
+
this.trackHubMap.set(genomeId, new TrackDbHub(trackStanzas, groupStanzas));
|
|
30440
31006
|
}
|
|
30441
31007
|
}
|
|
30442
31008
|
|
|
30443
|
-
|
|
30444
|
-
|
|
31009
|
+
|
|
31010
|
+
getName() {
|
|
31011
|
+
return this.hubStanza.getProperty("hub")
|
|
30445
31012
|
}
|
|
30446
31013
|
|
|
31014
|
+
getShortLabel() {
|
|
31015
|
+
return this.hubStanza.getProperty("shortLabel")
|
|
31016
|
+
}
|
|
31017
|
+
|
|
31018
|
+
getLongLabel() {
|
|
31019
|
+
return this.hubStanza.getProperty("longLabel")
|
|
31020
|
+
}
|
|
31021
|
+
|
|
31022
|
+
getDescriptionUrl() {
|
|
31023
|
+
return this.hubStanza.getProperty("descriptionUrl")
|
|
31024
|
+
}
|
|
31025
|
+
|
|
31026
|
+
|
|
30447
31027
|
/* Example genome stanza
|
|
30448
31028
|
genome GCF_000186305.1
|
|
30449
31029
|
taxId 176946
|
|
@@ -30462,133 +31042,126 @@
|
|
|
30462
31042
|
isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
30463
31043
|
*/
|
|
30464
31044
|
|
|
30465
|
-
getGenomeConfig(
|
|
30466
|
-
|
|
31045
|
+
getGenomeConfig(genomeId) {
|
|
31046
|
+
|
|
31047
|
+
const genomeStanza = genomeId ? this.genomeStanzas.find(s => s.getProperty("genome") === genomeId) : this.genomeStanzas[0];
|
|
31048
|
+
if (!genomeStanza) {
|
|
31049
|
+
throw new Error(`Genome not found in hub: ${genomeId}`)
|
|
31050
|
+
}
|
|
31051
|
+
return this.#getGenomeConfig(genomeStanza)
|
|
31052
|
+
}
|
|
31053
|
+
|
|
31054
|
+
#getGenomeConfig(genomeStanza) {
|
|
30467
31055
|
|
|
30468
|
-
const id =
|
|
31056
|
+
const id = genomeStanza.getProperty("genome");
|
|
30469
31057
|
const gsName =
|
|
30470
31058
|
this.hubStanza.getProperty("shortLabel") ||
|
|
30471
|
-
|
|
30472
|
-
|
|
30473
|
-
|
|
31059
|
+
genomeStanza.getProperty("scientificName") ||
|
|
31060
|
+
genomeStanza.getProperty("organism") ||
|
|
31061
|
+
genomeStanza.getProperty("description");
|
|
30474
31062
|
const name = gsName + (gsName ? ` (${id})` : ` ${id}`);
|
|
30475
31063
|
|
|
30476
31064
|
const config = {
|
|
30477
|
-
|
|
31065
|
+
|
|
30478
31066
|
id: id,
|
|
30479
31067
|
name: name,
|
|
30480
|
-
twoBitURL:
|
|
31068
|
+
twoBitURL: genomeStanza.getProperty("twoBitPath"),
|
|
30481
31069
|
nameSet: "ucsc",
|
|
31070
|
+
hubs: [this.url]
|
|
30482
31071
|
};
|
|
30483
31072
|
|
|
30484
|
-
if (
|
|
30485
|
-
config.chromSizesURL =
|
|
31073
|
+
if (genomeStanza.hasProperty("chromSizes")) {
|
|
31074
|
+
config.chromSizesURL = genomeStanza.getProperty("chromSizes");
|
|
30486
31075
|
} else {
|
|
30487
31076
|
config.wholeGenomeView = false;
|
|
30488
31077
|
config.showChromosomeWidget = false;
|
|
30489
31078
|
}
|
|
30490
31079
|
|
|
30491
|
-
if (
|
|
30492
|
-
const hubLocus =
|
|
31080
|
+
if (genomeStanza.hasProperty("defaultPos")) {
|
|
31081
|
+
const hubLocus = genomeStanza.getProperty("defaultPos");
|
|
30493
31082
|
// Strip out coordinates => whole chromosome view
|
|
30494
|
-
if (hubLocus) {
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
}
|
|
31083
|
+
// if (hubLocus) {
|
|
31084
|
+
// const idx = hubLocus.lastIndexOf(":")
|
|
31085
|
+
// config.locus = idx > 0 ? hubLocus.substring(0, idx) : hubLocus
|
|
31086
|
+
// }
|
|
31087
|
+
config.locus = hubLocus;
|
|
30498
31088
|
}
|
|
30499
31089
|
|
|
30500
|
-
if (
|
|
30501
|
-
config.blat =
|
|
30502
|
-
}
|
|
30503
|
-
if (this.genomeStanza.hasProperty("chromAliasBb")) {
|
|
30504
|
-
config.chromAliasBbURL = this.baseURL + this.genomeStanza.getProperty("chromAliasBb");
|
|
31090
|
+
if (genomeStanza.hasProperty("blat")) {
|
|
31091
|
+
config.blat = genomeStanza.getProperty("blat");
|
|
30505
31092
|
}
|
|
30506
|
-
if (
|
|
30507
|
-
config.
|
|
31093
|
+
if (genomeStanza.hasProperty("chromAliasBb")) {
|
|
31094
|
+
config.chromAliasBbURL = genomeStanza.getProperty("chromAliasBb");
|
|
30508
31095
|
}
|
|
30509
|
-
if (
|
|
30510
|
-
config.
|
|
31096
|
+
if (genomeStanza.hasProperty("chromAlias")) {
|
|
31097
|
+
config.aliasURL = genomeStanza.getProperty("chromAlias");
|
|
30511
31098
|
}
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
config.twoBitBptURL = this.baseURL + this.genomeStanza.getProperty("twoBitBptUrl");
|
|
31099
|
+
if (genomeStanza.hasProperty("twoBitBptURL")) {
|
|
31100
|
+
config.twoBitBptURL = genomeStanza.getProperty("twoBitBptURL");
|
|
30515
31101
|
}
|
|
30516
31102
|
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
config.chromSizesURL = this.baseURL + this.genomeStanza.getProperty("chromSizes");
|
|
31103
|
+
if (genomeStanza.hasProperty("twoBitBptUrl")) {
|
|
31104
|
+
config.twoBitBptURL = genomeStanza.getProperty("twoBitBptUrl");
|
|
30520
31105
|
}
|
|
30521
31106
|
|
|
30522
31107
|
if (this.hubStanza.hasProperty("longLabel")) {
|
|
30523
31108
|
config.description = this.hubStanza.getProperty("longLabel").replace("/", "\n");
|
|
30524
31109
|
} else {
|
|
30525
31110
|
config.description = config.id;
|
|
30526
|
-
if (
|
|
30527
|
-
config.description += `\n${
|
|
31111
|
+
if (genomeStanza.hasProperty("description")) {
|
|
31112
|
+
config.description += `\n${genomeStanza.getProperty("description")}`;
|
|
30528
31113
|
}
|
|
30529
|
-
if (
|
|
30530
|
-
config.description += `\n${
|
|
31114
|
+
if (genomeStanza.hasProperty("organism")) {
|
|
31115
|
+
config.description += `\n${genomeStanza.getProperty("organism")}`;
|
|
30531
31116
|
}
|
|
30532
|
-
if (
|
|
30533
|
-
config.description += `\n${
|
|
31117
|
+
if (genomeStanza.hasProperty("scientificName")) {
|
|
31118
|
+
config.description += `\n${genomeStanza.getProperty("scientificName")}`;
|
|
30534
31119
|
}
|
|
30535
31120
|
|
|
30536
|
-
if (
|
|
30537
|
-
config.infoURL =
|
|
31121
|
+
if (genomeStanza.hasProperty("htmlPath")) {
|
|
31122
|
+
config.infoURL = genomeStanza.getProperty("htmlPath");
|
|
30538
31123
|
}
|
|
30539
31124
|
}
|
|
30540
31125
|
|
|
30541
|
-
// Search for cytoband
|
|
30542
|
-
/*
|
|
30543
|
-
track cytoBandIdeo
|
|
30544
|
-
shortLabel Chromosome Band (Ideogram)
|
|
30545
|
-
longLabel Ideogram for Orientation
|
|
30546
|
-
group map
|
|
30547
|
-
visibility dense
|
|
30548
|
-
type bigBed 4 +
|
|
30549
|
-
bigDataUrl bbi/GCA_004027145.1_DauMad_v1_BIUU.cytoBand.bb
|
|
30550
|
-
*/
|
|
30551
|
-
const cytoStanza = this.trackStanzas.filter(t => "cytoBandIdeo" === t.name && t.hasProperty("bigDataUrl"));
|
|
30552
|
-
if (cytoStanza.length > 0) {
|
|
30553
|
-
config.cytobandBbURL = this.baseURL + cytoStanza[0].getProperty("bigDataUrl");
|
|
30554
|
-
}
|
|
30555
|
-
|
|
30556
31126
|
// Tracks.
|
|
30557
31127
|
const filter = (t) => !Hub.filterTracks.has(t.name) && "hide" !== t.getProperty("visibility");
|
|
30558
31128
|
config.tracks = this.#getTracksConfigs(filter);
|
|
30559
31129
|
|
|
30560
|
-
|
|
30561
31130
|
return config
|
|
30562
31131
|
}
|
|
30563
31132
|
|
|
30564
|
-
getGroupedTrackConfigurations() {
|
|
30565
|
-
|
|
30566
|
-
|
|
30567
|
-
|
|
30568
|
-
for (let c of this.#getTracksConfigs()) {
|
|
30569
|
-
if (c.name === "cytoBandIdeo") continue
|
|
30570
|
-
const groupName = c.group || "other";
|
|
30571
|
-
if (trackConfigMap.has(groupName)) {
|
|
30572
|
-
trackConfigMap.get(groupName).push(c);
|
|
30573
|
-
} else {
|
|
30574
|
-
trackConfigMap.set(groupName, [c]);
|
|
30575
|
-
}
|
|
31133
|
+
async getGroupedTrackConfigurations(genomeId) {
|
|
31134
|
+
let trackHub = await this.#getTrackDbHub(genomeId);
|
|
31135
|
+
if (!trackHub && idMappings.has(genomeId)) {
|
|
31136
|
+
trackHub = await this.#getTrackDbHub(idMappings.get(genomeId));
|
|
30576
31137
|
}
|
|
31138
|
+
if (!trackHub) {
|
|
31139
|
+
console.log(`Warning: no trackDB found for genomeId ${genomeId}.`);
|
|
31140
|
+
}
|
|
31141
|
+
return trackHub ? trackHub.getGroupedTrackConfigurations() : []
|
|
31142
|
+
}
|
|
30577
31143
|
|
|
30578
|
-
|
|
30579
|
-
|
|
30580
|
-
|
|
30581
|
-
|
|
30582
|
-
|
|
30583
|
-
|
|
30584
|
-
|
|
30585
|
-
|
|
30586
|
-
|
|
31144
|
+
async #getTrackDbHub(genomeId) {
|
|
31145
|
+
let trackHub = this.trackHubMap.get(genomeId);
|
|
31146
|
+
if (!trackHub) {
|
|
31147
|
+
for (let stanza of this.genomeStanzas) {
|
|
31148
|
+
if (genomeId === stanza.getProperty("genome")) {
|
|
31149
|
+
try {
|
|
31150
|
+
const trackDbURL = stanza.getProperty("trackDb");
|
|
31151
|
+
const trackStanzas = await loadStanzas(trackDbURL);
|
|
31152
|
+
trackHub = new TrackDbHub(trackStanzas, this.groupStanzas);
|
|
31153
|
+
this.trackHubMap.set(genomeId, trackHub);
|
|
31154
|
+
} catch (error) {
|
|
31155
|
+
console.error(`Error loading trackDb file: ${stanza.getProperty("trackDb")}`, error);
|
|
31156
|
+
}
|
|
31157
|
+
break
|
|
31158
|
+
}
|
|
30587
31159
|
}
|
|
30588
|
-
}
|
|
30589
|
-
|
|
31160
|
+
}
|
|
31161
|
+
return trackHub
|
|
30590
31162
|
}
|
|
30591
31163
|
|
|
31164
|
+
|
|
30592
31165
|
/**
|
|
30593
31166
|
* Return an array of igv track config objects that satisfy the filter
|
|
30594
31167
|
*/
|
|
@@ -30626,7 +31199,7 @@
|
|
|
30626
31199
|
"id": t.getProperty("track"),
|
|
30627
31200
|
"name": t.getProperty("shortLabel"),
|
|
30628
31201
|
"format": format,
|
|
30629
|
-
"url":
|
|
31202
|
+
"url": t.getProperty("bigDataUrl"),
|
|
30630
31203
|
"displayMode": t.displayMode,
|
|
30631
31204
|
};
|
|
30632
31205
|
|
|
@@ -30637,7 +31210,7 @@
|
|
|
30637
31210
|
if (t.hasProperty("longLabel") && t.hasProperty("html")) {
|
|
30638
31211
|
if (config.description) config.description += "<br/>";
|
|
30639
31212
|
config.description =
|
|
30640
|
-
`<a target="_blank" href="${
|
|
31213
|
+
`<a target="_blank" href="${t.getProperty("html")}">${t.getProperty("longLabel")}</a>`;
|
|
30641
31214
|
} else if (t.hasProperty("longLabel")) {
|
|
30642
31215
|
config.description = t.getProperty("longLabel");
|
|
30643
31216
|
}
|
|
@@ -30669,7 +31242,7 @@
|
|
|
30669
31242
|
max = Number.parseInt(tokens[1]);
|
|
30670
31243
|
}
|
|
30671
31244
|
if (Number.isNaN(max) || Number.isNaN(min)) {
|
|
30672
|
-
console.warn(`Unexpected viewLimits value in track line: ${
|
|
31245
|
+
console.warn(`Unexpected viewLimits value in track line: ${t.getProperty("viewLimits")}`);
|
|
30673
31246
|
} else {
|
|
30674
31247
|
config.min = min;
|
|
30675
31248
|
config.max = max;
|
|
@@ -30688,15 +31261,15 @@
|
|
|
30688
31261
|
config.searchIndex = t.getProperty("searchIndex");
|
|
30689
31262
|
}
|
|
30690
31263
|
if (t.hasProperty("searchTrix")) {
|
|
30691
|
-
config.trixURL =
|
|
31264
|
+
config.trixURL = t.getProperty("searchTrix");
|
|
30692
31265
|
}
|
|
30693
31266
|
|
|
30694
31267
|
if (t.hasProperty("group")) {
|
|
30695
|
-
config.
|
|
30696
|
-
if (this.groupPriorityMap && this.groupPriorityMap.has(config.
|
|
30697
|
-
const nextPriority = this.groupPriorityMap.get(config.
|
|
31268
|
+
config._group = t.getProperty("group");
|
|
31269
|
+
if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
|
|
31270
|
+
const nextPriority = this.groupPriorityMap.get(config._group) + 1;
|
|
30698
31271
|
config.order = nextPriority;
|
|
30699
|
-
this.groupPriorityMap.set(config.
|
|
31272
|
+
this.groupPriorityMap.set(config._group, nextPriority);
|
|
30700
31273
|
}
|
|
30701
31274
|
}
|
|
30702
31275
|
|
|
@@ -30705,96 +31278,92 @@
|
|
|
30705
31278
|
|
|
30706
31279
|
}
|
|
30707
31280
|
|
|
30708
|
-
|
|
30709
|
-
|
|
30710
|
-
|
|
30711
|
-
|
|
31281
|
+
/*
|
|
31282
|
+
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
31283
|
+
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
31284
|
+
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
31285
|
+
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
31286
|
+
*/
|
|
30712
31287
|
|
|
30713
|
-
|
|
31288
|
+
const urlProperties = new Set(["descriptionUrl", "desriptionUrl",
|
|
31289
|
+
"twoBitPath", "blat", "chromAliasBb", "twoBitBptURL", "twoBitBptUrl", "htmlPath", "bigDataUrl",
|
|
31290
|
+
"genomesFile", "trackDb", "groups", "include", "html", "searchTrix", "groups",
|
|
31291
|
+
"chromSizes"]);
|
|
30714
31292
|
|
|
30715
|
-
properties = new Map()
|
|
30716
31293
|
|
|
30717
|
-
|
|
30718
|
-
this.type = type;
|
|
30719
|
-
this.name = name;
|
|
30720
|
-
}
|
|
31294
|
+
const hubCache = new Map();
|
|
30721
31295
|
|
|
30722
|
-
|
|
30723
|
-
|
|
31296
|
+
async function loadHub(url) {
|
|
31297
|
+
if (hubCache.has(url)) {
|
|
31298
|
+
return hubCache.get(url)
|
|
30724
31299
|
}
|
|
30725
31300
|
|
|
30726
|
-
|
|
30727
|
-
|
|
30728
|
-
|
|
30729
|
-
} else if (this.parent) {
|
|
30730
|
-
return this.parent.getProperty(key)
|
|
30731
|
-
} else {
|
|
30732
|
-
return undefined
|
|
30733
|
-
}
|
|
31301
|
+
const stanzas = await loadStanzas(url);
|
|
31302
|
+
if (stanzas.length < 1) {
|
|
31303
|
+
throw new Error("Empty hub file")
|
|
30734
31304
|
}
|
|
30735
31305
|
|
|
30736
|
-
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
} else if (this.parent) {
|
|
30740
|
-
return this.parent.hasProperty(key)
|
|
30741
|
-
} else {
|
|
30742
|
-
return false
|
|
30743
|
-
}
|
|
31306
|
+
const hubStanza = stanzas[0];
|
|
31307
|
+
if (hubStanza.type !== "hub") {
|
|
31308
|
+
throw new Error("First stanza must be a hub stanza")
|
|
30744
31309
|
}
|
|
30745
31310
|
|
|
30746
|
-
|
|
30747
|
-
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
|
|
31311
|
+
let genomeStanzas;
|
|
31312
|
+
let trackStanzas;
|
|
31313
|
+
if (hubStanza.getProperty("useOneFile") === "on") {
|
|
31314
|
+
// This is a "onefile" hub, all stanzas are in the same file
|
|
31315
|
+
if (stanzas[1].type !== "genome") {
|
|
31316
|
+
throw new Error("Unexpected hub file -- expected 'genome' stanza but found " + stanzas[1].type)
|
|
30751
31317
|
}
|
|
30752
|
-
|
|
30753
|
-
|
|
31318
|
+
const genomeStanza = stanzas[1];
|
|
31319
|
+
genomeStanzas = [genomeStanza];
|
|
31320
|
+
trackStanzas = stanzas.slice(2);
|
|
30754
31321
|
|
|
30755
|
-
|
|
30756
|
-
|
|
30757
|
-
|
|
30758
|
-
|
|
30759
|
-
|
|
30760
|
-
|
|
30761
|
-
|
|
30762
|
-
|
|
30763
|
-
|
|
30764
|
-
|
|
30765
|
-
|
|
30766
|
-
return "COLLAPSED"
|
|
30767
|
-
case "pack":
|
|
30768
|
-
return "EXPANDED"
|
|
30769
|
-
case "squish":
|
|
30770
|
-
return "SQUISHED"
|
|
30771
|
-
default:
|
|
30772
|
-
return "COLLAPSED"
|
|
31322
|
+
// If this is an assembly check chromSizes. This file can be very large, and not needed if whole genome view
|
|
31323
|
+
// is not enabled. Remove it if > 100 kb
|
|
31324
|
+
if (genomeStanza.hasOwnProperty("chromSizes")) {
|
|
31325
|
+
const chromSizes = genomeStanza.getProperty("chromSizes");
|
|
31326
|
+
try {
|
|
31327
|
+
const contentLength = await igvxhr.getContentLength(chromSizes);
|
|
31328
|
+
if (contentLength > 100000) {
|
|
31329
|
+
genomeStanza.removeProperty("chromSizes");
|
|
31330
|
+
}
|
|
31331
|
+
} catch (e) {
|
|
31332
|
+
console.error(`Error getting content length for chromSizes ${chromSizes}`, e);
|
|
30773
31333
|
}
|
|
30774
|
-
}
|
|
30775
|
-
}
|
|
30776
|
-
}
|
|
30777
31334
|
|
|
31335
|
+
}
|
|
30778
31336
|
|
|
30779
|
-
|
|
30780
|
-
|
|
30781
|
-
|
|
30782
|
-
* @returns {Promise<number|string>}
|
|
30783
|
-
*/
|
|
30784
|
-
async function getContentLength(url) {
|
|
30785
|
-
try {
|
|
30786
|
-
const response = await fetch(url, {method: 'HEAD'});
|
|
30787
|
-
const headers = response.headers;
|
|
30788
|
-
if (headers.has("content-length")) {
|
|
30789
|
-
return headers.get("content-length")
|
|
30790
|
-
} else {
|
|
30791
|
-
return null
|
|
31337
|
+
} else {
|
|
31338
|
+
if (!hubStanza.hasProperty("genomesFile")) {
|
|
31339
|
+
throw new Error("hub.txt must specify 'genomesFile'")
|
|
30792
31340
|
}
|
|
30793
|
-
|
|
30794
|
-
return null
|
|
31341
|
+
genomeStanzas = await loadStanzas(hubStanza.getProperty("genomesFile"));
|
|
30795
31342
|
}
|
|
31343
|
+
|
|
31344
|
+
// Load group files for all genomes, if any.
|
|
31345
|
+
const uniqGroupURLs = new Set();
|
|
31346
|
+
genomeStanzas.forEach(s => {
|
|
31347
|
+
const groupURL = s.getProperty("groups");
|
|
31348
|
+
if (groupURL) uniqGroupURLs.add(groupURL);
|
|
31349
|
+
|
|
31350
|
+
});
|
|
31351
|
+
const groupStanzas = [];
|
|
31352
|
+
const groupPromises = Array.from(uniqGroupURLs).map(async url => {
|
|
31353
|
+
const stanza = await loadStanzas(url);
|
|
31354
|
+
return stanza
|
|
31355
|
+
});
|
|
31356
|
+
const groupResults = await Promise.all(groupPromises);
|
|
31357
|
+
groupResults.forEach(stanza => groupStanzas.push(...stanza));
|
|
31358
|
+
|
|
31359
|
+
const hub = new Hub(url, hubStanza, genomeStanzas, trackStanzas, groupStanzas);
|
|
31360
|
+
|
|
31361
|
+
hubCache.set(url, hub);
|
|
31362
|
+
|
|
31363
|
+
return hub
|
|
30796
31364
|
}
|
|
30797
31365
|
|
|
31366
|
+
|
|
30798
31367
|
/**
|
|
30799
31368
|
* Parse a UCSC file
|
|
30800
31369
|
* @param url
|
|
@@ -30802,38 +31371,92 @@
|
|
|
30802
31371
|
*/
|
|
30803
31372
|
async function loadStanzas(url) {
|
|
30804
31373
|
|
|
30805
|
-
const
|
|
30806
|
-
const
|
|
31374
|
+
const idx = url.lastIndexOf("/");
|
|
31375
|
+
const baseURL = url.substring(0, idx + 1);
|
|
31376
|
+
const host = getHost(url);
|
|
31377
|
+
|
|
31378
|
+
//const response = await fetch(url)
|
|
31379
|
+
const data = await igvxhr.loadString(url, {}); //await response.text()
|
|
30807
31380
|
const lines = data.split(/\n|\r\n|\r/g);
|
|
30808
31381
|
|
|
30809
31382
|
const nodes = [];
|
|
30810
31383
|
let currentNode;
|
|
30811
31384
|
let startNewNode = true;
|
|
30812
|
-
for (let
|
|
30813
|
-
|
|
30814
|
-
|
|
30815
|
-
|
|
31385
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31386
|
+
|
|
31387
|
+
let line = lines[i].trim();
|
|
31388
|
+
|
|
31389
|
+
if (line.length == 0) {
|
|
30816
31390
|
// Break - start a new node
|
|
30817
31391
|
startNewNode = true;
|
|
30818
31392
|
} else {
|
|
30819
|
-
|
|
30820
|
-
|
|
30821
|
-
|
|
31393
|
+
if (line.startsWith("#")) {
|
|
31394
|
+
continue
|
|
31395
|
+
}
|
|
31396
|
+
|
|
31397
|
+
while (line.endsWith('\\')) {
|
|
31398
|
+
i++;
|
|
31399
|
+
if (i >= lines.length) {
|
|
31400
|
+
break
|
|
31401
|
+
}
|
|
31402
|
+
line = line.substring(0, line.length - 1) + lines[i].trim();
|
|
31403
|
+
}
|
|
31404
|
+
|
|
31405
|
+
if (line.startsWith("include")) {
|
|
31406
|
+
const relativeURL = line.substring(8).trim();
|
|
31407
|
+
const includeURL = getDataURL(relativeURL, host, baseURL);
|
|
31408
|
+
const includeStanzas = await loadStanzas(includeURL);
|
|
31409
|
+
for (let s of includeStanzas) {
|
|
31410
|
+
nodes.push(s);
|
|
31411
|
+
}
|
|
31412
|
+
}
|
|
31413
|
+
|
|
31414
|
+
|
|
31415
|
+
const i = line.indexOf(' ');
|
|
31416
|
+
const key = line.substring(0, i).trim();
|
|
31417
|
+
let value = line.substring(i + 1).trim();
|
|
31418
|
+
|
|
31419
|
+
if (key === "type") {
|
|
31420
|
+
// The "type" property contains format and sometimes other information. For example, data range
|
|
31421
|
+
// on a bigwig "type bigWig 0 .5"
|
|
31422
|
+
const tokens = value.split(/\s+/);
|
|
31423
|
+
value = tokens[0];
|
|
31424
|
+
if (value === "bigWig" && tokens.length === 3) {
|
|
31425
|
+
// This is a bigWig with a range
|
|
31426
|
+
const min = tokens[1];
|
|
31427
|
+
const max = tokens[2];
|
|
31428
|
+
if (currentNode) {
|
|
31429
|
+
currentNode.setProperty("min", min);
|
|
31430
|
+
currentNode.setProperty("max", max);
|
|
31431
|
+
}
|
|
31432
|
+
}
|
|
31433
|
+
|
|
31434
|
+
} else if (!["shortLabel", "longLabel", "metadata", "label"].includes(key)) {
|
|
31435
|
+
const tokens = value.split(/\s+/);
|
|
31436
|
+
value = tokens[0];
|
|
31437
|
+
}
|
|
31438
|
+
|
|
31439
|
+
if (urlProperties.has(key) || value.endsWith("URL") || value.endsWith("Url")) {
|
|
31440
|
+
value = getDataURL(value, host, baseURL);
|
|
31441
|
+
}
|
|
31442
|
+
|
|
30822
31443
|
if (startNewNode) {
|
|
30823
|
-
|
|
30824
|
-
|
|
30825
|
-
const newNode = new Stanza(key, value);
|
|
30826
|
-
nodes.push(newNode);
|
|
30827
|
-
currentNode = newNode;
|
|
31444
|
+
currentNode = new Stanza(key, value);
|
|
31445
|
+
nodes.push(currentNode);
|
|
30828
31446
|
startNewNode = false;
|
|
30829
31447
|
}
|
|
31448
|
+
|
|
30830
31449
|
currentNode.setProperty(key, value);
|
|
30831
31450
|
}
|
|
30832
31451
|
}
|
|
30833
|
-
|
|
30834
31452
|
return resolveParents(nodes)
|
|
30835
31453
|
}
|
|
30836
31454
|
|
|
31455
|
+
function firstWord(str) {
|
|
31456
|
+
const idx = str.indexOf(' ');
|
|
31457
|
+
return idx > 0 ? str.substring(0, idx) : str
|
|
31458
|
+
}
|
|
31459
|
+
|
|
30837
31460
|
function resolveParents(nodes) {
|
|
30838
31461
|
const nodeMap = new Map();
|
|
30839
31462
|
for (let n of nodes) {
|
|
@@ -30848,17 +31471,34 @@
|
|
|
30848
31471
|
return nodes
|
|
30849
31472
|
}
|
|
30850
31473
|
|
|
30851
|
-
function
|
|
30852
|
-
|
|
30853
|
-
|
|
30854
|
-
|
|
30855
|
-
|
|
31474
|
+
function getDataURL(url, host, baseURL) {
|
|
31475
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("gs://") || url.startsWith("s3://")) {
|
|
31476
|
+
return url
|
|
31477
|
+
} else if (url.startsWith("/")) {
|
|
31478
|
+
return host + url
|
|
31479
|
+
} else {
|
|
31480
|
+
return baseURL + url
|
|
30856
31481
|
}
|
|
30857
|
-
|
|
31482
|
+
}
|
|
31483
|
+
|
|
31484
|
+
function getHost(url) {
|
|
31485
|
+
let host;
|
|
31486
|
+
if (url.startsWith("https://") || url.startsWith("http://")) {
|
|
31487
|
+
try {
|
|
31488
|
+
const tmp = new URL(url);
|
|
31489
|
+
host = `${tmp.protocol}//${tmp.host}`;
|
|
31490
|
+
} catch (e) {
|
|
31491
|
+
console.error("Error parsing base URL host", e);
|
|
31492
|
+
throw e
|
|
31493
|
+
}
|
|
31494
|
+
} else {
|
|
31495
|
+
host = '';
|
|
31496
|
+
}
|
|
31497
|
+
return host
|
|
30858
31498
|
}
|
|
30859
31499
|
|
|
30860
31500
|
const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
|
|
30861
|
-
const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv
|
|
31501
|
+
const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-genomes/refs/heads/main/dist/genomes.json";
|
|
30862
31502
|
|
|
30863
31503
|
const GenomeUtils = {
|
|
30864
31504
|
|
|
@@ -30866,7 +31506,7 @@
|
|
|
30866
31506
|
|
|
30867
31507
|
if (!GenomeUtils.KNOWN_GENOMES) {
|
|
30868
31508
|
|
|
30869
|
-
|
|
31509
|
+
let table = {};
|
|
30870
31510
|
|
|
30871
31511
|
const processJson = (jsonArray, table) => {
|
|
30872
31512
|
jsonArray.forEach(function (json) {
|
|
@@ -30879,12 +31519,12 @@
|
|
|
30879
31519
|
if (config.loadDefaultGenomes !== false) {
|
|
30880
31520
|
try {
|
|
30881
31521
|
const jsonArray = await igvxhr.loadJson(DEFAULT_GENOMES_URL, {timeout: 2000});
|
|
30882
|
-
processJson(jsonArray,
|
|
31522
|
+
processJson(jsonArray, table);
|
|
30883
31523
|
} catch (error) {
|
|
30884
31524
|
try {
|
|
30885
31525
|
console.error("Error initializing default genomes:", error);
|
|
30886
31526
|
const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 2000});
|
|
30887
|
-
processJson(jsonArray,
|
|
31527
|
+
processJson(jsonArray, table);
|
|
30888
31528
|
} catch (e) {
|
|
30889
31529
|
console.error("Error initializing backup genomes:", error);
|
|
30890
31530
|
}
|
|
@@ -30896,11 +31536,12 @@
|
|
|
30896
31536
|
if (genomeList) {
|
|
30897
31537
|
if (typeof genomeList === 'string') {
|
|
30898
31538
|
const jsonArray = await igvxhr.loadJson(genomeList, {});
|
|
30899
|
-
processJson(jsonArray,
|
|
31539
|
+
processJson(jsonArray, table);
|
|
30900
31540
|
} else {
|
|
30901
|
-
processJson(genomeList,
|
|
31541
|
+
processJson(genomeList, table);
|
|
30902
31542
|
}
|
|
30903
31543
|
}
|
|
31544
|
+
GenomeUtils.KNOWN_GENOMES = table;
|
|
30904
31545
|
}
|
|
30905
31546
|
},
|
|
30906
31547
|
|
|
@@ -30937,8 +31578,8 @@
|
|
|
30937
31578
|
if ((genomeID.startsWith("GCA_") || genomeID.startsWith("GCF_")) && genomeID.length >= 13) {
|
|
30938
31579
|
try {
|
|
30939
31580
|
const hubURL = convertToHubURL(genomeID);
|
|
30940
|
-
const hub = await
|
|
30941
|
-
reference = hub.getGenomeConfig();
|
|
31581
|
+
const hub = await loadHub(hubURL);
|
|
31582
|
+
reference = hub.getGenomeConfig(genomeID);
|
|
30942
31583
|
} catch (e) {
|
|
30943
31584
|
console.error(e);
|
|
30944
31585
|
}
|
|
@@ -32900,27 +33541,18 @@
|
|
|
32900
33541
|
return [ r, g, b ]
|
|
32901
33542
|
});
|
|
32902
33543
|
|
|
32903
|
-
const colorForNA = appleCrayonRGB('magnesium');
|
|
32904
|
-
const sampleInfoFileHeaders = ['#sampleTable', '#sampleMapping', '#colors'];
|
|
32905
|
-
|
|
32906
33544
|
class SampleInfo {
|
|
32907
33545
|
|
|
32908
33546
|
static emptySpaceReplacement = '|'
|
|
32909
|
-
|
|
32910
|
-
|
|
32911
|
-
attributeNames = []
|
|
32912
|
-
sampleMappingDictionary = {}
|
|
32913
|
-
colorDictionary = {}
|
|
32914
|
-
attributeRangeLUT = {}
|
|
33547
|
+
static colorForNA = appleCrayonRGB('magnesium')
|
|
33548
|
+
static sampleInfoFileHeaders = ['#sampleTable', '#sampleMapping', '#colors']
|
|
32915
33549
|
|
|
32916
33550
|
constructor(browser) {
|
|
32917
|
-
|
|
32918
33551
|
const found = browser.tracks.some(t => typeof t.getSamples === 'function');
|
|
32919
33552
|
if (found.length > 0) {
|
|
32920
33553
|
browser.sampleInfoControl.setButtonVisibility(true);
|
|
32921
33554
|
}
|
|
32922
33555
|
this.initialize();
|
|
32923
|
-
|
|
32924
33556
|
}
|
|
32925
33557
|
|
|
32926
33558
|
initialize() {
|
|
@@ -32947,47 +33579,60 @@
|
|
|
32947
33579
|
|
|
32948
33580
|
getAttributes(sampleName) {
|
|
32949
33581
|
|
|
32950
|
-
const key =
|
|
33582
|
+
const key = this.sampleMappingDictionary[sampleName] || sampleName;
|
|
32951
33583
|
return this.sampleDictionary[key]
|
|
32952
33584
|
}
|
|
32953
33585
|
|
|
32954
|
-
async
|
|
32955
|
-
|
|
32956
|
-
|
|
32957
|
-
this
|
|
32958
|
-
|
|
32959
|
-
|
|
32960
|
-
|
|
33586
|
+
async loadSampleInfo(config) {
|
|
33587
|
+
|
|
33588
|
+
if (config.url) {
|
|
33589
|
+
await this.loadSampleInfoFile(config.url);
|
|
33590
|
+
} else {
|
|
33591
|
+
|
|
33592
|
+
const samples = { ...config };
|
|
33593
|
+
for (const [key, record] of Object.entries(samples)) {
|
|
33594
|
+
samples[key] = SampleInfo.toNumericalRepresentation(record);
|
|
33595
|
+
}
|
|
33596
|
+
|
|
33597
|
+
const [ value ] = Object.values(samples);
|
|
33598
|
+
const attributes = Object.keys(value);
|
|
33599
|
+
|
|
33600
|
+
this.loadSampleInfoHelper(attributes, samples);
|
|
33601
|
+
|
|
32961
33602
|
}
|
|
32962
|
-
}
|
|
32963
33603
|
|
|
32964
|
-
|
|
33604
|
+
this.initialized = true;
|
|
33605
|
+
}
|
|
32965
33606
|
|
|
32966
|
-
|
|
33607
|
+
loadSampleInfoHelper(attributes, samples){
|
|
32967
33608
|
|
|
32968
|
-
|
|
32969
|
-
|
|
32970
|
-
|
|
32971
|
-
this.#accumulateSampleTableDictionary(value);
|
|
32972
|
-
break
|
|
32973
|
-
case '#sampleMapping':
|
|
32974
|
-
this.#accumulateSampleMappingDictionary(value);
|
|
32975
|
-
break
|
|
32976
|
-
case '#colors':
|
|
32977
|
-
this.#accumulateColorScheme(value);
|
|
32978
|
-
break
|
|
33609
|
+
// Establish the range of values for each attribute
|
|
33610
|
+
const lut = createAttributeRangeLUT(attributes, samples);
|
|
33611
|
+
accumulateDictionary(this.attributeRangeLUT, lut);
|
|
32979
33612
|
|
|
33613
|
+
// Ensure unique attribute names list
|
|
33614
|
+
const currentAttributeNameSet = new Set(this.attributeNames);
|
|
33615
|
+
for (const name of attributes) {
|
|
33616
|
+
if (!currentAttributeNameSet.has(name)) {
|
|
33617
|
+
this.attributeNames.push(name);
|
|
32980
33618
|
}
|
|
32981
33619
|
}
|
|
32982
33620
|
|
|
32983
|
-
this.
|
|
33621
|
+
accumulateDictionary(this.sampleDictionary, samples);
|
|
33622
|
+
|
|
33623
|
+
}
|
|
32984
33624
|
|
|
33625
|
+
async loadSampleInfoFile(path) {
|
|
33626
|
+
try {
|
|
33627
|
+
const string = await igvxhr.loadString(path);
|
|
33628
|
+
this.#processSampleInfoFileAsString(string);
|
|
33629
|
+
this.sampleInfoFiles.push(path);
|
|
33630
|
+
} catch (e) {
|
|
33631
|
+
console.error(e.message);
|
|
33632
|
+
}
|
|
32985
33633
|
}
|
|
32986
33634
|
|
|
32987
33635
|
getAttributeColor(attribute, value) {
|
|
32988
|
-
// if (value === 'NA') {
|
|
32989
|
-
// console.log(`${ attribute } : ${ value }`)
|
|
32990
|
-
// }
|
|
32991
33636
|
|
|
32992
33637
|
let color;
|
|
32993
33638
|
|
|
@@ -33005,7 +33650,7 @@
|
|
|
33005
33650
|
|
|
33006
33651
|
} else if (typeof value === "string") {
|
|
33007
33652
|
|
|
33008
|
-
color = 'NA' === value ? colorForNA : stringToRGBString(value);
|
|
33653
|
+
color = 'NA' === value ? SampleInfo.colorForNA : SampleInfo.stringToRGBString(value);
|
|
33009
33654
|
|
|
33010
33655
|
} else {
|
|
33011
33656
|
|
|
@@ -33073,6 +33718,27 @@
|
|
|
33073
33718
|
return json
|
|
33074
33719
|
}
|
|
33075
33720
|
|
|
33721
|
+
#processSampleInfoFileAsString(string) {
|
|
33722
|
+
|
|
33723
|
+
const sectionDictionary = createSectionDictionary(string);
|
|
33724
|
+
|
|
33725
|
+
for (const [header, value] of Object.entries(sectionDictionary)) {
|
|
33726
|
+
switch (header) {
|
|
33727
|
+
case '#sampleTable':
|
|
33728
|
+
this.#accumulateSampleTableDictionary(value);
|
|
33729
|
+
break
|
|
33730
|
+
case '#sampleMapping':
|
|
33731
|
+
this.#accumulateSampleMappingDictionary(value);
|
|
33732
|
+
break
|
|
33733
|
+
case '#colors':
|
|
33734
|
+
this.#accumulateColorScheme(value);
|
|
33735
|
+
break
|
|
33736
|
+
|
|
33737
|
+
}
|
|
33738
|
+
}
|
|
33739
|
+
|
|
33740
|
+
}
|
|
33741
|
+
|
|
33076
33742
|
#accumulateSampleTableDictionary(lines) {
|
|
33077
33743
|
|
|
33078
33744
|
// shift array with first item that is 'sample' or 'Linking_id'. Remaining items are attribute names
|
|
@@ -33112,22 +33778,11 @@
|
|
|
33112
33778
|
} // for (lines)
|
|
33113
33779
|
|
|
33114
33780
|
for (const [key, record] of Object.entries(samples)) {
|
|
33115
|
-
samples[key] = toNumericalRepresentation(record);
|
|
33781
|
+
samples[key] = SampleInfo.toNumericalRepresentation(record);
|
|
33116
33782
|
}
|
|
33117
33783
|
|
|
33118
|
-
|
|
33119
|
-
const lut = createAttributeRangeLUT(attributes, samples);
|
|
33120
|
-
accumulateDictionary(this.attributeRangeLUT, lut);
|
|
33121
|
-
|
|
33122
|
-
// Ensure unique attribute names list
|
|
33123
|
-
const currentAttributeNameSet = new Set(this.attributeNames);
|
|
33124
|
-
for (const name of attributes) {
|
|
33125
|
-
if (!currentAttributeNameSet.has(name)) {
|
|
33126
|
-
this.attributeNames.push(name);
|
|
33127
|
-
}
|
|
33128
|
-
}
|
|
33784
|
+
this.loadSampleInfoHelper(attributes, samples);
|
|
33129
33785
|
|
|
33130
|
-
accumulateDictionary(this.sampleDictionary, samples);
|
|
33131
33786
|
}
|
|
33132
33787
|
|
|
33133
33788
|
#accumulateSampleMappingDictionary(lines) {
|
|
@@ -33228,7 +33883,7 @@
|
|
|
33228
33883
|
this.colorDictionary[attribute] = attributeValue => {
|
|
33229
33884
|
|
|
33230
33885
|
if ('NA' === attributeValue) {
|
|
33231
|
-
return colorForNA
|
|
33886
|
+
return SampleInfo.colorForNA
|
|
33232
33887
|
} else {
|
|
33233
33888
|
const [min, max] = this.attributeRangeLUT[attribute];
|
|
33234
33889
|
const interpolant = (attributeValue - min) / (max - min);
|
|
@@ -33248,8 +33903,34 @@
|
|
|
33248
33903
|
|
|
33249
33904
|
}
|
|
33250
33905
|
|
|
33251
|
-
|
|
33906
|
+
static toNumericalRepresentation(obj) {
|
|
33907
|
+
const result = Object.assign({}, obj);
|
|
33908
|
+
|
|
33909
|
+
for (const [key, value] of Object.entries(result)) {
|
|
33910
|
+
if (typeof value === 'string' && !isNaN(value)) {
|
|
33911
|
+
result[key] = Number(value);
|
|
33912
|
+
}
|
|
33913
|
+
}
|
|
33914
|
+
|
|
33915
|
+
return result
|
|
33916
|
+
}
|
|
33917
|
+
|
|
33918
|
+
static stringToRGBString(str) {
|
|
33919
|
+
let hash = 0;
|
|
33920
|
+
for (let i = 0; i < str.length; i++) {
|
|
33921
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
33922
|
+
}
|
|
33252
33923
|
|
|
33924
|
+
let color = [];
|
|
33925
|
+
for (let i = 0; i < 3; i++) {
|
|
33926
|
+
const value = (hash >> (i * 8)) & 0xff;
|
|
33927
|
+
color.push(value);
|
|
33928
|
+
}
|
|
33929
|
+
|
|
33930
|
+
return `rgb(${color.join(', ')})`
|
|
33931
|
+
}
|
|
33932
|
+
|
|
33933
|
+
}
|
|
33253
33934
|
|
|
33254
33935
|
function createSectionDictionary(string) {
|
|
33255
33936
|
|
|
@@ -33260,14 +33941,14 @@
|
|
|
33260
33941
|
let currentHeader;
|
|
33261
33942
|
|
|
33262
33943
|
// If the first line does not start with a section header an initial #sampleTable is implied
|
|
33263
|
-
if (!sampleInfoFileHeaders.includes(lines[0])) {
|
|
33944
|
+
if (!SampleInfo.sampleInfoFileHeaders.includes(lines[0])) {
|
|
33264
33945
|
currentHeader = '#sampleTable';
|
|
33265
33946
|
dictionary[currentHeader] = [];
|
|
33266
33947
|
}
|
|
33267
33948
|
|
|
33268
33949
|
for (const line of lines) {
|
|
33269
33950
|
|
|
33270
|
-
if (sampleInfoFileHeaders.includes(line)) {
|
|
33951
|
+
if (SampleInfo.sampleInfoFileHeaders.includes(line)) {
|
|
33271
33952
|
currentHeader = line;
|
|
33272
33953
|
dictionary[currentHeader] = [];
|
|
33273
33954
|
} else if (currentHeader && false === line.startsWith('#')) {
|
|
@@ -33286,7 +33967,6 @@
|
|
|
33286
33967
|
}
|
|
33287
33968
|
}
|
|
33288
33969
|
|
|
33289
|
-
|
|
33290
33970
|
function createAttributeRangeLUT(names, dictionary) {
|
|
33291
33971
|
|
|
33292
33972
|
const lut = {};
|
|
@@ -33332,35 +34012,6 @@
|
|
|
33332
34012
|
return lut
|
|
33333
34013
|
}
|
|
33334
34014
|
|
|
33335
|
-
function toNumericalRepresentation(obj) {
|
|
33336
|
-
|
|
33337
|
-
const result = Object.assign({}, obj);
|
|
33338
|
-
|
|
33339
|
-
for (const [key, value] of Object.entries(result)) {
|
|
33340
|
-
if (typeof value === 'string' && !isNaN(value)) {
|
|
33341
|
-
result[key] = Number(value);
|
|
33342
|
-
}
|
|
33343
|
-
}
|
|
33344
|
-
|
|
33345
|
-
return result
|
|
33346
|
-
}
|
|
33347
|
-
|
|
33348
|
-
function stringToRGBString(str) {
|
|
33349
|
-
|
|
33350
|
-
let hash = 0;
|
|
33351
|
-
for (let i = 0; i < str.length; i++) {
|
|
33352
|
-
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
33353
|
-
}
|
|
33354
|
-
|
|
33355
|
-
let color = [];
|
|
33356
|
-
for (let i = 0; i < 3; i++) {
|
|
33357
|
-
const value = (hash >> (i * 8)) & 0xff;
|
|
33358
|
-
color.push(value);
|
|
33359
|
-
}
|
|
33360
|
-
|
|
33361
|
-
return `rgb(${color.join(', ')})`
|
|
33362
|
-
}
|
|
33363
|
-
|
|
33364
34015
|
const sampleInfoTileXShim = 8;
|
|
33365
34016
|
const sampleInfoTileWidth = 16;
|
|
33366
34017
|
|
|
@@ -33969,7 +34620,11 @@
|
|
|
33969
34620
|
|
|
33970
34621
|
checkCanvas() {
|
|
33971
34622
|
|
|
33972
|
-
|
|
34623
|
+
let width = 0;
|
|
34624
|
+
if (true === this.browser.showSampleNames) {
|
|
34625
|
+
width = undefined === this.browser.sampleNameViewportWidth ? 0 : this.browser.sampleNameViewportWidth;
|
|
34626
|
+
}
|
|
34627
|
+
|
|
33973
34628
|
this.ctx.canvas.width = width * window.devicePixelRatio;
|
|
33974
34629
|
this.ctx.canvas.style.width = `${width}px`;
|
|
33975
34630
|
|
|
@@ -35081,7 +35736,7 @@
|
|
|
35081
35736
|
let element = document.createElement('div');
|
|
35082
35737
|
element.textContent = 'Separate tracks';
|
|
35083
35738
|
|
|
35084
|
-
function click(e) {
|
|
35739
|
+
async function click(e) {
|
|
35085
35740
|
|
|
35086
35741
|
// Capture state which will be nulled when track is removed
|
|
35087
35742
|
const groupAutoscale = this.autoscale;
|
|
@@ -35097,9 +35752,10 @@
|
|
|
35097
35752
|
track.autoscaleGroup = name;
|
|
35098
35753
|
}
|
|
35099
35754
|
track.isMergedTrack = false;
|
|
35100
|
-
browser.addTrack(track
|
|
35755
|
+
browser.addTrack(track);
|
|
35101
35756
|
}
|
|
35102
|
-
browser.updateViews();
|
|
35757
|
+
await browser.updateViews();
|
|
35758
|
+
browser.reorderTracks();
|
|
35103
35759
|
}
|
|
35104
35760
|
|
|
35105
35761
|
return {element, click}
|
|
@@ -35194,7 +35850,7 @@
|
|
|
35194
35850
|
}
|
|
35195
35851
|
}
|
|
35196
35852
|
|
|
35197
|
-
function trackOverlayClickHandler(e) {
|
|
35853
|
+
async function trackOverlayClickHandler(e) {
|
|
35198
35854
|
|
|
35199
35855
|
if (true === isOverlayTrackCriteriaMet(this.browser)) {
|
|
35200
35856
|
|
|
@@ -35231,9 +35887,9 @@
|
|
|
35231
35887
|
track.trackView.dispose();
|
|
35232
35888
|
}
|
|
35233
35889
|
|
|
35234
|
-
this.browser.addTrack(
|
|
35235
|
-
mergedTrack.trackView.updateViews();
|
|
35236
|
-
|
|
35890
|
+
this.browser.addTrack(mergedTrack);
|
|
35891
|
+
await mergedTrack.trackView.updateViews();
|
|
35892
|
+
this.browser.reorderTracks();
|
|
35237
35893
|
}
|
|
35238
35894
|
|
|
35239
35895
|
}
|
|
@@ -36840,14 +37496,45 @@
|
|
|
36840
37496
|
|
|
36841
37497
|
|
|
36842
37498
|
present(options, e) {
|
|
36843
|
-
|
|
36844
37499
|
this.label.textContent = options.label;
|
|
36845
37500
|
this._input.value = options.value;
|
|
36846
37501
|
this.callback = options.callback || options.click;
|
|
36847
37502
|
|
|
36848
|
-
|
|
36849
|
-
|
|
36850
|
-
|
|
37503
|
+
this.container.style.display = '';
|
|
37504
|
+
|
|
37505
|
+
// Get click coordinates
|
|
37506
|
+
const clickX = e.clientX;
|
|
37507
|
+
const clickY = e.clientY;
|
|
37508
|
+
|
|
37509
|
+
// Get dialog dimensions
|
|
37510
|
+
const dialogWidth = this.container.offsetWidth;
|
|
37511
|
+
const dialogHeight = this.container.offsetHeight;
|
|
37512
|
+
|
|
37513
|
+
// Calculate available space
|
|
37514
|
+
const windowWidth = window.innerWidth;
|
|
37515
|
+
const windowHeight = window.innerHeight;
|
|
37516
|
+
|
|
37517
|
+
// Calculate position to keep dialog on screen
|
|
37518
|
+
let left = clickX;
|
|
37519
|
+
let top = clickY;
|
|
37520
|
+
|
|
37521
|
+
// Adjust horizontal position if dialog would go off screen
|
|
37522
|
+
if (left + dialogWidth > windowWidth) {
|
|
37523
|
+
left = windowWidth - dialogWidth - 10; // 10px padding from edge
|
|
37524
|
+
}
|
|
37525
|
+
|
|
37526
|
+
// Adjust vertical position if dialog would go off screen
|
|
37527
|
+
if (top + dialogHeight > windowHeight) {
|
|
37528
|
+
top = windowHeight - dialogHeight - 10; // 10px padding from edge
|
|
37529
|
+
}
|
|
37530
|
+
|
|
37531
|
+
// Ensure minimum distance from edges
|
|
37532
|
+
left = Math.max(10, left);
|
|
37533
|
+
top = Math.max(10, top);
|
|
37534
|
+
|
|
37535
|
+
// Apply positions
|
|
37536
|
+
this.container.style.left = `${left}px`;
|
|
37537
|
+
this.container.style.top = `${top}px`;
|
|
36851
37538
|
}
|
|
36852
37539
|
}
|
|
36853
37540
|
|
|
@@ -47627,7 +48314,7 @@
|
|
|
47627
48314
|
if (!this.colorBy) {
|
|
47628
48315
|
this.colorBy = this.hasPairs ? "unexpectedPair" : "none";
|
|
47629
48316
|
}
|
|
47630
|
-
|
|
48317
|
+
|
|
47631
48318
|
let pixelTop = options.pixelTop - BAMTrack.coverageTrackHeight;
|
|
47632
48319
|
if (this.top) {
|
|
47633
48320
|
ctx.translate(0, this.top);
|
|
@@ -48334,7 +49021,12 @@
|
|
|
48334
49021
|
this.trackView.repaintViews();
|
|
48335
49022
|
}
|
|
48336
49023
|
|
|
48337
|
-
return {
|
|
49024
|
+
return {
|
|
49025
|
+
name: undefined,
|
|
49026
|
+
element: createCheckbox(menuItem.label, showCheck),
|
|
49027
|
+
click: clickHandler,
|
|
49028
|
+
init: undefined
|
|
49029
|
+
}
|
|
48338
49030
|
}
|
|
48339
49031
|
|
|
48340
49032
|
|
|
@@ -48381,7 +49073,12 @@
|
|
|
48381
49073
|
}
|
|
48382
49074
|
}
|
|
48383
49075
|
|
|
48384
|
-
return {
|
|
49076
|
+
return {
|
|
49077
|
+
name: undefined,
|
|
49078
|
+
element: createCheckbox(menuItem.label, showCheck),
|
|
49079
|
+
dialog: clickHandler,
|
|
49080
|
+
init: undefined
|
|
49081
|
+
}
|
|
48385
49082
|
|
|
48386
49083
|
}
|
|
48387
49084
|
|
|
@@ -48503,38 +49200,88 @@
|
|
|
48503
49200
|
});
|
|
48504
49201
|
}
|
|
48505
49202
|
|
|
49203
|
+
list.push('<hr/>');
|
|
49204
|
+
const softClips = clickedAlignment.softClippedBlocks();
|
|
48506
49205
|
list.push({
|
|
48507
49206
|
label: 'View read sequence',
|
|
48508
49207
|
click: () => {
|
|
48509
|
-
const seqstring = clickedAlignment.seq;
|
|
48510
|
-
|
|
48511
|
-
this.browser.alert.present("Read sequence: *");
|
|
48512
|
-
} else {
|
|
48513
|
-
this.browser.alert.present(seqstring);
|
|
48514
|
-
}
|
|
49208
|
+
const seqstring = clickedAlignment.seq;
|
|
49209
|
+
this.browser.alert.present(seqstring && seqstring !== "*" ? seqstring : "Read sequence: *");
|
|
48515
49210
|
}
|
|
48516
49211
|
});
|
|
48517
49212
|
|
|
49213
|
+
if (softClips.left && softClips.left.len > 0) {
|
|
49214
|
+
list.push({
|
|
49215
|
+
label: 'View left soft-clipped sequence',
|
|
49216
|
+
click: () => {
|
|
49217
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
|
|
49218
|
+
this.browser.alert.present(clippedSequence);
|
|
49219
|
+
}
|
|
49220
|
+
});
|
|
49221
|
+
}
|
|
49222
|
+
|
|
49223
|
+
if (softClips.right && softClips.right.len > 0) {
|
|
49224
|
+
list.push({
|
|
49225
|
+
label: 'View right soft-clipped sequence',
|
|
49226
|
+
click: () => {
|
|
49227
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
|
|
49228
|
+
this.browser.alert.present(clippedSequence);
|
|
49229
|
+
}
|
|
49230
|
+
});
|
|
49231
|
+
}
|
|
49232
|
+
|
|
49233
|
+
list.push('<hr/>');
|
|
49234
|
+
|
|
48518
49235
|
if (isSecureContext()) {
|
|
48519
49236
|
list.push({
|
|
48520
49237
|
label: 'Copy read sequence',
|
|
48521
49238
|
click: async () => {
|
|
48522
|
-
const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
|
|
48523
49239
|
try {
|
|
48524
|
-
await navigator.clipboard.writeText(seq);
|
|
49240
|
+
await navigator.clipboard.writeText(clickedAlignment.seq);
|
|
48525
49241
|
} catch (e) {
|
|
48526
49242
|
console.error(e);
|
|
48527
49243
|
this.browser.alert.present(`error copying sequence to clipboard ${e}`);
|
|
48528
49244
|
}
|
|
48529
|
-
|
|
48530
49245
|
}
|
|
48531
49246
|
});
|
|
49247
|
+
|
|
49248
|
+
if (softClips.left && softClips.left.len > 0) {
|
|
49249
|
+
list.push({
|
|
49250
|
+
label: 'Copy left soft-clipped sequence',
|
|
49251
|
+
click: async () => {
|
|
49252
|
+
try {
|
|
49253
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
|
|
49254
|
+
await navigator.clipboard.writeText(clippedSequence);
|
|
49255
|
+
} catch (e) {
|
|
49256
|
+
console.error(e);
|
|
49257
|
+
this.browser.alert.present(`error copying sequence to clipboard ${e}`);
|
|
49258
|
+
}
|
|
49259
|
+
}
|
|
49260
|
+
});
|
|
49261
|
+
}
|
|
49262
|
+
|
|
49263
|
+
if (softClips.right && softClips.right.len > 0) {
|
|
49264
|
+
list.push({
|
|
49265
|
+
label: 'Copy right soft-clipped sequence',
|
|
49266
|
+
click: async () => {
|
|
49267
|
+
try {
|
|
49268
|
+
const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
|
|
49269
|
+
await navigator.clipboard.writeText(clippedSequence);
|
|
49270
|
+
} catch (e) {
|
|
49271
|
+
console.error(e);
|
|
49272
|
+
this.browser.alert.present(`error copying sequence to clipboard ${e}`);
|
|
49273
|
+
}
|
|
49274
|
+
}
|
|
49275
|
+
});
|
|
49276
|
+
}
|
|
48532
49277
|
}
|
|
48533
49278
|
|
|
48534
|
-
// TODO if genome supports blat
|
|
49279
|
+
// TODO test if genome supports blat
|
|
48535
49280
|
const seqstring = clickedAlignment.seq;
|
|
48536
49281
|
if (seqstring && "*" !== seqstring) {
|
|
48537
49282
|
|
|
49283
|
+
list.push('<hr/>');
|
|
49284
|
+
|
|
48538
49285
|
if (seqstring.length < maxSequenceSize$1) {
|
|
48539
49286
|
list.push({
|
|
48540
49287
|
label: 'BLAT read sequence',
|
|
@@ -48552,7 +49299,7 @@
|
|
|
48552
49299
|
list.push({
|
|
48553
49300
|
label: 'BLAT left soft-clipped sequence',
|
|
48554
49301
|
click: () => {
|
|
48555
|
-
const clippedSequence = seqstring.
|
|
49302
|
+
const clippedSequence = seqstring.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
|
|
48556
49303
|
const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
|
|
48557
49304
|
const name = `${clickedAlignment.readName} - blat left clip`;
|
|
48558
49305
|
const title = `${this.name} - ${name}`;
|
|
@@ -48564,7 +49311,7 @@
|
|
|
48564
49311
|
list.push({
|
|
48565
49312
|
label: 'BLAT right soft-clipped sequence',
|
|
48566
49313
|
click: () => {
|
|
48567
|
-
const clippedSequence = seqstring.
|
|
49314
|
+
const clippedSequence = seqstring.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
|
|
48568
49315
|
const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
|
|
48569
49316
|
const name = `${clickedAlignment.readName} - blat right clip`;
|
|
48570
49317
|
const title = `${this.name} - ${name}`;
|
|
@@ -48613,7 +49360,7 @@
|
|
|
48613
49360
|
const offsetY = y - this.top;
|
|
48614
49361
|
const genomicLocation = clickState.genomicLocation;
|
|
48615
49362
|
|
|
48616
|
-
if(features.packedGroups) {
|
|
49363
|
+
if (features.packedGroups) {
|
|
48617
49364
|
let minGroupY = Number.MAX_VALUE;
|
|
48618
49365
|
for (let group of features.packedGroups.values()) {
|
|
48619
49366
|
minGroupY = Math.min(minGroupY, group.pixelTop);
|
|
@@ -50435,7 +51182,7 @@
|
|
|
50435
51182
|
|
|
50436
51183
|
let url = this.url.slice(); // slice => copy
|
|
50437
51184
|
if (this.config.oauthToken) {
|
|
50438
|
-
const token = resolveToken(this.config.oauthToken);
|
|
51185
|
+
const token = await resolveToken(this.config.oauthToken);
|
|
50439
51186
|
headers['Authorization'] = `Bearer ${token}`;
|
|
50440
51187
|
}
|
|
50441
51188
|
|
|
@@ -55200,7 +55947,7 @@
|
|
|
55200
55947
|
async function openH5File(options) {
|
|
55201
55948
|
|
|
55202
55949
|
// Some clients (notably igv-webapp) pass a File reference in the url field. Fix this
|
|
55203
|
-
if(options.url && isBlobLike(options.url)) {
|
|
55950
|
+
if (options.url && isBlobLike(options.url)) {
|
|
55204
55951
|
options.file = options.url;
|
|
55205
55952
|
options.url = undefined;
|
|
55206
55953
|
}
|
|
@@ -55230,26 +55977,30 @@
|
|
|
55230
55977
|
|
|
55231
55978
|
async function readExternalIndex(options) {
|
|
55232
55979
|
|
|
55233
|
-
|
|
55234
|
-
if(options.indexReader) {
|
|
55235
|
-
indexReader = options.indexReader;
|
|
55236
|
-
}
|
|
55237
|
-
else if(options.index) {
|
|
55980
|
+
if (options.index) {
|
|
55238
55981
|
return options.index
|
|
55239
|
-
} else
|
|
55240
|
-
indexReader
|
|
55241
|
-
|
|
55242
|
-
|
|
55243
|
-
|
|
55244
|
-
|
|
55245
|
-
|
|
55246
|
-
|
|
55982
|
+
} else {
|
|
55983
|
+
let indexReader;
|
|
55984
|
+
if (options.indexReader) {
|
|
55985
|
+
indexReader = options.indexReader;
|
|
55986
|
+
} else {
|
|
55987
|
+
let indexOptions;
|
|
55988
|
+
if (options.indexURL) {
|
|
55989
|
+
indexOptions = Object.assign({url: options.indexURL}, options);
|
|
55990
|
+
} else if (options.indexPath) {
|
|
55991
|
+
indexOptions = Object.assign({path: options.indexPath}, options);
|
|
55992
|
+
} else if (options.indexFile) {
|
|
55993
|
+
indexOptions = Object.assign({file: options.indexFile}, options);
|
|
55994
|
+
} else {
|
|
55995
|
+
return undefined
|
|
55996
|
+
}
|
|
55997
|
+
indexReader = getReaderFor(indexOptions);
|
|
55998
|
+
}
|
|
55247
55999
|
const indexFileContents = await indexReader.read();
|
|
55248
56000
|
const indexFileJson = new TextDecoder().decode(indexFileContents);
|
|
55249
56001
|
return JSON.parse(indexFileJson)
|
|
55250
|
-
} else {
|
|
55251
|
-
return undefined
|
|
55252
56002
|
}
|
|
56003
|
+
|
|
55253
56004
|
}
|
|
55254
56005
|
|
|
55255
56006
|
|
|
@@ -55327,9 +56078,9 @@
|
|
|
55327
56078
|
* @param {string} h5_file - path for the pytor file
|
|
55328
56079
|
* @param {integer} bin_size - bin size
|
|
55329
56080
|
*/
|
|
55330
|
-
constructor(
|
|
56081
|
+
constructor(config, bin_size=100000){
|
|
55331
56082
|
|
|
55332
|
-
this.
|
|
56083
|
+
this.config = config;
|
|
55333
56084
|
this.bin_size = bin_size;
|
|
55334
56085
|
this.h5_obj = undefined;
|
|
55335
56086
|
this.pytorKeys = [];
|
|
@@ -55339,11 +56090,8 @@
|
|
|
55339
56090
|
async fetch(){
|
|
55340
56091
|
|
|
55341
56092
|
if(!this.h5_obj) {
|
|
55342
|
-
this.
|
|
55343
|
-
|
|
55344
|
-
fetchSize: 1000000,
|
|
55345
|
-
maxSize: 200000000
|
|
55346
|
-
});
|
|
56093
|
+
const options = Object.assign(this.config, {fetchSize: 1000000, maxSize: 200000000});
|
|
56094
|
+
this.h5_obj = await openH5File(options);
|
|
55347
56095
|
}
|
|
55348
56096
|
return this.h5_obj
|
|
55349
56097
|
}
|
|
@@ -64379,7 +65127,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
64379
65127
|
this.set_available_callers();
|
|
64380
65128
|
|
|
64381
65129
|
} else {
|
|
64382
|
-
this.cnvpytor_obj = new HDF5Reader(this.config
|
|
65130
|
+
this.cnvpytor_obj = new HDF5Reader(this.config, this.bin_size);
|
|
64383
65131
|
// get chrom list that currently user viewing
|
|
64384
65132
|
let chroms = [ ...new Set(this.browser.referenceFrameList.map(val => val.chr))];
|
|
64385
65133
|
|
|
@@ -69367,6 +70115,23 @@ ${indent}columns: ${matrix.columns}
|
|
|
69367
70115
|
return this.genome.getChromosome(this.chr)
|
|
69368
70116
|
}
|
|
69369
70117
|
|
|
70118
|
+
/**
|
|
70119
|
+
* Update reference frame based on new viewport width
|
|
70120
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
70121
|
+
*/
|
|
70122
|
+
updateForViewportWidth(viewportWidth) {
|
|
70123
|
+
const {chr} = this;
|
|
70124
|
+
const {bpLength} = this.getChromosome();
|
|
70125
|
+
const viewportWidthBP = this.toBP(viewportWidth);
|
|
70126
|
+
|
|
70127
|
+
// viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
|
|
70128
|
+
if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
|
|
70129
|
+
this.bpPerPixel = bpLength / viewportWidth;
|
|
70130
|
+
} else {
|
|
70131
|
+
this.end = this.start + this.toBP(viewportWidth);
|
|
70132
|
+
}
|
|
70133
|
+
}
|
|
70134
|
+
|
|
69370
70135
|
getMultiLocusLabelBPLengthOnly(pixels) {
|
|
69371
70136
|
const margin = ' ';
|
|
69372
70137
|
const ss = Math.floor(this.start) + 1;
|
|
@@ -69452,7 +70217,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
69452
70217
|
})
|
|
69453
70218
|
}
|
|
69454
70219
|
|
|
69455
|
-
const _version = "3.
|
|
70220
|
+
const _version = "3.3.0";
|
|
69456
70221
|
function version() {
|
|
69457
70222
|
return _version
|
|
69458
70223
|
}
|
|
@@ -69496,10 +70261,24 @@ ${indent}columns: ${matrix.columns}
|
|
|
69496
70261
|
this.select.setAttribute('name', 'chromosome-select-widget');
|
|
69497
70262
|
this.container.appendChild(this.select);
|
|
69498
70263
|
|
|
69499
|
-
this.select.addEventListener('change', () => {
|
|
70264
|
+
this.select.addEventListener('change', async () => {
|
|
69500
70265
|
this.select.blur();
|
|
69501
70266
|
if (this.select.value !== '' && maximumSequenceCountExceeded !== this.select.value) {
|
|
69502
|
-
|
|
70267
|
+
|
|
70268
|
+
if (this.select.value.trim().toLowerCase() === "all" || this.select.value === "*") {
|
|
70269
|
+
if (browser.genome.wholeGenomeView) {
|
|
70270
|
+
const wgChr = browser.genome.getChromosome("all");
|
|
70271
|
+
return {chr: "all", start: 0, end: wgChr.bpLength}
|
|
70272
|
+
}
|
|
70273
|
+
} else {
|
|
70274
|
+
const chromosome = await browser.genome.loadChromosome(this.select.value);
|
|
70275
|
+
const locusObject = {chr: chromosome.name};
|
|
70276
|
+
if (locusObject.start === undefined && locusObject.end === undefined) {
|
|
70277
|
+
locusObject.start = 0;
|
|
70278
|
+
locusObject.end = chromosome.bpLength;
|
|
70279
|
+
}
|
|
70280
|
+
browser.updateLoci([locusObject]);
|
|
70281
|
+
}
|
|
69503
70282
|
}
|
|
69504
70283
|
});
|
|
69505
70284
|
|
|
@@ -69524,7 +70303,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
69524
70303
|
this.genome = genome;
|
|
69525
70304
|
|
|
69526
70305
|
// Start with explicit chromosome name list
|
|
69527
|
-
const list = genome.wgChromosomeNames
|
|
70306
|
+
const list = (genome.wgChromosomeNames) ?
|
|
70307
|
+
genome.wgChromosomeNames.map(nm => genome.getChromosomeDisplayName(nm)) :
|
|
70308
|
+
[];
|
|
69528
70309
|
|
|
69529
70310
|
if (this.showAllChromosomes && genome.chromosomeNames.length > 1) {
|
|
69530
70311
|
const exclude = new Set(list);
|
|
@@ -70750,7 +71531,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
70750
71531
|
});
|
|
70751
71532
|
|
|
70752
71533
|
this.searchInput.addEventListener('change', () => {
|
|
70753
|
-
|
|
71534
|
+
this.doSearch(this.searchInput.value);
|
|
70754
71535
|
});
|
|
70755
71536
|
|
|
70756
71537
|
const searchIconContainer = document.createElement('div');
|
|
@@ -70761,7 +71542,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
70761
71542
|
searchIconContainer.appendChild(searchIcon);
|
|
70762
71543
|
|
|
70763
71544
|
searchIconContainer.addEventListener('click', () => {
|
|
70764
|
-
|
|
71545
|
+
this.doSearch(this.searchInput.value);
|
|
70765
71546
|
});
|
|
70766
71547
|
|
|
70767
71548
|
this.windowSizePanel = new WindowSizePanel(locusSizeGroup, browser);
|
|
@@ -70909,6 +71690,22 @@ ${indent}columns: ${matrix.columns}
|
|
|
70909
71690
|
this.navigation.style.display = 'flex';
|
|
70910
71691
|
}
|
|
70911
71692
|
|
|
71693
|
+
/**
|
|
71694
|
+
|
|
71695
|
+
* Search for the locus string -- this function is called from the navbar search box, and is not part of the API.
|
|
71696
|
+
* Wraps ```search``` and presents an error dialog if false.
|
|
71697
|
+
*
|
|
71698
|
+
* @param locus
|
|
71699
|
+
* @param init
|
|
71700
|
+
* @returns {Promise<void>}
|
|
71701
|
+
*/
|
|
71702
|
+
async doSearch(locus) {
|
|
71703
|
+
const success = await this.browser.search(locus);
|
|
71704
|
+
if (!success) {
|
|
71705
|
+
this.browser.alert.present(new Error(`Unrecognized locus: <b> ${locus} </b>`));
|
|
71706
|
+
}
|
|
71707
|
+
}
|
|
71708
|
+
|
|
70912
71709
|
}
|
|
70913
71710
|
|
|
70914
71711
|
function logo() {
|
|
@@ -72160,6 +72957,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
72160
72957
|
aliasRecord["_cap"] = cap;
|
|
72161
72958
|
}
|
|
72162
72959
|
|
|
72960
|
+
const chrPrefixAlias = aliasRecord.chr.startsWith("chr") ? aliasRecord.chr.substring(3) : "chr" + aliasRecord.chr;
|
|
72961
|
+
aliasRecord["_chrprefix_"] = chrPrefixAlias;
|
|
72962
|
+
|
|
72163
72963
|
}
|
|
72164
72964
|
|
|
72165
72965
|
}
|
|
@@ -72184,7 +72984,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
72184
72984
|
}
|
|
72185
72985
|
|
|
72186
72986
|
async preload(chrNames) {
|
|
72187
|
-
|
|
72987
|
+
await this.reader.preload();
|
|
72188
72988
|
for(let nm of chrNames) {
|
|
72189
72989
|
await this.search(nm);
|
|
72190
72990
|
}
|
|
@@ -72236,12 +73036,6 @@ ${indent}columns: ${matrix.columns}
|
|
|
72236
73036
|
}
|
|
72237
73037
|
return this.aliasRecordCache.get(alias)
|
|
72238
73038
|
}
|
|
72239
|
-
|
|
72240
|
-
async getChromosomeNames() {
|
|
72241
|
-
await this.reader.loadHeader();
|
|
72242
|
-
return Array.from(this.reader.chrNames)
|
|
72243
|
-
}
|
|
72244
|
-
|
|
72245
73039
|
}
|
|
72246
73040
|
|
|
72247
73041
|
/**
|
|
@@ -72266,10 +73060,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
72266
73060
|
this.genome = genome;
|
|
72267
73061
|
}
|
|
72268
73062
|
|
|
72269
|
-
async preload() {
|
|
72270
|
-
|
|
73063
|
+
async preload(chrNames) {
|
|
73064
|
+
// A no-op, this is a text file, no need to preload
|
|
72271
73065
|
}
|
|
72272
|
-
|
|
72273
73066
|
/**
|
|
72274
73067
|
* Return the canonical chromosome name for the alias. If none found return the alias
|
|
72275
73068
|
*
|
|
@@ -72435,7 +73228,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
72435
73228
|
for (let line of lines) {
|
|
72436
73229
|
|
|
72437
73230
|
const tokens = line.split("\t");
|
|
72438
|
-
const chrName = tokens[0];
|
|
73231
|
+
const chrName = tokens[0];
|
|
72439
73232
|
if (!lastChr) lastChr = chrName;
|
|
72440
73233
|
|
|
72441
73234
|
if (chrName !== lastChr) {
|
|
@@ -72453,37 +73246,20 @@ ${indent}columns: ${matrix.columns}
|
|
|
72453
73246
|
bands.push(new Cytoband(start, end, name, stain));
|
|
72454
73247
|
}
|
|
72455
73248
|
}
|
|
72456
|
-
|
|
72457
|
-
|
|
72458
|
-
|
|
72459
|
-
/**
|
|
72460
|
-
* Infer genome chromosome names from cytoband data. This should only be used as a last resort
|
|
72461
|
-
*/
|
|
72462
|
-
async getChromosomeNames() {
|
|
72463
|
-
if(this.cytobands.size === 0) {
|
|
72464
|
-
await this.#loadCytobands();
|
|
72465
|
-
}
|
|
72466
|
-
return Array.from(this.cytobands.keys())
|
|
72467
|
-
}
|
|
72468
|
-
|
|
72469
|
-
/**
|
|
72470
|
-
* Infer chromosome objects from cytoband data. This should only be used as last resort.
|
|
72471
|
-
*/
|
|
72472
|
-
async getChromosomes() {
|
|
72473
|
-
if(this.cytobands.size === 0) {
|
|
72474
|
-
await this.#loadCytobands();
|
|
73249
|
+
if(bands.length > 0) {
|
|
73250
|
+
this.cytobands.set(lastChr, bands);
|
|
72475
73251
|
}
|
|
72476
73252
|
|
|
72477
|
-
const chromosomes = [];
|
|
72478
|
-
let order = 0;
|
|
72479
|
-
for(let [chrName, cytoList] of this.cytobands.entries()) {
|
|
72480
|
-
chromosomes.push(new Chromosome(chrName, order++, cytoList[cytoList.length - 1].end));
|
|
72481
|
-
}
|
|
72482
|
-
return chromosomes
|
|
72483
73253
|
}
|
|
72484
73254
|
|
|
72485
73255
|
}
|
|
72486
73256
|
|
|
73257
|
+
const ucsdIDMap = new Map([
|
|
73258
|
+
["1kg_ref", "hg18"],
|
|
73259
|
+
["1kg_v37", "hg19"],
|
|
73260
|
+
["b37", "hg19"]
|
|
73261
|
+
]);
|
|
73262
|
+
|
|
72487
73263
|
/**
|
|
72488
73264
|
* The Genome class represents an assembly and consists of the following elements
|
|
72489
73265
|
* sequence - Object representing the DNA sequence
|
|
@@ -72508,8 +73284,10 @@ ${indent}columns: ${matrix.columns}
|
|
|
72508
73284
|
this.config = config;
|
|
72509
73285
|
this.browser = browser;
|
|
72510
73286
|
this.id = config.id || generateGenomeID(config);
|
|
73287
|
+
this.ucscID = config.ucscID || ucsdIDMap.get(this.id) || this.id;
|
|
73288
|
+
this.blatDB = config.blatDB || this.ucscID;
|
|
72511
73289
|
this.name = config.name;
|
|
72512
|
-
this.nameSet = config.nameSet;
|
|
73290
|
+
this.nameSet = config.nameSet || 'ucsc';
|
|
72513
73291
|
}
|
|
72514
73292
|
|
|
72515
73293
|
|
|
@@ -72517,49 +73295,45 @@ ${indent}columns: ${matrix.columns}
|
|
|
72517
73295
|
|
|
72518
73296
|
const config = this.config;
|
|
72519
73297
|
|
|
73298
|
+
// Load sequence
|
|
72520
73299
|
this.sequence = await loadSequence(config, this.browser);
|
|
72521
73300
|
|
|
72522
|
-
|
|
72523
|
-
|
|
73301
|
+
|
|
73302
|
+
// Load cytobands. This is optional but required to support the ideogram. Only needed for whole genome view
|
|
73303
|
+
if(false !== config.showIdeogram && false !== config.wholeGenomeView) {
|
|
73304
|
+
if (config.cytobandURL) {
|
|
73305
|
+
this.cytobandSource = new CytobandFile(config.cytobandURL, Object.assign({}, config));
|
|
73306
|
+
} else if (config.cytobandBbURL) {
|
|
73307
|
+
this.cytobandSource = new CytobandFileBB(config.cytobandBbURL, Object.assign({}, config), this);
|
|
73308
|
+
}
|
|
73309
|
+
}
|
|
73310
|
+
|
|
73311
|
+
// Search for chromosomes, that is an array of chromosome objects containing name and length. This is
|
|
73312
|
+
// optional but required to support whole genome view.
|
|
73313
|
+
if (this.sequence.chromosomes) {
|
|
73314
|
+
this.chromosomes = this.sequence.chromosomes;
|
|
73315
|
+
} else if (config.chromSizesURL) {
|
|
72524
73316
|
this.chromosomes = await loadChromSizes(config.chromSizesURL);
|
|
72525
73317
|
} else {
|
|
72526
|
-
|
|
72527
|
-
this.chromosomes = this.sequence.chromosomes || new Map();
|
|
73318
|
+
this.chromosomes = new Map(); // Cache, chromosome are added as they are loaded
|
|
72528
73319
|
}
|
|
72529
73320
|
|
|
72530
|
-
|
|
73321
|
+
// Search for chromosome names. This is optional but required to support the chromosome pulldown
|
|
73322
|
+
if (this.sequence.chromosomeNames) {
|
|
73323
|
+
this.chromosomeNames = this.sequence.chromosomeNames; // Twobit files can supply chromosome names unless they use an external index
|
|
73324
|
+
} else if (this.chromosomes.size > 0) {
|
|
72531
73325
|
this.chromosomeNames = Array.from(this.chromosomes.keys());
|
|
72532
73326
|
}
|
|
72533
73327
|
|
|
73328
|
+
// Chromosome alias
|
|
72534
73329
|
if (config.chromAliasBbURL) {
|
|
72535
73330
|
this.chromAlias = new ChromAliasBB(config.chromAliasBbURL, Object.assign({}, config), this);
|
|
72536
|
-
if (!this.chromosomeNames) {
|
|
72537
|
-
this.chromosomeNames = await this.chromAlias.getChromosomeNames();
|
|
72538
|
-
}
|
|
72539
73331
|
} else if (config.aliasURL) {
|
|
72540
73332
|
this.chromAlias = new ChromAliasFile(config.aliasURL, Object.assign({}, config), this);
|
|
72541
73333
|
} else if (this.chromosomeNames) {
|
|
72542
73334
|
this.chromAlias = new ChromAliasDefaults(this.id, this.chromosomeNames);
|
|
72543
73335
|
}
|
|
72544
73336
|
|
|
72545
|
-
if (config.cytobandBbURL) {
|
|
72546
|
-
this.cytobandSource = new CytobandFileBB(config.cytobandBbURL, Object.assign({}, config), this);
|
|
72547
|
-
} else if (config.cytobandURL) {
|
|
72548
|
-
this.cytobandSource = new CytobandFile(config.cytobandURL, Object.assign({}, config));
|
|
72549
|
-
}
|
|
72550
|
-
|
|
72551
|
-
// Last resort for chromosome information -- retrieve it from the cytoband source if supported
|
|
72552
|
-
if (!this.chromosomeNames && typeof this.cytobandSource.getChromosomeNames === 'function') {
|
|
72553
|
-
this.chromosomeNames = await this.cytobandSource.getChromosomeNames();
|
|
72554
|
-
}
|
|
72555
|
-
if (this.chromosomes.size === 0 && typeof this.cytobandSource.getChromosomes === 'function') {
|
|
72556
|
-
const c = await this.cytobandSource.getChromosomes();
|
|
72557
|
-
for (let chromosome of c) {
|
|
72558
|
-
this.chromosomes.set(c.name, c);
|
|
72559
|
-
}
|
|
72560
|
-
}
|
|
72561
|
-
|
|
72562
|
-
|
|
72563
73337
|
if (false !== config.wholeGenomeView && this.chromosomes.size > 0) {
|
|
72564
73338
|
// Set chromosome order for WG view and chromosome pulldown. If chromosome order is not specified sort
|
|
72565
73339
|
if (config.chromosomeOrder) {
|
|
@@ -72611,9 +73385,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
72611
73385
|
getHomeChromosomeName() {
|
|
72612
73386
|
if (this.showWholeGenomeView() && this.chromosomes.has("all")) {
|
|
72613
73387
|
return "all"
|
|
72614
|
-
} else {
|
|
73388
|
+
} else if (this.chromosomeNames) {
|
|
72615
73389
|
return this.chromosomeNames[0]
|
|
72616
|
-
}
|
|
73390
|
+
} else ;
|
|
72617
73391
|
}
|
|
72618
73392
|
|
|
72619
73393
|
getChromosomeName(chr) {
|
|
@@ -72664,18 +73438,18 @@ ${indent}columns: ${matrix.columns}
|
|
|
72664
73438
|
if (!aliasRecord && chr !== chr.toLowerCase()) {
|
|
72665
73439
|
aliasRecord = await this.chromAlias.search(chr.toLowerCase());
|
|
72666
73440
|
}
|
|
72667
|
-
if(aliasRecord) {
|
|
73441
|
+
if (aliasRecord) {
|
|
72668
73442
|
// Add some aliases for case insensitivy
|
|
72669
73443
|
const upper = aliasRecord.chr.toUpperCase();
|
|
72670
73444
|
const lower = aliasRecord.chr.toLowerCase();
|
|
72671
73445
|
const cap = aliasRecord.chr.charAt(0).toUpperCase() + aliasRecord.chr.slice(1);
|
|
72672
|
-
if(aliasRecord.chr !== upper) {
|
|
73446
|
+
if (aliasRecord.chr !== upper) {
|
|
72673
73447
|
aliasRecord["_uppercase"] = upper;
|
|
72674
73448
|
}
|
|
72675
|
-
if(aliasRecord.chr !== lower) {
|
|
73449
|
+
if (aliasRecord.chr !== lower) {
|
|
72676
73450
|
aliasRecord["_lowercase"] = lower;
|
|
72677
73451
|
}
|
|
72678
|
-
if(aliasRecord.chr !== cap) {
|
|
73452
|
+
if (aliasRecord.chr !== cap) {
|
|
72679
73453
|
aliasRecord["_cap"] = cap;
|
|
72680
73454
|
}
|
|
72681
73455
|
}
|
|
@@ -72807,6 +73581,10 @@ ${indent}columns: ${matrix.columns}
|
|
|
72807
73581
|
return undefined
|
|
72808
73582
|
}
|
|
72809
73583
|
}
|
|
73584
|
+
|
|
73585
|
+
getHubURLs() {
|
|
73586
|
+
return this.config.hubs
|
|
73587
|
+
}
|
|
72810
73588
|
}
|
|
72811
73589
|
|
|
72812
73590
|
/**
|
|
@@ -72849,7 +73627,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
72849
73627
|
}
|
|
72850
73628
|
}
|
|
72851
73629
|
|
|
72852
|
-
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';
|
|
73630
|
+
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';
|
|
72853
73631
|
|
|
72854
73632
|
/**
|
|
72855
73633
|
* Manages XQTL selections.
|
|
@@ -73383,7 +74161,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
73383
74161
|
this.dataRangeDialog = new DataRangeDialog(this, this.root);
|
|
73384
74162
|
this.dataRangeDialog.container.id = `igv-data-range-dialog-${guid$2()}`;
|
|
73385
74163
|
|
|
73386
|
-
this.genericColorPicker = new GenericColorPicker({
|
|
74164
|
+
this.genericColorPicker = new GenericColorPicker({parent: this.root, width: 180});
|
|
73387
74165
|
this.genericColorPicker.container.id = `igv-track-color-picker-${guid$2()}`;
|
|
73388
74166
|
|
|
73389
74167
|
this.sliderDialog = new SliderDialog(this.root);
|
|
@@ -73393,10 +74171,10 @@ ${indent}columns: ${matrix.columns}
|
|
|
73393
74171
|
|
|
73394
74172
|
getSampleNameViewportWidth() {
|
|
73395
74173
|
|
|
73396
|
-
if (undefined === this.sampleNameViewportWidth) {
|
|
74174
|
+
if (false === this.showSampleNames || undefined === this.sampleNameViewportWidth) {
|
|
73397
74175
|
return 0
|
|
73398
74176
|
} else {
|
|
73399
|
-
return
|
|
74177
|
+
return this.sampleNameViewportWidth
|
|
73400
74178
|
}
|
|
73401
74179
|
|
|
73402
74180
|
}
|
|
@@ -73558,7 +74336,8 @@ ${indent}columns: ${matrix.columns}
|
|
|
73558
74336
|
} else {
|
|
73559
74337
|
session = options;
|
|
73560
74338
|
}
|
|
73561
|
-
|
|
74339
|
+
|
|
74340
|
+
await this.loadSessionObject(session);
|
|
73562
74341
|
}
|
|
73563
74342
|
|
|
73564
74343
|
/**
|
|
@@ -73587,8 +74366,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
73587
74366
|
config = new XMLSession(string, knownGenomes);
|
|
73588
74367
|
|
|
73589
74368
|
} else if (filename.endsWith("hub.txt")) {
|
|
73590
|
-
|
|
73591
|
-
const hub = await Hub.loadHub(urlOrFile, options);
|
|
74369
|
+
const hub = await loadHub(urlOrFile);
|
|
73592
74370
|
const genomeConfig = hub.getGenomeConfig();
|
|
73593
74371
|
config = {
|
|
73594
74372
|
reference: genomeConfig
|
|
@@ -73679,7 +74457,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
73679
74457
|
}
|
|
73680
74458
|
|
|
73681
74459
|
// ROIs
|
|
73682
|
-
if(session.showROIOverlays !== undefined) {
|
|
74460
|
+
if (session.showROIOverlays !== undefined) {
|
|
73683
74461
|
this.roiManager.showOverlays = session.showROIOverlays;
|
|
73684
74462
|
}
|
|
73685
74463
|
this.roiManager.clearROIs();
|
|
@@ -73693,12 +74471,16 @@ ${indent}columns: ${matrix.columns}
|
|
|
73693
74471
|
// Sample info
|
|
73694
74472
|
const localSampleInfoFiles = [];
|
|
73695
74473
|
if (session.sampleinfo) {
|
|
73696
|
-
for (const
|
|
73697
|
-
|
|
73698
|
-
|
|
73699
|
-
|
|
74474
|
+
for (const sampleInfoConfig of session.sampleinfo) {
|
|
74475
|
+
// The "file" property is recorded in the session when a local file is referenced. It can't be used
|
|
74476
|
+
// on reloading, its only purpose is to present an alert to the user. This could also be used
|
|
74477
|
+
// to prompt the user to load the file manually, but we don't currently do that.
|
|
74478
|
+
if (sampleInfoConfig.file) {
|
|
74479
|
+
localSampleInfoFiles.push(sampleInfoConfig.file);
|
|
73700
74480
|
} else {
|
|
73701
|
-
this.loadSampleInfo(
|
|
74481
|
+
// this.loadSampleInfo(sampleInfoConfig)
|
|
74482
|
+
await this.sampleInfo.loadSampleInfo(sampleInfoConfig);
|
|
74483
|
+
|
|
73702
74484
|
}
|
|
73703
74485
|
|
|
73704
74486
|
}
|
|
@@ -73739,23 +74521,14 @@ ${indent}columns: ${matrix.columns}
|
|
|
73739
74521
|
}
|
|
73740
74522
|
}
|
|
73741
74523
|
|
|
73742
|
-
|
|
73743
|
-
|
|
73744
|
-
|
|
73745
|
-
|
|
73746
|
-
await
|
|
74524
|
+
// Load a hidden track -- used to populate searchable database without creating a track
|
|
74525
|
+
const configHidden = nonLocalTrackConfigurations.filter(config => true === config.hidden);
|
|
74526
|
+
for (const config of configHidden) {
|
|
74527
|
+
const featureSource = FeatureSource(config, this.genome);
|
|
74528
|
+
await featureSource.getFeatures({chr: "1", start: 0, end: Number.MAX_SAFE_INTEGER});
|
|
73747
74529
|
}
|
|
73748
74530
|
|
|
73749
|
-
|
|
73750
|
-
if (this.trackViews.some(tv => tv.track.selected)) {
|
|
73751
|
-
this.navbar.setEnableTrackSelection(true);
|
|
73752
|
-
}
|
|
73753
|
-
|
|
73754
|
-
this.updateUIWithReferenceFrameList();
|
|
73755
|
-
|
|
73756
|
-
this.updateLocusSearchWidget();
|
|
73757
|
-
|
|
73758
|
-
return trackConfigurations
|
|
74531
|
+
await this.loadTrackList(nonLocalTrackConfigurations);
|
|
73759
74532
|
|
|
73760
74533
|
}
|
|
73761
74534
|
|
|
@@ -73816,13 +74589,8 @@ ${indent}columns: ${matrix.columns}
|
|
|
73816
74589
|
}
|
|
73817
74590
|
|
|
73818
74591
|
if (genomeChange) {
|
|
73819
|
-
|
|
73820
|
-
|
|
73821
|
-
// TODO -- refactor this so "hub" is not loaded twice
|
|
73822
|
-
const hub = await Hub.loadHub(genomeConfig.hubURL);
|
|
73823
|
-
trackConfigurations = hub.getGroupedTrackConfigurations();
|
|
73824
|
-
}
|
|
73825
|
-
this.fireEvent('genomechange', [{genome, trackConfigurations}]);
|
|
74592
|
+
|
|
74593
|
+
this.fireEvent('genomechange', [{genome}]);
|
|
73826
74594
|
|
|
73827
74595
|
if (this.circularView) {
|
|
73828
74596
|
this.circularView.setAssembly({
|
|
@@ -73861,7 +74629,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
73861
74629
|
let genomeConfig;
|
|
73862
74630
|
const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
|
|
73863
74631
|
if (isHubGenome) {
|
|
73864
|
-
const hub = await
|
|
74632
|
+
const hub = await loadHub(idOrConfig.hubURL || idOrConfig.url);
|
|
73865
74633
|
genomeConfig = hub.getGenomeConfig();
|
|
73866
74634
|
} else if (isString$2(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
|
|
73867
74635
|
// Either an ID, a json string, or an object missing required properties.
|
|
@@ -73891,22 +74659,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
73891
74659
|
|
|
73892
74660
|
await this.loadTrackList(tracks);
|
|
73893
74661
|
|
|
73894
|
-
await this.updateViews();
|
|
73895
|
-
|
|
73896
74662
|
return this.genome
|
|
73897
74663
|
}
|
|
73898
74664
|
|
|
73899
|
-
/**
|
|
73900
|
-
* Load a UCSC single-file genome assembly hub.
|
|
73901
|
-
* @param options
|
|
73902
|
-
* @returns {Promise<void>}
|
|
73903
|
-
*/
|
|
73904
|
-
async loadTrackHub(options) {
|
|
73905
|
-
const hub = await Hub.loadHub(options.url, options);
|
|
73906
|
-
const genomeConfig = setDefaults(hub.getGenomeConfig());
|
|
73907
|
-
return this.loadGenome(genomeConfig)
|
|
73908
|
-
}
|
|
73909
|
-
|
|
73910
74665
|
/**
|
|
73911
74666
|
* Called after a session load, search, pan (horizontal drag), or resize
|
|
73912
74667
|
*
|
|
@@ -74003,18 +74758,23 @@ ${indent}columns: ${matrix.columns}
|
|
|
74003
74758
|
}
|
|
74004
74759
|
|
|
74005
74760
|
const promises = [];
|
|
74006
|
-
for (
|
|
74007
|
-
promises.push(this
|
|
74761
|
+
for (const config of configList) {
|
|
74762
|
+
promises.push(this.#loadTrackHelper(config));
|
|
74008
74763
|
}
|
|
74009
74764
|
|
|
74010
74765
|
const loadedTracks = await Promise.all(promises);
|
|
74011
74766
|
|
|
74012
|
-
|
|
74013
|
-
|
|
74014
|
-
|
|
74015
|
-
if (groupAutoscaleViews.length > 0) {
|
|
74016
|
-
this.updateViews();
|
|
74767
|
+
// If any tracks are selected show the selection buttons
|
|
74768
|
+
if (this.trackViews.some(({track}) => track.selected)) {
|
|
74769
|
+
this.navbar.setEnableTrackSelection(true);
|
|
74017
74770
|
}
|
|
74771
|
+
|
|
74772
|
+
this.reorderTracks();
|
|
74773
|
+
|
|
74774
|
+
await resize.call(this);
|
|
74775
|
+
|
|
74776
|
+
this.fireEvent('trackorderchanged', [this.getTrackOrder()]);
|
|
74777
|
+
|
|
74018
74778
|
return loadedTracks
|
|
74019
74779
|
}
|
|
74020
74780
|
|
|
@@ -74028,54 +74788,23 @@ ${indent}columns: ${matrix.columns}
|
|
|
74028
74788
|
*/
|
|
74029
74789
|
async loadTrack(config) {
|
|
74030
74790
|
|
|
74031
|
-
|
|
74032
|
-
|
|
74033
|
-
|
|
74034
|
-
const newTrack = this._loadTrack(config);
|
|
74035
|
-
|
|
74036
|
-
if (newTrack && config.autoscaleGroup) {
|
|
74037
|
-
// Await newTrack load and update all views
|
|
74038
|
-
await newTrack;
|
|
74791
|
+
const loadedTracks = await this.loadTrackList([config]);
|
|
74792
|
+
if (config.autoscaleGroup) {
|
|
74039
74793
|
this.updateViews();
|
|
74040
74794
|
}
|
|
74041
|
-
|
|
74042
|
-
return newTrack
|
|
74795
|
+
return loadedTracks[0]
|
|
74043
74796
|
}
|
|
74044
74797
|
|
|
74045
|
-
|
|
74046
|
-
* Return a promise to load a track. Private function used by loadTrack() and loadTrackList()
|
|
74047
|
-
*
|
|
74048
|
-
* @param config
|
|
74049
|
-
* @returns {*}
|
|
74050
|
-
*/
|
|
74051
|
-
|
|
74052
|
-
async _loadTrack(config) {
|
|
74798
|
+
async #loadTrackHelper(config) {
|
|
74053
74799
|
|
|
74054
74800
|
// config might be json
|
|
74055
74801
|
if (isString$2(config)) {
|
|
74056
74802
|
config = JSON.parse(config);
|
|
74057
74803
|
}
|
|
74058
74804
|
|
|
74805
|
+
let track;
|
|
74059
74806
|
try {
|
|
74060
|
-
|
|
74061
|
-
// Load a hidden track -- used to populate searchable database without creating a track
|
|
74062
|
-
if (config.hidden) {
|
|
74063
|
-
const featureSource = FeatureSource(config, this.genome);
|
|
74064
|
-
await featureSource.getFeatures({chr: "1", start: 0, end: Number.MAX_SAFE_INTEGER});
|
|
74065
|
-
return
|
|
74066
|
-
}
|
|
74067
|
-
|
|
74068
|
-
const newTrack = await this.createTrack(config);
|
|
74069
|
-
|
|
74070
|
-
if ('sampleinfo' === config.type) {
|
|
74071
|
-
this.layoutChange();
|
|
74072
|
-
return
|
|
74073
|
-
} else if (undefined === newTrack) {
|
|
74074
|
-
return
|
|
74075
|
-
}
|
|
74076
|
-
|
|
74077
|
-
return this.addTrack(config, newTrack)
|
|
74078
|
-
|
|
74807
|
+
track = await this.createTrack(config);
|
|
74079
74808
|
} catch (error) {
|
|
74080
74809
|
|
|
74081
74810
|
let msg = error.message || error.error || error.toString();
|
|
@@ -74092,62 +74821,53 @@ ${indent}columns: ${matrix.columns}
|
|
|
74092
74821
|
}
|
|
74093
74822
|
|
|
74094
74823
|
msg = `${msg} : ${isFile(config.url) ? config.url.name : config.url}`;
|
|
74095
|
-
// msg += (": " + FileUtils.isFile(config.url) ? config.url.name : config.url)
|
|
74096
74824
|
const err = new Error(msg);
|
|
74097
74825
|
console.error(err);
|
|
74098
74826
|
throw err
|
|
74099
|
-
// this.alert.present(new Error(msg), undefined)
|
|
74100
74827
|
}
|
|
74101
|
-
}
|
|
74102
74828
|
|
|
74103
|
-
|
|
74829
|
+
if (track) {
|
|
74830
|
+
return await this.addTrack(track)
|
|
74831
|
+
} else {
|
|
74832
|
+
return undefined
|
|
74833
|
+
}
|
|
74104
74834
|
|
|
74835
|
+
}
|
|
74836
|
+
|
|
74837
|
+
async addTrack(track) {
|
|
74105
74838
|
|
|
74106
74839
|
// Set order field of track here, otherwise track order might get shuffled during asynchronous load
|
|
74107
|
-
if (undefined ===
|
|
74108
|
-
|
|
74840
|
+
if (undefined === track.order) {
|
|
74841
|
+
track.order = this.trackViews.length;
|
|
74109
74842
|
}
|
|
74110
74843
|
|
|
74111
|
-
const trackView = new TrackView(this, this.columnContainer,
|
|
74844
|
+
const trackView = new TrackView(this, this.columnContainer, track);
|
|
74112
74845
|
this.trackViews.push(trackView);
|
|
74113
74846
|
toggleTrackLabels(this.trackViews, this.doShowTrackLabels);
|
|
74114
74847
|
|
|
74115
|
-
if (typeof
|
|
74848
|
+
if (typeof track.postInit === 'function') {
|
|
74116
74849
|
try {
|
|
74117
74850
|
trackView.startSpinner();
|
|
74118
|
-
await
|
|
74851
|
+
await track.postInit();
|
|
74119
74852
|
} finally {
|
|
74120
74853
|
trackView.stopSpinner();
|
|
74121
74854
|
}
|
|
74122
74855
|
}
|
|
74123
74856
|
|
|
74124
|
-
if (
|
|
74125
|
-
// Group autoscale will get updated later (as a group)
|
|
74126
|
-
if (config.sync) {
|
|
74127
|
-
await trackView.updateViews();
|
|
74128
|
-
} else {
|
|
74129
|
-
trackView.updateViews();
|
|
74130
|
-
}
|
|
74131
|
-
}
|
|
74132
|
-
|
|
74133
|
-
if (typeof newTrack.hasSamples === 'function' && newTrack.hasSamples()) {
|
|
74857
|
+
if (typeof track.hasSamples === 'function' && track.hasSamples()) {
|
|
74134
74858
|
|
|
74135
74859
|
if (this.sampleInfo.hasAttributes()) {
|
|
74136
74860
|
this.sampleInfoControl.setButtonVisibility(true);
|
|
74137
74861
|
}
|
|
74138
74862
|
|
|
74139
74863
|
if (this.config.showSampleNameButton !== false) {
|
|
74140
|
-
this.sampleNameControl.show();
|
|
74864
|
+
this.sampleNameControl.show();
|
|
74141
74865
|
}
|
|
74142
74866
|
}
|
|
74143
74867
|
|
|
74144
|
-
|
|
74145
|
-
this.reorderTracks();
|
|
74146
|
-
this.fireEvent('trackorderchanged', [this.getTrackOrder()]);
|
|
74147
|
-
|
|
74148
|
-
newTrack.trackView.enableTrackSelection(this.navbar.getEnableTrackSelection());
|
|
74868
|
+
track.trackView.enableTrackSelection(this.navbar.getEnableTrackSelection());
|
|
74149
74869
|
|
|
74150
|
-
return
|
|
74870
|
+
return track
|
|
74151
74871
|
|
|
74152
74872
|
}
|
|
74153
74873
|
|
|
@@ -74292,7 +75012,6 @@ ${indent}columns: ${matrix.columns}
|
|
|
74292
75012
|
}
|
|
74293
75013
|
}
|
|
74294
75014
|
|
|
74295
|
-
|
|
74296
75015
|
reorderTracks() {
|
|
74297
75016
|
|
|
74298
75017
|
this.trackViews.sort(function (a, b) {
|
|
@@ -74529,19 +75248,19 @@ ${indent}columns: ${matrix.columns}
|
|
|
74529
75248
|
|
|
74530
75249
|
this.updateLocusSearchWidget();
|
|
74531
75250
|
|
|
74532
|
-
for (
|
|
74533
|
-
if (
|
|
74534
|
-
await this.genome.getSequence(
|
|
75251
|
+
for (const {bpPerPixel, chr, start} of this.referenceFrameList) {
|
|
75252
|
+
if (bpPerPixel <= bppSequenceThreshold) {
|
|
75253
|
+
await this.genome.getSequence(chr, start, start + 1);
|
|
74535
75254
|
}
|
|
74536
75255
|
}
|
|
74537
75256
|
|
|
74538
|
-
for (
|
|
75257
|
+
for (const centerGuide of this.centerLineList) {
|
|
74539
75258
|
centerGuide.repaint();
|
|
74540
75259
|
}
|
|
74541
75260
|
|
|
74542
75261
|
// Don't autoscale while dragging.
|
|
74543
75262
|
if (this.dragObject) {
|
|
74544
|
-
for (
|
|
75263
|
+
for (const trackView of trackViews) {
|
|
74545
75264
|
await trackView.updateViews();
|
|
74546
75265
|
}
|
|
74547
75266
|
} else {
|
|
@@ -74565,8 +75284,8 @@ ${indent}columns: ${matrix.columns}
|
|
|
74565
75284
|
// Calculate group autoscale dataRange
|
|
74566
75285
|
if (Object.entries(groupAutoscaleTrackViews).length > 0) {
|
|
74567
75286
|
for (const [group, trackViews] of Object.entries(groupAutoscaleTrackViews)) {
|
|
74568
|
-
const
|
|
74569
|
-
const dataRange = doAutoscale(
|
|
75287
|
+
const inViewFeatures = await Promise.all(trackViews.map(trackView => trackView.getInViewFeatures()));
|
|
75288
|
+
const dataRange = doAutoscale(inViewFeatures.flat());
|
|
74570
75289
|
for (const trackView of trackViews) {
|
|
74571
75290
|
trackView.track.dataRange = Object.assign({}, dataRange);
|
|
74572
75291
|
trackView.track.autoscale = false;
|
|
@@ -74575,7 +75294,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
74575
75294
|
}
|
|
74576
75295
|
}
|
|
74577
75296
|
|
|
74578
|
-
await Promise.all(otherTrackViews.map(
|
|
75297
|
+
await Promise.all(otherTrackViews.map(trackView => trackView.updateViews()));
|
|
74579
75298
|
}
|
|
74580
75299
|
|
|
74581
75300
|
}
|
|
@@ -74598,10 +75317,10 @@ ${indent}columns: ${matrix.columns}
|
|
|
74598
75317
|
referenceFrame.end = referenceFrame.start + referenceFrame.bpPerPixel * width;
|
|
74599
75318
|
}
|
|
74600
75319
|
|
|
74601
|
-
const chrName = referenceFrameList.length === 1 ? this.referenceFrameList[0].chr : '';
|
|
74602
|
-
|
|
74603
75320
|
const loc = this.referenceFrameList.map(rf => rf.getLocusString()).join(' ');
|
|
74604
75321
|
|
|
75322
|
+
const chrName = referenceFrameList.length === 1 ? this.genome.getChromosomeDisplayName(this.referenceFrameList[0].chr) : '';
|
|
75323
|
+
|
|
74605
75324
|
this.navbar.updateLocus(loc, chrName);
|
|
74606
75325
|
|
|
74607
75326
|
this.fireEvent('locuschange', [this.referenceFrameList]);
|
|
@@ -74618,6 +75337,46 @@ ${indent}columns: ${matrix.columns}
|
|
|
74618
75337
|
return Math.floor(width / columnCount)
|
|
74619
75338
|
}
|
|
74620
75339
|
|
|
75340
|
+
/**
|
|
75341
|
+
* Update reference frames based on new viewport width
|
|
75342
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
75343
|
+
*/
|
|
75344
|
+
updateReferenceFrames(viewportWidth) {
|
|
75345
|
+
|
|
75346
|
+
for (const referenceFrame of this.referenceFrameList) {
|
|
75347
|
+
referenceFrame.updateForViewportWidth(viewportWidth);
|
|
75348
|
+
}
|
|
75349
|
+
}
|
|
75350
|
+
|
|
75351
|
+
/**
|
|
75352
|
+
* Update DOM viewport elements with new width
|
|
75353
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
75354
|
+
*/
|
|
75355
|
+
updateViewportElements(viewportWidth) {
|
|
75356
|
+
|
|
75357
|
+
for (let i = 0; i < this.referenceFrameList.length; i++) {
|
|
75358
|
+
|
|
75359
|
+
for (const {viewports} of this.trackViews) {
|
|
75360
|
+
viewports[i].setWidth(viewportWidth);
|
|
75361
|
+
}
|
|
75362
|
+
|
|
75363
|
+
for (const {sampleInfoViewport} of this.trackViews) {
|
|
75364
|
+
sampleInfoViewport.setWidth(this.getSampleInfoColumnWidth());
|
|
75365
|
+
sampleInfoViewport.repaint();
|
|
75366
|
+
}
|
|
75367
|
+
|
|
75368
|
+
}
|
|
75369
|
+
}
|
|
75370
|
+
|
|
75371
|
+
/**
|
|
75372
|
+
* Synchronize UI state after viewport updates
|
|
75373
|
+
* @returns {Promise<void>}
|
|
75374
|
+
*/
|
|
75375
|
+
async syncUIState() {
|
|
75376
|
+
this.updateUIWithReferenceFrameList();
|
|
75377
|
+
await this.updateViews(true);
|
|
75378
|
+
}
|
|
75379
|
+
|
|
74621
75380
|
minimumBases() {
|
|
74622
75381
|
return this.config.minimumBases
|
|
74623
75382
|
}
|
|
@@ -74803,29 +75562,12 @@ ${indent}columns: ${matrix.columns}
|
|
|
74803
75562
|
}
|
|
74804
75563
|
|
|
74805
75564
|
/**
|
|
74806
|
-
* @deprecated This is a deprecated method with no known usages.
|
|
75565
|
+
* @deprecated This is a deprecated method with no known usages.
|
|
74807
75566
|
*/
|
|
74808
75567
|
async goto(chr, start, end) {
|
|
74809
75568
|
await this.search(chr + ":" + start + "-" + end);
|
|
74810
75569
|
}
|
|
74811
75570
|
|
|
74812
|
-
/**
|
|
74813
|
-
|
|
74814
|
-
* Search for the locus string -- this function is called from various igv.js GUI elements, and is not part of the
|
|
74815
|
-
* API. Wraps ```search``` and presents an error dialog if false.
|
|
74816
|
-
*
|
|
74817
|
-
* @param string
|
|
74818
|
-
* @param init
|
|
74819
|
-
* @returns {Promise<void>}
|
|
74820
|
-
*/
|
|
74821
|
-
async doSearch(string, init) {
|
|
74822
|
-
const success = await this.search(string, init);
|
|
74823
|
-
if (!success) {
|
|
74824
|
-
this.alert.present(new Error(`Unrecognized locus: <b> ${string} </b>`));
|
|
74825
|
-
}
|
|
74826
|
-
return success
|
|
74827
|
-
}
|
|
74828
|
-
|
|
74829
75571
|
|
|
74830
75572
|
/**
|
|
74831
75573
|
* Search for the locus string
|
|
@@ -74838,6 +75580,10 @@ ${indent}columns: ${matrix.columns}
|
|
|
74838
75580
|
async search(stringOrArray, init) {
|
|
74839
75581
|
|
|
74840
75582
|
const loci = await search(this, stringOrArray);
|
|
75583
|
+
return this.updateLoci(loci, init)
|
|
75584
|
+
}
|
|
75585
|
+
|
|
75586
|
+
async updateLoci(loci, init) {
|
|
74841
75587
|
|
|
74842
75588
|
if (loci && loci.length > 0) {
|
|
74843
75589
|
|
|
@@ -74874,9 +75620,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
74874
75620
|
}
|
|
74875
75621
|
}
|
|
74876
75622
|
|
|
74877
|
-
async loadSampleInfo(
|
|
75623
|
+
async loadSampleInfo(sampleInfoConfig) {
|
|
74878
75624
|
|
|
74879
|
-
await this.sampleInfo.
|
|
75625
|
+
await this.sampleInfo.loadSampleInfo(sampleInfoConfig);
|
|
74880
75626
|
|
|
74881
75627
|
for (const {sampleInfoViewport} of this.trackViews) {
|
|
74882
75628
|
sampleInfoViewport.setWidth(this.getSampleInfoColumnWidth());
|
|
@@ -75014,9 +75760,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
75014
75760
|
json["locus"] = locus.length === 1 ? locus[0] : locus;
|
|
75015
75761
|
|
|
75016
75762
|
const roiSets = this.roiManager.toJSON();
|
|
75017
|
-
if(roiSets) {
|
|
75763
|
+
if (roiSets) {
|
|
75018
75764
|
json["roi"] = roiSets;
|
|
75019
|
-
if(!this.roiManager.showOverlays){
|
|
75765
|
+
if (!this.roiManager.showOverlays) {
|
|
75020
75766
|
json["showROIOverlays"] = false; // true is the default
|
|
75021
75767
|
}
|
|
75022
75768
|
}
|
|
@@ -75359,8 +76105,6 @@ ${indent}columns: ${matrix.columns}
|
|
|
75359
76105
|
}
|
|
75360
76106
|
}
|
|
75361
76107
|
|
|
75362
|
-
|
|
75363
|
-
|
|
75364
76108
|
// Navbar delegates
|
|
75365
76109
|
get sampleInfoControl() {
|
|
75366
76110
|
return this.navbar.sampleInfoControl
|
|
@@ -75382,6 +76126,9 @@ ${indent}columns: ${matrix.columns}
|
|
|
75382
76126
|
return this.navbar.sampleNameControl
|
|
75383
76127
|
}
|
|
75384
76128
|
|
|
76129
|
+
async blat(sequence) {
|
|
76130
|
+
return createBlatTrack({sequence, browser: this, name: 'Blat', title: 'Blat'})
|
|
76131
|
+
}
|
|
75385
76132
|
}
|
|
75386
76133
|
|
|
75387
76134
|
function getFileExtension(input) {
|
|
@@ -75407,7 +76154,7 @@ ${indent}columns: ${matrix.columns}
|
|
|
75407
76154
|
}
|
|
75408
76155
|
|
|
75409
76156
|
/**
|
|
75410
|
-
*
|
|
76157
|
+
* Called when window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
|
|
75411
76158
|
* than class method because it needs to be copied and bound to specific instances of browser to support listener
|
|
75412
76159
|
* removal
|
|
75413
76160
|
*
|
|
@@ -75415,40 +76162,14 @@ ${indent}columns: ${matrix.columns}
|
|
|
75415
76162
|
*/
|
|
75416
76163
|
async function resize() {
|
|
75417
76164
|
|
|
75418
|
-
if (
|
|
75419
|
-
|
|
75420
|
-
const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
|
|
75421
|
-
|
|
75422
|
-
for (let referenceFrame of this.referenceFrameList) {
|
|
75423
|
-
|
|
75424
|
-
const index = this.referenceFrameList.indexOf(referenceFrame);
|
|
75425
|
-
|
|
75426
|
-
const {chr, genome} = referenceFrame;
|
|
75427
|
-
|
|
75428
|
-
const {bpLength} = genome.getChromosome(referenceFrame.chr);
|
|
75429
|
-
|
|
75430
|
-
const viewportWidthBP = referenceFrame.toBP(viewportWidth);
|
|
75431
|
-
|
|
75432
|
-
// viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
|
|
75433
|
-
if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
|
|
75434
|
-
// console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
|
|
75435
|
-
referenceFrame.bpPerPixel = bpLength / viewportWidth;
|
|
75436
|
-
} else {
|
|
75437
|
-
// console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
|
|
75438
|
-
referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
|
|
75439
|
-
}
|
|
75440
|
-
|
|
75441
|
-
for (let {viewports} of this.trackViews) {
|
|
75442
|
-
viewports[index].setWidth(viewportWidth);
|
|
75443
|
-
}
|
|
75444
|
-
|
|
76165
|
+
if (undefined === this.referenceFrameList || 0 === this.referenceFrameList.length) {
|
|
76166
|
+
return
|
|
75445
76167
|
}
|
|
75446
76168
|
|
|
75447
|
-
this.
|
|
75448
|
-
|
|
75449
|
-
|
|
75450
|
-
|
|
75451
|
-
await this.updateViews(true);
|
|
76169
|
+
const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
|
|
76170
|
+
this.updateReferenceFrames(viewportWidth);
|
|
76171
|
+
this.updateViewportElements(viewportWidth);
|
|
76172
|
+
await this.syncUIState();
|
|
75452
76173
|
}
|
|
75453
76174
|
|
|
75454
76175
|
|
|
@@ -75917,7 +76638,8 @@ ${indent}columns: ${matrix.columns}
|
|
|
75917
76638
|
registerTrackClass,
|
|
75918
76639
|
registerTrackCreatorFunction,
|
|
75919
76640
|
registerFileFormats,
|
|
75920
|
-
loadSessionFile: Browser.loadSessionFile
|
|
76641
|
+
loadSessionFile: Browser.loadSessionFile,
|
|
76642
|
+
loadHub
|
|
75921
76643
|
};
|
|
75922
76644
|
|
|
75923
76645
|
return index;
|