igv 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/igv.js CHANGED
@@ -30750,260 +30750,660 @@
30750
30750
 
30751
30751
  }
30752
30752
 
30753
- const DEFAULT_MAX_WG_COUNT = 10000;
30754
-
30755
- /**
30756
- * feature source for "bed like" files (tab or whitespace delimited files with 1 feature per line: bed, gff, vcf, etc)
30753
+ /*
30754
+ * The MIT License (MIT)
30757
30755
  *
30758
- * @param config
30759
- * @constructor
30756
+ * Copyright (c) 2016 University of California San Diego
30757
+ * Author: Jim Robinson
30758
+ *
30759
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
30760
+ * of this software and associated documentation files (the "Software"), to deal
30761
+ * in the Software without restriction, including without limitation the rights
30762
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30763
+ * copies of the Software, and to permit persons to whom the Software is
30764
+ * furnished to do so, subject to the following conditions:
30765
+ *
30766
+ * The above copyright notice and this permission notice shall be included in
30767
+ * all copies or substantial portions of the Software.
30768
+ *
30769
+ *
30770
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30771
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30772
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30773
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30774
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30775
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30776
+ * THE SOFTWARE.
30760
30777
  */
30761
- class TextFeatureSource extends BaseFeatureSource {
30762
30778
 
30763
- constructor(config, genome) {
30779
+ const GZIP_FLAG = 0x1;
30764
30780
 
30765
- super(genome);
30781
+ class TDFReader {
30766
30782
 
30767
- this.config = config || {};
30783
+ constructor(config, genome) {
30784
+ this.config = config;
30768
30785
  this.genome = genome;
30769
- this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
30770
- this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
30771
- this.windowFunctions = ["mean", "min", "max", "none"];
30786
+ this.path = config.url;
30787
+ this.groupCache = {};
30788
+ this.datasetCache = {};
30789
+ }
30772
30790
 
30773
- const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
30774
30791
 
30775
- this.queryable = config.indexURL || config.queryable === true; // False by default, unless explicitly set
30776
- if (config.reader) {
30777
- // Explicit reader implementation
30778
- this.reader = config.reader;
30779
- this.queryable = config.queryable !== false;
30780
- } else if (config.sourceType === "ga4gh") {
30781
- throw Error("Unsupported source type 'ga4gh'")
30782
- } else if ((config.type === "eqtl" || config.type === "qtl") && config.sourceType === "gtex-ws") {
30783
- this.reader = new GtexReader(config);
30784
- this.queryable = true;
30785
- } else if ("htsget" === config.sourceType) {
30786
- this.reader = new HtsgetVariantReader(config, genome);
30787
- this.queryable = true;
30788
- } else if (config.sourceType === 'ucscservice') {
30789
- this.reader = new UCSCServiceReader(config.source);
30790
- this.queryable = true;
30791
- } else if (config.sourceType === 'custom') {
30792
- this.reader = new CustomServiceReader(config.source);
30793
- this.queryable = false !== config.source.queryable;
30794
- } else if ('service' === config.sourceType) {
30795
- this.reader = new FeatureFileReader(config, genome);
30796
- this.queryable = true;
30797
- } else {
30798
- // File of some type (i.e. not a webservice)
30799
- this.reader = new FeatureFileReader(config, genome);
30800
- if (config.queryable !== undefined) {
30801
- this.queryable = config.queryable;
30802
- } else if (queryableFormats.has(config.format) || this.reader.indexed) {
30803
- this.queryable = true;
30804
- } else ;
30792
+ async readHeader() {
30793
+
30794
+ if (this.magic !== undefined) {
30795
+ return this // Already read
30805
30796
  }
30806
30797
 
30807
- // Flag indicating if features loaded by this source can be searched for by name or attribute, true by default
30808
- this.searchable = config.searchable !== false;
30798
+ let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start: 0, size: 64000}}));
30799
+ let binaryParser = new BinaryParser$1(new DataView(data));
30800
+ this.magic = binaryParser.getInt();
30801
+ this.version = binaryParser.getInt();
30802
+ this.indexPos = binaryParser.getLong();
30803
+ this.indexSize = binaryParser.getInt();
30804
+ binaryParser.getInt();
30805
+
30806
+
30807
+ if (this.version >= 2) {
30808
+ let nWindowFunctions = binaryParser.getInt();
30809
+ this.windowFunctions = [];
30810
+ while (nWindowFunctions-- > 0) {
30811
+ this.windowFunctions.push(binaryParser.getString());
30812
+ }
30813
+ }
30814
+
30815
+ this.trackType = binaryParser.getString();
30816
+ this.trackLine = binaryParser.getString();
30817
+
30818
+ let nTracks = binaryParser.getInt();
30819
+ this.trackNames = [];
30820
+ while (nTracks-- > 0) {
30821
+ this.trackNames.push(binaryParser.getString());
30822
+ }
30823
+ this.genomeID = binaryParser.getString();
30824
+ this.flags = binaryParser.getInt();
30825
+ this.compressed = (this.flags & GZIP_FLAG) !== 0;
30809
30826
 
30827
+ // Now read index
30828
+ data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30829
+ range: {
30830
+ start: this.indexPos,
30831
+ size: this.indexSize
30832
+ }
30833
+ }));
30834
+ binaryParser = new BinaryParser$1(new DataView(data));
30835
+ this.datasetIndex = {};
30836
+ let nEntries = binaryParser.getInt();
30837
+ while (nEntries-- > 0) {
30838
+ const name = binaryParser.getString();
30839
+ const pos = binaryParser.getLong();
30840
+ const size = binaryParser.getInt();
30841
+ this.datasetIndex[name] = {position: pos, size: size};
30842
+ }
30843
+
30844
+ this.groupIndex = {};
30845
+ nEntries = binaryParser.getInt();
30846
+ while (nEntries-- > 0) {
30847
+ const name = binaryParser.getString();
30848
+ const pos = binaryParser.getLong();
30849
+ const size = binaryParser.getInt();
30850
+ this.groupIndex[name] = {position: pos, size: size};
30851
+ }
30852
+
30853
+ return this
30810
30854
  }
30811
30855
 
30812
- async defaultVisibilityWindow() {
30813
- if (this.reader && typeof this.reader.defaultVisibilityWindow === 'function') {
30814
- return this.reader.defaultVisibilityWindow()
30856
+ async readDataset(chr, windowFunction, zoom) {
30857
+
30858
+ const key = chr + "_" + windowFunction + "_" + zoom;
30859
+
30860
+ if (this.datasetCache[key]) {
30861
+ return this.datasetCache[key]
30862
+
30863
+ } else {
30864
+ await this.readHeader();
30865
+ const wf = (this.version < 2) ? "" : "/" + windowFunction;
30866
+ const zoomString = (chr.toLowerCase() === "all" || zoom === undefined) ? "0" : zoom.toString();
30867
+
30868
+ let dsName;
30869
+ if (windowFunction === "raw") {
30870
+ dsName = "/" + chr + "/raw";
30871
+ } else {
30872
+ dsName = "/" + chr + "/z" + zoomString + wf;
30873
+ }
30874
+ const indexEntry = this.datasetIndex[dsName];
30875
+
30876
+ if (indexEntry === undefined) {
30877
+ return undefined
30878
+ }
30879
+
30880
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30881
+ range: {
30882
+ start: indexEntry.position,
30883
+ size: indexEntry.size
30884
+ }
30885
+ }));
30886
+
30887
+ if (!data) {
30888
+ return undefined
30889
+ }
30890
+
30891
+ const binaryParser = new BinaryParser$1(new DataView(data));
30892
+ let nAttributes = binaryParser.getInt();
30893
+ const attributes = {};
30894
+ while (nAttributes-- > 0) {
30895
+ attributes[binaryParser.getString()] = binaryParser.getString();
30896
+ }
30897
+ const dataType = binaryParser.getString();
30898
+ const tileWidth = binaryParser.getFloat();
30899
+ let nTiles = binaryParser.getInt();
30900
+ const tiles = [];
30901
+ while (nTiles-- > 0) {
30902
+ tiles.push({position: binaryParser.getLong(), size: binaryParser.getInt()});
30903
+ }
30904
+
30905
+ const dataset = {
30906
+ name: dsName,
30907
+ attributes: attributes,
30908
+ dataType: dataType,
30909
+ tileWidth: tileWidth,
30910
+ tiles: tiles
30911
+ };
30912
+
30913
+ this.datasetCache[key] = dataset;
30914
+ return dataset
30815
30915
  }
30816
30916
  }
30817
30917
 
