igv 3.2.6 → 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 +1548 -893
- package/dist/igv.esm.min.js +9 -9
- package/dist/igv.esm.min.js.map +1 -1
- package/dist/igv.js +1548 -893
- package/dist/igv.min.js +9 -9
- package/dist/igv.min.js.map +1 -1
- package/package.json +3 -3
package/dist/igv.esm.js
CHANGED
|
@@ -12874,6 +12874,20 @@ class NonIndexedFasta {
|
|
|
12874
12874
|
|
|
12875
12875
|
async loadAll() {
|
|
12876
12876
|
|
|
12877
|
+
|
|
12878
|
+
const pushChromosome = (current, order) => {
|
|
12879
|
+
const length = current.length || (current.offset + current.seq.length);
|
|
12880
|
+
if (!chrNameSet.has(current.chr)) {
|
|
12881
|
+
this.sequences.set(current.chr, []);
|
|
12882
|
+
this.chromosomes.set(current.chr, new Chromosome(current.chr, order, length));
|
|
12883
|
+
chrNameSet.add(current.chr);
|
|
12884
|
+
} else {
|
|
12885
|
+
const c = this.chromosomes.get(current.chr);
|
|
12886
|
+
c.bpLength = Math.max(c.bpLength, length);
|
|
12887
|
+
}
|
|
12888
|
+
this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
|
|
12889
|
+
};
|
|
12890
|
+
|
|
12877
12891
|
let data;
|
|
12878
12892
|
if (isDataURL(this.fastaURL)) {
|
|
12879
12893
|
let bytes = decodeDataURI$1(this.fastaURL);
|
|
@@ -12887,70 +12901,62 @@ class NonIndexedFasta {
|
|
|
12887
12901
|
|
|
12888
12902
|
const chrNameSet = new Set();
|
|
12889
12903
|
const lines = splitLines$2(data);
|
|
12890
|
-
const len = lines.length;
|
|
12891
|
-
let lineNo = 0;
|
|
12892
12904
|
let order = 0;
|
|
12893
|
-
let nextLine;
|
|
12894
12905
|
let current = {};
|
|
12895
|
-
|
|
12896
|
-
nextLine = lines[lineNo++].trim();
|
|
12906
|
+
for (let nextLine of lines) {
|
|
12897
12907
|
if (nextLine.startsWith("#") || nextLine.length === 0) ; else if (nextLine.startsWith(">")) {
|
|
12898
12908
|
// Start the next sequence
|
|
12899
|
-
if (current && current.seq) {
|
|
12900
|
-
pushChromosome
|
|
12909
|
+
if (current.seq && current.seq.length > 0) {
|
|
12910
|
+
pushChromosome(current, order++);
|
|
12901
12911
|
}
|
|
12912
|
+
current.seq = "";
|
|
12902
12913
|
|
|
12903
12914
|
const parts = nextLine.substr(1).split(/\s+/);
|
|
12904
12915
|
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12916
|
+
|
|
12917
|
+
// Check for @len= token, which is a non-standard extension supporting igv-reports.
|
|
12918
|
+
if (nextLine.includes("@len=")) {
|
|
12919
|
+
const nameParts = parts[0].split(':');
|
|
12920
|
+
current.chr = nameParts[0];
|
|
12921
|
+
if (nameParts.length > 1 && nameParts[1].indexOf('-') > 0) {
|
|
12922
|
+
|
|
12923
|
+
const locusParts = nameParts[1].split('-');
|
|
12924
|
+
if (locusParts.length === 2 &&
|
|
12925
|
+
/^[0-9]+$/.test(locusParts[0]) &&
|
|
12926
|
+
/^[0-9]+$/.test(locusParts[1])) ;
|
|
12927
|
+
const from = Number.parseInt(locusParts[0]);
|
|
12928
|
+
Number.parseInt(locusParts[1]);
|
|
12918
12929
|
current.offset = from - 1;
|
|
12919
|
-
}
|
|
12920
12930
|
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12931
|
+
// Check for chromosome length token
|
|
12932
|
+
if (parts.length > 1 && parts[1].startsWith("@len=")) {
|
|
12933
|
+
try {
|
|
12934
|
+
current.length = parseInt(parts[1].trim().substring(5));
|
|
12935
|
+
} catch (e) {
|
|
12936
|
+
current.length = undefined;
|
|
12937
|
+
console.error(`Error parsing sequence length for ${nextLine}`);
|
|
12938
|
+
}
|
|
12939
|
+
} else {
|
|
12926
12940
|
current.length = undefined;
|
|
12927
|
-
console.error(`Error parsing sequence length for ${nextLine}`);
|
|
12928
12941
|
}
|
|
12929
|
-
} else {
|
|
12930
|
-
current.length = undefined;
|
|
12931
12942
|
}
|
|
12943
|
+
} else {
|
|
12944
|
+
// No special tokens, a standard FASTA header
|
|
12945
|
+
current.chr = parts[0];
|
|
12946
|
+
current.offset = 0;
|
|
12932
12947
|
}
|
|
12948
|
+
|
|
12933
12949
|
} else {
|
|
12950
|
+
// Not a header or comment, so it must be sequence data
|
|
12934
12951
|
current.seq += nextLine;
|
|
12935
12952
|
}
|
|
12936
|
-
// add last seq
|
|
12937
|
-
if (current && current.seq) {
|
|
12938
|
-
pushChromosome.call(this, current, order);
|
|
12939
|
-
}
|
|
12940
12953
|
}
|
|
12941
12954
|
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
this.sequences.set(current.chr, []);
|
|
12946
|
-
this.chromosomes.set(current.chr, new Chromosome(current.chr, order, length));
|
|
12947
|
-
chrNameSet.add(current.chr);
|
|
12948
|
-
} else {
|
|
12949
|
-
const c = this.chromosomes.get(current.chr);
|
|
12950
|
-
c.bpLength = Math.max(c.bpLength, length);
|
|
12951
|
-
}
|
|
12952
|
-
this.sequences.get(current.chr).push(new SequenceSlice(current.offset, current.seq));
|
|
12955
|
+
// Handle the last sequence
|
|
12956
|
+
if (current.seq && current.seq.length > 0) {
|
|
12957
|
+
pushChromosome(current, order);
|
|
12953
12958
|
}
|
|
12959
|
+
|
|
12954
12960
|
}
|
|
12955
12961
|
|
|
12956
12962
|
/**
|
|
@@ -13715,17 +13721,22 @@ class BPTree {
|
|
|
13715
13721
|
|
|
13716
13722
|
static magic = 2026540177
|
|
13717
13723
|
littleEndian = true
|
|
13724
|
+
type = 'BPTree' // Either BPTree or BPChromTree
|
|
13718
13725
|
nodeCache = new Map()
|
|
13719
13726
|
|
|
13720
|
-
static async loadBpTree(path, config, startOffset) {
|
|
13721
|
-
const bpTree = new BPTree(path, config, startOffset);
|
|
13727
|
+
static async loadBpTree(path, config, startOffset, type, loader) {
|
|
13728
|
+
const bpTree = new BPTree(path, config, startOffset, type, loader);
|
|
13722
13729
|
return bpTree.init()
|
|
13723
13730
|
}
|
|
13724
13731
|
|
|
13725
|
-
constructor(path, config, startOffset) {
|
|
13732
|
+
constructor(path, config, startOffset, type, loader) {
|
|
13726
13733
|
this.path = path;
|
|
13727
13734
|
this.config = config;
|
|
13728
13735
|
this.startOffset = startOffset;
|
|
13736
|
+
if(type) {
|
|
13737
|
+
this.type = type;
|
|
13738
|
+
}
|
|
13739
|
+
this.loader = loader || igvxhr;
|
|
13729
13740
|
}
|
|
13730
13741
|
|
|
13731
13742
|
async init() {
|
|
@@ -13751,69 +13762,22 @@ class BPTree {
|
|
|
13751
13762
|
return this
|
|
13752
13763
|
}
|
|
13753
13764
|
|
|
13754
|
-
|
|
13755
|
-
|
|
13765
|
+
getItemCount() {
|
|
13756
13766
|
if(!this.header) {
|
|
13757
|
-
|
|
13767
|
+
throw Error("Header not initialized")
|
|
13758
13768
|
}
|
|
13769
|
+
return this.header.itemCount
|
|
13770
|
+
}
|
|
13759
13771
|
|
|
13760
|
-
|
|
13772
|
+
async search(term) {
|
|
13761
13773
|
|
|
13762
|
-
if
|
|
13763
|
-
|
|
13774
|
+
if(!this.header) {
|
|
13775
|
+
await this.init();
|
|
13764
13776
|
}
|
|
13765
13777
|
|
|
13766
|
-
const readTreeNode = async (offset) => {
|
|
13767
|
-
|
|
13768
|
-
if (this.nodeCache.has(offset)) {
|
|
13769
|
-
return this.nodeCache.get(offset)
|
|
13770
|
-
} else {
|
|
13771
|
-
|
|
13772
|
-
let binaryParser = await this.#getParserFor(offset, 4);
|
|
13773
|
-
const type = binaryParser.getByte();
|
|
13774
|
-
binaryParser.getByte();
|
|
13775
|
-
const count = binaryParser.getUShort();
|
|
13776
|
-
const items = [];
|
|
13777
|
-
|
|
13778
|
-
if (type === 1) {
|
|
13779
|
-
// Leaf node
|
|
13780
|
-
const size = count * (keySize + valSize);
|
|
13781
|
-
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13782
|
-
for (let i = 0; i < count; i++) {
|
|
13783
|
-
const key = binaryParser.getFixedLengthString(keySize);
|
|
13784
|
-
const offset = binaryParser.getLong();
|
|
13785
|
-
|
|
13786
|
-
let value;
|
|
13787
|
-
if (valSize === 16) {
|
|
13788
|
-
const length = binaryParser.getInt();
|
|
13789
|
-
binaryParser.getInt();
|
|
13790
|
-
value = {offset, length};
|
|
13791
|
-
} else {
|
|
13792
|
-
value = {offset};
|
|
13793
|
-
}
|
|
13794
|
-
items.push({key, value});
|
|
13795
|
-
}
|
|
13796
|
-
} else {
|
|
13797
|
-
// Non leaf node
|
|
13798
|
-
const size = count * (keySize + 8);
|
|
13799
|
-
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13800
|
-
|
|
13801
|
-
for (let i = 0; i < count; i++) {
|
|
13802
|
-
const key = binaryParser.getFixedLengthString(keySize);
|
|
13803
|
-
const offset = binaryParser.getLong();
|
|
13804
|
-
items.push({key, offset});
|
|
13805
|
-
}
|
|
13806
|
-
}
|
|
13807
|
-
|
|
13808
|
-
const node = {type, count, items};
|
|
13809
|
-
this.nodeCache.set(offset, node);
|
|
13810
|
-
return node
|
|
13811
|
-
}
|
|
13812
|
-
};
|
|
13813
|
-
|
|
13814
13778
|
const walkTreeNode = async (offset) => {
|
|
13815
13779
|
|
|
13816
|
-
const node = await readTreeNode(offset);
|
|
13780
|
+
const node = await this.readTreeNode(offset);
|
|
13817
13781
|
|
|
13818
13782
|
if (node.type === 1) {
|
|
13819
13783
|
// Leaf node
|
|
@@ -13844,9 +13808,66 @@ class BPTree {
|
|
|
13844
13808
|
return walkTreeNode(this.header.nodeOffset)
|
|
13845
13809
|
}
|
|
13846
13810
|
|
|
13811
|
+
async readTreeNode (offset) {
|
|
13812
|
+
|
|
13813
|
+
if (this.nodeCache.has(offset)) {
|
|
13814
|
+
return this.nodeCache.get(offset)
|
|
13815
|
+
} else {
|
|
13816
|
+
let binaryParser = await this.#getParserFor(offset, 4);
|
|
13817
|
+
const type = binaryParser.getByte();
|
|
13818
|
+
binaryParser.getByte();
|
|
13819
|
+
const count = binaryParser.getUShort();
|
|
13820
|
+
const items = [];
|
|
13821
|
+
|
|
13822
|
+
const {keySize, valSize} = this.header;
|
|
13823
|
+
|
|
13824
|
+
if (type === 1) {
|
|
13825
|
+
// Leaf node
|
|
13826
|
+
const size = count * (keySize + valSize);
|
|
13827
|
+
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13828
|
+
for (let i = 0; i < count; i++) {
|
|
13829
|
+
const key = binaryParser.getFixedLengthString(keySize);
|
|
13830
|
+
let value;
|
|
13831
|
+
if(this.type === 'BPChromTree') {
|
|
13832
|
+
const id = binaryParser.getInt();
|
|
13833
|
+
const size = binaryParser.getInt();
|
|
13834
|
+
value = {id, size};
|
|
13835
|
+
} else {
|
|
13836
|
+
const offset = binaryParser.getLong();
|
|
13837
|
+
if (valSize === 16) {
|
|
13838
|
+
const length = binaryParser.getLong();
|
|
13839
|
+
value = {offset, length};
|
|
13840
|
+
} else {
|
|
13841
|
+
value = {offset};
|
|
13842
|
+
}
|
|
13843
|
+
}
|
|
13844
|
+
items.push({key, value});
|
|
13845
|
+
}
|
|
13846
|
+
} else {
|
|
13847
|
+
// Non leaf node
|
|
13848
|
+
const size = count * (keySize + 8);
|
|
13849
|
+
binaryParser = await this.#getParserFor(offset + 4, size);
|
|
13850
|
+
|
|
13851
|
+
for (let i = 0; i < count; i++) {
|
|
13852
|
+
const key = binaryParser.getFixedLengthString(keySize);
|
|
13853
|
+
const offset = binaryParser.getLong();
|
|
13854
|
+
items.push({key, offset});
|
|
13855
|
+
}
|
|
13856
|
+
}
|
|
13857
|
+
|
|
13858
|
+
const node = {type, count, items};
|
|
13859
|
+
this.nodeCache.set(offset, node);
|
|
13860
|
+
return node
|
|
13861
|
+
}
|
|
13862
|
+
}
|
|
13863
|
+
|
|
13847
13864
|
async #getParserFor(start, size) {
|
|
13848
|
-
|
|
13849
|
-
|
|
13865
|
+
try {
|
|
13866
|
+
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start, size}}));
|
|
13867
|
+
return new BinaryParser$1(new DataView(data), this.littleEndian)
|
|
13868
|
+
} catch (e) {
|
|
13869
|
+
console.error(e);
|
|
13870
|
+
}
|
|
13850
13871
|
}
|
|
13851
13872
|
|
|
13852
13873
|
}
|
|
@@ -13872,6 +13893,7 @@ class TwobitSequence {
|
|
|
13872
13893
|
|
|
13873
13894
|
littleEndian
|
|
13874
13895
|
metaIndex = new Map()
|
|
13896
|
+
chromosomeNames
|
|
13875
13897
|
|
|
13876
13898
|
constructor(config) {
|
|
13877
13899
|
this.url = config.twoBitURL || config.fastaURL;
|
|
@@ -13964,9 +13986,16 @@ class TwobitSequence {
|
|
|
13964
13986
|
return sequenceBases
|
|
13965
13987
|
}
|
|
13966
13988
|
|
|
13989
|
+
/**
|
|
13990
|
+
* Read the internal index of the 2bit file. This is a list of sequence names and their offsets in the file.
|
|
13991
|
+
*
|
|
13992
|
+
* @returns {Promise<Map<any, any>>}
|
|
13993
|
+
* @private
|
|
13994
|
+
*/
|
|
13967
13995
|
async _readIndex() {
|
|
13968
13996
|
|
|
13969
13997
|
const index = new Map();
|
|
13998
|
+
this.chromosomeNames = [];
|
|
13970
13999
|
|
|
13971
14000
|
const loadRange = {start: 0, size: 64};
|
|
13972
14001
|
let arrayBuffer = await igvxhr.loadArrayBuffer(this.url, {range: loadRange});
|
|
@@ -14019,6 +14048,8 @@ class TwobitSequence {
|
|
|
14019
14048
|
index.set(name, offset);
|
|
14020
14049
|
|
|
14021
14050
|
estNameLength = Math.floor(estNameLength * (i / (i + 1)) + name.length / (i + 1));
|
|
14051
|
+
|
|
14052
|
+
this.chromosomeNames.push(name);
|
|
14022
14053
|
}
|
|
14023
14054
|
return index
|
|
14024
14055
|
}
|
|
@@ -22164,84 +22195,6 @@ function zoomLevelForScale$1(chr, bpPerPixel, genome) {
|
|
|
22164
22195
|
return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
|
|
22165
22196
|
}
|
|
22166
22197
|
|
|
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
22198
|
const RPTREE_HEADER_SIZE = 48;
|
|
22246
22199
|
const RPTREE_NODE_LEAF_ITEM_SIZE = 32; // leaf item size
|
|
22247
22200
|
const RPTREE_NODE_CHILD_ITEM_SIZE = 24; // child item size
|
|
@@ -22252,11 +22205,12 @@ class RPTree {
|
|
|
22252
22205
|
littleEndian = true
|
|
22253
22206
|
nodeCache = new Map()
|
|
22254
22207
|
|
|
22255
|
-
constructor(path, config, startOffset) {
|
|
22208
|
+
constructor(path, config, startOffset, loader) {
|
|
22256
22209
|
|
|
22257
22210
|
this.path = path;
|
|
22258
22211
|
this.config = config;
|
|
22259
22212
|
this.startOffset = startOffset;
|
|
22213
|
+
this.loader = loader || igvxhr;
|
|
22260
22214
|
}
|
|
22261
22215
|
|
|
22262
22216
|
|
|
@@ -22300,7 +22254,7 @@ class RPTree {
|
|
|
22300
22254
|
}
|
|
22301
22255
|
|
|
22302
22256
|
async #getParserFor(start, size) {
|
|
22303
|
-
const data = await
|
|
22257
|
+
const data = await this.loader.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start, size}}));
|
|
22304
22258
|
return new BinaryParser$1(new DataView(data), this.littleEndian)
|
|
22305
22259
|
}
|
|
22306
22260
|
|
|
@@ -22657,6 +22611,165 @@ class Trix {
|
|
|
22657
22611
|
}
|
|
22658
22612
|
}
|
|
22659
22613
|
|
|
22614
|
+
class ChromTree {
|
|
22615
|
+
|
|
22616
|
+
nameToId = new Map()
|
|
22617
|
+
idToName = new Map()
|
|
22618
|
+
|
|
22619
|
+
constructor(path, config, startOffset, loader) {
|
|
22620
|
+
this.path = path;
|
|
22621
|
+
this.config = config;
|
|
22622
|
+
this.startOffset = startOffset;
|
|
22623
|
+
|
|
22624
|
+
this.bpTree = new BPTree(path, config, startOffset, 'BPChromTree', loader);
|
|
22625
|
+
}
|
|
22626
|
+
|
|
22627
|
+
async init() {
|
|
22628
|
+
return this.bpTree.init()
|
|
22629
|
+
}
|
|
22630
|
+
|
|
22631
|
+
getItemCount() {
|
|
22632
|
+
return this.bpTree.getItemCount()
|
|
22633
|
+
}
|
|
22634
|
+
|
|
22635
|
+
/**
|
|
22636
|
+
* Return the chromosome ID for the given name. This is the internal chromosome ID for the parent BB file only.
|
|
22637
|
+
* @param {string} chr - The chromosome name.
|
|
22638
|
+
* @returns {number|null} - The chromosome ID or null if not found.
|
|
22639
|
+
*/
|
|
22640
|
+
async getIdForName(chr) {
|
|
22641
|
+
if (this.nameToId.has(chr)) {
|
|
22642
|
+
return this.nameToId.get(chr)
|
|
22643
|
+
} else {
|
|
22644
|
+
try {
|
|
22645
|
+
const result = await this.bpTree.search(chr);
|
|
22646
|
+
if (result) {
|
|
22647
|
+
const id = result.id;
|
|
22648
|
+
this.nameToId.set(chr, id);
|
|
22649
|
+
return id
|
|
22650
|
+
} else {
|
|
22651
|
+
return
|
|
22652
|
+
}
|
|
22653
|
+
} catch (error) {
|
|
22654
|
+
throw new Error(error)
|
|
22655
|
+
}
|
|
22656
|
+
}
|
|
22657
|
+
}
|
|
22658
|
+
|
|
22659
|
+
/**
|
|
22660
|
+
* Return the chromosome name for the given ID. This is a potentially expensive operation as it involves
|
|
22661
|
+
* walking the tree until the leaf item for the given name is found. Currently it is used in only 2
|
|
22662
|
+
* situations:
|
|
22663
|
+
* (1) decoding features from a bigbed search-by-name query
|
|
22664
|
+
* (2) decoding bigwig data from the whole genome view
|
|
22665
|
+
* @param {number} id
|
|
22666
|
+
* @return {string|null}
|
|
22667
|
+
*/
|
|
22668
|
+
async getNameForId(id) {
|
|
22669
|
+
if (this.idToName.has(id)) {
|
|
22670
|
+
return this.idToName.get(id)
|
|
22671
|
+
} else {
|
|
22672
|
+
const name = await this.searchForName(id);
|
|
22673
|
+
if (name !== null) {
|
|
22674
|
+
this.idToName.set(id, name);
|
|
22675
|
+
return name
|
|
22676
|
+
}
|
|
22677
|
+
}
|
|
22678
|
+
return null
|
|
22679
|
+
}
|
|
22680
|
+
|
|
22681
|
+
/**
|
|
22682
|
+
* Perform a reverse search by traversing the tree starting at the given offset. This is potentially expensive
|
|
22683
|
+
* as it traverses the tree to find the name corresponding to the given ID. It shoud not be used for large trees.
|
|
22684
|
+
*
|
|
22685
|
+
* @param {number} id - The ID to search for.
|
|
22686
|
+
* @returns {string|null} - The name corresponding to the ID, or null if not found.
|
|
22687
|
+
*/
|
|
22688
|
+
async searchForName(id) {
|
|
22689
|
+
|
|
22690
|
+
const reverseSearch = async (offset, id) => {
|
|
22691
|
+
|
|
22692
|
+
const node = await this.bpTree.readTreeNode(offset);
|
|
22693
|
+
|
|
22694
|
+
let found = null;
|
|
22695
|
+
|
|
22696
|
+
if (node.type === 1) {
|
|
22697
|
+
// Leaf node
|
|
22698
|
+
for (const item of node.items) {
|
|
22699
|
+
const key = item.key;
|
|
22700
|
+
const itemId = item.value.id;
|
|
22701
|
+
if (itemId === id) {
|
|
22702
|
+
found = key;
|
|
22703
|
+
}
|
|
22704
|
+
// Cache the name and ID for future lookups
|
|
22705
|
+
this.nameToId.set(key, itemId);
|
|
22706
|
+
this.idToName.set(id, itemId);
|
|
22707
|
+
}
|
|
22708
|
+
return found
|
|
22709
|
+
} else {
|
|
22710
|
+
// Non-leaf node
|
|
22711
|
+
for (const item of node.items) {
|
|
22712
|
+
found = await reverseSearch.call(this, item.offset, id);
|
|
22713
|
+
if (found !== null) {
|
|
22714
|
+
break
|
|
22715
|
+
}
|
|
22716
|
+
}
|
|
22717
|
+
}
|
|
22718
|
+
return found
|
|
22719
|
+
};
|
|
22720
|
+
|
|
22721
|
+
try {
|
|
22722
|
+
return reverseSearch.call(this, this.startOffset + 32, id)
|
|
22723
|
+
} catch (error) {
|
|
22724
|
+
throw new Error(error)
|
|
22725
|
+
}
|
|
22726
|
+
}
|
|
22727
|
+
|
|
22728
|
+
/**
|
|
22729
|
+
* Return an estimated length of the genome, which might be the actual length if the number of contigs is small.
|
|
22730
|
+
* This is only used for calculating a default feature visibility window.
|
|
22731
|
+
*
|
|
22732
|
+
* @return {number}
|
|
22733
|
+
*/
|
|
22734
|
+
async estimateGenomeSize() {
|
|
22735
|
+
try {
|
|
22736
|
+
const runningTotal = {total: 0, count: 0};
|
|
22737
|
+
await this.accumulateSize(this.startOffset + 32, runningTotal, 10000);
|
|
22738
|
+
const itemCount = this.getItemCount();
|
|
22739
|
+
return (itemCount / runningTotal.count) * runningTotal.total
|
|
22740
|
+
|
|
22741
|
+
} catch (error) {
|
|
22742
|
+
console.error("Error estimating genome size", error);
|
|
22743
|
+
return -1
|
|
22744
|
+
}
|
|
22745
|
+
}
|
|
22746
|
+
|
|
22747
|
+
async accumulateSize(offset, runningTotal, maxCount) {
|
|
22748
|
+
|
|
22749
|
+
const node = await this.bpTree.readTreeNode(offset);
|
|
22750
|
+
|
|
22751
|
+
if (node.type === 1) {
|
|
22752
|
+
// Leaf node
|
|
22753
|
+
for (const item of node.items) {
|
|
22754
|
+
const value = item.value;
|
|
22755
|
+
runningTotal.total += value.size;
|
|
22756
|
+
runningTotal.count += 1;
|
|
22757
|
+
}
|
|
22758
|
+
} else {
|
|
22759
|
+
// Non-leaf node. Items are visited in random order to avoid biasing the estimate
|
|
22760
|
+
const shuffledItems = node.items.slice().sort(() => Math.random() - 0.5);
|
|
22761
|
+
for (const item of shuffledItems) {
|
|
22762
|
+
await this.accumulateSize(item.offset, runningTotal, maxCount);
|
|
22763
|
+
if (runningTotal.count > maxCount) {
|
|
22764
|
+
break
|
|
22765
|
+
}
|
|
22766
|
+
}
|
|
22767
|
+
}
|
|
22768
|
+
return runningTotal
|
|
22769
|
+
}
|
|
22770
|
+
|
|
22771
|
+
}
|
|
22772
|
+
|
|
22660
22773
|
/*
|
|
22661
22774
|
* The MIT License (MIT)
|
|
22662
22775
|
*
|
|
@@ -22720,14 +22833,40 @@ class BWReader {
|
|
|
22720
22833
|
async preload() {
|
|
22721
22834
|
const data = await igvxhr.loadArrayBuffer(this.path);
|
|
22722
22835
|
this.loader = new DataBuffer(data);
|
|
22836
|
+
for (let rpTree of this.rpTreeCache.values()) {
|
|
22837
|
+
rpTree.loader = this.loader;
|
|
22838
|
+
}
|
|
22839
|
+
if (this._searchTrees) {
|
|
22840
|
+
for (let bpTree of this._searchTrees) {
|
|
22841
|
+
bpTree.loader = this.loader;
|
|
22842
|
+
}
|
|
22843
|
+
}
|
|
22723
22844
|
}
|
|
22724
22845
|
|
|
22725
|
-
async readWGFeatures(bpPerPixel, windowFunction) {
|
|
22846
|
+
async readWGFeatures(wgChromosomeNames, bpPerPixel, windowFunction) {
|
|
22847
|
+
|
|
22726
22848
|
await this.loadHeader();
|
|
22727
|
-
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22849
|
+
// Convert the logic to JavaScript
|
|
22850
|
+
let minID = Number.MAX_SAFE_INTEGER;
|
|
22851
|
+
let maxID = -1;
|
|
22852
|
+
let chr1;
|
|
22853
|
+
let chr2;
|
|
22854
|
+
|
|
22855
|
+
for (const chr of wgChromosomeNames) {
|
|
22856
|
+
const id = await this.getIdForChr(chr);
|
|
22857
|
+
if (id === null || id === undefined) {
|
|
22858
|
+
continue
|
|
22859
|
+
}
|
|
22860
|
+
if (id < minID) {
|
|
22861
|
+
minID = id;
|
|
22862
|
+
chr1 = chr;
|
|
22863
|
+
}
|
|
22864
|
+
if (id > maxID) {
|
|
22865
|
+
maxID = id;
|
|
22866
|
+
chr2 = chr;
|
|
22867
|
+
}
|
|
22868
|
+
}
|
|
22869
|
+
|
|
22731
22870
|
return this.readFeatures(chr1, 0, chr2, Number.MAX_VALUE, bpPerPixel, windowFunction)
|
|
22732
22871
|
}
|
|
22733
22872
|
|
|
@@ -22738,8 +22877,8 @@ class BWReader {
|
|
|
22738
22877
|
|
|
22739
22878
|
await this.loadHeader();
|
|
22740
22879
|
|
|
22741
|
-
|
|
22742
|
-
|
|
22880
|
+
const chrIdx1 = await this.getIdForChr(chr1);
|
|
22881
|
+
const chrIdx2 = await this.getIdForChr(chr2);
|
|
22743
22882
|
|
|
22744
22883
|
if (chrIdx1 === undefined || chrIdx2 === undefined) {
|
|
22745
22884
|
return []
|
|
@@ -22798,7 +22937,7 @@ class BWReader {
|
|
|
22798
22937
|
} else {
|
|
22799
22938
|
plain = uint8Array;
|
|
22800
22939
|
}
|
|
22801
|
-
decodeFunction.call(this, new DataView(plain.buffer), chrIdx1, bpStart, chrIdx2, bpEnd, features,
|
|
22940
|
+
await decodeFunction.call(this, new DataView(plain.buffer), chrIdx1, bpStart, chrIdx2, bpEnd, features, windowFunction);
|
|
22802
22941
|
}
|
|
22803
22942
|
|
|
22804
22943
|
features.sort(function (a, b) {
|
|
@@ -22815,29 +22954,30 @@ class BWReader {
|
|
|
22815
22954
|
* @param chr
|
|
22816
22955
|
* @returns {Promise<*>}
|
|
22817
22956
|
*/
|
|
22818
|
-
async
|
|
22957
|
+
async getIdForChr(chr) {
|
|
22819
22958
|
|
|
22820
22959
|
if (this.chrAliasTable.has(chr)) {
|
|
22821
22960
|
chr = this.chrAliasTable.get(chr);
|
|
22822
|
-
if (chr
|
|
22961
|
+
if (!chr) {
|
|
22823
22962
|
return undefined
|
|
22824
22963
|
}
|
|
22825
22964
|
}
|
|
22826
22965
|
|
|
22827
|
-
let chrIdx = this.chromTree.
|
|
22966
|
+
let chrIdx = await this.chromTree.getIdForName(chr);
|
|
22828
22967
|
|
|
22829
22968
|
// Try alias
|
|
22830
22969
|
if (chrIdx === undefined && this.genome) {
|
|
22831
22970
|
const aliasRecord = await this.genome.getAliasRecord(chr);
|
|
22832
22971
|
let alias;
|
|
22833
22972
|
if (aliasRecord) {
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
22973
|
+
for (let k of Object.keys(aliasRecord)) {
|
|
22974
|
+
if (k === "start" || k === "end") continue
|
|
22975
|
+
alias = aliasRecord[k];
|
|
22976
|
+
if (alias === chr) continue // Already tried this
|
|
22977
|
+
chrIdx = await this.chromTree.getIdForName(alias);
|
|
22978
|
+
if (chrIdx !== undefined) {
|
|
22979
|
+
break
|
|
22980
|
+
}
|
|
22841
22981
|
}
|
|
22842
22982
|
}
|
|
22843
22983
|
this.chrAliasTable.set(chr, alias); // alias may be undefined => no alias exists. Setting prevents repeated attempts
|
|
@@ -22924,7 +23064,8 @@ class BWReader {
|
|
|
22924
23064
|
this.header.extraIndexOffsets.length > 0) {
|
|
22925
23065
|
this._searchTrees = [];
|
|
22926
23066
|
for (let offset of this.header.extraIndexOffsets) {
|
|
22927
|
-
const
|
|
23067
|
+
const type = undefined;
|
|
23068
|
+
const bpTree = await BPTree.loadBpTree(this.path, this.config, offset, type, this.loader);
|
|
22928
23069
|
this._searchTrees.push(bpTree);
|
|
22929
23070
|
}
|
|
22930
23071
|
}
|
|
@@ -23041,15 +23182,13 @@ class BWReader {
|
|
|
23041
23182
|
this.totalSummary = new BWTotalSummary(extHeaderParser);
|
|
23042
23183
|
}
|
|
23043
23184
|
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
this.chromTree = await this.#readChromTree(header.chromTreeOffset, bufferSize);
|
|
23047
|
-
this.chrNames = new Set(this.chromTree.idToName);
|
|
23185
|
+
this.chromTree = new ChromTree(this.path, this.config, header.chromTreeOffset, this.loader);
|
|
23186
|
+
await this.chromTree.init();
|
|
23048
23187
|
|
|
23049
23188
|
// Estimate feature density from dataCount (bigbed only)
|
|
23050
|
-
if("bigbed" === this.type) {
|
|
23189
|
+
if ("bigbed" === this.type) {
|
|
23051
23190
|
const dataCount = await this.#readDataCount(header.fullDataOffset);
|
|
23052
|
-
this.featureDensity = dataCount / this.chromTree.
|
|
23191
|
+
this.featureDensity = dataCount / await this.chromTree.estimateGenomeSize();
|
|
23053
23192
|
}
|
|
23054
23193
|
|
|
23055
23194
|
this.header = header;
|
|
@@ -23073,38 +23212,6 @@ class BWReader {
|
|
|
23073
23212
|
return binaryParser.getInt()
|
|
23074
23213
|
}
|
|
23075
23214
|
|
|
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
23215
|
|
|
23109
23216
|
async loadExtendedHeader(offset) {
|
|
23110
23217
|
|
|
@@ -23160,7 +23267,7 @@ class BWReader {
|
|
|
23160
23267
|
if (rpTree) {
|
|
23161
23268
|
return rpTree
|
|
23162
23269
|
} else {
|
|
23163
|
-
rpTree = new RPTree(this.path, this.config, offset);
|
|
23270
|
+
rpTree = new RPTree(this.path, this.config, offset, this.loader);
|
|
23164
23271
|
await rpTree.init();
|
|
23165
23272
|
this.rpTreeCache.set(offset, rpTree);
|
|
23166
23273
|
return rpTree
|
|
@@ -23200,9 +23307,7 @@ class BWReader {
|
|
|
23200
23307
|
const plain = (this.header.uncompressBuffSize > 0) ? inflate_1$3(uint8Array) : uint8Array;
|
|
23201
23308
|
const decodeFunction = getBedDataDecoder.call(this);
|
|
23202
23309
|
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
|
-
|
|
23310
|
+
await decodeFunction.call(this, new DataView(plain.buffer), 0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, features);
|
|
23206
23311
|
return features
|
|
23207
23312
|
|
|
23208
23313
|
}
|
|
@@ -23270,7 +23375,7 @@ function zoomLevelForScale(bpPerPixel, zoomLevelHeaders) {
|
|
|
23270
23375
|
}
|
|
23271
23376
|
|
|
23272
23377
|
|
|
23273
|
-
function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray,
|
|
23378
|
+
async function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, windowFunction, littleEndian) {
|
|
23274
23379
|
|
|
23275
23380
|
const binaryParser = new BinaryParser$1(data, littleEndian);
|
|
23276
23381
|
const chromId = binaryParser.getInt();
|
|
@@ -23311,7 +23416,7 @@ function decodeWigData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, chr
|
|
|
23311
23416
|
else if (chromId > chrIdx2 || (chromId === chrIdx2 && chromStart >= bpEnd)) break
|
|
23312
23417
|
|
|
23313
23418
|
if (Number.isFinite(value)) {
|
|
23314
|
-
const chr =
|
|
23419
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23315
23420
|
featureArray.push({chr: chr, start: chromStart, end: chromEnd, value: value});
|
|
23316
23421
|
}
|
|
23317
23422
|
}
|
|
@@ -23322,12 +23427,13 @@ function getBedDataDecoder() {
|
|
|
23322
23427
|
|
|
23323
23428
|
const minSize = 3 * 4 + 1; // Minimum # of bytes required for a bed record
|
|
23324
23429
|
const decoder = getDecoder(this.header.definedFieldCount, this.header.fieldCount, this.autoSql, this.format);
|
|
23325
|
-
return function (data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray
|
|
23326
|
-
|
|
23430
|
+
return async function (data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray) {
|
|
23431
|
+
|
|
23432
|
+
const binaryParser = new BinaryParser$1(data, this.littleEndian);
|
|
23327
23433
|
while (binaryParser.remLength() >= minSize) {
|
|
23328
23434
|
|
|
23329
23435
|
const chromId = binaryParser.getInt();
|
|
23330
|
-
const chr =
|
|
23436
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23331
23437
|
const chromStart = binaryParser.getInt();
|
|
23332
23438
|
const chromEnd = binaryParser.getInt();
|
|
23333
23439
|
const rest = binaryParser.getString();
|
|
@@ -23344,8 +23450,7 @@ function getBedDataDecoder() {
|
|
|
23344
23450
|
}
|
|
23345
23451
|
}
|
|
23346
23452
|
|
|
23347
|
-
|
|
23348
|
-
function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, chrDict, windowFunction, littleEndian) {
|
|
23453
|
+
async function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, windowFunction, littleEndian) {
|
|
23349
23454
|
|
|
23350
23455
|
const binaryParser = new BinaryParser$1(data, littleEndian);
|
|
23351
23456
|
const minSize = 8 * 4; // Minimum # of bytes required for a zoom record
|
|
@@ -23377,7 +23482,7 @@ function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, ch
|
|
|
23377
23482
|
|
|
23378
23483
|
|
|
23379
23484
|
if (Number.isFinite(value)) {
|
|
23380
|
-
const chr =
|
|
23485
|
+
const chr = await this.chromTree.getNameForId(chromId);
|
|
23381
23486
|
featureArray.push({chr: chr, start: chromStart, end: chromEnd, value: value});
|
|
23382
23487
|
|
|
23383
23488
|
|
|
@@ -23462,7 +23567,8 @@ class BWSource extends BaseFeatureSource {
|
|
|
23462
23567
|
|
|
23463
23568
|
let features;
|
|
23464
23569
|
if ("all" === chr.toLowerCase()) {
|
|
23465
|
-
|
|
23570
|
+
const wgChromosomeNames = this.genome.wgChromosomeNames;
|
|
23571
|
+
features = isBigWig && wgChromosomeNames? await this.getWGValues(wgChromosomeNames, windowFunction, bpPerPixel) : [];
|
|
23466
23572
|
} else {
|
|
23467
23573
|
features = await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
|
|
23468
23574
|
}
|
|
@@ -23486,15 +23592,14 @@ class BWSource extends BaseFeatureSource {
|
|
|
23486
23592
|
|
|
23487
23593
|
}
|
|
23488
23594
|
|
|
23489
|
-
async getWGValues(windowFunction, bpPerPixel) {
|
|
23595
|
+
async getWGValues(wgChromosomeNames, windowFunction, bpPerPixel) {
|
|
23490
23596
|
|
|
23491
23597
|
const genome = this.genome;
|
|
23492
23598
|
const cached = this.#wgValues[windowFunction];
|
|
23493
23599
|
if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
|
|
23494
23600
|
return cached.values
|
|
23495
23601
|
} else {
|
|
23496
|
-
|
|
23497
|
-
const features = await this.reader.readWGFeatures(bpPerPixel, windowFunction);
|
|
23602
|
+
const features = await this.reader.readWGFeatures(wgChromosomeNames, bpPerPixel, windowFunction);
|
|
23498
23603
|
let wgValues = [];
|
|
23499
23604
|
for (let f of features) {
|
|
23500
23605
|
const chr = f.chr;
|
|
@@ -28289,7 +28394,7 @@ async function createBlatTrack({sequence, browser, name, title}) {
|
|
|
28289
28394
|
features
|
|
28290
28395
|
};
|
|
28291
28396
|
|
|
28292
|
-
const track = await browser.
|
|
28397
|
+
const track = (await browser.loadTrackList([trackConfig]))[0];
|
|
28293
28398
|
track.openTableView();
|
|
28294
28399
|
|
|
28295
28400
|
} catch (e) {
|
|
@@ -30341,109 +30446,578 @@ function convertToHubURL(accension) {
|
|
|
30341
30446
|
}
|
|
30342
30447
|
}
|
|
30343
30448
|
|
|
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
|
-
*/
|
|
30449
|
+
const parentOverrideProperties = new Set(["visibility", "priority", "group"]);
|
|
30350
30450
|
|
|
30451
|
+
const nonInheritableProperties = new Set([
|
|
30452
|
+
"track", "type", "shortLabel", "longLabel", "bigDataUrl",
|
|
30453
|
+
"parent", "superTrack", "priority", "view", "compositeContainer", "compositeTrack"
|
|
30454
|
+
]);
|
|
30351
30455
|
|
|
30352
|
-
class Hub {
|
|
30353
30456
|
|
|
30354
|
-
|
|
30355
|
-
|
|
30356
|
-
|
|
30457
|
+
class Stanza {
|
|
30458
|
+
|
|
30459
|
+
properties = new Map()
|
|
30460
|
+
|
|
30461
|
+
constructor(type, name) {
|
|
30462
|
+
this.type = type;
|
|
30463
|
+
this.name = name;
|
|
30464
|
+
}
|
|
30465
|
+
|
|
30466
|
+
setProperty(key, value) {
|
|
30467
|
+
this.properties.set(key, value);
|
|
30468
|
+
}
|
|
30469
|
+
|
|
30470
|
+
getProperty(key) {
|
|
30471
|
+
if (this.properties.has("noInherit")) {
|
|
30472
|
+
return this.properties.get(key)
|
|
30473
|
+
} else if (this.parent && parentOverrideProperties.has(key) && this.parent.hasProperty(key)) {
|
|
30474
|
+
return this.parent.getProperty(key)
|
|
30475
|
+
} else if (this.properties.has(key)) {
|
|
30476
|
+
return this.properties.get(key)
|
|
30477
|
+
} else if (this.parent && !nonInheritableProperties.has(key)) {
|
|
30478
|
+
return this.parent.getProperty(key)
|
|
30479
|
+
} else {
|
|
30480
|
+
return undefined
|
|
30481
|
+
}
|
|
30482
|
+
}
|
|
30483
|
+
|
|
30484
|
+
hasProperty(key) {
|
|
30485
|
+
return this.getProperty(key) !== null && this.getProperty(key) !== undefined
|
|
30486
|
+
}
|
|
30357
30487
|
|
|
30358
|
-
|
|
30488
|
+
hasOwnProperty(key) {
|
|
30489
|
+
return this.properties.has(key)
|
|
30490
|
+
}
|
|
30359
30491
|
|
|
30360
|
-
|
|
30361
|
-
|
|
30362
|
-
|
|
30363
|
-
|
|
30364
|
-
|
|
30365
|
-
|
|
30366
|
-
|
|
30367
|
-
|
|
30368
|
-
|
|
30492
|
+
getOwnProperty(key) {
|
|
30493
|
+
return this.properties.get(key)
|
|
30494
|
+
}
|
|
30495
|
+
|
|
30496
|
+
removeProperty(key) {
|
|
30497
|
+
this.properties.delete(key);
|
|
30498
|
+
}
|
|
30499
|
+
|
|
30500
|
+
get format() {
|
|
30501
|
+
const type = this.getProperty("type");
|
|
30502
|
+
if (type) {
|
|
30503
|
+
// Trim extra bed qualifiers (e.g. bigBed + 4)
|
|
30504
|
+
return firstWord$1(type)
|
|
30505
|
+
}
|
|
30506
|
+
return undefined // unknown type
|
|
30507
|
+
}
|
|
30508
|
+
|
|
30509
|
+
/**
|
|
30510
|
+
* IGV display mode
|
|
30511
|
+
*/
|
|
30512
|
+
get displayMode() {
|
|
30513
|
+
let viz = this.getProperty("visibility");
|
|
30514
|
+
if (!viz) {
|
|
30515
|
+
return "COLLAPSED"
|
|
30516
|
+
} else {
|
|
30517
|
+
viz = viz.toLowerCase();
|
|
30518
|
+
switch (viz) {
|
|
30519
|
+
case "dense":
|
|
30520
|
+
return "COLLAPSED"
|
|
30521
|
+
case "pack":
|
|
30522
|
+
return "EXPANDED"
|
|
30523
|
+
case "squish":
|
|
30524
|
+
return "SQUISHED"
|
|
30525
|
+
default:
|
|
30526
|
+
return "COLLAPSED"
|
|
30369
30527
|
}
|
|
30528
|
+
}
|
|
30529
|
+
}
|
|
30530
|
+
}
|
|
30531
|
+
|
|
30532
|
+
|
|
30533
|
+
function firstWord$1(str) {
|
|
30534
|
+
const idx = str.indexOf(' ');
|
|
30535
|
+
return idx > 0 ? str.substring(0, idx) : str
|
|
30536
|
+
}
|
|
30370
30537
|
|
|
30371
|
-
|
|
30372
|
-
|
|
30373
|
-
|
|
30374
|
-
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30538
|
+
class TrackConfigContainer {
|
|
30539
|
+
constructor(name, label, priority, defaultOpen) {
|
|
30540
|
+
this.name = name;
|
|
30541
|
+
this.priority = priority;
|
|
30542
|
+
this.label = label;
|
|
30543
|
+
this.defaultOpen = defaultOpen;
|
|
30544
|
+
this.tracks = [];
|
|
30545
|
+
this.children = [];
|
|
30546
|
+
}
|
|
30547
|
+
|
|
30548
|
+
isEmpty() {
|
|
30549
|
+
return this.tracks.length === 0 &&
|
|
30550
|
+
(!this.children || this.children.length === 0 || this.children.every(child => child.isEmpty()));
|
|
30551
|
+
}
|
|
30552
|
+
|
|
30553
|
+
map(callback) {
|
|
30554
|
+
this.tracks.forEach(callback);
|
|
30555
|
+
this.children.forEach(child => child.map(callback));
|
|
30556
|
+
}
|
|
30557
|
+
|
|
30558
|
+
findTracks(filter) {
|
|
30559
|
+
const found = [];
|
|
30560
|
+
this._find(found, filter);
|
|
30561
|
+
return found;
|
|
30562
|
+
}
|
|
30563
|
+
|
|
30564
|
+
_find(found, filter) {
|
|
30565
|
+
this.tracks.forEach(track => {
|
|
30566
|
+
if (filter(track)) {
|
|
30567
|
+
found.push(track);
|
|
30378
30568
|
}
|
|
30569
|
+
});
|
|
30570
|
+
this.children.forEach(child => child._find(found, filter));
|
|
30571
|
+
}
|
|
30572
|
+
|
|
30573
|
+
countTracks() {
|
|
30574
|
+
return this.tracks.length + this.children.reduce((count, child) => count + child.countTracks(), 0);
|
|
30575
|
+
}
|
|
30576
|
+
|
|
30577
|
+
countSelectedTracks() {
|
|
30578
|
+
const selectedCount = this.tracks.filter(track => track.visible).length;
|
|
30579
|
+
return selectedCount + this.children.reduce((count, child) => count + child.countSelectedTracks(), 0);
|
|
30580
|
+
}
|
|
30581
|
+
|
|
30582
|
+
trim() {
|
|
30583
|
+
this.children = this.children.filter(child => !child.isEmpty());
|
|
30584
|
+
this.children.forEach(child => child.trim());
|
|
30585
|
+
}
|
|
30586
|
+
|
|
30587
|
+
setTrackVisibility(loadedTrackPaths) {
|
|
30588
|
+
this.tracks.forEach(track => {
|
|
30589
|
+
track.visible = loadedTrackPaths.has(track.url);
|
|
30590
|
+
});
|
|
30591
|
+
this.children.forEach(child => child.setTrackVisibility(loadedTrackPaths));
|
|
30592
|
+
}
|
|
30593
|
+
}
|
|
30594
|
+
|
|
30595
|
+
const supportedTypes = new Set([
|
|
30596
|
+
"bigbed", "bigwig", "biggenepred", "vcftabix", "refgene",
|
|
30597
|
+
"bam", "sampleinfo", "vcf.list", "ucscsnp", "bed", "tdf", "gff", "gff3", "gtf", "vcf", "vcfphasedtrio",
|
|
30598
|
+
"bigdbsnp", "rmask", "genepred", "wig", "bedgraph", "interact", "broadpeak", "narrowpeak", "gappedpeak",
|
|
30599
|
+
"gistic", "seg", "mut, bigrmsk"
|
|
30600
|
+
]);
|
|
30601
|
+
|
|
30602
|
+
const filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps",
|
|
30603
|
+
"cpgIslandExtUnmasked", "windowMasker"]);
|
|
30604
|
+
|
|
30605
|
+
class TrackDbHub {
|
|
30606
|
+
|
|
30607
|
+
constructor(trackStanzas, groupStanzas) {
|
|
30608
|
+
this.groupStanzas = groupStanzas;
|
|
30609
|
+
this.trackStanzas = trackStanzas;
|
|
30610
|
+
}
|
|
30611
|
+
|
|
30612
|
+
findCytobandURL() {
|
|
30613
|
+
for (const t of this.trackStanzas) {
|
|
30614
|
+
if (t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) {
|
|
30615
|
+
return t.getProperty("bigDataUrl")
|
|
30379
30616
|
}
|
|
30380
30617
|
}
|
|
30618
|
+
}
|
|
30381
30619
|
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30385
|
-
|
|
30386
|
-
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
|
|
30391
|
-
|
|
30392
|
-
|
|
30620
|
+
getSupportedTrackCount() {
|
|
30621
|
+
let count = 0;
|
|
30622
|
+
for (const t of this.trackStanzas) {
|
|
30623
|
+
if (!filterTracks.has(t.name) &&
|
|
30624
|
+
t.hasProperty("bigDataUrl") &&
|
|
30625
|
+
t.format &&
|
|
30626
|
+
supportedTypes.has(t.format.toLowerCase())) {
|
|
30627
|
+
count++;
|
|
30628
|
+
}
|
|
30629
|
+
}
|
|
30630
|
+
return count
|
|
30631
|
+
}
|
|
30632
|
+
|
|
30633
|
+
getGroupedTrackConfigurations() {
|
|
30634
|
+
|
|
30635
|
+
if (!this.groupTrackConfigs) {
|
|
30636
|
+
this.groupTrackConfigs = [];
|
|
30637
|
+
const trackContainers = new Map();
|
|
30393
30638
|
|
|
30394
|
-
|
|
30639
|
+
// create a container for tracks with no parent
|
|
30640
|
+
const nullContainer = new TrackConfigContainer('', '', 0, true);
|
|
30641
|
+
this.groupTrackConfigs.push(nullContainer);
|
|
30642
|
+
|
|
30643
|
+
const hasGroups = this.groupStanzas && this.groupStanzas.length > 0;
|
|
30644
|
+
if (hasGroups) {
|
|
30645
|
+
for (const groupStanza of this.groupStanzas) {
|
|
30646
|
+
const name = groupStanza.getProperty("name");
|
|
30647
|
+
const defaultOpen = groupStanza.getProperty("defaultIsClosed") === "0";
|
|
30648
|
+
const priority = groupStanza.hasProperty("priority") ? getPriority(groupStanza.getProperty("priority")) : Number.MAX_SAFE_INTEGER - 1;
|
|
30649
|
+
const container = new TrackConfigContainer(name, groupStanza.getProperty("label"), priority, defaultOpen);
|
|
30650
|
+
trackContainers.set(name, container);
|
|
30651
|
+
this.groupTrackConfigs.push(container);
|
|
30652
|
+
}
|
|
30653
|
+
}
|
|
30654
|
+
|
|
30655
|
+
for (let s of this.trackStanzas) {
|
|
30656
|
+
|
|
30657
|
+
const isContainer = (s.hasOwnProperty("superTrack") && !s.hasOwnProperty("bigDataUrl")) ||
|
|
30658
|
+
s.hasOwnProperty("compositeTrack") || s.hasOwnProperty("view") ||
|
|
30659
|
+
(s.hasOwnProperty("container") && s.getOwnProperty("container").equals("multiWig"));
|
|
30660
|
+
|
|
30661
|
+
// Find parent, if any. "group" containers can be implicit, all other types should be explicitly
|
|
30662
|
+
// defined before their children
|
|
30663
|
+
let parent;
|
|
30664
|
+
|
|
30665
|
+
if (s.hasOwnProperty("parent")) {
|
|
30666
|
+
parent = trackContainers.get(s.getOwnProperty("parent"));
|
|
30667
|
+
}
|
|
30668
|
+
|
|
30669
|
+
if (!parent && hasGroups && s.hasProperty("group")) {
|
|
30670
|
+
const groupName = s.getProperty("group");
|
|
30671
|
+
if (trackContainers.has(groupName)) {
|
|
30672
|
+
parent = trackContainers.get(groupName);
|
|
30673
|
+
} else {
|
|
30674
|
+
const container = new TrackConfigContainer(groupName, groupName, 1000, true);
|
|
30675
|
+
trackContainers.set(groupName, container);
|
|
30676
|
+
this.groupTrackConfigs.push(container);
|
|
30677
|
+
parent = container;
|
|
30678
|
+
}
|
|
30679
|
+
}
|
|
30680
|
+
|
|
30681
|
+
if (isContainer) {
|
|
30682
|
+
|
|
30683
|
+
const name = s.getProperty("track");
|
|
30684
|
+
const priority = s.hasProperty("priority") ? getPriority(s.getProperty("priority")) : Number.MAX_SAFE_INTEGER - 1;
|
|
30685
|
+
const defaultOpen = s.getProperty("defaultIsClosed") === "0";
|
|
30686
|
+
const longLabel = s.getOwnProperty("longLabel");
|
|
30687
|
+
const label = longLabel && longLabel.length < 50 ? longLabel : s.getOwnProperty("shortLabel");
|
|
30688
|
+
const container = new TrackConfigContainer(name, label, priority, defaultOpen);
|
|
30689
|
+
|
|
30690
|
+
if (trackContainers.has(name)) {
|
|
30691
|
+
throw new Error(`Duplicate track container: ${name}`)
|
|
30692
|
+
}
|
|
30693
|
+
trackContainers.set(name, container);
|
|
30694
|
+
|
|
30695
|
+
if (parent) {
|
|
30696
|
+
parent.children.push(container);
|
|
30697
|
+
} else {
|
|
30698
|
+
// No parent or a superTrack => promote to top level
|
|
30699
|
+
this.groupTrackConfigs.push(container);
|
|
30700
|
+
}
|
|
30701
|
+
} else if (!filterTracks.has(s.name) &&
|
|
30702
|
+
s.hasProperty("bigDataUrl") &&
|
|
30703
|
+
s.format &&
|
|
30704
|
+
supportedTypes.has(s.format.toLowerCase())) {
|
|
30705
|
+
|
|
30706
|
+
const trackConfig = this.#getTrackConfig(s);
|
|
30707
|
+
if (parent) {
|
|
30708
|
+
parent.tracks.push(trackConfig);
|
|
30709
|
+
} else {
|
|
30710
|
+
nullContainer.tracks.push(trackConfig);
|
|
30711
|
+
}
|
|
30712
|
+
}
|
|
30713
|
+
}
|
|
30714
|
+
|
|
30715
|
+
}
|
|
30716
|
+
|
|
30717
|
+
// Filter empty groups and sort
|
|
30718
|
+
this.groupTrackConfigs.forEach(c => c.trim());
|
|
30719
|
+
this.groupTrackConfigs = this.groupTrackConfigs.filter(t => !t.isEmpty());
|
|
30720
|
+
|
|
30721
|
+
this.groupTrackConfigs.sort((a, b) => a.priority - b.priority);
|
|
30722
|
+
return this.groupTrackConfigs
|
|
30395
30723
|
}
|
|
30396
30724
|
|
|
30397
|
-
|
|
30725
|
+
/**
|
|
30726
|
+
* Return an array of igv track config objects that satisfy the filter
|
|
30727
|
+
*/
|
|
30728
|
+
#getTracksConfigs(filter) {
|
|
30729
|
+
return this.trackStanzas.filter(t => {
|
|
30730
|
+
return supportedTypes.has(t.format) && t.hasProperty("bigDataUrl") && (!filter || filter(t))
|
|
30731
|
+
})
|
|
30732
|
+
.map(t => this.#getTrackConfig(t))
|
|
30733
|
+
}
|
|
30398
30734
|
|
|
30399
|
-
this.url = url;
|
|
30400
30735
|
|
|
30401
|
-
|
|
30402
|
-
|
|
30736
|
+
/** example
|
|
30737
|
+
* track gc5Base
|
|
30738
|
+
* shortLabel GC Percent
|
|
30739
|
+
* longLabel GC Percent in 5-Base Windows
|
|
30740
|
+
* group map
|
|
30741
|
+
* visibility full
|
|
30742
|
+
* autoScale Off
|
|
30743
|
+
* maxHeightPixels 128:36:16
|
|
30744
|
+
* graphTypeDefault Bar
|
|
30745
|
+
* gridDefault OFF
|
|
30746
|
+
* windowingFunction Mean
|
|
30747
|
+
* color 0,0,0
|
|
30748
|
+
* altColor 128,128,128
|
|
30749
|
+
* viewLimits 30:70
|
|
30750
|
+
* type bigWig 0 100
|
|
30751
|
+
* bigDataUrl bbi/GCA_011100615.1_Macaca_fascicularis_6.0.gc5Base.bw
|
|
30752
|
+
* html html/GCA_011100615.1_Macaca_fascicularis_6.0.gc5Base
|
|
30753
|
+
* @param t
|
|
30754
|
+
*/
|
|
30755
|
+
#getTrackConfig(t) {
|
|
30403
30756
|
|
|
30404
|
-
|
|
30405
|
-
|
|
30406
|
-
|
|
30407
|
-
|
|
30408
|
-
|
|
30757
|
+
const format = t.format;
|
|
30758
|
+
|
|
30759
|
+
const config = {
|
|
30760
|
+
"id": t.getProperty("track"),
|
|
30761
|
+
"name": t.getProperty("shortLabel"),
|
|
30762
|
+
"format": format,
|
|
30763
|
+
"url": t.getProperty("bigDataUrl"),
|
|
30764
|
+
"displayMode": t.displayMode,
|
|
30765
|
+
};
|
|
30766
|
+
|
|
30767
|
+
if ("vcfTabix" === format) {
|
|
30768
|
+
config.indexURL = config.url + ".tbi";
|
|
30769
|
+
}
|
|
30770
|
+
|
|
30771
|
+
if (t.hasProperty("longLabel") && t.hasProperty("html")) {
|
|
30772
|
+
if (config.description) config.description += "<br/>";
|
|
30773
|
+
config.description =
|
|
30774
|
+
`<a target="_blank" href="${t.getProperty("html")}">${t.getProperty("longLabel")}</a>`;
|
|
30775
|
+
} else if (t.hasProperty("longLabel")) {
|
|
30776
|
+
config.description = t.getProperty("longLabel");
|
|
30777
|
+
}
|
|
30778
|
+
|
|
30779
|
+
if (t.hasProperty("autoScale")) {
|
|
30780
|
+
config.autoscale = t.getProperty("autoScale").toLowerCase() === "on";
|
|
30781
|
+
}
|
|
30782
|
+
if (t.hasProperty("maxHeightPixels")) {
|
|
30783
|
+
const tokens = t.getProperty("maxHeightPixels").split(":");
|
|
30784
|
+
config.maxHeight = Number.parseInt(tokens[0]);
|
|
30785
|
+
config.height = Number.parseInt(tokens[1]);
|
|
30786
|
+
config.minHeight = Number.parseInt(tokens[2]);
|
|
30787
|
+
}
|
|
30788
|
+
// TODO -- graphTypeDefault
|
|
30789
|
+
// TODO -- windowingFunction
|
|
30790
|
+
if (t.hasProperty("color")) {
|
|
30791
|
+
const c = t.getProperty("color");
|
|
30792
|
+
config.color = c.indexOf(",") > 0 ? `rgb(${c})` : c;
|
|
30793
|
+
}
|
|
30794
|
+
if (t.hasProperty("altColor")) {
|
|
30795
|
+
const c = t.getProperty("altColor");
|
|
30796
|
+
config.altColor = c.indexOf(",") > 0 ? `rgb(${c})` : c;
|
|
30797
|
+
}
|
|
30798
|
+
if (t.hasProperty("viewLimits")) {
|
|
30799
|
+
const tokens = t.getProperty("viewLimits").split(":");
|
|
30800
|
+
let min, max;
|
|
30801
|
+
if (tokens.length > 1) {
|
|
30802
|
+
min = Number.parseInt(tokens[0]);
|
|
30803
|
+
max = Number.parseInt(tokens[1]);
|
|
30804
|
+
}
|
|
30805
|
+
if (Number.isNaN(max) || Number.isNaN(min)) {
|
|
30806
|
+
console.warn(`Unexpected viewLimits value in track line: ${properties["viewLimits"]}`);
|
|
30807
|
+
} else {
|
|
30808
|
+
config.min = min;
|
|
30809
|
+
config.max = max;
|
|
30810
|
+
}
|
|
30811
|
+
|
|
30812
|
+
}
|
|
30813
|
+
if (t.hasProperty("itemRgb")) ;
|
|
30814
|
+
if ("hide" === t.getProperty("visibility")) {
|
|
30815
|
+
// TODO -- this not supported yet
|
|
30816
|
+
config.visible = false;
|
|
30409
30817
|
}
|
|
30410
|
-
if (
|
|
30411
|
-
|
|
30818
|
+
if (t.hasProperty("url")) {
|
|
30819
|
+
config.infoURL = t.getProperty("url");
|
|
30412
30820
|
}
|
|
30413
|
-
if (
|
|
30414
|
-
|
|
30821
|
+
if (t.hasProperty("searchIndex")) {
|
|
30822
|
+
config.searchIndex = t.getProperty("searchIndex");
|
|
30823
|
+
}
|
|
30824
|
+
if (t.hasProperty("searchTrix")) {
|
|
30825
|
+
config.trixURL = t.getProperty("searchTrix");
|
|
30826
|
+
}
|
|
30827
|
+
if (t.hasProperty("html")) {
|
|
30828
|
+
config.html = t.getProperty("html");
|
|
30415
30829
|
}
|
|
30416
30830
|
|
|
30417
|
-
|
|
30418
|
-
|
|
30419
|
-
this.
|
|
30420
|
-
|
|
30421
|
-
|
|
30831
|
+
if (t.hasProperty("group")) {
|
|
30832
|
+
config._group = t.getProperty("group");
|
|
30833
|
+
if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
|
|
30834
|
+
const nextPriority = this.groupPriorityMap.get(config._group) + 1;
|
|
30835
|
+
config.order = nextPriority;
|
|
30836
|
+
this.groupPriorityMap.set(config._group, nextPriority);
|
|
30837
|
+
}
|
|
30838
|
+
}
|
|
30839
|
+
|
|
30840
|
+
if (t.hasProperty("metadata")) {
|
|
30841
|
+
config.attributes = parseMetadata(t.getProperty("metadata"));
|
|
30842
|
+
}
|
|
30843
|
+
|
|
30844
|
+
if (t.hasProperty("maxWindowToDraw")) {
|
|
30845
|
+
let maxWindowToDraw = parseInt(t.getProperty("maxWindowToDraw"), 10);
|
|
30846
|
+
if (maxWindowToDraw > Number.MAX_SAFE_INTEGER) {
|
|
30847
|
+
maxWindowToDraw = Number.MAX_SAFE_INTEGER;
|
|
30848
|
+
}
|
|
30849
|
+
config.visibilityWindow = maxWindowToDraw;
|
|
30422
30850
|
}
|
|
30423
30851
|
|
|
30424
|
-
//
|
|
30425
|
-
|
|
30426
|
-
|
|
30427
|
-
if (
|
|
30428
|
-
|
|
30852
|
+
// IGV does not support "maxWindowCoverage" in the same way as UCSC. Use to limit visibility window
|
|
30853
|
+
if (t.hasProperty("maxWindowCoverage")) {
|
|
30854
|
+
let maxWindowToDraw = parseInt(t.getProperty("maxWindowCoverage"), 10);
|
|
30855
|
+
if (maxWindowToDraw > Number.MAX_SAFE_INTEGER) {
|
|
30856
|
+
maxWindowToDraw = Number.MAX_SAFE_INTEGER;
|
|
30429
30857
|
}
|
|
30858
|
+
config.visibilityWindow = maxWindowToDraw;
|
|
30430
30859
|
}
|
|
30431
30860
|
|
|
30432
|
-
|
|
30433
|
-
|
|
30434
|
-
|
|
30435
|
-
|
|
30436
|
-
|
|
30437
|
-
|
|
30861
|
+
return config
|
|
30862
|
+
}
|
|
30863
|
+
}
|
|
30864
|
+
|
|
30865
|
+
function htmlText(html) {
|
|
30866
|
+
// Assumes a pattern like <span style="color:#C58DAA">Digestive</span>
|
|
30867
|
+
const idx1 = html.indexOf('>');
|
|
30868
|
+
const idx2 = html.indexOf('<', idx1);
|
|
30869
|
+
if (idx1 > 0 && idx2 > idx1) {
|
|
30870
|
+
return html.substring(idx1 + 1, idx2)
|
|
30871
|
+
} else {
|
|
30872
|
+
return html
|
|
30873
|
+
}
|
|
30874
|
+
}
|
|
30875
|
+
|
|
30876
|
+
/**
|
|
30877
|
+
* Return the priority for the group. The priority format is uncertain, but extends to at least 2 levels (e.g. 3.4).
|
|
30878
|
+
* Ignore levels > 3
|
|
30879
|
+
*
|
|
30880
|
+
* @param {string} priorityString Priority as a string (e.g. 3.4)
|
|
30881
|
+
* @return {number} A priority as an integer
|
|
30882
|
+
*/
|
|
30883
|
+
function getPriority(priorityString) {
|
|
30884
|
+
try {
|
|
30885
|
+
const tokens = priorityString.trim().split(".");
|
|
30886
|
+
let p = parseInt(tokens[0], 10) * 100;
|
|
30887
|
+
if (tokens.length > 1) {
|
|
30888
|
+
p += parseInt(tokens[1], 10) * 10;
|
|
30889
|
+
}
|
|
30890
|
+
if (tokens.length > 2) {
|
|
30891
|
+
p += parseInt(tokens[2], 10);
|
|
30892
|
+
}
|
|
30893
|
+
return p
|
|
30894
|
+
} catch (e) {
|
|
30895
|
+
console.error(`Error parsing priority string: ${priorityString}`, e);
|
|
30896
|
+
return Number.MAX_SAFE_INTEGER
|
|
30897
|
+
}
|
|
30898
|
+
}
|
|
30899
|
+
|
|
30900
|
+
function parseMetadata(metadata) {
|
|
30901
|
+
const attrs = new Map();
|
|
30902
|
+
let lastMetdataLengh = -1;
|
|
30903
|
+
while (metadata && metadata.length > 0) {
|
|
30904
|
+
try {
|
|
30905
|
+
if (metadata.length === lastMetdataLengh) {
|
|
30906
|
+
break
|
|
30907
|
+
}
|
|
30908
|
+
lastMetdataLengh = metadata.length;
|
|
30909
|
+
let idx = metadata.indexOf("=");
|
|
30910
|
+
if (idx === -1 || idx === metadata.length - 1) {
|
|
30911
|
+
break
|
|
30912
|
+
}
|
|
30913
|
+
let idx2;
|
|
30914
|
+
const key = capitalize(stripQuotes$2(metadata.substring(0, idx)));
|
|
30915
|
+
let value;
|
|
30916
|
+
|
|
30917
|
+
if (metadata.charAt(idx + 1) === '"') {
|
|
30918
|
+
idx++;
|
|
30919
|
+
idx2 = metadata.indexOf('" ', idx + 1);
|
|
30920
|
+
value = idx2 > 0 ? metadata.substring(idx + 1, idx2) : metadata.substring(idx + 1);
|
|
30921
|
+
idx2++;
|
|
30922
|
+
} else {
|
|
30923
|
+
idx2 = metadata.indexOf(" ", idx + 1);
|
|
30924
|
+
if (idx2 === -1) {
|
|
30925
|
+
idx2 = metadata.length;
|
|
30438
30926
|
}
|
|
30927
|
+
value = metadata.substring(idx + 1, idx2);
|
|
30928
|
+
}
|
|
30929
|
+
value = stripQuotes$2(value);
|
|
30930
|
+
if (value.endsWith('"')) {
|
|
30931
|
+
value = value.substring(0, value.length - 1);
|
|
30932
|
+
}
|
|
30933
|
+
if (value.startsWith("<") && value.endsWith(">")) {
|
|
30934
|
+
value = htmlText(value);
|
|
30439
30935
|
}
|
|
30936
|
+
attrs.set(key, value);
|
|
30937
|
+
if (idx2 === metadata.length) {
|
|
30938
|
+
break
|
|
30939
|
+
}
|
|
30940
|
+
metadata = idx2 > 0 ? metadata.substring(idx2 + 1).trim() : "";
|
|
30941
|
+
} catch (e) {
|
|
30942
|
+
// We don't want to fail parsing the hub due to a failure parsing metadata. Also, we don't want to
|
|
30943
|
+
// overwhelm the log. Metadata is of marginal importance in IGV.
|
|
30440
30944
|
}
|
|
30441
30945
|
}
|
|
30946
|
+
return attrs
|
|
30947
|
+
}
|
|
30948
|
+
|
|
30949
|
+
/*
|
|
30950
|
+
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
30951
|
+
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
30952
|
+
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
30953
|
+
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
30954
|
+
*/
|
|
30955
|
+
|
|
30956
|
+
const idMappings = new Map([
|
|
30957
|
+
["hg38", "GCF_000001405.40"],
|
|
30958
|
+
["mm39", "GCF_000001635.27"],
|
|
30959
|
+
["mm10", "GCF_000001635.26"],
|
|
30960
|
+
["bosTau9", "GCF_002263795.1"],
|
|
30961
|
+
["canFam4", "GCF_011100685.1"],
|
|
30962
|
+
["canFam6", "GCF_000002285.5"],
|
|
30963
|
+
["ce11", "GCF_000002985.6"],
|
|
30964
|
+
["dm6", "GCF_000001215.4"],
|
|
30965
|
+
["galGal6", "GCF_000002315.6"],
|
|
30966
|
+
["gorGor6", "GCF_008122165.1"],
|
|
30967
|
+
["macFas5", "GCA_000364345.1"],
|
|
30968
|
+
["panTro6", "GCA_002880755.3"],
|
|
30969
|
+
["rn6", "GCF_000001895.5"],
|
|
30970
|
+
["rn7", "GCF_015227675.2"],
|
|
30971
|
+
["sacCer3", "GCF_000146045.2"],
|
|
30972
|
+
["sacCer2", "GCF_000146045.2"],
|
|
30973
|
+
["susScr11", "GCF_000003025.6"],
|
|
30974
|
+
["taeGut1", "GCF_000002275.3"],
|
|
30975
|
+
["tetNig2", "GCF_000002275.3"],
|
|
30976
|
+
["xenTro10", "GCF_000002035.6"],
|
|
30977
|
+
["xenTro9", "GCF_000002035.6"],
|
|
30978
|
+
["tair10", "GCF_000001735.4"],
|
|
30979
|
+
]);
|
|
30442
30980
|
|
|
30443
|
-
|
|
30444
|
-
|
|
30981
|
+
class Hub {
|
|
30982
|
+
|
|
30983
|
+
static supportedTypes = new Set(["bigBed", "bigWig", "bigGenePred", "vcfTabix"])
|
|
30984
|
+
static filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps",
|
|
30985
|
+
"cpgIslandExtUnmasked", "windowMasker"])
|
|
30986
|
+
|
|
30987
|
+
constructor(url, hubStanza, genomeStanzas, trackStanzas, groupStanzas) {
|
|
30988
|
+
|
|
30989
|
+
this.url = url;
|
|
30990
|
+
this.hubStanza = hubStanza;
|
|
30991
|
+
this.genomeStanzas = genomeStanzas;
|
|
30992
|
+
this.trackStanzas = trackStanzas;
|
|
30993
|
+
this.groupStanzas = groupStanzas;
|
|
30994
|
+
this.trackHubMap = new Map();
|
|
30995
|
+
|
|
30996
|
+
// trackStanzas will not be null if this is a "onefile" hub
|
|
30997
|
+
if (trackStanzas) {
|
|
30998
|
+
const genomeId = genomeStanzas[0].getProperty("genome"); // Assumption here this is a single genome hub
|
|
30999
|
+
this.trackHubMap.set(genomeId, new TrackDbHub(trackStanzas, groupStanzas));
|
|
31000
|
+
}
|
|
30445
31001
|
}
|
|
30446
31002
|
|
|
31003
|
+
|
|
31004
|
+
getName() {
|
|
31005
|
+
return this.hubStanza.getProperty("hub")
|
|
31006
|
+
}
|
|
31007
|
+
|
|
31008
|
+
getShortLabel() {
|
|
31009
|
+
return this.hubStanza.getProperty("shortLabel")
|
|
31010
|
+
}
|
|
31011
|
+
|
|
31012
|
+
getLongLabel() {
|
|
31013
|
+
return this.hubStanza.getProperty("longLabel")
|
|
31014
|
+
}
|
|
31015
|
+
|
|
31016
|
+
getDescriptionUrl() {
|
|
31017
|
+
return this.hubStanza.getProperty("descriptionUrl")
|
|
31018
|
+
}
|
|
31019
|
+
|
|
31020
|
+
|
|
30447
31021
|
/* Example genome stanza
|
|
30448
31022
|
genome GCF_000186305.1
|
|
30449
31023
|
taxId 176946
|
|
@@ -30462,133 +31036,126 @@ transBlat dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30462
31036
|
isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
30463
31037
|
*/
|
|
30464
31038
|
|
|
30465
|
-
getGenomeConfig(
|
|
30466
|
-
|
|
31039
|
+
getGenomeConfig(genomeId) {
|
|
31040
|
+
|
|
31041
|
+
const genomeStanza = genomeId ? this.genomeStanzas.find(s => s.getProperty("genome") === genomeId) : this.genomeStanzas[0];
|
|
31042
|
+
if (!genomeStanza) {
|
|
31043
|
+
throw new Error(`Genome not found in hub: ${genomeId}`)
|
|
31044
|
+
}
|
|
31045
|
+
return this.#getGenomeConfig(genomeStanza)
|
|
31046
|
+
}
|
|
31047
|
+
|
|
31048
|
+
#getGenomeConfig(genomeStanza) {
|
|
30467
31049
|
|
|
30468
|
-
const id =
|
|
31050
|
+
const id = genomeStanza.getProperty("genome");
|
|
30469
31051
|
const gsName =
|
|
30470
31052
|
this.hubStanza.getProperty("shortLabel") ||
|
|
30471
|
-
|
|
30472
|
-
|
|
30473
|
-
|
|
31053
|
+
genomeStanza.getProperty("scientificName") ||
|
|
31054
|
+
genomeStanza.getProperty("organism") ||
|
|
31055
|
+
genomeStanza.getProperty("description");
|
|
30474
31056
|
const name = gsName + (gsName ? ` (${id})` : ` ${id}`);
|
|
30475
31057
|
|
|
30476
31058
|
const config = {
|
|
30477
|
-
|
|
31059
|
+
|
|
30478
31060
|
id: id,
|
|
30479
31061
|
name: name,
|
|
30480
|
-
twoBitURL:
|
|
31062
|
+
twoBitURL: genomeStanza.getProperty("twoBitPath"),
|
|
30481
31063
|
nameSet: "ucsc",
|
|
31064
|
+
hubs: [this.url]
|
|
30482
31065
|
};
|
|
30483
31066
|
|
|
30484
|
-
if (
|
|
30485
|
-
config.chromSizesURL =
|
|
31067
|
+
if (genomeStanza.hasProperty("chromSizes")) {
|
|
31068
|
+
config.chromSizesURL = genomeStanza.getProperty("chromSizes");
|
|
30486
31069
|
} else {
|
|
30487
31070
|
config.wholeGenomeView = false;
|
|
30488
31071
|
config.showChromosomeWidget = false;
|
|
30489
31072
|
}
|
|
30490
31073
|
|
|
30491
|
-
if (
|
|
30492
|
-
const hubLocus =
|
|
31074
|
+
if (genomeStanza.hasProperty("defaultPos")) {
|
|
31075
|
+
const hubLocus = genomeStanza.getProperty("defaultPos");
|
|
30493
31076
|
// Strip out coordinates => whole chromosome view
|
|
30494
|
-
if (hubLocus) {
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
}
|
|
31077
|
+
// if (hubLocus) {
|
|
31078
|
+
// const idx = hubLocus.lastIndexOf(":")
|
|
31079
|
+
// config.locus = idx > 0 ? hubLocus.substring(0, idx) : hubLocus
|
|
31080
|
+
// }
|
|
31081
|
+
config.locus = hubLocus;
|
|
30498
31082
|
}
|
|
30499
31083
|
|
|
30500
|
-
if (
|
|
30501
|
-
config.blat =
|
|
30502
|
-
}
|
|
30503
|
-
if (this.genomeStanza.hasProperty("chromAliasBb")) {
|
|
30504
|
-
config.chromAliasBbURL = this.baseURL + this.genomeStanza.getProperty("chromAliasBb");
|
|
31084
|
+
if (genomeStanza.hasProperty("blat")) {
|
|
31085
|
+
config.blat = genomeStanza.getProperty("blat");
|
|
30505
31086
|
}
|
|
30506
|
-
if (
|
|
30507
|
-
config.
|
|
31087
|
+
if (genomeStanza.hasProperty("chromAliasBb")) {
|
|
31088
|
+
config.chromAliasBbURL = genomeStanza.getProperty("chromAliasBb");
|
|
30508
31089
|
}
|
|
30509
|
-
if (
|
|
30510
|
-
config.
|
|
31090
|
+
if (genomeStanza.hasProperty("chromAlias")) {
|
|
31091
|
+
config.aliasURL = genomeStanza.getProperty("chromAlias");
|
|
30511
31092
|
}
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
config.twoBitBptURL = this.baseURL + this.genomeStanza.getProperty("twoBitBptUrl");
|
|
31093
|
+
if (genomeStanza.hasProperty("twoBitBptURL")) {
|
|
31094
|
+
config.twoBitBptURL = genomeStanza.getProperty("twoBitBptURL");
|
|
30515
31095
|
}
|
|
30516
31096
|
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
config.chromSizesURL = this.baseURL + this.genomeStanza.getProperty("chromSizes");
|
|
31097
|
+
if (genomeStanza.hasProperty("twoBitBptUrl")) {
|
|
31098
|
+
config.twoBitBptURL = genomeStanza.getProperty("twoBitBptUrl");
|
|
30520
31099
|
}
|
|
30521
31100
|
|
|
30522
31101
|
if (this.hubStanza.hasProperty("longLabel")) {
|
|
30523
31102
|
config.description = this.hubStanza.getProperty("longLabel").replace("/", "\n");
|
|
30524
31103
|
} else {
|
|
30525
31104
|
config.description = config.id;
|
|
30526
|
-
if (
|
|
30527
|
-
config.description += `\n${
|
|
31105
|
+
if (genomeStanza.hasProperty("description")) {
|
|
31106
|
+
config.description += `\n${genomeStanza.getProperty("description")}`;
|
|
30528
31107
|
}
|
|
30529
|
-
if (
|
|
30530
|
-
config.description += `\n${
|
|
31108
|
+
if (genomeStanza.hasProperty("organism")) {
|
|
31109
|
+
config.description += `\n${genomeStanza.getProperty("organism")}`;
|
|
30531
31110
|
}
|
|
30532
|
-
if (
|
|
30533
|
-
config.description += `\n${
|
|
31111
|
+
if (genomeStanza.hasProperty("scientificName")) {
|
|
31112
|
+
config.description += `\n${genomeStanza.getProperty("scientificName")}`;
|
|
30534
31113
|
}
|
|
30535
31114
|
|
|
30536
|
-
if (
|
|
30537
|
-
config.infoURL =
|
|
31115
|
+
if (genomeStanza.hasProperty("htmlPath")) {
|
|
31116
|
+
config.infoURL = genomeStanza.getProperty("htmlPath");
|
|
30538
31117
|
}
|
|
30539
31118
|
}
|
|
30540
31119
|
|
|
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
31120
|
// Tracks.
|
|
30557
31121
|
const filter = (t) => !Hub.filterTracks.has(t.name) && "hide" !== t.getProperty("visibility");
|
|
30558
31122
|
config.tracks = this.#getTracksConfigs(filter);
|
|
30559
31123
|
|
|
30560
|
-
|
|
30561
31124
|
return config
|
|
30562
31125
|
}
|
|
30563
31126
|
|
|
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
|
-
}
|
|
31127
|
+
async getGroupedTrackConfigurations(genomeId) {
|
|
31128
|
+
let trackHub = await this.#getTrackDbHub(genomeId);
|
|
31129
|
+
if (!trackHub && idMappings.has(genomeId)) {
|
|
31130
|
+
trackHub = await this.#getTrackDbHub(idMappings.get(genomeId));
|
|
30576
31131
|
}
|
|
31132
|
+
if (!trackHub) {
|
|
31133
|
+
console.log(`Warning: no trackDB found for genomeId ${genomeId}.`);
|
|
31134
|
+
}
|
|
31135
|
+
return trackHub ? trackHub.getGroupedTrackConfigurations() : []
|
|
31136
|
+
}
|
|
30577
31137
|
|
|
30578
|
-
|
|
30579
|
-
|
|
30580
|
-
|
|
30581
|
-
|
|
30582
|
-
|
|
30583
|
-
|
|
30584
|
-
|
|
30585
|
-
|
|
30586
|
-
|
|
31138
|
+
async #getTrackDbHub(genomeId) {
|
|
31139
|
+
let trackHub = this.trackHubMap.get(genomeId);
|
|
31140
|
+
if (!trackHub) {
|
|
31141
|
+
for (let stanza of this.genomeStanzas) {
|
|
31142
|
+
if (genomeId === stanza.getProperty("genome")) {
|
|
31143
|
+
try {
|
|
31144
|
+
const trackDbURL = stanza.getProperty("trackDb");
|
|
31145
|
+
const trackStanzas = await loadStanzas(trackDbURL);
|
|
31146
|
+
trackHub = new TrackDbHub(trackStanzas, this.groupStanzas);
|
|
31147
|
+
this.trackHubMap.set(genomeId, trackHub);
|
|
31148
|
+
} catch (error) {
|
|
31149
|
+
console.error(`Error loading trackDb file: ${stanza.getProperty("trackDb")}`, error);
|
|
31150
|
+
}
|
|
31151
|
+
break
|
|
31152
|
+
}
|
|
30587
31153
|
}
|
|
30588
|
-
}
|
|
30589
|
-
|
|
31154
|
+
}
|
|
31155
|
+
return trackHub
|
|
30590
31156
|
}
|
|
30591
31157
|
|
|
31158
|
+
|
|
30592
31159
|
/**
|
|
30593
31160
|
* Return an array of igv track config objects that satisfy the filter
|
|
30594
31161
|
*/
|
|
@@ -30626,7 +31193,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30626
31193
|
"id": t.getProperty("track"),
|
|
30627
31194
|
"name": t.getProperty("shortLabel"),
|
|
30628
31195
|
"format": format,
|
|
30629
|
-
"url":
|
|
31196
|
+
"url": t.getProperty("bigDataUrl"),
|
|
30630
31197
|
"displayMode": t.displayMode,
|
|
30631
31198
|
};
|
|
30632
31199
|
|
|
@@ -30637,7 +31204,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30637
31204
|
if (t.hasProperty("longLabel") && t.hasProperty("html")) {
|
|
30638
31205
|
if (config.description) config.description += "<br/>";
|
|
30639
31206
|
config.description =
|
|
30640
|
-
`<a target="_blank" href="${
|
|
31207
|
+
`<a target="_blank" href="${t.getProperty("html")}">${t.getProperty("longLabel")}</a>`;
|
|
30641
31208
|
} else if (t.hasProperty("longLabel")) {
|
|
30642
31209
|
config.description = t.getProperty("longLabel");
|
|
30643
31210
|
}
|
|
@@ -30669,7 +31236,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30669
31236
|
max = Number.parseInt(tokens[1]);
|
|
30670
31237
|
}
|
|
30671
31238
|
if (Number.isNaN(max) || Number.isNaN(min)) {
|
|
30672
|
-
console.warn(`Unexpected viewLimits value in track line: ${
|
|
31239
|
+
console.warn(`Unexpected viewLimits value in track line: ${t.getProperty("viewLimits")}`);
|
|
30673
31240
|
} else {
|
|
30674
31241
|
config.min = min;
|
|
30675
31242
|
config.max = max;
|
|
@@ -30688,15 +31255,15 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30688
31255
|
config.searchIndex = t.getProperty("searchIndex");
|
|
30689
31256
|
}
|
|
30690
31257
|
if (t.hasProperty("searchTrix")) {
|
|
30691
|
-
config.trixURL =
|
|
31258
|
+
config.trixURL = t.getProperty("searchTrix");
|
|
30692
31259
|
}
|
|
30693
31260
|
|
|
30694
31261
|
if (t.hasProperty("group")) {
|
|
30695
|
-
config.
|
|
30696
|
-
if (this.groupPriorityMap && this.groupPriorityMap.has(config.
|
|
30697
|
-
const nextPriority = this.groupPriorityMap.get(config.
|
|
31262
|
+
config._group = t.getProperty("group");
|
|
31263
|
+
if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
|
|
31264
|
+
const nextPriority = this.groupPriorityMap.get(config._group) + 1;
|
|
30698
31265
|
config.order = nextPriority;
|
|
30699
|
-
this.groupPriorityMap.set(config.
|
|
31266
|
+
this.groupPriorityMap.set(config._group, nextPriority);
|
|
30700
31267
|
}
|
|
30701
31268
|
}
|
|
30702
31269
|
|
|
@@ -30705,96 +31272,92 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
|
|
|
30705
31272
|
|
|
30706
31273
|
}
|
|
30707
31274
|
|
|
30708
|
-
|
|
30709
|
-
|
|
30710
|
-
|
|
30711
|
-
|
|
31275
|
+
/*
|
|
31276
|
+
https://genomewiki.ucsc.edu/index.php/Assembly_Hubs
|
|
31277
|
+
https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html
|
|
31278
|
+
https://genome.ucsc.edu/goldenPath/help/hgTrackHubHelp
|
|
31279
|
+
https://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html
|
|
31280
|
+
*/
|
|
30712
31281
|
|
|
30713
|
-
|
|
31282
|
+
const urlProperties = new Set(["descriptionUrl", "desriptionUrl",
|
|
31283
|
+
"twoBitPath", "blat", "chromAliasBb", "twoBitBptURL", "twoBitBptUrl", "htmlPath", "bigDataUrl",
|
|
31284
|
+
"genomesFile", "trackDb", "groups", "include", "html", "searchTrix", "groups",
|
|
31285
|
+
"chromSizes"]);
|
|
30714
31286
|
|
|
30715
|
-
properties = new Map()
|
|
30716
31287
|
|
|
30717
|
-
|
|
30718
|
-
this.type = type;
|
|
30719
|
-
this.name = name;
|
|
30720
|
-
}
|
|
31288
|
+
const hubCache = new Map();
|
|
30721
31289
|
|
|
30722
|
-
|
|
30723
|
-
|
|
31290
|
+
async function loadHub(url) {
|
|
31291
|
+
if (hubCache.has(url)) {
|
|
31292
|
+
return hubCache.get(url)
|
|
30724
31293
|
}
|
|
30725
31294
|
|
|
30726
|
-
|
|
30727
|
-
|
|
30728
|
-
|
|
30729
|
-
} else if (this.parent) {
|
|
30730
|
-
return this.parent.getProperty(key)
|
|
30731
|
-
} else {
|
|
30732
|
-
return undefined
|
|
30733
|
-
}
|
|
31295
|
+
const stanzas = await loadStanzas(url);
|
|
31296
|
+
if (stanzas.length < 1) {
|
|
31297
|
+
throw new Error("Empty hub file")
|
|
30734
31298
|
}
|
|
30735
31299
|
|
|
30736
|
-
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
} else if (this.parent) {
|
|
30740
|
-
return this.parent.hasProperty(key)
|
|
30741
|
-
} else {
|
|
30742
|
-
return false
|
|
30743
|
-
}
|
|
31300
|
+
const hubStanza = stanzas[0];
|
|
31301
|
+
if (hubStanza.type !== "hub") {
|
|
31302
|
+
throw new Error("First stanza must be a hub stanza")
|
|
30744
31303
|
}
|
|
30745
31304
|
|
|
30746
|
-
|
|
30747
|
-
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
|
|
31305
|
+
let genomeStanzas;
|
|
31306
|
+
let trackStanzas;
|
|
31307
|
+
if (hubStanza.getProperty("useOneFile") === "on") {
|
|
31308
|
+
// This is a "onefile" hub, all stanzas are in the same file
|
|
31309
|
+
if (stanzas[1].type !== "genome") {
|
|
31310
|
+
throw new Error("Unexpected hub file -- expected 'genome' stanza but found " + stanzas[1].type)
|
|
30751
31311
|
}
|
|
30752
|
-
|
|
30753
|
-
|
|
31312
|
+
const genomeStanza = stanzas[1];
|
|
31313
|
+
genomeStanzas = [genomeStanza];
|
|
31314
|
+
trackStanzas = stanzas.slice(2);
|
|
30754
31315
|
|
|
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"
|
|
31316
|
+
// If this is an assembly check chromSizes. This file can be very large, and not needed if whole genome view
|
|
31317
|
+
// is not enabled. Remove it if > 100 kb
|
|
31318
|
+
if (genomeStanza.hasOwnProperty("chromSizes")) {
|
|
31319
|
+
const chromSizes = genomeStanza.getProperty("chromSizes");
|
|
31320
|
+
try {
|
|
31321
|
+
const contentLength = await igvxhr.getContentLength(chromSizes);
|
|
31322
|
+
if (contentLength > 100000) {
|
|
31323
|
+
genomeStanza.removeProperty("chromSizes");
|
|
31324
|
+
}
|
|
31325
|
+
} catch (e) {
|
|
31326
|
+
console.error(`Error getting content length for chromSizes ${chromSizes}`, e);
|
|
30773
31327
|
}
|
|
30774
|
-
}
|
|
30775
|
-
}
|
|
30776
|
-
}
|
|
30777
31328
|
|
|
31329
|
+
}
|
|
30778
31330
|
|
|
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
|
|
31331
|
+
} else {
|
|
31332
|
+
if (!hubStanza.hasProperty("genomesFile")) {
|
|
31333
|
+
throw new Error("hub.txt must specify 'genomesFile'")
|
|
30792
31334
|
}
|
|
30793
|
-
|
|
30794
|
-
return null
|
|
31335
|
+
genomeStanzas = await loadStanzas(hubStanza.getProperty("genomesFile"));
|
|
30795
31336
|
}
|
|
31337
|
+
|
|
31338
|
+
// Load group files for all genomes, if any.
|
|
31339
|
+
const uniqGroupURLs = new Set();
|
|
31340
|
+
genomeStanzas.forEach(s => {
|
|
31341
|
+
const groupURL = s.getProperty("groups");
|
|
31342
|
+
if (groupURL) uniqGroupURLs.add(groupURL);
|
|
31343
|
+
|
|
31344
|
+
});
|
|
31345
|
+
const groupStanzas = [];
|
|
31346
|
+
const groupPromises = Array.from(uniqGroupURLs).map(async url => {
|
|
31347
|
+
const stanza = await loadStanzas(url);
|
|
31348
|
+
return stanza
|
|
31349
|
+
});
|
|
31350
|
+
const groupResults = await Promise.all(groupPromises);
|
|
31351
|
+
groupResults.forEach(stanza => groupStanzas.push(...stanza));
|
|
31352
|
+
|
|
31353
|
+
const hub = new Hub(url, hubStanza, genomeStanzas, trackStanzas, groupStanzas);
|
|
31354
|
+
|
|
31355
|
+
hubCache.set(url, hub);
|
|
31356
|
+
|
|
31357
|
+
return hub
|
|
30796
31358
|
}
|
|
30797
31359
|
|
|
31360
|
+
|
|
30798
31361
|
/**
|
|
30799
31362
|
* Parse a UCSC file
|
|
30800
31363
|
* @param url
|
|
@@ -30802,38 +31365,92 @@ async function getContentLength(url) {
|
|
|
30802
31365
|
*/
|
|
30803
31366
|
async function loadStanzas(url) {
|
|
30804
31367
|
|
|
30805
|
-
const
|
|
30806
|
-
const
|
|
31368
|
+
const idx = url.lastIndexOf("/");
|
|
31369
|
+
const baseURL = url.substring(0, idx + 1);
|
|
31370
|
+
const host = getHost(url);
|
|
31371
|
+
|
|
31372
|
+
//const response = await fetch(url)
|
|
31373
|
+
const data = await igvxhr.loadString(url, {}); //await response.text()
|
|
30807
31374
|
const lines = data.split(/\n|\r\n|\r/g);
|
|
30808
31375
|
|
|
30809
31376
|
const nodes = [];
|
|
30810
31377
|
let currentNode;
|
|
30811
31378
|
let startNewNode = true;
|
|
30812
|
-
for (let
|
|
30813
|
-
|
|
30814
|
-
|
|
30815
|
-
|
|
31379
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31380
|
+
|
|
31381
|
+
let line = lines[i].trim();
|
|
31382
|
+
|
|
31383
|
+
if (line.length == 0) {
|
|
30816
31384
|
// Break - start a new node
|
|
30817
31385
|
startNewNode = true;
|
|
30818
31386
|
} else {
|
|
30819
|
-
|
|
30820
|
-
|
|
30821
|
-
|
|
31387
|
+
if (line.startsWith("#")) {
|
|
31388
|
+
continue
|
|
31389
|
+
}
|
|
31390
|
+
|
|
31391
|
+
while (line.endsWith('\\')) {
|
|
31392
|
+
i++;
|
|
31393
|
+
if (i >= lines.length) {
|
|
31394
|
+
break
|
|
31395
|
+
}
|
|
31396
|
+
line = line.substring(0, line.length - 1) + lines[i].trim();
|
|
31397
|
+
}
|
|
31398
|
+
|
|
31399
|
+
if (line.startsWith("include")) {
|
|
31400
|
+
const relativeURL = line.substring(8).trim();
|
|
31401
|
+
const includeURL = getDataURL(relativeURL, host, baseURL);
|
|
31402
|
+
const includeStanzas = await loadStanzas(includeURL);
|
|
31403
|
+
for (let s of includeStanzas) {
|
|
31404
|
+
nodes.push(s);
|
|
31405
|
+
}
|
|
31406
|
+
}
|
|
31407
|
+
|
|
31408
|
+
|
|
31409
|
+
const i = line.indexOf(' ');
|
|
31410
|
+
const key = line.substring(0, i).trim();
|
|
31411
|
+
let value = line.substring(i + 1).trim();
|
|
31412
|
+
|
|
31413
|
+
if (key === "type") {
|
|
31414
|
+
// The "type" property contains format and sometimes other information. For example, data range
|
|
31415
|
+
// on a bigwig "type bigWig 0 .5"
|
|
31416
|
+
const tokens = value.split(/\s+/);
|
|
31417
|
+
value = tokens[0];
|
|
31418
|
+
if (value === "bigWig" && tokens.length === 3) {
|
|
31419
|
+
// This is a bigWig with a range
|
|
31420
|
+
const min = tokens[1];
|
|
31421
|
+
const max = tokens[2];
|
|
31422
|
+
if (currentNode) {
|
|
31423
|
+
currentNode.setProperty("min", min);
|
|
31424
|
+
currentNode.setProperty("max", max);
|
|
31425
|
+
}
|
|
31426
|
+
}
|
|
31427
|
+
|
|
31428
|
+
} else if (!["shortLabel", "longLabel", "metadata", "label"].includes(key)) {
|
|
31429
|
+
const tokens = value.split(/\s+/);
|
|
31430
|
+
value = tokens[0];
|
|
31431
|
+
}
|
|
31432
|
+
|
|
31433
|
+
if (urlProperties.has(key) || value.endsWith("URL") || value.endsWith("Url")) {
|
|
31434
|
+
value = getDataURL(value, host, baseURL);
|
|
31435
|
+
}
|
|
31436
|
+
|
|
30822
31437
|
if (startNewNode) {
|
|
30823
|
-
|
|
30824
|
-
|
|
30825
|
-
const newNode = new Stanza(key, value);
|
|
30826
|
-
nodes.push(newNode);
|
|
30827
|
-
currentNode = newNode;
|
|
31438
|
+
currentNode = new Stanza(key, value);
|
|
31439
|
+
nodes.push(currentNode);
|
|
30828
31440
|
startNewNode = false;
|
|
30829
31441
|
}
|
|
31442
|
+
|
|
30830
31443
|
currentNode.setProperty(key, value);
|
|
30831
31444
|
}
|
|
30832
31445
|
}
|
|
30833
|
-
|
|
30834
31446
|
return resolveParents(nodes)
|
|
30835
31447
|
}
|
|
30836
31448
|
|
|
31449
|
+
function firstWord(str) {
|
|
31450
|
+
const idx = str.indexOf(' ');
|
|
31451
|
+
return idx > 0 ? str.substring(0, idx) : str
|
|
31452
|
+
}
|
|
31453
|
+
|
|
30837
31454
|
function resolveParents(nodes) {
|
|
30838
31455
|
const nodeMap = new Map();
|
|
30839
31456
|
for (let n of nodes) {
|
|
@@ -30848,13 +31465,30 @@ function resolveParents(nodes) {
|
|
|
30848
31465
|
return nodes
|
|
30849
31466
|
}
|
|
30850
31467
|
|
|
30851
|
-
function
|
|
30852
|
-
|
|
30853
|
-
|
|
30854
|
-
|
|
30855
|
-
|
|
31468
|
+
function getDataURL(url, host, baseURL) {
|
|
31469
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("gs://") || url.startsWith("s3://")) {
|
|
31470
|
+
return url
|
|
31471
|
+
} else if (url.startsWith("/")) {
|
|
31472
|
+
return host + url
|
|
31473
|
+
} else {
|
|
31474
|
+
return baseURL + url
|
|
30856
31475
|
}
|
|
30857
|
-
|
|
31476
|
+
}
|
|
31477
|
+
|
|
31478
|
+
function getHost(url) {
|
|
31479
|
+
let host;
|
|
31480
|
+
if (url.startsWith("https://") || url.startsWith("http://")) {
|
|
31481
|
+
try {
|
|
31482
|
+
const tmp = new URL(url);
|
|
31483
|
+
host = `${tmp.protocol}//${tmp.host}`;
|
|
31484
|
+
} catch (e) {
|
|
31485
|
+
console.error("Error parsing base URL host", e);
|
|
31486
|
+
throw e
|
|
31487
|
+
}
|
|
31488
|
+
} else {
|
|
31489
|
+
host = '';
|
|
31490
|
+
}
|
|
31491
|
+
return host
|
|
30858
31492
|
}
|
|
30859
31493
|
|
|
30860
31494
|
const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
|
|
@@ -30938,8 +31572,8 @@ const GenomeUtils = {
|
|
|
30938
31572
|
if ((genomeID.startsWith("GCA_") || genomeID.startsWith("GCF_")) && genomeID.length >= 13) {
|
|
30939
31573
|
try {
|
|
30940
31574
|
const hubURL = convertToHubURL(genomeID);
|
|
30941
|
-
const hub = await
|
|
30942
|
-
reference = hub.getGenomeConfig();
|
|
31575
|
+
const hub = await loadHub(hubURL);
|
|
31576
|
+
reference = hub.getGenomeConfig(genomeID);
|
|
30943
31577
|
} catch (e) {
|
|
30944
31578
|
console.error(e);
|
|
30945
31579
|
}
|
|
@@ -32901,27 +33535,18 @@ const distinctColorsPalette = _18_DistinctColorsViaChatGPT4.map(str => {
|
|
|
32901
33535
|
return [ r, g, b ]
|
|
32902
33536
|
});
|
|
32903
33537
|
|
|
32904
|
-
const colorForNA = appleCrayonRGB('magnesium');
|
|
32905
|
-
const sampleInfoFileHeaders = ['#sampleTable', '#sampleMapping', '#colors'];
|
|
32906
|
-
|
|
32907
33538
|
class SampleInfo {
|
|
32908
33539
|
|
|
32909
33540
|
static emptySpaceReplacement = '|'
|
|
32910
|
-
|
|
32911
|
-
|
|
32912
|
-
attributeNames = []
|
|
32913
|
-
sampleMappingDictionary = {}
|
|
32914
|
-
colorDictionary = {}
|
|
32915
|
-
attributeRangeLUT = {}
|
|
33541
|
+
static colorForNA = appleCrayonRGB('magnesium')
|
|
33542
|
+
static sampleInfoFileHeaders = ['#sampleTable', '#sampleMapping', '#colors']
|
|
32916
33543
|
|
|
32917
33544
|
constructor(browser) {
|
|
32918
|
-
|
|
32919
33545
|
const found = browser.tracks.some(t => typeof t.getSamples === 'function');
|
|
32920
33546
|
if (found.length > 0) {
|
|
32921
33547
|
browser.sampleInfoControl.setButtonVisibility(true);
|
|
32922
33548
|
}
|
|
32923
33549
|
this.initialize();
|
|
32924
|
-
|
|
32925
33550
|
}
|
|
32926
33551
|
|
|
32927
33552
|
initialize() {
|
|
@@ -32948,47 +33573,60 @@ class SampleInfo {
|
|
|
32948
33573
|
|
|
32949
33574
|
getAttributes(sampleName) {
|
|
32950
33575
|
|
|
32951
|
-
const key =
|
|
33576
|
+
const key = this.sampleMappingDictionary[sampleName] || sampleName;
|
|
32952
33577
|
return this.sampleDictionary[key]
|
|
32953
33578
|
}
|
|
32954
33579
|
|
|
32955
|
-
async
|
|
32956
|
-
|
|
32957
|
-
|
|
32958
|
-
this
|
|
32959
|
-
|
|
32960
|
-
|
|
32961
|
-
|
|
33580
|
+
async loadSampleInfo(config) {
|
|
33581
|
+
|
|
33582
|
+
if (config.url) {
|
|
33583
|
+
await this.loadSampleInfoFile(config.url);
|
|
33584
|
+
} else {
|
|
33585
|
+
|
|
33586
|
+
const samples = { ...config };
|
|
33587
|
+
for (const [key, record] of Object.entries(samples)) {
|
|
33588
|
+
samples[key] = SampleInfo.toNumericalRepresentation(record);
|
|
33589
|
+
}
|
|
33590
|
+
|
|
33591
|
+
const [ value ] = Object.values(samples);
|
|
33592
|
+
const attributes = Object.keys(value);
|
|
33593
|
+
|
|
33594
|
+
this.loadSampleInfoHelper(attributes, samples);
|
|
33595
|
+
|
|
32962
33596
|
}
|
|
32963
|
-
}
|
|
32964
33597
|
|
|
32965
|
-
|
|
33598
|
+
this.initialized = true;
|
|
33599
|
+
}
|
|
32966
33600
|
|
|
32967
|
-
|
|
33601
|
+
loadSampleInfoHelper(attributes, samples){
|
|
32968
33602
|
|
|
32969
|
-
|
|
32970
|
-
|
|
32971
|
-
|
|
32972
|
-
this.#accumulateSampleTableDictionary(value);
|
|
32973
|
-
break
|
|
32974
|
-
case '#sampleMapping':
|
|
32975
|
-
this.#accumulateSampleMappingDictionary(value);
|
|
32976
|
-
break
|
|
32977
|
-
case '#colors':
|
|
32978
|
-
this.#accumulateColorScheme(value);
|
|
32979
|
-
break
|
|
33603
|
+
// Establish the range of values for each attribute
|
|
33604
|
+
const lut = createAttributeRangeLUT(attributes, samples);
|
|
33605
|
+
accumulateDictionary(this.attributeRangeLUT, lut);
|
|
32980
33606
|
|
|
33607
|
+
// Ensure unique attribute names list
|
|
33608
|
+
const currentAttributeNameSet = new Set(this.attributeNames);
|
|
33609
|
+
for (const name of attributes) {
|
|
33610
|
+
if (!currentAttributeNameSet.has(name)) {
|
|
33611
|
+
this.attributeNames.push(name);
|
|
32981
33612
|
}
|
|
32982
33613
|
}
|
|
32983
33614
|
|
|
32984
|
-
this.
|
|
33615
|
+
accumulateDictionary(this.sampleDictionary, samples);
|
|
32985
33616
|
|
|
32986
33617
|
}
|
|
32987
33618
|
|
|
33619
|
+
async loadSampleInfoFile(path) {
|
|
33620
|
+
try {
|
|
33621
|
+
const string = await igvxhr.loadString(path);
|
|
33622
|
+
this.#processSampleInfoFileAsString(string);
|
|
33623
|
+
this.sampleInfoFiles.push(path);
|
|
33624
|
+
} catch (e) {
|
|
33625
|
+
console.error(e.message);
|
|
33626
|
+
}
|
|
33627
|
+
}
|
|
33628
|
+
|
|
32988
33629
|
getAttributeColor(attribute, value) {
|
|
32989
|
-
// if (value === 'NA') {
|
|
32990
|
-
// console.log(`${ attribute } : ${ value }`)
|
|
32991
|
-
// }
|
|
32992
33630
|
|
|
32993
33631
|
let color;
|
|
32994
33632
|
|
|
@@ -33006,7 +33644,7 @@ class SampleInfo {
|
|
|
33006
33644
|
|
|
33007
33645
|
} else if (typeof value === "string") {
|
|
33008
33646
|
|
|
33009
|
-
color = 'NA' === value ? colorForNA : stringToRGBString(value);
|
|
33647
|
+
color = 'NA' === value ? SampleInfo.colorForNA : SampleInfo.stringToRGBString(value);
|
|
33010
33648
|
|
|
33011
33649
|
} else {
|
|
33012
33650
|
|
|
@@ -33074,6 +33712,27 @@ class SampleInfo {
|
|
|
33074
33712
|
return json
|
|
33075
33713
|
}
|
|
33076
33714
|
|
|
33715
|
+
#processSampleInfoFileAsString(string) {
|
|
33716
|
+
|
|
33717
|
+
const sectionDictionary = createSectionDictionary(string);
|
|
33718
|
+
|
|
33719
|
+
for (const [header, value] of Object.entries(sectionDictionary)) {
|
|
33720
|
+
switch (header) {
|
|
33721
|
+
case '#sampleTable':
|
|
33722
|
+
this.#accumulateSampleTableDictionary(value);
|
|
33723
|
+
break
|
|
33724
|
+
case '#sampleMapping':
|
|
33725
|
+
this.#accumulateSampleMappingDictionary(value);
|
|
33726
|
+
break
|
|
33727
|
+
case '#colors':
|
|
33728
|
+
this.#accumulateColorScheme(value);
|
|
33729
|
+
break
|
|
33730
|
+
|
|
33731
|
+
}
|
|
33732
|
+
}
|
|
33733
|
+
|
|
33734
|
+
}
|
|
33735
|
+
|
|
33077
33736
|
#accumulateSampleTableDictionary(lines) {
|
|
33078
33737
|
|
|
33079
33738
|
// shift array with first item that is 'sample' or 'Linking_id'. Remaining items are attribute names
|
|
@@ -33113,22 +33772,11 @@ class SampleInfo {
|
|
|
33113
33772
|
} // for (lines)
|
|
33114
33773
|
|
|
33115
33774
|
for (const [key, record] of Object.entries(samples)) {
|
|
33116
|
-
samples[key] = toNumericalRepresentation(record);
|
|
33775
|
+
samples[key] = SampleInfo.toNumericalRepresentation(record);
|
|
33117
33776
|
}
|
|
33118
33777
|
|
|
33119
|
-
|
|
33120
|
-
const lut = createAttributeRangeLUT(attributes, samples);
|
|
33121
|
-
accumulateDictionary(this.attributeRangeLUT, lut);
|
|
33122
|
-
|
|
33123
|
-
// Ensure unique attribute names list
|
|
33124
|
-
const currentAttributeNameSet = new Set(this.attributeNames);
|
|
33125
|
-
for (const name of attributes) {
|
|
33126
|
-
if (!currentAttributeNameSet.has(name)) {
|
|
33127
|
-
this.attributeNames.push(name);
|
|
33128
|
-
}
|
|
33129
|
-
}
|
|
33778
|
+
this.loadSampleInfoHelper(attributes, samples);
|
|
33130
33779
|
|
|
33131
|
-
accumulateDictionary(this.sampleDictionary, samples);
|
|
33132
33780
|
}
|
|
33133
33781
|
|
|
33134
33782
|
#accumulateSampleMappingDictionary(lines) {
|
|
@@ -33229,7 +33877,7 @@ class SampleInfo {
|
|
|
33229
33877
|
this.colorDictionary[attribute] = attributeValue => {
|
|
33230
33878
|
|
|
33231
33879
|
if ('NA' === attributeValue) {
|
|
33232
|
-
return colorForNA
|
|
33880
|
+
return SampleInfo.colorForNA
|
|
33233
33881
|
} else {
|
|
33234
33882
|
const [min, max] = this.attributeRangeLUT[attribute];
|
|
33235
33883
|
const interpolant = (attributeValue - min) / (max - min);
|
|
@@ -33249,8 +33897,34 @@ class SampleInfo {
|
|
|
33249
33897
|
|
|
33250
33898
|
}
|
|
33251
33899
|
|
|
33252
|
-
|
|
33900
|
+
static toNumericalRepresentation(obj) {
|
|
33901
|
+
const result = Object.assign({}, obj);
|
|
33253
33902
|
|
|
33903
|
+
for (const [key, value] of Object.entries(result)) {
|
|
33904
|
+
if (typeof value === 'string' && !isNaN(value)) {
|
|
33905
|
+
result[key] = Number(value);
|
|
33906
|
+
}
|
|
33907
|
+
}
|
|
33908
|
+
|
|
33909
|
+
return result
|
|
33910
|
+
}
|
|
33911
|
+
|
|
33912
|
+
static stringToRGBString(str) {
|
|
33913
|
+
let hash = 0;
|
|
33914
|
+
for (let i = 0; i < str.length; i++) {
|
|
33915
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
33916
|
+
}
|
|
33917
|
+
|
|
33918
|
+
let color = [];
|
|
33919
|
+
for (let i = 0; i < 3; i++) {
|
|
33920
|
+
const value = (hash >> (i * 8)) & 0xff;
|
|
33921
|
+
color.push(value);
|
|
33922
|
+
}
|
|
33923
|
+
|
|
33924
|
+
return `rgb(${color.join(', ')})`
|
|
33925
|
+
}
|
|
33926
|
+
|
|
33927
|
+
}
|
|
33254
33928
|
|
|
33255
33929
|
function createSectionDictionary(string) {
|
|
33256
33930
|
|
|
@@ -33261,14 +33935,14 @@ function createSectionDictionary(string) {
|
|
|
33261
33935
|
let currentHeader;
|
|
33262
33936
|
|
|
33263
33937
|
// If the first line does not start with a section header an initial #sampleTable is implied
|
|
33264
|
-
if (!sampleInfoFileHeaders.includes(lines[0])) {
|
|
33938
|
+
if (!SampleInfo.sampleInfoFileHeaders.includes(lines[0])) {
|
|
33265
33939
|
currentHeader = '#sampleTable';
|
|
33266
33940
|
dictionary[currentHeader] = [];
|
|
33267
33941
|
}
|
|
33268
33942
|
|
|
33269
33943
|
for (const line of lines) {
|
|
33270
33944
|
|
|
33271
|
-
if (sampleInfoFileHeaders.includes(line)) {
|
|
33945
|
+
if (SampleInfo.sampleInfoFileHeaders.includes(line)) {
|
|
33272
33946
|
currentHeader = line;
|
|
33273
33947
|
dictionary[currentHeader] = [];
|
|
33274
33948
|
} else if (currentHeader && false === line.startsWith('#')) {
|
|
@@ -33287,7 +33961,6 @@ function accumulateDictionary(accumulator, dictionary) {
|
|
|
33287
33961
|
}
|
|
33288
33962
|
}
|
|
33289
33963
|
|
|
33290
|
-
|
|
33291
33964
|
function createAttributeRangeLUT(names, dictionary) {
|
|
33292
33965
|
|
|
33293
33966
|
const lut = {};
|
|
@@ -33333,35 +34006,6 @@ function createAttributeRangeLUT(names, dictionary) {
|
|
|
33333
34006
|
return lut
|
|
33334
34007
|
}
|
|
33335
34008
|
|
|
33336
|
-
function toNumericalRepresentation(obj) {
|
|
33337
|
-
|
|
33338
|
-
const result = Object.assign({}, obj);
|
|
33339
|
-
|
|
33340
|
-
for (const [key, value] of Object.entries(result)) {
|
|
33341
|
-
if (typeof value === 'string' && !isNaN(value)) {
|
|
33342
|
-
result[key] = Number(value);
|
|
33343
|
-
}
|
|
33344
|
-
}
|
|
33345
|
-
|
|
33346
|
-
return result
|
|
33347
|
-
}
|
|
33348
|
-
|
|
33349
|
-
function stringToRGBString(str) {
|
|
33350
|
-
|
|
33351
|
-
let hash = 0;
|
|
33352
|
-
for (let i = 0; i < str.length; i++) {
|
|
33353
|
-
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
33354
|
-
}
|
|
33355
|
-
|
|
33356
|
-
let color = [];
|
|
33357
|
-
for (let i = 0; i < 3; i++) {
|
|
33358
|
-
const value = (hash >> (i * 8)) & 0xff;
|
|
33359
|
-
color.push(value);
|
|
33360
|
-
}
|
|
33361
|
-
|
|
33362
|
-
return `rgb(${color.join(', ')})`
|
|
33363
|
-
}
|
|
33364
|
-
|
|
33365
34009
|
const sampleInfoTileXShim = 8;
|
|
33366
34010
|
const sampleInfoTileWidth = 16;
|
|
33367
34011
|
|
|
@@ -33970,7 +34614,11 @@ class SampleNameViewport {
|
|
|
33970
34614
|
|
|
33971
34615
|
checkCanvas() {
|
|
33972
34616
|
|
|
33973
|
-
|
|
34617
|
+
let width = 0;
|
|
34618
|
+
if (true === this.browser.showSampleNames) {
|
|
34619
|
+
width = undefined === this.browser.sampleNameViewportWidth ? 0 : this.browser.sampleNameViewportWidth;
|
|
34620
|
+
}
|
|
34621
|
+
|
|
33974
34622
|
this.ctx.canvas.width = width * window.devicePixelRatio;
|
|
33975
34623
|
this.ctx.canvas.style.width = `${width}px`;
|
|
33976
34624
|
|
|
@@ -35082,7 +35730,7 @@ class MergedTrack extends TrackBase {
|
|
|
35082
35730
|
let element = document.createElement('div');
|
|
35083
35731
|
element.textContent = 'Separate tracks';
|
|
35084
35732
|
|
|
35085
|
-
function click(e) {
|
|
35733
|
+
async function click(e) {
|
|
35086
35734
|
|
|
35087
35735
|
// Capture state which will be nulled when track is removed
|
|
35088
35736
|
const groupAutoscale = this.autoscale;
|
|
@@ -35098,9 +35746,10 @@ class MergedTrack extends TrackBase {
|
|
|
35098
35746
|
track.autoscaleGroup = name;
|
|
35099
35747
|
}
|
|
35100
35748
|
track.isMergedTrack = false;
|
|
35101
|
-
browser.addTrack(track
|
|
35749
|
+
browser.addTrack(track);
|
|
35102
35750
|
}
|
|
35103
|
-
browser.updateViews();
|
|
35751
|
+
await browser.updateViews();
|
|
35752
|
+
browser.reorderTracks();
|
|
35104
35753
|
}
|
|
35105
35754
|
|
|
35106
35755
|
return {element, click}
|
|
@@ -35195,7 +35844,7 @@ class OverlayTrackButton extends NavbarButton {
|
|
|
35195
35844
|
}
|
|
35196
35845
|
}
|
|
35197
35846
|
|
|
35198
|
-
function trackOverlayClickHandler(e) {
|
|
35847
|
+
async function trackOverlayClickHandler(e) {
|
|
35199
35848
|
|
|
35200
35849
|
if (true === isOverlayTrackCriteriaMet(this.browser)) {
|
|
35201
35850
|
|
|
@@ -35232,9 +35881,9 @@ function trackOverlayClickHandler(e) {
|
|
|
35232
35881
|
track.trackView.dispose();
|
|
35233
35882
|
}
|
|
35234
35883
|
|
|
35235
|
-
this.browser.addTrack(
|
|
35236
|
-
mergedTrack.trackView.updateViews();
|
|
35237
|
-
|
|
35884
|
+
this.browser.addTrack(mergedTrack);
|
|
35885
|
+
await mergedTrack.trackView.updateViews();
|
|
35886
|
+
this.browser.reorderTracks();
|
|
35238
35887
|
}
|
|
35239
35888
|
|
|
35240
35889
|
}
|
|
@@ -36841,14 +37490,45 @@ class InputDialog {
|
|
|
36841
37490
|
|
|
36842
37491
|
|
|
36843
37492
|
present(options, e) {
|
|
36844
|
-
|
|
36845
37493
|
this.label.textContent = options.label;
|
|
36846
37494
|
this._input.value = options.value;
|
|
36847
37495
|
this.callback = options.callback || options.click;
|
|
36848
37496
|
|
|
36849
|
-
|
|
36850
|
-
|
|
36851
|
-
|
|
37497
|
+
this.container.style.display = '';
|
|
37498
|
+
|
|
37499
|
+
// Get click coordinates
|
|
37500
|
+
const clickX = e.clientX;
|
|
37501
|
+
const clickY = e.clientY;
|
|
37502
|
+
|
|
37503
|
+
// Get dialog dimensions
|
|
37504
|
+
const dialogWidth = this.container.offsetWidth;
|
|
37505
|
+
const dialogHeight = this.container.offsetHeight;
|
|
37506
|
+
|
|
37507
|
+
// Calculate available space
|
|
37508
|
+
const windowWidth = window.innerWidth;
|
|
37509
|
+
const windowHeight = window.innerHeight;
|
|
37510
|
+
|
|
37511
|
+
// Calculate position to keep dialog on screen
|
|
37512
|
+
let left = clickX;
|
|
37513
|
+
let top = clickY;
|
|
37514
|
+
|
|
37515
|
+
// Adjust horizontal position if dialog would go off screen
|
|
37516
|
+
if (left + dialogWidth > windowWidth) {
|
|
37517
|
+
left = windowWidth - dialogWidth - 10; // 10px padding from edge
|
|
37518
|
+
}
|
|
37519
|
+
|
|
37520
|
+
// Adjust vertical position if dialog would go off screen
|
|
37521
|
+
if (top + dialogHeight > windowHeight) {
|
|
37522
|
+
top = windowHeight - dialogHeight - 10; // 10px padding from edge
|
|
37523
|
+
}
|
|
37524
|
+
|
|
37525
|
+
// Ensure minimum distance from edges
|
|
37526
|
+
left = Math.max(10, left);
|
|
37527
|
+
top = Math.max(10, top);
|
|
37528
|
+
|
|
37529
|
+
// Apply positions
|
|
37530
|
+
this.container.style.left = `${left}px`;
|
|
37531
|
+
this.container.style.top = `${top}px`;
|
|
36852
37532
|
}
|
|
36853
37533
|
}
|
|
36854
37534
|
|
|
@@ -50496,7 +51176,7 @@ class RemoteFile {
|
|
|
50496
51176
|
|
|
50497
51177
|
let url = this.url.slice(); // slice => copy
|
|
50498
51178
|
if (this.config.oauthToken) {
|
|
50499
|
-
const token = resolveToken(this.config.oauthToken);
|
|
51179
|
+
const token = await resolveToken(this.config.oauthToken);
|
|
50500
51180
|
headers['Authorization'] = `Bearer ${token}`;
|
|
50501
51181
|
}
|
|
50502
51182
|
|
|
@@ -55261,7 +55941,7 @@ function create_nested_array(value, shape) {
|
|
|
55261
55941
|
async function openH5File(options) {
|
|
55262
55942
|
|
|
55263
55943
|
// Some clients (notably igv-webapp) pass a File reference in the url field. Fix this
|
|
55264
|
-
if(options.url && isBlobLike(options.url)) {
|
|
55944
|
+
if (options.url && isBlobLike(options.url)) {
|
|
55265
55945
|
options.file = options.url;
|
|
55266
55946
|
options.url = undefined;
|
|
55267
55947
|
}
|
|
@@ -55291,26 +55971,30 @@ async function openH5File(options) {
|
|
|
55291
55971
|
|
|
55292
55972
|
async function readExternalIndex(options) {
|
|
55293
55973
|
|
|
55294
|
-
|
|
55295
|
-
if(options.indexReader) {
|
|
55296
|
-
indexReader = options.indexReader;
|
|
55297
|
-
}
|
|
55298
|
-
else if(options.index) {
|
|
55974
|
+
if (options.index) {
|
|
55299
55975
|
return options.index
|
|
55300
|
-
} else
|
|
55301
|
-
indexReader
|
|
55302
|
-
|
|
55303
|
-
|
|
55304
|
-
|
|
55305
|
-
|
|
55306
|
-
|
|
55307
|
-
|
|
55976
|
+
} else {
|
|
55977
|
+
let indexReader;
|
|
55978
|
+
if (options.indexReader) {
|
|
55979
|
+
indexReader = options.indexReader;
|
|
55980
|
+
} else {
|
|
55981
|
+
let indexOptions;
|
|
55982
|
+
if (options.indexURL) {
|
|
55983
|
+
indexOptions = Object.assign({url: options.indexURL}, options);
|
|
55984
|
+
} else if (options.indexPath) {
|
|
55985
|
+
indexOptions = Object.assign({path: options.indexPath}, options);
|
|
55986
|
+
} else if (options.indexFile) {
|
|
55987
|
+
indexOptions = Object.assign({file: options.indexFile}, options);
|
|
55988
|
+
} else {
|
|
55989
|
+
return undefined
|
|
55990
|
+
}
|
|
55991
|
+
indexReader = getReaderFor(indexOptions);
|
|
55992
|
+
}
|
|
55308
55993
|
const indexFileContents = await indexReader.read();
|
|
55309
55994
|
const indexFileJson = new TextDecoder().decode(indexFileContents);
|
|
55310
55995
|
return JSON.parse(indexFileJson)
|
|
55311
|
-
} else {
|
|
55312
|
-
return undefined
|
|
55313
55996
|
}
|
|
55997
|
+
|
|
55314
55998
|
}
|
|
55315
55999
|
|
|
55316
56000
|
|
|
@@ -55388,9 +56072,9 @@ class HDF5Reader {
|
|
|
55388
56072
|
* @param {string} h5_file - path for the pytor file
|
|
55389
56073
|
* @param {integer} bin_size - bin size
|
|
55390
56074
|
*/
|
|
55391
|
-
constructor(
|
|
56075
|
+
constructor(config, bin_size=100000){
|
|
55392
56076
|
|
|
55393
|
-
this.
|
|
56077
|
+
this.config = config;
|
|
55394
56078
|
this.bin_size = bin_size;
|
|
55395
56079
|
this.h5_obj = undefined;
|
|
55396
56080
|
this.pytorKeys = [];
|
|
@@ -55400,11 +56084,8 @@ class HDF5Reader {
|
|
|
55400
56084
|
async fetch(){
|
|
55401
56085
|
|
|
55402
56086
|
if(!this.h5_obj) {
|
|
55403
|
-
this.
|
|
55404
|
-
|
|
55405
|
-
fetchSize: 1000000,
|
|
55406
|
-
maxSize: 200000000
|
|
55407
|
-
});
|
|
56087
|
+
const options = Object.assign(this.config, {fetchSize: 1000000, maxSize: 200000000});
|
|
56088
|
+
this.h5_obj = await openH5File(options);
|
|
55408
56089
|
}
|
|
55409
56090
|
return this.h5_obj
|
|
55410
56091
|
}
|
|
@@ -64440,7 +65121,7 @@ class CNVPytorTrack extends TrackBase {
|
|
|
64440
65121
|
this.set_available_callers();
|
|
64441
65122
|
|
|
64442
65123
|
} else {
|
|
64443
|
-
this.cnvpytor_obj = new HDF5Reader(this.config
|
|
65124
|
+
this.cnvpytor_obj = new HDF5Reader(this.config, this.bin_size);
|
|
64444
65125
|
// get chrom list that currently user viewing
|
|
64445
65126
|
let chroms = [ ...new Set(this.browser.referenceFrameList.map(val => val.chr))];
|
|
64446
65127
|
|
|
@@ -69428,6 +70109,23 @@ class ReferenceFrame {
|
|
|
69428
70109
|
return this.genome.getChromosome(this.chr)
|
|
69429
70110
|
}
|
|
69430
70111
|
|
|
70112
|
+
/**
|
|
70113
|
+
* Update reference frame based on new viewport width
|
|
70114
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
70115
|
+
*/
|
|
70116
|
+
updateForViewportWidth(viewportWidth) {
|
|
70117
|
+
const {chr} = this;
|
|
70118
|
+
const {bpLength} = this.getChromosome();
|
|
70119
|
+
const viewportWidthBP = this.toBP(viewportWidth);
|
|
70120
|
+
|
|
70121
|
+
// viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
|
|
70122
|
+
if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
|
|
70123
|
+
this.bpPerPixel = bpLength / viewportWidth;
|
|
70124
|
+
} else {
|
|
70125
|
+
this.end = this.start + this.toBP(viewportWidth);
|
|
70126
|
+
}
|
|
70127
|
+
}
|
|
70128
|
+
|
|
69431
70129
|
getMultiLocusLabelBPLengthOnly(pixels) {
|
|
69432
70130
|
const margin = ' ';
|
|
69433
70131
|
const ss = Math.floor(this.start) + 1;
|
|
@@ -69513,7 +70211,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
|
|
|
69513
70211
|
})
|
|
69514
70212
|
}
|
|
69515
70213
|
|
|
69516
|
-
const _version = "3.
|
|
70214
|
+
const _version = "3.3.0";
|
|
69517
70215
|
function version() {
|
|
69518
70216
|
return _version
|
|
69519
70217
|
}
|
|
@@ -69557,10 +70255,24 @@ class ChromosomeSelectWidget {
|
|
|
69557
70255
|
this.select.setAttribute('name', 'chromosome-select-widget');
|
|
69558
70256
|
this.container.appendChild(this.select);
|
|
69559
70257
|
|
|
69560
|
-
this.select.addEventListener('change', () => {
|
|
70258
|
+
this.select.addEventListener('change', async () => {
|
|
69561
70259
|
this.select.blur();
|
|
69562
70260
|
if (this.select.value !== '' && maximumSequenceCountExceeded !== this.select.value) {
|
|
69563
|
-
|
|
70261
|
+
|
|
70262
|
+
if (this.select.value.trim().toLowerCase() === "all" || this.select.value === "*") {
|
|
70263
|
+
if (browser.genome.wholeGenomeView) {
|
|
70264
|
+
const wgChr = browser.genome.getChromosome("all");
|
|
70265
|
+
return {chr: "all", start: 0, end: wgChr.bpLength}
|
|
70266
|
+
}
|
|
70267
|
+
} else {
|
|
70268
|
+
const chromosome = await browser.genome.loadChromosome(this.select.value);
|
|
70269
|
+
const locusObject = {chr: chromosome.name};
|
|
70270
|
+
if (locusObject.start === undefined && locusObject.end === undefined) {
|
|
70271
|
+
locusObject.start = 0;
|
|
70272
|
+
locusObject.end = chromosome.bpLength;
|
|
70273
|
+
}
|
|
70274
|
+
browser.updateLoci([locusObject]);
|
|
70275
|
+
}
|
|
69564
70276
|
}
|
|
69565
70277
|
});
|
|
69566
70278
|
|
|
@@ -69585,7 +70297,9 @@ class ChromosomeSelectWidget {
|
|
|
69585
70297
|
this.genome = genome;
|
|
69586
70298
|
|
|
69587
70299
|
// Start with explicit chromosome name list
|
|
69588
|
-
const list = genome.wgChromosomeNames
|
|
70300
|
+
const list = (genome.wgChromosomeNames) ?
|
|
70301
|
+
genome.wgChromosomeNames.map(nm => genome.getChromosomeDisplayName(nm)) :
|
|
70302
|
+
[];
|
|
69589
70303
|
|
|
69590
70304
|
if (this.showAllChromosomes && genome.chromosomeNames.length > 1) {
|
|
69591
70305
|
const exclude = new Set(list);
|
|
@@ -70811,7 +71525,7 @@ class ResponsiveNavbar {
|
|
|
70811
71525
|
});
|
|
70812
71526
|
|
|
70813
71527
|
this.searchInput.addEventListener('change', () => {
|
|
70814
|
-
|
|
71528
|
+
this.doSearch(this.searchInput.value);
|
|
70815
71529
|
});
|
|
70816
71530
|
|
|
70817
71531
|
const searchIconContainer = document.createElement('div');
|
|
@@ -70822,7 +71536,7 @@ class ResponsiveNavbar {
|
|
|
70822
71536
|
searchIconContainer.appendChild(searchIcon);
|
|
70823
71537
|
|
|
70824
71538
|
searchIconContainer.addEventListener('click', () => {
|
|
70825
|
-
|
|
71539
|
+
this.doSearch(this.searchInput.value);
|
|
70826
71540
|
});
|
|
70827
71541
|
|
|
70828
71542
|
this.windowSizePanel = new WindowSizePanel(locusSizeGroup, browser);
|
|
@@ -70970,6 +71684,22 @@ class ResponsiveNavbar {
|
|
|
70970
71684
|
this.navigation.style.display = 'flex';
|
|
70971
71685
|
}
|
|
70972
71686
|
|
|
71687
|
+
/**
|
|
71688
|
+
|
|
71689
|
+
* Search for the locus string -- this function is called from the navbar search box, and is not part of the API.
|
|
71690
|
+
* Wraps ```search``` and presents an error dialog if false.
|
|
71691
|
+
*
|
|
71692
|
+
* @param locus
|
|
71693
|
+
* @param init
|
|
71694
|
+
* @returns {Promise<void>}
|
|
71695
|
+
*/
|
|
71696
|
+
async doSearch(locus) {
|
|
71697
|
+
const success = await this.browser.search(locus);
|
|
71698
|
+
if (!success) {
|
|
71699
|
+
this.browser.alert.present(new Error(`Unrecognized locus: <b> ${locus} </b>`));
|
|
71700
|
+
}
|
|
71701
|
+
}
|
|
71702
|
+
|
|
70973
71703
|
}
|
|
70974
71704
|
|
|
70975
71705
|
function logo() {
|
|
@@ -72221,6 +72951,9 @@ class ChromAliasDefaults {
|
|
|
72221
72951
|
aliasRecord["_cap"] = cap;
|
|
72222
72952
|
}
|
|
72223
72953
|
|
|
72954
|
+
const chrPrefixAlias = aliasRecord.chr.startsWith("chr") ? aliasRecord.chr.substring(3) : "chr" + aliasRecord.chr;
|
|
72955
|
+
aliasRecord["_chrprefix_"] = chrPrefixAlias;
|
|
72956
|
+
|
|
72224
72957
|
}
|
|
72225
72958
|
|
|
72226
72959
|
}
|
|
@@ -72245,7 +72978,7 @@ class ChromAliasBB {
|
|
|
72245
72978
|
}
|
|
72246
72979
|
|
|
72247
72980
|
async preload(chrNames) {
|
|
72248
|
-
|
|
72981
|
+
await this.reader.preload();
|
|
72249
72982
|
for(let nm of chrNames) {
|
|
72250
72983
|
await this.search(nm);
|
|
72251
72984
|
}
|
|
@@ -72297,12 +73030,6 @@ class ChromAliasBB {
|
|
|
72297
73030
|
}
|
|
72298
73031
|
return this.aliasRecordCache.get(alias)
|
|
72299
73032
|
}
|
|
72300
|
-
|
|
72301
|
-
async getChromosomeNames() {
|
|
72302
|
-
await this.reader.loadHeader();
|
|
72303
|
-
return Array.from(this.reader.chrNames)
|
|
72304
|
-
}
|
|
72305
|
-
|
|
72306
73033
|
}
|
|
72307
73034
|
|
|
72308
73035
|
/**
|
|
@@ -72327,10 +73054,9 @@ class ChromAliasFile {
|
|
|
72327
73054
|
this.genome = genome;
|
|
72328
73055
|
}
|
|
72329
73056
|
|
|
72330
|
-
async preload() {
|
|
72331
|
-
|
|
73057
|
+
async preload(chrNames) {
|
|
73058
|
+
// A no-op, this is a text file, no need to preload
|
|
72332
73059
|
}
|
|
72333
|
-
|
|
72334
73060
|
/**
|
|
72335
73061
|
* Return the canonical chromosome name for the alias. If none found return the alias
|
|
72336
73062
|
*
|
|
@@ -72496,7 +73222,7 @@ class CytobandFile {
|
|
|
72496
73222
|
for (let line of lines) {
|
|
72497
73223
|
|
|
72498
73224
|
const tokens = line.split("\t");
|
|
72499
|
-
const chrName = tokens[0];
|
|
73225
|
+
const chrName = tokens[0];
|
|
72500
73226
|
if (!lastChr) lastChr = chrName;
|
|
72501
73227
|
|
|
72502
73228
|
if (chrName !== lastChr) {
|
|
@@ -72514,37 +73240,20 @@ class CytobandFile {
|
|
|
72514
73240
|
bands.push(new Cytoband(start, end, name, stain));
|
|
72515
73241
|
}
|
|
72516
73242
|
}
|
|
72517
|
-
|
|
72518
|
-
|
|
72519
|
-
|
|
72520
|
-
/**
|
|
72521
|
-
* Infer genome chromosome names from cytoband data. This should only be used as a last resort
|
|
72522
|
-
*/
|
|
72523
|
-
async getChromosomeNames() {
|
|
72524
|
-
if(this.cytobands.size === 0) {
|
|
72525
|
-
await this.#loadCytobands();
|
|
73243
|
+
if(bands.length > 0) {
|
|
73244
|
+
this.cytobands.set(lastChr, bands);
|
|
72526
73245
|
}
|
|
72527
|
-
return Array.from(this.cytobands.keys())
|
|
72528
|
-
}
|
|
72529
73246
|
|
|
72530
|
-
/**
|
|
72531
|
-
* Infer chromosome objects from cytoband data. This should only be used as last resort.
|
|
72532
|
-
*/
|
|
72533
|
-
async getChromosomes() {
|
|
72534
|
-
if(this.cytobands.size === 0) {
|
|
72535
|
-
await this.#loadCytobands();
|
|
72536
|
-
}
|
|
72537
|
-
|
|
72538
|
-
const chromosomes = [];
|
|
72539
|
-
let order = 0;
|
|
72540
|
-
for(let [chrName, cytoList] of this.cytobands.entries()) {
|
|
72541
|
-
chromosomes.push(new Chromosome(chrName, order++, cytoList[cytoList.length - 1].end));
|
|
72542
|
-
}
|
|
72543
|
-
return chromosomes
|
|
72544
73247
|
}
|
|
72545
73248
|
|
|
72546
73249
|
}
|
|
72547
73250
|
|
|
73251
|
+
const ucsdIDMap = new Map([
|
|
73252
|
+
["1kg_ref", "hg18"],
|
|
73253
|
+
["1kg_v37", "hg19"],
|
|
73254
|
+
["b37", "hg19"]
|
|
73255
|
+
]);
|
|
73256
|
+
|
|
72548
73257
|
/**
|
|
72549
73258
|
* The Genome class represents an assembly and consists of the following elements
|
|
72550
73259
|
* sequence - Object representing the DNA sequence
|
|
@@ -72569,8 +73278,10 @@ class Genome {
|
|
|
72569
73278
|
this.config = config;
|
|
72570
73279
|
this.browser = browser;
|
|
72571
73280
|
this.id = config.id || generateGenomeID(config);
|
|
73281
|
+
this.ucscID = config.ucscID || ucsdIDMap.get(this.id) || this.id;
|
|
73282
|
+
this.blatDB = config.blatDB || this.ucscID;
|
|
72572
73283
|
this.name = config.name;
|
|
72573
|
-
this.nameSet = config.nameSet;
|
|
73284
|
+
this.nameSet = config.nameSet || 'ucsc';
|
|
72574
73285
|
}
|
|
72575
73286
|
|
|
72576
73287
|
|
|
@@ -72578,49 +73289,45 @@ class Genome {
|
|
|
72578
73289
|
|
|
72579
73290
|
const config = this.config;
|
|
72580
73291
|
|
|
73292
|
+
// Load sequence
|
|
72581
73293
|
this.sequence = await loadSequence(config, this.browser);
|
|
72582
73294
|
|
|
72583
|
-
|
|
72584
|
-
|
|
73295
|
+
|
|
73296
|
+
// Load cytobands. This is optional but required to support the ideogram. Only needed for whole genome view
|
|
73297
|
+
if(false !== config.showIdeogram && false !== config.wholeGenomeView) {
|
|
73298
|
+
if (config.cytobandURL) {
|
|
73299
|
+
this.cytobandSource = new CytobandFile(config.cytobandURL, Object.assign({}, config));
|
|
73300
|
+
} else if (config.cytobandBbURL) {
|
|
73301
|
+
this.cytobandSource = new CytobandFileBB(config.cytobandBbURL, Object.assign({}, config), this);
|
|
73302
|
+
}
|
|
73303
|
+
}
|
|
73304
|
+
|
|
73305
|
+
// Search for chromosomes, that is an array of chromosome objects containing name and length. This is
|
|
73306
|
+
// optional but required to support whole genome view.
|
|
73307
|
+
if (this.sequence.chromosomes) {
|
|
73308
|
+
this.chromosomes = this.sequence.chromosomes;
|
|
73309
|
+
} else if (config.chromSizesURL) {
|
|
72585
73310
|
this.chromosomes = await loadChromSizes(config.chromSizesURL);
|
|
72586
73311
|
} else {
|
|
72587
|
-
|
|
72588
|
-
this.chromosomes = this.sequence.chromosomes || new Map();
|
|
73312
|
+
this.chromosomes = new Map(); // Cache, chromosome are added as they are loaded
|
|
72589
73313
|
}
|
|
72590
73314
|
|
|
72591
|
-
|
|
73315
|
+
// Search for chromosome names. This is optional but required to support the chromosome pulldown
|
|
73316
|
+
if (this.sequence.chromosomeNames) {
|
|
73317
|
+
this.chromosomeNames = this.sequence.chromosomeNames; // Twobit files can supply chromosome names unless they use an external index
|
|
73318
|
+
} else if (this.chromosomes.size > 0) {
|
|
72592
73319
|
this.chromosomeNames = Array.from(this.chromosomes.keys());
|
|
72593
73320
|
}
|
|
72594
73321
|
|
|
73322
|
+
// Chromosome alias
|
|
72595
73323
|
if (config.chromAliasBbURL) {
|
|
72596
73324
|
this.chromAlias = new ChromAliasBB(config.chromAliasBbURL, Object.assign({}, config), this);
|
|
72597
|
-
if (!this.chromosomeNames) {
|
|
72598
|
-
this.chromosomeNames = await this.chromAlias.getChromosomeNames();
|
|
72599
|
-
}
|
|
72600
73325
|
} else if (config.aliasURL) {
|
|
72601
73326
|
this.chromAlias = new ChromAliasFile(config.aliasURL, Object.assign({}, config), this);
|
|
72602
73327
|
} else if (this.chromosomeNames) {
|
|
72603
73328
|
this.chromAlias = new ChromAliasDefaults(this.id, this.chromosomeNames);
|
|
72604
73329
|
}
|
|
72605
73330
|
|
|
72606
|
-
if (config.cytobandBbURL) {
|
|
72607
|
-
this.cytobandSource = new CytobandFileBB(config.cytobandBbURL, Object.assign({}, config), this);
|
|
72608
|
-
} else if (config.cytobandURL) {
|
|
72609
|
-
this.cytobandSource = new CytobandFile(config.cytobandURL, Object.assign({}, config));
|
|
72610
|
-
}
|
|
72611
|
-
|
|
72612
|
-
// Last resort for chromosome information -- retrieve it from the cytoband source if supported
|
|
72613
|
-
if (!this.chromosomeNames && typeof this.cytobandSource.getChromosomeNames === 'function') {
|
|
72614
|
-
this.chromosomeNames = await this.cytobandSource.getChromosomeNames();
|
|
72615
|
-
}
|
|
72616
|
-
if (this.chromosomes.size === 0 && typeof this.cytobandSource.getChromosomes === 'function') {
|
|
72617
|
-
const c = await this.cytobandSource.getChromosomes();
|
|
72618
|
-
for (let chromosome of c) {
|
|
72619
|
-
this.chromosomes.set(c.name, c);
|
|
72620
|
-
}
|
|
72621
|
-
}
|
|
72622
|
-
|
|
72623
|
-
|
|
72624
73331
|
if (false !== config.wholeGenomeView && this.chromosomes.size > 0) {
|
|
72625
73332
|
// Set chromosome order for WG view and chromosome pulldown. If chromosome order is not specified sort
|
|
72626
73333
|
if (config.chromosomeOrder) {
|
|
@@ -72672,9 +73379,9 @@ class Genome {
|
|
|
72672
73379
|
getHomeChromosomeName() {
|
|
72673
73380
|
if (this.showWholeGenomeView() && this.chromosomes.has("all")) {
|
|
72674
73381
|
return "all"
|
|
72675
|
-
} else {
|
|
73382
|
+
} else if (this.chromosomeNames) {
|
|
72676
73383
|
return this.chromosomeNames[0]
|
|
72677
|
-
}
|
|
73384
|
+
} else ;
|
|
72678
73385
|
}
|
|
72679
73386
|
|
|
72680
73387
|
getChromosomeName(chr) {
|
|
@@ -72725,18 +73432,18 @@ class Genome {
|
|
|
72725
73432
|
if (!aliasRecord && chr !== chr.toLowerCase()) {
|
|
72726
73433
|
aliasRecord = await this.chromAlias.search(chr.toLowerCase());
|
|
72727
73434
|
}
|
|
72728
|
-
if(aliasRecord) {
|
|
73435
|
+
if (aliasRecord) {
|
|
72729
73436
|
// Add some aliases for case insensitivy
|
|
72730
73437
|
const upper = aliasRecord.chr.toUpperCase();
|
|
72731
73438
|
const lower = aliasRecord.chr.toLowerCase();
|
|
72732
73439
|
const cap = aliasRecord.chr.charAt(0).toUpperCase() + aliasRecord.chr.slice(1);
|
|
72733
|
-
if(aliasRecord.chr !== upper) {
|
|
73440
|
+
if (aliasRecord.chr !== upper) {
|
|
72734
73441
|
aliasRecord["_uppercase"] = upper;
|
|
72735
73442
|
}
|
|
72736
|
-
if(aliasRecord.chr !== lower) {
|
|
73443
|
+
if (aliasRecord.chr !== lower) {
|
|
72737
73444
|
aliasRecord["_lowercase"] = lower;
|
|
72738
73445
|
}
|
|
72739
|
-
if(aliasRecord.chr !== cap) {
|
|
73446
|
+
if (aliasRecord.chr !== cap) {
|
|
72740
73447
|
aliasRecord["_cap"] = cap;
|
|
72741
73448
|
}
|
|
72742
73449
|
}
|
|
@@ -72868,6 +73575,10 @@ class Genome {
|
|
|
72868
73575
|
return undefined
|
|
72869
73576
|
}
|
|
72870
73577
|
}
|
|
73578
|
+
|
|
73579
|
+
getHubURLs() {
|
|
73580
|
+
return this.config.hubs
|
|
73581
|
+
}
|
|
72871
73582
|
}
|
|
72872
73583
|
|
|
72873
73584
|
/**
|
|
@@ -72910,7 +73621,7 @@ function generateGenomeID(config) {
|
|
|
72910
73621
|
}
|
|
72911
73622
|
}
|
|
72912
73623
|
|
|
72913
|
-
var igvCss = '.igv-ui-dropdown {\n cursor: default;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n background-color: white;\n}\n.igv-ui-dropdown > div {\n overflow-y: auto;\n overflow-x: hidden;\n background-color: white;\n}\n.igv-ui-dropdown > div > div {\n padding: 4px;\n width: 100%;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: 1px;\n background-color: white;\n}\n.igv-ui-dropdown > div > div:last-child {\n border-bottom-color: transparent;\n border-bottom-width: 0;\n}\n.igv-ui-dropdown > div > div:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white;\n}\n.igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-width: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px;\n}\n.igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-popover > div:last-child {\n user-select: text;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white;\n border-bottom-width: 0;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-ui-popover > div:last-child > div {\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.igv-ui-popover > div:last-child > div > span {\n font-weight: bolder;\n}\n.igv-ui-popover > div:last-child hr {\n width: 100%;\n}\n\n.igv-ui-alert-dialog-container {\n position: fixed;\n top: 20%;\n left: 50%;\n transform: translateX(-50%);\n z-index: 2048;\n box-sizing: content-box;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:first-child {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n background-color: white;\n border: unset;\n}\n.igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n width: 300px;\n height: fit-content;\n padding-bottom: 16px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input > div {\n width: fit-content;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input[type=range] {\n width: 70%;\n -webkit-appearance: none;\n background: linear-gradient(90deg, white, black);\n outline: none;\n margin: 0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input output {\n display: block;\n height: 100%;\n width: 20%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n padding-top: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div {\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n\n.igv-ui-generic-container {\n position: fixed;\n cursor: pointer;\n background-color: white;\n z-index: 2048;\n box-sizing: content-box;\n width: 300px;\n height: 200px;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-ui-generic-container > div:nth-child(1) > div {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-ui-colorpicker-container {\n background-color: #eee;\n height: fit-content;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n.igv-ui-colorpicker-container > div:nth-child(2) {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-colorpicker-container > div:nth-child(3) {\n box-sizing: border-box;\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n padding: 0.5rem;\n font-family: \"Open Sans\", sans-serif;\n font-size: 1rem;\n font-weight: 400;\n width: 100%;\n border-top-style: solid;\n border-top-width: thin;\n border-top-color: #373737;\n}\n.igv-ui-colorpicker-container > div:nth-child(4) {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n margin: 2px;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 0;\n border-color: white;\n border-radius: 3px;\n}\n\n.igv-ui-color-swatch-shim {\n cursor: pointer;\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n}\n.igv-ui-dialog .igv-ui-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-ui-panel, .igv-ui-panel-row, .igv-ui-panel-column {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column;\n}\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row;\n}\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-table {\n background-color: white;\n}\n\n.igv-ui-table thead {\n position: sticky;\n top: 0;\n}\n\n.igv-ui-table th {\n text-align: left;\n}\n\n.igv-ui-table td {\n padding-right: 20px;\n}\n\n.igv-ui-table tr:hover {\n background-color: lightblue;\n}\n\n.igv-ui-center-fixed {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 240px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-hidden {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-icon-button {\n cursor: pointer;\n position: relative;\n width: 24px;\n height: 24px;\n margin-left: 4px;\n margin-right: 4px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:first-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: -18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:last-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.igv-navbar-text-button {\n cursor: pointer;\n position: relative;\n margin-left: 2px;\n margin-right: 2px;\n border: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar-text-button > div:nth-child(2) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 0;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-text-button > div:nth-child(3) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 42px;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n#igv-text-button-label {\n text-anchor: middle;\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-inactive rect {\n stroke: #737373;\n fill: white;\n}\n.igv-navbar-text-button-svg-inactive text {\n fill: #737373;\n}\n.igv-navbar-text-button-svg-inactive tspan {\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-hover rect {\n stroke: #737373;\n fill: #737373;\n}\n.igv-navbar-text-button-svg-hover text {\n fill: white;\n}\n.igv-navbar-text-button-svg-hover tspan {\n dominant-baseline: middle;\n}\n\n#igv-save-svg-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-svg-group text {\n fill: #737373;\n}\n\n#igv-save-svg-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-svg-group:hover text {\n fill: white;\n}\n\n#igv-save-png-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-png-group text {\n fill: #737373;\n}\n\n#igv-save-png-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-png-group:hover text {\n fill: white;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 256;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 12px;\n height: 12px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 12px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label > div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n.igv-multi-locus-ruler-label > div {\n cursor: pointer;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 50%;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 32;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 32;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container > div:nth-child(1) i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n max-width: 400px;\n z-index: 512;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n}\n.igv-roi-menu > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu > div:last-child > div:not(:first-child):hover {\n cursor: pointer;\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu > div:last-child div:first-child {\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n resize: both;\n overflow: hidden;\n width: min-content;\n max-width: 1600px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n cursor: default;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-description {\n padding: 4px;\n margin-left: 4px;\n word-break: break-all;\n overflow-y: auto;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-goto-explainer {\n margin-top: 5px;\n margin-left: 4px;\n color: #7F7F7F;\n font-style: italic;\n height: 24px;\n border-top: solid lightgray;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: #7f7f7f;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n overflow: auto;\n height: 360px;\n flex: 1 1 auto;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: transparent;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n min-height: 32px;\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n cursor: pointer;\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n pointer-events: auto;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.picker_wrapper.no_alpha .picker_alpha {\n display: none;\n}\n\n.picker_wrapper.no_editor .picker_editor {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.picker_wrapper.no_cancel .picker_cancel {\n display: none;\n}\n\n.layout_default.picker_wrapper {\n display: flex;\n flex-flow: row wrap;\n justify-content: space-between;\n align-items: stretch;\n font-size: 10px;\n width: 25em;\n padding: 0.5em;\n}\n\n.layout_default.picker_wrapper input, .layout_default.picker_wrapper button {\n font-size: 1rem;\n}\n\n.layout_default.picker_wrapper > * {\n margin: 0.5em;\n}\n\n.layout_default.picker_wrapper::before {\n content: \"\";\n display: block;\n width: 100%;\n height: 0;\n order: 1;\n}\n\n.layout_default .picker_slider, .layout_default .picker_selector {\n padding: 1em;\n}\n\n.layout_default .picker_hue {\n width: 100%;\n}\n\n.layout_default .picker_sl {\n flex: 1 1 auto;\n}\n\n.layout_default .picker_sl::before {\n content: \"\";\n display: block;\n padding-bottom: 100%;\n}\n\n.layout_default .picker_editor {\n order: 1;\n width: 6.5rem;\n}\n\n.layout_default .picker_editor input {\n width: 100%;\n height: 100%;\n}\n\n.layout_default .picker_sample {\n order: 1;\n flex: 1 1 auto;\n}\n\n.layout_default .picker_done, .layout_default .picker_cancel {\n order: 1;\n}\n\n.picker_wrapper {\n box-sizing: border-box;\n background: #f2f2f2;\n box-shadow: 0 0 0 1px silver;\n cursor: default;\n font-family: sans-serif;\n color: #444;\n pointer-events: auto;\n}\n\n.picker_wrapper:focus {\n outline: none;\n}\n\n.picker_wrapper button, .picker_wrapper input {\n box-sizing: border-box;\n border: none;\n box-shadow: 0 0 0 1px silver;\n outline: none;\n}\n\n.picker_wrapper button:focus, .picker_wrapper button:active, .picker_wrapper input:focus, .picker_wrapper input:active {\n box-shadow: 0 0 2px 1px #1e90ff;\n}\n\n.picker_wrapper button {\n padding: 0.4em 0.6em;\n cursor: pointer;\n background-color: #f5f5f5;\n background-image: linear-gradient(0deg, gainsboro, transparent);\n}\n\n.picker_wrapper button:active {\n background-image: linear-gradient(0deg, transparent, gainsboro);\n}\n\n.picker_wrapper button:hover {\n background-color: #fff;\n}\n\n.picker_selector {\n position: absolute;\n z-index: 1;\n display: block;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n border: 2px solid #fff;\n border-radius: 100%;\n box-shadow: 0 0 3px 1px #67b9ff;\n background: currentColor;\n cursor: pointer;\n}\n\n.picker_slider .picker_selector {\n border-radius: 2px;\n}\n\n.picker_hue {\n position: relative;\n background-image: linear-gradient(90deg, red, yellow, lime, cyan, blue, magenta, red);\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_sl {\n position: relative;\n box-shadow: 0 0 0 1px silver;\n background-image: linear-gradient(180deg, white, rgba(255, 255, 255, 0) 50%), linear-gradient(0deg, black, rgba(0, 0, 0, 0) 50%), linear-gradient(90deg, #808080, rgba(128, 128, 128, 0));\n}\n\n.picker_alpha, .picker_sample {\n position: relative;\n background: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%) 0 0/2em 2em, linear-gradient(45deg, lightgrey 25%, white 25%, white 75%, lightgrey 75%) 1em 1em/2em 2em;\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_alpha .picker_selector, .picker_sample .picker_selector {\n background: none;\n}\n\n.picker_editor input {\n font-family: monospace;\n padding: 0.2em 0.4em;\n}\n\n.picker_sample::before {\n content: \"\";\n position: absolute;\n display: block;\n width: 100%;\n height: 100%;\n background: currentColor;\n}\n\n.picker_arrow {\n position: absolute;\n z-index: -1;\n}\n\n.picker_wrapper.popup {\n position: absolute;\n z-index: 2;\n margin: 1.5em;\n}\n\n.picker_wrapper.popup, .picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n background: #f2f2f2;\n box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.4);\n}\n\n.picker_wrapper.popup .picker_arrow {\n width: 3em;\n height: 3em;\n margin: 0;\n}\n\n.picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n content: \"\";\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n z-index: -99;\n}\n\n.picker_wrapper.popup .picker_arrow::before {\n width: 100%;\n height: 100%;\n -webkit-transform: skew(45deg);\n transform: skew(45deg);\n -webkit-transform-origin: 0 100%;\n transform-origin: 0 100%;\n}\n\n.picker_wrapper.popup .picker_arrow::after {\n width: 150%;\n height: 150%;\n box-shadow: none;\n}\n\n.popup.popup_top {\n bottom: 100%;\n left: 0;\n}\n\n.popup.popup_top .picker_arrow {\n bottom: 0;\n left: 0;\n -webkit-transform: rotate(-90deg);\n transform: rotate(-90deg);\n}\n\n.popup.popup_bottom {\n top: 100%;\n left: 0;\n}\n\n.popup.popup_bottom .picker_arrow {\n top: 0;\n left: 0;\n -webkit-transform: rotate(90deg) scale(1, -1);\n transform: rotate(90deg) scale(1, -1);\n}\n\n.popup.popup_left {\n top: 0;\n right: 100%;\n}\n\n.popup.popup_left .picker_arrow {\n top: 0;\n right: 0;\n -webkit-transform: scale(-1, 1);\n transform: scale(-1, 1);\n}\n\n.popup.popup_right {\n top: 0;\n left: 100%;\n}\n\n.popup.popup_right .picker_arrow {\n top: 0;\n left: 0;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n min-height: 160px;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n line-height: 1;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n position: relative;\n margin-top: 5px;\n width: 100%;\n}\n.igv-axis-column > div > div {\n z-index: 512;\n position: absolute;\n top: 8px;\n left: 8px;\n width: fit-content;\n height: fit-content;\n background-color: transparent;\n display: grid;\n align-items: start;\n justify-items: center;\n}\n.igv-axis-column > div > div > input {\n display: block;\n margin: unset;\n cursor: pointer;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-info-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.igv-track-drag-column .igv-track-drag-handle-color {\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover-color {\n background-color: #787878;\n}\n.igv-track-drag-column .igv-track-drag-handle-selected-color {\n background-color: #0963fa;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-vertical-center {\n margin: 0 !important;\n top: 50% !important;\n -ms-transform: translateY(-50%) !important;\n transform: translateY(-50%) !important;\n}\n\n/*# sourceMappingURL=igv.css.map */\n';
|
|
73624
|
+
var igvCss = '.igv-ui-dropdown {\n cursor: default;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n background-color: white;\n}\n.igv-ui-dropdown > div {\n overflow-y: auto;\n overflow-x: hidden;\n background-color: white;\n}\n.igv-ui-dropdown > div > div {\n padding: 4px;\n width: 100%;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: 1px;\n background-color: white;\n}\n.igv-ui-dropdown > div > div:last-child {\n border-bottom-color: transparent;\n border-bottom-width: 0;\n}\n.igv-ui-dropdown > div > div:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white;\n}\n.igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-width: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px;\n}\n.igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-popover > div:last-child {\n user-select: text;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white;\n border-bottom-width: 0;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-ui-popover > div:last-child > div {\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.igv-ui-popover > div:last-child > div > span {\n font-weight: bolder;\n}\n.igv-ui-popover > div:last-child hr {\n width: 100%;\n}\n\n.igv-ui-alert-dialog-container {\n position: fixed;\n top: 20%;\n left: 50%;\n transform: translateX(-50%);\n z-index: 2048;\n box-sizing: content-box;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:first-child {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n background-color: white;\n border: unset;\n}\n.igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n width: 300px;\n height: fit-content;\n padding-bottom: 16px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input > div {\n width: fit-content;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input[type=range] {\n width: 70%;\n -webkit-appearance: none;\n background: linear-gradient(90deg, white, black);\n outline: none;\n margin: 0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input output {\n display: block;\n height: 100%;\n width: 20%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n padding-top: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div {\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n\n.igv-ui-generic-container {\n position: fixed;\n cursor: pointer;\n background-color: white;\n z-index: 2048;\n box-sizing: content-box;\n width: 300px;\n height: 200px;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-ui-generic-container > div:nth-child(1) > div {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-ui-colorpicker-container {\n background-color: #eee;\n height: fit-content;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n.igv-ui-colorpicker-container > div:nth-child(2) {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-colorpicker-container > div:nth-child(3) {\n box-sizing: border-box;\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n padding: 0.5rem;\n font-family: \"Open Sans\", sans-serif;\n font-size: 1rem;\n font-weight: 400;\n width: 100%;\n border-top-style: solid;\n border-top-width: thin;\n border-top-color: #373737;\n}\n.igv-ui-colorpicker-container > div:nth-child(4) {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n margin: 2px;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 0;\n border-color: white;\n border-radius: 3px;\n}\n\n.igv-ui-color-swatch-shim {\n cursor: pointer;\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n}\n.igv-ui-dialog .igv-ui-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-ui-panel, .igv-ui-panel-row, .igv-ui-panel-column {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column;\n}\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row;\n}\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-table {\n background-color: white;\n}\n\n.igv-ui-table thead {\n position: sticky;\n top: 0;\n}\n\n.igv-ui-table th {\n text-align: left;\n}\n\n.igv-ui-table td {\n padding-right: 20px;\n}\n\n.igv-ui-table tr:hover {\n background-color: lightblue;\n}\n\n.igv-ui-center-fixed {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 240px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-hidden {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-icon-button {\n cursor: pointer;\n position: relative;\n width: 24px;\n height: 24px;\n margin-left: 4px;\n margin-right: 4px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:first-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: -18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:last-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.igv-navbar-text-button {\n cursor: pointer;\n position: relative;\n margin-left: 2px;\n margin-right: 2px;\n border: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar-text-button > div:nth-child(2) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 0;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-text-button > div:nth-child(3) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 42px;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n#igv-text-button-label {\n text-anchor: middle;\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-inactive rect {\n stroke: #737373;\n fill: white;\n}\n.igv-navbar-text-button-svg-inactive text {\n fill: #737373;\n}\n.igv-navbar-text-button-svg-inactive tspan {\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-hover rect {\n stroke: #737373;\n fill: #737373;\n}\n.igv-navbar-text-button-svg-hover text {\n fill: white;\n}\n.igv-navbar-text-button-svg-hover tspan {\n dominant-baseline: middle;\n}\n\n#igv-save-svg-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-svg-group text {\n fill: #737373;\n}\n\n#igv-save-svg-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-svg-group:hover text {\n fill: white;\n}\n\n#igv-save-png-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-png-group text {\n fill: #737373;\n}\n\n#igv-save-png-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-png-group:hover text {\n fill: white;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 256;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 12px;\n height: 12px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 12px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label > div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n.igv-multi-locus-ruler-label > div {\n cursor: pointer;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 50%;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 32;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 32;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: fixed;\n top: 20%;\n left: 75%;\n transform: translateX(-50%);\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container > div:nth-child(1) {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container > div:nth-child(1) i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n max-width: 400px;\n z-index: 2048;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n}\n.igv-roi-menu > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu > div:last-child > div:not(:first-child):hover {\n cursor: pointer;\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu > div:last-child div:first-child {\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n resize: both;\n overflow: hidden;\n width: min-content;\n max-width: 1600px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n cursor: default;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-description {\n padding: 4px;\n margin-left: 4px;\n word-break: break-all;\n overflow-y: auto;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-goto-explainer {\n margin-top: 5px;\n margin-left: 4px;\n color: #7F7F7F;\n font-style: italic;\n height: 24px;\n border-top: solid lightgray;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: #7f7f7f;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n overflow: auto;\n height: 360px;\n flex: 1 1 auto;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: transparent;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n min-height: 32px;\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n cursor: pointer;\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n pointer-events: auto;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.picker_wrapper.no_alpha .picker_alpha {\n display: none;\n}\n\n.picker_wrapper.no_editor .picker_editor {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.picker_wrapper.no_cancel .picker_cancel {\n display: none;\n}\n\n.layout_default.picker_wrapper {\n display: flex;\n flex-flow: row wrap;\n justify-content: space-between;\n align-items: stretch;\n font-size: 10px;\n width: 25em;\n padding: 0.5em;\n}\n\n.layout_default.picker_wrapper input, .layout_default.picker_wrapper button {\n font-size: 1rem;\n}\n\n.layout_default.picker_wrapper > * {\n margin: 0.5em;\n}\n\n.layout_default.picker_wrapper::before {\n content: \"\";\n display: block;\n width: 100%;\n height: 0;\n order: 1;\n}\n\n.layout_default .picker_slider, .layout_default .picker_selector {\n padding: 1em;\n}\n\n.layout_default .picker_hue {\n width: 100%;\n}\n\n.layout_default .picker_sl {\n flex: 1 1 auto;\n}\n\n.layout_default .picker_sl::before {\n content: \"\";\n display: block;\n padding-bottom: 100%;\n}\n\n.layout_default .picker_editor {\n order: 1;\n width: 6.5rem;\n}\n\n.layout_default .picker_editor input {\n width: 100%;\n height: 100%;\n}\n\n.layout_default .picker_sample {\n order: 1;\n flex: 1 1 auto;\n}\n\n.layout_default .picker_done, .layout_default .picker_cancel {\n order: 1;\n}\n\n.picker_wrapper {\n box-sizing: border-box;\n background: #f2f2f2;\n box-shadow: 0 0 0 1px silver;\n cursor: default;\n font-family: sans-serif;\n color: #444;\n pointer-events: auto;\n}\n\n.picker_wrapper:focus {\n outline: none;\n}\n\n.picker_wrapper button, .picker_wrapper input {\n box-sizing: border-box;\n border: none;\n box-shadow: 0 0 0 1px silver;\n outline: none;\n}\n\n.picker_wrapper button:focus, .picker_wrapper button:active, .picker_wrapper input:focus, .picker_wrapper input:active {\n box-shadow: 0 0 2px 1px #1e90ff;\n}\n\n.picker_wrapper button {\n padding: 0.4em 0.6em;\n cursor: pointer;\n background-color: #f5f5f5;\n background-image: linear-gradient(0deg, gainsboro, transparent);\n}\n\n.picker_wrapper button:active {\n background-image: linear-gradient(0deg, transparent, gainsboro);\n}\n\n.picker_wrapper button:hover {\n background-color: #fff;\n}\n\n.picker_selector {\n position: absolute;\n z-index: 1;\n display: block;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n border: 2px solid #fff;\n border-radius: 100%;\n box-shadow: 0 0 3px 1px #67b9ff;\n background: currentColor;\n cursor: pointer;\n}\n\n.picker_slider .picker_selector {\n border-radius: 2px;\n}\n\n.picker_hue {\n position: relative;\n background-image: linear-gradient(90deg, red, yellow, lime, cyan, blue, magenta, red);\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_sl {\n position: relative;\n box-shadow: 0 0 0 1px silver;\n background-image: linear-gradient(180deg, white, rgba(255, 255, 255, 0) 50%), linear-gradient(0deg, black, rgba(0, 0, 0, 0) 50%), linear-gradient(90deg, #808080, rgba(128, 128, 128, 0));\n}\n\n.picker_alpha, .picker_sample {\n position: relative;\n background: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%) 0 0/2em 2em, linear-gradient(45deg, lightgrey 25%, white 25%, white 75%, lightgrey 75%) 1em 1em/2em 2em;\n box-shadow: 0 0 0 1px silver;\n}\n\n.picker_alpha .picker_selector, .picker_sample .picker_selector {\n background: none;\n}\n\n.picker_editor input {\n font-family: monospace;\n padding: 0.2em 0.4em;\n}\n\n.picker_sample::before {\n content: \"\";\n position: absolute;\n display: block;\n width: 100%;\n height: 100%;\n background: currentColor;\n}\n\n.picker_arrow {\n position: absolute;\n z-index: -1;\n}\n\n.picker_wrapper.popup {\n position: absolute;\n z-index: 2;\n margin: 1.5em;\n}\n\n.picker_wrapper.popup, .picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n background: #f2f2f2;\n box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.4);\n}\n\n.picker_wrapper.popup .picker_arrow {\n width: 3em;\n height: 3em;\n margin: 0;\n}\n\n.picker_wrapper.popup .picker_arrow::before, .picker_wrapper.popup .picker_arrow::after {\n content: \"\";\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n z-index: -99;\n}\n\n.picker_wrapper.popup .picker_arrow::before {\n width: 100%;\n height: 100%;\n -webkit-transform: skew(45deg);\n transform: skew(45deg);\n -webkit-transform-origin: 0 100%;\n transform-origin: 0 100%;\n}\n\n.picker_wrapper.popup .picker_arrow::after {\n width: 150%;\n height: 150%;\n box-shadow: none;\n}\n\n.popup.popup_top {\n bottom: 100%;\n left: 0;\n}\n\n.popup.popup_top .picker_arrow {\n bottom: 0;\n left: 0;\n -webkit-transform: rotate(-90deg);\n transform: rotate(-90deg);\n}\n\n.popup.popup_bottom {\n top: 100%;\n left: 0;\n}\n\n.popup.popup_bottom .picker_arrow {\n top: 0;\n left: 0;\n -webkit-transform: rotate(90deg) scale(1, -1);\n transform: rotate(90deg) scale(1, -1);\n}\n\n.popup.popup_left {\n top: 0;\n right: 100%;\n}\n\n.popup.popup_left .picker_arrow {\n top: 0;\n right: 0;\n -webkit-transform: scale(-1, 1);\n transform: scale(-1, 1);\n}\n\n.popup.popup_right {\n top: 0;\n left: 100%;\n}\n\n.popup.popup_right .picker_arrow {\n top: 0;\n left: 0;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n min-height: 160px;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n line-height: 1;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n position: relative;\n margin-top: 5px;\n width: 100%;\n}\n.igv-axis-column > div > div {\n z-index: 512;\n position: absolute;\n top: 8px;\n left: 8px;\n width: fit-content;\n height: fit-content;\n background-color: transparent;\n display: grid;\n align-items: start;\n justify-items: center;\n}\n.igv-axis-column > div > div > input {\n display: block;\n margin: unset;\n cursor: pointer;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-info-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.igv-track-drag-column .igv-track-drag-handle-color {\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover-color {\n background-color: #787878;\n}\n.igv-track-drag-column .igv-track-drag-handle-selected-color {\n background-color: #0963fa;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-vertical-center {\n margin: 0 !important;\n top: 50% !important;\n -ms-transform: translateY(-50%) !important;\n transform: translateY(-50%) !important;\n}\n\n/*# sourceMappingURL=igv.css.map */\n';
|
|
72914
73625
|
|
|
72915
73626
|
/**
|
|
72916
73627
|
* Manages XQTL selections.
|
|
@@ -73444,7 +74155,7 @@ class Browser {
|
|
|
73444
74155
|
this.dataRangeDialog = new DataRangeDialog(this, this.root);
|
|
73445
74156
|
this.dataRangeDialog.container.id = `igv-data-range-dialog-${guid$2()}`;
|
|
73446
74157
|
|
|
73447
|
-
this.genericColorPicker = new GenericColorPicker({
|
|
74158
|
+
this.genericColorPicker = new GenericColorPicker({parent: this.root, width: 180});
|
|
73448
74159
|
this.genericColorPicker.container.id = `igv-track-color-picker-${guid$2()}`;
|
|
73449
74160
|
|
|
73450
74161
|
this.sliderDialog = new SliderDialog(this.root);
|
|
@@ -73454,10 +74165,10 @@ class Browser {
|
|
|
73454
74165
|
|
|
73455
74166
|
getSampleNameViewportWidth() {
|
|
73456
74167
|
|
|
73457
|
-
if (undefined === this.sampleNameViewportWidth) {
|
|
74168
|
+
if (false === this.showSampleNames || undefined === this.sampleNameViewportWidth) {
|
|
73458
74169
|
return 0
|
|
73459
74170
|
} else {
|
|
73460
|
-
return
|
|
74171
|
+
return this.sampleNameViewportWidth
|
|
73461
74172
|
}
|
|
73462
74173
|
|
|
73463
74174
|
}
|
|
@@ -73619,7 +74330,8 @@ class Browser {
|
|
|
73619
74330
|
} else {
|
|
73620
74331
|
session = options;
|
|
73621
74332
|
}
|
|
73622
|
-
|
|
74333
|
+
|
|
74334
|
+
await this.loadSessionObject(session);
|
|
73623
74335
|
}
|
|
73624
74336
|
|
|
73625
74337
|
/**
|
|
@@ -73648,8 +74360,7 @@ class Browser {
|
|
|
73648
74360
|
config = new XMLSession(string, knownGenomes);
|
|
73649
74361
|
|
|
73650
74362
|
} else if (filename.endsWith("hub.txt")) {
|
|
73651
|
-
|
|
73652
|
-
const hub = await Hub.loadHub(urlOrFile, options);
|
|
74363
|
+
const hub = await loadHub(urlOrFile);
|
|
73653
74364
|
const genomeConfig = hub.getGenomeConfig();
|
|
73654
74365
|
config = {
|
|
73655
74366
|
reference: genomeConfig
|
|
@@ -73740,7 +74451,7 @@ class Browser {
|
|
|
73740
74451
|
}
|
|
73741
74452
|
|
|
73742
74453
|
// ROIs
|
|
73743
|
-
if(session.showROIOverlays !== undefined) {
|
|
74454
|
+
if (session.showROIOverlays !== undefined) {
|
|
73744
74455
|
this.roiManager.showOverlays = session.showROIOverlays;
|
|
73745
74456
|
}
|
|
73746
74457
|
this.roiManager.clearROIs();
|
|
@@ -73754,12 +74465,16 @@ class Browser {
|
|
|
73754
74465
|
// Sample info
|
|
73755
74466
|
const localSampleInfoFiles = [];
|
|
73756
74467
|
if (session.sampleinfo) {
|
|
73757
|
-
for (const
|
|
73758
|
-
|
|
73759
|
-
|
|
73760
|
-
|
|
74468
|
+
for (const sampleInfoConfig of session.sampleinfo) {
|
|
74469
|
+
// The "file" property is recorded in the session when a local file is referenced. It can't be used
|
|
74470
|
+
// on reloading, its only purpose is to present an alert to the user. This could also be used
|
|
74471
|
+
// to prompt the user to load the file manually, but we don't currently do that.
|
|
74472
|
+
if (sampleInfoConfig.file) {
|
|
74473
|
+
localSampleInfoFiles.push(sampleInfoConfig.file);
|
|
73761
74474
|
} else {
|
|
73762
|
-
this.loadSampleInfo(
|
|
74475
|
+
// this.loadSampleInfo(sampleInfoConfig)
|
|
74476
|
+
await this.sampleInfo.loadSampleInfo(sampleInfoConfig);
|
|
74477
|
+
|
|
73763
74478
|
}
|
|
73764
74479
|
|
|
73765
74480
|
}
|
|
@@ -73800,23 +74515,14 @@ class Browser {
|
|
|
73800
74515
|
}
|
|
73801
74516
|
}
|
|
73802
74517
|
|
|
73803
|
-
|
|
73804
|
-
|
|
73805
|
-
|
|
73806
|
-
|
|
73807
|
-
await
|
|
74518
|
+
// Load a hidden track -- used to populate searchable database without creating a track
|
|
74519
|
+
const configHidden = nonLocalTrackConfigurations.filter(config => true === config.hidden);
|
|
74520
|
+
for (const config of configHidden) {
|
|
74521
|
+
const featureSource = FeatureSource(config, this.genome);
|
|
74522
|
+
await featureSource.getFeatures({chr: "1", start: 0, end: Number.MAX_SAFE_INTEGER});
|
|
73808
74523
|
}
|
|
73809
74524
|
|
|
73810
|
-
|
|
73811
|
-
if (this.trackViews.some(tv => tv.track.selected)) {
|
|
73812
|
-
this.navbar.setEnableTrackSelection(true);
|
|
73813
|
-
}
|
|
73814
|
-
|
|
73815
|
-
this.updateUIWithReferenceFrameList();
|
|
73816
|
-
|
|
73817
|
-
this.updateLocusSearchWidget();
|
|
73818
|
-
|
|
73819
|
-
return trackConfigurations
|
|
74525
|
+
await this.loadTrackList(nonLocalTrackConfigurations);
|
|
73820
74526
|
|
|
73821
74527
|
}
|
|
73822
74528
|
|
|
@@ -73877,13 +74583,8 @@ class Browser {
|
|
|
73877
74583
|
}
|
|
73878
74584
|
|
|
73879
74585
|
if (genomeChange) {
|
|
73880
|
-
|
|
73881
|
-
|
|
73882
|
-
// TODO -- refactor this so "hub" is not loaded twice
|
|
73883
|
-
const hub = await Hub.loadHub(genomeConfig.hubURL);
|
|
73884
|
-
trackConfigurations = hub.getGroupedTrackConfigurations();
|
|
73885
|
-
}
|
|
73886
|
-
this.fireEvent('genomechange', [{genome, trackConfigurations}]);
|
|
74586
|
+
|
|
74587
|
+
this.fireEvent('genomechange', [{genome}]);
|
|
73887
74588
|
|
|
73888
74589
|
if (this.circularView) {
|
|
73889
74590
|
this.circularView.setAssembly({
|
|
@@ -73922,7 +74623,7 @@ class Browser {
|
|
|
73922
74623
|
let genomeConfig;
|
|
73923
74624
|
const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
|
|
73924
74625
|
if (isHubGenome) {
|
|
73925
|
-
const hub = await
|
|
74626
|
+
const hub = await loadHub(idOrConfig.hubURL || idOrConfig.url);
|
|
73926
74627
|
genomeConfig = hub.getGenomeConfig();
|
|
73927
74628
|
} else if (isString$2(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
|
|
73928
74629
|
// Either an ID, a json string, or an object missing required properties.
|
|
@@ -73952,22 +74653,9 @@ class Browser {
|
|
|
73952
74653
|
|
|
73953
74654
|
await this.loadTrackList(tracks);
|
|
73954
74655
|
|
|
73955
|
-
await this.updateViews();
|
|
73956
|
-
|
|
73957
74656
|
return this.genome
|
|
73958
74657
|
}
|
|
73959
74658
|
|
|
73960
|
-
/**
|
|
73961
|
-
* Load a UCSC single-file genome assembly hub.
|
|
73962
|
-
* @param options
|
|
73963
|
-
* @returns {Promise<void>}
|
|
73964
|
-
*/
|
|
73965
|
-
async loadTrackHub(options) {
|
|
73966
|
-
const hub = await Hub.loadHub(options.url, options);
|
|
73967
|
-
const genomeConfig = setDefaults(hub.getGenomeConfig());
|
|
73968
|
-
return this.loadGenome(genomeConfig)
|
|
73969
|
-
}
|
|
73970
|
-
|
|
73971
74659
|
/**
|
|
73972
74660
|
* Called after a session load, search, pan (horizontal drag), or resize
|
|
73973
74661
|
*
|
|
@@ -74064,18 +74752,23 @@ class Browser {
|
|
|
74064
74752
|
}
|
|
74065
74753
|
|
|
74066
74754
|
const promises = [];
|
|
74067
|
-
for (
|
|
74068
|
-
promises.push(this
|
|
74755
|
+
for (const config of configList) {
|
|
74756
|
+
promises.push(this.#loadTrackHelper(config));
|
|
74069
74757
|
}
|
|
74070
74758
|
|
|
74071
74759
|
const loadedTracks = await Promise.all(promises);
|
|
74072
74760
|
|
|
74073
|
-
|
|
74074
|
-
|
|
74075
|
-
|
|
74076
|
-
if (groupAutoscaleViews.length > 0) {
|
|
74077
|
-
this.updateViews();
|
|
74761
|
+
// If any tracks are selected show the selection buttons
|
|
74762
|
+
if (this.trackViews.some(({track}) => track.selected)) {
|
|
74763
|
+
this.navbar.setEnableTrackSelection(true);
|
|
74078
74764
|
}
|
|
74765
|
+
|
|
74766
|
+
this.reorderTracks();
|
|
74767
|
+
|
|
74768
|
+
await resize.call(this);
|
|
74769
|
+
|
|
74770
|
+
this.fireEvent('trackorderchanged', [this.getTrackOrder()]);
|
|
74771
|
+
|
|
74079
74772
|
return loadedTracks
|
|
74080
74773
|
}
|
|
74081
74774
|
|
|
@@ -74089,54 +74782,23 @@ class Browser {
|
|
|
74089
74782
|
*/
|
|
74090
74783
|
async loadTrack(config) {
|
|
74091
74784
|
|
|
74092
|
-
|
|
74093
|
-
|
|
74094
|
-
|
|
74095
|
-
const newTrack = this._loadTrack(config);
|
|
74096
|
-
|
|
74097
|
-
if (newTrack && config.autoscaleGroup) {
|
|
74098
|
-
// Await newTrack load and update all views
|
|
74099
|
-
await newTrack;
|
|
74785
|
+
const loadedTracks = await this.loadTrackList([config]);
|
|
74786
|
+
if (config.autoscaleGroup) {
|
|
74100
74787
|
this.updateViews();
|
|
74101
74788
|
}
|
|
74102
|
-
|
|
74103
|
-
return newTrack
|
|
74789
|
+
return loadedTracks[0]
|
|
74104
74790
|
}
|
|
74105
74791
|
|
|
74106
|
-
|
|
74107
|
-
* Return a promise to load a track. Private function used by loadTrack() and loadTrackList()
|
|
74108
|
-
*
|
|
74109
|
-
* @param config
|
|
74110
|
-
* @returns {*}
|
|
74111
|
-
*/
|
|
74112
|
-
|
|
74113
|
-
async _loadTrack(config) {
|
|
74792
|
+
async #loadTrackHelper(config) {
|
|
74114
74793
|
|
|
74115
74794
|
// config might be json
|
|
74116
74795
|
if (isString$2(config)) {
|
|
74117
74796
|
config = JSON.parse(config);
|
|
74118
74797
|
}
|
|
74119
74798
|
|
|
74799
|
+
let track;
|
|
74120
74800
|
try {
|
|
74121
|
-
|
|
74122
|
-
// Load a hidden track -- used to populate searchable database without creating a track
|
|
74123
|
-
if (config.hidden) {
|
|
74124
|
-
const featureSource = FeatureSource(config, this.genome);
|
|
74125
|
-
await featureSource.getFeatures({chr: "1", start: 0, end: Number.MAX_SAFE_INTEGER});
|
|
74126
|
-
return
|
|
74127
|
-
}
|
|
74128
|
-
|
|
74129
|
-
const newTrack = await this.createTrack(config);
|
|
74130
|
-
|
|
74131
|
-
if ('sampleinfo' === config.type) {
|
|
74132
|
-
this.layoutChange();
|
|
74133
|
-
return
|
|
74134
|
-
} else if (undefined === newTrack) {
|
|
74135
|
-
return
|
|
74136
|
-
}
|
|
74137
|
-
|
|
74138
|
-
return this.addTrack(config, newTrack)
|
|
74139
|
-
|
|
74801
|
+
track = await this.createTrack(config);
|
|
74140
74802
|
} catch (error) {
|
|
74141
74803
|
|
|
74142
74804
|
let msg = error.message || error.error || error.toString();
|
|
@@ -74153,62 +74815,53 @@ class Browser {
|
|
|
74153
74815
|
}
|
|
74154
74816
|
|
|
74155
74817
|
msg = `${msg} : ${isFile(config.url) ? config.url.name : config.url}`;
|
|
74156
|
-
// msg += (": " + FileUtils.isFile(config.url) ? config.url.name : config.url)
|
|
74157
74818
|
const err = new Error(msg);
|
|
74158
74819
|
console.error(err);
|
|
74159
74820
|
throw err
|
|
74160
|
-
// this.alert.present(new Error(msg), undefined)
|
|
74161
74821
|
}
|
|
74162
|
-
}
|
|
74163
74822
|
|
|
74164
|
-
|
|
74823
|
+
if (track) {
|
|
74824
|
+
return await this.addTrack(track)
|
|
74825
|
+
} else {
|
|
74826
|
+
return undefined
|
|
74827
|
+
}
|
|
74828
|
+
|
|
74829
|
+
}
|
|
74165
74830
|
|
|
74831
|
+
async addTrack(track) {
|
|
74166
74832
|
|
|
74167
74833
|
// Set order field of track here, otherwise track order might get shuffled during asynchronous load
|
|
74168
|
-
if (undefined ===
|
|
74169
|
-
|
|
74834
|
+
if (undefined === track.order) {
|
|
74835
|
+
track.order = this.trackViews.length;
|
|
74170
74836
|
}
|
|
74171
74837
|
|
|
74172
|
-
const trackView = new TrackView(this, this.columnContainer,
|
|
74838
|
+
const trackView = new TrackView(this, this.columnContainer, track);
|
|
74173
74839
|
this.trackViews.push(trackView);
|
|
74174
74840
|
toggleTrackLabels(this.trackViews, this.doShowTrackLabels);
|
|
74175
74841
|
|
|
74176
|
-
if (typeof
|
|
74842
|
+
if (typeof track.postInit === 'function') {
|
|
74177
74843
|
try {
|
|
74178
74844
|
trackView.startSpinner();
|
|
74179
|
-
await
|
|
74845
|
+
await track.postInit();
|
|
74180
74846
|
} finally {
|
|
74181
74847
|
trackView.stopSpinner();
|
|
74182
74848
|
}
|
|
74183
74849
|
}
|
|
74184
74850
|
|
|
74185
|
-
if (
|
|
74186
|
-
// Group autoscale will get updated later (as a group)
|
|
74187
|
-
if (config.sync) {
|
|
74188
|
-
await trackView.updateViews();
|
|
74189
|
-
} else {
|
|
74190
|
-
trackView.updateViews();
|
|
74191
|
-
}
|
|
74192
|
-
}
|
|
74193
|
-
|
|
74194
|
-
if (typeof newTrack.hasSamples === 'function' && newTrack.hasSamples()) {
|
|
74851
|
+
if (typeof track.hasSamples === 'function' && track.hasSamples()) {
|
|
74195
74852
|
|
|
74196
74853
|
if (this.sampleInfo.hasAttributes()) {
|
|
74197
74854
|
this.sampleInfoControl.setButtonVisibility(true);
|
|
74198
74855
|
}
|
|
74199
74856
|
|
|
74200
74857
|
if (this.config.showSampleNameButton !== false) {
|
|
74201
|
-
this.sampleNameControl.show();
|
|
74858
|
+
this.sampleNameControl.show();
|
|
74202
74859
|
}
|
|
74203
74860
|
}
|
|
74204
74861
|
|
|
74205
|
-
|
|
74206
|
-
this.reorderTracks();
|
|
74207
|
-
this.fireEvent('trackorderchanged', [this.getTrackOrder()]);
|
|
74862
|
+
track.trackView.enableTrackSelection(this.navbar.getEnableTrackSelection());
|
|
74208
74863
|
|
|
74209
|
-
|
|
74210
|
-
|
|
74211
|
-
return newTrack
|
|
74864
|
+
return track
|
|
74212
74865
|
|
|
74213
74866
|
}
|
|
74214
74867
|
|
|
@@ -74353,7 +75006,6 @@ class Browser {
|
|
|
74353
75006
|
}
|
|
74354
75007
|
}
|
|
74355
75008
|
|
|
74356
|
-
|
|
74357
75009
|
reorderTracks() {
|
|
74358
75010
|
|
|
74359
75011
|
this.trackViews.sort(function (a, b) {
|
|
@@ -74590,19 +75242,19 @@ class Browser {
|
|
|
74590
75242
|
|
|
74591
75243
|
this.updateLocusSearchWidget();
|
|
74592
75244
|
|
|
74593
|
-
for (
|
|
74594
|
-
if (
|
|
74595
|
-
await this.genome.getSequence(
|
|
75245
|
+
for (const {bpPerPixel, chr, start} of this.referenceFrameList) {
|
|
75246
|
+
if (bpPerPixel <= bppSequenceThreshold) {
|
|
75247
|
+
await this.genome.getSequence(chr, start, start + 1);
|
|
74596
75248
|
}
|
|
74597
75249
|
}
|
|
74598
75250
|
|
|
74599
|
-
for (
|
|
75251
|
+
for (const centerGuide of this.centerLineList) {
|
|
74600
75252
|
centerGuide.repaint();
|
|
74601
75253
|
}
|
|
74602
75254
|
|
|
74603
75255
|
// Don't autoscale while dragging.
|
|
74604
75256
|
if (this.dragObject) {
|
|
74605
|
-
for (
|
|
75257
|
+
for (const trackView of trackViews) {
|
|
74606
75258
|
await trackView.updateViews();
|
|
74607
75259
|
}
|
|
74608
75260
|
} else {
|
|
@@ -74626,8 +75278,8 @@ class Browser {
|
|
|
74626
75278
|
// Calculate group autoscale dataRange
|
|
74627
75279
|
if (Object.entries(groupAutoscaleTrackViews).length > 0) {
|
|
74628
75280
|
for (const [group, trackViews] of Object.entries(groupAutoscaleTrackViews)) {
|
|
74629
|
-
const
|
|
74630
|
-
const dataRange = doAutoscale(
|
|
75281
|
+
const inViewFeatures = await Promise.all(trackViews.map(trackView => trackView.getInViewFeatures()));
|
|
75282
|
+
const dataRange = doAutoscale(inViewFeatures.flat());
|
|
74631
75283
|
for (const trackView of trackViews) {
|
|
74632
75284
|
trackView.track.dataRange = Object.assign({}, dataRange);
|
|
74633
75285
|
trackView.track.autoscale = false;
|
|
@@ -74636,7 +75288,7 @@ class Browser {
|
|
|
74636
75288
|
}
|
|
74637
75289
|
}
|
|
74638
75290
|
|
|
74639
|
-
await Promise.all(otherTrackViews.map(
|
|
75291
|
+
await Promise.all(otherTrackViews.map(trackView => trackView.updateViews()));
|
|
74640
75292
|
}
|
|
74641
75293
|
|
|
74642
75294
|
}
|
|
@@ -74659,10 +75311,10 @@ class Browser {
|
|
|
74659
75311
|
referenceFrame.end = referenceFrame.start + referenceFrame.bpPerPixel * width;
|
|
74660
75312
|
}
|
|
74661
75313
|
|
|
74662
|
-
const chrName = referenceFrameList.length === 1 ? this.referenceFrameList[0].chr : '';
|
|
74663
|
-
|
|
74664
75314
|
const loc = this.referenceFrameList.map(rf => rf.getLocusString()).join(' ');
|
|
74665
75315
|
|
|
75316
|
+
const chrName = referenceFrameList.length === 1 ? this.genome.getChromosomeDisplayName(this.referenceFrameList[0].chr) : '';
|
|
75317
|
+
|
|
74666
75318
|
this.navbar.updateLocus(loc, chrName);
|
|
74667
75319
|
|
|
74668
75320
|
this.fireEvent('locuschange', [this.referenceFrameList]);
|
|
@@ -74679,6 +75331,46 @@ class Browser {
|
|
|
74679
75331
|
return Math.floor(width / columnCount)
|
|
74680
75332
|
}
|
|
74681
75333
|
|
|
75334
|
+
/**
|
|
75335
|
+
* Update reference frames based on new viewport width
|
|
75336
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
75337
|
+
*/
|
|
75338
|
+
updateReferenceFrames(viewportWidth) {
|
|
75339
|
+
|
|
75340
|
+
for (const referenceFrame of this.referenceFrameList) {
|
|
75341
|
+
referenceFrame.updateForViewportWidth(viewportWidth);
|
|
75342
|
+
}
|
|
75343
|
+
}
|
|
75344
|
+
|
|
75345
|
+
/**
|
|
75346
|
+
* Update DOM viewport elements with new width
|
|
75347
|
+
* @param {number} viewportWidth - The calculated viewport width
|
|
75348
|
+
*/
|
|
75349
|
+
updateViewportElements(viewportWidth) {
|
|
75350
|
+
|
|
75351
|
+
for (let i = 0; i < this.referenceFrameList.length; i++) {
|
|
75352
|
+
|
|
75353
|
+
for (const {viewports} of this.trackViews) {
|
|
75354
|
+
viewports[i].setWidth(viewportWidth);
|
|
75355
|
+
}
|
|
75356
|
+
|
|
75357
|
+
for (const {sampleInfoViewport} of this.trackViews) {
|
|
75358
|
+
sampleInfoViewport.setWidth(this.getSampleInfoColumnWidth());
|
|
75359
|
+
sampleInfoViewport.repaint();
|
|
75360
|
+
}
|
|
75361
|
+
|
|
75362
|
+
}
|
|
75363
|
+
}
|
|
75364
|
+
|
|
75365
|
+
/**
|
|
75366
|
+
* Synchronize UI state after viewport updates
|
|
75367
|
+
* @returns {Promise<void>}
|
|
75368
|
+
*/
|
|
75369
|
+
async syncUIState() {
|
|
75370
|
+
this.updateUIWithReferenceFrameList();
|
|
75371
|
+
await this.updateViews(true);
|
|
75372
|
+
}
|
|
75373
|
+
|
|
74682
75374
|
minimumBases() {
|
|
74683
75375
|
return this.config.minimumBases
|
|
74684
75376
|
}
|
|
@@ -74864,29 +75556,12 @@ class Browser {
|
|
|
74864
75556
|
}
|
|
74865
75557
|
|
|
74866
75558
|
/**
|
|
74867
|
-
* @deprecated This is a deprecated method with no known usages.
|
|
75559
|
+
* @deprecated This is a deprecated method with no known usages.
|
|
74868
75560
|
*/
|
|
74869
75561
|
async goto(chr, start, end) {
|
|
74870
75562
|
await this.search(chr + ":" + start + "-" + end);
|
|
74871
75563
|
}
|
|
74872
75564
|
|
|
74873
|
-
/**
|
|
74874
|
-
|
|
74875
|
-
* Search for the locus string -- this function is called from various igv.js GUI elements, and is not part of the
|
|
74876
|
-
* API. Wraps ```search``` and presents an error dialog if false.
|
|
74877
|
-
*
|
|
74878
|
-
* @param string
|
|
74879
|
-
* @param init
|
|
74880
|
-
* @returns {Promise<void>}
|
|
74881
|
-
*/
|
|
74882
|
-
async doSearch(string, init) {
|
|
74883
|
-
const success = await this.search(string, init);
|
|
74884
|
-
if (!success) {
|
|
74885
|
-
this.alert.present(new Error(`Unrecognized locus: <b> ${string} </b>`));
|
|
74886
|
-
}
|
|
74887
|
-
return success
|
|
74888
|
-
}
|
|
74889
|
-
|
|
74890
75565
|
|
|
74891
75566
|
/**
|
|
74892
75567
|
* Search for the locus string
|
|
@@ -74899,6 +75574,10 @@ class Browser {
|
|
|
74899
75574
|
async search(stringOrArray, init) {
|
|
74900
75575
|
|
|
74901
75576
|
const loci = await search(this, stringOrArray);
|
|
75577
|
+
return this.updateLoci(loci, init)
|
|
75578
|
+
}
|
|
75579
|
+
|
|
75580
|
+
async updateLoci(loci, init) {
|
|
74902
75581
|
|
|
74903
75582
|
if (loci && loci.length > 0) {
|
|
74904
75583
|
|
|
@@ -74935,9 +75614,9 @@ class Browser {
|
|
|
74935
75614
|
}
|
|
74936
75615
|
}
|
|
74937
75616
|
|
|
74938
|
-
async loadSampleInfo(
|
|
75617
|
+
async loadSampleInfo(sampleInfoConfig) {
|
|
74939
75618
|
|
|
74940
|
-
await this.sampleInfo.
|
|
75619
|
+
await this.sampleInfo.loadSampleInfo(sampleInfoConfig);
|
|
74941
75620
|
|
|
74942
75621
|
for (const {sampleInfoViewport} of this.trackViews) {
|
|
74943
75622
|
sampleInfoViewport.setWidth(this.getSampleInfoColumnWidth());
|
|
@@ -75075,9 +75754,9 @@ class Browser {
|
|
|
75075
75754
|
json["locus"] = locus.length === 1 ? locus[0] : locus;
|
|
75076
75755
|
|
|
75077
75756
|
const roiSets = this.roiManager.toJSON();
|
|
75078
|
-
if(roiSets) {
|
|
75757
|
+
if (roiSets) {
|
|
75079
75758
|
json["roi"] = roiSets;
|
|
75080
|
-
if(!this.roiManager.showOverlays){
|
|
75759
|
+
if (!this.roiManager.showOverlays) {
|
|
75081
75760
|
json["showROIOverlays"] = false; // true is the default
|
|
75082
75761
|
}
|
|
75083
75762
|
}
|
|
@@ -75420,8 +76099,6 @@ class Browser {
|
|
|
75420
76099
|
}
|
|
75421
76100
|
}
|
|
75422
76101
|
|
|
75423
|
-
|
|
75424
|
-
|
|
75425
76102
|
// Navbar delegates
|
|
75426
76103
|
get sampleInfoControl() {
|
|
75427
76104
|
return this.navbar.sampleInfoControl
|
|
@@ -75443,6 +76120,9 @@ class Browser {
|
|
|
75443
76120
|
return this.navbar.sampleNameControl
|
|
75444
76121
|
}
|
|
75445
76122
|
|
|
76123
|
+
async blat(sequence) {
|
|
76124
|
+
return createBlatTrack({sequence, browser: this, name: 'Blat', title: 'Blat'})
|
|
76125
|
+
}
|
|
75446
76126
|
}
|
|
75447
76127
|
|
|
75448
76128
|
function getFileExtension(input) {
|
|
@@ -75468,7 +76148,7 @@ function getFileExtension(input) {
|
|
|
75468
76148
|
}
|
|
75469
76149
|
|
|
75470
76150
|
/**
|
|
75471
|
-
*
|
|
76151
|
+
* Called when window is resized, or visibility changed (e.g. "show" from a tab). This is a function rather
|
|
75472
76152
|
* than class method because it needs to be copied and bound to specific instances of browser to support listener
|
|
75473
76153
|
* removal
|
|
75474
76154
|
*
|
|
@@ -75476,40 +76156,14 @@ function getFileExtension(input) {
|
|
|
75476
76156
|
*/
|
|
75477
76157
|
async function resize() {
|
|
75478
76158
|
|
|
75479
|
-
if (
|
|
75480
|
-
|
|
75481
|
-
const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
|
|
75482
|
-
|
|
75483
|
-
for (let referenceFrame of this.referenceFrameList) {
|
|
75484
|
-
|
|
75485
|
-
const index = this.referenceFrameList.indexOf(referenceFrame);
|
|
75486
|
-
|
|
75487
|
-
const {chr, genome} = referenceFrame;
|
|
75488
|
-
|
|
75489
|
-
const {bpLength} = genome.getChromosome(referenceFrame.chr);
|
|
75490
|
-
|
|
75491
|
-
const viewportWidthBP = referenceFrame.toBP(viewportWidth);
|
|
75492
|
-
|
|
75493
|
-
// viewportWidthBP > bpLength occurs when locus is full chromosome and user widens browser
|
|
75494
|
-
if (GenomeUtils.isWholeGenomeView(chr) || viewportWidthBP > bpLength) {
|
|
75495
|
-
// console.log(`${ Date.now() } Recalc referenceFrame(${ index }) bpp. viewport ${ StringUtils.numberFormatter(viewportWidthBP) } > ${ StringUtils.numberFormatter(bpLength) }.`)
|
|
75496
|
-
referenceFrame.bpPerPixel = bpLength / viewportWidth;
|
|
75497
|
-
} else {
|
|
75498
|
-
// console.log(`${ Date.now() } Recalc referenceFrame(${ index }) end.`)
|
|
75499
|
-
referenceFrame.end = referenceFrame.start + referenceFrame.toBP(viewportWidth);
|
|
75500
|
-
}
|
|
75501
|
-
|
|
75502
|
-
for (let {viewports} of this.trackViews) {
|
|
75503
|
-
viewports[index].setWidth(viewportWidth);
|
|
75504
|
-
}
|
|
75505
|
-
|
|
76159
|
+
if (undefined === this.referenceFrameList || 0 === this.referenceFrameList.length) {
|
|
76160
|
+
return
|
|
75506
76161
|
}
|
|
75507
76162
|
|
|
75508
|
-
this.
|
|
75509
|
-
|
|
75510
|
-
|
|
75511
|
-
|
|
75512
|
-
await this.updateViews(true);
|
|
76163
|
+
const viewportWidth = this.calculateViewportWidth(this.referenceFrameList.length);
|
|
76164
|
+
this.updateReferenceFrames(viewportWidth);
|
|
76165
|
+
this.updateViewportElements(viewportWidth);
|
|
76166
|
+
await this.syncUIState();
|
|
75513
76167
|
}
|
|
75514
76168
|
|
|
75515
76169
|
|
|
@@ -75978,7 +76632,8 @@ var index = {
|
|
|
75978
76632
|
registerTrackClass,
|
|
75979
76633
|
registerTrackCreatorFunction,
|
|
75980
76634
|
registerFileFormats,
|
|
75981
|
-
loadSessionFile: Browser.loadSessionFile
|
|
76635
|
+
loadSessionFile: Browser.loadSessionFile,
|
|
76636
|
+
loadHub
|
|
75982
76637
|
};
|
|
75983
76638
|
|
|
75984
76639
|
export { index as default };
|