igv 3.0.2 → 3.0.4

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
@@ -25216,9 +25216,7 @@ class GFFHelper {
25216
25216
  } else {
25217
25217
  combinedFeatures = this.combineFeaturesByType(features);
25218
25218
  }
25219
- combinedFeatures.sort(function (a, b) {
25220
- return a.start - b.start
25221
- });
25219
+
25222
25220
  this.numberExons(combinedFeatures, genomicInterval);
25223
25221
  this.nameFeatures(combinedFeatures);
25224
25222
  return combinedFeatures
@@ -29775,20 +29773,30 @@ class FeatureFileReader {
29775
29773
  await this.readHeader();
29776
29774
  }
29777
29775
 
29776
+ let allFeatures;
29778
29777
  const index = await this.getIndex();
29779
29778
  if (index) {
29780
29779
  this.indexed = true;
29781
- return this.loadFeaturesWithIndex(chr, start, end)
29780
+ allFeatures = await this.loadFeaturesWithIndex(chr, start, end);
29782
29781
  } else if (this.dataURI) {
29783
29782
  this.indexed = false;
29784
- return this.loadFeaturesFromDataURI()
29783
+ allFeatures = await this.loadFeaturesFromDataURI();
29785
29784
  } else if ("service" === this.config.sourceType) {
29786
- return this.loadFeaturesFromService(chr, start, end)
29785
+ allFeatures = await this.loadFeaturesFromService(chr, start, end);
29787
29786
  } else {
29788
29787
  this.indexed = false;
29789
- return this.loadFeaturesNoIndex()
29788
+ allFeatures = await this.loadFeaturesNoIndex();
29790
29789
  }
29791
29790
 
29791
+ allFeatures.sort(function (a, b) {
29792
+ if (a.chr === b.chr) {
29793
+ return a.start - b.start
29794
+ } else {
29795
+ return a.chr.localeCompare(b.chr)
29796
+ }
29797
+ });
29798
+
29799
+ return allFeatures
29792
29800
  }
29793
29801
 
29794
29802
  async readHeader() {
@@ -29960,9 +29968,6 @@ class FeatureFileReader {
29960
29968
  await this._parse(allFeatures, dataWrapper, chr, end, start);
29961
29969
 
29962
29970
  }
29963
- allFeatures.sort(function (a, b) {
29964
- return a.start - b.start
29965
- });
29966
29971
 
29967
29972
  return allFeatures
29968
29973
  }
@@ -29992,8 +29997,13 @@ class FeatureFileReader {
29992
29997
 
29993
29998
  let features = await this.parser.parseFeatures(dataWrapper);
29994
29999
 
29995
- // Filter psuedo-features (e.g. created mates for VCF SV records) TODO why?
29996
- //slicedFeatures = slicedFeatures.filter(f => f._f === undefined)
30000
+ features.sort(function (a, b) {
30001
+ if (a.chr === b.chr) {
30002
+ return a.start - b.start
30003
+ } else {
30004
+ return a.chr.localeCompare(b.chr)
30005
+ }
30006
+ });
29997
30007
 
29998
30008
  // Filter features not in requested range.
29999
30009
  if (undefined === chr) {
@@ -30002,26 +30012,21 @@ class FeatureFileReader {
30002
30012
  let inInterval = false;
30003
30013
  for (let i = 0; i < features.length; i++) {
30004
30014
  const f = features[i];
30005
- if (f.chr !== chr) {
30006
- if (allFeatures.length === 0) {
30007
- continue //adjacent chr to the left
30008
- } else {
30009
- break //adjacent chr to the right
30015
+ if (f.chr === chr) {
30016
+ if (f.start > end) {
30017
+ allFeatures.push(f); // First feature beyond interval
30018
+ break
30010
30019
  }
30011
- }
30012
- if (f.start > end) {
30013
- allFeatures.push(f); // First feature beyond interval
30014
- break
30015
- }
30016
- if (f.end >= start && f.start <= end) {
30017
- // All this to grab first feature before start of interval. Needed for some track renderers, like line plot
30018
- if (!inInterval) {
30019
- inInterval = true;
30020
- if (i > 0) {
30021
- allFeatures.push(features[i - 1]);
30020
+ if (f.end >= start && f.start <= end) {
30021
+ // All this to grab first feature before start of interval. Needed for some track renderers, like line plot
30022
+ if (!inInterval) {
30023
+ inInterval = true;
30024
+ if (i > 0) {
30025
+ allFeatures.push(features[i - 1]);
30026
+ }
30022
30027
  }
30028
+ allFeatures.push(f);
30023
30029
  }
30024
- allFeatures.push(f);
30025
30030
  }
30026
30031
  }
30027
30032
  }
@@ -30744,286 +30749,686 @@ class BaseFeatureSource {
30744
30749
 
30745
30750
  }
30746
30751
 
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)
30752
+ /*
30753
+ * The MIT License (MIT)
30751
30754
  *
30752
- * @param config
30753
- * @constructor
30755
+ * Copyright (c) 2016 University of California San Diego
30756
+ * Author: Jim Robinson
30757
+ *
30758
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
30759
+ * of this software and associated documentation files (the "Software"), to deal
30760
+ * in the Software without restriction, including without limitation the rights
30761
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30762
+ * copies of the Software, and to permit persons to whom the Software is
30763
+ * furnished to do so, subject to the following conditions:
30764
+ *
30765
+ * The above copyright notice and this permission notice shall be included in
30766
+ * all copies or substantial portions of the Software.
30767
+ *
30768
+ *
30769
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30770
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30771
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30772
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30773
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30774
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30775
+ * THE SOFTWARE.
30754
30776
  */
30755
- class TextFeatureSource extends BaseFeatureSource {
30756
30777
 
30757
- constructor(config, genome) {
30778
+ const GZIP_FLAG = 0x1;
30758
30779
 
30759
- super(genome);
30780
+ class TDFReader {
30760
30781
 
30761
- this.config = config || {};
30782
+ constructor(config, genome) {
30783
+ this.config = config;
30762
30784
  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"];
30785
+ this.path = config.url;
30786
+ this.groupCache = {};
30787
+ this.datasetCache = {};
30788
+ }
30766
30789
 
30767
- const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
30768
30790
 
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 ;
30791
+ async readHeader() {
30792
+
30793
+ if (this.magic !== undefined) {
30794
+ return this // Already read
30799
30795
  }
30800
30796
 
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;
30797
+ let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start: 0, size: 64000}}));
30798
+ let binaryParser = new BinaryParser$1(new DataView(data));
30799
+ this.magic = binaryParser.getInt();
30800
+ this.version = binaryParser.getInt();
30801
+ this.indexPos = binaryParser.getLong();
30802
+ this.indexSize = binaryParser.getInt();
30803
+ binaryParser.getInt();
30803
30804
 
30804
- }
30805
30805
 
30806
- async defaultVisibilityWindow() {
30807
- if (this.reader && typeof this.reader.defaultVisibilityWindow === 'function') {
30808
- return this.reader.defaultVisibilityWindow()
30806
+ if (this.version >= 2) {
30807
+ let nWindowFunctions = binaryParser.getInt();
30808
+ this.windowFunctions = [];
30809
+ while (nWindowFunctions-- > 0) {
30810
+ this.windowFunctions.push(binaryParser.getString());
30811
+ }
30809
30812
  }
30810
- }
30811
30813
 
30812
- async trackType() {
30813
- const header = await this.getHeader();
30814
- if (header) {
30815
- return header.type
30816
- } else {
30817
- return undefined // Convention for unknown or unspecified
30818
- }
30819
- }
30814
+ this.trackType = binaryParser.getString();
30815
+ this.trackLine = binaryParser.getString();
30820
30816
 
30821
- async getHeader() {
30822
- if (!this.header) {
30817
+ let nTracks = binaryParser.getInt();
30818
+ this.trackNames = [];
30819
+ while (nTracks-- > 0) {
30820
+ this.trackNames.push(binaryParser.getString());
30821
+ }
30822
+ this.genomeID = binaryParser.getString();
30823
+ this.flags = binaryParser.getInt();
30824
+ this.compressed = (this.flags & GZIP_FLAG) !== 0;
30823
30825
 
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 = {};
30833
- }
30834
- } else {
30835
- this.header = {};
30826
+ // Now read index
30827
+ data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30828
+ range: {
30829
+ start: this.indexPos,
30830
+ size: this.indexSize
30836
30831
  }
30832
+ }));
30833
+ binaryParser = new BinaryParser$1(new DataView(data));
30834
+ this.datasetIndex = {};
30835
+ let nEntries = binaryParser.getInt();
30836
+ while (nEntries-- > 0) {
30837
+ const name = binaryParser.getString();
30838
+ const pos = binaryParser.getLong();
30839
+ const size = binaryParser.getInt();
30840
+ this.datasetIndex[name] = {position: pos, size: size};
30837
30841
  }
30838
- return this.header
30842
+
30843
+ this.groupIndex = {};
30844
+ nEntries = binaryParser.getInt();
30845
+ while (nEntries-- > 0) {
30846
+ const name = binaryParser.getString();
30847
+ const pos = binaryParser.getLong();
30848
+ const size = binaryParser.getInt();
30849
+ this.groupIndex[name] = {position: pos, size: size};
30850
+ }
30851
+
30852
+ return this
30839
30853
  }
30840
30854
 
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}) {
30855
+ async readDataset(chr, windowFunction, zoom) {
30854
30856
 
30855
- const isWholeGenome = ("all" === chr.toLowerCase());
30857
+ const key = chr + "_" + windowFunction + "_" + zoom;
30856
30858
 
30857
- start = start || 0;
30858
- end = end || Number.MAX_SAFE_INTEGER;
30859
+ if (this.datasetCache[key]) {
30860
+ return this.datasetCache[key]
30859
30861
 
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);
30870
- }
30862
+ } else {
30863
+ await this.readHeader();
30864
+ const wf = (this.version < 2) ? "" : "/" + windowFunction;
30865
+ const zoomString = (chr.toLowerCase() === "all" || zoom === undefined) ? "0" : zoom.toString();
30871
30866
 
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 = [];
30867
+ let dsName;
30868
+ if (windowFunction === "raw") {
30869
+ dsName = "/" + chr + "/raw";
30870
+ } else {
30871
+ dsName = "/" + chr + "/z" + zoomString + wf;
30872
+ }
30873
+ const indexEntry = this.datasetIndex[dsName];
30874
+
30875
+ if (indexEntry === undefined) {
30876
+ return undefined
30877
+ }
30878
+
30879
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30880
+ range: {
30881
+ start: indexEntry.position,
30882
+ size: indexEntry.size
30878
30883
  }
30884
+ }));
30885
+
30886
+ if (!data) {
30887
+ return undefined
30879
30888
  }
30880
- return this.wgFeatures
30881
- } else {
30882
- return this.featureCache.queryFeatures(chr, start, end)
30883
- }
30884
- }
30885
30889
 
30886
- async findFeatures(fn) {
30887
- return this.featureCache ? this.featureCache.findFeatures(fn) : []
30888
- }
30890
+ const binaryParser = new BinaryParser$1(new DataView(data));
30891
+ let nAttributes = binaryParser.getInt();
30892
+ const attributes = {};
30893
+ while (nAttributes-- > 0) {
30894
+ attributes[binaryParser.getString()] = binaryParser.getString();
30895
+ }
30896
+ const dataType = binaryParser.getString();
30897
+ const tileWidth = binaryParser.getFloat();
30898
+ let nTiles = binaryParser.getInt();
30899
+ const tiles = [];
30900
+ while (nTiles-- > 0) {
30901
+ tiles.push({position: binaryParser.getLong(), size: binaryParser.getInt()});
30902
+ }
30889
30903
 
30890
- supportsWholeGenome() {
30891
- return !this.queryable // queryable (indexed, web services) sources don't support whole genome view
30892
- }
30904
+ const dataset = {
30905
+ name: dsName,
30906
+ attributes: attributes,
30907
+ dataType: dataType,
30908
+ tileWidth: tileWidth,
30909
+ tiles: tiles
30910
+ };
30893
30911
 
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()
30912
+ this.datasetCache[key] = dataset;
30913
+ return dataset
30900
30914
  }
30901
30915
  }
30902
30916
 
30917
+ async readRootGroup() {
30903
30918
 
30904
- async loadFeatures(chr, start, end, visibilityWindow) {
30919
+ const genome = this.genome;
30920
+ const rootGroup = this.groupCache["/"];
30921
+ if (rootGroup) {
30922
+ return rootGroup
30923
+ } else {
30905
30924
 
30906
- await this.getHeader();
30925
+ const group = await this.readGroup("/");
30926
+ const names = group["chromosomes"];
30927
+ const maxZoomString = group["maxZoom"];
30907
30928
 
30908
- const reader = this.reader;
30909
- let intervalStart = start;
30910
- let intervalEnd = end;
30929
+ // Now parse out interesting attributes.
30930
+ if (maxZoomString) {
30931
+ this.maxZoom = Number(maxZoomString);
30932
+ }
30911
30933
 
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);
30919
- }
30934
+ const totalCountString = group["totalCount"];
30935
+ if (totalCountString) {
30936
+ group.totalCount = Number(totalCountString);
30937
+ }
30920
30938
 
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;
30939
+ // Chromosome names
30940
+ const chrAliasTable = {};
30941
+ if (names) {
30942
+ names.split(",").forEach(function (chr) {
30943
+ const canonicalName = genome.getChromosomeName(chr);
30944
+ chrAliasTable[canonicalName] = chr;
30945
+ });
30933
30946
  }
30934
- intervalStart = Math.max(0, (start + end - expansionWindow) / 2);
30935
- intervalEnd = intervalStart + expansionWindow;
30936
- }
30947
+ this.chrAliasTable = chrAliasTable;
30937
30948
 
30938
- let features = await reader.readFeatures(queryChr, intervalStart, intervalEnd);
30939
- if (this.queryable === undefined) {
30940
- this.queryable = reader.indexed;
30949
+ this.groupCache["/"] = group;
30950
+ return group
30941
30951
  }
30952
+ }
30942
30953
 
30943
- const genomicInterval = this.queryable ?
30944
- new GenomicInterval(chr, intervalStart, intervalEnd) :
30945
- undefined;
30954
+ async readGroup(name) {
30946
30955
 
30947
- if (features) {
30956
+ const group = this.groupCache[name];
30957
+ if (group) {
30958
+ return group
30959
+ } else {
30948
30960
 
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);
30961
+ await this.readHeader();
30962
+ const indexEntry = this.groupIndex[name];
30963
+ if (indexEntry === undefined) {
30964
+ return undefined
30953
30965
  }
30954
30966
 
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);
30967
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30968
+ range: {
30969
+ start: indexEntry.position,
30970
+ size: indexEntry.size
30971
+ }
30972
+ }));
30957
30973
 
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);
30974
+ if (!data) {
30975
+ return undefined
30961
30976
  }
30962
- } else {
30963
- this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
30977
+
30978
+ const binaryParser = new BinaryParser$1(new DataView(data));
30979
+ const group = {name: name};
30980
+ let nAttributes = binaryParser.getInt();
30981
+ while (nAttributes-- > 0) {
30982
+ const key = binaryParser.getString();
30983
+ const value = binaryParser.getString();
30984
+ group[key] = value;
30985
+ }
30986
+ this.groupCache[name] = group;
30987
+ return group
30964
30988
  }
30965
30989
  }
30966
30990
 
30967
- addFeaturesToDB(featureList, config) {
30968
- if (!this.featureMap) {
30969
- this.featureMap = new Map();
30991
+ async readTiles(tileIndeces, nTracks) {
30992
+
30993
+ tileIndeces.sort(function (a, b) {
30994
+ return a.position - b.position
30995
+ });
30996
+
30997
+ tileIndeces = tileIndeces.filter(function (idx) {
30998
+ return idx.size > 0
30999
+ });
31000
+
31001
+ if (tileIndeces.length === 0) {
31002
+ return []
30970
31003
  }
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);
30977
- }
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
30985
- }
30986
- }
30987
- this.featureMap.set(key, feature);
31004
+
31005
+ const tiles = [];
31006
+
31007
+ for (let indexEntry of tileIndeces) {
31008
+
31009
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
31010
+ range: {
31011
+ start: indexEntry.position,
31012
+ size: indexEntry.size
30988
31013
  }
31014
+ }));
31015
+
31016
+ let tileData;
31017
+ try {
31018
+ tileData = this.compressed ? inflate_1$3(data).buffer : data;
31019
+ } catch (e) {
31020
+ console.error(e);
31021
+ continue
31022
+ }
31023
+
31024
+ const binaryParser = new BinaryParser$1(new DataView(tileData));
31025
+ const type = binaryParser.getString();
31026
+ let tile;
31027
+ switch (type) {
31028
+ case "fixedStep":
31029
+ tile = createFixedStep(binaryParser, nTracks);
31030
+ break
31031
+ case "variableStep":
31032
+ tile = createVariableStep(binaryParser, nTracks);
31033
+ break
31034
+ case "bed":
31035
+ case "bedWithName":
31036
+ tile = createBed(binaryParser, nTracks, type);
31037
+ break
31038
+ default:
31039
+ throw "Unknown tile type: " + type
30989
31040
  }
31041
+ tiles.push(tile);
30990
31042
  }
31043
+ return tiles
30991
31044
  }
30992
31045
 
30993
- search(term) {
30994
- if (this.featureMap) {
30995
- return this.featureMap.get(term.toUpperCase())
31046
+ async readTile(indexEntry, nTracks) {
31047
+
31048
+ let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
31049
+ range: {
31050
+ start: indexEntry.position,
31051
+ size: indexEntry.size
31052
+ }
31053
+ }));
31054
+
31055
+ if (this.compressed) {
31056
+ const plain = inflate_1$3(data);
31057
+ data = plain.buffer;
30996
31058
  }
30997
31059
 
31060
+ const binaryParser = new BinaryParser$1(new DataView(data));
31061
+ const type = binaryParser.getString();
31062
+ switch (type) {
31063
+ case "fixedStep":
31064
+ return createFixedStep(binaryParser, nTracks)
31065
+ case "variableStep":
31066
+ return createVariableStep(binaryParser, nTracks)
31067
+ case "bed":
31068
+ case "bedWithName":
31069
+ return createBed(binaryParser, nTracks, type)
31070
+ default:
31071
+ throw "Unknown tile type: " + type
31072
+ }
30998
31073
  }
31074
+
30999
31075
  }
31000
31076
 
31001
- /**
31002
- * A ChromTree parses a UCSC bigbed/bigwig "chromosomeTree" section of the header to produce 2 maps,
31003
- * (1) ID -> chromosome names, and its
31004
- * (2) chromsome name -> ID
31005
- *
31006
- * Both maps are needed by IGV
31007
- *
31008
- * The chromosome tree is a B+ index, but is located continguously in memory in the header section of the file. This
31009
- * makes it feasible to parse the whole tree with data from a single fetch. In the end the tree is discarded
31010
- * leaving only the mapps.
31011
- */
31012
- class ChromTree {
31077
+ function createFixedStep(binaryParser, nTracks) {
31078
+ const nPositions = binaryParser.getInt();
31079
+ const start = binaryParser.getInt();
31080
+ const span = binaryParser.getFloat();
31013
31081
 
31014
- constructor(header, nameToID, valueToKey, sumLengths) {
31015
- this.header = header;
31016
- this.nameToId = nameToID;
31017
- this.idToName = valueToKey;
31018
- this.sumLengths = sumLengths;
31082
+ const data = [];
31083
+ let nt = nTracks;
31084
+ while (nt-- > 0) {
31085
+ let np = nPositions;
31086
+ const dtrack = [];
31087
+ while (np-- > 0) {
31088
+ dtrack.push(binaryParser.getFloat());
31089
+ }
31090
+ data.push(dtrack);
31019
31091
  }
31020
31092
 
31021
- static parseTree(binaryParser, startOffset, genome = false) {
31022
- {
31023
- const magic = binaryParser.getInt();
31024
- const blockSize = binaryParser.getInt();
31025
- const keySize = binaryParser.getInt();
31026
- const valSize = binaryParser.getInt();
31093
+ return {
31094
+ type: "fixedStep",
31095
+ start: start,
31096
+ span: span,
31097
+ data: data,
31098
+ nTracks: nTracks,
31099
+ nPositions: nPositions
31100
+ }
31101
+ }
31102
+
31103
+ function createVariableStep(binaryParser, nTracks) {
31104
+
31105
+ const tileStart = binaryParser.getInt();
31106
+ const span = binaryParser.getFloat();
31107
+ const nPositions = binaryParser.getInt();
31108
+ const start = [];
31109
+
31110
+ let np = nPositions;
31111
+ while (np-- > 0) {
31112
+ start.push(binaryParser.getInt());
31113
+ }
31114
+ binaryParser.getInt(); // # of samples, ignored but should === nTracks
31115
+
31116
+ const data = [];
31117
+ let nt = nTracks;
31118
+ while (nt-- > 0) {
31119
+ np = nPositions;
31120
+ const dtrack = [];
31121
+ while (np-- > 0) {
31122
+ dtrack.push(binaryParser.getFloat());
31123
+ }
31124
+ data.push(dtrack);
31125
+ }
31126
+
31127
+ return {
31128
+ type: "variableStep",
31129
+ tileStart: tileStart,
31130
+ span: span,
31131
+ start: start,
31132
+ data: data,
31133
+ nTracks: nTracks,
31134
+ nPositions: nPositions
31135
+ }
31136
+ }
31137
+
31138
+ function createBed(binaryParser, nTracks, type) {
31139
+
31140
+ const nPositions = binaryParser.getInt();
31141
+
31142
+ let n = nPositions;
31143
+ const start = [];
31144
+ while (n-- > 0) {
31145
+ start.push(binaryParser.getInt());
31146
+ }
31147
+
31148
+ n = nPositions;
31149
+ const end = [];
31150
+ while (n-- > 0) {
31151
+ end.push(binaryParser.getInt());
31152
+ }
31153
+
31154
+ binaryParser.getInt(); // # of samples, ignored but should === nTracks
31155
+ const data = [];
31156
+ let nt = nTracks;
31157
+ while (nt-- > 0) {
31158
+ let np = nPositions;
31159
+ const dtrack = [];
31160
+ while (np-- > 0) {
31161
+ dtrack.push(binaryParser.getFloat());
31162
+ }
31163
+ data.push(dtrack);
31164
+ }
31165
+
31166
+ if (type === "bedWithName") {
31167
+ n = nPositions;
31168
+ const name = [];
31169
+ while (n-- > 0) {
31170
+ name.push(binaryParser.getString());
31171
+ }
31172
+ }
31173
+
31174
+ return {
31175
+ type: type,
31176
+ start: start,
31177
+ end: end,
31178
+ data: data,
31179
+ nTracks: nTracks,
31180
+ nPositions: nPositions
31181
+ }
31182
+ }
31183
+
31184
+ /*
31185
+ * The MIT License (MIT)
31186
+ *
31187
+ * Copyright (c) 2016 University of California San Diego
31188
+ * Author: Jim Robinson
31189
+ *
31190
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
31191
+ * of this software and associated documentation files (the "Software"), to deal
31192
+ * in the Software without restriction, including without limitation the rights
31193
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31194
+ * copies of the Software, and to permit persons to whom the Software is
31195
+ * furnished to do so, subject to the following conditions:
31196
+ *
31197
+ * The above copyright notice and this permission notice shall be included in
31198
+ * all copies or substantial portions of the Software.
31199
+ *
31200
+ *
31201
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31202
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31203
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31204
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31205
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31206
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31207
+ * THE SOFTWARE.
31208
+ */
31209
+
31210
+ class TDFSource extends BaseFeatureSource {
31211
+
31212
+ #wgValues = {}
31213
+ searchable = false
31214
+
31215
+
31216
+ constructor(config, genome) {
31217
+ super(genome);
31218
+ this.genome = genome;
31219
+ this.reader = new TDFReader(config, genome);
31220
+ this.queryable = true;
31221
+ }
31222
+
31223
+ async getFeatures({chr, start, end, bpPerPixel, windowFunction = "mean"}) {
31224
+
31225
+ if (chr.toLowerCase() === "all") {
31226
+ return this.getWGValues(windowFunction, bpPerPixel)
31227
+ } else {
31228
+ return this._getFeatures(chr, start, end, bpPerPixel, windowFunction)
31229
+ }
31230
+ }
31231
+ async _getFeatures(chr, start, end, bpPerPixel, windowFunction) {
31232
+ const genomicInterval = new GenomicInterval(chr, start, end);
31233
+ const genome = this.genome;
31234
+
31235
+
31236
+ if (!this.rootGroup) {
31237
+ this.rootGroup = await this.reader.readRootGroup();
31238
+ if (!this.normalizationFactor) {
31239
+ const totalCount = this.rootGroup.totalCount;
31240
+ if (totalCount) {
31241
+ this.normalizationFactor = 1.0e6 / totalCount;
31242
+ }
31243
+ }
31244
+ }
31245
+
31246
+ genomicInterval.bpPerPixel = bpPerPixel;
31247
+ const zoom = zoomLevelForScale$1(chr, bpPerPixel, genome);
31248
+ let queryChr = this.reader.chrAliasTable[chr];
31249
+ let maxZoom = this.reader.maxZoom;
31250
+ if (queryChr === undefined) queryChr = chr;
31251
+ if (maxZoom === undefined) maxZoom = -1;
31252
+
31253
+ const wf = zoom > maxZoom ? "raw" : windowFunction;
31254
+ const dataset = await this.reader.readDataset(queryChr, wf, zoom);
31255
+ if (dataset == null) {
31256
+ return []
31257
+ }
31258
+
31259
+ const tileWidth = dataset.tileWidth;
31260
+ const startTile = Math.floor(start / tileWidth);
31261
+ const endTile = Math.floor(end / tileWidth);
31262
+ const NTRACKS = 1; // TODO read this
31263
+ const tiles = await this.reader.readTiles(dataset.tiles.slice(startTile, endTile + 1), NTRACKS);
31264
+ const features = [];
31265
+ for (let tile of tiles) {
31266
+ switch (tile.type) {
31267
+ case "bed":
31268
+ decodeBedTile(tile, chr, start, end, bpPerPixel, features);
31269
+ break
31270
+ case "variableStep":
31271
+ decodeVaryTile(tile, chr, start, end, bpPerPixel, features);
31272
+ break
31273
+ case "fixedStep":
31274
+ decodeFixedTile(tile, chr, start, end, bpPerPixel, features);
31275
+ break
31276
+ default:
31277
+ throw ("Unknown tile type: " + tile.type)
31278
+ }
31279
+ }
31280
+ features.sort(function (a, b) {
31281
+ return a.start - b.start
31282
+ });
31283
+
31284
+ return features
31285
+ }
31286
+
31287
+ get supportsWholeGenome() {
31288
+ return true
31289
+ }
31290
+
31291
+ get windowFunctions() {
31292
+ return this.reader.windowFunctions
31293
+ }
31294
+
31295
+ async getWGValues(windowFunction, bpPerPixel) {
31296
+
31297
+ const cached = this.#wgValues[windowFunction];
31298
+ if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
31299
+ return cached.values
31300
+ } else {
31301
+ const wgFeatures = [];
31302
+ const genome = this.genome;
31303
+ const chrNames = this.genome.wgChromosomeNames;
31304
+ if (chrNames) {
31305
+ for (let c of genome.wgChromosomeNames) {
31306
+ const len = genome.getChromosome(c).bpLength;
31307
+ bpPerPixel = len / 1000;
31308
+ const chrFeatures = await this._getFeatures(c, 0, len, bpPerPixel, windowFunction);
31309
+ if (chrFeatures) {
31310
+ for (let f of chrFeatures) {
31311
+ const wg = Object.assign({}, f);
31312
+ wg.chr = "all";
31313
+ wg.start = genome.getGenomeCoordinate(f.chr, f.start);
31314
+ wg.end = genome.getGenomeCoordinate(f.chr, f.end);
31315
+ wg._f = f;
31316
+ wgFeatures.push(wg);
31317
+ }
31318
+ }
31319
+ }
31320
+ }
31321
+ this.#wgValues[windowFunction] = {values: wgFeatures, bpPerPixel};
31322
+ return wgFeatures
31323
+ }
31324
+ }
31325
+
31326
+ }
31327
+
31328
+ function decodeBedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31329
+
31330
+ const nPositions = tile.nPositions;
31331
+ const starts = tile.start;
31332
+ const ends = tile.end;
31333
+ const data = tile.data[0]; // Single track for now
31334
+ for (let i = 0; i < nPositions; i++) {
31335
+ const s = starts[i];
31336
+ const e = ends[i];
31337
+ if (e < bpStart) continue
31338
+ if (s > bpEnd) break
31339
+ features.push({
31340
+ chr: chr,
31341
+ start: s,
31342
+ end: e,
31343
+ value: data[i]
31344
+ });
31345
+ }
31346
+ }
31347
+
31348
+ function decodeVaryTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31349
+
31350
+ const nPositions = tile.nPositions;
31351
+ const starts = tile.start;
31352
+ const span = tile.span;
31353
+ const data = tile.data[0]; // Single track for now
31354
+ for (let i = 0; i < nPositions; i++) {
31355
+ const s = starts[i];
31356
+ const e = s + span;
31357
+ if (e < bpStart) continue
31358
+ if (s > bpEnd) break
31359
+ features.push({
31360
+ chr: chr,
31361
+ start: s,
31362
+ end: e,
31363
+ value: data[i]
31364
+ });
31365
+ }
31366
+ }
31367
+
31368
+ function decodeFixedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31369
+
31370
+ const nPositions = tile.nPositions;
31371
+ let s = tile.start;
31372
+ const span = tile.span;
31373
+ const data = tile.data[0]; // Single track for now
31374
+
31375
+ for (let i = 0; i < nPositions; i++) {
31376
+ const e = s + span;
31377
+ if (s > bpEnd) break
31378
+ if (e >= bpStart) {
31379
+ if (!Number.isNaN(data[i])) {
31380
+ features.push({
31381
+ chr: chr,
31382
+ start: s,
31383
+ end: e,
31384
+ value: data[i]
31385
+ });
31386
+ }
31387
+ }
31388
+ s = e;
31389
+ }
31390
+ }
31391
+
31392
+
31393
+ var log2 = Math.log(2);
31394
+
31395
+ function zoomLevelForScale$1(chr, bpPerPixel, genome) {
31396
+
31397
+ // Convert bpPerPixel to IGV "zoom" level. This is a bit convoluted, TDF is computed zoom levels assuming
31398
+ // display in a 700 pixel window. The fully zoomed out view of a chromosome is zoom level "0".
31399
+ // Zoom level 1 is magnified 2X, and so forth
31400
+
31401
+ var chrSize = genome.getChromosome(chr).bpLength;
31402
+
31403
+ return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
31404
+ }
31405
+
31406
+ /**
31407
+ * A ChromTree parses a UCSC bigbed/bigwig "chromosomeTree" section of the header to produce 2 maps,
31408
+ * (1) ID -> chromosome names, and its
31409
+ * (2) chromsome name -> ID
31410
+ *
31411
+ * Both maps are needed by IGV
31412
+ *
31413
+ * The chromosome tree is a B+ index, but is located continguously in memory in the header section of the file. This
31414
+ * makes it feasible to parse the whole tree with data from a single fetch. In the end the tree is discarded
31415
+ * leaving only the mapps.
31416
+ */
31417
+ class ChromTree {
31418
+
31419
+ constructor(header, nameToID, valueToKey, sumLengths) {
31420
+ this.header = header;
31421
+ this.nameToId = nameToID;
31422
+ this.idToName = valueToKey;
31423
+ this.sumLengths = sumLengths;
31424
+ }
31425
+
31426
+ static parseTree(binaryParser, startOffset, genome = false) {
31427
+ {
31428
+ const magic = binaryParser.getInt();
31429
+ const blockSize = binaryParser.getInt();
31430
+ const keySize = binaryParser.getInt();
31431
+ const valSize = binaryParser.getInt();
31027
31432
  const itemCount = binaryParser.getLong();
31028
31433
  const reserved = binaryParser.getLong();
31029
31434
 
@@ -31569,7 +31974,7 @@ class BWReader {
31569
31974
  if (this.type === "bigwig") {
31570
31975
  // Select a biwig "zoom level" appropriate for the current resolution.
31571
31976
  const zoomLevelHeaders = await this.getZoomHeaders();
31572
- let zoomLevelHeader = bpPerPixel ? zoomLevelForScale$1(bpPerPixel, zoomLevelHeaders) : undefined;
31977
+ let zoomLevelHeader = bpPerPixel ? zoomLevelForScale(bpPerPixel, zoomLevelHeaders) : undefined;
31573
31978
  if (zoomLevelHeader) {
31574
31979
  treeOffset = zoomLevelHeader.indexOffset;
31575
31980
  decodeFunction = decodeZoomData;
@@ -32025,7 +32430,7 @@ function computeStats() {
32025
32430
  }
32026
32431
  }
32027
32432
 
32028
- function zoomLevelForScale$1(bpPerPixel, zoomLevelHeaders) {
32433
+ function zoomLevelForScale(bpPerPixel, zoomLevelHeaders) {
32029
32434
  let level;
32030
32435
  for (let i = 0; i < zoomLevelHeaders.length; i++) {
32031
32436
  const zl = zoomLevelHeaders[i];
@@ -32213,7 +32618,7 @@ class DataBuffer {
32213
32618
  class BWSource extends BaseFeatureSource {
32214
32619
 
32215
32620
  queryable = true
32216
- wgValues = {}
32621
+ #wgValues = {}
32217
32622
  windowFunctions = ["mean", "min", "max"]
32218
32623
 
32219
32624
  constructor(config, genome) {
@@ -32225,12 +32630,15 @@ class BWSource extends BaseFeatureSource {
32225
32630
 
32226
32631
  async getFeatures({chr, start, end, bpPerPixel, windowFunction}) {
32227
32632
 
32228
- await this.reader.loadHeader();
32633
+ await this.reader.loadHeader();
32229
32634
  const isBigWig = this.reader.type === "bigwig";
32230
32635
 
32231
- const features = (chr.toLowerCase() === "all") ?
32232
- (isBigWig ? await this.getWGValues(windowFunction) : []) :
32233
- await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
32636
+ let features;
32637
+ if ("all" === chr.toLowerCase()) {
32638
+ features = isBigWig ? await this.getWGValues(windowFunction, bpPerPixel) : [];
32639
+ } else {
32640
+ features = await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
32641
+ }
32234
32642
 
32235
32643
  if (!isBigWig) {
32236
32644
  pack(features);
@@ -32246,26 +32654,25 @@ class BWSource extends BaseFeatureSource {
32246
32654
  if (this.reader.type === "bigwig") {
32247
32655
  return -1
32248
32656
  } else {
32249
- return this.reader.featureDensity ? Math.floor(10000 / this.reader.featureDensity) : -1
32657
+ return this.reader.featureDensity ? Math.floor(10000 / this.reader.featureDensity) : -1
32250
32658
  }
32251
32659
 
32252
32660
  }
32253
32661
 
32254
- async getWGValues(windowFunction) {
32662
+ async getWGValues(windowFunction, bpPerPixel) {
32255
32663
 
32256
- const numberOfBins = 1000; // This doesn't need to be precise
32257
32664
  const genome = this.genome;
32258
-
32259
- if (this.wgValues[windowFunction]) {
32260
- return this.wgValues[windowFunction]
32665
+ const cached = this.#wgValues[windowFunction];
32666
+ if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
32667
+ return cached.values
32261
32668
  } else {
32262
32669
 
32263
- const bpPerPixel = genome.getGenomeLength() / numberOfBins;
32264
32670
  const features = await this.reader.readWGFeatures(bpPerPixel, windowFunction);
32265
32671
  let wgValues = [];
32266
32672
  for (let f of features) {
32267
32673
  const chr = f.chr;
32268
32674
  const offset = genome.getCumulativeOffset(chr);
32675
+ if (undefined === offset) continue
32269
32676
  const wgFeature = Object.assign({}, f);
32270
32677
  wgFeature.chr = "all";
32271
32678
  wgFeature.start = offset + f.start;
@@ -32274,7 +32681,7 @@ class BWSource extends BaseFeatureSource {
32274
32681
  wgValues.push(wgFeature);
32275
32682
  }
32276
32683
  wgValues.sort((a, b) => a.start - b.start);
32277
- this.wgValues[windowFunction] = wgValues;
32684
+ this.#wgValues[windowFunction] = {values: wgValues, bpPerPixel};
32278
32685
  return wgValues
32279
32686
  }
32280
32687
  }
@@ -32296,658 +32703,780 @@ class BWSource extends BaseFeatureSource {
32296
32703
  }
32297
32704
  }
32298
32705
 
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
- */
32324
-
32325
- const GZIP_FLAG = 0x1;
32706
+ const shim = .01;
32707
+ const colorStripWidth = 4;
32708
+ const axesXOffset = colorStripWidth + 1;
32709
+ function paintAxis(ctx, width, height, colorOrUndefined) {
32326
32710
 
32327
- class TDFReader {
32711
+ if (undefined === this.dataRange || undefined === this.dataRange.max || undefined === this.dataRange.min) {
32712
+ return
32713
+ }
32328
32714
 
32329
- constructor(config, genome) {
32330
- this.config = config;
32331
- this.genome = genome;
32332
- this.path = config.url;
32333
- this.groupCache = {};
32334
- this.datasetCache = {};
32715
+ IGVGraphics.fillRect(ctx, 0, 0, width, height, { fillStyle: 'white' });
32716
+ if (colorOrUndefined) {
32717
+ IGVGraphics.fillRect(ctx, width - colorStripWidth - 2, 0, colorStripWidth, height, { fillStyle: colorOrUndefined });
32335
32718
  }
32336
32719
 
32720
+ const flipAxis = (undefined === this.flipAxis) ? false : this.flipAxis;
32337
32721
 
32338
- async readHeader() {
32722
+ const xTickStart = 0.95 * width - 8 - axesXOffset;
32723
+ const xTickEnd = 0.95 * width - axesXOffset;
32339
32724
 
32340
- if (this.magic !== undefined) {
32341
- return this // Already read
32342
- }
32725
+ const properties =
32726
+ {
32727
+ font: 'normal 10px Arial',
32728
+ textAlign: 'right',
32729
+ fillStyle: 'black',
32730
+ strokeStyle: 'black',
32731
+ };
32343
32732
 
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();
32733
+ // tick
32734
+ IGVGraphics.strokeLine(ctx, xTickStart, shim * height, xTickEnd, shim * height, properties);
32735
+ IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.min : this.dataRange.max), xTickStart + 4, shim * height + 12, properties);
32351
32736
 
32737
+ const y = (1.0 - shim) * height;
32352
32738
 
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
- }
32739
+ // tick
32740
+ IGVGraphics.strokeLine(ctx, xTickStart, y, xTickEnd, y, properties);
32741
+ IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.max : this.dataRange.min), xTickStart + 4, y - 4, properties);
32360
32742
 
32361
- this.trackType = binaryParser.getString();
32362
- this.trackLine = binaryParser.getString();
32743
+ // vertical axis
32744
+ IGVGraphics.strokeLine(ctx, xTickEnd, shim * height, xTickEnd, y, properties);
32363
32745
 
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;
32746
+ function prettyPrint(number) {
32372
32747
 
32373
- // Now read index
32374
- data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32375
- range: {
32376
- start: this.indexPos,
32377
- size: this.indexSize
32378
- }
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};
32748
+ if (number === 0) {
32749
+ return "0"
32750
+ } else if (Math.abs(number) >= 10) {
32751
+ return number.toFixed()
32752
+ } else if (Math.abs(number) >= 1) {
32753
+ return number.toFixed(1)
32754
+ } else if (Math.abs(number) >= 0.1) {
32755
+ return number.toFixed(2)
32756
+ } else {
32757
+ return number.toExponential(1)
32388
32758
  }
32759
+ }
32760
+ }
32389
32761
 
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};
32397
- }
32762
+ const DEFAULT_COLOR$2 = 'rgb(150, 150, 150)';
32398
32763
 
32399
- return this
32764
+
32765
+ class WigTrack extends TrackBase {
32766
+
32767
+ static defaults = {
32768
+ height: 50,
32769
+ flipAxis: false,
32770
+ logScale: false,
32771
+ windowFunction: 'mean',
32772
+ graphType: 'bar',
32773
+ normalize: undefined,
32774
+ scaleFactor: undefined,
32775
+ overflowColor: `rgb(255, 32, 255)`,
32776
+ baselineColor: 'lightGray',
32777
+ summarize: true
32400
32778
  }
32401
32779
 
32402
- async readDataset(chr, windowFunction, zoom) {
32780
+ constructor(config, browser) {
32781
+ super(config, browser);
32782
+ }
32403
32783
 
32404
- const key = chr + "_" + windowFunction + "_" + zoom;
32784
+ init(config) {
32785
+
32786
+ super.init(config);
32787
+
32788
+ this.type = "wig";
32789
+ this.featureType = 'numeric';
32790
+ this.resolutionAware = true;
32791
+ this.paintAxis = paintAxis;
32792
+
32793
+ const format = config.format ? config.format.toLowerCase() : config.format;
32794
+ if (config.featureSource) {
32795
+ this.featureSource = config.featureSource;
32796
+ delete config.featureSource;
32797
+ } else if ("bigwig" === format) {
32798
+ this.featureSource = new BWSource(config, this.browser.genome);
32799
+ } else if ("tdf" === format) {
32800
+ this.featureSource = new TDFSource(config, this.browser.genome);
32801
+ } else {
32802
+ this.featureSource = FeatureSource(config, this.browser.genome);
32803
+ }
32405
32804
 
32406
- if (this.datasetCache[key]) {
32407
- return this.datasetCache[key]
32408
32805
 
32806
+ // Override autoscale default
32807
+ if (config.max === undefined || config.autoscale === true) {
32808
+ this.autoscale = true;
32409
32809
  } else {
32410
- await this.readHeader();
32411
- const wf = (this.version < 2) ? "" : "/" + windowFunction;
32412
- const zoomString = (chr.toLowerCase() === "all" || zoom === undefined) ? "0" : zoom.toString();
32810
+ this.dataRange = {
32811
+ min: config.min || 0,
32812
+ max: config.max
32813
+ };
32814
+ }
32815
+ }
32413
32816
 
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];
32817
+ async postInit() {
32818
+ const header = await this.getHeader();
32819
+ if (this.disposed) return // This track was removed during async load
32820
+ if (header) this.setTrackProperties(header);
32821
+ }
32421
32822
 
32422
- if (indexEntry === undefined) {
32423
- return undefined
32424
- }
32823
+ async getFeatures(chr, start, end, bpPerPixel) {
32425
32824
 
32426
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32427
- range: {
32428
- start: indexEntry.position,
32429
- size: indexEntry.size
32430
- }
32431
- }));
32825
+ const windowFunction = this.windowFunction;
32432
32826
 
32433
- if (!data) {
32434
- return undefined
32827
+ const features = await this.featureSource.getFeatures({
32828
+ chr,
32829
+ start,
32830
+ end,
32831
+ bpPerPixel,
32832
+ visibilityWindow: this.visibilityWindow,
32833
+ windowFunction
32834
+ });
32835
+ if (this.normalize && this.featureSource.normalizationFactor) {
32836
+ const scaleFactor = this.featureSource.normalizationFactor;
32837
+ for (let f of features) {
32838
+ f.value *= scaleFactor;
32435
32839
  }
32436
-
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();
32840
+ }
32841
+ if (this.scaleFactor) {
32842
+ const scaleFactor = this.scaleFactor;
32843
+ for (let f of features) {
32844
+ f.value *= scaleFactor;
32442
32845
  }
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()});
32846
+ }
32847
+
32848
+ // Summarize features to current resolution. This needs to be done here, rather than in the "draw" function,
32849
+ // for group autoscale to work.
32850
+ if (this.summarize && ("mean" === windowFunction || "min" === windowFunction || "max" === windowFunction)) {
32851
+ return summarizeData(features, start, bpPerPixel, windowFunction)
32852
+ } else {
32853
+ return features
32854
+ }
32855
+ }
32856
+
32857
+ menuItemList() {
32858
+ const items = [];
32859
+
32860
+ if (this.flipAxis !== undefined) {
32861
+ items.push('<hr>');
32862
+
32863
+ function click() {
32864
+ this.flipAxis = !this.flipAxis;
32865
+ this.trackView.repaintViews();
32449
32866
  }
32450
32867
 
32451
- const dataset = {
32452
- name: dsName,
32453
- attributes: attributes,
32454
- dataType: dataType,
32455
- tileWidth: tileWidth,
32456
- tiles: tiles
32457
- };
32868
+ items.push({label: 'Flip y-axis', click});
32869
+ }
32458
32870
 
32459
- this.datasetCache[key] = dataset;
32460
- return dataset
32871
+ if(this.featureSource.windowFunctions) {
32872
+ items.push(...this.wigSummarizationItems());
32461
32873
  }
32874
+
32875
+ items.push(...this.numericDataMenuItems());
32876
+
32877
+ return items
32462
32878
  }
32463
32879
 
32464
- async readRootGroup() {
32880
+ wigSummarizationItems() {
32465
32881
 
32466
- const genome = this.genome;
32467
- const rootGroup = this.groupCache["/"];
32468
- if (rootGroup) {
32469
- return rootGroup
32470
- } else {
32882
+ const windowFunctions = this.featureSource.windowFunctions;
32471
32883
 
32472
- const group = await this.readGroup("/");
32473
- const names = group["chromosomes"];
32474
- const maxZoomString = group["maxZoom"];
32884
+ const menuItems = [];
32885
+ menuItems.push('<hr/>');
32886
+ menuItems.push("<div>Windowing function</div>");
32887
+ for (const wf of windowFunctions) {
32888
+ const object = $$1(createCheckbox(wf, this.windowFunction === wf));
32475
32889
 
32476
- // Now parse out interesting attributes.
32477
- if (maxZoomString) {
32478
- this.maxZoom = Number(maxZoomString);
32890
+ function clickHandler() {
32891
+ this.windowFunction = wf;
32892
+ this.trackView.updateViews();
32479
32893
  }
32480
32894
 
32481
- const totalCountString = group["totalCount"];
32482
- if (totalCountString) {
32483
- group.totalCount = Number(totalCountString);
32484
- }
32895
+ menuItems.push({object, click: clickHandler});
32896
+ }
32485
32897
 
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;
32898
+ return menuItems
32899
+ }
32495
32900
 
32496
- this.groupCache["/"] = group;
32497
- return group
32901
+
32902
+ async getHeader() {
32903
+
32904
+ if (typeof this.featureSource.getHeader === "function") {
32905
+ this.header = await this.featureSource.getHeader();
32498
32906
  }
32907
+ return this.header
32499
32908
  }
32500
32909
 
32501
- async readGroup(name) {
32910
+ // TODO: refactor to igvUtils.js
32911
+ getScaleFactor(min, max, height, logScale) {
32912
+ const minValue = (logScale === true) ? ((min < 0) ? -Math.log10(Math.abs(min) + 1) : Math.log10(Math.abs(min) + 1)) : min;
32913
+ const maxValue = (logScale === true) ? Math.log10(Math.abs(max) + 1) : max;
32914
+ const scale = height / (maxValue - minValue);
32915
+ return scale
32916
+ }
32502
32917
 
32503
- const group = this.groupCache[name];
32504
- if (group) {
32505
- return group
32506
- } else {
32918
+ computeYPixelValue(yValue, yScaleFactor) {
32919
+ return (this.flipAxis ? (yValue - this.dataRange.min) : (this.dataRange.max - yValue)) * yScaleFactor
32920
+ }
32507
32921
 
32508
- await this.readHeader();
32509
- const indexEntry = this.groupIndex[name];
32510
- if (indexEntry === undefined) {
32511
- return undefined
32512
- }
32922
+ computeYPixelValueInLogScale(yValue, yScaleFactor) {
32923
+ let maxValue = this.dataRange.max;
32924
+ let minValue = this.dataRange.min;
32925
+ minValue = (minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1);
32926
+ maxValue = (maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1);
32927
+
32928
+ yValue = (yValue < 0) ? -Math.log10(Math.abs(yValue) +1) : Math.log10(yValue + 1);
32929
+ return ((this.flipAxis ? (yValue - minValue) : (maxValue - yValue)) * yScaleFactor)
32930
+ }
32513
32931
 
32514
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32515
- range: {
32516
- start: indexEntry.position,
32517
- size: indexEntry.size
32932
+ draw(options) {
32933
+
32934
+ const features = options.features;
32935
+ const ctx = options.context;
32936
+ const bpPerPixel = options.bpPerPixel;
32937
+ const bpStart = options.bpStart;
32938
+ const pixelWidth = options.pixelWidth;
32939
+ const pixelHeight = options.pixelHeight - 1;
32940
+ const bpEnd = bpStart + pixelWidth * bpPerPixel + 1;
32941
+
32942
+ const scaleFactor = this.getScaleFactor(this.dataRange.min, this.dataRange.max, pixelHeight, this.logScale);
32943
+ const yScale = (yValue) => this.logScale
32944
+ ? this.computeYPixelValueInLogScale(yValue, scaleFactor)
32945
+ : this.computeYPixelValue(yValue, scaleFactor);
32946
+
32947
+ if (features && features.length > 0) {
32948
+
32949
+ if (this.dataRange.min === undefined) this.dataRange.min = 0;
32950
+
32951
+ // Max can be less than min if config.min is set but max left to autoscale. If that's the case there is
32952
+ // nothing to paint.
32953
+ if (this.dataRange.max > this.dataRange.min) {
32954
+
32955
+ let lastPixelEnd = -1;
32956
+ let lastY;
32957
+ const y0 = yScale(0);
32958
+
32959
+ for (let f of features) {
32960
+
32961
+ if (f.end < bpStart) continue
32962
+ if (f.start > bpEnd) break
32963
+
32964
+ const x = (f.start - bpStart) / bpPerPixel;
32965
+ if (isNaN(x)) continue
32966
+
32967
+ let y = yScale(f.value);
32968
+
32969
+ const rectEnd = (f.end - bpStart) / bpPerPixel;
32970
+ const width = rectEnd - x;
32971
+
32972
+ const color = options.alpha ? IGVColor.addAlpha(this.getColorForFeature(f), options.alpha) : this.getColorForFeature(f);
32973
+
32974
+ if (this.graphType === "line") {
32975
+ if (lastY !== undefined) {
32976
+ IGVGraphics.strokeLine(ctx, lastPixelEnd, lastY, x, y, {
32977
+ "fillStyle": color,
32978
+ "strokeStyle": color
32979
+ });
32980
+ }
32981
+ IGVGraphics.strokeLine(ctx, x, y, x + width, y, {"fillStyle": color, "strokeStyle": color});
32982
+ } else if (this.graphType === "points") {
32983
+ const pointSize = this.config.pointSize || 3;
32984
+ const px = x + width / 2;
32985
+ IGVGraphics.fillCircle(ctx, px, y, pointSize / 2, {"fillStyle": color, "strokeStyle": color});
32986
+
32987
+ if (f.value > this.dataRange.max) {
32988
+ IGVGraphics.fillCircle(ctx, px, pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
32989
+ } else if (f.value < this.dataRange.min) {
32990
+ IGVGraphics.fillCircle(ctx, px, pixelHeight - pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
32991
+ }
32992
+
32993
+ } else {
32994
+ // Default graph type (bar)
32995
+ const height = Math.min(pixelHeight, y - y0);
32996
+ IGVGraphics.fillRect(ctx, x, y0, width, height, {fillStyle: color});
32997
+ if (f.value > this.dataRange.max) {
32998
+ IGVGraphics.fillRect(ctx, x, 0, width, 3, {fillStyle: this.overflowColor});
32999
+ } else if (f.value < this.dataRange.min) {
33000
+ IGVGraphics.fillRect(ctx, x, pixelHeight - 2, width, 3, {fillStyle: this.overflowColor});
33001
+ }
33002
+
33003
+ }
33004
+ lastPixelEnd = x + width;
33005
+ lastY = y;
32518
33006
  }
32519
- }));
32520
33007
 
32521
- if (!data) {
32522
- return undefined
33008
+ // If the track includes negative values draw a baseline
33009
+ if (this.dataRange.min < 0) {
33010
+ let maxValue = this.dataRange.max;
33011
+ let minValue = this.dataRange.min;
33012
+ minValue = (this.logScale === true) ? ((minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1)) : minValue;
33013
+ maxValue = (this.logScale === true) ? ((maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1)) : maxValue;
33014
+ const ratio = maxValue / (maxValue - minValue);
33015
+ const basepx = this.flipAxis ? (1 - ratio) * pixelHeight : ratio * pixelHeight;
33016
+ IGVGraphics.strokeLine(ctx, 0, basepx, options.pixelWidth, basepx, {strokeStyle: this.baselineColor});
33017
+ }
32523
33018
  }
33019
+ }
32524
33020
 
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;
33021
+ // Draw guidelines
33022
+ if (this.config.hasOwnProperty('guideLines')) {
33023
+ for (let line of this.config.guideLines) {
33024
+ if (line.hasOwnProperty('color') && line.hasOwnProperty('y') && line.hasOwnProperty('dotted')) {
33025
+ let y = yScale(line.y);
33026
+ let props = {
33027
+ 'strokeStyle': line['color'],
33028
+ 'strokeWidth': 2
33029
+ };
33030
+ if (line['dotted']) IGVGraphics.dashedLine(options.context, 0, y, options.pixelWidth, y, 5, props);
33031
+ else IGVGraphics.strokeLine(options.context, 0, y, options.pixelWidth, y, props);
33032
+ }
32532
33033
  }
32533
- this.groupCache[name] = group;
32534
- return group
32535
33034
  }
32536
33035
  }
32537
33036
 
32538
- async readTiles(tileIndeces, nTracks) {
33037
+ popupData(clickState, features) {
32539
33038
 
32540
- tileIndeces.sort(function (a, b) {
32541
- return a.position - b.position
32542
- });
33039
+ if (features === undefined) features = this.clickedFeatures(clickState);
32543
33040
 
32544
- tileIndeces = tileIndeces.filter(function (idx) {
32545
- return idx.size > 0
32546
- });
33041
+ if (features && features.length > 0) {
32547
33042
 
32548
- if (tileIndeces.length === 0) {
32549
- return []
32550
- }
33043
+ const genomicLocation = clickState.genomicLocation;
33044
+ const popupData = [];
32551
33045
 
32552
- tileIndeces = consolidateTiles(tileIndeces);
33046
+ // Sort features based on distance from click
33047
+ features.sort(function (a, b) {
33048
+ const distA = Math.abs((a.start + a.end) / 2 - genomicLocation);
33049
+ const distB = Math.abs((b.start + b.end) / 2 - genomicLocation);
33050
+ return distA - distB
33051
+ });
32553
33052
 
32554
- const tiles = [];
33053
+ // Display closest 10
33054
+ const displayFeatures = features.length > 10 ? features.slice(0, 10) : features;
32555
33055
 
32556
- for (let indexEntry of tileIndeces) {
33056
+ // Resort in ascending order
33057
+ displayFeatures.sort(function (a, b) {
33058
+ return a.start - b.start
33059
+ });
32557
33060
 
32558
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32559
- range: {
32560
- start: indexEntry.position,
32561
- size: indexEntry.size
33061
+ for (let selectedFeature of displayFeatures) {
33062
+ if (selectedFeature) {
33063
+ if (popupData.length > 0) {
33064
+ popupData.push('<hr/>');
33065
+ }
33066
+ let posString = (selectedFeature.end - selectedFeature.start) === 1 ?
33067
+ numberFormatter$1(Math.floor(selectedFeature.start) + 1)
33068
+ : numberFormatter$1(Math.floor(selectedFeature.start) + 1) + "-" + numberFormatter$1(Math.floor(selectedFeature.end));
33069
+ popupData.push({name: "Position:", value: posString});
33070
+ popupData.push({
33071
+ name: "Value:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;",
33072
+ value: numberFormatter$1(selectedFeature.value.toFixed(4))
33073
+ });
32562
33074
  }
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
33075
  }
32584
- tiles.push(tile);
33076
+ if (displayFeatures.length < features.length) {
33077
+ popupData.push("<hr/>...");
33078
+ }
33079
+
33080
+ return popupData
32585
33081
 
33082
+ } else {
33083
+ return []
32586
33084
  }
32587
- return tiles
32588
33085
  }
32589
33086
 
32590
- async readTile(indexEntry, nTracks) {
33087
+ get supportsWholeGenome() {
33088
+ return !this.config.indexURL && this.config.supportsWholeGenome !== false
33089
+ }
32591
33090
 
32592
- let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32593
- range: {
32594
- start: indexEntry.position,
32595
- size: indexEntry.size
32596
- }
32597
- }));
33091
+ /**
33092
+ * Return color for feature.
33093
+ * @param feature
33094
+ * @returns {string}
33095
+ */
32598
33096
 
32599
- if (this.compressed) {
32600
- const plain = inflate_1$3(data);
32601
- data = plain.buffer;
32602
- }
33097
+ getColorForFeature(f) {
33098
+ let c = (f.value < 0 && this.altColor) ? this.altColor : this.color || DEFAULT_COLOR$2;
33099
+ return (typeof c === "function") ? c(f.value) : c
33100
+ }
32603
33101
 
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
32616
- }
33102
+ /**
33103
+ * Called when the track is removed. Do any needed cleanup here
33104
+ */
33105
+ dispose() {
33106
+ this.trackView = undefined;
32617
33107
  }
32618
33108
 
32619
33109
  }
32620
33110
 
32621
- function createFixedStep(binaryParser, nTracks) {
32622
- const nPositions = binaryParser.getInt();
32623
- const start = binaryParser.getInt();
32624
- const span = binaryParser.getFloat();
33111
+ /**
33112
+ * Summarize wig data in bins of size "bpPerPixel" with the given window function.
33113
+ *
33114
+ * @param features wig (numeric) data -- features cannot overlap, and are in ascending order by start position
33115
+ * @param startBP bp start position for computing binned data
33116
+ * @param bpPerPixel bp per pixel (bin)
33117
+ * @param windowFunction mean, min, or max
33118
+ * @returns {*|*[]}
33119
+ */
33120
+ function summarizeData(features, startBP, bpPerPixel, windowFunction = "mean") {
32625
33121
 
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);
33122
+ if (bpPerPixel <= 1 || !features || features.length === 0) {
33123
+ return features
32635
33124
  }
32636
33125
 
32637
- return {
32638
- type: "fixedStep",
32639
- start: start,
32640
- span: span,
32641
- data: data,
32642
- nTracks: nTracks,
32643
- nPositions: nPositions
32644
- }
32645
- }
33126
+ // Assume features are sorted by position. Wig features cannot overlap. Note, UCSC "reductionLevel" == bpPerPixel
33127
+ const chr = features[0].chr;
33128
+ const binSize = bpPerPixel;
33129
+ const summaryFeatures = [];
32646
33130
 
32647
- function createVariableStep(binaryParser, nTracks) {
33131
+ const finishBin = (bin) => {
33132
+ const start = startBP + bin.bin * binSize;
33133
+ const end = start + binSize;
33134
+ let value;
33135
+ switch (windowFunction) {
33136
+ case "mean":
33137
+ value = bin.sumData / bin.count;
33138
+ break
33139
+ case "max":
33140
+ value = bin.max;
33141
+ break
33142
+ case "min":
33143
+ value = bin.min;
33144
+ break
33145
+ default:
33146
+ throw Error(`Unknown window function: ${windowFunction}`)
33147
+ }
33148
+ const description = `${windowFunction} of ${bin.count} values`;
33149
+ summaryFeatures.push({chr, start, end, value, description});
33150
+ };
32648
33151
 
32649
- const tileStart = binaryParser.getInt();
32650
- const span = binaryParser.getFloat();
32651
- const nPositions = binaryParser.getInt();
32652
- const start = [];
33152
+ let currentBinData;
33153
+ for (let f of features) {
32653
33154
 
32654
- let np = nPositions;
32655
- while (np-- > 0) {
32656
- start.push(binaryParser.getInt());
32657
- }
32658
- binaryParser.getInt(); // # of samples, ignored but should === nTracks
33155
+ // Loop through bins this feature overlaps, updating the weighted sum for each bin or min/max,
33156
+ // depending on window function
33157
+ let startBin = Math.floor((f.start - startBP) / binSize);
33158
+ const endBin = Math.floor((f.end - startBP) / binSize);
32659
33159
 
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());
33160
+ if (currentBinData && startBin === currentBinData.bin) {
33161
+ currentBinData.add(f);
33162
+ startBin++;
32667
33163
  }
32668
- data.push(dtrack);
32669
- }
32670
33164
 
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
- }
33165
+ if (!currentBinData || endBin > currentBinData.bin) {
33166
+
33167
+ if(currentBinData) {
33168
+ finishBin(currentBinData);
33169
+ }
32681
33170
 
32682
- function createBed(binaryParser, nTracks, type) {
33171
+ // Feature stretches across multiple bins.
33172
+ if (endBin > startBin) {
33173
+ const end = startBP + endBin * binSize;
33174
+ summaryFeatures.push({chr, start: f.start, end, value: f.value});
33175
+ }
32683
33176
 
32684
- const nPositions = binaryParser.getInt();
33177
+ currentBinData = new SummaryBinData(endBin, f);
33178
+ }
32685
33179
 
32686
- let n = nPositions;
32687
- const start = [];
32688
- while (n-- > 0) {
32689
- start.push(binaryParser.getInt());
32690
33180
  }
32691
-
32692
- n = nPositions;
32693
- const end = [];
32694
- while (n-- > 0) {
32695
- end.push(binaryParser.getInt());
33181
+ if(currentBinData) {
33182
+ finishBin(currentBinData);
32696
33183
  }
32697
33184
 
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());
33185
+ // Consolidate
33186
+ const c = [];
33187
+ let lastFeature = summaryFeatures[0];
33188
+ for (let f of summaryFeatures) {
33189
+ if (lastFeature.value === f.value && f.start <= lastFeature.end) {
33190
+ lastFeature.end = f.end;
33191
+ } else {
33192
+ c.push(lastFeature);
33193
+ lastFeature = f;
32706
33194
  }
32707
- data.push(dtrack);
32708
33195
  }
33196
+ c.push(lastFeature);
32709
33197
 
32710
- if (type === "bedWithName") {
32711
- n = nPositions;
32712
- const name = [];
32713
- while (n-- > 0) {
32714
- name.push(binaryParser.getString());
32715
- }
32716
- }
33198
+ return c
32717
33199
 
32718
- return {
32719
- type: type,
32720
- start: start,
32721
- end: end,
32722
- data: data,
32723
- nTracks: nTracks,
32724
- nPositions: nPositions
32725
- }
32726
33200
  }
32727
33201
 
32728
- function consolidateTiles(tiles) {
33202
+ class SummaryBinData {
33203
+ constructor(bin, feature) {
33204
+ this.bin = bin;
33205
+ this.sumData = feature.value;
33206
+ this.count = 1;
33207
+ this.min = feature.value;
33208
+ this.max = feature.value;
33209
+ }
33210
+
33211
+ add(feature) {
33212
+ this.sumData += feature.value;
33213
+ this.max = Math.max(feature.value, this.max);
33214
+ this.min = Math.min(feature.value, this.min);
33215
+ this.count++;
33216
+ }
32729
33217
 
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
- }
33218
+ get mean() {
33219
+ return this.sumData / this.count
32740
33220
  }
32741
- consolidated.push(current);
32742
- return consolidated
32743
33221
  }
32744
33222
 
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
- *
33223
+ const DEFAULT_MAX_WG_COUNT = 10000;
33224
+
33225
+ /**
33226
+ * feature source for "bed like" files (tab or whitespace delimited files with 1 feature per line: bed, gff, vcf, etc)
32761
33227
  *
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.
33228
+ * @param config
33229
+ * @constructor
32769
33230
  */
33231
+ class TextFeatureSource extends BaseFeatureSource {
32770
33232
 
32771
- class TDFSource extends BaseFeatureSource {
32772
-
32773
- searchable = false
32774
33233
  constructor(config, genome) {
33234
+
32775
33235
  super(genome);
33236
+
33237
+ this.config = config || {};
32776
33238
  this.genome = genome;
32777
- this.reader = new TDFReader(config, genome);
32778
- this.queryable = true;
32779
- }
33239
+ this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
33240
+ this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
33241
+ this.windowFunctions = ["mean", "min", "max", "none"];
32780
33242
 
32781
- async getFeatures({chr, start, end, bpPerPixel, windowFunction = "mean"}) {
33243
+ const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
32782
33244
 
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
33245
+ this.queryable = config.indexURL || config.queryable === true; // False by default, unless explicitly set
33246
+ if (config.reader) {
33247
+ // Explicit reader implementation
33248
+ this.reader = config.reader;
33249
+ this.queryable = config.queryable !== false;
33250
+ } else if (config.sourceType === "ga4gh") {
33251
+ throw Error("Unsupported source type 'ga4gh'")
33252
+ } else if ((config.type === "eqtl" || config.type === "qtl") && config.sourceType === "gtex-ws") {
33253
+ this.reader = new GtexReader(config);
33254
+ this.queryable = true;
33255
+ } else if ("htsget" === config.sourceType) {
33256
+ this.reader = new HtsgetVariantReader(config, genome);
33257
+ this.queryable = true;
33258
+ } else if (config.sourceType === 'ucscservice') {
33259
+ this.reader = new UCSCServiceReader(config.source);
33260
+ this.queryable = true;
33261
+ } else if (config.sourceType === 'custom') {
33262
+ this.reader = new CustomServiceReader(config.source);
33263
+ this.queryable = false !== config.source.queryable;
33264
+ } else if ('service' === config.sourceType) {
33265
+ this.reader = new FeatureFileReader(config, genome);
33266
+ this.queryable = true;
33267
+ } else {
33268
+ // File of some type (i.e. not a webservice)
33269
+ this.reader = new FeatureFileReader(config, genome);
33270
+ if (config.queryable !== undefined) {
33271
+ this.queryable = config.queryable;
33272
+ } else if (queryableFormats.has(config.format) || this.reader.indexed) {
33273
+ this.queryable = true;
33274
+ } else ;
33275
+ }
33276
+
33277
+ // Flag indicating if features loaded by this source can be searched for by name or attribute, true by default
33278
+ this.searchable = config.searchable !== false;
33279
+
33280
+ }
33281
+
33282
+ async defaultVisibilityWindow() {
33283
+ if (this.reader && typeof this.reader.defaultVisibilityWindow === 'function') {
33284
+ return this.reader.defaultVisibilityWindow()
33285
+ }
33286
+ }
32805
33287
 
33288
+ async trackType() {
33289
+ const header = await this.getHeader();
33290
+ if (header) {
33291
+ return header.type
32806
33292
  } else {
32807
- return this._getFeatures(chr, start, end, bpPerPixel, windowFunction)
33293
+ return undefined // Convention for unknown or unspecified
32808
33294
  }
32809
33295
  }
32810
- async _getFeatures(chr, start, end, bpPerPixel, windowFunction) {
32811
- const genomicInterval = new GenomicInterval(chr, start, end);
32812
- const genome = this.genome;
32813
33296
 
33297
+ async getHeader() {
33298
+ if (!this.header) {
32814
33299
 
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;
33300
+ if (this.reader && typeof this.reader.readHeader === "function") {
33301
+ const header = await this.reader.readHeader();
33302
+ if (header) {
33303
+ this.header = header;
33304
+ if (header.format) {
33305
+ this.config.format = header.format;
33306
+ }
33307
+ } else {
33308
+ this.header = {};
32821
33309
  }
33310
+ } else {
33311
+ this.header = {};
32822
33312
  }
32823
33313
  }
33314
+ return this.header
33315
+ }
32824
33316
 
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;
33317
+ /**
33318
+ * Required function for all data source objects. Fetches features for the
33319
+ * range requested.
33320
+ *
33321
+ * This function is quite complex due to the variety of reader types backing it, some indexed, some queryable,
33322
+ * some not.
33323
+ *
33324
+ * @param chr
33325
+ * @param start
33326
+ * @param end
33327
+ * @param bpPerPixel
33328
+ */
33329
+ async getFeatures({chr, start, end, bpPerPixel, visibilityWindow, windowFunction}) {
32831
33330
 
32832
- const wf = zoom > maxZoom ? "raw" : windowFunction;
32833
- const dataset = await this.reader.readDataset(queryChr, wf, zoom);
32834
- if (dataset == null) {
32835
- return []
33331
+ const isWholeGenome = ("all" === chr.toLowerCase());
33332
+
33333
+ start = start || 0;
33334
+ end = end || Number.MAX_SAFE_INTEGER;
33335
+
33336
+ // Various conditions that can require a feature load
33337
+ // * view is "whole genome" but no features are loaded
33338
+ // * cache is disabled
33339
+ // * cache does not contain requested range
33340
+ // const containsRange = this.featureCache.containsRange(new GenomicInterval(queryChr, start, end))
33341
+ if ((isWholeGenome && !this.wgFeatures && this.supportsWholeGenome()) ||
33342
+ this.config.disableCache ||
33343
+ !this.featureCache ||
33344
+ !this.featureCache.containsRange(new GenomicInterval(chr, start, end))) {
33345
+ await this.loadFeatures(chr, start, end, visibilityWindow);
32836
33346
  }
32837
33347
 
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)
33348
+ if (isWholeGenome) {
33349
+ if (!this.wgFeatures) {
33350
+ if (this.supportsWholeGenome()) {
33351
+ if("wig" === this.config.type) {
33352
+ const allWgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, 1000000);
33353
+ this.wgFeatures = summarizeData(allWgFeatures, 0, bpPerPixel, windowFunction);
33354
+ } else {
33355
+ this.wgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, this.maxWGCount);
33356
+ }
33357
+ } else {
33358
+ this.wgFeatures = [];
33359
+ }
32857
33360
  }
33361
+ return this.wgFeatures
33362
+ } else {
33363
+ return this.featureCache.queryFeatures(chr, start, end)
32858
33364
  }
32859
- features.sort(function (a, b) {
32860
- return a.start - b.start
32861
- });
32862
-
32863
- return features
32864
33365
  }
32865
33366
 
32866
- get supportsWholeGenome() {
32867
- return true
33367
+ async findFeatures(fn) {
33368
+ return this.featureCache ? this.featureCache.findFeatures(fn) : []
32868
33369
  }
32869
33370
 
32870
- get windowFunctions() {
32871
- return this.reader.windowFunctions
33371
+ supportsWholeGenome() {
33372
+ return !this.queryable // queryable (indexed, web services) sources don't support whole genome view
32872
33373
  }
32873
- }
32874
-
32875
- function decodeBedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
32876
33374
 
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
- });
33375
+ // TODO -- experimental, will only work for non-indexed sources
33376
+ getAllFeatures() {
33377
+ if (this.queryable || !this.featureCache) { // queryable sources don't support all features
33378
+ return []
33379
+ } else {
33380
+ return this.featureCache.getAllFeatures()
33381
+ }
32892
33382
  }
32893
- }
32894
33383
 
32895
- function decodeVaryTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
32896
33384
 
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
- }
33385
+ async loadFeatures(chr, start, end, visibilityWindow) {
32914
33386
 
32915
- function decodeFixedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
33387
+ await this.getHeader();
32916
33388
 
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
33389
+ const reader = this.reader;
33390
+ let intervalStart = start;
33391
+ let intervalEnd = end;
32921
33392
 
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
- });
33393
+ // chr aliasing
33394
+ let queryChr = chr;
33395
+ if (!this.chrAliasManager && this.reader && this.reader.sequenceNames) {
33396
+ this.chrAliasManager = new ChromAliasManager(this.reader.sequenceNames, this.genome);
33397
+ }
33398
+ if (this.chrAliasManager) {
33399
+ queryChr = await this.chrAliasManager.getAliasName(chr);
33400
+ }
33401
+
33402
+ // Use visibility window to potentially expand query interval.
33403
+ // This can save re-queries as we zoom out. Visibility window <= 0 is a special case
33404
+ // indicating whole chromosome should be read at once.
33405
+ if ((!visibilityWindow || visibilityWindow <= 0) && this.config.expandQuery !== false) {
33406
+ // Whole chromosome
33407
+ const chromosome = this.genome ? this.genome.getChromosome(queryChr) : undefined;
33408
+ intervalStart = 0;
33409
+ intervalEnd = Math.max(chromosome ? chromosome.bpLength : Number.MAX_SAFE_INTEGER, end);
33410
+ } else if (visibilityWindow > (end - start) && this.config.expandQuery !== false) {
33411
+ let expansionWindow = Math.min(4.1 * (end - start), visibilityWindow);
33412
+ if(this.config.minQuerySize && expansionWindow < this.config.minQuerySize) {
33413
+ expansionWindow = this.config.minQuerySize;
32933
33414
  }
33415
+ intervalStart = Math.max(0, (start + end - expansionWindow) / 2);
33416
+ intervalEnd = intervalStart + expansionWindow;
32934
33417
  }
32935
- s = e;
32936
- }
32937
- }
32938
33418
 
33419
+ let features = await reader.readFeatures(queryChr, intervalStart, intervalEnd);
33420
+ if (this.queryable === undefined) {
33421
+ this.queryable = reader.indexed;
33422
+ }
32939
33423
 
32940
- var log2 = Math.log(2);
33424
+ const genomicInterval = this.queryable ?
33425
+ new GenomicInterval(chr, intervalStart, intervalEnd) :
33426
+ undefined;
32941
33427
 
32942
- function zoomLevelForScale(chr, bpPerPixel, genome) {
33428
+ if (features) {
32943
33429
 
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
33430
+ // Assign overlapping features to rows
33431
+ if (this.config.format !== "wig" && this.config.type !== "junctions") {
33432
+ const maxRows = this.config.maxRows || Number.MAX_SAFE_INTEGER;
33433
+ packFeatures(features, maxRows);
33434
+ }
32947
33435
 
32948
- var chrSize = genome.getChromosome(chr).bpLength;
33436
+ // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
33437
+ this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
32949
33438
 
32950
- return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
33439
+ // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
33440
+ if (this.searchable) {
33441
+ this.addFeaturesToDB(features, this.config);
33442
+ }
33443
+ } else {
33444
+ this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
33445
+ }
33446
+ }
33447
+
33448
+ addFeaturesToDB(featureList, config) {
33449
+ if (!this.featureMap) {
33450
+ this.featureMap = new Map();
33451
+ }
33452
+ const searchableFields = config.searchableFields || ["name", "transcript_id", "gene_id", "gene_name", "id"];
33453
+ for (let feature of featureList) {
33454
+ for (let field of searchableFields) {
33455
+ let key;
33456
+ if (typeof feature.getAttributeValue === 'function') {
33457
+ key = feature.getAttributeValue(field);
33458
+ }
33459
+ if (key) {
33460
+ key = key.replaceAll(' ', '+').toUpperCase();
33461
+ // If feature is already present keep largest one
33462
+ if (this.featureMap.has(key)) {
33463
+ const f2 = this.featureMap.get(key);
33464
+ if (feature.end - feature.start < f2.end - f2.start) {
33465
+ continue
33466
+ }
33467
+ }
33468
+ this.featureMap.set(key, feature);
33469
+ }
33470
+ }
33471
+ }
33472
+ }
33473
+
33474
+ search(term) {
33475
+ if (this.featureMap) {
33476
+ return this.featureMap.get(term.toUpperCase())
33477
+ }
33478
+
33479
+ }
32951
33480
  }
32952
33481
 
32953
33482
  /*
@@ -33971,7 +34500,11 @@ function renderFeatureLabel(ctx, feature, featureX, featureX1, featureY, referen
33971
34500
  if (name === undefined) name = feature.id || feature.ID;
33972
34501
  if (!name || name === '.') return
33973
34502
 
33974
- let centerX = (featureX + featureX1) / 2;
34503
+ let pixelXOffset = options.pixelXOffset || 0;
34504
+ const t1 = Math.max(featureX, -pixelXOffset);
34505
+ const t2 = Math.min(featureX1, -pixelXOffset + options.viewportWidth);
34506
+ let centerX = (t1 + t2) / 2;
34507
+ //let centerX = (featureX + featureX1) / 2
33975
34508
 
33976
34509
  let transform;
33977
34510
  if (this.displayMode === "COLLAPSED" && this.labelDisplayMode === "SLANT") {
@@ -34239,7 +34772,7 @@ function renderFusionJuncSpan(feature, bpStart, xScale, pixelHeight, ctx) {
34239
34772
  }
34240
34773
  }
34241
34774
 
34242
- const DEFAULT_COLOR$2 = 'rgb(0, 0, 150)';
34775
+ const DEFAULT_COLOR$1 = 'rgb(0, 0, 150)';
34243
34776
 
34244
34777
 
34245
34778
  class FeatureTrack extends TrackBase {
@@ -34250,7 +34783,6 @@ class FeatureTrack extends TrackBase {
34250
34783
  displayMode: "EXPANDED", // COLLAPSED | EXPANDED | SQUISHED
34251
34784
  margin: 10,
34252
34785
  featureHeight: 14,
34253
- autoHeight: false,
34254
34786
  useScore: false
34255
34787
  }
34256
34788
 
@@ -34736,7 +35268,7 @@ class FeatureTrack extends TrackBase {
34736
35268
 
34737
35269
  // If no explicit setting use the default
34738
35270
  if (!color) {
34739
- color = DEFAULT_COLOR$2; // Track default
35271
+ color = DEFAULT_COLOR$1; // Track default
34740
35272
  }
34741
35273
 
34742
35274
  if (feature.alpha && feature.alpha !== 1) {
@@ -34782,7 +35314,8 @@ function monitorTrackDrag(track) {
34782
35314
 
34783
35315
  function onDragEnd() {
34784
35316
  if (track.trackView && track.displayMode !== "SQUISHED") {
34785
- track.trackView.updateViews(); // TODO -- refine this to the viewport that was dragged after DOM refactor
35317
+ // Repaint views to adjust feature name if center is moved out of view
35318
+ track.trackView.repaintViews();
34786
35319
  }
34787
35320
  }
34788
35321
 
@@ -37863,7 +38396,6 @@ function indentLevel(str) {
37863
38396
  }
37864
38397
 
37865
38398
  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
38399
 
37868
38400
  const GenomeUtils = {
37869
38401
 
@@ -37875,21 +38407,9 @@ const GenomeUtils = {
37875
38407
 
37876
38408
  // Get default genomes
37877
38409
  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
- }
38410
+ const url = DEFAULT_GENOMES_URL;
38411
+ const jsonArray = await igvxhr.loadJson(url, {timeout: 5000});
38412
+ processJson(jsonArray);
37893
38413
  }
37894
38414
 
37895
38415
  // Add user-defined genomes
@@ -37954,7 +38474,7 @@ const GenomeUtils = {
37954
38474
  }
37955
38475
  }
37956
38476
 
37957
- if(!reference) {
38477
+ if (!reference) {
37958
38478
  alert.present(new Error(`Unknown genome id: ${genomeID}`), undefined);
37959
38479
  }
37960
38480
  }
@@ -41532,62 +42052,6 @@ const sampleNameButtonLabel =
41532
42052
  </g>
41533
42053
  </svg>`;
41534
42054
 
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
42055
  /*
41592
42056
  * The MIT License (MIT)
41593
42057
  *
@@ -42588,7 +43052,7 @@ class TrackView {
42588
43052
  const viewportsToRepaint = visibleViewports.filter(vp => vp.needsRepaint()).filter(viewport => viewport.checkZoomIn());
42589
43053
 
42590
43054
  // Get viewports that require a data load
42591
- const viewportsToReload = viewportsToRepaint.filter(viewport => viewport.needsReload());
43055
+ const viewportsToReload = visibleViewports.filter(viewport => viewport.needsReload());
42592
43056
 
42593
43057
  // Trigger viewport to load features needed to cover current genomic range
42594
43058
  // NOTE: these must be loaded synchronously, do not user Promise.all, not all file readers are thread safe
@@ -43183,462 +43647,6 @@ function renderSVGAxis(context, track, axisCanvas, deltaX, deltaY) {
43183
43647
 
43184
43648
  }
43185
43649
 
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
43650
  /**
43643
43651
  *
43644
43652
  * @param cs - object containing
@@ -48553,7 +48561,7 @@ class AlignmentContainer {
48553
48561
  allAlignments() {
48554
48562
  if (this.alignments) {
48555
48563
  return this.alignments
48556
- } else {
48564
+ } else if (this.packedGroups) {
48557
48565
  const all = Array.from(this.packedGroups.values()).flatMap(group => group.rows.flatMap(row => row.alignments));
48558
48566
  if (this.#unpacked && this.#unpacked.length > 0) {
48559
48567
  for (let a of this.#unpacked) {
@@ -48561,6 +48569,8 @@ class AlignmentContainer {
48561
48569
  }
48562
48570
  }
48563
48571
  return all
48572
+ } else {
48573
+ return []
48564
48574
  }
48565
48575
  }
48566
48576
 
@@ -48569,9 +48579,10 @@ class AlignmentContainer {
48569
48579
  }
48570
48580
 
48571
48581
  sortRows(options) {
48572
-
48573
- for (let group of this.packedGroups.values()) {
48574
- group.sortRows(options, this);
48582
+ if(this.packedGroups) {
48583
+ for (let group of this.packedGroups.values()) {
48584
+ group.sortRows(options, this);
48585
+ }
48575
48586
  }
48576
48587
  }
48577
48588
  }
@@ -57361,23 +57372,24 @@ class AlignmentTrack extends TrackBase {
57361
57372
  const y = clickState.y;
57362
57373
  const offsetY = y - this.top;
57363
57374
  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]
57375
+
57376
+ if(features.packedGroups) {
57377
+ let minGroupY = Number.MAX_VALUE;
57378
+ for (let group of features.packedGroups.values()) {
57379
+ minGroupY = Math.min(minGroupY, group.pixelTop);
57380
+ if (offsetY > group.pixelTop && offsetY <= group.pixelBottom) {
57381
+
57382
+ const alignmentRowHeight = this.displayMode === "SQUISHED" ?
57383
+ this.squishedRowHeight :
57384
+ this.alignmentRowHeight;
57385
+
57386
+ let packedAlignmentsIndex = Math.floor((offsetY - group.pixelTop) / alignmentRowHeight);
57387
+
57388
+ if (packedAlignmentsIndex >= 0 && packedAlignmentsIndex < group.length) {
57389
+ const alignmentRow = group.rows[packedAlignmentsIndex];
57390
+ const clicked = alignmentRow.alignments.filter(alignment => alignment.containsLocation(genomicLocation, this.showSoftClips));
57391
+ if (clicked.length > 0) return clicked[0]
57392
+ }
57381
57393
  }
57382
57394
  }
57383
57395
  }
@@ -67605,8 +67617,6 @@ class VariantTrack extends TrackBase {
67605
67617
  this.trackView.setTrackHeight(this.config.height || CNVPytorTrack.DEFAULT_TRACK_HEIGHT);
67606
67618
  this.trackView.checkContentHeight();
67607
67619
  this.trackView.updateViews();
67608
- this.trackView.track.autoHeight = false;
67609
-
67610
67620
 
67611
67621
  } finally {
67612
67622
  this.trackView.stopSpinner();
@@ -68027,13 +68037,8 @@ class QTLTrack extends TrackBase {
68027
68037
  this.divider = config.divider || "rgb(225,225,225)";
68028
68038
  this.dotSize = config.dotSize || 2;
68029
68039
  this.height = config.height || 100;
68030
- this.autoHeight = false;
68031
68040
  this.disableButtons = config.disableButtons;
68032
68041
 
68033
- // Limit visibility window to 2 mb, gtex server gets flaky beyond that
68034
- //this.visibilityWindow = config.visibilityWindow === undefined ?
68035
- // 2000000 : config.visibilityWindow >= 0 ? Math.min(2000000, config.visibilityWindow) : 2000000
68036
-
68037
68042
  this.featureSource = FeatureSource(config, this.browser.genome);
68038
68043
  }
68039
68044
 
@@ -70512,7 +70517,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
70512
70517
  })
70513
70518
  }
70514
70519
 
70515
- const _version = "3.0.2";
70520
+ const _version = "3.0.4";
70516
70521
  function version() {
70517
70522
  return _version
70518
70523
  }
@@ -71983,16 +71988,17 @@ class ROIMenu {
71983
71988
 
71984
71989
  }
71985
71990
 
71986
- async present(feature, isUserDefined, event, roiManager, columnContainer, regionElement) {
71987
- const menuItems = this.menuItems(feature, isUserDefined, event, roiManager, columnContainer, regionElement);
71991
+ async present(feature, roiSet, event, roiManager, columnContainer, regionElement) {
71992
+ const menuItems = this.menuItems(feature, roiSet, event, roiManager, columnContainer, regionElement);
71988
71993
  this.browser.menuPopup.presentTrackContextMenu(event, menuItems);
71989
71994
  }
71990
71995
 
71991
- menuItems(feature, isUserDefined, event, roiManager, columnContainer, regionElement) {
71996
+ menuItems(feature, roiSet, event, roiManager, columnContainer, regionElement) {
71997
+ const items = feature.name ? [`<b>${feature.name}</b><br/>`] : [];
71998
+ if ('name' in roiSet) items.push(`<b>ROI Set: ${roiSet.name}</b>`);
71999
+ if (items.length > 0) items.push(`<hr/>`);
71992
72000
 
71993
- const items = [`<b>${feature.name || ''}</b>`,];
71994
-
71995
- if (isUserDefined) {
72001
+ if (roiSet.isUserDefined) {
71996
72002
  items.push(
71997
72003
  {
71998
72004
  label: 'Set description ...',
@@ -72056,7 +72062,7 @@ class ROIMenu {
72056
72062
  }
72057
72063
 
72058
72064
 
72059
- if (isUserDefined) {
72065
+ if (roiSet.isUserDefined) {
72060
72066
  items.push(
72061
72067
  '<hr/>',
72062
72068
  {
@@ -72341,13 +72347,17 @@ class ROIManager {
72341
72347
  const [rectA, rectB] = tracks
72342
72348
  .map(track => track.trackView.viewports[0].$viewport.get(0))
72343
72349
  .map(element => getElementVerticalDimension(element));
72344
-
72350
+
72351
+ //Covers cases in which ruler and/or ideogram are hidden
72352
+ const heightA = rectA ? rectA.height : 0;
72353
+ const heightB = rectB ? rectB.height : 0;
72354
+
72345
72355
  const elements = browser.columnContainer.querySelectorAll('.igv-roi-region');
72346
72356
 
72347
72357
  const fudge = -0.5;
72348
72358
  if (elements) {
72349
72359
  for (const element of elements) {
72350
- element.style.marginTop = `${rectA.height + rectB.height + fudge}px`;
72360
+ element.style.marginTop = `${heightA + heightB + fudge}px`;
72351
72361
  }
72352
72362
 
72353
72363
  }
@@ -72515,7 +72525,6 @@ class ROIManager {
72515
72525
  if (features) {
72516
72526
 
72517
72527
  for (let feature of features) {
72518
-
72519
72528
  const regionKey = createRegionKey(chr, feature.start, feature.end);
72520
72529
 
72521
72530
  const {
@@ -72560,8 +72569,7 @@ class ROIManager {
72560
72569
  event.stopPropagation();
72561
72570
 
72562
72571
  translateMouseCoordinates(event, columnContainer);
72563
- const isUserDefined = roiSet.isUserDefined;
72564
- this.roiMenu.present(feature, isUserDefined, event, this, columnContainer, regionElement);
72572
+ this.roiMenu.present(feature, roiSet, event, this, columnContainer, regionElement);
72565
72573
  });
72566
72574
 
72567
72575
 
@@ -73730,7 +73738,7 @@ function generateGenomeID(config) {
73730
73738
  }
73731
73739
  }
73732
73740
 
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';
73741
+ 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
73742
 
73735
73743
  /**
73736
73744
  * Manages XQTL selections.
@@ -74613,6 +74621,10 @@ class Browser {
74613
74621
  */
74614
74622
  async loadGenome(idOrConfig) {
74615
74623
 
74624
+ if (idOrConfig.genarkAccession) {
74625
+ idOrConfig.url = convertToHubURL(idOrConfig.genarkAccession);
74626
+ }
74627
+
74616
74628
  // Translate the generic "url" field, used by clients such as igv-webapp
74617
74629
  if (idOrConfig.url) {
74618
74630
  if (isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {
@@ -75905,7 +75917,7 @@ class Browser {
75905
75917
 
75906
75918
  if (dragObject && dragObject.viewport.referenceFrame.start !== dragObject.start) {
75907
75919
  this.updateViews();
75908
- this.fireEvent('trackdragend');
75920
+ this.fireEvent('trackdragend', [dragObject.viewport]);
75909
75921
  }
75910
75922
  }
75911
75923