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