igv 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/igv.esm.js CHANGED
@@ -19015,13 +19015,13 @@ class DataRangeDialog {
19015
19015
  if (Array.isArray(trackViewOrTrackViewList)) {
19016
19016
  dataRange = { min: Number.MAX_SAFE_INTEGER, max:-Number.MAX_SAFE_INTEGER };
19017
19017
  for (const trackView of trackViewOrTrackViewList) {
19018
- if (trackView.dataRange) {
19019
- dataRange.min = Math.min(trackView.dataRange.min, dataRange.min);
19020
- dataRange.max = Math.max(trackView.dataRange.max, dataRange.max);
19018
+ if (trackView.track.dataRange) {
19019
+ dataRange.min = Math.min(trackView.track.dataRange.min, dataRange.min);
19020
+ dataRange.max = Math.max(trackView.track.dataRange.max, dataRange.max);
19021
19021
  }
19022
19022
  }
19023
19023
  } else {
19024
- dataRange = trackViewOrTrackViewList.dataRange;
19024
+ dataRange = trackViewOrTrackViewList.track.dataRange;
19025
19025
  }
19026
19026
 
19027
19027
  if (dataRange) {
@@ -19063,7 +19063,7 @@ class DataRangeDialog {
19063
19063
  } else {
19064
19064
  const list = Array.isArray(trackViewOfTrackViewList) ? trackViewOfTrackViewList : [ trackViewOfTrackViewList ];
19065
19065
  for (const trackView of list) {
19066
- trackView.dataRange = { min, max };
19066
+ trackView.track.setDataRange({ min, max });
19067
19067
  }
19068
19068
 
19069
19069
  }
@@ -26696,6 +26696,14 @@ class TrackBase {
26696
26696
  return menuItems
26697
26697
  }
26698
26698
 
26699
+ setDataRange({ min, max }) {
26700
+
26701
+ this.dataRange = { min, max };
26702
+ this.autoscale = false;
26703
+ this.autoscaleGroup = undefined;
26704
+ this.trackView.repaintViews();
26705
+ }
26706
+
26699
26707
  /**
26700
26708
  * Return the first feature in this track whose start position is > position
26701
26709
  * @param chr
@@ -27063,7 +27071,7 @@ class Variant {
27063
27071
  this.filter = tokens[6];
27064
27072
  this.info = {};
27065
27073
  const infoStr = tokens[7];
27066
- if (infoStr) {
27074
+ if (infoStr && infoStr !== '.') {
27067
27075
  for (let elem of infoStr.split(';')) {
27068
27076
  var element = elem.split('=');
27069
27077
  this.info[element[0]] = element[1];
@@ -27222,9 +27230,10 @@ class Variant {
27222
27230
  }
27223
27231
  }
27224
27232
 
27225
- if (this.info) {
27233
+ const infoKeys = Object.keys(this.info);
27234
+ if (this.info && infoKeys.length > 0) {
27226
27235
  fields.push({html: '<hr style="border-top: dotted 1px;border-color: #c9c3ba" />'});
27227
- for (let key of Object.keys(this.info)) {
27236
+ for (let key of infoKeys) {
27228
27237
  fields.push({name: key, value: arrayToString(decodeURIComponent(this.info[key]))});
27229
27238
  }
27230
27239
  }
@@ -27291,7 +27300,7 @@ class SVComplement {
27291
27300
  }
27292
27301
 
27293
27302
  getAttributeValue(key) {
27294
- return this.mate.getAttributeValue(key)
27303
+ return this.mate.getAttributeValue(key)
27295
27304
  }
27296
27305
 
27297
27306
  getInfo(tag) {
@@ -27314,7 +27323,7 @@ class SVComplement {
27314
27323
  popupData.push({name: 'Pos', value: `${numberFormatter$1(this.pos)}`});
27315
27324
  popupData.push({html: '<hr style="border-top: dotted 1px;border-color: #c9c3ba" />'});
27316
27325
  popupData.push("SV");
27317
- popupData.push(... this.mate.popupData(genomicLocation, genomeId));
27326
+ popupData.push(...this.mate.popupData(genomicLocation, genomeId));
27318
27327
 
27319
27328
  return popupData
27320
27329
  }
@@ -30735,346 +30744,746 @@ class BaseFeatureSource {
30735
30744
 
30736
30745
  }
30737
30746
 
30738
- const DEFAULT_MAX_WG_COUNT = 10000;
30739
-
30740
- /**
30741
- * feature source for "bed like" files (tab or whitespace delimited files with 1 feature per line: bed, gff, vcf, etc)
30747
+ /*
30748
+ * The MIT License (MIT)
30742
30749
  *
30743
- * @param config
30744
- * @constructor
30750
+ * Copyright (c) 2016 University of California San Diego
30751
+ * Author: Jim Robinson
30752
+ *
30753
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
30754
+ * of this software and associated documentation files (the "Software"), to deal
30755
+ * in the Software without restriction, including without limitation the rights
30756
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30757
+ * copies of the Software, and to permit persons to whom the Software is
30758
+ * furnished to do so, subject to the following conditions:
30759
+ *
30760
+ * The above copyright notice and this permission notice shall be included in
30761
+ * all copies or substantial portions of the Software.
30762
+ *
30763
+ *
30764
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30765
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30766
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30767
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30768
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30769
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30770
+ * THE SOFTWARE.
30745
30771
  */
30746
- class TextFeatureSource extends BaseFeatureSource {
30747
30772
 
30748
- constructor(config, genome) {
30773
+ const GZIP_FLAG = 0x1;
30749
30774
 
30750
- super(genome);
30775
+ class TDFReader {
30751
30776
 
30752
- this.config = config || {};
30777
+ constructor(config, genome) {
30778
+ this.config = config;
30753
30779
  this.genome = genome;
30754
- this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
30755
- this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
30756
- this.windowFunctions = ["mean", "min", "max", "none"];
30780
+ this.path = config.url;
30781
+ this.groupCache = {};
30782
+ this.datasetCache = {};
30783
+ }
30757
30784
 
30758
- const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
30759
30785
 
30760
- this.queryable = config.indexURL || config.queryable === true; // False by default, unless explicitly set
30761
- if (config.reader) {
30762
- // Explicit reader implementation
30763
- this.reader = config.reader;
30764
- this.queryable = config.queryable !== false;
30765
- } else if (config.sourceType === "ga4gh") {
30766
- throw Error("Unsupported source type 'ga4gh'")
30767
- } else if ((config.type === "eqtl" || config.type === "qtl") && config.sourceType === "gtex-ws") {
30768
- this.reader = new GtexReader(config);
30769
- this.queryable = true;
30770
- } else if ("htsget" === config.sourceType) {
30771
- this.reader = new HtsgetVariantReader(config, genome);
30772
- this.queryable = true;
30773
- } else if (config.sourceType === 'ucscservice') {
30774
- this.reader = new UCSCServiceReader(config.source);
30775
- this.queryable = true;
30776
- } else if (config.sourceType === 'custom') {
30777
- this.reader = new CustomServiceReader(config.source);
30778
- this.queryable = false !== config.source.queryable;
30779
- } else if ('service' === config.sourceType) {
30780
- this.reader = new FeatureFileReader(config, genome);
30781
- this.queryable = true;
30782
- } else {
30783
- // File of some type (i.e. not a webservice)
30784
- this.reader = new FeatureFileReader(config, genome);
30785
- if (config.queryable !== undefined) {
30786
- this.queryable = config.queryable;
30787
- } else if (queryableFormats.has(config.format) || this.reader.indexed) {
30788
- this.queryable = true;
30789
- } else ;
30786
+ async readHeader() {
30787
+
30788
+ if (this.magic !== undefined) {
30789
+ return this // Already read
30790
30790
  }
30791
30791
 
30792
- // Flag indicating if features loaded by this source can be searched for by name or attribute, true by default
30793
- this.searchable = config.searchable !== false;
30792
+ let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start: 0, size: 64000}}));
30793
+ let binaryParser = new BinaryParser$1(new DataView(data));
30794
+ this.magic = binaryParser.getInt();
30795
+ this.version = binaryParser.getInt();
30796
+ this.indexPos = binaryParser.getLong();
30797
+ this.indexSize = binaryParser.getInt();
30798
+ binaryParser.getInt();
30794
30799
 
30795
- }
30796
30800
 
30797
- async defaultVisibilityWindow() {
30798
- if (this.reader && typeof this.reader.defaultVisibilityWindow === 'function') {
30799
- return this.reader.defaultVisibilityWindow()
30801
+ if (this.version >= 2) {
30802
+ let nWindowFunctions = binaryParser.getInt();
30803
+ this.windowFunctions = [];
30804
+ while (nWindowFunctions-- > 0) {
30805
+ this.windowFunctions.push(binaryParser.getString());
30806
+ }
30800
30807
  }
30801
- }
30802
30808
 
30803
- async trackType() {
30804
- const header = await this.getHeader();
30805
- if (header) {
30806
- return header.type
30807
- } else {
30808
- return undefined // Convention for unknown or unspecified
30809
- }
30810
- }
30809
+ this.trackType = binaryParser.getString();
30810
+ this.trackLine = binaryParser.getString();
30811
30811
 
30812
- async getHeader() {
30813
- if (!this.header) {
30812
+ let nTracks = binaryParser.getInt();
30813
+ this.trackNames = [];
30814
+ while (nTracks-- > 0) {
30815
+ this.trackNames.push(binaryParser.getString());
30816
+ }
30817
+ this.genomeID = binaryParser.getString();
30818
+ this.flags = binaryParser.getInt();
30819
+ this.compressed = (this.flags & GZIP_FLAG) !== 0;
30814
30820
 
30815
- if (this.reader && typeof this.reader.readHeader === "function") {
30816
- const header = await this.reader.readHeader();
30817
- if (header) {
30818
- this.header = header;
30819
- if (header.format) {
30820
- this.config.format = header.format;
30821
- }
30822
- } else {
30823
- this.header = {};
30824
- }
30825
- } else {
30826
- this.header = {};
30821
+ // Now read index
30822
+ data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30823
+ range: {
30824
+ start: this.indexPos,
30825
+ size: this.indexSize
30827
30826
  }
30827
+ }));
30828
+ binaryParser = new BinaryParser$1(new DataView(data));
30829
+ this.datasetIndex = {};
30830
+ let nEntries = binaryParser.getInt();
30831
+ while (nEntries-- > 0) {
30832
+ const name = binaryParser.getString();
30833
+ const pos = binaryParser.getLong();
30834
+ const size = binaryParser.getInt();
30835
+ this.datasetIndex[name] = {position: pos, size: size};
30828
30836
  }
30829
- return this.header
30837
+
30838
+ this.groupIndex = {};
30839
+ nEntries = binaryParser.getInt();
30840
+ while (nEntries-- > 0) {
30841
+ const name = binaryParser.getString();
30842
+ const pos = binaryParser.getLong();
30843
+ const size = binaryParser.getInt();
30844
+ this.groupIndex[name] = {position: pos, size: size};
30845
+ }
30846
+
30847
+ return this
30830
30848
  }
30831
30849
 
30832
- /**
30833
- * Required function for all data source objects. Fetches features for the
30834
- * range requested.
30835
- *
30836
- * This function is quite complex due to the variety of reader types backing it, some indexed, some queryable,
30837
- * some not.
30838
- *
30839
- * @param chr
30840
- * @param start
30841
- * @param end
30842
- * @param bpPerPixel
30843
- */
30844
- async getFeatures({chr, start, end, bpPerPixel, visibilityWindow}) {
30850
+ async readDataset(chr, windowFunction, zoom) {
30845
30851
 
30846
- const isWholeGenome = ("all" === chr.toLowerCase());
30852
+ const key = chr + "_" + windowFunction + "_" + zoom;
30847
30853
 
30848
- start = start || 0;
30849
- end = end || Number.MAX_SAFE_INTEGER;
30854
+ if (this.datasetCache[key]) {
30855
+ return this.datasetCache[key]
30850
30856
 
30851
- // Various conditions that can require a feature load
30852
- // * view is "whole genome" but no features are loaded
30853
- // * cache is disabled
30854
- // * cache does not contain requested range
30855
- // const containsRange = this.featureCache.containsRange(new GenomicInterval(queryChr, start, end))
30856
- if ((isWholeGenome && !this.wgFeatures && this.supportsWholeGenome()) ||
30857
- this.config.disableCache ||
30858
- !this.featureCache ||
30859
- !this.featureCache.containsRange(new GenomicInterval(chr, start, end))) {
30860
- await this.loadFeatures(chr, start, end, visibilityWindow);
30861
- }
30857
+ } else {
30858
+ await this.readHeader();
30859
+ const wf = (this.version < 2) ? "" : "/" + windowFunction;
30860
+ const zoomString = (chr.toLowerCase() === "all" || zoom === undefined) ? "0" : zoom.toString();
30862
30861
 
30863
- if (isWholeGenome) {
30864
- if (!this.wgFeatures) {
30865
- if (this.supportsWholeGenome()) {
30866
- this.wgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, this.maxWGCount);
30867
- } else {
30868
- this.wgFeatures = [];
30862
+ let dsName;
30863
+ if (windowFunction === "raw") {
30864
+ dsName = "/" + chr + "/raw";
30865
+ } else {
30866
+ dsName = "/" + chr + "/z" + zoomString + wf;
30867
+ }
30868
+ const indexEntry = this.datasetIndex[dsName];
30869
+
30870
+ if (indexEntry === undefined) {
30871
+ return undefined
30872
+ }
30873
+
30874
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30875
+ range: {
30876
+ start: indexEntry.position,
30877
+ size: indexEntry.size
30869
30878
  }
30879
+ }));
30880
+
30881
+ if (!data) {
30882
+ return undefined
30870
30883
  }
30871
- return this.wgFeatures
30872
- } else {
30873
- return this.featureCache.queryFeatures(chr, start, end)
30874
- }
30875
- }
30876
30884
 
30877
- async findFeatures(fn) {
30878
- return this.featureCache ? this.featureCache.findFeatures(fn) : []
30879
- }
30885
+ const binaryParser = new BinaryParser$1(new DataView(data));
30886
+ let nAttributes = binaryParser.getInt();
30887
+ const attributes = {};
30888
+ while (nAttributes-- > 0) {
30889
+ attributes[binaryParser.getString()] = binaryParser.getString();
30890
+ }
30891
+ const dataType = binaryParser.getString();
30892
+ const tileWidth = binaryParser.getFloat();
30893
+ let nTiles = binaryParser.getInt();
30894
+ const tiles = [];
30895
+ while (nTiles-- > 0) {
30896
+ tiles.push({position: binaryParser.getLong(), size: binaryParser.getInt()});
30897
+ }
30880
30898
 
30881
- supportsWholeGenome() {
30882
- return !this.queryable // queryable (indexed, web services) sources don't support whole genome view
30883
- }
30899
+ const dataset = {
30900
+ name: dsName,
30901
+ attributes: attributes,
30902
+ dataType: dataType,
30903
+ tileWidth: tileWidth,
30904
+ tiles: tiles
30905
+ };
30884
30906
 
30885
- // TODO -- experimental, will only work for non-indexed sources
30886
- getAllFeatures() {
30887
- if (this.queryable || !this.featureCache) { // queryable sources don't support all features
30888
- return []
30889
- } else {
30890
- return this.featureCache.getAllFeatures()
30907
+ this.datasetCache[key] = dataset;
30908
+ return dataset
30891
30909
  }
30892
30910
  }
30893
30911
 
30912
+ async readRootGroup() {
30894
30913
 
30895
- async loadFeatures(chr, start, end, visibilityWindow) {
30914
+ const genome = this.genome;
30915
+ const rootGroup = this.groupCache["/"];
30916
+ if (rootGroup) {
30917
+ return rootGroup
30918
+ } else {
30896
30919
 
30897
- await this.getHeader();
30920
+ const group = await this.readGroup("/");
30921
+ const names = group["chromosomes"];
30922
+ const maxZoomString = group["maxZoom"];
30898
30923
 
30899
- const reader = this.reader;
30900
- let intervalStart = start;
30901
- let intervalEnd = end;
30924
+ // Now parse out interesting attributes.
30925
+ if (maxZoomString) {
30926
+ this.maxZoom = Number(maxZoomString);
30927
+ }
30902
30928
 
30903
- // chr aliasing
30904
- let queryChr = chr;
30905
- if (!this.chrAliasManager && this.reader && this.reader.sequenceNames) {
30906
- this.chrAliasManager = new ChromAliasManager(this.reader.sequenceNames, this.genome);
30907
- }
30908
- if (this.chrAliasManager) {
30909
- queryChr = await this.chrAliasManager.getAliasName(chr);
30910
- }
30929
+ const totalCountString = group["totalCount"];
30930
+ if (totalCountString) {
30931
+ group.totalCount = Number(totalCountString);
30932
+ }
30911
30933
 
30912
- // Use visibility window to potentially expand query interval.
30913
- // This can save re-queries as we zoom out. Visibility window <= 0 is a special case
30914
- // indicating whole chromosome should be read at once.
30915
- if ((!visibilityWindow || visibilityWindow <= 0) && this.config.expandQuery !== false) {
30916
- // Whole chromosome
30917
- const chromosome = this.genome ? this.genome.getChromosome(queryChr) : undefined;
30918
- intervalStart = 0;
30919
- intervalEnd = Math.max(chromosome ? chromosome.bpLength : Number.MAX_SAFE_INTEGER, end);
30920
- } else if (visibilityWindow > (end - start) && this.config.expandQuery !== false) {
30921
- let expansionWindow = Math.min(4.1 * (end - start), visibilityWindow);
30922
- if(this.config.minQuerySize && expansionWindow < this.config.minQuerySize) {
30923
- expansionWindow = this.config.minQuerySize;
30934
+ // Chromosome names
30935
+ const chrAliasTable = {};
30936
+ if (names) {
30937
+ names.split(",").forEach(function (chr) {
30938
+ const canonicalName = genome.getChromosomeName(chr);
30939
+ chrAliasTable[canonicalName] = chr;
30940
+ });
30924
30941
  }
30925
- intervalStart = Math.max(0, (start + end - expansionWindow) / 2);
30926
- intervalEnd = intervalStart + expansionWindow;
30927
- }
30942
+ this.chrAliasTable = chrAliasTable;
30928
30943
 
30929
- let features = await reader.readFeatures(queryChr, intervalStart, intervalEnd);
30930
- if (this.queryable === undefined) {
30931
- this.queryable = reader.indexed;
30944
+ this.groupCache["/"] = group;
30945
+ return group
30932
30946
  }
30947
+ }
30933
30948
 
30934
- const genomicInterval = this.queryable ?
30935
- new GenomicInterval(chr, intervalStart, intervalEnd) :
30936
- undefined;
30949
+ async readGroup(name) {
30937
30950
 
30938
- if (features) {
30951
+ const group = this.groupCache[name];
30952
+ if (group) {
30953
+ return group
30954
+ } else {
30939
30955
 
30940
- // Assign overlapping features to rows
30941
- if (this.config.format !== "wig" && this.config.type !== "junctions") {
30942
- const maxRows = this.config.maxRows || Number.MAX_SAFE_INTEGER;
30943
- packFeatures(features, maxRows);
30956
+ await this.readHeader();
30957
+ const indexEntry = this.groupIndex[name];
30958
+ if (indexEntry === undefined) {
30959
+ return undefined
30944
30960
  }
30945
30961
 
30946
- // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
30947
- this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
30962
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
30963
+ range: {
30964
+ start: indexEntry.position,
30965
+ size: indexEntry.size
30966
+ }
30967
+ }));
30948
30968
 
30949
- // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
30950
- if (this.searchable) {
30951
- this.addFeaturesToDB(features, this.config);
30969
+ if (!data) {
30970
+ return undefined
30952
30971
  }
30953
- } else {
30954
- this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
30955
- }
30956
- }
30957
30972
 
30958
- addFeaturesToDB(featureList, config) {
30959
- if (!this.featureMap) {
30960
- this.featureMap = new Map();
30961
- }
30962
- const searchableFields = config.searchableFields || ["name", "transcript_id", "gene_id", "gene_name", "id"];
30963
- for (let feature of featureList) {
30964
- for (let field of searchableFields) {
30965
- let key;
30966
- if (typeof feature.getAttributeValue === 'function') {
30967
- key = feature.getAttributeValue(field);
30968
- }
30969
- if (key) {
30970
- key = key.replaceAll(' ', '+').toUpperCase();
30971
- // If feature is already present keep largest one
30972
- if (this.featureMap.has(key)) {
30973
- const f2 = this.featureMap.get(key);
30974
- if (feature.end - feature.start < f2.end - f2.start) {
30975
- continue
30976
- }
30977
- }
30978
- this.featureMap.set(key, feature);
30979
- }
30973
+ const binaryParser = new BinaryParser$1(new DataView(data));
30974
+ const group = {name: name};
30975
+ let nAttributes = binaryParser.getInt();
30976
+ while (nAttributes-- > 0) {
30977
+ const key = binaryParser.getString();
30978
+ const value = binaryParser.getString();
30979
+ group[key] = value;
30980
30980
  }
30981
+ this.groupCache[name] = group;
30982
+ return group
30981
30983
  }
30982
30984
  }
30983
30985
 
30984
- search(term) {
30985
- if (this.featureMap) {
30986
- return this.featureMap.get(term.toUpperCase())
30987
- }
30988
-
30989
- }
30990
- }
30991
-
30992
- /**
30993
- * A ChromTree parses a UCSC bigbed/bigwig "chromosomeTree" section of the header to produce 2 maps,
30994
- * (1) ID -> chromosome names, and its
30995
- * (2) chromsome name -> ID
30996
- *
30997
- * Both maps are needed by IGV
30998
- *
30999
- * The chromosome tree is a B+ index, but is located continguously in memory in the header section of the file. This
31000
- * makes it feasible to parse the whole tree with data from a single fetch. In the end the tree is discarded
31001
- * leaving only the mapps.
31002
- */
31003
- class ChromTree {
30986
+ async readTiles(tileIndeces, nTracks) {
31004
30987
 
31005
- constructor(header, nameToID, valueToKey, sumLengths) {
31006
- this.header = header;
31007
- this.nameToId = nameToID;
31008
- this.idToName = valueToKey;
31009
- this.sumLengths = sumLengths;
31010
- }
30988
+ tileIndeces.sort(function (a, b) {
30989
+ return a.position - b.position
30990
+ });
31011
30991
 
31012
- static parseTree(binaryParser, startOffset, genome = false) {
31013
- {
31014
- const magic = binaryParser.getInt();
31015
- const blockSize = binaryParser.getInt();
31016
- const keySize = binaryParser.getInt();
31017
- const valSize = binaryParser.getInt();
31018
- const itemCount = binaryParser.getLong();
31019
- const reserved = binaryParser.getLong();
30992
+ tileIndeces = tileIndeces.filter(function (idx) {
30993
+ return idx.size > 0
30994
+ });
31020
30995
 
31021
- const header = {magic, blockSize, keySize, valSize, itemCount, reserved};
31022
- const nameToId = new Map();
31023
- const idToName = [];
31024
- let sumLengths = 0;
31025
- const readTreeNode = (offset) => {
30996
+ if (tileIndeces.length === 0) {
30997
+ return []
30998
+ }
31026
30999
 
31027
- if (offset >= 0) binaryParser.position = offset;
31028
- const type = binaryParser.getByte();
31029
- binaryParser.getByte();
31030
- const count = binaryParser.getUShort();
31000
+ const tiles = [];
31031
31001
 
31032
- if (type === 1) {
31033
- // Leaf node
31034
- for (let i = 0; i < count; i++) {
31035
- let key = binaryParser.getFixedLengthString(keySize);
31036
- let value;
31037
- if (valSize === 8) {
31038
- value = binaryParser.getInt();
31039
- const chromSize = binaryParser.getInt();
31040
- sumLengths += chromSize;
31041
- if (genome) key = genome.getChromosomeName(key); // Translate to canonical chr name
31042
- nameToId.set(key, value);
31043
- idToName[value] = key;
31002
+ for (let indexEntry of tileIndeces) {
31044
31003
 
31045
- } else {
31046
- throw Error(`Unexpected "valSize" value in chromosome tree. Expected 8, actual value is ${valSize}`)
31047
- }
31048
- }
31049
- } else {
31050
- // non-leaf
31051
- for (let i = 0; i < count; i++) {
31052
- binaryParser.getFixedLengthString(keySize);
31053
- const childOffset = binaryParser.getLong();
31054
- const bufferOffset = childOffset - startOffset;
31055
- const currOffset = binaryParser.position;
31056
- readTreeNode(bufferOffset);
31057
- binaryParser.position = currOffset;
31058
- }
31004
+ const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
31005
+ range: {
31006
+ start: indexEntry.position,
31007
+ size: indexEntry.size
31059
31008
  }
31060
- };
31009
+ }));
31061
31010
 
31062
- // Recursively walk tree to populate dictionary
31063
- readTreeNode(binaryParser);
31011
+ let tileData;
31012
+ try {
31013
+ tileData = this.compressed ? inflate_1$3(data).buffer : data;
31014
+ } catch (e) {
31015
+ console.error(e);
31016
+ continue
31017
+ }
31064
31018
 
31065
- return new ChromTree(header, nameToId, idToName, sumLengths)
31019
+ const binaryParser = new BinaryParser$1(new DataView(tileData));
31020
+ const type = binaryParser.getString();
31021
+ let tile;
31022
+ switch (type) {
31023
+ case "fixedStep":
31024
+ tile = createFixedStep(binaryParser, nTracks);
31025
+ break
31026
+ case "variableStep":
31027
+ tile = createVariableStep(binaryParser, nTracks);
31028
+ break
31029
+ case "bed":
31030
+ case "bedWithName":
31031
+ tile = createBed(binaryParser, nTracks, type);
31032
+ break
31033
+ default:
31034
+ throw "Unknown tile type: " + type
31035
+ }
31036
+ tiles.push(tile);
31066
31037
  }
31038
+ return tiles
31067
31039
  }
31068
31040
 
31069
- }
31070
-
31071
- const RPTREE_HEADER_SIZE = 48;
31072
- const RPTREE_NODE_LEAF_ITEM_SIZE = 32; // leaf item size
31073
- const RPTREE_NODE_CHILD_ITEM_SIZE = 24; // child item size
31041
+ async readTile(indexEntry, nTracks) {
31074
31042
 
31075
- class RPTree {
31043
+ let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
31044
+ range: {
31045
+ start: indexEntry.position,
31046
+ size: indexEntry.size
31047
+ }
31048
+ }));
31076
31049
 
31077
- static magic = 610839776
31050
+ if (this.compressed) {
31051
+ const plain = inflate_1$3(data);
31052
+ data = plain.buffer;
31053
+ }
31054
+
31055
+ const binaryParser = new BinaryParser$1(new DataView(data));
31056
+ const type = binaryParser.getString();
31057
+ switch (type) {
31058
+ case "fixedStep":
31059
+ return createFixedStep(binaryParser, nTracks)
31060
+ case "variableStep":
31061
+ return createVariableStep(binaryParser, nTracks)
31062
+ case "bed":
31063
+ case "bedWithName":
31064
+ return createBed(binaryParser, nTracks, type)
31065
+ default:
31066
+ throw "Unknown tile type: " + type
31067
+ }
31068
+ }
31069
+
31070
+ }
31071
+
31072
+ function createFixedStep(binaryParser, nTracks) {
31073
+ const nPositions = binaryParser.getInt();
31074
+ const start = binaryParser.getInt();
31075
+ const span = binaryParser.getFloat();
31076
+
31077
+ const data = [];
31078
+ let nt = nTracks;
31079
+ while (nt-- > 0) {
31080
+ let np = nPositions;
31081
+ const dtrack = [];
31082
+ while (np-- > 0) {
31083
+ dtrack.push(binaryParser.getFloat());
31084
+ }
31085
+ data.push(dtrack);
31086
+ }
31087
+
31088
+ return {
31089
+ type: "fixedStep",
31090
+ start: start,
31091
+ span: span,
31092
+ data: data,
31093
+ nTracks: nTracks,
31094
+ nPositions: nPositions
31095
+ }
31096
+ }
31097
+
31098
+ function createVariableStep(binaryParser, nTracks) {
31099
+
31100
+ const tileStart = binaryParser.getInt();
31101
+ const span = binaryParser.getFloat();
31102
+ const nPositions = binaryParser.getInt();
31103
+ const start = [];
31104
+
31105
+ let np = nPositions;
31106
+ while (np-- > 0) {
31107
+ start.push(binaryParser.getInt());
31108
+ }
31109
+ binaryParser.getInt(); // # of samples, ignored but should === nTracks
31110
+
31111
+ const data = [];
31112
+ let nt = nTracks;
31113
+ while (nt-- > 0) {
31114
+ np = nPositions;
31115
+ const dtrack = [];
31116
+ while (np-- > 0) {
31117
+ dtrack.push(binaryParser.getFloat());
31118
+ }
31119
+ data.push(dtrack);
31120
+ }
31121
+
31122
+ return {
31123
+ type: "variableStep",
31124
+ tileStart: tileStart,
31125
+ span: span,
31126
+ start: start,
31127
+ data: data,
31128
+ nTracks: nTracks,
31129
+ nPositions: nPositions
31130
+ }
31131
+ }
31132
+
31133
+ function createBed(binaryParser, nTracks, type) {
31134
+
31135
+ const nPositions = binaryParser.getInt();
31136
+
31137
+ let n = nPositions;
31138
+ const start = [];
31139
+ while (n-- > 0) {
31140
+ start.push(binaryParser.getInt());
31141
+ }
31142
+
31143
+ n = nPositions;
31144
+ const end = [];
31145
+ while (n-- > 0) {
31146
+ end.push(binaryParser.getInt());
31147
+ }
31148
+
31149
+ binaryParser.getInt(); // # of samples, ignored but should === nTracks
31150
+ const data = [];
31151
+ let nt = nTracks;
31152
+ while (nt-- > 0) {
31153
+ let np = nPositions;
31154
+ const dtrack = [];
31155
+ while (np-- > 0) {
31156
+ dtrack.push(binaryParser.getFloat());
31157
+ }
31158
+ data.push(dtrack);
31159
+ }
31160
+
31161
+ if (type === "bedWithName") {
31162
+ n = nPositions;
31163
+ const name = [];
31164
+ while (n-- > 0) {
31165
+ name.push(binaryParser.getString());
31166
+ }
31167
+ }
31168
+
31169
+ return {
31170
+ type: type,
31171
+ start: start,
31172
+ end: end,
31173
+ data: data,
31174
+ nTracks: nTracks,
31175
+ nPositions: nPositions
31176
+ }
31177
+ }
31178
+
31179
+ /*
31180
+ * The MIT License (MIT)
31181
+ *
31182
+ * Copyright (c) 2016 University of California San Diego
31183
+ * Author: Jim Robinson
31184
+ *
31185
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
31186
+ * of this software and associated documentation files (the "Software"), to deal
31187
+ * in the Software without restriction, including without limitation the rights
31188
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31189
+ * copies of the Software, and to permit persons to whom the Software is
31190
+ * furnished to do so, subject to the following conditions:
31191
+ *
31192
+ * The above copyright notice and this permission notice shall be included in
31193
+ * all copies or substantial portions of the Software.
31194
+ *
31195
+ *
31196
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31197
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31198
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31199
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31200
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31201
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31202
+ * THE SOFTWARE.
31203
+ */
31204
+
31205
+ class TDFSource extends BaseFeatureSource {
31206
+
31207
+ #wgValues = {}
31208
+ searchable = false
31209
+
31210
+
31211
+ constructor(config, genome) {
31212
+ super(genome);
31213
+ this.genome = genome;
31214
+ this.reader = new TDFReader(config, genome);
31215
+ this.queryable = true;
31216
+ }
31217
+
31218
+ async getFeatures({chr, start, end, bpPerPixel, windowFunction = "mean"}) {
31219
+
31220
+ if (chr.toLowerCase() === "all") {
31221
+ return this.getWGValues(windowFunction, bpPerPixel)
31222
+ } else {
31223
+ return this._getFeatures(chr, start, end, bpPerPixel, windowFunction)
31224
+ }
31225
+ }
31226
+ async _getFeatures(chr, start, end, bpPerPixel, windowFunction) {
31227
+ const genomicInterval = new GenomicInterval(chr, start, end);
31228
+ const genome = this.genome;
31229
+
31230
+
31231
+ if (!this.rootGroup) {
31232
+ this.rootGroup = await this.reader.readRootGroup();
31233
+ if (!this.normalizationFactor) {
31234
+ const totalCount = this.rootGroup.totalCount;
31235
+ if (totalCount) {
31236
+ this.normalizationFactor = 1.0e6 / totalCount;
31237
+ }
31238
+ }
31239
+ }
31240
+
31241
+ genomicInterval.bpPerPixel = bpPerPixel;
31242
+ const zoom = zoomLevelForScale$1(chr, bpPerPixel, genome);
31243
+ let queryChr = this.reader.chrAliasTable[chr];
31244
+ let maxZoom = this.reader.maxZoom;
31245
+ if (queryChr === undefined) queryChr = chr;
31246
+ if (maxZoom === undefined) maxZoom = -1;
31247
+
31248
+ const wf = zoom > maxZoom ? "raw" : windowFunction;
31249
+ const dataset = await this.reader.readDataset(queryChr, wf, zoom);
31250
+ if (dataset == null) {
31251
+ return []
31252
+ }
31253
+
31254
+ const tileWidth = dataset.tileWidth;
31255
+ const startTile = Math.floor(start / tileWidth);
31256
+ const endTile = Math.floor(end / tileWidth);
31257
+ const NTRACKS = 1; // TODO read this
31258
+ const tiles = await this.reader.readTiles(dataset.tiles.slice(startTile, endTile + 1), NTRACKS);
31259
+ const features = [];
31260
+ for (let tile of tiles) {
31261
+ switch (tile.type) {
31262
+ case "bed":
31263
+ decodeBedTile(tile, chr, start, end, bpPerPixel, features);
31264
+ break
31265
+ case "variableStep":
31266
+ decodeVaryTile(tile, chr, start, end, bpPerPixel, features);
31267
+ break
31268
+ case "fixedStep":
31269
+ decodeFixedTile(tile, chr, start, end, bpPerPixel, features);
31270
+ break
31271
+ default:
31272
+ throw ("Unknown tile type: " + tile.type)
31273
+ }
31274
+ }
31275
+ features.sort(function (a, b) {
31276
+ return a.start - b.start
31277
+ });
31278
+
31279
+ return features
31280
+ }
31281
+
31282
+ get supportsWholeGenome() {
31283
+ return true
31284
+ }
31285
+
31286
+ get windowFunctions() {
31287
+ return this.reader.windowFunctions
31288
+ }
31289
+
31290
+ async getWGValues(windowFunction, bpPerPixel) {
31291
+
31292
+ const cached = this.#wgValues[windowFunction];
31293
+ if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
31294
+ return cached.values
31295
+ } else {
31296
+ const wgFeatures = [];
31297
+ const genome = this.genome;
31298
+ const chrNames = this.genome.wgChromosomeNames;
31299
+ if (chrNames) {
31300
+ for (let c of genome.wgChromosomeNames) {
31301
+ const len = genome.getChromosome(c).bpLength;
31302
+ bpPerPixel = len / 1000;
31303
+ const chrFeatures = await this._getFeatures(c, 0, len, bpPerPixel, windowFunction);
31304
+ if (chrFeatures) {
31305
+ for (let f of chrFeatures) {
31306
+ const wg = Object.assign({}, f);
31307
+ wg.chr = "all";
31308
+ wg.start = genome.getGenomeCoordinate(f.chr, f.start);
31309
+ wg.end = genome.getGenomeCoordinate(f.chr, f.end);
31310
+ wg._f = f;
31311
+ wgFeatures.push(wg);
31312
+ }
31313
+ }
31314
+ }
31315
+ }
31316
+ this.#wgValues[windowFunction] = {values: wgFeatures, bpPerPixel};
31317
+ return wgFeatures
31318
+ }
31319
+ }
31320
+
31321
+ }
31322
+
31323
+ function decodeBedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31324
+
31325
+ const nPositions = tile.nPositions;
31326
+ const starts = tile.start;
31327
+ const ends = tile.end;
31328
+ const data = tile.data[0]; // Single track for now
31329
+ for (let i = 0; i < nPositions; i++) {
31330
+ const s = starts[i];
31331
+ const e = ends[i];
31332
+ if (e < bpStart) continue
31333
+ if (s > bpEnd) break
31334
+ features.push({
31335
+ chr: chr,
31336
+ start: s,
31337
+ end: e,
31338
+ value: data[i]
31339
+ });
31340
+ }
31341
+ }
31342
+
31343
+ function decodeVaryTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31344
+
31345
+ const nPositions = tile.nPositions;
31346
+ const starts = tile.start;
31347
+ const span = tile.span;
31348
+ const data = tile.data[0]; // Single track for now
31349
+ for (let i = 0; i < nPositions; i++) {
31350
+ const s = starts[i];
31351
+ const e = s + span;
31352
+ if (e < bpStart) continue
31353
+ if (s > bpEnd) break
31354
+ features.push({
31355
+ chr: chr,
31356
+ start: s,
31357
+ end: e,
31358
+ value: data[i]
31359
+ });
31360
+ }
31361
+ }
31362
+
31363
+ function decodeFixedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
31364
+
31365
+ const nPositions = tile.nPositions;
31366
+ let s = tile.start;
31367
+ const span = tile.span;
31368
+ const data = tile.data[0]; // Single track for now
31369
+
31370
+ for (let i = 0; i < nPositions; i++) {
31371
+ const e = s + span;
31372
+ if (s > bpEnd) break
31373
+ if (e >= bpStart) {
31374
+ if (!Number.isNaN(data[i])) {
31375
+ features.push({
31376
+ chr: chr,
31377
+ start: s,
31378
+ end: e,
31379
+ value: data[i]
31380
+ });
31381
+ }
31382
+ }
31383
+ s = e;
31384
+ }
31385
+ }
31386
+
31387
+
31388
+ var log2 = Math.log(2);
31389
+
31390
+ function zoomLevelForScale$1(chr, bpPerPixel, genome) {
31391
+
31392
+ // Convert bpPerPixel to IGV "zoom" level. This is a bit convoluted, TDF is computed zoom levels assuming
31393
+ // display in a 700 pixel window. The fully zoomed out view of a chromosome is zoom level "0".
31394
+ // Zoom level 1 is magnified 2X, and so forth
31395
+
31396
+ var chrSize = genome.getChromosome(chr).bpLength;
31397
+
31398
+ return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
31399
+ }
31400
+
31401
+ /**
31402
+ * A ChromTree parses a UCSC bigbed/bigwig "chromosomeTree" section of the header to produce 2 maps,
31403
+ * (1) ID -> chromosome names, and its
31404
+ * (2) chromsome name -> ID
31405
+ *
31406
+ * Both maps are needed by IGV
31407
+ *
31408
+ * The chromosome tree is a B+ index, but is located continguously in memory in the header section of the file. This
31409
+ * makes it feasible to parse the whole tree with data from a single fetch. In the end the tree is discarded
31410
+ * leaving only the mapps.
31411
+ */
31412
+ class ChromTree {
31413
+
31414
+ constructor(header, nameToID, valueToKey, sumLengths) {
31415
+ this.header = header;
31416
+ this.nameToId = nameToID;
31417
+ this.idToName = valueToKey;
31418
+ this.sumLengths = sumLengths;
31419
+ }
31420
+
31421
+ static parseTree(binaryParser, startOffset, genome = false) {
31422
+ {
31423
+ const magic = binaryParser.getInt();
31424
+ const blockSize = binaryParser.getInt();
31425
+ const keySize = binaryParser.getInt();
31426
+ const valSize = binaryParser.getInt();
31427
+ const itemCount = binaryParser.getLong();
31428
+ const reserved = binaryParser.getLong();
31429
+
31430
+ const header = {magic, blockSize, keySize, valSize, itemCount, reserved};
31431
+ const nameToId = new Map();
31432
+ const idToName = [];
31433
+ let sumLengths = 0;
31434
+ const readTreeNode = (offset) => {
31435
+
31436
+ if (offset >= 0) binaryParser.position = offset;
31437
+ const type = binaryParser.getByte();
31438
+ binaryParser.getByte();
31439
+ const count = binaryParser.getUShort();
31440
+
31441
+ if (type === 1) {
31442
+ // Leaf node
31443
+ for (let i = 0; i < count; i++) {
31444
+ let key = binaryParser.getFixedLengthString(keySize);
31445
+ let value;
31446
+ if (valSize === 8) {
31447
+ value = binaryParser.getInt();
31448
+ const chromSize = binaryParser.getInt();
31449
+ sumLengths += chromSize;
31450
+ if (genome) key = genome.getChromosomeName(key); // Translate to canonical chr name
31451
+ nameToId.set(key, value);
31452
+ idToName[value] = key;
31453
+
31454
+ } else {
31455
+ throw Error(`Unexpected "valSize" value in chromosome tree. Expected 8, actual value is ${valSize}`)
31456
+ }
31457
+ }
31458
+ } else {
31459
+ // non-leaf
31460
+ for (let i = 0; i < count; i++) {
31461
+ binaryParser.getFixedLengthString(keySize);
31462
+ const childOffset = binaryParser.getLong();
31463
+ const bufferOffset = childOffset - startOffset;
31464
+ const currOffset = binaryParser.position;
31465
+ readTreeNode(bufferOffset);
31466
+ binaryParser.position = currOffset;
31467
+ }
31468
+ }
31469
+ };
31470
+
31471
+ // Recursively walk tree to populate dictionary
31472
+ readTreeNode(binaryParser);
31473
+
31474
+ return new ChromTree(header, nameToId, idToName, sumLengths)
31475
+ }
31476
+ }
31477
+
31478
+ }
31479
+
31480
+ const RPTREE_HEADER_SIZE = 48;
31481
+ const RPTREE_NODE_LEAF_ITEM_SIZE = 32; // leaf item size
31482
+ const RPTREE_NODE_CHILD_ITEM_SIZE = 24; // child item size
31483
+
31484
+ class RPTree {
31485
+
31486
+ static magic = 610839776
31078
31487
  littleEndian = true
31079
31488
  nodeCache = new Map()
31080
31489
 
@@ -31210,7 +31619,7 @@ function overlaps(item, chrIdx1, startBase, chrIdx2, endBase) {
31210
31619
 
31211
31620
  function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
31212
31621
 
31213
- if ("biginteract" === format || (autoSql && ('chromatinInteract' === autoSql.table) || 'interact' === autoSql.table)) {
31622
+ if ("biginteract" === format || (autoSql && ('chromatinInteract' === autoSql.table || 'interact' === autoSql.table))) {
31214
31623
  return decodeInteract
31215
31624
  } else {
31216
31625
  const standardFieldCount = definedFieldCount - 3;
@@ -31560,7 +31969,7 @@ class BWReader {
31560
31969
  if (this.type === "bigwig") {
31561
31970
  // Select a biwig "zoom level" appropriate for the current resolution.
31562
31971
  const zoomLevelHeaders = await this.getZoomHeaders();
31563
- let zoomLevelHeader = bpPerPixel ? zoomLevelForScale$1(bpPerPixel, zoomLevelHeaders) : undefined;
31972
+ let zoomLevelHeader = bpPerPixel ? zoomLevelForScale(bpPerPixel, zoomLevelHeaders) : undefined;
31564
31973
  if (zoomLevelHeader) {
31565
31974
  treeOffset = zoomLevelHeader.indexOffset;
31566
31975
  decodeFunction = decodeZoomData;
@@ -32016,7 +32425,7 @@ function computeStats() {
32016
32425
  }
32017
32426
  }
32018
32427
 
32019
- function zoomLevelForScale$1(bpPerPixel, zoomLevelHeaders) {
32428
+ function zoomLevelForScale(bpPerPixel, zoomLevelHeaders) {
32020
32429
  let level;
32021
32430
  for (let i = 0; i < zoomLevelHeaders.length; i++) {
32022
32431
  const zl = zoomLevelHeaders[i];
@@ -32204,7 +32613,7 @@ class DataBuffer {
32204
32613
  class BWSource extends BaseFeatureSource {
32205
32614
 
32206
32615
  queryable = true
32207
- wgValues = {}
32616
+ #wgValues = {}
32208
32617
  windowFunctions = ["mean", "min", "max"]
32209
32618
 
32210
32619
  constructor(config, genome) {
@@ -32216,12 +32625,15 @@ class BWSource extends BaseFeatureSource {
32216
32625
 
32217
32626
  async getFeatures({chr, start, end, bpPerPixel, windowFunction}) {
32218
32627
 
32219
- await this.reader.loadHeader();
32628
+ await this.reader.loadHeader();
32220
32629
  const isBigWig = this.reader.type === "bigwig";
32221
32630
 
32222
- const features = (chr.toLowerCase() === "all") ?
32223
- (isBigWig ? await this.getWGValues(windowFunction) : []) :
32224
- await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
32631
+ let features;
32632
+ if ("all" === chr.toLowerCase()) {
32633
+ features = isBigWig ? await this.getWGValues(windowFunction, bpPerPixel) : [];
32634
+ } else {
32635
+ features = await this.reader.readFeatures(chr, start, chr, end, bpPerPixel, windowFunction);
32636
+ }
32225
32637
 
32226
32638
  if (!isBigWig) {
32227
32639
  pack(features);
@@ -32237,26 +32649,25 @@ class BWSource extends BaseFeatureSource {
32237
32649
  if (this.reader.type === "bigwig") {
32238
32650
  return -1
32239
32651
  } else {
32240
- return this.reader.featureDensity ? Math.floor(10000 / this.reader.featureDensity) : -1
32652
+ return this.reader.featureDensity ? Math.floor(10000 / this.reader.featureDensity) : -1
32241
32653
  }
32242
32654
 
32243
32655
  }
32244
32656
 
32245
- async getWGValues(windowFunction) {
32657
+ async getWGValues(windowFunction, bpPerPixel) {
32246
32658
 
32247
- const numberOfBins = 1000; // This doesn't need to be precise
32248
32659
  const genome = this.genome;
32249
-
32250
- if (this.wgValues[windowFunction]) {
32251
- return this.wgValues[windowFunction]
32660
+ const cached = this.#wgValues[windowFunction];
32661
+ if (cached && cached.bpPerPixel > 0.8 * bpPerPixel && cached.bpPerPixel < 1.2 * bpPerPixel) {
32662
+ return cached.values
32252
32663
  } else {
32253
32664
 
32254
- const bpPerPixel = genome.getGenomeLength() / numberOfBins;
32255
32665
  const features = await this.reader.readWGFeatures(bpPerPixel, windowFunction);
32256
32666
  let wgValues = [];
32257
32667
  for (let f of features) {
32258
32668
  const chr = f.chr;
32259
32669
  const offset = genome.getCumulativeOffset(chr);
32670
+ if (undefined === offset) continue
32260
32671
  const wgFeature = Object.assign({}, f);
32261
32672
  wgFeature.chr = "all";
32262
32673
  wgFeature.start = offset + f.start;
@@ -32265,7 +32676,7 @@ class BWSource extends BaseFeatureSource {
32265
32676
  wgValues.push(wgFeature);
32266
32677
  }
32267
32678
  wgValues.sort((a, b) => a.start - b.start);
32268
- this.wgValues[windowFunction] = wgValues;
32679
+ this.#wgValues[windowFunction] = {values: wgValues, bpPerPixel};
32269
32680
  return wgValues
32270
32681
  }
32271
32682
  }
@@ -32287,658 +32698,780 @@ class BWSource extends BaseFeatureSource {
32287
32698
  }
32288
32699
  }
32289
32700
 
32290
- /*
32291
- * The MIT License (MIT)
32292
- *
32293
- * Copyright (c) 2016 University of California San Diego
32294
- * Author: Jim Robinson
32295
- *
32296
- * Permission is hereby granted, free of charge, to any person obtaining a copy
32297
- * of this software and associated documentation files (the "Software"), to deal
32298
- * in the Software without restriction, including without limitation the rights
32299
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32300
- * copies of the Software, and to permit persons to whom the Software is
32301
- * furnished to do so, subject to the following conditions:
32302
- *
32303
- * The above copyright notice and this permission notice shall be included in
32304
- * all copies or substantial portions of the Software.
32305
- *
32306
- *
32307
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32308
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32309
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32310
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32311
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32312
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32313
- * THE SOFTWARE.
32314
- */
32315
-
32316
- const GZIP_FLAG = 0x1;
32701
+ const shim = .01;
32702
+ const colorStripWidth = 4;
32703
+ const axesXOffset = colorStripWidth + 1;
32704
+ function paintAxis(ctx, width, height, colorOrUndefined) {
32317
32705
 
32318
- class TDFReader {
32706
+ if (undefined === this.dataRange || undefined === this.dataRange.max || undefined === this.dataRange.min) {
32707
+ return
32708
+ }
32319
32709
 
32320
- constructor(config, genome) {
32321
- this.config = config;
32322
- this.genome = genome;
32323
- this.path = config.url;
32324
- this.groupCache = {};
32325
- this.datasetCache = {};
32710
+ IGVGraphics.fillRect(ctx, 0, 0, width, height, { fillStyle: 'white' });
32711
+ if (colorOrUndefined) {
32712
+ IGVGraphics.fillRect(ctx, width - colorStripWidth - 2, 0, colorStripWidth, height, { fillStyle: colorOrUndefined });
32326
32713
  }
32327
32714
 
32715
+ const flipAxis = (undefined === this.flipAxis) ? false : this.flipAxis;
32328
32716
 
32329
- async readHeader() {
32717
+ const xTickStart = 0.95 * width - 8 - axesXOffset;
32718
+ const xTickEnd = 0.95 * width - axesXOffset;
32330
32719
 
32331
- if (this.magic !== undefined) {
32332
- return this // Already read
32333
- }
32720
+ const properties =
32721
+ {
32722
+ font: 'normal 10px Arial',
32723
+ textAlign: 'right',
32724
+ fillStyle: 'black',
32725
+ strokeStyle: 'black',
32726
+ };
32334
32727
 
32335
- let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {range: {start: 0, size: 64000}}));
32336
- let binaryParser = new BinaryParser$1(new DataView(data));
32337
- this.magic = binaryParser.getInt();
32338
- this.version = binaryParser.getInt();
32339
- this.indexPos = binaryParser.getLong();
32340
- this.indexSize = binaryParser.getInt();
32341
- binaryParser.getInt();
32728
+ // tick
32729
+ IGVGraphics.strokeLine(ctx, xTickStart, shim * height, xTickEnd, shim * height, properties);
32730
+ IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.min : this.dataRange.max), xTickStart + 4, shim * height + 12, properties);
32342
32731
 
32732
+ const y = (1.0 - shim) * height;
32343
32733
 
32344
- if (this.version >= 2) {
32345
- let nWindowFunctions = binaryParser.getInt();
32346
- this.windowFunctions = [];
32347
- while (nWindowFunctions-- > 0) {
32348
- this.windowFunctions.push(binaryParser.getString());
32349
- }
32350
- }
32734
+ // tick
32735
+ IGVGraphics.strokeLine(ctx, xTickStart, y, xTickEnd, y, properties);
32736
+ IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.max : this.dataRange.min), xTickStart + 4, y - 4, properties);
32351
32737
 
32352
- this.trackType = binaryParser.getString();
32353
- this.trackLine = binaryParser.getString();
32738
+ // vertical axis
32739
+ IGVGraphics.strokeLine(ctx, xTickEnd, shim * height, xTickEnd, y, properties);
32354
32740
 
32355
- let nTracks = binaryParser.getInt();
32356
- this.trackNames = [];
32357
- while (nTracks-- > 0) {
32358
- this.trackNames.push(binaryParser.getString());
32359
- }
32360
- this.genomeID = binaryParser.getString();
32361
- this.flags = binaryParser.getInt();
32362
- this.compressed = (this.flags & GZIP_FLAG) !== 0;
32741
+ function prettyPrint(number) {
32363
32742
 
32364
- // Now read index
32365
- data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32366
- range: {
32367
- start: this.indexPos,
32368
- size: this.indexSize
32369
- }
32370
- }));
32371
- binaryParser = new BinaryParser$1(new DataView(data));
32372
- this.datasetIndex = {};
32373
- let nEntries = binaryParser.getInt();
32374
- while (nEntries-- > 0) {
32375
- const name = binaryParser.getString();
32376
- const pos = binaryParser.getLong();
32377
- const size = binaryParser.getInt();
32378
- this.datasetIndex[name] = {position: pos, size: size};
32743
+ if (number === 0) {
32744
+ return "0"
32745
+ } else if (Math.abs(number) >= 10) {
32746
+ return number.toFixed()
32747
+ } else if (Math.abs(number) >= 1) {
32748
+ return number.toFixed(1)
32749
+ } else if (Math.abs(number) >= 0.1) {
32750
+ return number.toFixed(2)
32751
+ } else {
32752
+ return number.toExponential(1)
32379
32753
  }
32754
+ }
32755
+ }
32380
32756
 
32381
- this.groupIndex = {};
32382
- nEntries = binaryParser.getInt();
32383
- while (nEntries-- > 0) {
32384
- const name = binaryParser.getString();
32385
- const pos = binaryParser.getLong();
32386
- const size = binaryParser.getInt();
32387
- this.groupIndex[name] = {position: pos, size: size};
32388
- }
32757
+ const DEFAULT_COLOR$2 = 'rgb(150, 150, 150)';
32389
32758
 
32390
- return this
32759
+
32760
+ class WigTrack extends TrackBase {
32761
+
32762
+ static defaults = {
32763
+ height: 50,
32764
+ flipAxis: false,
32765
+ logScale: false,
32766
+ windowFunction: 'mean',
32767
+ graphType: 'bar',
32768
+ normalize: undefined,
32769
+ scaleFactor: undefined,
32770
+ overflowColor: `rgb(255, 32, 255)`,
32771
+ baselineColor: 'lightGray',
32772
+ summarize: true
32391
32773
  }
32392
32774
 
32393
- async readDataset(chr, windowFunction, zoom) {
32775
+ constructor(config, browser) {
32776
+ super(config, browser);
32777
+ }
32394
32778
 
32395
- const key = chr + "_" + windowFunction + "_" + zoom;
32779
+ init(config) {
32396
32780
 
32397
- if (this.datasetCache[key]) {
32398
- return this.datasetCache[key]
32781
+ super.init(config);
32782
+
32783
+ this.type = "wig";
32784
+ this.featureType = 'numeric';
32785
+ this.resolutionAware = true;
32786
+ this.paintAxis = paintAxis;
32399
32787
 
32788
+ const format = config.format ? config.format.toLowerCase() : config.format;
32789
+ if (config.featureSource) {
32790
+ this.featureSource = config.featureSource;
32791
+ delete config.featureSource;
32792
+ } else if ("bigwig" === format) {
32793
+ this.featureSource = new BWSource(config, this.browser.genome);
32794
+ } else if ("tdf" === format) {
32795
+ this.featureSource = new TDFSource(config, this.browser.genome);
32400
32796
  } else {
32401
- await this.readHeader();
32402
- const wf = (this.version < 2) ? "" : "/" + windowFunction;
32403
- const zoomString = (chr.toLowerCase() === "all" || zoom === undefined) ? "0" : zoom.toString();
32797
+ this.featureSource = FeatureSource(config, this.browser.genome);
32798
+ }
32404
32799
 
32405
- let dsName;
32406
- if (windowFunction === "raw") {
32407
- dsName = "/" + chr + "/raw";
32408
- } else {
32409
- dsName = "/" + chr + "/z" + zoomString + wf;
32410
- }
32411
- const indexEntry = this.datasetIndex[dsName];
32412
32800
 
32413
- if (indexEntry === undefined) {
32414
- return undefined
32415
- }
32801
+ // Override autoscale default
32802
+ if (config.max === undefined || config.autoscale === true) {
32803
+ this.autoscale = true;
32804
+ } else {
32805
+ this.dataRange = {
32806
+ min: config.min || 0,
32807
+ max: config.max
32808
+ };
32809
+ }
32810
+ }
32416
32811
 
32417
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32418
- range: {
32419
- start: indexEntry.position,
32420
- size: indexEntry.size
32421
- }
32422
- }));
32812
+ async postInit() {
32813
+ const header = await this.getHeader();
32814
+ if (this.disposed) return // This track was removed during async load
32815
+ if (header) this.setTrackProperties(header);
32816
+ }
32423
32817
 
32424
- if (!data) {
32425
- return undefined
32426
- }
32818
+ async getFeatures(chr, start, end, bpPerPixel) {
32427
32819
 
32428
- const binaryParser = new BinaryParser$1(new DataView(data));
32429
- let nAttributes = binaryParser.getInt();
32430
- const attributes = {};
32431
- while (nAttributes-- > 0) {
32432
- attributes[binaryParser.getString()] = binaryParser.getString();
32820
+ const windowFunction = this.windowFunction;
32821
+
32822
+ const features = await this.featureSource.getFeatures({
32823
+ chr,
32824
+ start,
32825
+ end,
32826
+ bpPerPixel,
32827
+ visibilityWindow: this.visibilityWindow,
32828
+ windowFunction
32829
+ });
32830
+ if (this.normalize && this.featureSource.normalizationFactor) {
32831
+ const scaleFactor = this.featureSource.normalizationFactor;
32832
+ for (let f of features) {
32833
+ f.value *= scaleFactor;
32433
32834
  }
32434
- const dataType = binaryParser.getString();
32435
- const tileWidth = binaryParser.getFloat();
32436
- let nTiles = binaryParser.getInt();
32437
- const tiles = [];
32438
- while (nTiles-- > 0) {
32439
- tiles.push({position: binaryParser.getLong(), size: binaryParser.getInt()});
32835
+ }
32836
+ if (this.scaleFactor) {
32837
+ const scaleFactor = this.scaleFactor;
32838
+ for (let f of features) {
32839
+ f.value *= scaleFactor;
32440
32840
  }
32841
+ }
32441
32842
 
32442
- const dataset = {
32443
- name: dsName,
32444
- attributes: attributes,
32445
- dataType: dataType,
32446
- tileWidth: tileWidth,
32447
- tiles: tiles
32448
- };
32843
+ // Summarize features to current resolution. This needs to be done here, rather than in the "draw" function,
32844
+ // for group autoscale to work.
32845
+ if (this.summarize && ("mean" === windowFunction || "min" === windowFunction || "max" === windowFunction)) {
32846
+ return summarizeData(features, start, bpPerPixel, windowFunction)
32847
+ } else {
32848
+ return features
32849
+ }
32850
+ }
32449
32851
 
32450
- this.datasetCache[key] = dataset;
32451
- return dataset
32852
+ menuItemList() {
32853
+ const items = [];
32854
+
32855
+ if (this.flipAxis !== undefined) {
32856
+ items.push('<hr>');
32857
+
32858
+ function click() {
32859
+ this.flipAxis = !this.flipAxis;
32860
+ this.trackView.repaintViews();
32861
+ }
32862
+
32863
+ items.push({label: 'Flip y-axis', click});
32452
32864
  }
32865
+
32866
+ if(this.featureSource.windowFunctions) {
32867
+ items.push(...this.wigSummarizationItems());
32868
+ }
32869
+
32870
+ items.push(...this.numericDataMenuItems());
32871
+
32872
+ return items
32453
32873
  }
32454
32874
 
32455
- async readRootGroup() {
32875
+ wigSummarizationItems() {
32456
32876
 
32457
- const genome = this.genome;
32458
- const rootGroup = this.groupCache["/"];
32459
- if (rootGroup) {
32460
- return rootGroup
32461
- } else {
32877
+ const windowFunctions = this.featureSource.windowFunctions;
32462
32878
 
32463
- const group = await this.readGroup("/");
32464
- const names = group["chromosomes"];
32465
- const maxZoomString = group["maxZoom"];
32879
+ const menuItems = [];
32880
+ menuItems.push('<hr/>');
32881
+ menuItems.push("<div>Windowing function</div>");
32882
+ for (const wf of windowFunctions) {
32883
+ const object = $$1(createCheckbox(wf, this.windowFunction === wf));
32466
32884
 
32467
- // Now parse out interesting attributes.
32468
- if (maxZoomString) {
32469
- this.maxZoom = Number(maxZoomString);
32885
+ function clickHandler() {
32886
+ this.windowFunction = wf;
32887
+ this.trackView.updateViews();
32470
32888
  }
32471
32889
 
32472
- const totalCountString = group["totalCount"];
32473
- if (totalCountString) {
32474
- group.totalCount = Number(totalCountString);
32475
- }
32890
+ menuItems.push({object, click: clickHandler});
32891
+ }
32476
32892
 
32477
- // Chromosome names
32478
- const chrAliasTable = {};
32479
- if (names) {
32480
- names.split(",").forEach(function (chr) {
32481
- const canonicalName = genome.getChromosomeName(chr);
32482
- chrAliasTable[canonicalName] = chr;
32483
- });
32484
- }
32485
- this.chrAliasTable = chrAliasTable;
32893
+ return menuItems
32894
+ }
32486
32895
 
32487
- this.groupCache["/"] = group;
32488
- return group
32896
+
32897
+ async getHeader() {
32898
+
32899
+ if (typeof this.featureSource.getHeader === "function") {
32900
+ this.header = await this.featureSource.getHeader();
32489
32901
  }
32902
+ return this.header
32490
32903
  }
32491
32904
 
32492
- async readGroup(name) {
32905
+ // TODO: refactor to igvUtils.js
32906
+ getScaleFactor(min, max, height, logScale) {
32907
+ const minValue = (logScale === true) ? ((min < 0) ? -Math.log10(Math.abs(min) + 1) : Math.log10(Math.abs(min) + 1)) : min;
32908
+ const maxValue = (logScale === true) ? Math.log10(Math.abs(max) + 1) : max;
32909
+ const scale = height / (maxValue - minValue);
32910
+ return scale
32911
+ }
32493
32912
 
32494
- const group = this.groupCache[name];
32495
- if (group) {
32496
- return group
32497
- } else {
32913
+ computeYPixelValue(yValue, yScaleFactor) {
32914
+ return (this.flipAxis ? (yValue - this.dataRange.min) : (this.dataRange.max - yValue)) * yScaleFactor
32915
+ }
32498
32916
 
32499
- await this.readHeader();
32500
- const indexEntry = this.groupIndex[name];
32501
- if (indexEntry === undefined) {
32502
- return undefined
32503
- }
32917
+ computeYPixelValueInLogScale(yValue, yScaleFactor) {
32918
+ let maxValue = this.dataRange.max;
32919
+ let minValue = this.dataRange.min;
32920
+ minValue = (minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1);
32921
+ maxValue = (maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1);
32922
+
32923
+ yValue = (yValue < 0) ? -Math.log10(Math.abs(yValue) +1) : Math.log10(yValue + 1);
32924
+ return ((this.flipAxis ? (yValue - minValue) : (maxValue - yValue)) * yScaleFactor)
32925
+ }
32504
32926
 
32505
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32506
- range: {
32507
- start: indexEntry.position,
32508
- size: indexEntry.size
32927
+ draw(options) {
32928
+
32929
+ const features = options.features;
32930
+ const ctx = options.context;
32931
+ const bpPerPixel = options.bpPerPixel;
32932
+ const bpStart = options.bpStart;
32933
+ const pixelWidth = options.pixelWidth;
32934
+ const pixelHeight = options.pixelHeight - 1;
32935
+ const bpEnd = bpStart + pixelWidth * bpPerPixel + 1;
32936
+
32937
+ const scaleFactor = this.getScaleFactor(this.dataRange.min, this.dataRange.max, pixelHeight, this.logScale);
32938
+ const yScale = (yValue) => this.logScale
32939
+ ? this.computeYPixelValueInLogScale(yValue, scaleFactor)
32940
+ : this.computeYPixelValue(yValue, scaleFactor);
32941
+
32942
+ if (features && features.length > 0) {
32943
+
32944
+ if (this.dataRange.min === undefined) this.dataRange.min = 0;
32945
+
32946
+ // Max can be less than min if config.min is set but max left to autoscale. If that's the case there is
32947
+ // nothing to paint.
32948
+ if (this.dataRange.max > this.dataRange.min) {
32949
+
32950
+ let lastPixelEnd = -1;
32951
+ let lastY;
32952
+ const y0 = yScale(0);
32953
+
32954
+ for (let f of features) {
32955
+
32956
+ if (f.end < bpStart) continue
32957
+ if (f.start > bpEnd) break
32958
+
32959
+ const x = (f.start - bpStart) / bpPerPixel;
32960
+ if (isNaN(x)) continue
32961
+
32962
+ let y = yScale(f.value);
32963
+
32964
+ const rectEnd = (f.end - bpStart) / bpPerPixel;
32965
+ const width = rectEnd - x;
32966
+
32967
+ const color = options.alpha ? IGVColor.addAlpha(this.getColorForFeature(f), options.alpha) : this.getColorForFeature(f);
32968
+
32969
+ if (this.graphType === "line") {
32970
+ if (lastY !== undefined) {
32971
+ IGVGraphics.strokeLine(ctx, lastPixelEnd, lastY, x, y, {
32972
+ "fillStyle": color,
32973
+ "strokeStyle": color
32974
+ });
32975
+ }
32976
+ IGVGraphics.strokeLine(ctx, x, y, x + width, y, {"fillStyle": color, "strokeStyle": color});
32977
+ } else if (this.graphType === "points") {
32978
+ const pointSize = this.config.pointSize || 3;
32979
+ const px = x + width / 2;
32980
+ IGVGraphics.fillCircle(ctx, px, y, pointSize / 2, {"fillStyle": color, "strokeStyle": color});
32981
+
32982
+ if (f.value > this.dataRange.max) {
32983
+ IGVGraphics.fillCircle(ctx, px, pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
32984
+ } else if (f.value < this.dataRange.min) {
32985
+ IGVGraphics.fillCircle(ctx, px, pixelHeight - pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
32986
+ }
32987
+
32988
+ } else {
32989
+ // Default graph type (bar)
32990
+ const height = Math.min(pixelHeight, y - y0);
32991
+ IGVGraphics.fillRect(ctx, x, y0, width, height, {fillStyle: color});
32992
+ if (f.value > this.dataRange.max) {
32993
+ IGVGraphics.fillRect(ctx, x, 0, width, 3, {fillStyle: this.overflowColor});
32994
+ } else if (f.value < this.dataRange.min) {
32995
+ IGVGraphics.fillRect(ctx, x, pixelHeight - 2, width, 3, {fillStyle: this.overflowColor});
32996
+ }
32997
+
32998
+ }
32999
+ lastPixelEnd = x + width;
33000
+ lastY = y;
32509
33001
  }
32510
- }));
32511
33002
 
32512
- if (!data) {
32513
- return undefined
33003
+ // If the track includes negative values draw a baseline
33004
+ if (this.dataRange.min < 0) {
33005
+ let maxValue = this.dataRange.max;
33006
+ let minValue = this.dataRange.min;
33007
+ minValue = (this.logScale === true) ? ((minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1)) : minValue;
33008
+ maxValue = (this.logScale === true) ? ((maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1)) : maxValue;
33009
+ const ratio = maxValue / (maxValue - minValue);
33010
+ const basepx = this.flipAxis ? (1 - ratio) * pixelHeight : ratio * pixelHeight;
33011
+ IGVGraphics.strokeLine(ctx, 0, basepx, options.pixelWidth, basepx, {strokeStyle: this.baselineColor});
33012
+ }
32514
33013
  }
33014
+ }
32515
33015
 
32516
- const binaryParser = new BinaryParser$1(new DataView(data));
32517
- const group = {name: name};
32518
- let nAttributes = binaryParser.getInt();
32519
- while (nAttributes-- > 0) {
32520
- const key = binaryParser.getString();
32521
- const value = binaryParser.getString();
32522
- group[key] = value;
33016
+ // Draw guidelines
33017
+ if (this.config.hasOwnProperty('guideLines')) {
33018
+ for (let line of this.config.guideLines) {
33019
+ if (line.hasOwnProperty('color') && line.hasOwnProperty('y') && line.hasOwnProperty('dotted')) {
33020
+ let y = yScale(line.y);
33021
+ let props = {
33022
+ 'strokeStyle': line['color'],
33023
+ 'strokeWidth': 2
33024
+ };
33025
+ if (line['dotted']) IGVGraphics.dashedLine(options.context, 0, y, options.pixelWidth, y, 5, props);
33026
+ else IGVGraphics.strokeLine(options.context, 0, y, options.pixelWidth, y, props);
33027
+ }
32523
33028
  }
32524
- this.groupCache[name] = group;
32525
- return group
32526
33029
  }
32527
33030
  }
32528
33031
 
32529
- async readTiles(tileIndeces, nTracks) {
33032
+ popupData(clickState, features) {
32530
33033
 
32531
- tileIndeces.sort(function (a, b) {
32532
- return a.position - b.position
32533
- });
33034
+ if (features === undefined) features = this.clickedFeatures(clickState);
32534
33035
 
32535
- tileIndeces = tileIndeces.filter(function (idx) {
32536
- return idx.size > 0
32537
- });
33036
+ if (features && features.length > 0) {
32538
33037
 
32539
- if (tileIndeces.length === 0) {
32540
- return []
32541
- }
33038
+ const genomicLocation = clickState.genomicLocation;
33039
+ const popupData = [];
32542
33040
 
32543
- tileIndeces = consolidateTiles(tileIndeces);
33041
+ // Sort features based on distance from click
33042
+ features.sort(function (a, b) {
33043
+ const distA = Math.abs((a.start + a.end) / 2 - genomicLocation);
33044
+ const distB = Math.abs((b.start + b.end) / 2 - genomicLocation);
33045
+ return distA - distB
33046
+ });
32544
33047
 
32545
- const tiles = [];
33048
+ // Display closest 10
33049
+ const displayFeatures = features.length > 10 ? features.slice(0, 10) : features;
32546
33050
 
32547
- for (let indexEntry of tileIndeces) {
33051
+ // Resort in ascending order
33052
+ displayFeatures.sort(function (a, b) {
33053
+ return a.start - b.start
33054
+ });
32548
33055
 
32549
- const data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32550
- range: {
32551
- start: indexEntry.position,
32552
- size: indexEntry.size
33056
+ for (let selectedFeature of displayFeatures) {
33057
+ if (selectedFeature) {
33058
+ if (popupData.length > 0) {
33059
+ popupData.push('<hr/>');
33060
+ }
33061
+ let posString = (selectedFeature.end - selectedFeature.start) === 1 ?
33062
+ numberFormatter$1(Math.floor(selectedFeature.start) + 1)
33063
+ : numberFormatter$1(Math.floor(selectedFeature.start) + 1) + "-" + numberFormatter$1(Math.floor(selectedFeature.end));
33064
+ popupData.push({name: "Position:", value: posString});
33065
+ popupData.push({
33066
+ name: "Value:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;",
33067
+ value: numberFormatter$1(selectedFeature.value.toFixed(4))
33068
+ });
32553
33069
  }
32554
- }));
32555
-
32556
- const tileData = this.compressed ? inflate_1$3(data).buffer : data;
32557
-
32558
- const binaryParser = new BinaryParser$1(new DataView(tileData));
32559
- const type = binaryParser.getString();
32560
- let tile;
32561
- switch (type) {
32562
- case "fixedStep":
32563
- tile = createFixedStep(binaryParser, nTracks);
32564
- break
32565
- case "variableStep":
32566
- tile = createVariableStep(binaryParser, nTracks);
32567
- break
32568
- case "bed":
32569
- case "bedWithName":
32570
- tile = createBed(binaryParser, nTracks, type);
32571
- break
32572
- default:
32573
- throw "Unknown tile type: " + type
32574
33070
  }
32575
- tiles.push(tile);
33071
+ if (displayFeatures.length < features.length) {
33072
+ popupData.push("<hr/>...");
33073
+ }
32576
33074
 
33075
+ return popupData
33076
+
33077
+ } else {
33078
+ return []
32577
33079
  }
32578
- return tiles
32579
33080
  }
32580
33081
 
32581
- async readTile(indexEntry, nTracks) {
33082
+ get supportsWholeGenome() {
33083
+ return !this.config.indexURL && this.config.supportsWholeGenome !== false
33084
+ }
32582
33085
 
32583
- let data = await igvxhr.loadArrayBuffer(this.path, buildOptions(this.config, {
32584
- range: {
32585
- start: indexEntry.position,
32586
- size: indexEntry.size
32587
- }
32588
- }));
33086
+ /**
33087
+ * Return color for feature.
33088
+ * @param feature
33089
+ * @returns {string}
33090
+ */
32589
33091
 
32590
- if (this.compressed) {
32591
- const plain = inflate_1$3(data);
32592
- data = plain.buffer;
32593
- }
33092
+ getColorForFeature(f) {
33093
+ let c = (f.value < 0 && this.altColor) ? this.altColor : this.color || DEFAULT_COLOR$2;
33094
+ return (typeof c === "function") ? c(f.value) : c
33095
+ }
32594
33096
 
32595
- const binaryParser = new BinaryParser$1(new DataView(data));
32596
- const type = binaryParser.getString();
32597
- switch (type) {
32598
- case "fixedStep":
32599
- return createFixedStep(binaryParser, nTracks)
32600
- case "variableStep":
32601
- return createVariableStep(binaryParser, nTracks)
32602
- case "bed":
32603
- case "bedWithName":
32604
- return createBed(binaryParser, nTracks, type)
33097
+ /**
33098
+ * Called when the track is removed. Do any needed cleanup here
33099
+ */
33100
+ dispose() {
33101
+ this.trackView = undefined;
33102
+ }
33103
+
33104
+ }
33105
+
33106
+ /**
33107
+ * Summarize wig data in bins of size "bpPerPixel" with the given window function.
33108
+ *
33109
+ * @param features wig (numeric) data -- features cannot overlap, and are in ascending order by start position
33110
+ * @param startBP bp start position for computing binned data
33111
+ * @param bpPerPixel bp per pixel (bin)
33112
+ * @param windowFunction mean, min, or max
33113
+ * @returns {*|*[]}
33114
+ */
33115
+ function summarizeData(features, startBP, bpPerPixel, windowFunction = "mean") {
33116
+
33117
+ if (bpPerPixel <= 1 || !features || features.length === 0) {
33118
+ return features
33119
+ }
33120
+
33121
+ // Assume features are sorted by position. Wig features cannot overlap. Note, UCSC "reductionLevel" == bpPerPixel
33122
+ const chr = features[0].chr;
33123
+ const binSize = bpPerPixel;
33124
+ const summaryFeatures = [];
33125
+
33126
+ const finishBin = (bin) => {
33127
+ const start = startBP + bin.bin * binSize;
33128
+ const end = start + binSize;
33129
+ let value;
33130
+ switch (windowFunction) {
33131
+ case "mean":
33132
+ value = bin.sumData / bin.count;
33133
+ break
33134
+ case "max":
33135
+ value = bin.max;
33136
+ break
33137
+ case "min":
33138
+ value = bin.min;
33139
+ break
32605
33140
  default:
32606
- throw "Unknown tile type: " + type
33141
+ throw Error(`Unknown window function: ${windowFunction}`)
33142
+ }
33143
+ const description = `${windowFunction} of ${bin.count} values`;
33144
+ summaryFeatures.push({chr, start, end, value, description});
33145
+ };
33146
+
33147
+ let currentBinData;
33148
+ for (let f of features) {
33149
+
33150
+ // Loop through bins this feature overlaps, updating the weighted sum for each bin or min/max,
33151
+ // depending on window function
33152
+ let startBin = Math.floor((f.start - startBP) / binSize);
33153
+ const endBin = Math.floor((f.end - startBP) / binSize);
33154
+
33155
+ if (currentBinData && startBin === currentBinData.bin) {
33156
+ currentBinData.add(f);
33157
+ startBin++;
32607
33158
  }
32608
- }
32609
33159
 
32610
- }
33160
+ if (!currentBinData || endBin > currentBinData.bin) {
32611
33161
 
32612
- function createFixedStep(binaryParser, nTracks) {
32613
- const nPositions = binaryParser.getInt();
32614
- const start = binaryParser.getInt();
32615
- const span = binaryParser.getFloat();
33162
+ if(currentBinData) {
33163
+ finishBin(currentBinData);
33164
+ }
32616
33165
 
32617
- const data = [];
32618
- let nt = nTracks;
32619
- while (nt-- > 0) {
32620
- let np = nPositions;
32621
- const dtrack = [];
32622
- while (np-- > 0) {
32623
- dtrack.push(binaryParser.getFloat());
33166
+ // Feature stretches across multiple bins.
33167
+ if (endBin > startBin) {
33168
+ const end = startBP + endBin * binSize;
33169
+ summaryFeatures.push({chr, start: f.start, end, value: f.value});
33170
+ }
33171
+
33172
+ currentBinData = new SummaryBinData(endBin, f);
32624
33173
  }
32625
- data.push(dtrack);
33174
+
33175
+ }
33176
+ if(currentBinData) {
33177
+ finishBin(currentBinData);
32626
33178
  }
32627
33179
 
32628
- return {
32629
- type: "fixedStep",
32630
- start: start,
32631
- span: span,
32632
- data: data,
32633
- nTracks: nTracks,
32634
- nPositions: nPositions
33180
+ // Consolidate
33181
+ const c = [];
33182
+ let lastFeature = summaryFeatures[0];
33183
+ for (let f of summaryFeatures) {
33184
+ if (lastFeature.value === f.value && f.start <= lastFeature.end) {
33185
+ lastFeature.end = f.end;
33186
+ } else {
33187
+ c.push(lastFeature);
33188
+ lastFeature = f;
33189
+ }
32635
33190
  }
32636
- }
33191
+ c.push(lastFeature);
32637
33192
 
32638
- function createVariableStep(binaryParser, nTracks) {
33193
+ return c
32639
33194
 
32640
- const tileStart = binaryParser.getInt();
32641
- const span = binaryParser.getFloat();
32642
- const nPositions = binaryParser.getInt();
32643
- const start = [];
33195
+ }
32644
33196
 
32645
- let np = nPositions;
32646
- while (np-- > 0) {
32647
- start.push(binaryParser.getInt());
33197
+ class SummaryBinData {
33198
+ constructor(bin, feature) {
33199
+ this.bin = bin;
33200
+ this.sumData = feature.value;
33201
+ this.count = 1;
33202
+ this.min = feature.value;
33203
+ this.max = feature.value;
32648
33204
  }
32649
- binaryParser.getInt(); // # of samples, ignored but should === nTracks
32650
33205
 
32651
- const data = [];
32652
- let nt = nTracks;
32653
- while (nt-- > 0) {
32654
- np = nPositions;
32655
- const dtrack = [];
32656
- while (np-- > 0) {
32657
- dtrack.push(binaryParser.getFloat());
32658
- }
32659
- data.push(dtrack);
33206
+ add(feature) {
33207
+ this.sumData += feature.value;
33208
+ this.max = Math.max(feature.value, this.max);
33209
+ this.min = Math.min(feature.value, this.min);
33210
+ this.count++;
32660
33211
  }
32661
33212
 
32662
- return {
32663
- type: "variableStep",
32664
- tileStart: tileStart,
32665
- span: span,
32666
- start: start,
32667
- data: data,
32668
- nTracks: nTracks,
32669
- nPositions: nPositions
33213
+ get mean() {
33214
+ return this.sumData / this.count
32670
33215
  }
32671
33216
  }
32672
33217
 
32673
- function createBed(binaryParser, nTracks, type) {
33218
+ const DEFAULT_MAX_WG_COUNT = 10000;
32674
33219
 
32675
- const nPositions = binaryParser.getInt();
33220
+ /**
33221
+ * feature source for "bed like" files (tab or whitespace delimited files with 1 feature per line: bed, gff, vcf, etc)
33222
+ *
33223
+ * @param config
33224
+ * @constructor
33225
+ */
33226
+ class TextFeatureSource extends BaseFeatureSource {
32676
33227
 
32677
- let n = nPositions;
32678
- const start = [];
32679
- while (n-- > 0) {
32680
- start.push(binaryParser.getInt());
32681
- }
33228
+ constructor(config, genome) {
32682
33229
 
32683
- n = nPositions;
32684
- const end = [];
32685
- while (n-- > 0) {
32686
- end.push(binaryParser.getInt());
32687
- }
33230
+ super(genome);
32688
33231
 
32689
- binaryParser.getInt(); // # of samples, ignored but should === nTracks
32690
- const data = [];
32691
- let nt = nTracks;
32692
- while (nt-- > 0) {
32693
- let np = nPositions;
32694
- const dtrack = [];
32695
- while (np-- > 0) {
32696
- dtrack.push(binaryParser.getFloat());
32697
- }
32698
- data.push(dtrack);
32699
- }
33232
+ this.config = config || {};
33233
+ this.genome = genome;
33234
+ this.sourceType = (config.sourceType === undefined ? "file" : config.sourceType);
33235
+ this.maxWGCount = config.maxWGCount || DEFAULT_MAX_WG_COUNT;
33236
+ this.windowFunctions = ["mean", "min", "max", "none"];
32700
33237
 
32701
- if (type === "bedWithName") {
32702
- n = nPositions;
32703
- const name = [];
32704
- while (n-- > 0) {
32705
- name.push(binaryParser.getString());
33238
+ const queryableFormats = new Set(["bigwig", "bw", "bigbed", "bb", "biginteract", "biggenepred", "bignarrowpeak", "tdf"]);
33239
+
33240
+ this.queryable = config.indexURL || config.queryable === true; // False by default, unless explicitly set
33241
+ if (config.reader) {
33242
+ // Explicit reader implementation
33243
+ this.reader = config.reader;
33244
+ this.queryable = config.queryable !== false;
33245
+ } else if (config.sourceType === "ga4gh") {
33246
+ throw Error("Unsupported source type 'ga4gh'")
33247
+ } else if ((config.type === "eqtl" || config.type === "qtl") && config.sourceType === "gtex-ws") {
33248
+ this.reader = new GtexReader(config);
33249
+ this.queryable = true;
33250
+ } else if ("htsget" === config.sourceType) {
33251
+ this.reader = new HtsgetVariantReader(config, genome);
33252
+ this.queryable = true;
33253
+ } else if (config.sourceType === 'ucscservice') {
33254
+ this.reader = new UCSCServiceReader(config.source);
33255
+ this.queryable = true;
33256
+ } else if (config.sourceType === 'custom') {
33257
+ this.reader = new CustomServiceReader(config.source);
33258
+ this.queryable = false !== config.source.queryable;
33259
+ } else if ('service' === config.sourceType) {
33260
+ this.reader = new FeatureFileReader(config, genome);
33261
+ this.queryable = true;
33262
+ } else {
33263
+ // File of some type (i.e. not a webservice)
33264
+ this.reader = new FeatureFileReader(config, genome);
33265
+ if (config.queryable !== undefined) {
33266
+ this.queryable = config.queryable;
33267
+ } else if (queryableFormats.has(config.format) || this.reader.indexed) {
33268
+ this.queryable = true;
33269
+ } else ;
32706
33270
  }
32707
- }
32708
33271
 
32709
- return {
32710
- type: type,
32711
- start: start,
32712
- end: end,
32713
- data: data,
32714
- nTracks: nTracks,
32715
- nPositions: nPositions
32716
- }
32717
- }
33272
+ // Flag indicating if features loaded by this source can be searched for by name or attribute, true by default
33273
+ this.searchable = config.searchable !== false;
32718
33274
 
32719
- function consolidateTiles(tiles) {
33275
+ }
32720
33276
 
32721
- const consolidated = [];
32722
- let current = tiles[0];
32723
- for (let i = 1; i < tiles.length; i++) {
32724
- const t = tiles[i];
32725
- if (t.position > current.position + current.size) {
32726
- consolidated.push(current);
32727
- current = t;
32728
- } else {
32729
- current.size = t.position + t.size - current.position;
33277
+ async defaultVisibilityWindow() {
33278
+ if (this.reader && typeof this.reader.defaultVisibilityWindow === 'function') {
33279
+ return this.reader.defaultVisibilityWindow()
32730
33280
  }
32731
33281
  }
32732
- consolidated.push(current);
32733
- return consolidated
32734
- }
32735
-
32736
- /*
32737
- * The MIT License (MIT)
32738
- *
32739
- * Copyright (c) 2016 University of California San Diego
32740
- * Author: Jim Robinson
32741
- *
32742
- * Permission is hereby granted, free of charge, to any person obtaining a copy
32743
- * of this software and associated documentation files (the "Software"), to deal
32744
- * in the Software without restriction, including without limitation the rights
32745
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32746
- * copies of the Software, and to permit persons to whom the Software is
32747
- * furnished to do so, subject to the following conditions:
32748
- *
32749
- * The above copyright notice and this permission notice shall be included in
32750
- * all copies or substantial portions of the Software.
32751
- *
32752
- *
32753
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32754
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32755
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32756
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32757
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32758
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32759
- * THE SOFTWARE.
32760
- */
32761
-
32762
- class TDFSource extends BaseFeatureSource {
32763
33282
 
32764
- searchable = false
32765
- constructor(config, genome) {
32766
- super(genome);
32767
- this.genome = genome;
32768
- this.reader = new TDFReader(config, genome);
32769
- this.queryable = true;
33283
+ async trackType() {
33284
+ const header = await this.getHeader();
33285
+ if (header) {
33286
+ return header.type
33287
+ } else {
33288
+ return undefined // Convention for unknown or unspecified
33289
+ }
32770
33290
  }
32771
33291
 
32772
- async getFeatures({chr, start, end, bpPerPixel, windowFunction = "mean"}) {
33292
+ async getHeader() {
33293
+ if (!this.header) {
32773
33294
 
32774
- if (chr.toLowerCase() === "all") {
32775
- const wgFeatures = [];
32776
- const genome = this.genome;
32777
- const chrNames = this.genome.wgChromosomeNames;
32778
- if (chrNames) {
32779
- for (let c of genome.wgChromosomeNames) {
32780
- const len = genome.getChromosome(c).bpLength;
32781
- bpPerPixel = len / 1000;
32782
- const chrFeatures = await this._getFeatures(c, 0, len, bpPerPixel, windowFunction);
32783
- if (chrFeatures) {
32784
- for (let f of chrFeatures) {
32785
- const wg = Object.assign({}, f);
32786
- wg.chr = "all";
32787
- wg.start = genome.getGenomeCoordinate(f.chr, f.start);
32788
- wg.end = genome.getGenomeCoordinate(f.chr, f.end);
32789
- wg._f = f;
32790
- wgFeatures.push(wg);
32791
- }
33295
+ if (this.reader && typeof this.reader.readHeader === "function") {
33296
+ const header = await this.reader.readHeader();
33297
+ if (header) {
33298
+ this.header = header;
33299
+ if (header.format) {
33300
+ this.config.format = header.format;
32792
33301
  }
33302
+ } else {
33303
+ this.header = {};
32793
33304
  }
33305
+ } else {
33306
+ this.header = {};
32794
33307
  }
32795
- return wgFeatures
32796
-
32797
- } else {
32798
- return this._getFeatures(chr, start, end, bpPerPixel, windowFunction)
32799
33308
  }
33309
+ return this.header
32800
33310
  }
32801
- async _getFeatures(chr, start, end, bpPerPixel, windowFunction) {
32802
- const genomicInterval = new GenomicInterval(chr, start, end);
32803
- const genome = this.genome;
32804
33311
 
33312
+ /**
33313
+ * Required function for all data source objects. Fetches features for the
33314
+ * range requested.
33315
+ *
33316
+ * This function is quite complex due to the variety of reader types backing it, some indexed, some queryable,
33317
+ * some not.
33318
+ *
33319
+ * @param chr
33320
+ * @param start
33321
+ * @param end
33322
+ * @param bpPerPixel
33323
+ */
33324
+ async getFeatures({chr, start, end, bpPerPixel, visibilityWindow, windowFunction}) {
32805
33325
 
32806
- if (!this.rootGroup) {
32807
- this.rootGroup = await this.reader.readRootGroup();
32808
- if (!this.normalizationFactor) {
32809
- const totalCount = this.rootGroup.totalCount;
32810
- if (totalCount) {
32811
- this.normalizationFactor = 1.0e6 / totalCount;
32812
- }
32813
- }
32814
- }
33326
+ const isWholeGenome = ("all" === chr.toLowerCase());
32815
33327
 
32816
- genomicInterval.bpPerPixel = bpPerPixel;
32817
- const zoom = zoomLevelForScale(chr, bpPerPixel, genome);
32818
- let queryChr = this.reader.chrAliasTable[chr];
32819
- let maxZoom = this.reader.maxZoom;
32820
- if (queryChr === undefined) queryChr = chr;
32821
- if (maxZoom === undefined) maxZoom = -1;
33328
+ start = start || 0;
33329
+ end = end || Number.MAX_SAFE_INTEGER;
32822
33330
 
32823
- const wf = zoom > maxZoom ? "raw" : windowFunction;
32824
- const dataset = await this.reader.readDataset(queryChr, wf, zoom);
32825
- if (dataset == null) {
32826
- return []
33331
+ // Various conditions that can require a feature load
33332
+ // * view is "whole genome" but no features are loaded
33333
+ // * cache is disabled
33334
+ // * cache does not contain requested range
33335
+ // const containsRange = this.featureCache.containsRange(new GenomicInterval(queryChr, start, end))
33336
+ if ((isWholeGenome && !this.wgFeatures && this.supportsWholeGenome()) ||
33337
+ this.config.disableCache ||
33338
+ !this.featureCache ||
33339
+ !this.featureCache.containsRange(new GenomicInterval(chr, start, end))) {
33340
+ await this.loadFeatures(chr, start, end, visibilityWindow);
32827
33341
  }
32828
33342
 
32829
- const tileWidth = dataset.tileWidth;
32830
- const startTile = Math.floor(start / tileWidth);
32831
- const endTile = Math.floor(end / tileWidth);
32832
- const NTRACKS = 1; // TODO read this
32833
- const tiles = await this.reader.readTiles(dataset.tiles.slice(startTile, endTile + 1), NTRACKS);
32834
- const features = [];
32835
- for (let tile of tiles) {
32836
- switch (tile.type) {
32837
- case "bed":
32838
- decodeBedTile(tile, chr, start, end, bpPerPixel, features);
32839
- break
32840
- case "variableStep":
32841
- decodeVaryTile(tile, chr, start, end, bpPerPixel, features);
32842
- break
32843
- case "fixedStep":
32844
- decodeFixedTile(tile, chr, start, end, bpPerPixel, features);
32845
- break
32846
- default:
32847
- throw ("Unknown tile type: " + tile.type)
33343
+ if (isWholeGenome) {
33344
+ if (!this.wgFeatures) {
33345
+ if (this.supportsWholeGenome()) {
33346
+ if("wig" === this.config.type) {
33347
+ const allWgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, 1000000);
33348
+ this.wgFeatures = summarizeData(allWgFeatures, 0, bpPerPixel, windowFunction);
33349
+ } else {
33350
+ this.wgFeatures = await computeWGFeatures(this.featureCache.getAllFeatures(), this.genome, this.maxWGCount);
33351
+ }
33352
+ } else {
33353
+ this.wgFeatures = [];
33354
+ }
32848
33355
  }
33356
+ return this.wgFeatures
33357
+ } else {
33358
+ return this.featureCache.queryFeatures(chr, start, end)
32849
33359
  }
32850
- features.sort(function (a, b) {
32851
- return a.start - b.start
32852
- });
32853
-
32854
- return features
32855
33360
  }
32856
33361
 
32857
- get supportsWholeGenome() {
32858
- return true
33362
+ async findFeatures(fn) {
33363
+ return this.featureCache ? this.featureCache.findFeatures(fn) : []
32859
33364
  }
32860
33365
 
32861
- get windowFunctions() {
32862
- return this.reader.windowFunctions
33366
+ supportsWholeGenome() {
33367
+ return !this.queryable // queryable (indexed, web services) sources don't support whole genome view
32863
33368
  }
32864
- }
32865
33369
 
32866
- function decodeBedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
32867
-
32868
- const nPositions = tile.nPositions;
32869
- const starts = tile.start;
32870
- const ends = tile.end;
32871
- const data = tile.data[0]; // Single track for now
32872
- for (let i = 0; i < nPositions; i++) {
32873
- const s = starts[i];
32874
- const e = ends[i];
32875
- if (e < bpStart) continue
32876
- if (s > bpEnd) break
32877
- features.push({
32878
- chr: chr,
32879
- start: s,
32880
- end: e,
32881
- value: data[i]
32882
- });
33370
+ // TODO -- experimental, will only work for non-indexed sources
33371
+ getAllFeatures() {
33372
+ if (this.queryable || !this.featureCache) { // queryable sources don't support all features
33373
+ return []
33374
+ } else {
33375
+ return this.featureCache.getAllFeatures()
33376
+ }
32883
33377
  }
32884
- }
32885
33378
 
32886
- function decodeVaryTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
32887
33379
 
32888
- const nPositions = tile.nPositions;
32889
- const starts = tile.start;
32890
- const span = tile.span;
32891
- const data = tile.data[0]; // Single track for now
32892
- for (let i = 0; i < nPositions; i++) {
32893
- const s = starts[i];
32894
- const e = s + span;
32895
- if (e < bpStart) continue
32896
- if (s > bpEnd) break
32897
- features.push({
32898
- chr: chr,
32899
- start: s,
32900
- end: e,
32901
- value: data[i]
32902
- });
32903
- }
32904
- }
33380
+ async loadFeatures(chr, start, end, visibilityWindow) {
32905
33381
 
32906
- function decodeFixedTile(tile, chr, bpStart, bpEnd, bpPerPixel, features) {
33382
+ await this.getHeader();
32907
33383
 
32908
- const nPositions = tile.nPositions;
32909
- let s = tile.start;
32910
- const span = tile.span;
32911
- const data = tile.data[0]; // Single track for now
33384
+ const reader = this.reader;
33385
+ let intervalStart = start;
33386
+ let intervalEnd = end;
32912
33387
 
32913
- for (let i = 0; i < nPositions; i++) {
32914
- const e = s + span;
32915
- if (s > bpEnd) break
32916
- if (e >= bpStart) {
32917
- if (!Number.isNaN(data[i])) {
32918
- features.push({
32919
- chr: chr,
32920
- start: s,
32921
- end: e,
32922
- value: data[i]
32923
- });
33388
+ // chr aliasing
33389
+ let queryChr = chr;
33390
+ if (!this.chrAliasManager && this.reader && this.reader.sequenceNames) {
33391
+ this.chrAliasManager = new ChromAliasManager(this.reader.sequenceNames, this.genome);
33392
+ }
33393
+ if (this.chrAliasManager) {
33394
+ queryChr = await this.chrAliasManager.getAliasName(chr);
33395
+ }
33396
+
33397
+ // Use visibility window to potentially expand query interval.
33398
+ // This can save re-queries as we zoom out. Visibility window <= 0 is a special case
33399
+ // indicating whole chromosome should be read at once.
33400
+ if ((!visibilityWindow || visibilityWindow <= 0) && this.config.expandQuery !== false) {
33401
+ // Whole chromosome
33402
+ const chromosome = this.genome ? this.genome.getChromosome(queryChr) : undefined;
33403
+ intervalStart = 0;
33404
+ intervalEnd = Math.max(chromosome ? chromosome.bpLength : Number.MAX_SAFE_INTEGER, end);
33405
+ } else if (visibilityWindow > (end - start) && this.config.expandQuery !== false) {
33406
+ let expansionWindow = Math.min(4.1 * (end - start), visibilityWindow);
33407
+ if(this.config.minQuerySize && expansionWindow < this.config.minQuerySize) {
33408
+ expansionWindow = this.config.minQuerySize;
32924
33409
  }
33410
+ intervalStart = Math.max(0, (start + end - expansionWindow) / 2);
33411
+ intervalEnd = intervalStart + expansionWindow;
32925
33412
  }
32926
- s = e;
32927
- }
32928
- }
32929
33413
 
33414
+ let features = await reader.readFeatures(queryChr, intervalStart, intervalEnd);
33415
+ if (this.queryable === undefined) {
33416
+ this.queryable = reader.indexed;
33417
+ }
32930
33418
 
32931
- var log2 = Math.log(2);
33419
+ const genomicInterval = this.queryable ?
33420
+ new GenomicInterval(chr, intervalStart, intervalEnd) :
33421
+ undefined;
32932
33422
 
32933
- function zoomLevelForScale(chr, bpPerPixel, genome) {
33423
+ if (features) {
32934
33424
 
32935
- // Convert bpPerPixel to IGV "zoom" level. This is a bit convoluted, TDF is computed zoom levels assuming
32936
- // display in a 700 pixel window. The fully zoomed out view of a chromosome is zoom level "0".
32937
- // Zoom level 1 is magnified 2X, and so forth
33425
+ // Assign overlapping features to rows
33426
+ if (this.config.format !== "wig" && this.config.type !== "junctions") {
33427
+ const maxRows = this.config.maxRows || Number.MAX_SAFE_INTEGER;
33428
+ packFeatures(features, maxRows);
33429
+ }
32938
33430
 
32939
- var chrSize = genome.getChromosome(chr).bpLength;
33431
+ // Note - replacing previous cache with new one. genomicInterval is optional (might be undefined => includes all features)
33432
+ this.featureCache = new FeatureCache$1(features, this.genome, genomicInterval);
32940
33433
 
32941
- return Math.ceil(Math.log(Math.max(0, (chrSize / (bpPerPixel * 700)))) / log2)
33434
+ // If track is marked "searchable"< cache features by name -- use this with caution, memory intensive
33435
+ if (this.searchable) {
33436
+ this.addFeaturesToDB(features, this.config);
33437
+ }
33438
+ } else {
33439
+ this.featureCache = new FeatureCache$1([], genomicInterval); // Empty cache
33440
+ }
33441
+ }
33442
+
33443
+ addFeaturesToDB(featureList, config) {
33444
+ if (!this.featureMap) {
33445
+ this.featureMap = new Map();
33446
+ }
33447
+ const searchableFields = config.searchableFields || ["name", "transcript_id", "gene_id", "gene_name", "id"];
33448
+ for (let feature of featureList) {
33449
+ for (let field of searchableFields) {
33450
+ let key;
33451
+ if (typeof feature.getAttributeValue === 'function') {
33452
+ key = feature.getAttributeValue(field);
33453
+ }
33454
+ if (key) {
33455
+ key = key.replaceAll(' ', '+').toUpperCase();
33456
+ // If feature is already present keep largest one
33457
+ if (this.featureMap.has(key)) {
33458
+ const f2 = this.featureMap.get(key);
33459
+ if (feature.end - feature.start < f2.end - f2.start) {
33460
+ continue
33461
+ }
33462
+ }
33463
+ this.featureMap.set(key, feature);
33464
+ }
33465
+ }
33466
+ }
33467
+ }
33468
+
33469
+ search(term) {
33470
+ if (this.featureMap) {
33471
+ return this.featureMap.get(term.toUpperCase())
33472
+ }
33473
+
33474
+ }
32942
33475
  }
32943
33476
 
32944
33477
  /*
@@ -34230,7 +34763,7 @@ function renderFusionJuncSpan(feature, bpStart, xScale, pixelHeight, ctx) {
34230
34763
  }
34231
34764
  }
34232
34765
 
34233
- const DEFAULT_COLOR$2 = 'rgb(0, 0, 150)';
34766
+ const DEFAULT_COLOR$1 = 'rgb(0, 0, 150)';
34234
34767
 
34235
34768
 
34236
34769
  class FeatureTrack extends TrackBase {
@@ -34416,7 +34949,7 @@ class FeatureTrack extends TrackBase {
34416
34949
  }
34417
34950
 
34418
34951
 
34419
- if (!this.config.isMergedTrack) {
34952
+ if (!this.isMergedTrack) {
34420
34953
  IGVGraphics.fillRect(context, 0, options.pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
34421
34954
  }
34422
34955
 
@@ -34727,7 +35260,7 @@ class FeatureTrack extends TrackBase {
34727
35260
 
34728
35261
  // If no explicit setting use the default
34729
35262
  if (!color) {
34730
- color = DEFAULT_COLOR$2; // Track default
35263
+ color = DEFAULT_COLOR$1; // Track default
34731
35264
  }
34732
35265
 
34733
35266
  if (feature.alpha && feature.alpha !== 1) {
@@ -37854,7 +38387,6 @@ function indentLevel(str) {
37854
38387
  }
37855
38388
 
37856
38389
  const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
37857
- const BACKUP_GENOMES_URL = "https://s3.amazonaws.com/igv.org.genomes/genomes.json";
37858
38390
 
37859
38391
  const GenomeUtils = {
37860
38392
 
@@ -37866,21 +38398,9 @@ const GenomeUtils = {
37866
38398
 
37867
38399
  // Get default genomes
37868
38400
  if (config.loadDefaultGenomes !== false) {
37869
- try {
37870
- const url = DEFAULT_GENOMES_URL;
37871
- const jsonArray = await igvxhr.loadJson(url, {timeout: 5000});
37872
- processJson(jsonArray);
37873
- } catch (e) {
37874
- console.error(e);
37875
- try {
37876
- const url = BACKUP_GENOMES_URL;
37877
- const jsonArray = await igvxhr.loadJson(url, {});
37878
- processJson(jsonArray);
37879
- } catch (e) {
37880
- console.error(e);
37881
- console.warn("Errors loading default genome definitions.");
37882
- }
37883
- }
38401
+ const url = DEFAULT_GENOMES_URL;
38402
+ const jsonArray = await igvxhr.loadJson(url, {timeout: 5000});
38403
+ processJson(jsonArray);
37884
38404
  }
37885
38405
 
37886
38406
  // Add user-defined genomes
@@ -37945,7 +38465,7 @@ const GenomeUtils = {
37945
38465
  }
37946
38466
  }
37947
38467
 
37948
- if(!reference) {
38468
+ if (!reference) {
37949
38469
  alert.present(new Error(`Unknown genome id: ${genomeID}`), undefined);
37950
38470
  }
37951
38471
  }
@@ -41343,301 +41863,242 @@ const hideAllMenuPopups = parent => {
41343
41863
  * THE SOFTWARE.
41344
41864
  */
41345
41865
 
41346
- class NavbarButton {
41347
-
41348
- constructor(browser, parent, title, buttonLabel, imageSVG, imageHoverSVG, initialButtonState) {
41349
-
41350
- this.browser = browser;
41351
-
41352
- this.button = div({class: 'igv-navbar-text-button'});
41353
- parent.appendChild(this.button);
41354
-
41355
- if (Array.isArray(title)) {
41356
- this.textContent = title[ 0 ];
41357
- this.title = title[ 1 ];
41358
- } else {
41359
- this.textContent = this.title = title;
41360
- }
41361
-
41362
- this.buttonLabel = buttonLabel;
41363
-
41364
- this.imageDictionary =
41365
- {
41366
- image: `url("data:image/svg+xml,${ encodeURIComponent(imageSVG) }")`,
41367
- imageHover: `url("data:image/svg+xml,${ encodeURIComponent(imageHoverSVG) }")`,
41368
- };
41369
-
41370
- this.responsiveKey = 'text';
41371
-
41372
- this.configureButton(this.textContent, this.title);
41373
-
41374
- this.setState(initialButtonState);
41375
-
41376
- browser.on('navbar-resize', navbarButtonCSSClass => {
41377
- this.navbarResizeHandler(navbarButtonCSSClass);
41378
- });
41379
-
41380
- }
41381
-
41382
- navbarResizeHandler(navbarButtonCSSClass) {
41383
- const key = 'igv-navbar-icon-button' === navbarButtonCSSClass ? 'image' : 'text';
41384
- if (key !== this.responsiveKey) {
41385
- this.responsiveKey = key;
41386
- this.configureButton(this.textContent, this.title);
41387
- this.setState(undefined);
41388
- }
41389
- }
41390
-
41391
- configureButton(textContent, title) {
41392
-
41393
- this.groupElement = undefined;
41394
- this.button.title = title;
41395
- this.button.innerHTML = '';
41396
- this.button.style.backgroundImage = 'none';
41397
- this.button.classList.remove('igv-navbar-icon-button');
41398
- this.button.classList.remove('igv-navbar-text-button');
41399
-
41400
- 'text' === this.responsiveKey ? this.configureTextButton(textContent) : this.configureIconButton();
41401
-
41402
- }
41403
-
41404
- configureTextButton(textContent) {
41405
-
41406
- this.button.classList.add('igv-navbar-text-button');
41407
-
41408
- const tempDiv = document.createElement('div');
41409
- tempDiv.innerHTML = this.buttonLabel;
41410
- const svgRoot = tempDiv.firstChild;
41411
- this.button.appendChild(svgRoot);
41412
-
41413
- this.groupElement = svgRoot.querySelector('#igv-navbar-button-group');
41414
-
41415
- const tspanElement = svgRoot.querySelector('#igv-navbar-button-label');
41416
- tspanElement.textContent = textContent;
41417
- }
41418
-
41419
- configureIconButton() {
41420
- this.button.classList.add('igv-navbar-icon-button');
41421
- }
41422
-
41423
- setState(doHover) {
41424
-
41425
- if (undefined !== doHover) {
41426
- this.doHover = doHover;
41427
- }
41428
-
41429
- 'text' === this.responsiveKey ? this.setTextButtonState(this.doHover) : this.setIconButtonState(this.doHover);
41430
-
41431
- }
41432
-
41433
- setTextButtonState(doHover) {
41434
- this.groupElement.classList.remove(...this.groupElement.classList);
41435
- const className = true === doHover ? 'igv-navbar-text-button-svg-hover' : 'igv-navbar-text-button-svg-inactive';
41436
- this.groupElement.classList.add(className);
41437
- }
41438
-
41439
- setIconButtonState(doHover) {
41440
- this.button.style.backgroundImage = true === doHover ? this.imageDictionary.imageHover : this.imageDictionary.image;
41441
- }
41442
-
41443
- show() {
41444
- // this.button.style.display = 'block'
41445
- this.button.style.display = 'flex';
41446
- }
41447
-
41448
- hide() {
41449
- this.button.style.display = 'none';
41450
- }
41451
-
41452
- setVisibility(isVisible) {
41453
- if (true === isVisible) {
41454
- this.show();
41455
- } else {
41456
- this.hide();
41457
- }
41458
- }
41459
-
41460
- static currentNavbarButtonClass(browser) {
41461
- const el = browser.$navigation.get(0).querySelector('.igv-navbar-text-button');
41462
- return el ? 'igv-navbar-text-button' : 'igv-navbar-icon-button'
41463
- }
41464
- }
41465
-
41466
- const overlayTrackImage =
41467
- `<svg width="625px" height="625px" viewBox="0 0 625 625" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
41468
- <title>Overlay Tracks</title>
41469
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
41470
- <g id="Overlay-Tracks">
41471
- <rect id="backdrop" stroke="#737373" stroke-width="12" fill="#FFFFFF" x="6" y="6" width="613" height="613" rx="135"></rect>
41472
- <g id="layer-group" transform="translate(3, 127)">
41473
- <rect id="a" stroke="#737373" stroke-width="24" fill="#A1A1A1" x="12" y="12" width="332" height="139"></rect>
41474
- <rect id="a---hold-out" fill="#A1A1A1" x="9" y="25" width="324" height="115"></rect>
41475
- <rect id="b" stroke="#737373" stroke-width="24" fill="#C9C9C9" x="81" y="103" width="474" height="139"></rect>
41476
- <rect id="c" stroke="#737373" stroke-width="24" fill="#ECECEC" x="238" y="214" width="372" height="139"></rect>
41477
- <rect id="c---hold-out" fill="#ECECEC" x="250" y="226" width="372" height="115"></rect>
41478
- </g>
41479
- <rect id="over-border" stroke="#737373" stroke-width="12" x="6" y="6" width="613" height="613" rx="135"></rect>
41480
- </g>
41481
- </g>
41482
- </svg>`;
41483
-
41484
- const overlayTrackImageHover =
41485
- `<svg width="625px" height="625px" viewBox="0 0 625 625" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
41486
- <title>Overlay Tracks Hover</title>
41487
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
41488
- <g id="Overlay-Tracks-Hover">
41489
- <rect id="backdrop-copy" stroke="#737373" stroke-width="12" fill="#737373" x="6" y="6" width="613" height="613" rx="135"></rect>
41490
- <g id="layer-group" transform="translate(3, 127)">
41491
- <rect id="a" stroke="#FFFFFF" stroke-width="24" fill="#A1A1A1" x="12" y="12" width="332" height="139"></rect>
41492
- <rect id="a---hold-out" fill="#A1A1A1" x="9" y="25" width="324" height="115"></rect>
41493
- <rect id="b" stroke="#FFFFFF" stroke-width="24" fill="#C9C9C9" x="81" y="103" width="474" height="139"></rect>
41494
- <rect id="c" stroke="#FFFFFF" stroke-width="24" fill="#ECECEC" x="238" y="214" width="372" height="139"></rect>
41495
- <rect id="c---hold-out" fill="#ECECEC" x="250" y="226" width="372" height="115"></rect>
41496
- </g>
41497
- <rect id="over-border-copy" stroke="#737373" stroke-width="12" x="6" y="6" width="613" height="613" rx="135"></rect>
41498
- </g>
41499
- </g>
41500
- </svg>`;
41501
-
41502
- const buttonLabel =
41503
- `<svg width="80px" height="18px" viewBox="0 0 80 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
41504
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
41505
- <g id="igv-navbar-button-group">
41506
- <rect id="Rectangle" x="0.5" y="0.5" width="79" height="17" rx="6"></rect>
41507
- <text id="igv-text-button-label" x="50%" y="50%" dy=".1em" font-family="Helvetica" font-size="12" font-weight="normal" letter-spacing="-0.372">
41508
- <tspan id="igv-navbar-button-label"></tspan>
41509
- </text>
41510
- </g>
41511
- </g>
41512
- </svg>`;
41513
-
41514
- const sampleNameButtonLabel =
41515
- `<svg width="90px" height="20px" viewBox="0 0 90 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
41516
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
41517
- <g id="igv-navbar-button-group">
41518
- <rect id="Rectangle" x="0.5" y="0.5" width="89" height="18" rx="6"></rect>
41519
- <text id="igv-text-button-label" x="50%" y="50%" dy=".1em" font-family="Helvetica" font-size="12" font-weight="normal" letter-spacing="-0.372">
41520
- <tspan id="igv-navbar-button-label"></tspan>
41521
- </text>
41522
- </g>
41523
- </g>
41524
- </svg>`;
41525
-
41526
- const shim = .01;
41527
- const colorStripWidth = 4;
41528
- const axesXOffset = colorStripWidth + 1;
41529
- function paintAxis(ctx, width, height, colorOrUndefined) {
41530
-
41531
- if (undefined === this.dataRange || undefined === this.dataRange.max || undefined === this.dataRange.min) {
41532
- return
41533
- }
41534
-
41535
- IGVGraphics.fillRect(ctx, 0, 0, width, height, { fillStyle: 'white' });
41536
- if (colorOrUndefined) {
41537
- IGVGraphics.fillRect(ctx, width - colorStripWidth - 2, 0, colorStripWidth, height, { fillStyle: colorOrUndefined });
41538
- }
41539
-
41540
- const flipAxis = (undefined === this.flipAxis) ? false : this.flipAxis;
41541
-
41542
- const xTickStart = 0.95 * width - 8 - axesXOffset;
41543
- const xTickEnd = 0.95 * width - axesXOffset;
41544
-
41545
- const properties =
41546
- {
41547
- font: 'normal 10px Arial',
41548
- textAlign: 'right',
41549
- fillStyle: 'black',
41550
- strokeStyle: 'black',
41551
- };
41552
-
41553
- // tick
41554
- IGVGraphics.strokeLine(ctx, xTickStart, shim * height, xTickEnd, shim * height, properties);
41555
- IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.min : this.dataRange.max), xTickStart + 4, shim * height + 12, properties);
41556
-
41557
- const y = (1.0 - shim) * height;
41558
-
41559
- // tick
41560
- IGVGraphics.strokeLine(ctx, xTickStart, y, xTickEnd, y, properties);
41561
- IGVGraphics.fillText(ctx, prettyPrint(flipAxis ? this.dataRange.max : this.dataRange.min), xTickStart + 4, y - 4, properties);
41562
-
41563
- // vertical axis
41564
- IGVGraphics.strokeLine(ctx, xTickEnd, shim * height, xTickEnd, y, properties);
41565
-
41566
- function prettyPrint(number) {
41567
-
41568
- if (number === 0) {
41569
- return "0"
41570
- } else if (Math.abs(number) >= 10) {
41571
- return number.toFixed()
41572
- } else if (Math.abs(number) >= 1) {
41573
- return number.toFixed(1)
41574
- } else if (Math.abs(number) >= 0.1) {
41575
- return number.toFixed(2)
41576
- } else {
41577
- return number.toExponential(1)
41578
- }
41579
- }
41580
- }
41581
-
41582
- /*
41583
- * The MIT License (MIT)
41584
- *
41585
- * Copyright (c) 2016-2017 The Regents of the University of California
41586
- * Author: Jim Robinson
41587
- *
41588
- * Permission is hereby granted, free of charge, to any person obtaining a copy
41589
- * of this software and associated documentation files (the "Software"), to deal
41590
- * in the Software without restriction, including without limitation the rights
41591
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41592
- * copies of the Software, and to permit persons to whom the Software is
41593
- * furnished to do so, subject to the following conditions:
41594
- *
41595
- * The above copyright notice and this permission notice shall be included in
41596
- * all copies or substantial portions of the Software.
41597
- *
41598
- *
41599
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41600
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41601
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41602
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41603
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41604
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
41605
- * THE SOFTWARE.
41606
- */
41607
-
41866
+ class NavbarButton {
41867
+
41868
+ constructor(browser, parent, title, buttonLabel, imageSVG, imageHoverSVG, initialButtonState) {
41869
+
41870
+ this.browser = browser;
41871
+
41872
+ this.button = div({class: 'igv-navbar-text-button'});
41873
+ parent.appendChild(this.button);
41874
+
41875
+ if (Array.isArray(title)) {
41876
+ this.textContent = title[ 0 ];
41877
+ this.title = title[ 1 ];
41878
+ } else {
41879
+ this.textContent = this.title = title;
41880
+ }
41881
+
41882
+ this.buttonLabel = buttonLabel;
41883
+
41884
+ this.imageDictionary =
41885
+ {
41886
+ image: `url("data:image/svg+xml,${ encodeURIComponent(imageSVG) }")`,
41887
+ imageHover: `url("data:image/svg+xml,${ encodeURIComponent(imageHoverSVG) }")`,
41888
+ };
41889
+
41890
+ this.responsiveKey = 'text';
41891
+
41892
+ this.configureButton(this.textContent, this.title);
41893
+
41894
+ this.setState(initialButtonState);
41895
+
41896
+ browser.on('navbar-resize', navbarButtonCSSClass => {
41897
+ this.navbarResizeHandler(navbarButtonCSSClass);
41898
+ });
41899
+
41900
+ }
41901
+
41902
+ navbarResizeHandler(navbarButtonCSSClass) {
41903
+ const key = 'igv-navbar-icon-button' === navbarButtonCSSClass ? 'image' : 'text';
41904
+ if (key !== this.responsiveKey) {
41905
+ this.responsiveKey = key;
41906
+ this.configureButton(this.textContent, this.title);
41907
+ this.setState(undefined);
41908
+ }
41909
+ }
41910
+
41911
+ configureButton(textContent, title) {
41912
+
41913
+ this.groupElement = undefined;
41914
+ this.button.title = title;
41915
+ this.button.innerHTML = '';
41916
+ this.button.style.backgroundImage = 'none';
41917
+ this.button.classList.remove('igv-navbar-icon-button');
41918
+ this.button.classList.remove('igv-navbar-text-button');
41919
+
41920
+ 'text' === this.responsiveKey ? this.configureTextButton(textContent) : this.configureIconButton();
41921
+
41922
+ }
41923
+
41924
+ configureTextButton(textContent) {
41925
+
41926
+ this.button.classList.add('igv-navbar-text-button');
41927
+
41928
+ const tempDiv = document.createElement('div');
41929
+ tempDiv.innerHTML = this.buttonLabel;
41930
+ const svgRoot = tempDiv.firstChild;
41931
+ this.button.appendChild(svgRoot);
41932
+
41933
+ this.groupElement = svgRoot.querySelector('#igv-navbar-button-group');
41934
+
41935
+ const tspanElement = svgRoot.querySelector('#igv-navbar-button-label');
41936
+ tspanElement.textContent = textContent;
41937
+ }
41938
+
41939
+ configureIconButton() {
41940
+ this.button.classList.add('igv-navbar-icon-button');
41941
+ }
41942
+
41943
+ setState(doHover) {
41944
+
41945
+ if (undefined !== doHover) {
41946
+ this.doHover = doHover;
41947
+ }
41948
+
41949
+ 'text' === this.responsiveKey ? this.setTextButtonState(this.doHover) : this.setIconButtonState(this.doHover);
41950
+
41951
+ }
41952
+
41953
+ setTextButtonState(doHover) {
41954
+ this.groupElement.classList.remove(...this.groupElement.classList);
41955
+ const className = true === doHover ? 'igv-navbar-text-button-svg-hover' : 'igv-navbar-text-button-svg-inactive';
41956
+ this.groupElement.classList.add(className);
41957
+ }
41958
+
41959
+ setIconButtonState(doHover) {
41960
+ this.button.style.backgroundImage = true === doHover ? this.imageDictionary.imageHover : this.imageDictionary.image;
41961
+ }
41962
+
41963
+ show() {
41964
+ // this.button.style.display = 'block'
41965
+ this.button.style.display = 'flex';
41966
+ }
41967
+
41968
+ hide() {
41969
+ this.button.style.display = 'none';
41970
+ }
41971
+
41972
+ setVisibility(isVisible) {
41973
+ if (true === isVisible) {
41974
+ this.show();
41975
+ } else {
41976
+ this.hide();
41977
+ }
41978
+ }
41979
+
41980
+ static currentNavbarButtonClass(browser) {
41981
+ const el = browser.$navigation.get(0).querySelector('.igv-navbar-text-button');
41982
+ return el ? 'igv-navbar-text-button' : 'igv-navbar-icon-button'
41983
+ }
41984
+ }
41985
+
41986
+ const overlayTrackImage =
41987
+ `<svg width="625px" height="625px" viewBox="0 0 625 625" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
41988
+ <title>Overlay Tracks</title>
41989
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
41990
+ <g id="Overlay-Tracks">
41991
+ <rect id="backdrop" stroke="#737373" stroke-width="12" fill="#FFFFFF" x="6" y="6" width="613" height="613" rx="135"></rect>
41992
+ <g id="layer-group" transform="translate(3, 127)">
41993
+ <rect id="a" stroke="#737373" stroke-width="24" fill="#A1A1A1" x="12" y="12" width="332" height="139"></rect>
41994
+ <rect id="a---hold-out" fill="#A1A1A1" x="9" y="25" width="324" height="115"></rect>
41995
+ <rect id="b" stroke="#737373" stroke-width="24" fill="#C9C9C9" x="81" y="103" width="474" height="139"></rect>
41996
+ <rect id="c" stroke="#737373" stroke-width="24" fill="#ECECEC" x="238" y="214" width="372" height="139"></rect>
41997
+ <rect id="c---hold-out" fill="#ECECEC" x="250" y="226" width="372" height="115"></rect>
41998
+ </g>
41999
+ <rect id="over-border" stroke="#737373" stroke-width="12" x="6" y="6" width="613" height="613" rx="135"></rect>
42000
+ </g>
42001
+ </g>
42002
+ </svg>`;
42003
+
42004
+ const overlayTrackImageHover =
42005
+ `<svg width="625px" height="625px" viewBox="0 0 625 625" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
42006
+ <title>Overlay Tracks Hover</title>
42007
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
42008
+ <g id="Overlay-Tracks-Hover">
42009
+ <rect id="backdrop-copy" stroke="#737373" stroke-width="12" fill="#737373" x="6" y="6" width="613" height="613" rx="135"></rect>
42010
+ <g id="layer-group" transform="translate(3, 127)">
42011
+ <rect id="a" stroke="#FFFFFF" stroke-width="24" fill="#A1A1A1" x="12" y="12" width="332" height="139"></rect>
42012
+ <rect id="a---hold-out" fill="#A1A1A1" x="9" y="25" width="324" height="115"></rect>
42013
+ <rect id="b" stroke="#FFFFFF" stroke-width="24" fill="#C9C9C9" x="81" y="103" width="474" height="139"></rect>
42014
+ <rect id="c" stroke="#FFFFFF" stroke-width="24" fill="#ECECEC" x="238" y="214" width="372" height="139"></rect>
42015
+ <rect id="c---hold-out" fill="#ECECEC" x="250" y="226" width="372" height="115"></rect>
42016
+ </g>
42017
+ <rect id="over-border-copy" stroke="#737373" stroke-width="12" x="6" y="6" width="613" height="613" rx="135"></rect>
42018
+ </g>
42019
+ </g>
42020
+ </svg>`;
42021
+
42022
+ const buttonLabel =
42023
+ `<svg width="80px" height="18px" viewBox="0 0 80 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
42024
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
42025
+ <g id="igv-navbar-button-group">
42026
+ <rect id="Rectangle" x="0.5" y="0.5" width="79" height="17" rx="6"></rect>
42027
+ <text id="igv-text-button-label" x="50%" y="50%" dy=".1em" font-family="Helvetica" font-size="12" font-weight="normal" letter-spacing="-0.372">
42028
+ <tspan id="igv-navbar-button-label"></tspan>
42029
+ </text>
42030
+ </g>
42031
+ </g>
42032
+ </svg>`;
42033
+
42034
+ const sampleNameButtonLabel =
42035
+ `<svg width="90px" height="20px" viewBox="0 0 90 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
42036
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
42037
+ <g id="igv-navbar-button-group">
42038
+ <rect id="Rectangle" x="0.5" y="0.5" width="89" height="18" rx="6"></rect>
42039
+ <text id="igv-text-button-label" x="50%" y="50%" dy=".1em" font-family="Helvetica" font-size="12" font-weight="normal" letter-spacing="-0.372">
42040
+ <tspan id="igv-navbar-button-label"></tspan>
42041
+ </text>
42042
+ </g>
42043
+ </g>
42044
+ </svg>`;
42045
+
42046
+ /*
42047
+ * The MIT License (MIT)
42048
+ *
42049
+ * Copyright (c) 2016-2017 The Regents of the University of California
42050
+ * Author: Jim Robinson
42051
+ *
42052
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
42053
+ * of this software and associated documentation files (the "Software"), to deal
42054
+ * in the Software without restriction, including without limitation the rights
42055
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42056
+ * copies of the Software, and to permit persons to whom the Software is
42057
+ * furnished to do so, subject to the following conditions:
42058
+ *
42059
+ * The above copyright notice and this permission notice shall be included in
42060
+ * all copies or substantial portions of the Software.
42061
+ *
42062
+ *
42063
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42064
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42065
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42066
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42067
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42068
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
42069
+ * THE SOFTWARE.
42070
+ */
41608
42071
 
41609
42072
  /**
41610
- * Represents 2 or more wig tracks overlaid on a common viewport.
42073
+ * Represents 2 or more tracks overlaid on a common viewport.
41611
42074
  */
41612
42075
  class MergedTrack extends TrackBase {
41613
42076
 
41614
42077
  static defaults = {
42078
+ autoscale: undefined,
41615
42079
  alpha: 0.5,
41616
42080
  height: 50
41617
42081
  }
41618
42082
 
41619
- constructor(config, browser) {
42083
+ constructor(config, browser, tracks) {
41620
42084
  super(config, browser);
41621
42085
  this.type = "merged";
41622
- this.featureType = "numeric";
41623
42086
  this.paintAxis = paintAxis;
41624
42087
  this.graphType = config.graphType;
41625
- }
41626
-
41627
- init(config) {
41628
- if (!(config.tracks || config._tracks)) {
41629
- throw Error("Error: no tracks defined for merged track" + config)
42088
+ if (tracks) {
42089
+ this.tracks = tracks; // Dynamic creation, actual track objects (not configurations)
42090
+ } else {
42091
+ this.tracks = [];
41630
42092
  }
41631
- super.init(config);
41632
42093
  }
41633
42094
 
42095
+
41634
42096
  async postInit() {
41635
42097
 
41636
- this.tracks = [];
41637
42098
  if (this.config.tracks) {
41638
- // Configured merged track
42099
+ // Track configurations, this indicates a configured merged track as opposed to dynamic merge through the UI
42100
+ // Actual track objects need to be created.
41639
42101
  for (let tconf of this.config.tracks) {
41640
- tconf.isMergedTrack = true;
41641
42102
  const t = await this.browser.createTrack(tconf);
41642
42103
  if (t) {
41643
42104
  this.tracks.push(t);
@@ -41648,24 +42109,27 @@ class MergedTrack extends TrackBase {
41648
42109
  await t.postInit();
41649
42110
  }
41650
42111
  }
41651
- // Explicit merged settings -- these will override any individual track settings
41652
- if (this.config.autoscale) {
41653
- this.autoscale = this.config.autoscale;
41654
- } else if (this.config.max !== undefined) {
41655
- this.dataRange = {
41656
- min: this.config.min || 0,
41657
- max: this.config.max
41658
- };
41659
- } else {
41660
- this.autoscale = !this.tracks.every(t => t.config.autoscale || t.config.max !== undefined);
42112
+ // Default to autoscale unless scale if range or autoscale is not otherwise defined
42113
+ const allTracksSpecified = this.config.tracks.every(config => config.autoscale !== undefined || config.max !== undefined);
42114
+ if (!allTracksSpecified) {
42115
+ this.config.autoscale = this.config.max === undefined;
41661
42116
  }
41662
- } else {
41663
- // Dynamic merged track
41664
- this.tracks = this.config._tracks;
41665
- this.autoscale = false;
41666
- delete this.config._tracks;
41667
42117
  }
41668
42118
 
42119
+ // Mark constitutive tracks as merged.
42120
+ for (let t of this.tracks) t.isMergedTrack = true;
42121
+
42122
+ // Explicit settings -- these will override any individual track settings
42123
+ if(this.config.autoscale) {
42124
+ this.autoscale = this.config.autoscale;
42125
+ } else if (this.config.max !== undefined) {
42126
+ this.setDataRange ({
42127
+ min: this.config.min || 0,
42128
+ max: this.config.max
42129
+ });
42130
+ }
42131
+
42132
+
41669
42133
  if (this.config.flipAxis !== undefined) {
41670
42134
  for (let t of this.tracks) t.flipAxis = this.config.flipAxis;
41671
42135
  }
@@ -41675,25 +42139,28 @@ class MergedTrack extends TrackBase {
41675
42139
  }
41676
42140
 
41677
42141
  this.resolutionAware = this.tracks.some(t => t.resolutionAware);
41678
-
41679
42142
  }
41680
42143
 
41681
42144
  set flipAxis(b) {
41682
42145
  this.config.flipAxis = b;
41683
- for (let t of this.tracks) t.flipAxis = b;
42146
+ for (let t of numericTracks(this.tracks)) {
42147
+ t.flipAxis = b;
42148
+ }
41684
42149
  }
41685
42150
 
41686
42151
  get flipAxis() {
41687
- return this.tracks.every(t => t.flipAxis)
42152
+ return numericTracks(this.tracks).every(t => t.flipAxis)
41688
42153
  }
41689
42154
 
41690
42155
  set logScale(b) {
41691
42156
  this.config.logScale = b;
41692
- for (let t of this.tracks) t.logScale = b;
42157
+ for (let t of numericTracks(this.tracks)) {
42158
+ t.logScale = b;
42159
+ }
41693
42160
  }
41694
42161
 
41695
42162
  get logScale() {
41696
- return this.tracks.every(t => t.logScale)
42163
+ return numericTracks(this.tracks).every(t => t.logScale)
41697
42164
  }
41698
42165
 
41699
42166
  get height() {
@@ -41711,49 +42178,76 @@ class MergedTrack extends TrackBase {
41711
42178
  }
41712
42179
  }
41713
42180
 
41714
- get dataRange() {
41715
-
41716
- if (undefined === this.tracks || 0 === this.tracks.length) {
41717
- return undefined
42181
+ set autoscale(b) {
42182
+ this._autoscale = b;
42183
+ if(b === false && this.tracks) {
42184
+ for(let t of this.tracks) t.autoscale = false;
41718
42185
  }
42186
+ }
41719
42187
 
41720
- const list = this.tracks.filter(track => undefined !== track.dataRange);
41721
- if (list.length !== this.tracks.length) {
41722
- return undefined
41723
- }
42188
+ get autoscale() {
42189
+ return this._autoscale
42190
+ }
41724
42191
 
41725
- const minSet = new Set(this.tracks.map(({dataRange}) => dataRange.min));
41726
- if (1 !== minSet.size) {
41727
- return undefined
41728
- }
42192
+ /**
42193
+ * Set the data range of all constitutive numeric tracks. This method is called from the menu item, i.e. an explicit
42194
+ * setting, so it should disable autoscale as well.
42195
+ *
42196
+ * @param min
42197
+ * @param max
42198
+ */
41729
42199
 
41730
- const maxSet = new Set(this.tracks.map(({dataRange}) => dataRange.max));
41731
- if (1 !== maxSet.size) {
41732
- return undefined
42200
+ setDataRange({min, max}) {
42201
+ this.autoscale = false;
42202
+ for (const track of numericTracks(this.tracks)) {
42203
+ track.dataRange = {min, max};
42204
+ track.autoscale = false;
42205
+ track.autoscaleGroup = false;
41733
42206
  }
42207
+ }
41734
42208
 
41735
- return { min: [ ...minSet ][ 0 ], max: [ ...maxSet ][ 0 ] }
42209
+ set dataRange({min, max}) {
42210
+ for (const track of numericTracks(this.tracks)) {
42211
+ track.dataRange = {min, max};
42212
+ }
41736
42213
  }
41737
42214
 
41738
- set dataRange({ min, max }) {
41739
- for (const track of this.tracks) {
41740
- track.dataRange = { min, max };
42215
+ /**
42216
+ * Return a DataRang {min, max} if all constitutive numeric tracks have identical range. A numeric track is defined
42217
+ * as a track with a data range. Otherwise return undefined.
42218
+ *
42219
+ * @returns {{min: any, max: any}|undefined}
42220
+ */
42221
+ get dataRange() {
42222
+ if(this.tracks) {
42223
+ const num = numericTracks(this.tracks);
42224
+ if (num.length > 0) {
42225
+ const firstRange = num[0].dataRange;
42226
+ if (num.every(t => t.dataRange && t.dataRange.min === firstRange.min && t.dataRange.max === firstRange.max)) {
42227
+ return firstRange
42228
+ }
42229
+ }
41741
42230
  }
42231
+ return undefined
41742
42232
  }
41743
42233
 
42234
+
41744
42235
  menuItemList() {
41745
42236
  const items = [];
41746
- if (this.flipAxis !== undefined) {
41747
- items.push({
41748
- label: "Flip y-axis",
41749
- click: function flipYAxisHandler() {
41750
- this.flipAxis = !this.flipAxis;
41751
- this.trackView.repaintViews();
41752
- }
41753
- });
41754
- }
42237
+ if (numericTracks(this.tracks).length > 0) {
41755
42238
 
41756
- items.push(...this.numericDataMenuItems());
42239
+ if (this.flipAxis !== undefined) {
42240
+ items.push({
42241
+ label: "Flip y-axis",
42242
+ click: function flipYAxisHandler() {
42243
+ this.flipAxis = !this.flipAxis;
42244
+ this.trackView.repaintViews();
42245
+ }
42246
+ });
42247
+ }
42248
+
42249
+ items.push(...this.numericDataMenuItems());
42250
+ }
41757
42251
 
41758
42252
  items.push('<hr/>');
41759
42253
  items.push(this.overlayTrackAlphaAdjustmentMenuItem());
@@ -41770,7 +42264,14 @@ class MergedTrack extends TrackBase {
41770
42264
 
41771
42265
  const promises = this.tracks.map((t) => t.getFeatures(chr, bpStart, bpEnd, bpPerPixel));
41772
42266
  const featureArrays = await Promise.all(promises);
41773
- return new MergedFeatureCollection(featureArrays)
42267
+
42268
+ if (featureArrays.every((arr) => arr.length === 0)){
42269
+ return new MergedFeatureCollection([], [])
42270
+ }
42271
+ else {
42272
+ const trackNames = this.tracks.map((t) => t.name);
42273
+ return new MergedFeatureCollection(featureArrays, trackNames)
42274
+ }
41774
42275
  }
41775
42276
 
41776
42277
  draw(options) {
@@ -41782,11 +42283,6 @@ class MergedTrack extends TrackBase {
41782
42283
  trackOptions.features = mergedFeatures.featureArrays[i];
41783
42284
  trackOptions.alpha = this.alpha;
41784
42285
 
41785
- if (this.dataRange) {
41786
- // Single data scale for all tracks
41787
- this.tracks[i].dataRange = this.dataRange;
41788
- }
41789
-
41790
42286
  if (this.graphType) {
41791
42287
  this.tracks[i].graphType = this.graphType;
41792
42288
  }
@@ -41880,6 +42376,7 @@ class MergedTrack extends TrackBase {
41880
42376
  let scaleChange;
41881
42377
 
41882
42378
  if (this.autoscale) {
42379
+ // Overrides any specific track scale settings
41883
42380
  scaleChange = true;
41884
42381
  let allFeatures = [];
41885
42382
  for (let visibleViewport of visibleViewports) {
@@ -41894,7 +42391,12 @@ class MergedTrack extends TrackBase {
41894
42391
  allFeatures.push({value: mergedFeatureCollection.getMin(start, end)});
41895
42392
  }
41896
42393
  }
41897
- this.dataRange = doAutoscale(allFeatures);
42394
+ const dataRange = doAutoscale(allFeatures);
42395
+ for (const track of numericTracks(this.tracks)) {
42396
+ // Do not use this.dataRange, as that has side effects
42397
+ track.dataRange = dataRange;
42398
+ }
42399
+
41898
42400
  }
41899
42401
  } else {
41900
42402
  // Individual track scaling
@@ -41974,6 +42476,7 @@ class MergedTrack extends TrackBase {
41974
42476
  if (groupAutoscale) {
41975
42477
  track.autoscaleGroup = name;
41976
42478
  }
42479
+ track.isMergedTrack = false;
41977
42480
  browser.addTrack(track.config, track);
41978
42481
  }
41979
42482
  browser.updateViews();
@@ -41987,25 +42490,30 @@ class MergedTrack extends TrackBase {
41987
42490
 
41988
42491
  class MergedFeatureCollection {
41989
42492
 
41990
- constructor(featureArrays) {
42493
+ constructor(featureArrays,trackNames) {
41991
42494
  this.featureArrays = featureArrays;
42495
+ //trackNames is needed for the popup data to populate track names
42496
+ //preserving the order of the actual tracks
42497
+ this.trackNames = trackNames;
41992
42498
  }
41993
42499
 
41994
42500
  getMax(start, end) {
41995
42501
  let max = -Number.MAX_VALUE;
41996
42502
 
41997
42503
  for (let a of this.featureArrays) {
41998
- for (let f of a) {
41999
- if (typeof f.value === 'undefined' || Number.isNaN(f.value)) {
42000
- continue
42001
- }
42002
- if (f.end < start) {
42003
- continue
42004
- }
42005
- if (f.start > end) {
42006
- break
42504
+ if (Array.isArray(a)) {
42505
+ for (let f of a) {
42506
+ if (typeof f.value === 'undefined' || Number.isNaN(f.value)) {
42507
+ continue
42508
+ }
42509
+ if (f.end < start) {
42510
+ continue
42511
+ }
42512
+ if (f.start > end) {
42513
+ break
42514
+ }
42515
+ max = Math.max(max, f.value);
42007
42516
  }
42008
- max = Math.max(max, f.value);
42009
42517
  }
42010
42518
  }
42011
42519
 
@@ -42016,15 +42524,17 @@ class MergedFeatureCollection {
42016
42524
  getMin(start, end) {
42017
42525
  let min = 0;
42018
42526
  for (let a of this.featureArrays) {
42019
- for (let f of a) {
42020
- if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
42021
- if (f.end < start) {
42022
- continue
42023
- }
42024
- if (f.start > end) {
42025
- break
42527
+ if (Array.isArray(a)) {
42528
+ for (let f of a) {
42529
+ if (typeof f.value !== 'undefined' && !Number.isNaN(f.value)) {
42530
+ if (f.end < start) {
42531
+ continue
42532
+ }
42533
+ if (f.start > end) {
42534
+ break
42535
+ }
42536
+ min = Math.min(min, f.value);
42026
42537
  }
42027
- min = Math.min(min, f.value);
42028
42538
  }
42029
42539
  }
42030
42540
  }
@@ -42032,6 +42542,16 @@ class MergedFeatureCollection {
42032
42542
  }
42033
42543
  }
42034
42544
 
42545
+ /**
42546
+ * Heuristic for finding numeric tracks.
42547
+ *
42548
+ * @param tracks
42549
+ * @returns {*}
42550
+ */
42551
+ const numericTracks = (tracks) => {
42552
+ return tracks ? tracks.filter(track => undefined !== track.dataRange || undefined !== track.autoscale || undefined !== track.autoscaleGroup) : []
42553
+ };
42554
+
42035
42555
  class OverlayTrackButton extends NavbarButton {
42036
42556
  constructor(browser, parent) {
42037
42557
 
@@ -42077,13 +42597,13 @@ function trackOverlayClickHandler(e) {
42077
42597
  {
42078
42598
  name: 'Overlay',
42079
42599
  type: 'merged',
42600
+ autoscale: false,
42080
42601
  alpha: 0.5, //fudge * (1.0/tracks.length),
42081
42602
  height: Math.max(...tracks.map(({ height }) => height)),
42082
42603
  order: Math.min(...tracks.map(({ order }) => order)),
42083
- _tracks: flattenedTracks
42084
42604
  };
42085
42605
 
42086
- const mergedTrack = new MergedTrack(config, this.browser);
42606
+ const mergedTrack = new MergedTrack(config, this.browser, flattenedTracks);
42087
42607
 
42088
42608
  for (const track of tracks) {
42089
42609
  this.browser.removeTrack(track);
@@ -42303,27 +42823,6 @@ class TrackView {
42303
42823
  }
42304
42824
  }
42305
42825
 
42306
- get dataRange() {
42307
- return this.track.dataRange ? this.track.dataRange : undefined
42308
- }
42309
-
42310
- set dataRange({ min, max }) {
42311
-
42312
- this.track.dataRange = { min, max };
42313
-
42314
- this.track.autoscale = false;
42315
- this.track.autoscaleGroup = undefined;
42316
-
42317
- const list = this.browser.trackViews.filter(({track}) => track.autoscaleGroup);
42318
- if (1 === list.length) {
42319
- list[0].track.autoscale = false;
42320
- list[0].track.autoscaleGroup = undefined;
42321
- list[0].repaintViews();
42322
- }
42323
-
42324
- this.repaintViews();
42325
-
42326
- }
42327
42826
 
42328
42827
  presentColorPicker(key) {
42329
42828
 
@@ -43139,462 +43638,6 @@ function renderSVGAxis(context, track, axisCanvas, deltaX, deltaY) {
43139
43638
 
43140
43639
  }
43141
43640
 
43142
- const DEFAULT_COLOR$1 = 'rgb(150, 150, 150)';
43143
-
43144
-
43145
- class WigTrack extends TrackBase {
43146
-
43147
- static defaults = {
43148
- height: 50,
43149
- flipAxis: false,
43150
- logScale: false,
43151
- windowFunction: 'mean',
43152
- graphType: 'bar',
43153
- normalize: undefined,
43154
- scaleFactor: undefined,
43155
- overflowColor: `rgb(255, 32, 255)`,
43156
- baselineColor: 'lightGray',
43157
- summarize: true
43158
- }
43159
-
43160
- constructor(config, browser) {
43161
- super(config, browser);
43162
- }
43163
-
43164
- init(config) {
43165
-
43166
- super.init(config);
43167
-
43168
- this.type = "wig";
43169
- this.featureType = 'numeric';
43170
- this.resolutionAware = true;
43171
- this.paintAxis = paintAxis;
43172
-
43173
- const format = config.format ? config.format.toLowerCase() : config.format;
43174
- if (config.featureSource) {
43175
- this.featureSource = config.featureSource;
43176
- delete config.featureSource;
43177
- } else if ("bigwig" === format) {
43178
- this.featureSource = new BWSource(config, this.browser.genome);
43179
- } else if ("tdf" === format) {
43180
- this.featureSource = new TDFSource(config, this.browser.genome);
43181
- } else {
43182
- this.featureSource = FeatureSource(config, this.browser.genome);
43183
- }
43184
-
43185
-
43186
- // Override autoscale default
43187
- if (config.max === undefined || config.autoscale === true) {
43188
- this.autoscale = true;
43189
- } else {
43190
- this.dataRange = {
43191
- min: config.min || 0,
43192
- max: config.max
43193
- };
43194
- }
43195
- }
43196
-
43197
- async postInit() {
43198
- const header = await this.getHeader();
43199
- if (this.disposed) return // This track was removed during async load
43200
- if (header) this.setTrackProperties(header);
43201
- }
43202
-
43203
- async getFeatures(chr, start, end, bpPerPixel) {
43204
-
43205
- const windowFunction = this.windowFunction;
43206
-
43207
- const features = await this.featureSource.getFeatures({
43208
- chr,
43209
- start,
43210
- end,
43211
- bpPerPixel,
43212
- visibilityWindow: this.visibilityWindow,
43213
- windowFunction
43214
- });
43215
- if (this.normalize && this.featureSource.normalizationFactor) {
43216
- const scaleFactor = this.featureSource.normalizationFactor;
43217
- for (let f of features) {
43218
- f.value *= scaleFactor;
43219
- }
43220
- }
43221
- if (this.scaleFactor) {
43222
- const scaleFactor = this.scaleFactor;
43223
- for (let f of features) {
43224
- f.value *= scaleFactor;
43225
- }
43226
- }
43227
-
43228
- // Summarize features to current resolution. This needs to be done here, rather than in the "draw" function,
43229
- // for group autoscale to work.
43230
- if (this.summarize && ("mean" === windowFunction || "min" === windowFunction || "max" === windowFunction)) {
43231
- return summarizeData(features, start, bpPerPixel, windowFunction)
43232
- } else {
43233
- return features
43234
- }
43235
- }
43236
-
43237
- menuItemList() {
43238
- const items = [];
43239
-
43240
- if (this.flipAxis !== undefined) {
43241
- items.push('<hr>');
43242
-
43243
- function click() {
43244
- this.flipAxis = !this.flipAxis;
43245
- this.trackView.repaintViews();
43246
- }
43247
-
43248
- items.push({label: 'Flip y-axis', click});
43249
- }
43250
-
43251
- if(this.featureSource.windowFunctions) {
43252
- items.push(...this.wigSummarizationItems());
43253
- }
43254
-
43255
- items.push(...this.numericDataMenuItems());
43256
-
43257
- return items
43258
- }
43259
-
43260
- wigSummarizationItems() {
43261
-
43262
- const windowFunctions = this.featureSource.windowFunctions;
43263
-
43264
- const menuItems = [];
43265
- menuItems.push('<hr/>');
43266
- menuItems.push("<div>Windowing function</div>");
43267
- for (const wf of windowFunctions) {
43268
- const object = $$1(createCheckbox(wf, this.windowFunction === wf));
43269
-
43270
- function clickHandler() {
43271
- this.windowFunction = wf;
43272
- this.trackView.updateViews();
43273
- }
43274
-
43275
- menuItems.push({object, click: clickHandler});
43276
- }
43277
-
43278
- return menuItems
43279
- }
43280
-
43281
-
43282
- async getHeader() {
43283
-
43284
- if (typeof this.featureSource.getHeader === "function") {
43285
- this.header = await this.featureSource.getHeader();
43286
- }
43287
- return this.header
43288
- }
43289
-
43290
- // TODO: refactor to igvUtils.js
43291
- getScaleFactor(min, max, height, logScale) {
43292
- const scale = logScale ? height / (Math.log10(max + 1) - (min <= 0 ? 0 : Math.log10(min + 1))) : height / (max - min);
43293
- return scale
43294
- }
43295
-
43296
- computeYPixelValue(yValue, yScaleFactor) {
43297
- return (this.flipAxis ? (yValue - this.dataRange.min) : (this.dataRange.max - yValue)) * yScaleFactor
43298
- }
43299
-
43300
- computeYPixelValueInLogScale(yValue, yScaleFactor) {
43301
- let maxValue = this.dataRange.max;
43302
- let minValue = this.dataRange.min;
43303
- if (maxValue <= 0) return 0 // TODO:
43304
- if (minValue <= -1) minValue = 0;
43305
- minValue = (minValue <= 0) ? 0 : Math.log10(minValue + 1);
43306
- maxValue = Math.log10(maxValue + 1);
43307
- yValue = Math.log10(yValue + 1);
43308
- return ((this.flipAxis ? (yValue - minValue) : (maxValue - yValue)) * yScaleFactor)
43309
- }
43310
-
43311
- draw(options) {
43312
-
43313
- const features = options.features;
43314
- const ctx = options.context;
43315
- const bpPerPixel = options.bpPerPixel;
43316
- const bpStart = options.bpStart;
43317
- const pixelWidth = options.pixelWidth;
43318
- const pixelHeight = options.pixelHeight;
43319
- const bpEnd = bpStart + pixelWidth * bpPerPixel + 1;
43320
- this.color || DEFAULT_COLOR$1;
43321
- const scaleFactor = this.getScaleFactor(this.dataRange.min, this.dataRange.max, options.pixelHeight, this.logScale);
43322
- const yScale = (yValue) => this.logScale
43323
- ? this.computeYPixelValueInLogScale(yValue, scaleFactor)
43324
- : this.computeYPixelValue(yValue, scaleFactor);
43325
-
43326
- if (features && features.length > 0) {
43327
-
43328
- if (this.dataRange.min === undefined) this.dataRange.min = 0;
43329
-
43330
- // Max can be less than min if config.min is set but max left to autoscale. If that's the case there is
43331
- // nothing to paint.
43332
- if (this.dataRange.max > this.dataRange.min) {
43333
-
43334
- let lastPixelEnd = -1;
43335
- let lastY;
43336
- const y0 = yScale(0);
43337
-
43338
- for (let f of features) {
43339
-
43340
- if (f.end < bpStart) continue
43341
- if (f.start > bpEnd) break
43342
-
43343
- const x = (f.start - bpStart) / bpPerPixel;
43344
- if (isNaN(x)) continue
43345
-
43346
- let y = yScale(f.value);
43347
-
43348
- const rectEnd = (f.end - bpStart) / bpPerPixel;
43349
- const width = rectEnd - x;
43350
-
43351
- const color = options.alpha ? IGVColor.addAlpha(this.getColorForFeature(f), options.alpha) : this.getColorForFeature(f);
43352
-
43353
- if (this.graphType === "line") {
43354
- if (lastY !== undefined) {
43355
- IGVGraphics.strokeLine(ctx, lastPixelEnd, lastY, x, y, {
43356
- "fillStyle": color,
43357
- "strokeStyle": color
43358
- });
43359
- }
43360
- IGVGraphics.strokeLine(ctx, x, y, x + width, y, {"fillStyle": color, "strokeStyle": color});
43361
- } else if (this.graphType === "points") {
43362
- const pointSize = this.config.pointSize || 3;
43363
- const px = x + width / 2;
43364
- IGVGraphics.fillCircle(ctx, px, y, pointSize / 2, {"fillStyle": color, "strokeStyle": color});
43365
-
43366
- if (f.value > this.dataRange.max) {
43367
- IGVGraphics.fillCircle(ctx, px, pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
43368
- } else if (f.value < this.dataRange.min) {
43369
- IGVGraphics.fillCircle(ctx, px, pixelHeight - pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor});
43370
- }
43371
-
43372
- } else {
43373
- // Default graph type (bar)
43374
- const height = Math.min(pixelHeight, y - y0);
43375
- IGVGraphics.fillRect(ctx, x, y0, width, height, {fillStyle: color});
43376
- if (f.value > this.dataRange.max) {
43377
- IGVGraphics.fillRect(ctx, x, 0, width, 3, {fillStyle: this.overflowColor});
43378
- } else if (f.value < this.dataRange.min) {
43379
- IGVGraphics.fillRect(ctx, x, pixelHeight - 3, width, 3, {fillStyle: this.overflowColor});
43380
- }
43381
-
43382
- }
43383
- lastPixelEnd = x + width;
43384
- lastY = y;
43385
- }
43386
-
43387
- // If the track includes negative values draw a baseline
43388
- if (this.dataRange.min < 0) {
43389
- const ratio = this.dataRange.max / (this.dataRange.max - this.dataRange.min);
43390
- const basepx = this.flipAxis ? (1 - ratio) * options.pixelHeight : ratio * options.pixelHeight;
43391
- IGVGraphics.strokeLine(ctx, 0, basepx, options.pixelWidth, basepx, {strokeStyle: this.baselineColor});
43392
- }
43393
- }
43394
- }
43395
-
43396
- // Draw guidelines
43397
- if (this.config.hasOwnProperty('guideLines')) {
43398
- for (let line of this.config.guideLines) {
43399
- if (line.hasOwnProperty('color') && line.hasOwnProperty('y') && line.hasOwnProperty('dotted')) {
43400
- let y = yScale(line.y);
43401
- let props = {
43402
- 'strokeStyle': line['color'],
43403
- 'strokeWidth': 2
43404
- };
43405
- if (line['dotted']) IGVGraphics.dashedLine(options.context, 0, y, options.pixelWidth, y, 5, props);
43406
- else IGVGraphics.strokeLine(options.context, 0, y, options.pixelWidth, y, props);
43407
- }
43408
- }
43409
- }
43410
- }
43411
-
43412
- popupData(clickState, features) {
43413
-
43414
- if (features === undefined) features = this.clickedFeatures(clickState);
43415
-
43416
- if (features && features.length > 0) {
43417
-
43418
- const genomicLocation = clickState.genomicLocation;
43419
- const popupData = [];
43420
-
43421
- // Sort features based on distance from click
43422
- features.sort(function (a, b) {
43423
- const distA = Math.abs((a.start + a.end) / 2 - genomicLocation);
43424
- const distB = Math.abs((b.start + b.end) / 2 - genomicLocation);
43425
- return distA - distB
43426
- });
43427
-
43428
- // Display closest 10
43429
- const displayFeatures = features.length > 10 ? features.slice(0, 10) : features;
43430
-
43431
- // Resort in ascending order
43432
- displayFeatures.sort(function (a, b) {
43433
- return a.start - b.start
43434
- });
43435
-
43436
- for (let selectedFeature of displayFeatures) {
43437
- if (selectedFeature) {
43438
- if (popupData.length > 0) {
43439
- popupData.push('<hr/>');
43440
- }
43441
- let posString = (selectedFeature.end - selectedFeature.start) === 1 ?
43442
- numberFormatter$1(Math.floor(selectedFeature.start) + 1)
43443
- : numberFormatter$1(Math.floor(selectedFeature.start) + 1) + "-" + numberFormatter$1(Math.floor(selectedFeature.end));
43444
- popupData.push({name: "Position:", value: posString});
43445
- popupData.push({
43446
- name: "Value:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;",
43447
- value: numberFormatter$1(selectedFeature.value.toFixed(4))
43448
- });
43449
- }
43450
- }
43451
- if (displayFeatures.length < features.length) {
43452
- popupData.push("<hr/>...");
43453
- }
43454
-
43455
- return popupData
43456
-
43457
- } else {
43458
- return []
43459
- }
43460
- }
43461
-
43462
- get supportsWholeGenome() {
43463
- return !this.config.indexURL && this.config.supportsWholeGenome !== false
43464
- }
43465
-
43466
- /**
43467
- * Return color for feature.
43468
- * @param feature
43469
- * @returns {string}
43470
- */
43471
-
43472
- getColorForFeature(f) {
43473
- let c = (f.value < 0 && this.altColor) ? this.altColor : this.color || DEFAULT_COLOR$1;
43474
- return (typeof c === "function") ? c(f.value) : c
43475
- }
43476
-
43477
- /**
43478
- * Called when the track is removed. Do any needed cleanup here
43479
- */
43480
- dispose() {
43481
- this.trackView = undefined;
43482
- }
43483
-
43484
- }
43485
-
43486
- /**
43487
- * Summarize wig data in bins of size "bpPerPixel" with the given window function.
43488
- *
43489
- * @param features wig (numeric) data -- features cannot overlap, and are in ascending order by start position
43490
- * @param startBP bp start position for computing binned data
43491
- * @param bpPerPixel bp per pixel (bin)
43492
- * @param windowFunction mean, min, or max
43493
- * @returns {*|*[]}
43494
- */
43495
- function summarizeData(features, startBP, bpPerPixel, windowFunction = "mean") {
43496
-
43497
- if (bpPerPixel <= 1 || !features || features.length === 0) {
43498
- return features
43499
- }
43500
-
43501
- // Assume features are sorted by position. Wig features cannot overlap. Note, UCSC "reductionLevel" == bpPerPixel
43502
- const chr = features[0].chr;
43503
- const binSize = bpPerPixel;
43504
- const summaryFeatures = [];
43505
-
43506
- const finishBin = (bin) => {
43507
- const start = startBP + bin.bin * binSize;
43508
- const end = start + binSize;
43509
- let value;
43510
- switch (windowFunction) {
43511
- case "mean":
43512
- value = bin.sumData / bin.count;
43513
- break
43514
- case "max":
43515
- value = bin.max;
43516
- break
43517
- case "min":
43518
- value = bin.min;
43519
- break
43520
- default:
43521
- throw Error(`Unknown window function: ${windowFunction}`)
43522
- }
43523
- const description = `${windowFunction} of ${bin.count} values`;
43524
- summaryFeatures.push({chr, start, end, value, description});
43525
- };
43526
-
43527
- let currentBinData;
43528
- for (let f of features) {
43529
-
43530
- // Loop through bins this feature overlaps, updating the weighted sum for each bin or min/max,
43531
- // depending on window function
43532
- let startBin = Math.floor((f.start - startBP) / binSize);
43533
- const endBin = Math.floor((f.end - startBP) / binSize);
43534
-
43535
- if (currentBinData && startBin === currentBinData.bin) {
43536
- currentBinData.add(f);
43537
- startBin++;
43538
- }
43539
-
43540
- if (!currentBinData || endBin > currentBinData.bin) {
43541
-
43542
- if(currentBinData) {
43543
- finishBin(currentBinData);
43544
- }
43545
-
43546
- // Feature stretches across multiple bins.
43547
- if (endBin > startBin) {
43548
- const end = startBP + endBin * binSize;
43549
- summaryFeatures.push({chr, start: f.start, end, value: f.value});
43550
- }
43551
-
43552
- currentBinData = new SummaryBinData(endBin, f);
43553
- }
43554
-
43555
- }
43556
- if(currentBinData) {
43557
- finishBin(currentBinData);
43558
- }
43559
-
43560
- // Consolidate
43561
- const c = [];
43562
- let lastFeature = summaryFeatures[0];
43563
- for (let f of summaryFeatures) {
43564
- if (lastFeature.value === f.value && f.start <= lastFeature.end) {
43565
- lastFeature.end = f.end;
43566
- } else {
43567
- c.push(lastFeature);
43568
- lastFeature = f;
43569
- }
43570
- }
43571
- c.push(lastFeature);
43572
-
43573
- return c
43574
-
43575
- }
43576
-
43577
- class SummaryBinData {
43578
- constructor(bin, feature) {
43579
- this.bin = bin;
43580
- this.sumData = feature.value;
43581
- this.count = 1;
43582
- this.min = feature.value;
43583
- this.max = feature.value;
43584
- }
43585
-
43586
- add(feature) {
43587
- this.sumData += feature.value;
43588
- this.max = Math.max(feature.value, this.max);
43589
- this.min = Math.min(feature.value, this.min);
43590
- this.count++;
43591
- }
43592
-
43593
- get mean() {
43594
- return this.sumData / this.count
43595
- }
43596
- }
43597
-
43598
43641
  /**
43599
43642
  *
43600
43643
  * @param cs - object containing
@@ -48509,7 +48552,7 @@ class AlignmentContainer {
48509
48552
  allAlignments() {
48510
48553
  if (this.alignments) {
48511
48554
  return this.alignments
48512
- } else {
48555
+ } else if (this.packedGroups) {
48513
48556
  const all = Array.from(this.packedGroups.values()).flatMap(group => group.rows.flatMap(row => row.alignments));
48514
48557
  if (this.#unpacked && this.#unpacked.length > 0) {
48515
48558
  for (let a of this.#unpacked) {
@@ -48517,6 +48560,8 @@ class AlignmentContainer {
48517
48560
  }
48518
48561
  }
48519
48562
  return all
48563
+ } else {
48564
+ return []
48520
48565
  }
48521
48566
  }
48522
48567
 
@@ -48525,9 +48570,10 @@ class AlignmentContainer {
48525
48570
  }
48526
48571
 
48527
48572
  sortRows(options) {
48528
-
48529
- for (let group of this.packedGroups.values()) {
48530
- group.sortRows(options, this);
48573
+ if(this.packedGroups) {
48574
+ for (let group of this.packedGroups.values()) {
48575
+ group.sortRows(options, this);
48576
+ }
48531
48577
  }
48532
48578
  }
48533
48579
  }
@@ -49772,46 +49818,41 @@ const BamUtils = {
49772
49818
  },
49773
49819
 
49774
49820
  /**
49821
+ * @param ba - UInt8Array bytes to decode
49775
49822
  *
49776
- * @param ba bytes to decode as a UInt8Array
49777
- * @param genome optional igv genome object
49778
- * @returns {{ magicNumer: number, size: number, chrNames: Array, chrToIndex: ({}|*), chrAliasTable: ({}|*) }}
49823
+ * @returns {{size: *, chrNames: *[], magicNumber: *, chrToIndex: {}}}
49779
49824
  */
49780
- decodeBamHeader: function (ba, genome) {
49825
+ decodeBamHeader: function (ba) {
49781
49826
 
49782
- var magic, samHeaderLen, samHeader, chrToIndex, chrNames;
49783
49827
 
49784
- magic = readInt(ba, 0);
49828
+ const magic = readInt(ba, 0);
49785
49829
  if (magic !== BAM1_MAGIC_NUMBER) {
49786
49830
  throw new Error('BAM header errror: bad magic number. This could be caused by either a corrupt or missing file.')
49787
49831
  }
49788
49832
 
49789
- samHeaderLen = readInt(ba, 4);
49790
- samHeader = '';
49791
-
49833
+ const samHeaderLen = readInt(ba, 4);
49834
+ let samHeader = '';
49792
49835
  for (var i = 0; i < samHeaderLen; ++i) {
49793
49836
  samHeader += String.fromCharCode(ba[i + 8]);
49794
49837
  }
49795
49838
 
49796
- var nRef = readInt(ba, samHeaderLen + 8);
49797
- var p = samHeaderLen + 12;
49839
+ const nRef = readInt(ba, samHeaderLen + 8);
49840
+ let p = samHeaderLen + 12;
49798
49841
 
49799
- chrToIndex = {};
49800
- chrNames = [];
49842
+ const chrToIndex = {};
49843
+ const chrNames = [];
49801
49844
 
49802
49845
  for (i = 0; i < nRef; ++i) {
49803
- var lName = readInt(ba, p);
49804
- var name = '';
49805
- for (var j = 0; j < lName - 1; ++j) {
49846
+ const len = readInt(ba, p);
49847
+ let name = '';
49848
+ for (var j = 0; j < len - 1; ++j) {
49806
49849
  name += String.fromCharCode(ba[p + 4 + j]);
49807
49850
  }
49808
- readInt(ba, p + lName + 4);
49809
- //dlog(name + ': ' + lRef);
49810
49851
 
49811
49852
  chrToIndex[name] = i;
49812
49853
  chrNames[i] = name;
49813
49854
 
49814
- p = p + 8 + lName;
49855
+ p = p + 8 + len;
49815
49856
  }
49816
49857
 
49817
49858
  return {
@@ -50801,8 +50842,8 @@ class HtsgetBamReader extends HtsgetReader {
50801
50842
  const ba = unbgzf(compressedData.buffer);
50802
50843
  this.header = BamUtils.decodeBamHeader(ba, this.genome);
50803
50844
  this.chrAliasTable = new Map();
50804
- for (let key of Object.keys(this.header.chrAliasTable)) {
50805
- this.chrAliasTable.set(key, this.header.chrAliasTable[key]);
50845
+ for (let name of this.header.chrNames) {
50846
+ this.chrAliasTable.set(name, this.genome.getChromosomeName(name));
50806
50847
  }
50807
50848
  }
50808
50849
 
@@ -50812,6 +50853,7 @@ class HtsgetBamReader extends HtsgetReader {
50812
50853
 
50813
50854
  // BAM decoding
50814
50855
  const ba = unbgzf(compressedData.buffer);
50856
+ this.header = BamUtils.decodeBamHeader(ba, this.genome);
50815
50857
 
50816
50858
  const chrIdx = this.header.chrToIndex[chr];
50817
50859
  const alignmentContainer = new AlignmentContainer(chr, start, end, this.config);
@@ -57321,23 +57363,24 @@ class AlignmentTrack extends TrackBase {
57321
57363
  const y = clickState.y;
57322
57364
  const offsetY = y - this.top;
57323
57365
  const genomicLocation = clickState.genomicLocation;
57324
- const showSoftClips = this.showSoftClips;
57325
-
57326
- let minGroupY = Number.MAX_VALUE;
57327
- for (let group of features.packedGroups.values()) {
57328
- minGroupY = Math.min(minGroupY, group.pixelTop);
57329
- if (offsetY > group.pixelTop && offsetY <= group.pixelBottom) {
57330
-
57331
- const alignmentRowHeight = this.displayMode === "SQUISHED" ?
57332
- this.squishedRowHeight :
57333
- this.alignmentRowHeight;
57334
-
57335
- let packedAlignmentsIndex = Math.floor((offsetY - group.pixelTop) / alignmentRowHeight);
57336
-
57337
- if (packedAlignmentsIndex >= 0 && packedAlignmentsIndex < group.length) {
57338
- const alignmentRow = group.rows[packedAlignmentsIndex];
57339
- const clicked = alignmentRow.alignments.filter(alignment => alignment.containsLocation(genomicLocation, showSoftClips));
57340
- if (clicked.length > 0) return clicked[0]
57366
+
57367
+ if(features.packedGroups) {
57368
+ let minGroupY = Number.MAX_VALUE;
57369
+ for (let group of features.packedGroups.values()) {
57370
+ minGroupY = Math.min(minGroupY, group.pixelTop);
57371
+ if (offsetY > group.pixelTop && offsetY <= group.pixelBottom) {
57372
+
57373
+ const alignmentRowHeight = this.displayMode === "SQUISHED" ?
57374
+ this.squishedRowHeight :
57375
+ this.alignmentRowHeight;
57376
+
57377
+ let packedAlignmentsIndex = Math.floor((offsetY - group.pixelTop) / alignmentRowHeight);
57378
+
57379
+ if (packedAlignmentsIndex >= 0 && packedAlignmentsIndex < group.length) {
57380
+ const alignmentRow = group.rows[packedAlignmentsIndex];
57381
+ const clicked = alignmentRow.alignments.filter(alignment => alignment.containsLocation(genomicLocation, this.showSoftClips));
57382
+ if (clicked.length > 0) return clicked[0]
57383
+ }
57341
57384
  }
57342
57385
  }
57343
57386
  }
@@ -57656,6 +57699,7 @@ class CoverageTrack {
57656
57699
 
57657
57700
 
57658
57701
  constructor(config, parent) {
57702
+ this.featureType = 'numeric';
57659
57703
  this.parent = parent;
57660
57704
  this.featureSource = parent.featureSource;
57661
57705
 
@@ -66134,12 +66178,12 @@ class CNVPytorTrack extends TrackBase {
66134
66178
  if (this.signals.includes(signal_name)) {
66135
66179
  let tconf = {};
66136
66180
  tconf.type = "wig";
66137
- tconf.isMergedTrack = true;
66138
66181
  tconf.features = wig;
66139
66182
  tconf.name = signal_name;
66140
66183
  tconf.color = this.signal_colors.filter(x => x.singal_name === signal_name).map(x => x.color);
66141
66184
  const t = await this.browser.createTrack(tconf);
66142
66185
  if (t) {
66186
+ t.isMergedTrack = true;
66143
66187
  t.autoscale = false; // Scaling done from merged track
66144
66188
  this.tracks.push(t);
66145
66189
  } else {
@@ -66318,12 +66362,12 @@ class CNVPytorTrack extends TrackBase {
66318
66362
  if (this.signals.includes(signal_name)) {
66319
66363
  let tconf = {};
66320
66364
  tconf.type = "wig";
66321
- tconf.isMergedTrack = true;
66322
66365
  tconf.features = wig;
66323
66366
  tconf.name = signal_name;
66324
66367
  tconf.color = this.signal_colors.filter(x => x.singal_name === signal_name).map(x => x.color);
66325
66368
  const t = await this.browser.createTrack(tconf);
66326
66369
  if (t) {
66370
+ t.isMergedTrack = true;
66327
66371
  t.autoscale = false; // Scaling done from merged track
66328
66372
  this.tracks.push(t);
66329
66373
  } else {
@@ -66716,23 +66760,26 @@ class VariantTrack extends TrackBase {
66716
66760
  this.header = await this.getHeader();
66717
66761
 
66718
66762
  // Set colorBy, if not explicitly set default to allele frequency, if available, otherwise default to none (undefined)
66719
- const infoFields = new Set(Object.keys(this.header.INFO));
66720
- if (this.config.colorBy) {
66721
- this.colorBy = this.config.colorBy;
66722
- } else if (!this.config.color && infoFields.has('AF')) {
66723
- this.colorBy = 'AF';
66724
- }
66763
+ if(this.header.INFO) {
66764
+ const infoFields = new Set(Object.keys(this.header.INFO));
66765
+ if (this.config.colorBy) {
66766
+ this.colorBy = this.config.colorBy;
66767
+ } else if (!this.config.color && infoFields.has('AF')) {
66768
+ this.colorBy = 'AF';
66769
+ }
66725
66770
 
66726
- // Configure menu items based on info available
66727
- if (infoFields.has('AF')) {
66728
- this._colorByItems.set('AF', 'Allele frequency');
66729
- }
66730
- if (infoFields.has('VT')) {
66731
- this._colorByItems.set('VT', 'Variant Type');
66732
- }
66733
- if (infoFields.has('SVTYPE')) {
66734
- this._colorByItems.set('SVTYPE', 'SV Type');
66771
+ // Configure menu items based on info available
66772
+ if (infoFields.has('AF')) {
66773
+ this._colorByItems.set('AF', 'Allele frequency');
66774
+ }
66775
+ if (infoFields.has('VT')) {
66776
+ this._colorByItems.set('VT', 'Variant Type');
66777
+ }
66778
+ if (infoFields.has('SVTYPE')) {
66779
+ this._colorByItems.set('SVTYPE', 'SV Type');
66780
+ }
66735
66781
  }
66782
+
66736
66783
  if (this.config.colorBy && !this._colorByItems.has(this.config.colorBy)) {
66737
66784
  this._colorByItems.set(this.config.colorBy, this.config.colorBy);
66738
66785
  }
@@ -69577,11 +69624,15 @@ someMotifValues.forEach(motif => {
69577
69624
  JUNCTION_MOTIF_PALETTE.getColor(motif);
69578
69625
  });
69579
69626
 
69580
- // rendering context with values that only need to be computed once per render, rather than for each splice junction
69581
- const junctionRenderingContext = {};
69582
69627
 
69583
69628
  class SpliceJunctionTrack extends TrackBase {
69584
69629
 
69630
+ static defaults = {
69631
+ margin: 10,
69632
+ colorByNumReadsThreshold: 5,
69633
+ height: 100
69634
+ }
69635
+
69585
69636
  constructor(config, browser) {
69586
69637
  super(config, browser);
69587
69638
  }
@@ -69602,16 +69653,6 @@ class SpliceJunctionTrack extends TrackBase {
69602
69653
  FeatureSource(config, this.browser.genome);
69603
69654
  }
69604
69655
 
69605
- this.margin = config.margin === undefined ? 10 : config.margin;
69606
-
69607
- if (!this.height) {
69608
- this.height = 100;
69609
- }
69610
-
69611
- //set defaults
69612
- if (config.colorByNumReadsThreshold === undefined) {
69613
- config.colorByNumReadsThreshold = 5;
69614
- }
69615
69656
  }
69616
69657
 
69617
69658
  async postInit() {
@@ -69666,13 +69707,16 @@ class SpliceJunctionTrack extends TrackBase {
69666
69707
  const bpEnd = bpStart + pixelWidth * bpPerPixel + 1;
69667
69708
 
69668
69709
 
69669
- if (!this.config.isMergedTrack) {
69710
+ if (!this.isMergedTrack) {
69670
69711
  IGVGraphics.fillRect(ctx, 0, options.pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
69671
69712
  }
69672
69713
 
69673
69714
  if (featureList) {
69674
69715
 
69675
69716
 
69717
+ // rendering context with values that only need to be computed once per render, rather than for each splice junction
69718
+ const junctionRenderingContext = {};
69719
+
69676
69720
  junctionRenderingContext.referenceFrame = options.viewport.referenceFrame;
69677
69721
  junctionRenderingContext.referenceFrameStart = junctionRenderingContext.referenceFrame.start;
69678
69722
  junctionRenderingContext.referenceFrameEnd = junctionRenderingContext.referenceFrameStart +
@@ -69685,7 +69729,7 @@ class SpliceJunctionTrack extends TrackBase {
69685
69729
  for (let feature of featureList) {
69686
69730
  if (feature.end < bpStart) continue
69687
69731
  if (feature.start > bpEnd) break
69688
- this.renderJunction(feature, bpStart, bpPerPixel, pixelHeight, ctx);
69732
+ this.renderJunction(feature, bpStart, bpPerPixel, pixelHeight, ctx, junctionRenderingContext);
69689
69733
  }
69690
69734
 
69691
69735
  } else {
@@ -69702,7 +69746,7 @@ class SpliceJunctionTrack extends TrackBase {
69702
69746
  * @param pixelHeight pixel height of the current canvas
69703
69747
  * @param ctx the canvas 2d context
69704
69748
  */
69705
- renderJunction(feature, bpStart, xScale, pixelHeight, ctx) {
69749
+ renderJunction(feature, bpStart, xScale, pixelHeight, ctx, junctionRenderingContext) {
69706
69750
  // cache whether this junction is rendered or filtered out. Use later to exclude non-rendered junctions from click detection.
69707
69751
  feature.isVisible = false;
69708
69752
 
@@ -69786,7 +69830,7 @@ class SpliceJunctionTrack extends TrackBase {
69786
69830
  }
69787
69831
 
69788
69832
  const py = this.margin;
69789
- const rowHeight = this.height;
69833
+ const rowHeight = pixelHeight;
69790
69834
 
69791
69835
  const cy = py + 0.5 * rowHeight;
69792
69836
  let topY = py;
@@ -70471,7 +70515,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
70471
70515
  })
70472
70516
  }
70473
70517
 
70474
- const _version = "3.0.1";
70518
+ const _version = "3.0.3";
70475
70519
  function version() {
70476
70520
  return _version
70477
70521
  }
@@ -71942,16 +71986,17 @@ class ROIMenu {
71942
71986
 
71943
71987
  }
71944
71988
 
71945
- async present(feature, isUserDefined, event, roiManager, columnContainer, regionElement) {
71946
- const menuItems = this.menuItems(feature, isUserDefined, event, roiManager, columnContainer, regionElement);
71989
+ async present(feature, roiSet, event, roiManager, columnContainer, regionElement) {
71990
+ const menuItems = this.menuItems(feature, roiSet, event, roiManager, columnContainer, regionElement);
71947
71991
  this.browser.menuPopup.presentTrackContextMenu(event, menuItems);
71948
71992
  }
71949
71993
 
71950
- menuItems(feature, isUserDefined, event, roiManager, columnContainer, regionElement) {
71994
+ menuItems(feature, roiSet, event, roiManager, columnContainer, regionElement) {
71995
+ const items = feature.name ? [`<b>${feature.name}</b><br/>`] : [];
71996
+ if ('name' in roiSet) items.push(`<b>ROI Set: ${roiSet.name}</b>`);
71997
+ if (items.length > 0) items.push(`<hr/>`);
71951
71998
 
71952
- const items = [`<b>${feature.name || ''}</b>`,];
71953
-
71954
- if (isUserDefined) {
71999
+ if (roiSet.isUserDefined) {
71955
72000
  items.push(
71956
72001
  {
71957
72002
  label: 'Set description ...',
@@ -72015,7 +72060,7 @@ class ROIMenu {
72015
72060
  }
72016
72061
 
72017
72062
 
72018
- if (isUserDefined) {
72063
+ if (roiSet.isUserDefined) {
72019
72064
  items.push(
72020
72065
  '<hr/>',
72021
72066
  {
@@ -72300,13 +72345,17 @@ class ROIManager {
72300
72345
  const [rectA, rectB] = tracks
72301
72346
  .map(track => track.trackView.viewports[0].$viewport.get(0))
72302
72347
  .map(element => getElementVerticalDimension(element));
72303
-
72348
+
72349
+ //Covers cases in which ruler and/or ideogram are hidden
72350
+ const heightA = rectA ? rectA.height : 0;
72351
+ const heightB = rectB ? rectB.height : 0;
72352
+
72304
72353
  const elements = browser.columnContainer.querySelectorAll('.igv-roi-region');
72305
72354
 
72306
72355
  const fudge = -0.5;
72307
72356
  if (elements) {
72308
72357
  for (const element of elements) {
72309
- element.style.marginTop = `${rectA.height + rectB.height + fudge}px`;
72358
+ element.style.marginTop = `${heightA + heightB + fudge}px`;
72310
72359
  }
72311
72360
 
72312
72361
  }
@@ -72474,7 +72523,6 @@ class ROIManager {
72474
72523
  if (features) {
72475
72524
 
72476
72525
  for (let feature of features) {
72477
-
72478
72526
  const regionKey = createRegionKey(chr, feature.start, feature.end);
72479
72527
 
72480
72528
  const {
@@ -72519,8 +72567,7 @@ class ROIManager {
72519
72567
  event.stopPropagation();
72520
72568
 
72521
72569
  translateMouseCoordinates(event, columnContainer);
72522
- const isUserDefined = roiSet.isUserDefined;
72523
- this.roiMenu.present(feature, isUserDefined, event, this, columnContainer, regionElement);
72570
+ this.roiMenu.present(feature, roiSet, event, this, columnContainer, regionElement);
72524
72571
  });
72525
72572
 
72526
72573
 
@@ -72922,7 +72969,7 @@ class ChromAliasBB {
72922
72969
  }
72923
72970
 
72924
72971
  /**
72925
- * Return the canonical chromosome name for the alias. If none found return the alias.
72972
+ * Return the cached canonical chromosome name for the alias. If none found return the alias.
72926
72973
  *
72927
72974
  * Note this will only work if a "search" for ths chromosome has been performed previously.
72928
72975
  *
@@ -73689,7 +73736,7 @@ function generateGenomeID(config) {
73689
73736
  }
73690
73737
  }
73691
73738
 
73692
- var igvCss = '.igv-ui-dropdown {\n cursor: default;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n background-color: white;\n}\n.igv-ui-dropdown > div {\n overflow-y: auto;\n overflow-x: hidden;\n background-color: white;\n}\n.igv-ui-dropdown > div > div {\n padding: 4px;\n width: 100%;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: 1px;\n background-color: white;\n}\n.igv-ui-dropdown > div > div:last-child {\n border-bottom-color: transparent;\n border-bottom-width: 0;\n}\n.igv-ui-dropdown > div > div:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white;\n}\n.igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-width: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px;\n}\n.igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-popover > div:last-child {\n user-select: text;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white;\n border-bottom-width: 0;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-ui-popover > div:last-child > div {\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.igv-ui-popover > div:last-child > div > span {\n font-weight: bolder;\n}\n.igv-ui-popover > div:last-child hr {\n width: 100%;\n}\n\n.igv-ui-alert-dialog-container {\n box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n top: 50%;\n left: 50%;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:first-child {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n background-color: white;\n border: unset;\n}\n.igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f;\n}\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 2px;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-color-swatch:hover {\n border-color: dimgray;\n}\n\n.igv-ui-colorpicker-menu-close-button {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 32px;\n margin-top: 4px;\n margin-bottom: 4px;\n padding-right: 8px;\n}\n.igv-ui-colorpicker-menu-close-button i.fa {\n display: block;\n margin-left: 4px;\n margin-right: 4px;\n color: #5f5f5f;\n}\n.igv-ui-colorpicker-menu-close-button i.fa:hover,\n.igv-ui-colorpicker-menu-close-button i.fa:focus,\n.igv-ui-colorpicker-menu-close-button i.fa:active {\n cursor: pointer;\n color: #0f0f0f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 0;\n left: 0;\n width: 300px;\n height: fit-content;\n padding-bottom: 16px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input > div {\n width: fit-content;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input[type=range] {\n width: 70%;\n -webkit-appearance: none;\n background: linear-gradient(90deg, white, black);\n outline: none;\n margin: 0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input output {\n display: block;\n height: 100%;\n width: 20%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n padding-top: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div {\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n\n.igv-ui-generic-container {\n box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-container > div:first-child {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-ui-generic-container > div:first-child > div {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n}\n.igv-ui-dialog .igv-ui-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-ui-panel, .igv-ui-panel-row, .igv-ui-panel-column {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column;\n}\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row;\n}\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-table {\n background-color: white;\n}\n\n.igv-ui-table thead {\n position: sticky;\n top: 0;\n}\n\n.igv-ui-table th {\n text-align: left;\n}\n\n.igv-ui-table td {\n padding-right: 20px;\n}\n\n.igv-ui-table tr:hover {\n background-color: lightblue;\n}\n\n.igv-ui-center-fixed {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 240px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-hidden {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-icon-button {\n cursor: pointer;\n position: relative;\n width: 24px;\n height: 24px;\n margin-left: 4px;\n margin-right: 4px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:first-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: -18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:last-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.igv-navbar-text-button {\n cursor: pointer;\n position: relative;\n margin-left: 2px;\n margin-right: 2px;\n border: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar-text-button > div:nth-child(2) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 0;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-text-button > div:nth-child(3) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 42px;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n#igv-text-button-label {\n text-anchor: middle;\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-inactive rect {\n stroke: #737373;\n fill: white;\n}\n.igv-navbar-text-button-svg-inactive text {\n fill: #737373;\n}\n.igv-navbar-text-button-svg-inactive tspan {\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-hover rect {\n stroke: #737373;\n fill: #737373;\n}\n.igv-navbar-text-button-svg-hover text {\n fill: white;\n}\n.igv-navbar-text-button-svg-hover tspan {\n dominant-baseline: middle;\n}\n\n#igv-save-svg-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-svg-group text {\n fill: #737373;\n}\n\n#igv-save-svg-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-svg-group:hover text {\n fill: white;\n}\n\n#igv-save-png-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-png-group text {\n fill: #737373;\n}\n\n#igv-save-png-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-png-group:hover text {\n fill: white;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 256;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 12px;\n height: 12px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 12px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label > div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n.igv-multi-locus-ruler-label > div {\n cursor: pointer;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 50%;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 32;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 32;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container div:first-child {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container div:first-child i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n z-index: 512;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n}\n.igv-roi-menu > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu > div:last-child > div:not(:first-child):hover {\n cursor: pointer;\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu > div:last-child div:first-child {\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n resize: both;\n overflow: hidden;\n width: min-content;\n max-width: 1600px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n cursor: default;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-description {\n padding: 4px;\n margin-left: 4px;\n word-break: break-all;\n overflow-y: auto;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-goto-explainer {\n margin-top: 5px;\n margin-left: 4px;\n color: #7F7F7F;\n font-style: italic;\n height: 24px;\n border-top: solid lightgray;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: #7f7f7f;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n overflow: auto;\n height: 360px;\n flex: 1 1 auto;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: transparent;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n min-height: 32px;\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n cursor: pointer;\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n margin-top: 66px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n pointer-events: auto;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n min-height: 160px;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n line-height: 1;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n position: relative;\n margin-top: 5px;\n width: 100%;\n}\n.igv-axis-column > div > div {\n z-index: 512;\n position: absolute;\n top: 8px;\n left: 8px;\n width: fit-content;\n height: fit-content;\n background-color: transparent;\n display: grid;\n align-items: start;\n justify-items: center;\n}\n.igv-axis-column > div > div > input {\n display: block;\n margin: unset;\n cursor: pointer;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-info-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.igv-track-drag-column .igv-track-drag-handle-color {\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover-color {\n background-color: #787878;\n}\n.igv-track-drag-column .igv-track-drag-handle-selected-color {\n background-color: #0963fa;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n/*# sourceMappingURL=igv.css.map */\n';
73739
+ var igvCss = '.igv-ui-dropdown {\n cursor: default;\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n background-color: white;\n}\n.igv-ui-dropdown > div {\n overflow-y: auto;\n overflow-x: hidden;\n background-color: white;\n}\n.igv-ui-dropdown > div > div {\n padding: 4px;\n width: 100%;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: 1px;\n background-color: white;\n}\n.igv-ui-dropdown > div > div:last-child {\n border-bottom-color: transparent;\n border-bottom-width: 0;\n}\n.igv-ui-dropdown > div > div:hover {\n cursor: pointer;\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.igv-ui-popover {\n cursor: default;\n position: absolute;\n z-index: 2048;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: 1px;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n background-color: white;\n}\n.igv-ui-popover > div:first-child {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-width: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-popover > div:first-child > div:first-child {\n margin-left: 4px;\n}\n.igv-ui-popover > div:first-child > div:last-child {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-popover > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-popover > div:last-child {\n user-select: text;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 400px;\n max-width: 800px;\n background-color: white;\n border-bottom-width: 0;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-ui-popover > div:last-child > div {\n margin-left: 4px;\n margin-right: 4px;\n min-width: 220px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.igv-ui-popover > div:last-child > div > span {\n font-weight: bolder;\n}\n.igv-ui-popover > div:last-child hr {\n width: 100%;\n}\n\n.igv-ui-alert-dialog-container {\n box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n top: 50%;\n left: 50%;\n width: 400px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n outline: none;\n font-family: \"Open Sans\", sans-serif;\n font-size: 15px;\n font-weight: 400;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:first-child {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-alert-dialog-container > div:first-child div:first-child {\n padding-left: 8px;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body {\n -webkit-user-select: text;\n -moz-user-select: text;\n -ms-user-select: text;\n user-select: text;\n color: #373737;\n width: 100%;\n height: calc(100% - 24px - 64px);\n overflow-y: scroll;\n}\n.igv-ui-alert-dialog-container .igv-ui-alert-dialog-body .igv-ui-alert-dialog-body-copy {\n margin: 16px;\n width: auto;\n height: auto;\n overflow-wrap: break-word;\n word-break: break-word;\n background-color: white;\n border: unset;\n}\n.igv-ui-alert-dialog-container > div:last-child {\n width: 100%;\n margin-bottom: 10px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-ui-alert-dialog-container > div:last-child div {\n margin: unset;\n width: 40px;\n height: 30px;\n line-height: 30px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n border-color: #2B81AF;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-alert-dialog-container > div:last-child div:hover {\n cursor: pointer;\n border-color: #25597f;\n background-color: #25597f;\n}\n\n.igv-ui-color-swatch {\n position: relative;\n box-sizing: content-box;\n display: flex;\n flex-flow: row;\n flex-wrap: wrap;\n justify-content: center;\n align-items: center;\n width: 32px;\n height: 32px;\n border-style: solid;\n border-width: 2px;\n border-color: white;\n border-radius: 4px;\n}\n\n.igv-ui-color-swatch:hover {\n border-color: dimgray;\n}\n\n.igv-ui-colorpicker-menu-close-button {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 32px;\n margin-top: 4px;\n margin-bottom: 4px;\n padding-right: 8px;\n}\n.igv-ui-colorpicker-menu-close-button i.fa {\n display: block;\n margin-left: 4px;\n margin-right: 4px;\n color: #5f5f5f;\n}\n.igv-ui-colorpicker-menu-close-button i.fa:hover,\n.igv-ui-colorpicker-menu-close-button i.fa:focus,\n.igv-ui-colorpicker-menu-close-button i.fa:active {\n cursor: pointer;\n color: #0f0f0f;\n}\n\n.igv-ui-generic-dialog-container {\n box-sizing: content-box;\n position: fixed;\n top: 0;\n left: 0;\n width: 300px;\n height: fit-content;\n padding-bottom: 16px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input > div {\n width: fit-content;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input input[type=range] {\n width: 70%;\n -webkit-appearance: none;\n background: linear-gradient(90deg, white, black);\n outline: none;\n margin: 0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-input output {\n display: block;\n height: 100%;\n width: 20%;\n font-size: 16px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n padding-top: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div {\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-generic-dialog-container .igv-ui-generic-dialog-ok-cancel > div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n\n.igv-ui-generic-container {\n box-sizing: content-box;\n position: absolute;\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ui-generic-container > div:first-child {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-ui-generic-container > div:first-child > div {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-ui-dialog {\n z-index: 2048;\n position: fixed;\n width: fit-content;\n height: fit-content;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n background-color: white;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n}\n.igv-ui-dialog .igv-ui-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-ui-dialog .igv-ui-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-ui-dialog .igv-ui-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-ui-dialog .igv-ui-dialog-one-liner {\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin: 8px;\n overflow-wrap: break-word;\n background-color: white;\n font-weight: bold;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel {\n width: 100%;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div {\n margin: 16px;\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child {\n background-color: #5ea4e0;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child {\n background-color: #c4c4c4;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-ui-dialog .igv-ui-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-ui-dialog .igv-ui-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-ui-panel, .igv-ui-panel-row, .igv-ui-panel-column {\n z-index: 2048;\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-panel-column {\n display: flex;\n flex-direction: column;\n}\n\n.igv-ui-panel-row {\n display: flex;\n flex-direction: row;\n}\n\n.igv-ui-textbox {\n background-color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n display: flex;\n justify-content: flex-start;\n align-items: flex-start;\n}\n\n.igv-ui-table {\n background-color: white;\n}\n\n.igv-ui-table thead {\n position: sticky;\n top: 0;\n}\n\n.igv-ui-table th {\n text-align: left;\n}\n\n.igv-ui-table td {\n padding-right: 20px;\n}\n\n.igv-ui-table tr:hover {\n background-color: lightblue;\n}\n\n.igv-ui-center-fixed {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.igv-navbar {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n width: 100%;\n color: #444;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n line-height: 32px;\n padding-left: 8px;\n padding-right: 8px;\n margin-top: 2px;\n margin-bottom: 6px;\n height: 32px;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: #f3f3f3;\n}\n.igv-navbar .igv-navbar-left-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 32px;\n line-height: 32px;\n}\n.igv-navbar .igv-navbar-left-container .igv-logo {\n width: 34px;\n height: 32px;\n margin-right: 8px;\n}\n.igv-navbar .igv-navbar-left-container .igv-current-genome {\n height: 32px;\n margin-left: 4px;\n margin-right: 4px;\n user-select: none;\n line-height: 32px;\n vertical-align: middle;\n text-align: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n height: 100%;\n width: 125px;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-chromosome-select-widget-container select {\n display: block;\n cursor: pointer;\n width: 100px;\n height: 75%;\n outline: none;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n margin-left: 8px;\n height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 240px;\n height: 22px;\n line-height: 22px;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container input.igv-search-input {\n cursor: text;\n width: 85%;\n height: 22px;\n line-height: 22px;\n font-size: 12px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n text-align: left;\n padding-left: 8px;\n margin-right: 8px;\n outline: none;\n border-style: solid;\n border-radius: 3px;\n border-width: thin;\n border-color: #bfbfbf;\n background-color: white;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-search-container .igv-search-icon-container {\n cursor: pointer;\n height: 16px;\n width: 16px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar .igv-navbar-left-container .igv-navbar-genomic-location .igv-locus-size-group .igv-windowsize-panel-container {\n margin-left: 4px;\n user-select: none;\n}\n.igv-navbar .igv-navbar-right-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container {\n position: relative;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-navbar-toggle-button-container-hidden {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n height: 100%;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget {\n color: #737373;\n font-size: 18px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget div:nth-child(even) {\n display: block;\n height: fit-content;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget input {\n display: block;\n width: 125px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 {\n color: #737373;\n font-size: 18px;\n height: 32px;\n line-height: 32px;\n margin-left: 8px;\n user-select: none;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div {\n cursor: pointer;\n margin-left: unset;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:first-child {\n height: 20px;\n width: 20px;\n margin-left: unset;\n margin-right: 4px;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:last-child {\n height: 20px;\n width: 20px;\n margin-left: 4px;\n margin-right: unset;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 div:nth-child(even) {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 input {\n width: 0;\n height: 0;\n display: none;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-900 svg {\n display: block;\n}\n.igv-navbar .igv-navbar-right-container .igv-zoom-widget-hidden {\n display: none;\n}\n\n.igv-navbar-button {\n display: block;\n box-sizing: unset;\n padding-left: 6px;\n padding-right: 6px;\n height: 18px;\n text-transform: capitalize;\n user-select: none;\n line-height: 18px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 11px;\n font-weight: 200;\n color: #737373;\n background-color: #f3f3f3;\n border-color: #737373;\n border-style: solid;\n border-width: thin;\n border-radius: 6px;\n}\n\n.igv-navbar-button:hover {\n cursor: pointer;\n}\n\n.igv-navbar-button-clicked {\n color: white;\n background-color: #737373;\n}\n\n.igv-navbar-icon-button {\n cursor: pointer;\n position: relative;\n width: 24px;\n height: 24px;\n margin-left: 4px;\n margin-right: 4px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:first-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: -18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-icon-button > div:last-child {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 18px;\n width: 24px;\n height: 24px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.igv-navbar-text-button {\n cursor: pointer;\n position: relative;\n margin-left: 2px;\n margin-right: 2px;\n border: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.igv-navbar-text-button > div:nth-child(2) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 0;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n.igv-navbar-text-button > div:nth-child(3) {\n z-index: 512;\n position: absolute;\n top: 36px;\n left: 42px;\n width: 38px;\n height: 18px;\n border: none;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n#igv-text-button-label {\n text-anchor: middle;\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-inactive rect {\n stroke: #737373;\n fill: white;\n}\n.igv-navbar-text-button-svg-inactive text {\n fill: #737373;\n}\n.igv-navbar-text-button-svg-inactive tspan {\n dominant-baseline: middle;\n}\n\n.igv-navbar-text-button-svg-hover rect {\n stroke: #737373;\n fill: #737373;\n}\n.igv-navbar-text-button-svg-hover text {\n fill: white;\n}\n.igv-navbar-text-button-svg-hover tspan {\n dominant-baseline: middle;\n}\n\n#igv-save-svg-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-svg-group text {\n fill: #737373;\n}\n\n#igv-save-svg-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-svg-group:hover text {\n fill: white;\n}\n\n#igv-save-png-group rect {\n stroke: #737373;\n fill: white;\n}\n#igv-save-png-group text {\n fill: #737373;\n}\n\n#igv-save-png-group:hover rect {\n stroke: #737373;\n fill: #737373;\n}\n#igv-save-png-group:hover text {\n fill: white;\n}\n\n.igv-zoom-in-notice-container {\n z-index: 256;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translate(-50%, 0%);\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n background-color: white;\n}\n.igv-zoom-in-notice-container > div {\n padding-left: 4px;\n padding-right: 4px;\n padding-top: 2px;\n padding-bottom: 2px;\n width: 100%;\n height: 100%;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: #3f3f3f;\n}\n\n.igv-zoom-in-notice {\n position: absolute;\n top: 10px;\n left: 50%;\n}\n.igv-zoom-in-notice div {\n position: relative;\n left: -50%;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #3f3f3f;\n background-color: rgba(255, 255, 255, 0.51);\n z-index: 64;\n}\n\n.igv-container-spinner {\n position: absolute;\n top: 90%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1024;\n width: 24px;\n height: 24px;\n pointer-events: none;\n color: #737373;\n}\n\n.igv-multi-locus-close-button {\n position: absolute;\n top: 2px;\n right: 0;\n padding-left: 2px;\n padding-right: 2px;\n width: 12px;\n height: 12px;\n color: #666666;\n background-color: white;\n z-index: 1000;\n}\n.igv-multi-locus-close-button > svg {\n vertical-align: top;\n}\n\n.igv-multi-locus-close-button:hover {\n cursor: pointer;\n color: #434343;\n}\n\n.igv-multi-locus-ruler-label {\n z-index: 64;\n position: absolute;\n top: 2px;\n left: 0;\n width: 100%;\n height: 12px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-multi-locus-ruler-label > div {\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n color: rgb(16, 16, 16);\n background-color: white;\n}\n.igv-multi-locus-ruler-label > div {\n cursor: pointer;\n}\n\n.igv-multi-locus-ruler-label-square-dot {\n z-index: 64;\n position: absolute;\n left: 50%;\n top: 5%;\n transform: translate(-50%, 0%);\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-multi-locus-ruler-label-square-dot > div:first-child {\n width: 14px;\n height: 14px;\n}\n.igv-multi-locus-ruler-label-square-dot > div:last-child {\n margin-left: 16px;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n color: rgb(16, 16, 16);\n}\n\n.igv-ruler-sweeper {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 26px;\n bottom: 0;\n left: 0;\n width: 0;\n z-index: 99999;\n background-color: rgba(68, 134, 247, 0.25);\n}\n\n.igv-ruler-tooltip {\n pointer-events: none;\n z-index: 128;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 32px;\n background-color: transparent;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-ruler-tooltip > div {\n pointer-events: none;\n width: 128px;\n height: auto;\n padding: 1px;\n color: #373737;\n font-size: 10px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n background-color: white;\n border-style: solid;\n border-width: thin;\n border-color: #373737;\n}\n\n.igv-track-label {\n position: absolute;\n left: 8px;\n top: 8px;\n width: auto;\n height: auto;\n max-width: 50%;\n padding-left: 4px;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n text-align: center;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-color: #444;\n border-radius: 2px;\n border-style: solid;\n border-width: thin;\n background-color: white;\n z-index: 128;\n cursor: pointer;\n}\n\n.igv-track-label:hover,\n.igv-track-label:focus,\n.igv-track-label:active {\n background-color: #e8e8e8;\n}\n\n.igv-track-label-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-top: 4px;\n}\n\n.igv-center-line {\n display: none;\n pointer-events: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n border-left-style: dashed;\n border-left-width: thin;\n border-right-style: dashed;\n border-right-width: thin;\n}\n\n.igv-center-line-wide {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(127, 127, 127, 0.51);\n}\n\n.igv-center-line-thin {\n background-color: rgba(0, 0, 0, 0);\n border-left-color: rgba(127, 127, 127, 0.51);\n border-right-color: rgba(0, 0, 0, 0);\n}\n\n.igv-cursor-guide-horizontal {\n display: none;\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 1px;\n z-index: 32;\n margin-left: 50px;\n margin-right: 54px;\n border-top-style: dotted;\n border-top-width: thin;\n border-top-color: rgba(127, 127, 127, 0.76);\n}\n\n.igv-cursor-guide-vertical {\n pointer-events: none;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-select: none;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n z-index: 32;\n border-left-style: dotted;\n border-left-width: thin;\n border-left-color: rgba(127, 127, 127, 0.76);\n display: none;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-generic-dialog-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 300px;\n height: 200px;\n border-color: #7F7F7F;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n z-index: 2048;\n background-color: white;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-generic-dialog-container .igv-generic-dialog-header div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-generic-dialog-container .igv-generic-dialog-one-liner {\n color: #373737;\n width: 95%;\n height: 24px;\n line-height: 24px;\n text-align: left;\n margin-top: 8px;\n padding-left: 8px;\n overflow-wrap: break-word;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input {\n margin-top: 8px;\n width: 95%;\n height: 24px;\n color: #373737;\n line-height: 24px;\n padding-left: 8px;\n background-color: white;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input div {\n width: 30%;\n height: 100%;\n font-size: 16px;\n text-align: right;\n padding-right: 8px;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-label-input input {\n width: 50%;\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input {\n margin-top: 8px;\n width: calc(100% - 16px);\n height: 24px;\n color: #373737;\n line-height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n display: block;\n height: 100%;\n width: 100%;\n padding-left: 4px;\n font-family: \"Open Sans\", sans-serif;\n font-weight: 400;\n color: #373737;\n text-align: left;\n outline: none;\n border-style: solid;\n border-width: thin;\n border-color: #7F7F7F;\n background-color: white;\n}\n.igv-generic-dialog-container .igv-generic-dialog-input input {\n font-size: 16px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel {\n width: 100%;\n height: 28px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div {\n margin-top: 32px;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: 14px;\n font-weight: 400;\n width: 75px;\n height: 28px;\n line-height: 28px;\n text-align: center;\n border-color: transparent;\n border-style: solid;\n border-width: thin;\n border-radius: 2px;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child {\n margin-left: 32px;\n margin-right: 0;\n background-color: #5ea4e0;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child {\n margin-left: 0;\n margin-right: 32px;\n background-color: #c4c4c4;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:first-child:hover {\n cursor: pointer;\n background-color: #3b5c7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok-cancel div:last-child:hover {\n cursor: pointer;\n background-color: #7f7f7f;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok {\n width: 100%;\n height: 36px;\n margin-top: 32px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div {\n width: 98px;\n height: 36px;\n line-height: 36px;\n text-align: center;\n color: white;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n border-color: white;\n border-style: solid;\n border-width: thin;\n border-radius: 4px;\n background-color: #2B81AF;\n}\n.igv-generic-dialog-container .igv-generic-dialog-ok div:hover {\n cursor: pointer;\n background-color: #25597f;\n}\n\n.igv-generic-container {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2048;\n background-color: white;\n cursor: pointer;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-generic-container div:first-child {\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n height: 24px;\n width: 100%;\n background-color: #dddddd;\n}\n.igv-generic-container div:first-child i {\n display: block;\n color: #5f5f5f;\n cursor: pointer;\n width: 14px;\n height: 14px;\n margin-right: 8px;\n margin-bottom: 4px;\n}\n\n.igv-menu-popup {\n position: absolute;\n top: 0;\n left: 0;\n width: max-content;\n max-width: 400px;\n z-index: 512;\n cursor: pointer;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background: white;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-end;\n text-align: left;\n}\n.igv-menu-popup > div:not(:first-child) {\n width: 100%;\n}\n.igv-menu-popup > div:not(:first-child) > div {\n background: white;\n}\n.igv-menu-popup > div:not(:first-child) > div.context-menu {\n padding-left: 4px;\n padding-right: 4px;\n}\n.igv-menu-popup > div:not(:first-child) > div:last-child {\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-menu-popup > div:not(:first-child) > div:hover {\n background: #efefef;\n}\n\n.igv-menu-popup-shim {\n padding-left: 8px;\n padding-right: 8px;\n padding-bottom: 1px;\n padding-top: 1px;\n}\n\n.igv-menu-popup-header {\n position: relative;\n width: 100%;\n height: 24px;\n cursor: move;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-menu-popup-header div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-menu-popup-header div:hover {\n cursor: pointer;\n color: #444;\n}\n\n.igv-menu-popup-check-container {\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n width: 100%;\n height: 20px;\n margin-right: 4px;\n background-color: transparent;\n}\n.igv-menu-popup-check-container div {\n padding-top: 2px;\n padding-left: 8px;\n}\n.igv-menu-popup-check-container div:first-child {\n position: relative;\n width: 12px;\n height: 12px;\n}\n.igv-menu-popup-check-container div:first-child svg {\n position: absolute;\n width: 12px;\n height: 12px;\n}\n\n.igv-user-feedback {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 512px;\n height: 360px;\n z-index: 2048;\n background-color: white;\n border-color: #a2a2a2;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: medium;\n font-weight: 400;\n color: #444;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n}\n.igv-user-feedback div:first-child {\n position: relative;\n height: 24px;\n width: 100%;\n background-color: white;\n border-bottom-color: #a2a2a2;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-user-feedback div:first-child div {\n position: absolute;\n top: 2px;\n width: 16px;\n height: 16px;\n background-color: transparent;\n}\n.igv-user-feedback div:first-child div:first-child {\n left: 8px;\n}\n.igv-user-feedback div:first-child div:last-child {\n cursor: pointer;\n right: 8px;\n}\n.igv-user-feedback div:last-child {\n width: 100%;\n height: calc(100% - 24px);\n border-width: 0;\n}\n.igv-user-feedback div:last-child div {\n width: auto;\n height: auto;\n margin: 8px;\n}\n\n.igv-loading-spinner-container {\n z-index: 1024;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 32px;\n height: 32px;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: center;\n align-items: center;\n}\n.igv-loading-spinner-container > div {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 4px solid rgba(128, 128, 128, 0.5);\n border-top-color: rgb(255, 255, 255);\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.igv-roi-menu {\n position: absolute;\n z-index: 512;\n font-family: \"Open Sans\", sans-serif;\n font-size: small;\n font-weight: 400;\n color: #4b4b4b;\n background-color: white;\n width: 192px;\n border-radius: 4px;\n border-color: #7F7F7F;\n border-style: solid;\n border-width: thin;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-menu > div:first-child {\n height: 24px;\n border-top-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-color: #7F7F7F;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: flex-end;\n align-items: center;\n}\n.igv-roi-menu > div:first-child > div {\n margin-right: 4px;\n height: 12px;\n width: 12px;\n color: #7F7F7F;\n}\n.igv-roi-menu > div:first-child > div:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-menu > div:last-child {\n background-color: white;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-color: transparent;\n border-bottom-style: solid;\n border-bottom-width: 0;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n text-align: start;\n vertical-align: middle;\n}\n.igv-roi-menu > div:last-child > div {\n height: 24px;\n padding-left: 4px;\n border-bottom-style: solid;\n border-bottom-width: thin;\n border-bottom-color: #7f7f7f;\n}\n.igv-roi-menu > div:last-child > div:not(:first-child):hover {\n cursor: pointer;\n background-color: rgba(127, 127, 127, 0.1);\n}\n.igv-roi-menu > div:last-child div:first-child {\n font-style: italic;\n text-align: center;\n padding-right: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.igv-roi-menu > div:last-child > div:last-child {\n border-bottom-width: 0;\n border-bottom-color: transparent;\n}\n\n.igv-roi-placeholder {\n font-style: normal;\n color: rgba(75, 75, 75, 0.6);\n}\n\n.igv-roi-table {\n position: absolute;\n z-index: 1024;\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n resize: both;\n overflow: hidden;\n width: min-content;\n max-width: 1600px;\n border-color: #7f7f7f;\n border-radius: 4px;\n border-style: solid;\n border-width: thin;\n font-family: \"Open Sans\", sans-serif;\n font-size: 12px;\n font-weight: 400;\n background-color: white;\n cursor: default;\n}\n.igv-roi-table > div {\n height: 24px;\n font-size: 14px;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n}\n.igv-roi-table > div:first-child {\n border-color: transparent;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-top-width: 0;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n background-color: #eee;\n cursor: move;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-between;\n align-items: center;\n}\n.igv-roi-table > div:first-child > div:first-child {\n text-align: center;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n width: calc(100% - 4px - 12px);\n}\n.igv-roi-table > div:first-child > div:last-child {\n margin-right: 4px;\n margin-bottom: 2px;\n height: 12px;\n width: 12px;\n color: #7f7f7f;\n}\n.igv-roi-table > div:first-child > div:last-child > svg {\n display: block;\n}\n.igv-roi-table > div:first-child > div:last-child:hover {\n cursor: pointer;\n color: #444;\n}\n.igv-roi-table > .igv-roi-table-description {\n padding: 4px;\n margin-left: 4px;\n word-break: break-all;\n overflow-y: auto;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-goto-explainer {\n margin-top: 5px;\n margin-left: 4px;\n color: #7F7F7F;\n font-style: italic;\n height: 24px;\n border-top: solid lightgray;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-column-titles {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n padding-right: 16px;\n background-color: white;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: #7f7f7f;\n border-bottom-style: solid;\n border-bottom-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: #7f7f7f;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-column-titles > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container {\n display: flex;\n flex-flow: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n overflow: auto;\n height: 360px;\n flex: 1 1 auto;\n background-color: transparent;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row {\n height: 24px;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div {\n font-size: 14px;\n vertical-align: middle;\n line-height: 24px;\n text-align: left;\n margin-left: 4px;\n height: 24px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right-color: transparent;\n border-right-style: solid;\n border-right-width: thin;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row > div:last-child {\n border-right: unset;\n}\n.igv-roi-table > .igv-roi-table-row-container > .igv-roi-table-row-hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.igv-roi-table > div:last-child {\n min-height: 32px;\n height: 32px;\n line-height: 32px;\n border-top-color: #7f7f7f;\n border-top-style: solid;\n border-top-width: thin;\n border-bottom-color: transparent;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n border-bottom-width: 0;\n background-color: #eee;\n display: flex;\n flex-flow: row;\n flex-wrap: nowrap;\n justify-content: space-around;\n align-items: center;\n}\n\n.igv-roi-table-row-selected {\n background-color: rgba(0, 0, 0, 0.125);\n}\n\n.igv-roi-table-button {\n cursor: pointer;\n height: 20px;\n user-select: none;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n font-family: \"Open Sans\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: black;\n padding-left: 6px;\n padding-right: 6px;\n background-color: rgb(239, 239, 239);\n border-color: black;\n border-style: solid;\n border-width: thin;\n border-radius: 3px;\n}\n\n.igv-roi-table-button:hover {\n font-weight: 400;\n background-color: rgba(0, 0, 0, 0.13);\n}\n\n.igv-roi-region {\n z-index: 64;\n position: absolute;\n top: 0;\n bottom: 0;\n pointer-events: none;\n overflow: visible;\n margin-top: 66px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n}\n.igv-roi-region > div {\n position: relative;\n width: 100%;\n height: 8px;\n pointer-events: auto;\n}\n\n.igv-roi-menu-row {\n height: 24px;\n padding-left: 8px;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n line-height: 24px;\n background-color: white;\n}\n\n.igv-roi-menu-row-edit-description {\n width: -webkit-fill-available;\n font-size: small;\n text-align: start;\n vertical-align: middle;\n background-color: white;\n padding-left: 4px;\n padding-right: 4px;\n padding-bottom: 4px;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: stretch;\n align-items: stretch;\n}\n.igv-roi-menu-row-edit-description > label {\n margin-left: 2px;\n margin-bottom: 0;\n display: block;\n width: -webkit-fill-available;\n}\n.igv-roi-menu-row-edit-description > input {\n display: block;\n margin-left: 2px;\n margin-right: 2px;\n margin-bottom: 1px;\n width: -webkit-fill-available;\n}\n\n.igv-container {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n padding-top: 4px;\n user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n min-height: 160px;\n}\n\n.igv-viewport {\n position: relative;\n margin-top: 5px;\n line-height: 1;\n overflow-x: hidden;\n overflow-y: hidden;\n}\n\n.igv-viewport-content {\n position: relative;\n width: 100%;\n}\n.igv-viewport-content > canvas {\n position: relative;\n display: block;\n}\n\n.igv-column-container {\n position: relative;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: stretch;\n width: 100%;\n}\n\n.igv-column-shim {\n width: 1px;\n margin-left: 2px;\n margin-right: 2px;\n background-color: #545453;\n}\n\n.igv-axis-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 50px;\n}\n.igv-axis-column > div {\n position: relative;\n margin-top: 5px;\n width: 100%;\n}\n.igv-axis-column > div > div {\n z-index: 512;\n position: absolute;\n top: 8px;\n left: 8px;\n width: fit-content;\n height: fit-content;\n background-color: transparent;\n display: grid;\n align-items: start;\n justify-items: center;\n}\n.igv-axis-column > div > div > input {\n display: block;\n margin: unset;\n cursor: pointer;\n}\n\n.igv-column {\n position: relative;\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-info-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-sample-name-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n}\n\n.igv-scrollbar-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 14px;\n}\n.igv-scrollbar-column > div {\n position: relative;\n margin-top: 5px;\n width: 14px;\n}\n.igv-scrollbar-column > div > div {\n cursor: pointer;\n position: absolute;\n top: 0;\n left: 2px;\n width: 8px;\n border-width: 1px;\n border-style: solid;\n border-color: #c4c4c4;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.igv-scrollbar-column > div > div:hover {\n background-color: #c4c4c4;\n}\n\n.igv-track-drag-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 12px;\n background-color: white;\n}\n.igv-track-drag-column > .igv-track-drag-handle {\n z-index: 512;\n position: relative;\n cursor: pointer;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.igv-track-drag-column .igv-track-drag-handle-color {\n background-color: #c4c4c4;\n}\n.igv-track-drag-column .igv-track-drag-handle-hover-color {\n background-color: #787878;\n}\n.igv-track-drag-column .igv-track-drag-handle-selected-color {\n background-color: #0963fa;\n}\n.igv-track-drag-column > .igv-track-drag-shim {\n position: relative;\n margin-top: 5px;\n width: 100%;\n border-style: solid;\n border-width: 0;\n}\n\n.igv-gear-menu-column {\n position: relative;\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: flex-start;\n box-sizing: border-box;\n height: 100%;\n width: 28px;\n}\n.igv-gear-menu-column > div {\n display: flex;\n flex-direction: column;\n flex-wrap: nowrap;\n justify-content: flex-start;\n align-items: center;\n margin-top: 5px;\n width: 100%;\n background: white;\n}\n.igv-gear-menu-column > div > div {\n position: relative;\n margin-top: 4px;\n width: 16px;\n height: 16px;\n color: #7F7F7F;\n}\n.igv-gear-menu-column > div > div:hover {\n cursor: pointer;\n color: #444;\n}\n\n/*# sourceMappingURL=igv.css.map */\n';
73693
73740
 
73694
73741
  /**
73695
73742
  * Manages XQTL selections.
@@ -74572,6 +74619,10 @@ class Browser {
74572
74619
  */
74573
74620
  async loadGenome(idOrConfig) {
74574
74621
 
74622
+ if(idOrConfig.genarkAccession) {
74623
+ idOrConfig.url = convertToHubURL(idOrConfig.genarkAccession);
74624
+ }
74625
+
74575
74626
  // Translate the generic "url" field, used by clients such as igv-webapp
74576
74627
  if (idOrConfig.url) {
74577
74628
  if (isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {