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