30818
- async trackType() {
30819
- const header = await this.getHeader();
30820
- if (header) {
30821
- return header.type
30918
+ async readRootGroup() {
30919
+
30920
+ const genome = this.genome;
30921
+ const rootGroup = this.groupCache["/"];
30922
+ if (rootGroup) {
30923
+ return rootGroup
30822
30924
  } else {
30823
- return undefined // Convention for unknown or unspecified
30925
+
30926
+ const group = await this.readGroup("/");
30927
+ const names = group["chromosomes"];
30928
+ const maxZoomString = group["maxZoom"];
30929
+
30930
+ // Now parse out interesting attributes.
30931
+ if (maxZoomString) {
30932
+ this.maxZoom = Number(maxZoomString);
30933
+ }
30934
+
30935
+ const totalCountString = group["totalCount"];
30936
+ if (totalCountString) {
30937
+ group.totalCount = Number(totalCountString);
30938
+ }
30939
+
30940
+ // Chromosome names
30941
+ const chrAliasTable = {};
30942
+ if (names) {
30943
+ names.split(",").forEach(function (chr) {
30944
+ const canonicalName = genome.getChromosomeName(chr);
30945
+ chrAliasTable[canonicalName] = chr;
30946
+ });
30947
+ }
30948
+ this.chrAliasTable = chrAliasTable;
30949
+
30950
+ this.groupCache["/"] = group;
30951
+ return group
30824
30952
  }
30825
30953
  }
30826
30954
 
30827
- async getHeader() {
30828
- if (!this.header) {
30955
+ async readGroup(name) {
30829
30956
 
30830
- if (this.reader && typeof this.reader.readHeader === "function") {
30831
- const header = await this.reader.readHeader();
30832
- if (header) {
30833
- this.header = header;
30834
- if (header.format) {
30835
- this.config.format = header.format;
30836
- }
30837
- } else {
30838
- this.header = {};
30957
+ const group = this.groupCache[name];
30958
+ if (group) {
30959
+ return group
30960
+ } else {
30961
+
30962
+ await this.readHeader();
30963
+ const indexEntry = this.groupIndex[name];
30964
+ if (indexEntry === undefined) {
30965
+ return undefined
30966
+ }
30967
+
30968
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30969
+ range: {
30970
+ start: indexEntry.position,
30971
+ size: indexEntry.size
30839
30972
  }
30840
- } else {
30841
- this.header = {};
30973
+ }));
30974
+
30975
+ if (!data) {
30976
+ return undefined
30842
30977
  }
30978
+
30979
+ const binaryParser = new BinaryParser$1(new DataView(data));
30980
+ const group = {name: name};
30981
+ let nAttributes = binaryParser.getInt();
30982
+ while (nAttributes-- > 0) {
30983
+ const key = binaryParser.getString();
30984
+ const value = binaryParser.getString();
30985
+ group[key] = value;
30986
+ }
30987
+ this.groupCache[name] = group;
30988
+ return group
30843
30989
  }
30844
- return this.header
30845
30990
  }
30846
30991
 
30847
- /**
30848
- * Required function for all data source objects. Fetches features for the
30849
- * range requested.
30850
- *
30851
- * This function is quite complex due to the variety of reader types backing it, some indexed, some queryable,
30852
- * some not.
30853
- *
30854
- * @param chr
30855
- * @param start
30856
- * @param end
30857
- * @param bpPerPixel
30858
- */
30859
- async getFeatures({chr, start, end, bpPerPixel, visibilityWindow}) {
30992
+ async readTiles(tileIndeces, nTracks) {
30860
30993
 
30861
- const isWholeGenome = ("all" === chr.toLowerCase());
30994
+ tileIndeces.sort(function (a, b) {
30995
+ return a.position - b.position
30996
+ });
30862
30997
 
30863
- start = start || 0;
30864
- end = end || Number.MAX_SAFE_INTEGER;
30998
+ tileIndeces = tileIndeces.filter(function (idx) {
30999
+ return idx.size > 0
31000
+ });
30865
31001
 
30866
- // Various conditions that can require a feature load
30867
- // * view is "whole genome" but no features are loaded
30868
- // * cache is disabled
30869
- // * cache does not contain requested range
30870
- // const containsRange = this.featureCache.containsRange(new GenomicInterval(queryChr, start, end))
30871
- if ((isWholeGenome && !this.wgFeatures && this.supportsWholeGenome()) ||
30872
- this.config.disableCache ||
30873
- !this.featureCache ||
30874
- !this.featureCache.containsRange(new GenomicInterval(chr, start, end))) {
30875
- await this.loadFeatures(chr, start, end, visibilityWindow);
31002
+ if (tileIndeces.length === 0) {
31003
+ return []
30876
31004
  }
30877
31005
 
30878
- if (isWholeGenome) {
30879
- if (!this.wgFeatures) {
30880
- if (this.supportsWholeGenome()) {
30881
- this.wgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, this.maxWGCount);
30882
- } else {
30883
- this.wgFeatures = [];
31006
+ const tiles = [];
31007
+
31008
+ for (let indexEntry of tileIndeces) {
31009
+
31010
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
31011
+ range: {
31012
+ start: indexEntry.position,
31013
+ size: indexEntry.size
30884
31014
  }
31015
+ }));
31016
+
31017
+ let tileData;
31018
+ try {
31019
+ tileData = this.compressed ? inflate_1$3(data).buffer : data;
31020
+ } catch (e) {
31021
+ console.error(e);
31022
+ continue
30885
31023
  }
30886
- return this.wgFeatures
30887
- } else {
30888
- return this.featureCache.queryFeatures(chr, start, end)
31024
+
31025
+ const binaryParser = new BinaryParser$1(new DataView(tileData));
31026
+ const type = binaryParser.getString();
31027
+ let tile;
31028
+ switch (type) {
31029
+ case "fixedStep":
31030
+ tile = createFixedStep(binaryParser, nTracks);
31031
+ break
31032
+ case "variableStep":
31033
+ tile = createVariableStep(binaryParser, nTracks);
31034
+ break
31035
+ case "bed":
31036
+ case "bedWithName":
31037
+ tile = createBed(binaryParser, nTracks, type);
31038
+ break
31039
+ default:
31040
+ throw "Unknown tile type: " + type
31041
+ }
31042
+ tiles.push(tile);
30889
31043
  }
31044
+ return tiles
30890
31045
  }
30891
31046
 
30892
- async findFeatures(fn) {
30893
- return this.featureCache ? this.featureCache.findFeatures(fn) : []
30894
- }
31047
+ async readTile(indexEntry, nTracks) {
30895
31048
 
30896
- supportsWholeGenome() {
30897
- return !this.queryable // queryable (indexed, web services) sources don't support whole genome view
31049
+ let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
31050
+ range: {
31051
+ start: indexEntry.position,
31052
+ size: indexEntry.size
31053
+ }
31054
+ }));
31055
+
31056
+ if (this.compressed) {
31057
+ const plain = inflate_1$3(data);
31058
+ data = plain.buffer;
31059
+ }
31060
+
31061
+ const binaryParser = new BinaryParser$1(new DataView(data));
31062
+ const type = binaryParser.getString();
31063
+ switch (type) {
31064
+ case "fixedStep":
31065
+ return createFixedStep(binaryParser, nTracks)
31066
+ case "variableStep":
31067
+ return createVariableStep(binaryParser, nTracks)
31068
+ case "bed":
31069
+ case "bedWithName":
31070
+ return createBed(binaryParser, nTracks, type)
31071
+ default:
31072
+ throw "Unknown tile type: " + type
31073
+ }
30898
31074
  }
30899
31075
 
30900
- // TODO -- experimental, will only work for non-indexed sources
30901
- getAllFeatures() {
30902
- if (this.queryable || !this.featureCache) { // queryable sources don't support all features
30903
- return []
30904
- } else {
30905
- return this.featureCache.getAllFeatures()
31076
+ }
31077
+
31078
+ function createFixedStep(binaryParser, nTracks) {
31079
+ const nPositions = binaryParser.getInt();
31080
+ const start = binaryParser.getInt();
31081
+ const span = binaryParser.getFloat();
31082
+
31083
+ const data = [];
31084
+ let nt = nTracks;
31085
+ while (nt-- > 0) {
31086
+ let np = nPositions;
31087
+ const dtrack = [];
31088
+ while (np-- > 0) {
31089
+ dtrack.push(binaryParser.getFloat());
30906
31090
  }
31091
+ data.push(dtrack);
30907
31092
  }
30908
31093
 
31094
+ return {
31095
+ type: "fixedStep",
31096
+ start: start,
31097
+ span: span,
31098
+ data: data,
31099
+ nTracks: nTracks,
31100
+ nPositions: nPositions
31101
+ }
31102
+ }
30909
31103
 
30910
- async loadFeatures(chr, start, end, visibilityWindow) {
31104
+ function createVariableStep(binaryParser, nTracks) {
30911
31105
 
30912
- await this.getHeader();
31106
+ const tileStart = binaryParser.getInt();
31107
+ const span = binaryParser.getFloat();
31108
+ const nPositions = binaryParser.getInt();
31109
+ const start = [];
30913
31110
 
30914
- const reader = this.reader;
30915
- let intervalStart = start;
30916
- let intervalEnd = end;
31111
+ let np = nPositions;
31112
+ while (np-- > 0) {
31113
+ start.push(binaryParser.getInt());
31114
+ }
31115
+ binaryParser.getInt(); // # of samples, ignored but should === nTracks
30917
31116
 
30918
- // chr aliasing
30919
- let queryChr = chr;
30920
- if (!this.chrAliasManager && this.reader && this.reader.sequenceNames) {
30921
- this.chrAliasManager = new ChromAliasManager(this.reader.sequenceNames, this.genome);
30922
- }
30923
- if (this.chrAliasManager) {
30924
- queryChr = await this.chrAliasManager.getAliasName(chr);
31117
+ const data = [];
31118
+ let nt = nTracks;
31119
+ while (nt-- > 0) {
31120
+ np = nPositions;
31121
+ const dtrack = [];
31122
+ while (np-- > 0) {
31123
+ dtrack.push(binaryParser.getFloat());
30925
31124
  }
31125
+ data.push(dtrack);
31126
+ }
30926
31127
 
30927
- // Use visibility window to potentially expand query interval.
30928
- // This can save re-queries as we zoom out. Visibility window <= 0 is a special case
30929
- // indicating whole chromosome should be read at once.
30930
- if ((!visibilityWindow || visibilityWindow <= 0) && this.config.expandQuery !== false) {
30931
- // Whole chromosome
30932
- const chromosome = this.genome ? this.genome.getChromosome(queryChr) : undefined;
30933
- intervalStart = 0;
30934
- intervalEnd = Math.max(chromosome ? chromosome.bpLength : Number.MAX_SAFE_INTEGER, end);
30935
- } else if (visibilityWindow > (end - start) && this.config.expandQuery !== false) {
30936
- let expansionWindow = Math.min(4.1 * (end - start), visibilityWindow);
30937
- if(this.config.minQuerySize && expansionWindow < this.config.minQuerySize) {
30938
- expansionWindow = this.config.minQuerySize;
30939
- }
30940
- intervalStart = Math.max(0, (start + end - expansionWindow) / 2);
30941
- intervalEnd = intervalStart + expansionWindow;
31128
+ return {
31129
+ type: "variableStep",
31130
+ tileStart: tileStart,
31131
+ span: span,
31132
+ start: start,
31133
+ data: data,
31134
+ nTracks: nTracks,
31135
+ nPositions: nPositions
31136
+ }
31137
+ }
31138
+
31139
+ function createBed(binaryParser, nTracks, type) {
31140
+
31141
+ const nPositions = binaryParser.getInt();
31142
+
31143
+ let n = nPositions;
31144
+ const start = [];
31145
+ while (n-- > 0) {
31146
+ start.push(binaryParser.getInt());
31147
+ }
31148
+
31149
+ n = nPositions;
31150
+ const end = [];
31151
+ while (n-- > 0) {
31152
+ end.push(binaryParser.getInt());
31153
+ }
31154
+
31155
+ binaryParser.getInt(); // # of samples, ignored but should === nTracks
31156
+ const data = [];
31157
+ let nt = nTracks;
31158
+ while (nt-- > 0) {
31159
+ let np = nPositions;
31160
+ const dtrack = [];
31161
+ while (np-- > 0) {
31162
+ dtrack.push(binaryParser.getFloat());
30942
31163
  }
31164
+ data.push(dtrack);
31165
+ }
30943
31166
 
30944
- let features = await reader.readFeatures(queryChr, intervalStart, intervalEnd);
30945
- if (this.queryable === undefined) {
30946
- this.queryable = reader.indexed;
31167
+ if (type === "bedWithName") {
31168
+ n = nPositions;
31169
+ const name = [];
31170
+ while (n-- > 0) {
31171
+ name.push(binaryParser.getString());
30947
31172
  }
31173
+ }
30948
31174
 
30949
- const genomicInterval = this.queryable ?
30950
- new GenomicInterval(chr, intervalStart, intervalEnd) :
30951
- undefined;
31175
+ return {
31176
+ type: type,
31177
+ start: start,
31178
+ end: end,
31179
+ data: data,
31180
+ nTracks: nTracks,
31181
+ nPositions: nPositions
31182
+ }
31183
+ }
30952
31184
 
30953
- if (features) {
31185
+ /*
31186
+ * The MIT License (MIT)
31187
+ *
31188
+ * Copyright (c) 2016 University of California San Diego
31189
+ * Author: Jim Robinson
31190
+ *
31191
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
31192
+ * of this software and associated documentation files (the "Software"), to deal
31193
+ * in the Software without restriction, including without limitation the rights
31194
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31195
+ * copies of the Software, and to permit persons to whom the Software is
31196
+ * furnished to do so, subject to the following conditions:
31197
+ *
31198
+ * The above copyright notice and this permission notice shall be included in
31199
+ * all copies or substantial portions of the Software.
31200
+ *
31201
+ *
31202
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31203
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31204
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31205
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31206
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31207
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31208
+ * THE SOFTWARE.
31209
+ */
30954
31210
 
30955
- // Assign overlapping features to rows
30956
- if (this.config.format !== "wig" && this.config.type !== "junctions") {
30957
- const maxRows = this.config.maxRows || Number.MAX_SAFE_INTEGER;
30958
- packFeatures(features, maxRows);
30959
- }
31211
+ class TDFSource extends BaseFeatureSource {
30960
31212
 
30961
- // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
30962
- this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
31213
+ #wgValues = {}
31214
+ searchable = false
30963
31215
 
30964
- // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
30965
- if (this.searchable) {
30966
- this.addFeaturesToDB(features, this.config);
30967
- }
31216
+
31217
+ constructor(config, genome) {
31218
+ super(genome);
31219
+ this.genome = genome;
31220
+ this.reader = new TDFReader(config, genome);
31221
+ this.queryable = true;
31222
+ }
31223
+
31224
+ async getFeatures({chr, start, end, bpPerPixel, windowFunction = "mean"}) {
31225
+
31226
+ if (chr.toLowerCase() === "all") {
31227
+ return this.getWGValues(windowFunction, bpPerPixel)
30968
31228
  } else {
30969
- this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
31229
+ return this._getFeatures(chr, start, end, bpPerPixel, windowFunction)
30970
31230
  }
30971
31231
  }
31232
+ async _getFeatures(chr, start, end, bpPerPixel, windowFunction) {
31233
+ const genomicInterval = new GenomicInterval(chr, start, end);
31234
+ const genome = this.genome;
30972
31235
 
30973
- addFeaturesToDB(featureList, config) {
30974
- if (!this.featureMap) {
30975
- this.featureMap = new Map();
30976
- }
30977
- const searchableFields = config.searchableFields || ["name", "transcript_id", "gene_id", "gene_name", "id"];
30978
- for (let feature of featureList) {
30979
- for (let field of searchableFields) {
30980
- let key;
30981
- if (typeof feature.getAttributeValue === 'function') {
30982
- key = feature.getAttributeValue(field);
31236
+
31237
+ if (!this.rootGroup) {
31238
+ this.rootGroup = await this.reader.readRootGroup();
31239
+ if (!this.normalizationFactor) {
31240
+ const totalCount = this.rootGroup.totalCount;
31241
+ if (totalCount) {
31242
+ this.normalizationFactor = 1.0e6 / totalCount;
30983
31243
  }
30984
- if (key) {
30985
- key = key.replaceAll(' ', '+').toUpperCase();
30986
- // If feature is already present keep largest one
30987
- if (this.featureMap.has(key)) {
30988
- const f2 = this.featureMap.get(key);
30989
- if (feature.end - feature.start < f2.end - f2.start) {
30990
- continue
31244
+ }
31245
+ }
31246
+
31247
+ genomicInterval.bpPerPixel = bpPerPixel;
31248
+ const zoom = zoomLevelForScale$1(chr, bpPerPixel, genome);
31249
+ let queryChr = this.reader.chrAliasTable[chr];
31250
+ let maxZoom = this.reader.maxZoom;
31251
+ if (queryChr === undefined) queryChr = chr;
31252
+ if (maxZoom === undefined) maxZoom = -1;
31253
+
31254
+ const wf = zoom > maxZoom ? "raw" : windowFunction;
31255
+ const dataset = await this.reader.readDataset(queryChr, wf, zoom);
31256
+ if (dataset == null) {
31257
+ return []
31258
+ }
31259
+
31260
+ const tileWidth = dataset.tileWidth;
31261
+ const startTile = Math.floor(start / tileWidth);
31262
+ const endTile = Math.floor(end / tileWidth);
31263
+ const NTRACKS = 1; // TODO read this
31264
+ const tiles = await this.reader.readTiles(dataset.tiles.slice(startTile, endTile + 1), NTRACKS);
31265
+ const features = [];
31266
+ for (let tile of tiles) {
31267
+ switch (tile.type) {
31268
+ case "bed":
31269
+ decodeBedTile(tile, chr, start, end, bpPerPixel, features);
31270
+ break
31271
+ case "variableStep":
31272
+ decodeVaryTile(tile, chr, start, end, bpPerPixel, features);
31273
+ break
31274
+ case "fixedStep":
31275
+ decodeFixedTile(tile, chr, start, end, bpPerPixel, features);
31276
+ break
31277
+ default:
31278
+ throw ("Unknown tile type: " + tile.type)
31279
+ }
31280
+ }
31281
+ features.sort(function (a, b) {
31282
+ return a.start - b.start
31283
+ });
31284
+
31285
+ return features
31286
+ }
31287
+
31288
+ get supportsWholeGenome() {
31289
+ return true
31290
+ }
31291
+
31292
+ get windowFunctions() {
31293
+ return this.reader.windowFunctions
31294
+ }
31295
+
31296
+ async getWGValues(windowFunction, bpPerPixel) {
31297
+
31298
+ const cached = this.#wgValues[windowFunction];
31299
+ if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
31300
+ return cached.values
31301
+ } else {
31302
+ const wgFeatures = [];
31303
+ const genome = this.genome;
31304
+ const chrNames = this.genome.wgChromosomeNames;
31305
+ if (chrNames) {
31306
+ for (let c of genome.wgChromosomeNames) {
31307
+ const len = genome.getChromosome(c).bpLength;
31308
+ bpPerPixel = len / 1000;
31309
+ const chrFeatures = await this._getFeatures(c, 0, len, bpPerPixel, windowFunction);
31310
+ if (chrFeatures) {
31311
+ for (let f of chrFeatures) {
31312
+ const wg = Object.assign({}, f);
31313
+ wg.chr = "all";
31314
+ wg.start = genome.getGenomeCoordinate(f.chr, f.start);
31315
+ wg.end = genome.getGenomeCoordinate(f.chr, f.end);
31316
+ wg._f = f;
31317
+ wgFeatures.push(wg);
30991
31318
  }
30992
31319
  }
30993
- this.featureMap.set(key, feature);
30994
31320
  }
30995
31321
  }
31322
+ this.#wgValues[windowFunction] = {values: wgFeatures, bpPerPixel};
31323
+ return wgFeatures
30996
31324
  }
30997
31325
  }
30998
31326
 
30999
- search(term) {
31000
- if (this.featureMap) {
31001
- return this.featureMap.get(term.toUpperCase())
31002
- }
31327
+ }
31003
31328
 
31329
+ function decodeBedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31330
+
31331
+ const nPositions = tile.nPositions;
31332
+ const starts = tile.start;
31333
+ const ends = tile.end;
31334
+ const data = tile.data[0]; // Single track for now
31335
+ for (let i = 0; i < nPositions; i++) {
31336
+ const s = starts[i];
31337
+ const e = ends[i];
31338
+ if (e < bpStart) continue
31339
+ if (s > bpEnd) break
31340
+ features.push({
31341
+ chr: chr,
31342
+ start: s,
31343
+ end: e,
31344
+ value: data[i]
31345
+ });
31004
31346
  }
31005
31347
  }
31006
31348
 
31349
+ function decodeVaryTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31350
+
31351
+ const nPositions = tile.nPositions;
31352
+ const starts = tile.start;
31353
+ const span = tile.span;
31354
+ const data = tile.data[0]; // Single track for now
31355
+ for (let i = 0; i < nPositions; i++) {
31356
+ const s = starts[i];
31357
+ const e = s + span;
31358
+ if (e < bpStart) continue
31359
+ if (s > bpEnd) break
31360
+ features.push({
31361
+ chr: chr,
31362
+ start: s,
31363
+ end: e,
31364
+ value: data[i]
31365
+ });
31366
+ }
31367
+ }
31368
+
31369
+ function decodeFixedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31370
+
31371
+ const nPositions = tile.nPositions;
31372
+ let s = tile.start;
31373
+ const span = tile.span;
31374
+ const data = tile.data[0]; // Single track for now
31375
+
31376
+ for (let i = 0; i < nPositions; i++) {
31377
+ const e = s + span;
31378
+ if (s > bpEnd) break
31379
+ if (e >= bpStart) {
31380
+ if (!Number.isNaN(data[i])) {
31381
+ features.push({
31382
+ chr: chr,
31383
+ start: s,
31384
+ end: e,
31385
+ value: data[i]
31386
+ });
31387
+ }
31388
+ }
31389
+ s = e;
31390
+ }
31391
+ }
31392
+
31393
+
31394
+ var log2 = Math.log(2);
31395
+
31396
+ function zoomLevelForScale$1(chr, bpPerPixel, genome) {
31397
+
31398
+ // Convert bpPerPixel to IGV "zoom" level. This is a bit convoluted, TDF is computed zoom levels assuming
31399
+ // display in a 700 pixel window. The fully zoomed out view of a chromosome is zoom level "0".
31400
+ // Zoom level 1 is magnified 2X, and so forth
31401
+
31402
+ var chrSize = genome.getChromosome(chr).bpLength;
31403
+
31404
+ return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
31405
+ }
31406
+
31007
31407
  /**
31008
31408
  * A ChromTree parses a UCSC bigbed/bigwig "chromosomeTree" section of the header to produce 2 maps,
31009
31409
  * (1) ID -> chromosome names, and its
@@ -31575,7 +31975,7 @@
31575
31975
  if (this.type === "bigwig") {
31576
31976
  // Select a biwig "zoom level" appropriate for the current resolution.
31577
31977
  const zoomLevelHeaders = await this.getZoomHeaders();
31578
- let zoomLevelHeader = bpPerPixel ? zoomLevelForScale$1(bpPerPixel, zoomLevelHeaders) : undefined;
31978
+ let zoomLevelHeader = bpPerPixel ? zoomLevelForScale(bpPerPixel, zoomLevelHeaders) : undefined;
31579
31979
  if (zoomLevelHeader) {
31580
31980
  treeOffset = zoomLevelHeader.indexOffset;
31581
31981
  decodeFunction = decodeZoomData;
@@ -32031,7 +32431,7 @@
32031
32431
  }
32032
32432
  }
32033
32433
 
32034
- function zoomLevelForScale$1(bpPerPixel, zoomLevelHeaders) {
32434
+ function zoomLevelForScale(bpPerPixel, zoomLevelHeaders) {
32035
32435
  let level;
32036
32436
  for (let i = 0; i < zoomLevelHeaders.length; i++) {
32037
32437
  const zl = zoomLevelHeaders[i];
@@ -32219,7 +32619,7 @@
32219
32619
  class BWSource extends BaseFeatureSource {
32220
32620
 
32221
32621
  queryable = true
32222
- wgValues = {}
32622
+ #wgValues = {}
32223
32623
  windowFunctions = ["mean", "min", "max"]
32224
32624
 
32225
32625
  constructor(config, genome) {
@@ -32231,12 +32631,15 @@
32231
32631
 
32232
32632
  async getFeatures({chr, start, end, bpPerPixel, windowFunction}) {
32233
32633
 
32234
- await this.reader.loadHeader();
32634
+ await this.reader.loadHeader();
32235
32635
  const isBigWig = this.reader.type === "bigwig";
32236
32636
 
32237
- const features = (chr.toLowerCase() === "all") ?
32238
- (isBigWig ? await this.getWGValues(windowFunction) : []) :
32239
- await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
32637
+ let features;
32638
+ if ("all" === chr.toLowerCase()) {
32639
+ features = isBigWig ? await this.getWGValues(windowFunction, bpPerPixel) : [];
32640
+ } else {
32641
+ features = await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
32642
+ }
32240
32643
 
32241
32644
  if (!isBigWig) {
32242
32645
  pack(features);
@@ -32252,26 +32655,25 @@
32252
32655
  if (this.reader.type === "bigwig") {
32253
32656
  return -1
32254
32657
  } else {
32255
- return this.reader.featureDensity ? Math.floor(10000 / this.reader.featureDensity) : -1
32658
+ return this.reader.featureDensity ? Math.floor(10000 / this.reader.featureDensity) : -1
32256
32659
  }
32257
32660
 
32258
32661
  }
32259
32662
 
32260
- async getWGValues(windowFunction) {
32663
+ async getWGValues(windowFunction, bpPerPixel) {
32261
32664
 
32262
- const numberOfBins = 1000; // This doesn't need to be precise
32263
32665
  const genome = this.genome;
32264
-
32265
- if (this.wgValues[windowFunction]) {
32266
- return this.wgValues[windowFunction]
32666
+ const cached = this.#wgValues[windowFunction];
32667
+ if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
32668
+ return cached.values
32267
32669
  } else {
32268
32670
 
32269
- const bpPerPixel = genome.getGenomeLength() / numberOfBins;
32270
32671
  const features = await this.reader.readWGFeatures(bpPerPixel, windowFunction);
32271
32672
  let wgValues = [];
32272
32673
  for (let f of features) {
32273
32674
  const chr = f.chr;
32274
32675
  const offset = genome.getCumulativeOffset(chr);
32676
+ if (undefined === offset) continue
32275
32677
  const wgFeature = Object.assign({}, f);
32276
32678
  wgFeature.chr = "all";
32277
32679
  wgFeature.start = offset + f.start;
@@ -32280,7 +32682,7 @@
32280
32682
  wgValues.push(wgFeature);
32281
32683
  }
32282
32684
  wgValues.sort((a, b) => a.start - b.start);
32283
- this.wgValues[windowFunction] = wgValues;
32685
+ this.#wgValues[windowFunction] = {values: wgValues, bpPerPixel};
32284
32686
  return wgValues
32285
32687
  }
32286
32688
  }
@@ -32302,658 +32704,780 @@
32302
32704
  }
32303
32705
  }
32304
32706
 
32305
- /*
32306
- * The MIT License (MIT)
32307
- *
32308
- * Copyright (c) 2016 University of California San Diego
32309
- * Author: Jim Robinson
32310
- *
32311
- * Permission is hereby granted, free of charge, to any person obtaining a copy
32312
- * of this software and associated documentation files (the "Software"), to deal
32313
- * in the Software without restriction, including without limitation the rights
32314
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32315
- * copies of the Software, and to permit persons to whom the Software is
32316
- * furnished to do so, subject to the following conditions:
32317
- *
32318
- * The above copyright notice and this permission notice shall be included in
32319
- * all copies or substantial portions of the Software.
32320
- *
32321
- *
32322
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32323
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32324
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32325
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32326
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32327
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32328
- * THE SOFTWARE.
32329
- */
32707
+ const shim = .01;
32708
+ const colorStripWidth = 4;
32709
+ const axesXOffset = colorStripWidth + 1;
32710
+ function paintAxis(ctx, width, height, colorOrUndefined) {
32711
+
32712
+ if (undefined === this.dataRange || undefined === this.dataRange.max || undefined === this.dataRange.min) {
32713
+ return
32714
+ }
32715
+
32716
+ IGVGraphics.fillRect(ctx, 0, 0, width, height, { fillStyle: 'white' });
32717
+ if (colorOrUndefined) {
32718
+ IGVGraphics.fillRect(ctx, width - colorStripWidth - 2, 0, colorStripWidth, height, { fillStyle: colorOrUndefined });
32719
+ }
32720
+
32721
+ const flipAxis = (undefined === this.flipAxis) ? false : this.flipAxis;
32722
+
32723
+ const xTickStart = 0.95 * width - 8 - axesXOffset;
32724
+ const xTickEnd = 0.95 * width - axesXOffset;
32725
+
32726
+ const properties =
32727
+ {
32728
+ font: 'normal 10px Arial',
32729
+ textAlign: 'right',
32730
+ fillStyle: 'black',
32731
+ strokeStyle: 'black',
32732
+ };
32733
+
32734
+ // tick
32735
+ IGVGraphics.strokeLine(ctx, xTickStart, shim * height, xTickEnd, shim * height, properties);
32736
+ IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.min : this.dataRange.max), xTickStart + 4, shim * height + 12, properties);
32737
+
32738
+ const y = (1.0 - shim) * height;
32739
+
32740
+ // tick
32741
+ IGVGraphics.strokeLine(ctx, xTickStart, y, xTickEnd, y, properties);
32742
+ IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.max : this.dataRange.min), xTickStart + 4, y - 4, properties);
32743
+
32744
+ // vertical axis
32745
+ IGVGraphics.strokeLine(ctx, xTickEnd, shim * height, xTickEnd, y, properties);
32746
+
32747
+ function prettyPrint(number) {
32748
+
32749
+ if (number === 0) {
32750
+ return "0"
32751
+ } else if (Math.abs(number) >= 10) {
32752
+ return number.toFixed()
32753
+ } else if (Math.abs(number) >= 1) {
32754
+ return number.toFixed(1)
32755
+ } else if (Math.abs(number) >= 0.1) {
32756
+ return number.toFixed(2)
32757
+ } else {
32758
+ return number.toExponential(1)
32759
+ }
32760
+ }
32761
+ }
32762
+
32763
+ const DEFAULT_COLOR$2 = 'rgb(150, 150, 150)';
32764
+
32765
+
32766
+ class WigTrack extends TrackBase {
32767
+
32768
+ static defaults = {
32769
+ height: 50,
32770
+ flipAxis: false,
32771
+ logScale: false,
32772
+ windowFunction: 'mean',
32773
+ graphType: 'bar',
32774
+ normalize: undefined,
32775
+ scaleFactor: undefined,
32776
+ overflowColor: `rgb(255, 32, 255)`,
32777
+ baselineColor: 'lightGray',
32778
+ summarize: true
32779
+ }
32780
+
32781
+ constructor(config, browser) {
32782
+ super(config, browser);
32783
+ }
32784
+
32785
+ init(config) {
32786
+
32787
+ super.init(config);
32788
+
32789
+ this.type = "wig";
32790
+ this.featureType = 'numeric';
32791
+ this.resolutionAware = true;
32792
+ this.paintAxis = paintAxis;
32793
+
32794
+ const format = config.format ? config.format.toLowerCase() : config.format;
32795
+ if (config.featureSource) {
32796
+ this.featureSource = config.featureSource;
32797
+ delete config.featureSource;
32798
+ } else if ("bigwig" === format) {
32799
+ this.featureSource = new BWSource(config, this.browser.genome);
32800
+ } else if ("tdf" === format) {
32801
+ this.featureSource = new TDFSource(config, this.browser.genome);
32802
+ } else {
32803
+ this.featureSource = FeatureSource(config, this.browser.genome);
32804
+ }
32805
+
32806
+
32807
+ // Override autoscale default
32808
+ if (config.max === undefined || config.autoscale === true) {
32809
+ this.autoscale = true;
32810
+ } else {
32811
+ this.dataRange = {
32812
+ min: config.min || 0,
32813
+ max: config.max
32814
+ };
32815
+ }
32816
+ }
32817
+
32818
+ async postInit() {
32819
+ const header = await this.getHeader();
32820
+ if (this.disposed) return // This track was removed during async load
32821
+ if (header) this.setTrackProperties(header);
32822
+ }
32823
+
32824
+ async getFeatures(chr, start, end, bpPerPixel) {
32825
+
32826
+ const windowFunction = this.windowFunction;
32827
+
32828
+ const features = await this.featureSource.getFeatures({
32829
+ chr,
32830
+ start,
32831
+ end,
32832
+ bpPerPixel,
32833
+ visibilityWindow: this.visibilityWindow,
32834
+ windowFunction
32835
+ });
32836
+ if (this.normalize && this.featureSource.normalizationFactor) {
32837
+ const scaleFactor = this.featureSource.normalizationFactor;
32838
+ for (let f of features) {
32839
+ f.value *= scaleFactor;
32840
+ }
32841
+ }
32842
+ if (this.scaleFactor) {
32843
+ const scaleFactor = this.scaleFactor;
32844
+ for (let f of features) {
32845
+ f.value *= scaleFactor;
32846
+ }
32847
+ }
32330
32848
 
32331
- const GZIP_FLAG = 0x1;
32849
+ // Summarize features to current resolution. This needs to be done here, rather than in the "draw" function,
32850
+ // for group autoscale to work.
32851
+ if (this.summarize && ("mean" === windowFunction || "min" === windowFunction || "max" === windowFunction)) {
32852
+ return summarizeData(features, start, bpPerPixel, windowFunction)
32853
+ } else {
32854
+ return features
32855
+ }
32856
+ }
32332
32857
 
32333
- class TDFReader {
32858
+ menuItemList() {
32859
+ const items = [];
32334
32860
 
32335
- constructor(config, genome) {
32336
- this.config = config;
32337
- this.genome = genome;
32338
- this.path = config.url;
32339
- this.groupCache = {};
32340
- this.datasetCache = {};
32341
- }
32861
+ if (this.flipAxis !== undefined) {
32862
+ items.push('<hr>');
32342
32863
 
32864
+ function click() {
32865
+ this.flipAxis = !this.flipAxis;
32866
+ this.trackView.repaintViews();
32867
+ }
32343
32868
 
32344
- async readHeader() {
32869
+ items.push({label: 'Flip y-axis', click});
32870
+ }
32345
32871
 
32346
- if (this.magic !== undefined) {
32347
- return this // Already read
32872
+ if(this.featureSource.windowFunctions) {
32873
+ items.push(...this.wigSummarizationItems());
32348
32874
  }
32349
32875
 
32350
- let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start: 0, size: 64000}}));
32351
- let binaryParser = new BinaryParser$1(new DataView(data));
32352
- this.magic = binaryParser.getInt();
32353
- this.version = binaryParser.getInt();
32354
- this.indexPos = binaryParser.getLong();
32355
- this.indexSize = binaryParser.getInt();
32356
- binaryParser.getInt();
32876
+ items.push(...this.numericDataMenuItems());
32357
32877
 
32878
+ return items
32879
+ }
32358
32880
 
32359
- if (this.version >= 2) {
32360
- let nWindowFunctions = binaryParser.getInt();
32361
- this.windowFunctions = [];
32362
- while (nWindowFunctions-- > 0) {
32363
- this.windowFunctions.push(binaryParser.getString());
32364
- }
32365
- }
32881
+ wigSummarizationItems() {
32366
32882
 
32367
- this.trackType = binaryParser.getString();
32368
- this.trackLine = binaryParser.getString();
32883
+ const windowFunctions = this.featureSource.windowFunctions;
32369
32884
 
32370
- let nTracks = binaryParser.getInt();
32371
- this.trackNames = [];
32372
- while (nTracks-- > 0) {
32373
- this.trackNames.push(binaryParser.getString());
32374
- }
32375
- this.genomeID = binaryParser.getString();
32376
- this.flags = binaryParser.getInt();
32377
- this.compressed = (this.flags & GZIP_FLAG) !== 0;
32885
+ const menuItems = [];
32886
+ menuItems.push('<hr/>');
32887
+ menuItems.push("<div>Windowing function</div>");
32888
+ for (const wf of windowFunctions) {
32889
+ const object = $$1(createCheckbox(wf, this.windowFunction === wf));
32378
32890
 
32379
- // Now read index
32380
- data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32381
- range: {
32382
- start: this.indexPos,
32383
- size: this.indexSize
32891
+ function clickHandler() {
32892
+ this.windowFunction = wf;
32893
+ this.trackView.updateViews();
32384
32894
  }
32385
- }));
32386
- binaryParser = new BinaryParser$1(new DataView(data));
32387
- this.datasetIndex = {};
32388
- let nEntries = binaryParser.getInt();
32389
- while (nEntries-- > 0) {
32390
- const name = binaryParser.getString();
32391
- const pos = binaryParser.getLong();
32392
- const size = binaryParser.getInt();
32393
- this.datasetIndex[name] = {position: pos, size: size};
32394
- }
32395
32895
 
32396
- this.groupIndex = {};
32397
- nEntries = binaryParser.getInt();
32398
- while (nEntries-- > 0) {
32399
- const name = binaryParser.getString();
32400
- const pos = binaryParser.getLong();
32401
- const size = binaryParser.getInt();
32402
- this.groupIndex[name] = {position: pos, size: size};
32896
+ menuItems.push({object, click: clickHandler});
32403
32897
  }
32404
32898
 
32405
- return this
32899
+ return menuItems
32406
32900
  }
32407
32901
 
32408
- async readDataset(chr, windowFunction, zoom) {
32409
32902
 
32410
- const key = chr + "_" + windowFunction + "_" + zoom;
32903
+ async getHeader() {
32411
32904
 
32412
- if (this.datasetCache[key]) {
32413
- return this.datasetCache[key]
32905
+ if (typeof this.featureSource.getHeader === "function") {
32906
+ this.header = await this.featureSource.getHeader();
32907
+ }
32908
+ return this.header
32909
+ }
32414
32910
 
32415
- } else {
32416
- await this.readHeader();
32417
- const wf = (this.version < 2) ? "" : "/" + windowFunction;
32418
- const zoomString = (chr.toLowerCase() === "all" || zoom === undefined) ? "0" : zoom.toString();
32911
+ // TODO: refactor to igvUtils.js
32912
+ getScaleFactor(min, max, height, logScale) {
32913
+ const minValue = (logScale === true) ? ((min < 0) ? -Math.log10(Math.abs(min) + 1) : Math.log10(Math.abs(min) + 1)) : min;
32914
+ const maxValue = (logScale === true) ? Math.log10(Math.abs(max) + 1) : max;
32915
+ const scale = height / (maxValue - minValue);
32916
+ return scale
32917
+ }
32419
32918
 
32420
- let dsName;
32421
- if (windowFunction === "raw") {
32422
- dsName = "/" + chr + "/raw";
32423
- } else {
32424
- dsName = "/" + chr + "/z" + zoomString + wf;
32425
- }
32426
- const indexEntry = this.datasetIndex[dsName];
32919
+ computeYPixelValue(yValue, yScaleFactor) {
32920
+ return (this.flipAxis ? (yValue - this.dataRange.min) : (this.dataRange.max - yValue)) * yScaleFactor
32921
+ }
32427
32922
 
32428
- if (indexEntry === undefined) {
32429
- return undefined
32430
- }
32923
+ computeYPixelValueInLogScale(yValue, yScaleFactor) {
32924
+ let maxValue = this.dataRange.max;
32925
+ let minValue = this.dataRange.min;
32926
+ minValue = (minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1);
32927
+ maxValue = (maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1);
32928
+
32929
+ yValue = (yValue < 0) ? -Math.log10(Math.abs(yValue) +1) : Math.log10(yValue + 1);
32930
+ return ((this.flipAxis ? (yValue - minValue) : (maxValue - yValue)) * yScaleFactor)
32931
+ }
32431
32932
 
32432
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32433
- range: {
32434
- start: indexEntry.position,
32435
- size: indexEntry.size
32436
- }
32437
- }));
32933
+ draw(options) {
32438
32934
 
32439
- if (!data) {
32440
- return undefined
32441
- }
32935
+ const features = options.features;
32936
+ const ctx = options.context;
32937
+ const bpPerPixel = options.bpPerPixel;
32938
+ const bpStart = options.bpStart;
32939
+ const pixelWidth = options.pixelWidth;
32940
+ const pixelHeight = options.pixelHeight - 1;
32941
+ const bpEnd = bpStart + pixelWidth * bpPerPixel + 1;
32942
+
32943
+ const scaleFactor = this.getScaleFactor(this.dataRange.min, this.dataRange.max, pixelHeight, this.logScale);
32944
+ const yScale = (yValue) => this.logScale
32945
+ ? this.computeYPixelValueInLogScale(yValue, scaleFactor)
32946
+ : this.computeYPixelValue(yValue, scaleFactor);
32442
32947
 
32443
- const binaryParser = new BinaryParser$1(new DataView(data));
32444
- let nAttributes = binaryParser.getInt();
32445
- const attributes = {};
32446
- while (nAttributes-- > 0) {
32447
- attributes[binaryParser.getString()] = binaryParser.getString();
32448
- }
32449
- const dataType = binaryParser.getString();
32450
- const tileWidth = binaryParser.getFloat();
32451
- let nTiles = binaryParser.getInt();
32452
- const tiles = [];
32453
- while (nTiles-- > 0) {
32454
- tiles.push({position: binaryParser.getLong(), size: binaryParser.getInt()});
32455
- }
32948
+ if (features && features.length > 0) {
32456
32949
 
32457
- const dataset = {
32458
- name: dsName,
32459
- attributes: attributes,
32460
- dataType: dataType,
32461
- tileWidth: tileWidth,
32462
- tiles: tiles
32463
- };
32950
+ if (this.dataRange.min === undefined) this.dataRange.min = 0;
32464
32951
 
32465
- this.datasetCache[key] = dataset;
32466
- return dataset
32467
- }
32468
- }
32952
+ // Max can be less than min if config.min is set but max left to autoscale. If that's the case there is
32953
+ // nothing to paint.
32954
+ if (this.dataRange.max > this.dataRange.min) {
32469
32955
 
32470
- async readRootGroup() {
32956
+ let lastPixelEnd = -1;
32957
+ let lastY;
32958
+ const y0 = yScale(0);
32471
32959
 
32472
- const genome = this.genome;
32473
- const rootGroup = this.groupCache["/"];
32474
- if (rootGroup) {
32475
- return rootGroup
32476
- } else {
32960
+ for (let f of features) {
32477
32961
 
32478
- const group = await this.readGroup("/");
32479
- const names = group["chromosomes"];
32480
- const maxZoomString = group["maxZoom"];
32962
+ if (f.end < bpStart) continue
32963
+ if (f.start > bpEnd) break
32481
32964
 
32482
- // Now parse out interesting attributes.
32483
- if (maxZoomString) {
32484
- this.maxZoom = Number(maxZoomString);
32485
- }
32965
+ const x = (f.start - bpStart) / bpPerPixel;
32966
+ if (isNaN(x)) continue
32486
32967
 
32487
- const totalCountString = group["totalCount"];
32488
- if (totalCountString) {
32489
- group.totalCount = Number(totalCountString);
32490
- }
32968
+ let y = yScale(f.value);
32491
32969
 
32492
- // Chromosome names
32493
- const chrAliasTable = {};
32494
- if (names) {
32495
- names.split(",").forEach(function (chr) {
32496
- const canonicalName = genome.getChromosomeName(chr);
32497
- chrAliasTable[canonicalName] = chr;
32498
- });
32499
- }
32500
- this.chrAliasTable = chrAliasTable;
32970
+ const rectEnd = (f.end - bpStart) / bpPerPixel;
32971
+ const width = rectEnd - x;
32501
32972
 
32502
- this.groupCache["/"] = group;
32503
- return group
32504
- }
32505
- }
32973
+ const color = options.alpha ? IGVColor.addAlpha(this.getColorForFeature(f), options.alpha) : this.getColorForFeature(f);
32506
32974
 
32507
- async readGroup(name) {
32975
+ if (this.graphType === "line") {
32976
+ if (lastY !== undefined) {
32977
+ IGVGraphics.strokeLine(ctx, lastPixelEnd, lastY, x, y, {
32978
+ "fillStyle": color,
32979
+ "strokeStyle": color
32980
+ });
32981
+ }
32982
+ IGVGraphics.strokeLine(ctx, x, y, x + width, y, {"fillStyle": color, "strokeStyle": color});
32983
+ } else if (this.graphType === "points") {
32984
+ const pointSize = this.config.pointSize || 3;
32985
+ const px = x + width / 2;
32986
+ IGVGraphics.fillCircle(ctx, px, y, pointSize / 2, {"fillStyle": color, "strokeStyle": color});
32508
32987
 
32509
- const group = this.groupCache[name];
32510
- if (group) {
32511
- return group
32512
- } else {
32988
+ if (f.value > this.dataRange.max) {
32989
+ IGVGraphics.fillCircle(ctx, px, pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
32990
+ } else if (f.value < this.dataRange.min) {
32991
+ IGVGraphics.fillCircle(ctx, px, pixelHeight - pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
32992
+ }
32513
32993
 
32514
- await this.readHeader();
32515
- const indexEntry = this.groupIndex[name];
32516
- if (indexEntry === undefined) {
32517
- return undefined
32518
- }
32994
+ } else {
32995
+ // Default graph type (bar)
32996
+ const height = Math.min(pixelHeight, y - y0);
32997
+ IGVGraphics.fillRect(ctx, x, y0, width, height, {fillStyle: color});
32998
+ if (f.value > this.dataRange.max) {
32999
+ IGVGraphics.fillRect(ctx, x, 0, width, 3, {fillStyle: this.overflowColor});
33000
+ } else if (f.value < this.dataRange.min) {
33001
+ IGVGraphics.fillRect(ctx, x, pixelHeight - 2, width, 3, {fillStyle: this.overflowColor});
33002
+ }
32519
33003
 
32520
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32521
- range: {
32522
- start: indexEntry.position,
32523
- size: indexEntry.size
33004
+ }
33005
+ lastPixelEnd = x + width;
33006
+ lastY = y;
32524
33007
  }
32525
- }));
32526
33008
 
32527
- if (!data) {
32528
- return undefined
33009
+ // If the track includes negative values draw a baseline
33010
+ if (this.dataRange.min < 0) {
33011
+ let maxValue = this.dataRange.max;
33012
+ let minValue = this.dataRange.min;
33013
+ minValue = (this.logScale === true) ? ((minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1)) : minValue;
33014
+ maxValue = (this.logScale === true) ? ((maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1)) : maxValue;
33015
+ const ratio = maxValue / (maxValue - minValue);
33016
+ const basepx = this.flipAxis ? (1 - ratio) * pixelHeight : ratio * pixelHeight;
33017
+ IGVGraphics.strokeLine(ctx, 0, basepx, options.pixelWidth, basepx, {strokeStyle: this.baselineColor});
33018
+ }
32529
33019
  }
33020
+ }
32530
33021
 
32531
- const binaryParser = new BinaryParser$1(new DataView(data));
32532
- const group = {name: name};
32533
- let nAttributes = binaryParser.getInt();
32534
- while (nAttributes-- > 0) {
32535
- const key = binaryParser.getString();
32536
- const value = binaryParser.getString();
32537
- group[key] = value;
33022
+ // Draw guidelines
33023
+ if (this.config.hasOwnProperty('guideLines')) {
33024
+ for (let line of this.config.guideLines) {
33025
+ if (line.hasOwnProperty('color') && line.hasOwnProperty('y') && line.hasOwnProperty('dotted')) {
33026
+ let y = yScale(line.y);
33027
+ let props = {
33028
+ 'strokeStyle': line['color'],
33029
+ 'strokeWidth': 2
33030
+ };
33031
+ if (line['dotted']) IGVGraphics.dashedLine(options.context, 0, y, options.pixelWidth, y, 5, props);
33032
+ else IGVGraphics.strokeLine(options.context, 0, y, options.pixelWidth, y, props);
33033
+ }
32538
33034
  }
32539
- this.groupCache[name] = group;
32540
- return group
32541
33035
  }
32542
33036
  }
32543
33037
 
32544
- async readTiles(tileIndeces, nTracks) {
33038
+ popupData(clickState, features) {
32545
33039
 
32546
- tileIndeces.sort(function (a, b) {
32547
- return a.position - b.position
32548
- });
33040
+ if (features === undefined) features = this.clickedFeatures(clickState);
32549
33041
 
32550
- tileIndeces = tileIndeces.filter(function (idx) {
32551
- return idx.size > 0
32552
- });
33042
+ if (features && features.length > 0) {
32553
33043
 
32554
- if (tileIndeces.length === 0) {
32555
- return []
32556
- }
33044
+ const genomicLocation = clickState.genomicLocation;
33045
+ const popupData = [];
32557
33046
 
32558
- tileIndeces = consolidateTiles(tileIndeces);
33047
+ // Sort features based on distance from click
33048
+ features.sort(function (a, b) {
33049
+ const distA = Math.abs((a.start + a.end) / 2 - genomicLocation);
33050
+ const distB = Math.abs((b.start + b.end) / 2 - genomicLocation);
33051
+ return distA - distB
33052
+ });
32559
33053
 
32560
- const tiles = [];
33054
+ // Display closest 10
33055
+ const displayFeatures = features.length > 10 ? features.slice(0, 10) : features;
32561
33056
 
32562
- for (let indexEntry of tileIndeces) {
33057
+ // Resort in ascending order
33058
+ displayFeatures.sort(function (a, b) {
33059
+ return a.start - b.start
33060
+ });
32563
33061
 
32564
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32565
- range: {
32566
- start: indexEntry.position,
32567
- size: indexEntry.size
33062
+ for (let selectedFeature of displayFeatures) {
33063
+ if (selectedFeature) {
33064
+ if (popupData.length > 0) {
33065
+ popupData.push('<hr/>');
33066
+ }
33067
+ let posString = (selectedFeature.end - selectedFeature.start) === 1 ?
33068
+ numberFormatter$1(Math.floor(selectedFeature.start) + 1)
33069
+ : numberFormatter$1(Math.floor(selectedFeature.start) + 1) + "-" + numberFormatter$1(Math.floor(selectedFeature.end));
33070
+ popupData.push({name: "Position:", value: posString});
33071
+ popupData.push({
33072
+ name: "Value:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;",
33073
+ value: numberFormatter$1(selectedFeature.value.toFixed(4))
33074
+ });
32568
33075
  }
32569
- }));
32570
-
32571
- const tileData = this.compressed ? inflate_1$3(data).buffer : data;
32572
-
32573
- const binaryParser = new BinaryParser$1(new DataView(tileData));
32574
- const type = binaryParser.getString();
32575
- let tile;
32576
- switch (type) {
32577
- case "fixedStep":
32578
- tile = createFixedStep(binaryParser, nTracks);
32579
- break
32580
- case "variableStep":
32581
- tile = createVariableStep(binaryParser, nTracks);
32582
- break
32583
- case "bed":
32584
- case "bedWithName":
32585
- tile = createBed(binaryParser, nTracks, type);
32586
- break
32587
- default:
32588
- throw "Unknown tile type: " + type
32589
33076
  }
32590
- tiles.push(tile);
32591
-
32592
- }
32593
- return tiles
32594
- }
32595
-
32596
- async readTile(indexEntry, nTracks) {
32597
-
32598
- let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32599
- range: {
32600
- start: indexEntry.position,
32601
- size: indexEntry.size
33077
+ if (displayFeatures.length < features.length) {
33078
+ popupData.push("<hr/>...");
32602
33079
  }
32603
- }));
32604
33080
 
32605
- if (this.compressed) {
32606
- const plain = inflate_1$3(data);
32607
- data = plain.buffer;
32608
- }
33081
+ return popupData
32609
33082
 
32610
- const binaryParser = new BinaryParser$1(new DataView(data));
32611
- const type = binaryParser.getString();
32612
- switch (type) {
32613
- case "fixedStep":
32614
- return createFixedStep(binaryParser, nTracks)
32615
- case "variableStep":
32616
- return createVariableStep(binaryParser, nTracks)
32617
- case "bed":
32618
- case "bedWithName":
32619
- return createBed(binaryParser, nTracks, type)
32620
- default:
32621
- throw "Unknown tile type: " + type
33083
+ } else {
33084
+ return []
32622
33085
  }
32623
33086
  }
32624
33087
 
32625
- }
33088
+ get supportsWholeGenome() {
33089
+ return !this.config.indexURL && this.config.supportsWholeGenome !== false
33090
+ }
32626
33091
 
32627
- function createFixedStep(binaryParser, nTracks) {
32628
- const nPositions = binaryParser.getInt();
32629
- const start = binaryParser.getInt();
32630
- const span = binaryParser.getFloat();
33092
+ /**
33093
+ * Return color for feature.
33094
+ * @param feature
33095
+ * @returns {string}
33096
+ */
32631
33097
 
32632
- const data = [];
32633
- let nt = nTracks;
32634
- while (nt-- > 0) {
32635
- let np = nPositions;
32636
- const dtrack = [];
32637
- while (np-- > 0) {
32638
- dtrack.push(binaryParser.getFloat());
32639
- }
32640
- data.push(dtrack);
33098
+ getColorForFeature(f) {
33099
+ let c = (f.value < 0 && this.altColor) ? this.altColor : this.color || DEFAULT_COLOR$2;
33100
+ return (typeof c === "function") ? c(f.value) : c
32641
33101
  }
32642
33102
 
32643
- return {
32644
- type: "fixedStep",
32645
- start: start,
32646
- span: span,
32647
- data: data,
32648
- nTracks: nTracks,
32649
- nPositions: nPositions
33103
+ /**
33104
+ * Called when the track is removed. Do any needed cleanup here
33105
+ */
33106
+ dispose() {
33107
+ this.trackView = undefined;
32650
33108
  }
32651
- }
32652
33109
 
32653
- function createVariableStep(binaryParser, nTracks) {
33110
+ }
32654
33111
 
32655
- const tileStart = binaryParser.getInt();
32656
- const span = binaryParser.getFloat();
32657
- const nPositions = binaryParser.getInt();
32658
- const start = [];
33112
+ /**
33113
+ * Summarize wig data in bins of size "bpPerPixel" with the given window function.
33114
+ *
33115
+ * @param features wig (numeric) data -- features cannot overlap, and are in ascending order by start position
33116
+ * @param startBP bp start position for computing binned data
33117
+ * @param bpPerPixel bp per pixel (bin)
33118
+ * @param windowFunction mean, min, or max
33119
+ * @returns {*|*[]}
33120
+ */
33121
+ function summarizeData(features, startBP, bpPerPixel, windowFunction = "mean") {
32659
33122
 
32660
- let np = nPositions;
32661
- while (np-- > 0) {
32662
- start.push(binaryParser.getInt());
33123
+ if (bpPerPixel <= 1 || !features || features.length === 0) {
33124
+ return features
32663
33125
  }
32664
- binaryParser.getInt(); // # of samples, ignored but should === nTracks
32665
33126
 
32666
- const data = [];
32667
- let nt = nTracks;
32668
- while (nt-- > 0) {
32669
- np = nPositions;
32670
- const dtrack = [];
32671
- while (np-- > 0) {
32672
- dtrack.push(binaryParser.getFloat());
33127
+ // Assume features are sorted by position. Wig features cannot overlap. Note, UCSC "reductionLevel" == bpPerPixel
33128
+ const chr = features[0].chr;
33129
+ const binSize = bpPerPixel;
33130
+ const summaryFeatures = [];
33131
+
33132
+ const finishBin = (bin) => {
33133
+ const start = startBP + bin.bin * binSize;
33134
+ const end = start + binSize;
33135
+ let value;
33136
+ switch (windowFunction) {
33137
+ case "mean":
33138
+ value = bin.sumData / bin.count;
33139
+ break
33140
+ case "max":
33141
+ value = bin.max;
33142
+ break
33143
+ case "min":
33144
+ value = bin.min;
33145
+ break
33146
+ default:
33147
+ throw Error(`Unknown window function: ${windowFunction}`)
32673
33148
  }
32674
- data.push(dtrack);
32675
- }
33149
+ const description = `${windowFunction} of ${bin.count} values`;
33150
+ summaryFeatures.push({chr, start, end, value, description});
33151
+ };
32676
33152
 
32677
- return {
32678
- type: "variableStep",
32679
- tileStart: tileStart,
32680
- span: span,
32681
- start: start,
32682
- data: data,
32683
- nTracks: nTracks,
32684
- nPositions: nPositions
32685
- }
32686
- }
33153
+ let currentBinData;
33154
+ for (let f of features) {
32687
33155
 
32688
- function createBed(binaryParser, nTracks, type) {
33156
+ // Loop through bins this feature overlaps, updating the weighted sum for each bin or min/max,
33157
+ // depending on window function
33158
+ let startBin = Math.floor((f.start - startBP) / binSize);
33159
+ const endBin = Math.floor((f.end - startBP) / binSize);
33160
+
33161
+ if (currentBinData && startBin === currentBinData.bin) {
33162
+ currentBinData.add(f);
33163
+ startBin++;
33164
+ }
32689
33165
 
32690
- const nPositions = binaryParser.getInt();
33166
+ if (!currentBinData || endBin > currentBinData.bin) {
32691
33167
 
32692
- let n = nPositions;
32693
- const start = [];
32694
- while (n-- > 0) {
32695
- start.push(binaryParser.getInt());
32696
- }
33168
+ if(currentBinData) {
33169
+ finishBin(currentBinData);
33170
+ }
32697
33171
 
32698
- n = nPositions;
32699
- const end = [];
32700
- while (n-- > 0) {
32701
- end.push(binaryParser.getInt());
32702
- }
33172
+ // Feature stretches across multiple bins.
33173
+ if (endBin > startBin) {
33174
+ const end = startBP + endBin * binSize;
33175
+ summaryFeatures.push({chr, start: f.start, end, value: f.value});
33176
+ }
32703
33177
 
32704
- binaryParser.getInt(); // # of samples, ignored but should === nTracks
32705
- const data = [];
32706
- let nt = nTracks;
32707
- while (nt-- > 0) {
32708
- let np = nPositions;
32709
- const dtrack = [];
32710
- while (np-- > 0) {
32711
- dtrack.push(binaryParser.getFloat());
33178
+ currentBinData = new SummaryBinData(endBin, f);
32712
33179
  }
32713
- data.push(dtrack);
33180
+
33181
+ }
33182
+ if(currentBinData) {
33183
+ finishBin(currentBinData);
32714
33184
  }
32715
33185
 
32716
- if (type === "bedWithName") {
32717
- n = nPositions;
32718
- const name = [];
32719
- while (n-- > 0) {
32720
- name.push(binaryParser.getString());
33186
+ // Consolidate
33187
+ const c = [];
33188
+ let lastFeature = summaryFeatures[0];
33189
+ for (let f of summaryFeatures) {
33190
+ if (lastFeature.value === f.value && f.start <= lastFeature.end) {
33191
+ lastFeature.end = f.end;
33192
+ } else {
33193
+ c.push(lastFeature);
33194
+ lastFeature = f;
32721
33195
  }
32722
33196
  }
33197
+ c.push(lastFeature);
33198
+
33199
+ return c
32723
33200
 
32724
- return {
32725
- type: type,
32726
- start: start,
32727
- end: end,
32728
- data: data,
32729
- nTracks: nTracks,
32730
- nPositions: nPositions
32731
- }
32732
33201
  }
32733
33202
 
32734
- function consolidateTiles(tiles) {
33203
+ class SummaryBinData {
33204
+ constructor(bin, feature) {
33205
+ this.bin = bin;
33206
+ this.sumData = feature.value;
33207
+ this.count = 1;
33208
+ this.min = feature.value;
33209
+ this.max = feature.value;
33210
+ }
33211
+
33212
+ add(feature) {
33213
+ this.sumData += feature.value;
33214
+ this.max = Math.max(feature.value, this.max);
33215
+ this.min = Math.min(feature.value, this.min);
33216
+ this.count++;
33217
+ }
32735
33218
 
32736
- const consolidated = [];
32737
- let current = tiles[0];
32738
- for (let i = 1; i < tiles.length; i++) {
32739
- const t = tiles[i];
32740
- if (t.position > current.position + current.size) {
32741
- consolidated.push(current);
32742
- current = t;
32743
- } else {
32744
- current.size = t.position + t.size - current.position;
32745
- }
33219
+ get mean() {
33220
+ return this.sumData / this.count
32746
33221
  }
32747
- consolidated.push(current);
32748
- return consolidated
32749
33222
  }
32750
33223
 
32751
- /*
32752
- * The MIT License (MIT)
32753
- *
32754
- * Copyright (c) 2016 University of California San Diego
32755
- * Author: Jim Robinson
32756
- *
32757
- * Permission is hereby granted, free of charge, to any person obtaining a copy
32758
- * of this software and associated documentation files (the "Software"), to deal
32759
- * in the Software without restriction, including without limitation the rights
32760
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32761
- * copies of the Software, and to permit persons to whom the Software is
32762
- * furnished to do so, subject to the following conditions:
32763
- *
32764
- * The above copyright notice and this permission notice shall be included in
32765
- * all copies or substantial portions of the Software.
32766
- *
33224
+ const DEFAULT_MAX_WG_COUNT = 10000;
33225
+
33226
+ /**
33227
+ * feature source for "bed like" files (tab or whitespace delimited files with 1 feature per line: bed, gff, vcf, etc)
32767
33228
  *
32768
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32769
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32770
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32771
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32772
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32773
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32774
- * THE SOFTWARE.
33229
+ * @param config
33230
+ * @constructor
32775
33231
  */
33232
+ class TextFeatureSource extends BaseFeatureSource {
32776
33233
 
32777
- class TDFSource extends BaseFeatureSource {
32778
-
32779
- searchable = false
32780
33234
  constructor(config, genome) {
33235
+
32781
33236
  super(genome);
33237
+
33238
+ this.config = config || {};
32782
33239
  this.genome = genome;
32783
- this.reader = new TDFReader(config, genome);
32784
- this.queryable = true;
32785
- }
33240
+ this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
33241
+ this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
33242
+ this.windowFunctions = ["mean", "min", "max", "none"];
32786
33243
 
32787
- async getFeatures({chr, start, end, bpPerPixel, windowFunction = "mean"}) {
33244
+ const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
32788
33245
 
32789
- if (chr.toLowerCase() === "all") {
32790
- const wgFeatures = [];
32791
- const genome = this.genome;
32792
- const chrNames = this.genome.wgChromosomeNames;
32793
- if (chrNames) {
32794
- for (let c of genome.wgChromosomeNames) {
32795
- const len = genome.getChromosome(c).bpLength;
32796
- bpPerPixel = len / 1000;
32797
- const chrFeatures = await this._getFeatures(c, 0, len, bpPerPixel, windowFunction);
32798
- if (chrFeatures) {
32799
- for (let f of chrFeatures) {
32800
- const wg = Object.assign({}, f);
32801
- wg.chr = "all";
32802
- wg.start = genome.getGenomeCoordinate(f.chr, f.start);
32803
- wg.end = genome.getGenomeCoordinate(f.chr, f.end);
32804
- wg._f = f;
32805
- wgFeatures.push(wg);
32806
- }
32807
- }
32808
- }
32809
- }
32810
- return wgFeatures
33246
+ this.queryable = config.indexURL || config.queryable === true; // False by default, unless explicitly set
33247
+ if (config.reader) {
33248
+ // Explicit reader implementation
33249
+ this.reader = config.reader;
33250
+ this.queryable = config.queryable !== false;
33251
+ } else if (config.sourceType === "ga4gh") {
33252
+ throw Error("Unsupported source type 'ga4gh'")
33253
+ } else if ((config.type === "eqtl" || config.type === "qtl") && config.sourceType === "gtex-ws") {
33254
+ this.reader = new GtexReader(config);
33255
+ this.queryable = true;
33256
+ } else if ("htsget" === config.sourceType) {
33257
+ this.reader = new HtsgetVariantReader(config, genome);
33258
+ this.queryable = true;
33259
+ } else if (config.sourceType === 'ucscservice') {
33260
+ this.reader = new UCSCServiceReader(config.source);
33261
+ this.queryable = true;
33262
+ } else if (config.sourceType === 'custom') {
33263
+ this.reader = new CustomServiceReader(config.source);
33264
+ this.queryable = false !== config.source.queryable;
33265
+ } else if ('service' === config.sourceType) {
33266
+ this.reader = new FeatureFileReader(config, genome);
33267
+ this.queryable = true;
33268
+ } else {
33269
+ // File of some type (i.e. not a webservice)
33270
+ this.reader = new FeatureFileReader(config, genome);
33271
+ if (config.queryable !== undefined) {
33272
+ this.queryable = config.queryable;
33273
+ } else if (queryableFormats.has(config.format) || this.reader.indexed) {
33274
+ this.queryable = true;
33275
+ } else ;
33276
+ }
33277
+
33278
+ // Flag indicating if features loaded by this source can be searched for by name or attribute, true by default
33279
+ this.searchable = config.searchable !== false;
33280
+
33281
+ }
33282
+
33283
+ async defaultVisibilityWindow() {
33284
+ if (this.reader && typeof this.reader.defaultVisibilityWindow === 'function') {
33285
+ return this.reader.defaultVisibilityWindow()
33286
+ }
33287
+ }
32811
33288
 
33289
+ async trackType() {
33290
+ const header = await this.getHeader();
33291
+ if (header) {
33292
+ return header.type
32812
33293
  } else {
32813
- return this._getFeatures(chr, start, end, bpPerPixel, windowFunction)
33294
+ return undefined // Convention for unknown or unspecified
32814
33295
  }
32815
33296
  }
32816
- async _getFeatures(chr, start, end, bpPerPixel, windowFunction) {
32817
- const genomicInterval = new GenomicInterval(chr, start, end);
32818
- const genome = this.genome;
32819
33297
 
33298
+ async getHeader() {
33299
+ if (!this.header) {
32820
33300
 
32821
- if (!this.rootGroup) {
32822
- this.rootGroup = await this.reader.readRootGroup();
32823
- if (!this.normalizationFactor) {
32824
- const totalCount = this.rootGroup.totalCount;
32825
- if (totalCount) {
32826
- this.normalizationFactor = 1.0e6 / totalCount;
33301
+ if (this.reader && typeof this.reader.readHeader === "function") {
33302
+ const header = await this.reader.readHeader();
33303
+ if (header) {
33304
+ this.header = header;
33305
+ if (header.format) {
33306
+ this.config.format = header.format;
33307
+ }
33308
+ } else {
33309
+ this.header = {};
32827
33310
  }
33311
+ } else {
33312
+ this.header = {};
32828
33313
  }
32829
33314
  }
33315
+ return this.header
33316
+ }
32830
33317
 
32831
- genomicInterval.bpPerPixel = bpPerPixel;
32832
- const zoom = zoomLevelForScale(chr, bpPerPixel, genome);
32833
- let queryChr = this.reader.chrAliasTable[chr];
32834
- let maxZoom = this.reader.maxZoom;
32835
- if (queryChr === undefined) queryChr = chr;
32836
- if (maxZoom === undefined) maxZoom = -1;
33318
+ /**
33319
+ * Required function for all data source objects. Fetches features for the
33320
+ * range requested.
33321
+ *
33322
+ * This function is quite complex due to the variety of reader types backing it, some indexed, some queryable,
33323
+ * some not.
33324
+ *
33325
+ * @param chr
33326
+ * @param start
33327
+ * @param end
33328
+ * @param bpPerPixel
33329
+ */
33330
+ async getFeatures({chr, start, end, bpPerPixel, visibilityWindow, windowFunction}) {
32837
33331
 
32838
- const wf = zoom > maxZoom ? "raw" : windowFunction;
32839
- const dataset = await this.reader.readDataset(queryChr, wf, zoom);
32840
- if (dataset == null) {
32841
- return []
33332
+ const isWholeGenome = ("all" === chr.toLowerCase());
33333
+
33334
+ start = start || 0;
33335
+ end = end || Number.MAX_SAFE_INTEGER;
33336
+
33337
+ // Various conditions that can require a feature load
33338
+ // * view is "whole genome" but no features are loaded
33339
+ // * cache is disabled
33340
+ // * cache does not contain requested range
33341
+ // const containsRange = this.featureCache.containsRange(new GenomicInterval(queryChr, start, end))
33342
+ if ((isWholeGenome && !this.wgFeatures && this.supportsWholeGenome()) ||
33343
+ this.config.disableCache ||
33344
+ !this.featureCache ||
33345
+ !this.featureCache.containsRange(new GenomicInterval(chr, start, end))) {
33346
+ await this.loadFeatures(chr, start, end, visibilityWindow);
32842
33347
  }
32843
33348
 
32844
- const tileWidth = dataset.tileWidth;
32845
- const startTile = Math.floor(start / tileWidth);
32846
- const endTile = Math.floor(end / tileWidth);
32847
- const NTRACKS = 1; // TODO read this
32848
- const tiles = await this.reader.readTiles(dataset.tiles.slice(startTile, endTile + 1), NTRACKS);
32849
- const features = [];
32850
- for (let tile of tiles) {
32851
- switch (tile.type) {
32852
- case "bed":
32853
- decodeBedTile(tile, chr, start, end, bpPerPixel, features);
32854
- break
32855
- case "variableStep":
32856
- decodeVaryTile(tile, chr, start, end, bpPerPixel, features);
32857
- break
32858
- case "fixedStep":
32859
- decodeFixedTile(tile, chr, start, end, bpPerPixel, features);
32860
- break
32861
- default:
32862
- throw ("Unknown tile type: " + tile.type)
33349
+ if (isWholeGenome) {
33350
+ if (!this.wgFeatures) {
33351
+ if (this.supportsWholeGenome()) {
33352
+ if("wig" === this.config.type) {
33353
+ const allWgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, 1000000);
33354
+ this.wgFeatures = summarizeData(allWgFeatures, 0, bpPerPixel, windowFunction);
33355
+ } else {
33356
+ this.wgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, this.maxWGCount);
33357
+ }
33358
+ } else {
33359
+ this.wgFeatures = [];
33360
+ }
32863
33361
  }
33362
+ return this.wgFeatures
33363
+ } else {
33364
+ return this.featureCache.queryFeatures(chr, start, end)
32864
33365
  }
32865
- features.sort(function (a, b) {
32866
- return a.start - b.start
32867
- });
32868
-
32869
- return features
32870
33366
  }
32871
33367
 
32872
- get supportsWholeGenome() {
32873
- return true
33368
+ async findFeatures(fn) {
33369
+ return this.featureCache ? this.featureCache.findFeatures(fn) : []
32874
33370
  }
32875
33371
 
32876
- get windowFunctions() {
32877
- return this.reader.windowFunctions
33372
+ supportsWholeGenome() {
33373
+ return !this.queryable // queryable (indexed, web services) sources don't support whole genome view
32878
33374
  }
32879
- }
32880
33375
 
32881
- function decodeBedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
32882
-
32883
- const nPositions = tile.nPositions;
32884
- const starts = tile.start;
32885
- const ends = tile.end;
32886
- const data = tile.data[0]; // Single track for now
32887
- for (let i = 0; i < nPositions; i++) {
32888
- const s = starts[i];
32889
- const e = ends[i];
32890
- if (e < bpStart) continue
32891
- if (s > bpEnd) break
32892
- features.push({
32893
- chr: chr,
32894
- start: s,
32895
- end: e,
32896
- value: data[i]
32897
- });
33376
+ // TODO -- experimental, will only work for non-indexed sources
33377
+ getAllFeatures() {
33378
+ if (this.queryable || !this.featureCache) { // queryable sources don't support all features
33379
+ return []
33380
+ } else {
33381
+ return this.featureCache.getAllFeatures()
33382
+ }
32898
33383
  }
32899
- }
32900
33384
 
32901
- function decodeVaryTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
32902
33385
 
32903
- const nPositions = tile.nPositions;
32904
- const starts = tile.start;
32905
- const span = tile.span;
32906
- const data = tile.data[0]; // Single track for now
32907
- for (let i = 0; i < nPositions; i++) {
32908
- const s = starts[i];
32909
- const e = s + span;
32910
- if (e < bpStart) continue
32911
- if (s > bpEnd) break
32912
- features.push({
32913
- chr: chr,
32914
- start: s,
32915
- end: e,
32916
- value: data[i]
32917
- });
32918
- }
32919
- }
33386
+ async loadFeatures(chr, start, end, visibilityWindow) {
32920
33387
 
32921
- function decodeFixedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
33388
+ await this.getHeader();
32922
33389
 
32923
- const nPositions = tile.nPositions;
32924
- let s = tile.start;
32925
- const span = tile.span;
32926
- const data = tile.data[0]; // Single track for now
33390
+ const reader = this.reader;
33391
+ let intervalStart = start;
33392
+ let intervalEnd = end;
32927
33393
 
32928
- for (let i = 0; i < nPositions; i++) {
32929
- const e = s + span;
32930
- if (s > bpEnd) break
32931
- if (e >= bpStart) {
32932
- if (!Number.isNaN(data[i])) {
32933
- features.push({
32934
- chr: chr,
32935
- start: s,
32936
- end: e,
32937
- value: data[i]
32938
- });
33394
+ // chr aliasing
33395
+ let queryChr = chr;
33396
+ if (!this.chrAliasManager && this.reader && this.reader.sequenceNames) {
33397
+ this.chrAliasManager = new ChromAliasManager(this.reader.sequenceNames, this.genome);
33398
+ }
33399
+ if (this.chrAliasManager) {
33400
+ queryChr = await this.chrAliasManager.getAliasName(chr);
33401
+ }
33402
+
33403
+ // Use visibility window to potentially expand query interval.
33404
+ // This can save re-queries as we zoom out. Visibility window <= 0 is a special case
33405
+ // indicating whole chromosome should be read at once.
33406
+ if ((!visibilityWindow || visibilityWindow <= 0) && this.config.expandQuery !== false) {
33407
+ // Whole chromosome
33408
+ const chromosome = this.genome ? this.genome.getChromosome(queryChr) : undefined;
33409
+ intervalStart = 0;
33410
+ intervalEnd = Math.max(chromosome ? chromosome.bpLength : Number.MAX_SAFE_INTEGER, end);
33411
+ } else if (visibilityWindow > (end - start) && this.config.expandQuery !== false) {
33412
+ let expansionWindow = Math.min(4.1 * (end - start), visibilityWindow);
33413
+ if(this.config.minQuerySize && expansionWindow < this.config.minQuerySize) {
33414
+ expansionWindow = this.config.minQuerySize;
32939
33415
  }
33416
+ intervalStart = Math.max(0, (start + end - expansionWindow) / 2);
33417
+ intervalEnd = intervalStart + expansionWindow;
32940
33418
  }
32941
- s = e;
32942
- }
32943
- }
32944
33419
 
33420
+ let features = await reader.readFeatures(queryChr, intervalStart, intervalEnd);
33421
+ if (this.queryable === undefined) {
33422
+ this.queryable = reader.indexed;
33423
+ }
32945
33424
 
32946
- var log2 = Math.log(2);
33425
+ const genomicInterval = this.queryable ?
33426
+ new GenomicInterval(chr, intervalStart, intervalEnd) :
33427
+ undefined;
32947
33428
 
32948
- function zoomLevelForScale(chr, bpPerPixel, genome) {
33429
+ if (features) {
32949
33430
 
32950
- // Convert bpPerPixel to IGV "zoom" level. This is a bit convoluted, TDF is computed zoom levels assuming
32951
- // display in a 700 pixel window. The fully zoomed out view of a chromosome is zoom level "0".
32952
- // Zoom level 1 is magnified 2X, and so forth
33431
+ // Assign overlapping features to rows
33432
+ if (this.config.format !== "wig" && this.config.type !== "junctions") {
33433
+ const maxRows = this.config.maxRows || Number.MAX_SAFE_INTEGER;
33434
+ packFeatures(features, maxRows);
33435
+ }
32953
33436
 
32954
- var chrSize = genome.getChromosome(chr).bpLength;
33437
+ // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
33438
+ this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
32955
33439
 
32956
- return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
33440
+ // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
33441
+ if (this.searchable) {
33442
+ this.addFeaturesToDB(features, this.config);
33443
+ }
33444
+ } else {
33445
+ this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
33446
+ }
33447
+ }
33448
+
33449
+ addFeaturesToDB(featureList, config) {
33450
+ if (!this.featureMap) {
33451
+ this.featureMap = new Map();
33452
+ }
33453
+ const searchableFields = config.searchableFields || ["name", "transcript_id", "gene_id", "gene_name", "id"];
33454
+ for (let feature of featureList) {
33455
+ for (let field of searchableFields) {
33456
+ let key;
33457
+ if (typeof feature.getAttributeValue === 'function') {
33458
+ key = feature.getAttributeValue(field);
33459
+ }
33460
+ if (key) {
33461
+ key = key.replaceAll(' ', '+').toUpperCase();
33462
+ // If feature is already present keep largest one
33463
+ if (this.featureMap.has(key)) {
33464
+ const f2 = this.featureMap.get(key);
33465
+ if (feature.end - feature.start < f2.end - f2.start) {
33466
+ continue
33467
+ }
33468
+ }
33469
+ this.featureMap.set(key, feature);
33470
+ }
33471
+ }
33472
+ }
33473
+ }
33474
+
33475
+ search(term) {
33476
+ if (this.featureMap) {
33477
+ return this.featureMap.get(term.toUpperCase())
33478
+ }
33479
+
33480
+ }
32957
33481
  }
32958
33482
 
32959
33483
  /*
@@ -34245,7 +34769,7 @@
34245
34769
  }
34246
34770
  }
34247
34771
 
34248
- const DEFAULT_COLOR$2 = 'rgb(0, 0, 150)';
34772
+ const DEFAULT_COLOR$1 = 'rgb(0, 0, 150)';
34249
34773
 
34250
34774
 
34251
34775
  class FeatureTrack extends TrackBase {
@@ -34742,7 +35266,7 @@
34742
35266
 
34743
35267
  // If no explicit setting use the default
34744
35268
  if (!color) {
34745
- color = DEFAULT_COLOR$2; // Track default
35269
+ color = DEFAULT_COLOR$1; // Track default
34746
35270
  }
34747
35271
 
34748
35272
  if (feature.alpha && feature.alpha !== 1) {
@@ -37869,7 +38393,6 @@
37869
38393
  }
37870
38394
 
37871
38395
  const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
37872
- const BACKUP_GENOMES_URL = "https://s3.amazonaws.com/igv.org.genomes/genomes.json";
37873
38396
 
37874
38397
  const GenomeUtils = {
37875
38398
 
@@ -37881,21 +38404,9 @@
37881
38404
 
37882
38405
  // Get default genomes
37883
38406
  if (config.loadDefaultGenomes !== false) {
37884
- try {
37885
- const url = DEFAULT_GENOMES_URL;
37886
- const jsonArray = await igvxhr.loadJson(url, {timeout: 5000});
37887
- processJson(jsonArray);
37888
- } catch (e) {
37889
- console.error(e);
37890
- try {
37891
- const url = BACKUP_GENOMES_URL;
37892
- const jsonArray = await igvxhr.loadJson(url, {});
37893
- processJson(jsonArray);
37894
- } catch (e) {
37895
- console.error(e);
37896
- console.warn("Errors loading default genome definitions.");
37897
- }
37898
- }
38407
+ const url = DEFAULT_GENOMES_URL;
38408
+ const jsonArray = await igvxhr.loadJson(url, {timeout: 5000});
38409
+ processJson(jsonArray);
37899
38410
  }
37900
38411
 
37901
38412
  // Add user-defined genomes
@@ -37960,7 +38471,7 @@
37960
38471
  }
37961
38472
  }
37962
38473
 
37963
- if(!reference) {
38474
+ if (!reference) {
37964
38475
  alert.present(new Error(`Unknown genome id: ${genomeID}`), undefined);
37965
38476
  }
37966
38477
  }
@@ -41538,62 +42049,6 @@
41538
42049
  </g>
41539
42050
  </svg>`;
41540
42051
 
41541
- const shim = .01;
41542
- const colorStripWidth = 4;
41543
- const axesXOffset = colorStripWidth + 1;
41544
- function paintAxis(ctx, width, height, colorOrUndefined) {
41545
-
41546
- if (undefined === this.dataRange || undefined === this.dataRange.max || undefined === this.dataRange.min) {
41547
- return
41548
- }
41549
-
41550
- IGVGraphics.fillRect(ctx, 0, 0, width, height, { fillStyle: 'white' });
41551
- if (colorOrUndefined) {
41552
- IGVGraphics.fillRect(ctx, width - colorStripWidth - 2, 0, colorStripWidth, height, { fillStyle: colorOrUndefined });
41553
- }
41554
-
41555
- const flipAxis = (undefined === this.flipAxis) ? false : this.flipAxis;
41556
-
41557
- const xTickStart = 0.95 * width - 8 - axesXOffset;
41558
- const xTickEnd = 0.95 * width - axesXOffset;
41559
-
41560
- const properties =
41561
- {
41562
- font: 'normal 10px Arial',
41563
- textAlign: 'right',
41564
- fillStyle: 'black',
41565
- strokeStyle: 'black',
41566
- };
41567
-
41568
- // tick
41569
- IGVGraphics.strokeLine(ctx, xTickStart, shim * height, xTickEnd, shim * height, properties);
41570
- IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.min : this.dataRange.max), xTickStart + 4, shim * height + 12, properties);
41571
-
41572
- const y = (1.0 - shim) * height;
41573
-
41574
- // tick
41575
- IGVGraphics.strokeLine(ctx, xTickStart, y, xTickEnd, y, properties);
41576
- IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.max : this.dataRange.min), xTickStart + 4, y - 4, properties);
41577
-
41578
- // vertical axis
41579
- IGVGraphics.strokeLine(ctx, xTickEnd, shim * height, xTickEnd, y, properties);
41580
-
41581
- function prettyPrint(number) {
41582
-
41583
- if (number === 0) {
41584
- return "0"
41585
- } else if (Math.abs(number) >= 10) {
41586
- return number.toFixed()
41587
- } else if (Math.abs(number) >= 1) {
41588
- return number.toFixed(1)
41589
- } else if (Math.abs(number) >= 0.1) {
41590
- return number.toFixed(2)
41591
- } else {
41592
- return number.toExponential(1)
41593
- }
41594
- }
41595
- }
41596
-
41597
42052
  /*
41598
42053
  * The MIT License (MIT)
41599
42054
  *
@@ -43189,462 +43644,6 @@
43189
43644
 
43190
43645
  }
43191
43646
 
43192
- const DEFAULT_COLOR$1 = 'rgb(150, 150, 150)';
43193
-
43194
-
43195
- class WigTrack extends TrackBase {
43196
-
43197
- static defaults = {
43198
- height: 50,
43199
- flipAxis: false,
43200
- logScale: false,
43201
- windowFunction: 'mean',
43202
- graphType: 'bar',
43203
- normalize: undefined,
43204
- scaleFactor: undefined,
43205
- overflowColor: `rgb(255, 32, 255)`,
43206
- baselineColor: 'lightGray',
43207
- summarize: true
43208
- }
43209
-
43210
- constructor(config, browser) {
43211
- super(config, browser);
43212
- }
43213
-
43214
- init(config) {
43215
-
43216
- super.init(config);
43217
-
43218
- this.type = "wig";
43219
- this.featureType = 'numeric';
43220
- this.resolutionAware = true;
43221
- this.paintAxis = paintAxis;
43222
-
43223
- const format = config.format ? config.format.toLowerCase() : config.format;
43224
- if (config.featureSource) {
43225
- this.featureSource = config.featureSource;
43226
- delete config.featureSource;
43227
- } else if ("bigwig" === format) {
43228
- this.featureSource = new BWSource(config, this.browser.genome);
43229
- } else if ("tdf" === format) {
43230
- this.featureSource = new TDFSource(config, this.browser.genome);
43231
- } else {
43232
- this.featureSource = FeatureSource(config, this.browser.genome);
43233
- }
43234
-
43235
-
43236
- // Override autoscale default
43237
- if (config.max === undefined || config.autoscale === true) {
43238
- this.autoscale = true;
43239
- } else {
43240
- this.dataRange = {
43241
- min: config.min || 0,
43242
- max: config.max
43243
- };
43244
- }
43245
- }
43246
-
43247
- async postInit() {
43248
- const header = await this.getHeader();
43249
- if (this.disposed) return // This track was removed during async load
43250
- if (header) this.setTrackProperties(header);
43251
- }
43252
-
43253
- async getFeatures(chr, start, end, bpPerPixel) {
43254
-
43255
- const windowFunction = this.windowFunction;
43256
-
43257
- const features = await this.featureSource.getFeatures({
43258
- chr,
43259
- start,
43260
- end,
43261
- bpPerPixel,
43262
- visibilityWindow: this.visibilityWindow,
43263
- windowFunction
43264
- });
43265
- if (this.normalize && this.featureSource.normalizationFactor) {
43266
- const scaleFactor = this.featureSource.normalizationFactor;
43267
- for (let f of features) {
43268
- f.value *= scaleFactor;
43269
- }
43270
- }
43271
- if (this.scaleFactor) {
43272
- const scaleFactor = this.scaleFactor;
43273
- for (let f of features) {
43274
- f.value *= scaleFactor;
43275
- }
43276
- }
43277
-
43278
- // Summarize features to current resolution. This needs to be done here, rather than in the "draw" function,
43279
- // for group autoscale to work.
43280
- if (this.summarize && ("mean" === windowFunction || "min" === windowFunction || "max" === windowFunction)) {
43281
- return summarizeData(features, start, bpPerPixel, windowFunction)
43282
- } else {
43283
- return features
43284
- }
43285
- }
43286
-
43287
- menuItemList() {
43288
- const items = [];
43289
-
43290
- if (this.flipAxis !== undefined) {
43291
- items.push('<hr>');
43292
-
43293
- function click() {
43294
- this.flipAxis = !this.flipAxis;
43295
- this.trackView.repaintViews();
43296
- }
43297
-
43298
- items.push({label: 'Flip y-axis', click});
43299
- }
43300
-
43301
- if(this.featureSource.windowFunctions) {
43302
- items.push(...this.wigSummarizationItems());
43303
- }
43304
-
43305
- items.push(...this.numericDataMenuItems());
43306
-
43307
- return items
43308
- }
43309
-
43310
- wigSummarizationItems() {
43311
-
43312
- const windowFunctions = this.featureSource.windowFunctions;
43313
-
43314
- const menuItems = [];
43315
- menuItems.push('<hr/>');
43316
- menuItems.push("<div>Windowing function</div>");
43317
- for (const wf of windowFunctions) {
43318
- const object = $$1(createCheckbox(wf, this.windowFunction === wf));
43319
-
43320
- function clickHandler() {
43321
- this.windowFunction = wf;
43322
- this.trackView.updateViews();
43323
- }
43324
-
43325
- menuItems.push({object, click: clickHandler});
43326
- }
43327
-
43328
- return menuItems
43329
- }
43330
-
43331
-
43332
- async getHeader() {
43333
-
43334
- if (typeof this.featureSource.getHeader === "function") {
43335
- this.header = await this.featureSource.getHeader();
43336
- }
43337
- return this.header
43338
- }
43339
-
43340
- // TODO: refactor to igvUtils.js
43341
- getScaleFactor(min, max, height, logScale) {
43342
- const scale = logScale ? height / (Math.log10(max + 1) - (min <= 0 ? 0 : Math.log10(min + 1))) : height / (max - min);
43343
- return scale
43344
- }
43345
-
43346
- computeYPixelValue(yValue, yScaleFactor) {
43347
- return (this.flipAxis ? (yValue - this.dataRange.min) : (this.dataRange.max - yValue)) * yScaleFactor
43348
- }
43349
-
43350
- computeYPixelValueInLogScale(yValue, yScaleFactor) {
43351
- let maxValue = this.dataRange.max;
43352
- let minValue = this.dataRange.min;
43353
- if (maxValue <= 0) return 0 // TODO:
43354
- if (minValue <= -1) minValue = 0;
43355
- minValue = (minValue <= 0) ? 0 : Math.log10(minValue + 1);
43356
- maxValue = Math.log10(maxValue + 1);
43357
- yValue = Math.log10(yValue + 1);
43358
- return ((this.flipAxis ? (yValue - minValue) : (maxValue - yValue)) * yScaleFactor)
43359
- }
43360
-
43361
- draw(options) {
43362
-
43363
- const features = options.features;
43364
- const ctx = options.context;
43365
- const bpPerPixel = options.bpPerPixel;
43366
- const bpStart = options.bpStart;
43367
- const pixelWidth = options.pixelWidth;
43368
- const pixelHeight = options.pixelHeight;
43369
- const bpEnd = bpStart + pixelWidth * bpPerPixel + 1;
43370
- this.color || DEFAULT_COLOR$1;
43371
- const scaleFactor = this.getScaleFactor(this.dataRange.min, this.dataRange.max, options.pixelHeight, this.logScale);
43372
- const yScale = (yValue) => this.logScale
43373
- ? this.computeYPixelValueInLogScale(yValue, scaleFactor)
43374
- : this.computeYPixelValue(yValue, scaleFactor);
43375
-
43376
- if (features && features.length > 0) {
43377
-
43378
- if (this.dataRange.min === undefined) this.dataRange.min = 0;
43379
-
43380
- // Max can be less than min if config.min is set but max left to autoscale. If that's the case there is
43381
- // nothing to paint.
43382
- if (this.dataRange.max > this.dataRange.min) {
43383
-
43384
- let lastPixelEnd = -1;
43385
- let lastY;
43386
- const y0 = yScale(0);
43387
-
43388
- for (let f of features) {
43389
-
43390
- if (f.end < bpStart) continue
43391
- if (f.start > bpEnd) break
43392
-
43393
- const x = (f.start - bpStart) / bpPerPixel;
43394
- if (isNaN(x)) continue
43395
-
43396
- let y = yScale(f.value);
43397
-
43398
- const rectEnd = (f.end - bpStart) / bpPerPixel;
43399
- const width = rectEnd - x;
43400
-
43401
- const color = options.alpha ? IGVColor.addAlpha(this.getColorForFeature(f), options.alpha) : this.getColorForFeature(f);
43402
-
43403
- if (this.graphType === "line") {
43404
- if (lastY !== undefined) {
43405
- IGVGraphics.strokeLine(ctx, lastPixelEnd, lastY, x, y, {
43406
- "fillStyle": color,
43407
- "strokeStyle": color
43408
- });
43409
- }
43410
- IGVGraphics.strokeLine(ctx, x, y, x + width, y, {"fillStyle": color, "strokeStyle": color});
43411
- } else if (this.graphType === "points") {
43412
- const pointSize = this.config.pointSize || 3;
43413
- const px = x + width / 2;
43414
- IGVGraphics.fillCircle(ctx, px, y, pointSize / 2, {"fillStyle": color, "strokeStyle": color});
43415
-
43416
- if (f.value > this.dataRange.max) {
43417
- IGVGraphics.fillCircle(ctx, px, pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
43418
- } else if (f.value < this.dataRange.min) {
43419
- IGVGraphics.fillCircle(ctx, px, pixelHeight - pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
43420
- }
43421
-
43422
- } else {
43423
- // Default graph type (bar)
43424
- const height = Math.min(pixelHeight, y - y0);
43425
- IGVGraphics.fillRect(ctx, x, y0, width, height, {fillStyle: color});
43426
- if (f.value > this.dataRange.max) {
43427
- IGVGraphics.fillRect(ctx, x, 0, width, 3, {fillStyle: this.overflowColor});
43428
- } else if (f.value < this.dataRange.min) {
43429
- IGVGraphics.fillRect(ctx, x, pixelHeight - 3, width, 3, {fillStyle: this.overflowColor});
43430
- }
43431
-
43432
- }
43433
- lastPixelEnd = x + width;
43434
- lastY = y;
43435
- }
43436
-
43437
- // If the track includes negative values draw a baseline
43438
- if (this.dataRange.min < 0) {
43439
- const ratio = this.dataRange.max / (this.dataRange.max - this.dataRange.min);
43440
- const basepx = this.flipAxis ? (1 - ratio) * options.pixelHeight : ratio * options.pixelHeight;
43441
- IGVGraphics.strokeLine(ctx, 0, basepx, options.pixelWidth, basepx, {strokeStyle: this.baselineColor});
43442
- }
43443
- }
43444
- }
43445
-
43446
- // Draw guidelines
43447
- if (this.config.hasOwnProperty('guideLines')) {
43448
- for (let line of this.config.guideLines) {
43449
- if (line.hasOwnProperty('color') && line.hasOwnProperty('y') && line.hasOwnProperty('dotted')) {
43450
- let y = yScale(line.y);
43451
- let props = {
43452
- 'strokeStyle': line['color'],
43453
- 'strokeWidth': 2
43454
- };
43455
- if (line['dotted']) IGVGraphics.dashedLine(options.context, 0, y, options.pixelWidth, y, 5, props);
43456
- else IGVGraphics.strokeLine(options.context, 0, y, options.pixelWidth, y, props);
43457
- }
43458
- }
43459
- }
43460
- }
43461
-
43462
- popupData(clickState, features) {
43463
-
43464
- if (features === undefined) features = this.clickedFeatures(clickState);
43465
-
43466
- if (features && features.length > 0) {
43467
-
43468
- const genomicLocation = clickState.genomicLocation;
43469
- const popupData = [];
43470
-
43471
- // Sort features based on distance from click
43472
- features.sort(function (a, b) {
43473
- const distA = Math.abs((a.start + a.end) / 2 - genomicLocation);
43474
- const distB = Math.abs((b.start + b.end) / 2 - genomicLocation);
43475
- return distA - distB
43476
- });
43477
-
43478
- // Display closest 10
43479
- const displayFeatures = features.length > 10 ? features.slice(0, 10) : features;
43480
-
43481
- // Resort in ascending order
43482
- displayFeatures.sort(function (a, b) {
43483
- return a.start - b.start
43484
- });
43485
-
43486
- for (let selectedFeature of displayFeatures) {
43487
- if (selectedFeature) {
43488
- if (popupData.length > 0) {
43489
- popupData.push('<hr/>');
43490
- }
43491
- let posString = (selectedFeature.end - selectedFeature.start) === 1 ?
43492
- numberFormatter$1(Math.floor(selectedFeature.start) + 1)
43493
- : numberFormatter$1(Math.floor(selectedFeature.start) + 1) + "-" + numberFormatter$1(Math.floor(selectedFeature.end));
43494
- popupData.push({name: "Position:", value: posString});
43495
- popupData.push({
43496
- name: "Value:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;",
43497
- value: numberFormatter$1(selectedFeature.value.toFixed(4))
43498
- });
43499
- }
43500
- }
43501
- if (displayFeatures.length < features.length) {
43502
- popupData.push("<hr/>...");
43503
- }
43504
-
43505
- return popupData
43506
-
43507
- } else {
43508
- return []
43509
- }
43510
- }
43511
-
43512
- get supportsWholeGenome() {
43513
- return !this.config.indexURL && this.config.supportsWholeGenome !== false
43514
- }
43515
-
43516
- /**
43517
- * Return color for feature.
43518
- * @param feature
43519
- * @returns {string}
43520
- */
43521
-
43522
- getColorForFeature(f) {
43523
- let c = (f.value < 0 && this.altColor) ? this.altColor : this.color || DEFAULT_COLOR$1;
43524
- return (typeof c === "function") ? c(f.value) : c
43525
- }
43526
-
43527
- /**
43528
- * Called when the track is removed. Do any needed cleanup here
43529
- */
43530
- dispose() {
43531
- this.trackView = undefined;
43532
- }
43533
-
43534
- }
43535
-
43536
- /**
43537
- * Summarize wig data in bins of size "bpPerPixel" with the given window function.
43538
- *
43539
- * @param features wig (numeric) data -- features cannot overlap, and are in ascending order by start position
43540
- * @param startBP bp start position for computing binned data
43541
- * @param bpPerPixel bp per pixel (bin)
43542
- * @param windowFunction mean, min, or max
43543
- * @returns {*|*[]}
43544
- */
43545
- function summarizeData(features, startBP, bpPerPixel, windowFunction = "mean") {
43546
-
43547
- if (bpPerPixel <= 1 || !features || features.length === 0) {
43548
- return features
43549
- }
43550
-
43551
- // Assume features are sorted by position. Wig features cannot overlap. Note, UCSC "reductionLevel" == bpPerPixel
43552
- const chr = features[0].chr;
43553
- const binSize = bpPerPixel;
43554
- const summaryFeatures = [];
43555
-
43556
- const finishBin = (bin) => {
43557
- const start = startBP + bin.bin * binSize;
43558
- const end = start + binSize;
43559
- let value;
43560
- switch (windowFunction) {
43561
- case "mean":
43562
- value = bin.sumData / bin.count;
43563
- break
43564
- case "max":
43565
- value = bin.max;
43566
- break
43567
- case "min":
43568
- value = bin.min;
43569
- break
43570
- default:
43571
- throw Error(`Unknown window function: ${windowFunction}`)
43572
- }
43573
- const description = `${windowFunction} of ${bin.count} values`;
43574
- summaryFeatures.push({chr, start, end, value, description});
43575
- };
43576
-
43577
- let currentBinData;
43578
- for (let f of features) {
43579
-
43580
- // Loop through bins this feature overlaps, updating the weighted sum for each bin or min/max,
43581
- // depending on window function
43582
- let startBin = Math.floor((f.start - startBP) / binSize);
43583
- const endBin = Math.floor((f.end - startBP) / binSize);
43584
-
43585
- if (currentBinData && startBin === currentBinData.bin) {
43586
- currentBinData.add(f);
43587
- startBin++;
43588
- }
43589
-
43590
- if (!currentBinData || endBin > currentBinData.bin) {
43591
-
43592
- if(currentBinData) {
43593
- finishBin(currentBinData);
43594
- }
43595
-
43596
- // Feature stretches across multiple bins.
43597
- if (endBin > startBin) {
43598
- const end = startBP + endBin * binSize;
43599
- summaryFeatures.push({chr, start: f.start, end, value: f.value});
43600
- }
43601
-
43602
- currentBinData = new SummaryBinData(endBin, f);
43603
- }
43604
-
43605
- }
43606
- if(currentBinData) {
43607
- finishBin(currentBinData);
43608
- }
43609
-
43610
- // Consolidate
43611
- const c = [];
43612
- let lastFeature = summaryFeatures[0];
43613
- for (let f of summaryFeatures) {
43614
- if (lastFeature.value === f.value && f.start <= lastFeature.end) {
43615
- lastFeature.end = f.end;
43616
- } else {
43617
- c.push(lastFeature);
43618
- lastFeature = f;
43619
- }
43620
- }
43621
- c.push(lastFeature);
43622
-
43623
- return c
43624
-
43625
- }
43626
-
43627
- class SummaryBinData {
43628
- constructor(bin, feature) {
43629
- this.bin = bin;
43630
- this.sumData = feature.value;
43631
- this.count = 1;
43632
- this.min = feature.value;
43633
- this.max = feature.value;
43634
- }
43635
-
43636
- add(feature) {
43637
- this.sumData += feature.value;
43638
- this.max = Math.max(feature.value, this.max);
43639
- this.min = Math.min(feature.value, this.min);
43640
- this.count++;
43641
- }
43642
-
43643
- get mean() {
43644
- return this.sumData / this.count
43645
- }
43646
- }
43647
-
43648
43647
  /**
43649
43648
  *
43650
43649
  * @param cs - object containing
@@ -48559,7 +48558,7 @@
48559
48558
  allAlignments() {
48560
48559
  if (this.alignments) {
48561
48560
  return this.alignments
48562
- } else {
48561
+ } else if (this.packedGroups) {
48563
48562
  const all = Array.from(this.packedGroups.values()).flatMap(group => group.rows.flatMap(row => row.alignments));
48564
48563
  if (this.#unpacked && this.#unpacked.length > 0) {
48565
48564
  for (let a of this.#unpacked) {
@@ -48567,6 +48566,8 @@
48567
48566
  }
48568
48567
  }
48569
48568
  return all
48569
+ } else {
48570
+ return []
48570
48571
  }
48571
48572
  }
48572
48573
 
@@ -48575,9 +48576,10 @@
48575
48576
  }
48576
48577
 
48577
48578
  sortRows(options) {
48578
-
48579
- for (let group of this.packedGroups.values()) {
48580
- group.sortRows(options, this);
48579
+ if(this.packedGroups) {
48580
+ for (let group of this.packedGroups.values()) {
48581
+ group.sortRows(options, this);
48582
+ }
48581
48583
  }
48582
48584
  }
48583
48585
  }
@@ -57367,23 +57369,24 @@
57367
57369
  const y = clickState.y;
57368
57370
  const offsetY = y - this.top;
57369
57371
  const genomicLocation = clickState.genomicLocation;
57370
- const showSoftClips = this.showSoftClips;
57371
-
57372
- let minGroupY = Number.MAX_VALUE;
57373
- for (let group of features.packedGroups.values()) {
57374
- minGroupY = Math.min(minGroupY, group.pixelTop);
57375
- if (offsetY > group.pixelTop && offsetY <= group.pixelBottom) {
57376
-
57377
- const alignmentRowHeight = this.displayMode === "SQUISHED" ?
57378
- this.squishedRowHeight :
57379
- this.alignmentRowHeight;
57380
-
57381
- let packedAlignmentsIndex = Math.floor((offsetY - group.pixelTop) / alignmentRowHeight);
57382
-
57383
- if (packedAlignmentsIndex >= 0 && packedAlignmentsIndex < group.length) {
57384
- const alignmentRow = group.rows[packedAlignmentsIndex];
57385
- const clicked = alignmentRow.alignments.filter(alignment => alignment.containsLocation(genomicLocation, showSoftClips));
57386
- if (clicked.length > 0) return clicked[0]
57372
+
57373
+ if(features.packedGroups) {
57374
+ let minGroupY = Number.MAX_VALUE;
57375
+ for (let group of features.packedGroups.values()) {
57376
+ minGroupY = Math.min(minGroupY, group.pixelTop);
57377
+ if (offsetY > group.pixelTop && offsetY <= group.pixelBottom) {
57378
+
57379
+ const alignmentRowHeight = this.displayMode === "SQUISHED" ?
57380
+ this.squishedRowHeight :
57381
+ this.alignmentRowHeight;
57382
+
57383
+ let packedAlignmentsIndex = Math.floor((offsetY - group.pixelTop) / alignmentRowHeight);
57384
+
57385
+ if (packedAlignmentsIndex >= 0 && packedAlignmentsIndex < group.length) {
57386
+ const alignmentRow = group.rows[packedAlignmentsIndex];
57387
+ const clicked = alignmentRow.alignments.filter(alignment => alignment.containsLocation(genomicLocation, this.showSoftClips));
57388
+ if (clicked.length > 0) return clicked[0]
57389
+ }
57387
57390
  }
57388
57391
  }
57389
57392
  }
@@ -70518,7 +70521,7 @@
70518
70521
  })
70519
70522
  }
70520
70523
 
70521
- const _version = "3.0.2";
70524
+ const _version = "3.0.3";
70522
70525
  function version() {
70523
70526
  return _version
70524
70527
  }
@@ -71989,16 +71992,17 @@
71989
71992
 
71990
71993
  }
71991
71994
 
71992
- async present(feature, isUserDefined, event, roiManager, columnContainer, regionElement) {
71993
- const menuItems = this.menuItems(feature, isUserDefined, event, roiManager, columnContainer, regionElement);
71995
+ async present(feature, roiSet, event, roiManager, columnContainer, regionElement) {
71996
+ const menuItems = this.menuItems(feature, roiSet, event, roiManager, columnContainer, regionElement);
71994
71997
  this.browser.menuPopup.presentTrackContextMenu(event, menuItems);
71995
71998
  }
71996
71999
 
71997
- menuItems(feature, isUserDefined, event, roiManager, columnContainer, regionElement) {
72000
+ menuItems(feature, roiSet, event, roiManager, columnContainer, regionElement) {
72001
+ const items = feature.name ? [`<b>${feature.name}</b><br/>`] : [];
72002
+ if ('name' in roiSet) items.push(`<b>ROI Set: ${roiSet.name}</b>`);
72003
+ if (items.length > 0) items.push(`<hr/>`);
71998
72004
 
71999
- const items = [`<b>${feature.name || ''}</b>`,];
72000
-
72001
- if (isUserDefined) {
72005
+ if (roiSet.isUserDefined) {
72002
72006
  items.push(
72003
72007
  {
72004
72008
  label: 'Set description ...',
@@ -72062,7 +72066,7 @@
72062
72066
  }
72063
72067
 
72064
72068
 
72065
- if (isUserDefined) {
72069
+ if (roiSet.isUserDefined) {
72066
72070
  items.push(
72067
72071
  '<hr/>',
72068
72072
  {
@@ -72347,13 +72351,17 @@
72347
72351
  const [rectA, rectB] = tracks
72348
72352
  .map(track => track.trackView.viewports[0].$viewport.get(0))
72349
72353
  .map(element => getElementVerticalDimension(element));
72350
-
72354
+
72355
+ //Covers cases in which ruler and/or ideogram are hidden
72356
+ const heightA = rectA ? rectA.height : 0;
72357
+ const heightB = rectB ? rectB.height : 0;
72358
+
72351
72359
  const elements = browser.columnContainer.querySelectorAll('.igv-roi-region');
72352
72360
 
72353
72361
  const fudge = -0.5;
72354
72362
  if (elements) {
72355
72363
  for (const element of elements) {
72356
- element.style.marginTop = `${rectA.height + rectB.height + fudge}px`;
72364
+ element.style.marginTop = `${heightA + heightB + fudge}px`;
72357
72365
  }
72358
72366
 
72359
72367
  }
@@ -72521,7 +72529,6 @@
72521
72529
  if (features) {
72522
72530
 
72523
72531
  for (let feature of features) {
72524
-
72525
72532
  const regionKey = createRegionKey(chr, feature.start, feature.end);
72526
72533
 
72527
72534
  const {
@@ -72566,8 +72573,7 @@
72566
72573
  event.stopPropagation();
72567
72574
 
72568
72575
  translateMouseCoordinates(event, columnContainer);
72569
- const isUserDefined = roiSet.isUserDefined;
72570
- this.roiMenu.present(feature, isUserDefined, event, this, columnContainer, regionElement);
72576
+ this.roiMenu.present(feature, roiSet, event, this, columnContainer, regionElement);
72571
72577
  });
72572
72578
 
72573
72579
 
@@ -73736,7 +73742,7 @@
73736
73742
  }
73737
73743
  }
73738
73744
 
73739
- 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 box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n top: 50%;\n left: 50%;\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-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 width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 2px;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-color-swatch:hover {\n border-color: dimgray;\n}\n\n.igv-ui-colorpicker-menu-close-button {\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: 32px;\n margin-top: 4px;\n margin-bottom: 4px;\n padding-right: 8px;\n}\n.igv-ui-colorpicker-menu-close-button i.fa {\n display: block;\n margin-left: 4px;\n margin-right: 4px;\n color: #5f5f5f;\n}\n.igv-ui-colorpicker-menu-close-button i.fa:hover,\n.igv-ui-colorpicker-menu-close-button i.fa:focus,\n.igv-ui-colorpicker-menu-close-button i.fa:active {\n cursor: pointer;\n color: #0f0f0f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 0;\n left: 0;\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 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-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 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-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 box-sizing: content-box;\n position: absolute;\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-ui-generic-container > div:first-child {\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:first-child > 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-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: absolute;\n top: 0;\n left: 0;\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: absolute;\n top: 0;\n left: 0;\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:first-child {\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:first-child 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 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 margin-top: 66px;\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.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/*# sourceMappingURL=igv.css.map */\n';
73745
+ 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 box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n top: 50%;\n left: 50%;\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-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 width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 2px;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-color-swatch:hover {\n border-color: dimgray;\n}\n\n.igv-ui-colorpicker-menu-close-button {\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: 32px;\n margin-top: 4px;\n margin-bottom: 4px;\n padding-right: 8px;\n}\n.igv-ui-colorpicker-menu-close-button i.fa {\n display: block;\n margin-left: 4px;\n margin-right: 4px;\n color: #5f5f5f;\n}\n.igv-ui-colorpicker-menu-close-button i.fa:hover,\n.igv-ui-colorpicker-menu-close-button i.fa:focus,\n.igv-ui-colorpicker-menu-close-button i.fa:active {\n cursor: pointer;\n color: #0f0f0f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 0;\n left: 0;\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 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-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 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-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 box-sizing: content-box;\n position: absolute;\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-ui-generic-container > div:first-child {\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:first-child > 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-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: absolute;\n top: 0;\n left: 0;\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: absolute;\n top: 0;\n left: 0;\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:first-child {\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:first-child 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 margin-top: 66px;\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.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/*# sourceMappingURL=igv.css.map */\n';
73740
73746
 
73741
73747
  /**
73742
73748
  * Manages XQTL selections.
@@ -74619,6 +74625,10 @@
74619
74625
  */
74620
74626
  async loadGenome(idOrConfig) {
74621
74627
 
74628
+ if(idOrConfig.genarkAccession) {
74629
+ idOrConfig.url = convertToHubURL(idOrConfig.genarkAccession);
74630
+ }
74631
+
74622
74632
  // Translate the generic "url" field, used by clients such as igv-webapp
74623
74633
  if (idOrConfig.url) {
74624
74634
  if (isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {