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