igv 3.2.4 → 3.2.6

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/README.md CHANGED
@@ -18,19 +18,19 @@ Below are examples and a quickstart guide. See the [developer documentation](ht
18
18
 
19
19
  # Examples
20
20
 
21
- ***[Alignments](https://igv.org/web/release/3.2.4/examples/cram-vcf.html)***
21
+ ***[Alignments](https://igv.org/web/release/3.2.6/examples/cram-vcf.html)***
22
22
 
23
- ***[Interactions](https://igv.org/web/release/3.2.4/examples/interact.html)***
23
+ ***[Interactions](https://igv.org/web/release/3.2.6/examples/interact.html)***
24
24
 
25
- ***[Copy number](https://igv.org/web/release/3.2.4/examples/copyNumber.html)***
25
+ ***[Copy number](https://igv.org/web/release/3.2.6/examples/copyNumber.html)***
26
26
 
27
- ***[Multiple regions](https://igv.org/web/release/3.2.4/examples/multi-locus.html)***
27
+ ***[Multiple regions](https://igv.org/web/release/3.2.6/examples/multi-locus.html)***
28
28
 
29
- ***[Mutation Annotation Format (MAF)](https://igv.org/web/release/3.2.4/examples/maf-tcga.html)***
29
+ ***[Mutation Annotation Format (MAF)](https://igv.org/web/release/3.2.6/examples/maf-tcga.html)***
30
30
 
31
- ***[Variant color options](https://igv.org/web/release/3.2.4/examples/variant-colors.html)***
31
+ ***[Variant color options](https://igv.org/web/release/3.2.6/examples/variant-colors.html)***
32
32
 
33
- ***[More](https://igv.org/web/release/3.2.4/examples/)***
33
+ ***[More](https://igv.org/web/release/3.2.6/examples/)***
34
34
 
35
35
 
36
36
  # Quickstart
@@ -39,18 +39,18 @@ Below are examples and a quickstart guide. See the [developer documentation](ht
39
39
  igv.js consists of a single javascript file with no external dependencies.
40
40
 
41
41
  Pre-built files for script include, AMD, or CJS module systems (igv.min.js) and an ES6 module (igv.esm.min.js)
42
- can be downloaded from [https://cdn.jsdelivr.net/npm/igv@3.2.4/dist/](https://cdn.jsdelivr.net/npm/igv@3.2.4/dist/).
42
+ can be downloaded from [https://cdn.jsdelivr.net/npm/igv@3.2.6/dist/](https://cdn.jsdelivr.net/npm/igv@3.2.6/dist/).
43
43
 
44
44
  To import igv as an ES6 module
45
45
 
46
46
  ```javascript
47
- import igv from "https://cdn.jsdelivr.net/npm/igv@3.2.4/dist/igv.esm.min.js"
47
+ import igv from "https://cdn.jsdelivr.net/npm/igv@3.2.6/dist/igv.esm.min.js"
48
48
  ```
49
49
 
50
50
  Or as a script include (defines the "igv" global)
51
51
 
52
52
  ```html
53
- <script src="https://cdn.jsdelivr.net/npm/igv@3.2.4/dist/igv.min.js"></script>
53
+ <script src="https://cdn.jsdelivr.net/npm/igv@3.2.6/dist/igv.min.js"></script>
54
54
  ```
55
55
 
56
56
  Alternatively you can install with npm
package/dist/igv.esm.js CHANGED
@@ -8623,6 +8623,7 @@ function getCurrentAccessToken() {
8623
8623
  }
8624
8624
 
8625
8625
 
8626
+ let promise;
8626
8627
  /**
8627
8628
  * Return a promise for an access token for the given scope. If the user hasn't authorized the scope request it
8628
8629
  *
@@ -8641,22 +8642,27 @@ async function getAccessToken(scope) {
8641
8642
  return google.igv.tokenResponse.access_token
8642
8643
  } else {
8643
8644
  const tokenClient = google.igv.tokenClient;
8644
- return new Promise((resolve, reject) => {
8645
- try {
8646
- // Settle this promise in the response callback for requestAccessToken()
8647
- tokenClient.callback = (tokenResponse) => {
8648
- if (tokenResponse.error !== undefined) {
8649
- reject(tokenResponse);
8650
- }
8651
- google.igv.tokenResponse = tokenResponse;
8652
- google.igv.tokenExpiresAt = Date.now() + tokenResponse.expires_in * 1000;
8653
- resolve(tokenResponse.access_token);
8654
- };
8655
- tokenClient.requestAccessToken({scope});
8656
- } catch (err) {
8657
- console.log(err);
8658
- }
8659
- })
8645
+ if(!promise) {
8646
+ promise = new Promise((resolve, reject) => {
8647
+ try {
8648
+ // Settle this promise in the response callback for requestAccessToken()
8649
+ tokenClient.callback = (tokenResponse) => {
8650
+ if (tokenResponse.error !== undefined) {
8651
+ reject(tokenResponse);
8652
+ }
8653
+ google.igv.tokenResponse = tokenResponse;
8654
+ google.igv.tokenExpiresAt = Date.now() + tokenResponse.expires_in * 1000;
8655
+ console.log("Access token expires at " + new Date(google.igv.tokenExpiresAt));
8656
+ resolve(tokenResponse.access_token);
8657
+ };
8658
+ console.log("Requesting access token");
8659
+ tokenClient.requestAccessToken({scope});
8660
+ } catch (err) {
8661
+ console.log(err);
8662
+ }
8663
+ });
8664
+ }
8665
+ return promise
8660
8666
  }
8661
8667
  }
8662
8668
 
@@ -10940,7 +10946,7 @@ function createMenuElements$1(itemList, popover) {
10940
10946
  return list;
10941
10947
  }
10942
10948
 
10943
- /*! @license DOMPurify 3.2.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.4/LICENSE */
10949
+ /*! @license DOMPurify 3.2.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.5/LICENSE */
10944
10950
 
10945
10951
  const {
10946
10952
  entries,
@@ -11000,6 +11006,9 @@ const typeErrorCreate = unconstruct(TypeError);
11000
11006
  */
11001
11007
  function unapply(func) {
11002
11008
  return function (thisArg) {
11009
+ if (thisArg instanceof RegExp) {
11010
+ thisArg.lastIndex = 0;
11011
+ }
11003
11012
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
11004
11013
  args[_key - 1] = arguments[_key];
11005
11014
  }
@@ -11238,7 +11247,7 @@ const _createHooksMap = function _createHooksMap() {
11238
11247
  function createDOMPurify() {
11239
11248
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
11240
11249
  const DOMPurify = root => createDOMPurify(root);
11241
- DOMPurify.version = '3.2.4';
11250
+ DOMPurify.version = '3.2.5';
11242
11251
  DOMPurify.removed = [];
11243
11252
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
11244
11253
  // Not running in a browser, provide a factory function
@@ -11843,7 +11852,7 @@ function createDOMPurify() {
11843
11852
  allowedTags: ALLOWED_TAGS
11844
11853
  });
11845
11854
  /* Detect mXSS attempts abusing namespace confusion */
11846
- if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
11855
+ if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
11847
11856
  _forceRemove(currentNode);
11848
11857
  return true;
11849
11858
  }
@@ -16337,7 +16346,10 @@ class FeatureParser {
16337
16346
  }
16338
16347
  } else {
16339
16348
  // All directives that could change the format, and thus decoder, should have been read by now.
16340
- this.setDecoder(header.format);
16349
+ // Set the decoder, unless it is explicitly set in the track configuration (not common)
16350
+ if(!this.config.decode) {
16351
+ this.setDecoder(header.format);
16352
+ }
16341
16353
 
16342
16354
  // If the line can be parsed as a feature assume we are beyond the header, if any
16343
16355
  const tokens = line.split(this.delimiter || "\t");
@@ -30846,6 +30858,7 @@ function indentLevel(str) {
30846
30858
  }
30847
30859
 
30848
30860
  const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
30861
+ const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-genomes/refs/heads/main/dist/genomes.json";
30849
30862
 
30850
30863
  const GenomeUtils = {
30851
30864
 
@@ -30853,34 +30866,42 @@ const GenomeUtils = {
30853
30866
 
30854
30867
  if (!GenomeUtils.KNOWN_GENOMES) {
30855
30868
 
30856
- const table = {};
30869
+ let table = {};
30870
+
30871
+ const processJson = (jsonArray, table) => {
30872
+ jsonArray.forEach(function (json) {
30873
+ table[json.id] = json;
30874
+ });
30875
+ return table
30876
+ };
30857
30877
 
30858
30878
  // Get default genomes
30859
30879
  if (config.loadDefaultGenomes !== false) {
30860
- const url = DEFAULT_GENOMES_URL;
30861
- const jsonArray = await igvxhr.loadJson(url, {timeout: 5000});
30862
- processJson(jsonArray);
30880
+ try {
30881
+ const jsonArray = await igvxhr.loadJson(DEFAULT_GENOMES_URL, {timeout: 2000});
30882
+ processJson(jsonArray, table);
30883
+ } catch (error) {
30884
+ try {
30885
+ console.error("Error initializing default genomes:", error);
30886
+ const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 2000});
30887
+ processJson(jsonArray, table);
30888
+ } catch (e) {
30889
+ console.error("Error initializing backup genomes:", error);
30890
+ }
30891
+ }
30863
30892
  }
30864
30893
 
30865
- // Add user-defined genomes
30894
+ // Append user-defined genomes, which might override defaults
30866
30895
  const genomeList = config.genomeList || config.genomes;
30867
30896
  if (genomeList) {
30868
30897
  if (typeof genomeList === 'string') {
30869
30898
  const jsonArray = await igvxhr.loadJson(genomeList, {});
30870
- processJson(jsonArray);
30899
+ processJson(jsonArray, table);
30871
30900
  } else {
30872
- processJson(genomeList);
30901
+ processJson(genomeList, table);
30873
30902
  }
30874
30903
  }
30875
-
30876
30904
  GenomeUtils.KNOWN_GENOMES = table;
30877
-
30878
- function processJson(jsonArray) {
30879
- jsonArray.forEach(function (json) {
30880
- table[json.id] = json;
30881
- });
30882
- return table
30883
- }
30884
30905
  }
30885
30906
  },
30886
30907
 
@@ -30906,7 +30927,7 @@ const GenomeUtils = {
30906
30927
  } else if (idOrConfig.genome) {
30907
30928
  genomeID = idOrConfig.genome;
30908
30929
  } else if (idOrConfig.id !== undefined && !(idOrConfig.fastaURL || idOrConfig.twobitURL)) {
30909
- // Backward compatibilityz
30930
+ // Backward compatibility
30910
30931
  genomeID = idOrConfig.id;
30911
30932
  }
30912
30933
 
@@ -47607,7 +47628,7 @@ class AlignmentTrack extends TrackBase {
47607
47628
  if (!this.colorBy) {
47608
47629
  this.colorBy = this.hasPairs ? "unexpectedPair" : "none";
47609
47630
  }
47610
-
47631
+
47611
47632
  let pixelTop = options.pixelTop - BAMTrack.coverageTrackHeight;
47612
47633
  if (this.top) {
47613
47634
  ctx.translate(0, this.top);
@@ -48314,7 +48335,12 @@ class AlignmentTrack extends TrackBase {
48314
48335
  this.trackView.repaintViews();
48315
48336
  }
48316
48337
 
48317
- return {name: undefined, element: createCheckbox(menuItem.label, showCheck), click: clickHandler, init: undefined}
48338
+ return {
48339
+ name: undefined,
48340
+ element: createCheckbox(menuItem.label, showCheck),
48341
+ click: clickHandler,
48342
+ init: undefined
48343
+ }
48318
48344
  }
48319
48345
 
48320
48346
 
@@ -48361,7 +48387,12 @@ class AlignmentTrack extends TrackBase {
48361
48387
  }
48362
48388
  }
48363
48389
 
48364
- return {name: undefined, element: createCheckbox(menuItem.label, showCheck), dialog: clickHandler, init: undefined}
48390
+ return {
48391
+ name: undefined,
48392
+ element: createCheckbox(menuItem.label, showCheck),
48393
+ dialog: clickHandler,
48394
+ init: undefined
48395
+ }
48365
48396
 
48366
48397
  }
48367
48398
 
@@ -48483,38 +48514,88 @@ class AlignmentTrack extends TrackBase {
48483
48514
  });
48484
48515
  }
48485
48516
 
48517
+ list.push('<hr/>');
48518
+ const softClips = clickedAlignment.softClippedBlocks();
48486
48519
  list.push({
48487
48520
  label: 'View read sequence',
48488
48521
  click: () => {
48489
- const seqstring = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
48490
- if (!seqstring || "*" === seqstring) {
48491
- this.browser.alert.present("Read sequence: *");
48492
- } else {
48493
- this.browser.alert.present(seqstring);
48494
- }
48522
+ const seqstring = clickedAlignment.seq;
48523
+ this.browser.alert.present(seqstring && seqstring !== "*" ? seqstring : "Read sequence: *");
48495
48524
  }
48496
48525
  });
48497
48526
 
48527
+ if (softClips.left && softClips.left.len > 0) {
48528
+ list.push({
48529
+ label: 'View left soft-clipped sequence',
48530
+ click: () => {
48531
+ const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
48532
+ this.browser.alert.present(clippedSequence);
48533
+ }
48534
+ });
48535
+ }
48536
+
48537
+ if (softClips.right && softClips.right.len > 0) {
48538
+ list.push({
48539
+ label: 'View right soft-clipped sequence',
48540
+ click: () => {
48541
+ const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
48542
+ this.browser.alert.present(clippedSequence);
48543
+ }
48544
+ });
48545
+ }
48546
+
48547
+ list.push('<hr/>');
48548
+
48498
48549
  if (isSecureContext()) {
48499
48550
  list.push({
48500
48551
  label: 'Copy read sequence',
48501
48552
  click: async () => {
48502
- const seq = clickedAlignment.seq; //.map(b => String.fromCharCode(b)).join("");
48503
48553
  try {
48504
- await navigator.clipboard.writeText(seq);
48554
+ await navigator.clipboard.writeText(clickedAlignment.seq);
48505
48555
  } catch (e) {
48506
48556
  console.error(e);
48507
48557
  this.browser.alert.present(`error copying sequence to clipboard ${e}`);
48508
48558
  }
48509
-
48510
48559
  }
48511
48560
  });
48561
+
48562
+ if (softClips.left && softClips.left.len > 0) {
48563
+ list.push({
48564
+ label: 'Copy left soft-clipped sequence',
48565
+ click: async () => {
48566
+ try {
48567
+ const clippedSequence = clickedAlignment.seq.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
48568
+ await navigator.clipboard.writeText(clippedSequence);
48569
+ } catch (e) {
48570
+ console.error(e);
48571
+ this.browser.alert.present(`error copying sequence to clipboard ${e}`);
48572
+ }
48573
+ }
48574
+ });
48575
+ }
48576
+
48577
+ if (softClips.right && softClips.right.len > 0) {
48578
+ list.push({
48579
+ label: 'Copy right soft-clipped sequence',
48580
+ click: async () => {
48581
+ try {
48582
+ const clippedSequence = clickedAlignment.seq.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
48583
+ await navigator.clipboard.writeText(clippedSequence);
48584
+ } catch (e) {
48585
+ console.error(e);
48586
+ this.browser.alert.present(`error copying sequence to clipboard ${e}`);
48587
+ }
48588
+ }
48589
+ });
48590
+ }
48512
48591
  }
48513
48592
 
48514
- // TODO if genome supports blat
48593
+ // TODO test if genome supports blat
48515
48594
  const seqstring = clickedAlignment.seq;
48516
48595
  if (seqstring && "*" !== seqstring) {
48517
48596
 
48597
+ list.push('<hr/>');
48598
+
48518
48599
  if (seqstring.length < maxSequenceSize$1) {
48519
48600
  list.push({
48520
48601
  label: 'BLAT read sequence',
@@ -48532,7 +48613,7 @@ class AlignmentTrack extends TrackBase {
48532
48613
  list.push({
48533
48614
  label: 'BLAT left soft-clipped sequence',
48534
48615
  click: () => {
48535
- const clippedSequence = seqstring.substr(softClips.left.seqOffset, softClips.left.len);
48616
+ const clippedSequence = seqstring.substring(softClips.left.seqOffset, softClips.left.seqOffset + softClips.left.len);
48536
48617
  const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
48537
48618
  const name = `${clickedAlignment.readName} - blat left clip`;
48538
48619
  const title = `${this.name} - ${name}`;
@@ -48544,7 +48625,7 @@ class AlignmentTrack extends TrackBase {
48544
48625
  list.push({
48545
48626
  label: 'BLAT right soft-clipped sequence',
48546
48627
  click: () => {
48547
- const clippedSequence = seqstring.substr(softClips.right.seqOffset, softClips.right.len);
48628
+ const clippedSequence = seqstring.substring(softClips.right.seqOffset, softClips.right.seqOffset + softClips.right.len);
48548
48629
  const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(clippedSequence) : clippedSequence;
48549
48630
  const name = `${clickedAlignment.readName} - blat right clip`;
48550
48631
  const title = `${this.name} - ${name}`;
@@ -48593,7 +48674,7 @@ class AlignmentTrack extends TrackBase {
48593
48674
  const offsetY = y - this.top;
48594
48675
  const genomicLocation = clickState.genomicLocation;
48595
48676
 
48596
- if(features.packedGroups) {
48677
+ if (features.packedGroups) {
48597
48678
  let minGroupY = Number.MAX_VALUE;
48598
48679
  for (let group of features.packedGroups.values()) {
48599
48680
  minGroupY = Math.min(minGroupY, group.pixelTop);
@@ -68819,6 +68900,115 @@ function prettyPrint(number) {
68819
68900
  }
68820
68901
  }
68821
68902
 
68903
+ /**
68904
+ * Configurable properties
68905
+ * Locus {chr, start, end}
68906
+ * url - url to image. Later url to webservice to fetch image
68907
+ */
68908
+
68909
+ class ImageTrack extends TrackBase {
68910
+
68911
+ static defaults = {}
68912
+
68913
+ constructor(config, browser) {
68914
+ super(config, browser);
68915
+ }
68916
+
68917
+ init(config) {
68918
+
68919
+ super.init(config);
68920
+
68921
+ if (!config.images) {
68922
+ throw Error("images are required")
68923
+ }
68924
+
68925
+ this.locus = config.locus;
68926
+ this.type = "image";
68927
+ this.resolutionAware = true;
68928
+ }
68929
+
68930
+
68931
+ async postInit() {
68932
+
68933
+ this._images = [];
68934
+
68935
+ for (let i of this.config.images) {
68936
+ const img = new Image();
68937
+ img.onload = () => {
68938
+ i.img = img;
68939
+ i.bpPerPixel = (i.end - i.start) / img.width;
68940
+ this._images.push(i);
68941
+ };
68942
+ img.onerror = (err) => {
68943
+ console.error(err);
68944
+ };
68945
+ //if (img.complete) { //cached image
68946
+ // img.onload()
68947
+ // }
68948
+ img.src = i.src;
68949
+ }
68950
+
68951
+ }
68952
+
68953
+ computePixelHeight(features) {
68954
+ return features ? features.height : 0
68955
+ }
68956
+
68957
+
68958
+ menuItemList() {
68959
+
68960
+ const menuItems = [];
68961
+
68962
+ return menuItems
68963
+ }
68964
+
68965
+
68966
+ async getFeatures(chr, start, end, bpPerPixel) {
68967
+ // Return image. Scaled or not?
68968
+ return this.selectImage(chr, start, end, bpPerPixel)
68969
+ }
68970
+
68971
+ selectImage(chr, start, end, bpPerPixel) {
68972
+
68973
+ // Select the highest resolution image containing the interval. If no image contains the interval return
68974
+ // the lowest resolution image if it overlaps
68975
+ if(this._images.length == 0) {
68976
+ return null
68977
+ }
68978
+ this._images.sort((a, b) => a.bpPerPixel < b.bpPerPixel ? -1 : 1);
68979
+ for(let i of this._images) {
68980
+ if(i.bpPerPixel > bpPerPixel) {
68981
+ return i
68982
+ }
68983
+ }
68984
+ const lowRes = this._images[this._images.length-1];
68985
+ if(lowRes.chr === chr) {
68986
+ return lowRes
68987
+ } else {
68988
+ return null
68989
+ }
68990
+ }
68991
+
68992
+ draw({context, pixelTop, pixelWidth, pixelHeight, features, bpPerPixel, bpStart}) {
68993
+
68994
+ const image = features.img;
68995
+ if (image) {
68996
+ const nw = image.width;
68997
+ const nh = image.height;
68998
+ const imageBpPerPixel = (features.end - features.start) / nw;
68999
+ const scale = imageBpPerPixel / bpPerPixel;
69000
+ const x = (features.start - bpStart) / bpPerPixel;
69001
+ context.drawImage(image, x, 0, scale * nw, nh);
69002
+ }
69003
+
69004
+ }
69005
+
69006
+ get supportsWholeGenome() {
69007
+ return false
69008
+ }
69009
+
69010
+ }
69011
+
68822
69012
  //import CNVPytorTrack from "./CNVpytor/cnvpytorTrack.js"
68823
69013
 
68824
69014
 
@@ -68844,7 +69034,8 @@ const trackFunctions =
68844
69034
  ['gcnv', (config, browser) => new GCNVTrack(config, browser)],
68845
69035
  ['junction', (config, browser) => new SpliceJunctionTrack(config, browser)],
68846
69036
  ['blat', (config, browser) => new BlatTrack(config, browser)],
68847
- ['cnvpytor', (config, browser) => new CNVPytorTrack(config, browser)]
69037
+ ['cnvpytor', (config, browser) => new CNVPytorTrack(config, browser)],
69038
+ ['image', (config, browser) => new ImageTrack(config, browser)]
68848
69039
  ]);
68849
69040
 
68850
69041
 
@@ -69322,7 +69513,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
69322
69513
  })
69323
69514
  }
69324
69515
 
69325
- const _version = "3.2.4";
69516
+ const _version = "3.2.6";
69326
69517
  function version() {
69327
69518
  return _version
69328
69519
  }
@@ -73865,7 +74056,7 @@ class Browser {
73865
74056
  async loadTrackList(configList) {
73866
74057
 
73867
74058
  // Impose an order if not specified
73868
- let order = 0;
74059
+ let order = this.trackViews.length + 1;
73869
74060
  for (let c of configList) {
73870
74061
  if (c.order === undefined) {
73871
74062
  c.order = order++;