igv 3.5.2 → 3.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/igv.esm.js CHANGED
@@ -8702,7 +8702,7 @@ async function getDriveFileInfo(googleDriveURL) {
8702
8702
  const response = await fetch(endPoint);
8703
8703
  let json = await response.json();
8704
8704
  if (json.error && json.error.code === 404) {
8705
- let scope = "https://www.googleapis.com/auth/drive.readonly";
8705
+ let scope = "https://www.googleapis.com/auth/drive.file";
8706
8706
  const access_token = await getAccessToken(scope);
8707
8707
  if (access_token) {
8708
8708
  const response = await fetch(endPoint, {
@@ -8884,6 +8884,10 @@ class Throttle {
8884
8884
 
8885
8885
  class IGVXhr {
8886
8886
 
8887
+ UCSC_HOST = "hgdownload.soe.ucsc.edu"
8888
+ UCSC_BACKUP_HOST = "genome-browser.s3.us-east-1.amazonaws.com"
8889
+
8890
+
8887
8891
  constructor() {
8888
8892
  this.apiKey = undefined;
8889
8893
  this.googleThrottle = new Throttle({
@@ -8891,6 +8895,7 @@ class IGVXhr {
8891
8895
  });
8892
8896
  this.RANGE_WARNING_GIVEN = false;
8893
8897
  this.oauth = new Oauth();
8898
+ this.corsProxy = undefined;
8894
8899
  }
8895
8900
 
8896
8901
  setApiKey(key) {
@@ -8915,7 +8920,7 @@ class IGVXhr {
8915
8920
  * @param options
8916
8921
  * @returns {Promise<Uint8Array>}
8917
8922
  */
8918
- async loadByteArray(url, options) {
8923
+ async loadByteArray(url, options) {
8919
8924
  const arraybuffer = await this.loadArrayBuffer(url, options);
8920
8925
  let plain;
8921
8926
  if (isgzipped(arraybuffer)) {
@@ -8960,7 +8965,7 @@ class IGVXhr {
8960
8965
 
8961
8966
  if (isFile(url)) {
8962
8967
  return this._loadFileSlice(url, options)
8963
- } else if (typeof url.startsWith === 'function') { // Test for string
8968
+ } else if (isString$3(url)) {
8964
8969
  if (url.startsWith("data:")) {
8965
8970
  const buffer = decodeDataURI$1(url).buffer;
8966
8971
  if (options.range) {
@@ -8990,6 +8995,7 @@ class IGVXhr {
8990
8995
 
8991
8996
  const self = this;
8992
8997
  const _url = url; // The unmodified URL, needed in case of an oAuth retry
8998
+ const {host} = parseUri(_url);
8993
8999
 
8994
9000
  url = mapUrl$1(url);
8995
9001
 
@@ -9070,8 +9076,10 @@ class IGVXhr {
9070
9076
 
9071
9077
  xhr.onload = async function (event) {
9072
9078
 
9079
+ const isFileProtocol = url.toLowerCase().startsWith("file:");
9080
+
9073
9081
  // when the url points to a local file, the status is 0
9074
- if (xhr.status === 0 || (xhr.status >= 200 && xhr.status <= 300)) {
9082
+ if ((xhr.status >= 200 && xhr.status <= 300) || (isFileProtocol && xhr.status === 0)) {
9075
9083
  if ("HEAD" === options.method) {
9076
9084
  // Support fetching specific headers. Attempting to fetch all headers can be problematic with CORS
9077
9085
  const headers = options.requestedHeaders || ['content-length'];
@@ -9087,7 +9095,7 @@ class IGVXhr {
9087
9095
  // For small files a range starting at 0 can return the whole file => 200
9088
9096
  // Provide just the slice we asked for, throw out the rest quietly
9089
9097
  // If file is large warn user
9090
- if (xhr.response.length > 100000 && !self.RANGE_WARNING_GIVEN) {
9098
+ if (xhr.response.length > 1000000 && !self.RANGE_WARNING_GIVEN) {
9091
9099
  alert(`Warning: Range header ignored for URL: ${url}. This can have severe performance impacts.`);
9092
9100
  }
9093
9101
  resolve(xhr.response.slice(range.start, range.start + range.size));
@@ -9104,20 +9112,29 @@ class IGVXhr {
9104
9112
  tryGoogleAuth();
9105
9113
 
9106
9114
  } else {
9115
+ const error = new Error(`Error accessing resource: ${url} status: ${xhr.status}`);
9107
9116
  if (xhr.status === 403) {
9108
9117
  handleError("Access forbidden: " + url);
9118
+ } else if (host === self.UCSC_HOST) {
9119
+ tryUcscBackup(self.UCSC_HOST, self.UCSC_BACKUP_HOST, error);
9120
+ } else if (xhr.status === 0 && self.corsProxy && !options.corsProxyRetried) {
9121
+ tryCorsProxy(error);
9109
9122
  } else {
9110
- handleError(xhr.status);
9123
+ handleError(error);
9111
9124
  }
9112
9125
  }
9113
9126
  };
9114
9127
 
9115
-
9116
9128
  xhr.onerror = function (event) {
9129
+ const error = new Error(`Error accessing resource: ${url} status: ${xhr.status}`);
9117
9130
  if (isGoogleURL(url) && !options.retries) {
9118
9131
  tryGoogleAuth();
9132
+ } else if (host === self.UCSC_HOST) {
9133
+ tryUcscBackup(self.UCSC_HOST, self.UCSC_BACKUP_HOST, error);
9134
+ } else if (self.corsProxy && !options.corsProxyRetried) {
9135
+ tryCorsProxy(error);
9119
9136
  } else {
9120
- handleError("Error accessing resource: " + url + " Status: " + xhr.status);
9137
+ handleError(error);
9121
9138
  }
9122
9139
  };
9123
9140
 
@@ -9149,6 +9166,27 @@ class IGVXhr {
9149
9166
  }
9150
9167
  }
9151
9168
 
9169
+ async function tryCorsProxy(error) {
9170
+ options.corsProxyRetried = true;
9171
+ const proxyUrl = self.corsProxy + (_url.includes("?") ? "&" : "?") + "url=" + encodeURIComponent(_url);
9172
+ try {
9173
+ const result = await self._loadURL(proxyUrl, options);
9174
+ resolve(result);
9175
+ } catch (e) {
9176
+ handleError(error);
9177
+ }
9178
+ }
9179
+
9180
+ async function tryUcscBackup(UCSC_HOST, UCSC_BACKUP_HOST, error) {
9181
+ const backupUrl = _url.replace(UCSC_HOST, UCSC_BACKUP_HOST);
9182
+ try {
9183
+ const result = await self._loadURL(backupUrl, options);
9184
+ resolve(result);
9185
+ } catch (e) {
9186
+ handleError(error);
9187
+ }
9188
+ }
9189
+
9152
9190
  async function tryGoogleAuth() {
9153
9191
  try {
9154
9192
  const accessToken = await fetchGoogleAccessToken(_url);
@@ -9167,6 +9205,8 @@ class IGVXhr {
9167
9205
  }
9168
9206
  }
9169
9207
  }
9208
+
9209
+
9170
9210
  })
9171
9211
 
9172
9212
  }
@@ -9239,20 +9279,29 @@ class IGVXhr {
9239
9279
  }
9240
9280
 
9241
9281
  /**
9242
- * This method should only be called when it is known the server supports HEAD requests. It is used to recover
9243
- * from 416 errors from out-of-spec WRT range request servers. Notably Globus.
9244
- * * *
9282
+ * Return the content length of the file at the given URL. This is not guaranteed to succeed, some servers
9283
+ * do not support or allow the content-length header.
9284
+ *
9245
9285
  * @param url
9246
9286
  * @param options
9247
9287
  * @returns {Promise<unknown>}
9248
9288
  */
9249
9289
  async getContentLength(url, options) {
9250
- options = options || {};
9251
- options.method = 'HEAD';
9252
- options.requestedHeaders = ['content-length'];
9253
- const headerMap = await this._loadURL(url, options);
9254
- const contentLengthString = headerMap['content-length'];
9255
- return contentLengthString ? Number.parseInt(contentLengthString) : 0
9290
+ if (isFile(url)) {
9291
+ return url.size
9292
+ } else {
9293
+ try {
9294
+ options = options || {};
9295
+ options.method = 'HEAD';
9296
+ options.requestedHeaders = ['content-length'];
9297
+ const headerMap = await this._loadURL(url, options);
9298
+ const contentLengthString = headerMap['content-length'];
9299
+ return contentLengthString ? Number.parseInt(contentLengthString) : 0
9300
+ } catch (e) {
9301
+ console.error(e);
9302
+ return -1
9303
+ }
9304
+ }
9256
9305
  }
9257
9306
 
9258
9307
  }
@@ -20664,6 +20713,7 @@ class FeatureFileReader {
20664
20713
 
20665
20714
  switch (config.format) {
20666
20715
  case "vcf":
20716
+ case "vcftabix":
20667
20717
  return new VcfParser(config)
20668
20718
  case "seg" :
20669
20719
  return new SegParser("seg")
@@ -21275,6 +21325,7 @@ class HtsgetVariantReader extends HtsgetReader {
21275
21325
  }
21276
21326
  }
21277
21327
 
21328
+ // Base class for feature sources. Subclasses must implement getFeatures().
21278
21329
  class BaseFeatureSource {
21279
21330
 
21280
21331
  constructor(genome) {
@@ -21336,59 +21387,10 @@ class BaseFeatureSource {
21336
21387
  }
21337
21388
  }
21338
21389
 
21339
- async previousFeature(chr, position, direction, visibilityWindow) {
21340
-
21341
- let chromosomeNames = this.genome.chromosomeNames || [chr];
21342
- let idx = chromosomeNames.indexOf(chr);
21343
- if (idx < 0) return // This shouldn't happen
21344
-
21345
- // Look ahead (or behind) in 10 kb intervals, but no further than visibilityWindow
21346
- const window = Math.min(10000, visibilityWindow || 10000);
21347
- let queryStart = direction ? position : Math.max(position - window, 0);
21348
- while (idx < chromosomeNames.length && idx >= 0) {
21349
- chr = chromosomeNames[idx];
21350
- const chromosome = this.genome.getChromosome(chr);
21351
- const chromosomeEnd = chromosome.bpLength;
21352
- while (queryStart < chromosomeEnd && queryStart >= 0) {
21353
- let queryEnd = Math.min(position, queryStart + window);
21354
- const featureList = await this.getFeatures({chr, start: queryStart, end: queryEnd, visibilityWindow});
21355
- if (featureList) {
21356
-
21357
- const compare = (o1, o2) => o1.start - o2.start + o1.end - o2.end;
21358
- const sortedList = Array.from(featureList);
21359
- sortedList.sort(compare);
21360
-
21361
- // Search for next or previous feature relative to centers. We use a linear search because the
21362
- // feature is likely to be near the first or end of the list
21363
- let idx = direction ? 0 : sortedList.length - 1;
21364
- while(idx >= 0 && idx < sortedList.length) {
21365
- const f = sortedList[idx];
21366
- const center = (f.start + f.end) / 2;
21367
- if(direction) {
21368
- if(center > position) return f
21369
- idx++;
21370
- } else {
21371
- if(center < position) return f
21372
- idx--;
21373
- }
21374
- }
21375
- }
21376
- queryStart = direction ? queryEnd : queryStart - window;
21377
- }
21378
- if (direction) {
21379
- idx++;
21380
- queryStart = 0;
21381
- position = 0;
21382
- } else {
21383
- idx--;
21384
- if (idx < 0) break
21385
- const prevChromosome = this.genome.getChromosome(chromosomeNames[idx]);
21386
- position = prevChromosome.bpLength;
21387
- queryStart = position - window;
21388
- }
21389
- }
21390
+ // Subclasses must implement
21391
+ async getFeatures({chr, start, end, bpPerPixel, visibilityWindow}) {
21392
+ throw new Error("getFeatures not implemented")
21390
21393
  }
21391
-
21392
21394
  }
21393
21395
 
21394
21396
  const GZIP_FLAG = 0x1;
@@ -26527,6 +26529,88 @@ class GenbankFeatureSource extends BaseFeatureSource {
26527
26529
  }
26528
26530
  }
26529
26531
 
26532
+ /**
26533
+ * A feature source for a "list" file. A list file is a text file with two columns, chromosome and URL. It was
26534
+ * created for the 1KG genotype files, which are split by chromosome, and at the moment that is the only use case.
26535
+ */
26536
+
26537
+ class ListFeatureSource extends BaseFeatureSource {
26538
+
26539
+ constructor(config, genome) {
26540
+ super(genome);
26541
+ this.config = config;
26542
+ this.featureSourceMap = null;
26543
+ this.header = null;
26544
+ }
26545
+
26546
+ async getHeader() {
26547
+
26548
+ if (!this.header) {
26549
+
26550
+ if (!this.featureSourceMap) {
26551
+ await this.init();
26552
+ }
26553
+ // Return the header from the first feature source. It is assumed that all sources have a common header.
26554
+ const firstFS = this.featureSourceMap.values().next().value;
26555
+ if (firstFS && firstFS.getHeader) {
26556
+ this.header = firstFS.getHeader();
26557
+ } else {
26558
+ this.header = Promise.resolve(undefined);
26559
+ }
26560
+ }
26561
+
26562
+ return this.header
26563
+
26564
+ }
26565
+
26566
+ async getFeatures({chr, start, end, bpPerPixel, visibilityWindow}) {
26567
+
26568
+ if (!this.featureSourceMap) {
26569
+ await this.init();
26570
+ }
26571
+ const fs = this.featureSourceMap.get(chr);
26572
+ if (fs) {
26573
+ return fs.getFeatures({chr, start, end, bpPerPixel, visibilityWindow})
26574
+ } else {
26575
+ return []
26576
+ }
26577
+ }
26578
+
26579
+ async init() {
26580
+ this.featureSourceMap = new Map();
26581
+
26582
+ const options = buildOptions(this.config);
26583
+ const data = await igvxhr.loadByteArray(this.config.url, options);
26584
+ const dataWrapper = getDataWrapper(data);
26585
+
26586
+ let line;
26587
+ while ((line = dataWrapper.nextLine()) !== undefined) {
26588
+ const trimmed = line.trim();
26589
+ if (!trimmed.startsWith('#')) {
26590
+ const tokens = trimmed.split(/\s+/);
26591
+ if (tokens.length > 1) {
26592
+ const chr = tokens[0];
26593
+ const path = tokens[1];
26594
+ const sourceConfig = Object.assign({}, this.config);
26595
+ sourceConfig.url = path;
26596
+ if (path.endsWith(".vcf.gz")) {
26597
+ sourceConfig.format = "vcf";
26598
+ sourceConfig.indexURL = path + ".tbi";
26599
+ }
26600
+ this.featureSourceMap.set(chr, FeatureSource(sourceConfig, this.genome));
26601
+ }
26602
+ }
26603
+ }
26604
+ }
26605
+
26606
+ supportWholeGenome() {
26607
+ return false
26608
+ }
26609
+ }
26610
+
26611
+ // chrY https://1000genomes.s3.amazonaws.com/release/20130502/ALL.chrY.phase3_integrated_v1b.20130502.genotypes.vcf.gz
26612
+ // chrX https://1000genomes.s3.amazonaws.com/release/20130502/ALL.chrX.phase3_shapeit2_mvncall_integrated_v1b.20130502.genotypes.vcf.gz
26613
+
26530
26614
  const bbFormats = new Set(['bigwig', 'bw', 'bigbed', 'bb', 'biginteract', 'biggenepred', 'bignarrowpeak']);
26531
26615
 
26532
26616
  function FeatureSource(config, genome) {
@@ -26541,6 +26625,9 @@ function FeatureSource(config, genome) {
26541
26625
  return new TDFSource(config, genome)
26542
26626
  } else if ("gbk" === format) {
26543
26627
  return new GenbankFeatureSource(config, genome)
26628
+ } else if ("vcf.list" === format) {
26629
+ // This is a text file with two columns: <chr> <url to vcf>
26630
+ return new ListFeatureSource(config, genome)
26544
26631
  } else {
26545
26632
  return new TextFeatureSource(config, genome)
26546
26633
  }
@@ -30403,6 +30490,19 @@ const supportedTypes = new Set([
30403
30490
  const filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps",
30404
30491
  "cpgIslandExtUnmasked", "windowMasker"]);
30405
30492
 
30493
+ const vizModeMap = new Map([
30494
+ ["pack", "EXPANDED"],
30495
+ ["full", "EXPANDED"],
30496
+ ["squish", "SQUISHED"],
30497
+ ["dense", "COLLAPSED"]
30498
+ ]);
30499
+ const typeFormatMap = new Map([
30500
+ ["vcftabix", "vcf"],
30501
+ ["vcfphasedtrio", "vcf"],
30502
+ ["bigdbsnp", "bigbed"],
30503
+ ["genepred", "refgene"]
30504
+ ]);
30505
+
30406
30506
  class TrackDbHub {
30407
30507
 
30408
30508
  constructor(trackStanzas, groupStanzas) {
@@ -30548,14 +30648,13 @@ class TrackDbHub {
30548
30648
  */
30549
30649
  #getTrackConfig(t) {
30550
30650
 
30551
- const format = t.format;
30651
+ const format = typeFormatMap.get(t.format) || t.format;
30552
30652
 
30553
30653
  const config = {
30554
30654
  "id": t.getProperty("track"),
30555
30655
  "name": t.getProperty("shortLabel"),
30556
30656
  "format": format,
30557
- "url": t.getProperty("bigDataUrl"),
30558
- "displayMode": t.displayMode,
30657
+ "url": t.getProperty("bigDataUrl")
30559
30658
  };
30560
30659
 
30561
30660
  if ("vcfTabix" === format) {
@@ -30605,9 +30704,14 @@ class TrackDbHub {
30605
30704
 
30606
30705
  }
30607
30706
  if (t.hasProperty("itemRgb")) ;
30608
- if ("hide" === t.getProperty("visibility")) {
30609
- // TODO -- this not supported yet
30610
- config.visible = false;
30707
+ if(t.hasProperty("visibility")) {
30708
+ if ("hide" === t.getProperty("visibility")) {
30709
+ // TODO -- this not supported yet
30710
+ config.visible = false;
30711
+ }
30712
+ else {
30713
+ config.displayMode = vizModeMap.get(t.getProperty("visibility")) || "COLLAPSED";
30714
+ }
30611
30715
  }
30612
30716
  if (t.hasProperty("url")) {
30613
30717
  config.infoURL = t.getProperty("url");
@@ -37788,6 +37892,8 @@ function inferTrackType(format) {
37788
37892
  case "tdf":
37789
37893
  return "wig"
37790
37894
  case "vcf":
37895
+ case "vcftabix":
37896
+ case "vcf.list":
37791
37897
  return "variant"
37792
37898
  case "seg":
37793
37899
  return "seg"
@@ -37842,7 +37948,7 @@ function translateDeprecatedTypes(config) {
37842
37948
  } else if ("bam" === config.type) {
37843
37949
  config.type = "alignment";
37844
37950
  config.format = "bam";
37845
- } else if ("vcf" === config.type) {
37951
+ } else if ("vcf" === config.type || "vcftabix" === config.type) {
37846
37952
  config.type = "variant";
37847
37953
  config.format = "vcf";
37848
37954
  } else if ("t2d" === config.type) {
@@ -38122,7 +38228,7 @@ class SegTrack extends TrackBase {
38122
38228
  const groupIndeces = NULL_GROUP !== this.groupBy ?
38123
38229
  this.sampleKeys.map(sample => this.getGroupIndex(sample)) : undefined;
38124
38230
  return {
38125
- names: this.sampleKeys,
38231
+ names: this.sampleKeys || [],
38126
38232
  height: this.sampleHeight,
38127
38233
  yOffset: 0,
38128
38234
  groups: this.groups,
@@ -62819,7 +62925,8 @@ class VariantTrack extends TrackBase {
62819
62925
  }
62820
62926
 
62821
62927
  get supportsWholeGenome() {
62822
- return !this.config.indexURL || this.config.supportsWholeGenome === true
62928
+ const sourceSupportsWG = typeof this.featureSource.supportsWholeGenome === 'function' && this.featureSource.supportsWholeGenome();
62929
+ return sourceSupportsWG || this.config.supportsWholeGenome === true
62823
62930
  }
62824
62931
 
62825
62932
  get color() {
@@ -62892,7 +62999,7 @@ class VariantTrack extends TrackBase {
62892
62999
  const yOffset = TOP_MARGIN + nVariantRows * (variantHeight + vGap);
62893
63000
 
62894
63001
  return {
62895
- names: this.sampleKeys,
63002
+ names: this.sampleKeys || [],
62896
63003
  yOffset,
62897
63004
  height,
62898
63005
  // groups: this.groups,
@@ -67023,7 +67130,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
67023
67130
  })
67024
67131
  }
67025
67132
 
67026
- const _version = "3.5.2";
67133
+ const _version = "3.5.3";
67027
67134
  function version() {
67028
67135
  return _version
67029
67136
  }
@@ -74570,6 +74677,10 @@ class Browser {
74570
74677
  config = JSON.parse(config);
74571
74678
  }
74572
74679
 
74680
+ if(config.format && config.format.toLowerCase() === 'sampleinfo') {
74681
+ return this.loadSampleInfo(config)
74682
+ }
74683
+
74573
74684
  let track;
74574
74685
  try {
74575
74686
  track = await this.createTrack(config);
@@ -76346,6 +76457,10 @@ function setOauthToken(accessToken, host) {
76346
76457
  return igvxhr.setOauthToken(accessToken, host)
76347
76458
  }
76348
76459
 
76460
+ function setCORSProxy(proxyURL) {
76461
+ igvxhr.corsProxy = proxyURL;
76462
+ }
76463
+
76349
76464
  // Backward compatibility
76350
76465
  const oauth = igvxhr.oauth;
76351
76466
 
@@ -76362,6 +76477,7 @@ var index = {
76362
76477
  visibilityChange,
76363
76478
  setGoogleOauthToken,
76364
76479
  setOauthToken,
76480
+ setCORSProxy,
76365
76481
  oauth,
76366
76482
  version,
76367
76483
  setApiKey,