igv 3.3.0 → 3.4.0

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
@@ -373,7 +373,7 @@ class Dialog {
373
373
  * @param x
374
374
  * @returns {boolean}
375
375
  */
376
- function isString$2(x) {
376
+ function isString$3(x) {
377
377
  return typeof x === "string" || x instanceof String
378
378
  }
379
379
 
@@ -473,7 +473,7 @@ function getFilename$2(urlOrFile) {
473
473
 
474
474
  if (urlOrFile.name !== undefined) {
475
475
  return urlOrFile.name
476
- } else if (isString$2(urlOrFile)) {
476
+ } else if (isString$3(urlOrFile)) {
477
477
 
478
478
  let index = urlOrFile.lastIndexOf("/");
479
479
  let filename = index < 0 ? urlOrFile : urlOrFile.substr(index + 1);
@@ -10678,7 +10678,7 @@ const isNumber = function (num) {
10678
10678
  };
10679
10679
 
10680
10680
  async function getFilename$1(url) {
10681
- if (isString$2(url) && url.startsWith("https://drive.google.com")) {
10681
+ if (isString$3(url) && url.startsWith("https://drive.google.com")) {
10682
10682
  // This will fail if Google API key is not defined
10683
10683
  if (getApiKey() === undefined) {
10684
10684
  throw Error("Google drive is referenced, but API key is not defined. An API key is required for Google Drive access")
@@ -10720,7 +10720,7 @@ function prettyBasePairNumber(raw) {
10720
10720
 
10721
10721
 
10722
10722
  function isDataURL(obj) {
10723
- return (isString$2(obj) && obj.startsWith("data:"))
10723
+ return (isString$3(obj) && obj.startsWith("data:"))
10724
10724
  }
10725
10725
 
10726
10726
  function createColumn(columnContainer, className) {
@@ -10946,7 +10946,7 @@ function createMenuElements$1(itemList, popover) {
10946
10946
  return list;
10947
10947
  }
10948
10948
 
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 */
10949
+ /*! @license DOMPurify 3.2.6 | (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.6/LICENSE */
10950
10950
 
10951
10951
  const {
10952
10952
  entries,
@@ -11150,7 +11150,7 @@ const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
11150
11150
  const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
11151
11151
  const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
11152
11152
  const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
11153
- const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
11153
+ const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
11154
11154
  );
11155
11155
  const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
11156
11156
  const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
@@ -11247,7 +11247,7 @@ const _createHooksMap = function _createHooksMap() {
11247
11247
  function createDOMPurify() {
11248
11248
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
11249
11249
  const DOMPurify = root => createDOMPurify(root);
11250
- DOMPurify.version = '3.2.5';
11250
+ DOMPurify.version = '3.2.6';
11251
11251
  DOMPurify.removed = [];
11252
11252
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
11253
11253
  // Not running in a browser, provide a factory function
@@ -11486,8 +11486,8 @@ function createDOMPurify() {
11486
11486
  URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
11487
11487
  DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
11488
11488
  FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
11489
- FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
11490
- FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
11489
+ FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
11490
+ FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
11491
11491
  USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
11492
11492
  ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
11493
11493
  ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
@@ -11852,7 +11852,7 @@ function createDOMPurify() {
11852
11852
  allowedTags: ALLOWED_TAGS
11853
11853
  });
11854
11854
  /* Detect mXSS attempts abusing namespace confusion */
11855
- if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
11855
+ if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
11856
11856
  _forceRemove(currentNode);
11857
11857
  return true;
11858
11858
  }
@@ -12004,7 +12004,8 @@ function createDOMPurify() {
12004
12004
  value: attrValue
12005
12005
  } = attr;
12006
12006
  const lcName = transformCaseFunc(name);
12007
- let value = name === 'value' ? attrValue : stringTrim(attrValue);
12007
+ const initValue = attrValue;
12008
+ let value = name === 'value' ? initValue : stringTrim(initValue);
12008
12009
  /* Execute a hook if present */
12009
12010
  hookEvent.attrName = lcName;
12010
12011
  hookEvent.attrValue = value;
@@ -12030,10 +12031,9 @@ function createDOMPurify() {
12030
12031
  if (hookEvent.forceKeepAttr) {
12031
12032
  continue;
12032
12033
  }
12033
- /* Remove attribute */
12034
- _removeAttribute(name, currentNode);
12035
12034
  /* Did the hooks approve of the attribute? */
12036
12035
  if (!hookEvent.keepAttr) {
12036
+ _removeAttribute(name, currentNode);
12037
12037
  continue;
12038
12038
  }
12039
12039
  /* Work around a security issue in jQuery 3.0 */
@@ -12050,6 +12050,7 @@ function createDOMPurify() {
12050
12050
  /* Is `value` valid for this attribute? */
12051
12051
  const lcTag = transformCaseFunc(currentNode.nodeName);
12052
12052
  if (!_isValidAttribute(lcTag, lcName, value)) {
12053
+ _removeAttribute(name, currentNode);
12053
12054
  continue;
12054
12055
  }
12055
12056
  /* Handle attributes that require Trusted Types */
@@ -12070,19 +12071,23 @@ function createDOMPurify() {
12070
12071
  }
12071
12072
  }
12072
12073
  /* Handle invalid data-* attribute set by try-catching it */
12073
- try {
12074
- if (namespaceURI) {
12075
- currentNode.setAttributeNS(namespaceURI, name, value);
12076
- } else {
12077
- /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
12078
- currentNode.setAttribute(name, value);
12079
- }
12080
- if (_isClobbered(currentNode)) {
12081
- _forceRemove(currentNode);
12082
- } else {
12083
- arrayPop(DOMPurify.removed);
12074
+ if (value !== initValue) {
12075
+ try {
12076
+ if (namespaceURI) {
12077
+ currentNode.setAttributeNS(namespaceURI, name, value);
12078
+ } else {
12079
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
12080
+ currentNode.setAttribute(name, value);
12081
+ }
12082
+ if (_isClobbered(currentNode)) {
12083
+ _forceRemove(currentNode);
12084
+ } else {
12085
+ arrayPop(DOMPurify.removed);
12086
+ }
12087
+ } catch (_) {
12088
+ _removeAttribute(name, currentNode);
12084
12089
  }
12085
- } catch (_) {}
12090
+ }
12086
12091
  }
12087
12092
  /* Execute a hook if present */
12088
12093
  _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
@@ -16903,7 +16908,7 @@ function findFeatureAfterCenter(featureList, center, direction = true) {
16903
16908
  */
16904
16909
 
16905
16910
  const fixColor = (colorString) => {
16906
- if (isString$2(colorString)) {
16911
+ if (isString$3(colorString)) {
16907
16912
  return (colorString.indexOf(",") > 0 && !(colorString.startsWith("rgb(") || colorString.startsWith("rgba("))) ?
16908
16913
  `rgb(${colorString})` : colorString
16909
16914
  } else {
@@ -16975,7 +16980,7 @@ class TrackBase {
16975
16980
  this.name = config.name || config.label;
16976
16981
  } else if (isFile(config.url)) {
16977
16982
  this.name = config.url.name;
16978
- } else if (isString$2(config.url) && !config.url.startsWith("data:")) {
16983
+ } else if (isString$3(config.url) && !config.url.startsWith("data:")) {
16979
16984
  this.name = getFilename$2(config.url);
16980
16985
  }
16981
16986
 
@@ -20897,7 +20902,7 @@ class FeatureFileReader {
20897
20902
  * THE SOFTWARE.
20898
20903
  */
20899
20904
 
20900
- const isString$1 = isString$2;
20905
+ const isString$2 = isString$3;
20901
20906
 
20902
20907
 
20903
20908
  class CustomServiceReader {
@@ -20936,7 +20941,7 @@ class CustomServiceReader {
20936
20941
  if (data) {
20937
20942
  if (typeof this.config.parser === "function") {
20938
20943
  features = this.config.parser(data);
20939
- } else if (isString$1(data)) {
20944
+ } else if (isString$2(data)) {
20940
20945
  features = JSON.parse(data);
20941
20946
  } else {
20942
20947
  features = data;
@@ -23019,7 +23024,7 @@ class BWReader {
23019
23024
  // TODO -- align all feature attribute names with UCSC, an use specific column
23020
23025
  for (let key of Object.keys(f)) {
23021
23026
  const v = f[key];
23022
- if (isString$2(v) && v.toLowerCase() === term.toLowerCase()) {
23027
+ if (isString$3(v) && v.toLowerCase() === term.toLowerCase()) {
23023
23028
  return true
23024
23029
  }
23025
23030
  }
@@ -23317,7 +23322,7 @@ class BWReader {
23317
23322
  class ZoomLevelHeader {
23318
23323
  constructor(index, byteBuffer) {
23319
23324
  this.index = index;
23320
- this.reductionLevel = byteBuffer.getInt();
23325
+ this.reductionLevel = byteBuffer.getUInt();
23321
23326
  this.reserved = byteBuffer.getInt();
23322
23327
  this.dataOffset = byteBuffer.getLong();
23323
23328
  this.indexOffset = byteBuffer.getLong();
@@ -27643,7 +27648,7 @@ class FeatureTrack extends TrackBase {
27643
27648
  fd.name &&
27644
27649
  fd.name.toLowerCase() === "name" &&
27645
27650
  fd.value &&
27646
- isString$2(fd.value) &&
27651
+ isString$3(fd.value) &&
27647
27652
  !fd.value.startsWith("<")) {
27648
27653
  const href = infoURL.replace("$$", feature.name);
27649
27654
  fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
@@ -28287,7 +28292,7 @@ class BlatTrack extends FeatureTrack {
28287
28292
  async postInit() {
28288
28293
  if(!this.featureSource) {
28289
28294
  // This will be the case when restoring from a session
28290
- const db = this.browser.genome.id; // TODO -- blat specific property
28295
+ const db = this.browser.genome.ucscID; // TODO -- blat specific property
28291
28296
  const url = this.browser.config["blatServerURL"];
28292
28297
  const features = await blat({url, userSeq: this.sequence, db});
28293
28298
  this._features = features;
@@ -28304,7 +28309,7 @@ class BlatTrack extends FeatureTrack {
28304
28309
  if (undefined === this.table) {
28305
28310
 
28306
28311
  const rows = this._features.map(f => [
28307
- f.chr,
28312
+ this.browser.genome.getChromosomeDisplayName(f.chr),
28308
28313
  (f.start + 1),
28309
28314
  f.end,
28310
28315
  f.strand,
@@ -28379,7 +28384,7 @@ async function createBlatTrack({sequence, browser, name, title}) {
28379
28384
 
28380
28385
  try {
28381
28386
 
28382
- const db = browser.genome.id; // TODO -- blat specific property
28387
+ const db = browser.genome.ucscID; // TODO -- blat specific property
28383
28388
  const url = browser.config["blatServerURL"] || defaultBlatServer;
28384
28389
  const features = await blat({url, userSeq: sequence, db});
28385
28390
 
@@ -30609,13 +30614,6 @@ class TrackDbHub {
30609
30614
  this.trackStanzas = trackStanzas;
30610
30615
  }
30611
30616
 
30612
- findCytobandURL() {
30613
- for (const t of this.trackStanzas) {
30614
- if (t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) {
30615
- return t.getProperty("bigDataUrl")
30616
- }
30617
- }
30618
- }
30619
30617
 
30620
30618
  getSupportedTrackCount() {
30621
30619
  let count = 0;
@@ -30955,6 +30953,7 @@ function parseMetadata(metadata) {
30955
30953
 
30956
30954
  const idMappings = new Map([
30957
30955
  ["hg38", "GCF_000001405.40"],
30956
+ ["hg38_1kg", "GCF_000001405.40"],
30958
30957
  ["mm39", "GCF_000001635.27"],
30959
30958
  ["mm10", "GCF_000001635.26"],
30960
30959
  ["bosTau9", "GCF_002263795.1"],
@@ -30991,12 +30990,16 @@ class Hub {
30991
30990
  this.genomeStanzas = genomeStanzas;
30992
30991
  this.trackStanzas = trackStanzas;
30993
30992
  this.groupStanzas = groupStanzas;
30993
+ this.cytobandStanza = null;
30994
30994
  this.trackHubMap = new Map();
30995
30995
 
30996
30996
  // trackStanzas will not be null if this is a "onefile" hub
30997
30997
  if (trackStanzas) {
30998
30998
  const genomeId = genomeStanzas[0].getProperty("genome"); // Assumption here this is a single genome hub
30999
30999
  this.trackHubMap.set(genomeId, new TrackDbHub(trackStanzas, groupStanzas));
31000
+
31001
+ // Search for cytoband track. This supports a special but important case -- Genark assembly hubs
31002
+ this.cytobandStanza = this.trackStanzas.find(t => t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) || null;
31000
31003
  }
31001
31004
  }
31002
31005
 
@@ -31098,6 +31101,10 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
31098
31101
  config.twoBitBptURL = genomeStanza.getProperty("twoBitBptUrl");
31099
31102
  }
31100
31103
 
31104
+ if(this.cytobandStanza){
31105
+ config.cytobandBbURL = this.cytobandStanza.getProperty("bigDataUrl");
31106
+ }
31107
+
31101
31108
  if (this.hubStanza.hasProperty("longLabel")) {
31102
31109
  config.description = this.hubStanza.getProperty("longLabel").replace("/", "\n");
31103
31110
  } else {
@@ -31491,8 +31498,8 @@ function getHost(url) {
31491
31498
  return host
31492
31499
  }
31493
31500
 
31494
- const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
31495
- const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-genomes/refs/heads/main/dist/genomes.json";
31501
+ const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes3.json";
31502
+ const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-data/refs/heads/main/genomes/web/genomes.json";
31496
31503
 
31497
31504
  const GenomeUtils = {
31498
31505
 
@@ -31517,7 +31524,7 @@ const GenomeUtils = {
31517
31524
  } catch (error) {
31518
31525
  try {
31519
31526
  console.error("Error initializing default genomes:", error);
31520
- const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 2000});
31527
+ const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 10000});
31521
31528
  processJson(jsonArray, table);
31522
31529
  } catch (e) {
31523
31530
  console.error("Error initializing backup genomes:", error);
@@ -31547,7 +31554,7 @@ const GenomeUtils = {
31547
31554
  expandReference: async function (alert, idOrConfig) {
31548
31555
 
31549
31556
  // idOrConfig might be a json string? I'm actually not sure how this arises.
31550
- if (isString$2(idOrConfig) && idOrConfig.startsWith("{")) {
31557
+ if (isString$3(idOrConfig) && idOrConfig.startsWith("{")) {
31551
31558
  try {
31552
31559
  idOrConfig = JSON.parse(idOrConfig);
31553
31560
  } catch (e) {
@@ -31556,7 +31563,7 @@ const GenomeUtils = {
31556
31563
  }
31557
31564
 
31558
31565
  let genomeID;
31559
- if (isString$2(idOrConfig)) {
31566
+ if (isString$3(idOrConfig)) {
31560
31567
  genomeID = idOrConfig;
31561
31568
  } else if (idOrConfig.genome) {
31562
31569
  genomeID = idOrConfig.genome;
@@ -32502,7 +32509,7 @@ class TrackViewport extends Viewport {
32502
32509
  let track = this.trackView.track;
32503
32510
  const dataList = track.popupData(clickState);
32504
32511
 
32505
- const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList]);
32512
+ const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList, clickState.genomicLocation]);
32506
32513
 
32507
32514
  let content;
32508
32515
  if (undefined === popupClickHandlerResult || true === popupClickHandlerResult) {
@@ -40992,6 +40999,9 @@ function doSortByAttributes(sampleInfo, sampleKeys) {
40992
40999
  return true
40993
41000
  }
40994
41001
 
41002
+ /**
41003
+ * Track for segmented copy number, mut, maf and shoebox files.
41004
+ */
40995
41005
  class SegTrack extends TrackBase {
40996
41006
 
40997
41007
  #sortDirections = new Map()
@@ -41139,7 +41149,10 @@ class SegTrack extends TrackBase {
41139
41149
  }, e);
41140
41150
  }
41141
41151
 
41142
- menuItems.push({ element: createElementWithString('<div>Set color scale threshold</div>'), dialog: dialogPresentationHandler});
41152
+ menuItems.push({
41153
+ element: createElementWithString('<div>Set color scale threshold</div>'),
41154
+ dialog: dialogPresentationHandler
41155
+ });
41143
41156
  }
41144
41157
 
41145
41158
  menuItems.push('<hr/>');
@@ -41170,12 +41183,75 @@ class SegTrack extends TrackBase {
41170
41183
 
41171
41184
  getSamples() {
41172
41185
  return {
41173
- names: this.sampleKeys,
41186
+ names: this.filteredSampleKeys,
41174
41187
  height: this.sampleHeight,
41175
41188
  yOffset: 0
41176
41189
  }
41177
41190
  }
41178
41191
 
41192
+ /**
41193
+ * Set the sample filter object. This is used to filter samples from the set based on values over a specified
41194
+ * genomic region. The values compared depend on the track data type:
41195
+ * - "seg" and "shoebox" -- average value over the region
41196
+ * - "mut" and "maf" -- count of features overlapping the region
41197
+ *
41198
+ * The method is asynchronous because it may need to fetch data from the server to compute the scores.
41199
+ * Computed scores are stored and used to filter the sample keys on demand.
41200
+ *
41201
+ * @param filterObject
41202
+ * @returns {Promise<void>}
41203
+ */
41204
+ async setSampleFilter(filterObject) {
41205
+ if (!filterObject) {
41206
+ this.config.filterObject = undefined;
41207
+ this.filterObject = undefined;
41208
+ this.trackView.repaintViews();
41209
+ } else {
41210
+ const filterObjectCopy = Object.assign({}, filterObject);
41211
+ this.config.filterObject = filterObjectCopy;
41212
+
41213
+ filterObject.scores = await this.computeRegionScores(filterObject);
41214
+ this.filterObject = filterObject;
41215
+ this.trackView.checkContentHeight();
41216
+ this.trackView.repaintViews();
41217
+ }
41218
+ // TODO - store filter object in session
41219
+ }
41220
+
41221
+
41222
+ /**
41223
+ * Filter function for sample keys.
41224
+ *
41225
+ * @param sampleKey
41226
+ * @returns {boolean}
41227
+ */
41228
+ filter(sampleKey) {
41229
+ if (this.filterObject) {
41230
+ const filterObject = this.filterObject;
41231
+ const scores = filterObject.scores;
41232
+ const score = scores[sampleKey];
41233
+
41234
+ if (this.type === 'seg') {
41235
+ if (filterObject.op === '>') {
41236
+ return score > filterObject.value
41237
+ } else if (filterObject.op === '<') {
41238
+ return score < filterObject.value
41239
+ }
41240
+ } else if (this.type === 'mut' || this.type === 'maf') {
41241
+ return 'HAS' === filterObject.op ? score : !score
41242
+ }
41243
+ }
41244
+ // else if (this.config.sampleFilter) {
41245
+ // return this.config.sampleFilter(sampleKey)
41246
+ // }
41247
+ return true
41248
+ }
41249
+
41250
+ get filteredSampleKeys() {
41251
+ return this.sampleKeys.filter(sampleKey => this.filter(sampleKey))
41252
+ }
41253
+
41254
+
41179
41255
  async getFeatures(chr, start, end) {
41180
41256
  const features = await this.featureSource.getFeatures({chr, start, end});
41181
41257
  // New segments could conceivably add new samples
@@ -41183,8 +41259,10 @@ class SegTrack extends TrackBase {
41183
41259
 
41184
41260
  if (this.initialSort) {
41185
41261
  const sort = this.initialSort;
41262
+
41186
41263
  if (sort.option === undefined || sort.option.toUpperCase() === "VALUE") {
41187
- this.sortByValue(sort, features);
41264
+ const sortFeatures = (sort.chr === chr && sort.start >= start && sort.end <= end) ? features : undefined;
41265
+ this.sortByValue(sort, sortFeatures);
41188
41266
  } else if ("ATTRIBUTE" === sort.option.toUpperCase() && sort.attribute) {
41189
41267
  const sortDirection = "DESC" === sort.direction ? 1 : -1;
41190
41268
  this.sortByAttribute(sort.attribute, sortDirection);
@@ -41199,6 +41277,7 @@ class SegTrack extends TrackBase {
41199
41277
 
41200
41278
  IGVGraphics.fillRect(context, 0, pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
41201
41279
 
41280
+
41202
41281
  if (features && features.length > 0) {
41203
41282
 
41204
41283
  this.checkForLog(features);
@@ -41210,14 +41289,15 @@ class SegTrack extends TrackBase {
41210
41289
 
41211
41290
  // Create a map for fast id -> row lookup
41212
41291
  const samples = {};
41213
- this.sampleKeys.forEach(function (id, index) {
41292
+ const filteredKeys = this.filteredSampleKeys;
41293
+ filteredKeys.forEach(function (id, index) {
41214
41294
  samples[id] = index;
41215
41295
  });
41216
41296
 
41217
41297
  let border;
41218
41298
  switch (this.displayMode) {
41219
41299
  case "FILL":
41220
- this.sampleHeight = pixelHeight / this.sampleKeys.length;
41300
+ this.sampleHeight = pixelHeight / filteredKeys.length;
41221
41301
  border = 0;
41222
41302
  break
41223
41303
 
@@ -41355,7 +41435,7 @@ class SegTrack extends TrackBase {
41355
41435
  if (!features) return 0
41356
41436
  const sampleHeight = ("SQUISHED" === this.displayMode) ? this.squishedRowHeight : this.expandedRowHeight;
41357
41437
  this.updateSampleKeys(features);
41358
- return this.sampleKeys.length * sampleHeight
41438
+ return this.filteredSampleKeys.length * sampleHeight
41359
41439
  }
41360
41440
 
41361
41441
  /**
@@ -41364,16 +41444,38 @@ class SegTrack extends TrackBase {
41364
41444
  async sortByValue(sort, featureList) {
41365
41445
 
41366
41446
  const chr = sort.chr;
41447
+ const start = sort.position !== undefined ? sort.position - 1 : sort.start;
41448
+ const end = sort.end === undefined ? start + 1 : sort.end;
41449
+ const scores = await this.computeRegionScores({chr, start, end}, featureList);
41450
+ const d2 = (sort.direction === "ASC" ? 1 : -1);
41451
+
41452
+ this.sampleKeys.sort(function (a, b) {
41453
+ let s1 = scores[a];
41454
+ let s2 = scores[b];
41455
+ if (!s1) s1 = d2 * Number.MAX_VALUE;
41456
+ if (!s2) s2 = d2 * Number.MAX_VALUE;
41457
+ if (s1 === s2) return 0
41458
+ else if (s1 > s2) return d2
41459
+ else return d2 * -1
41460
+ });
41461
+
41462
+ this.config.sort = sort;
41463
+ this.trackView.repaintViews();
41464
+ }
41465
+
41466
+
41467
+ async computeRegionScores(filterObject, featureList) {
41468
+
41469
+ const chr = filterObject.chr;
41367
41470
  let start, end;
41368
- if (sort.position) {
41369
- start = sort.position - 1;
41471
+ if (filterObject.position) {
41472
+ start = filterObject.position - 1;
41370
41473
  end = start + 1;
41371
41474
  } else {
41372
- start = sort.start;
41373
- end = sort.end;
41475
+ start = filterObject.start;
41476
+ end = filterObject.end;
41374
41477
  }
41375
41478
 
41376
-
41377
41479
  if (!featureList) {
41378
41480
  featureList = await this.featureSource.getFeatures({chr, start, end});
41379
41481
  }
@@ -41382,62 +41484,40 @@ class SegTrack extends TrackBase {
41382
41484
  this.updateSampleKeys(featureList);
41383
41485
 
41384
41486
  const scores = {};
41385
- const d2 = (sort.direction === "ASC" ? 1 : -1);
41487
+ const bpLength = end - start + 1;
41488
+
41489
+ const mutationTypes = filterObject.value ? new Set(filterObject.value) : undefined;
41490
+
41491
+ for (let segment of featureList) {
41492
+ if (segment.end < start) continue
41493
+ if (segment.start > end) break
41494
+ const sampleKey = segment.sampleKey || segment.sample;
41495
+
41496
+ if ("mut" === this.type) {
41497
+ if (mutationTypes) {
41498
+ const mutationType = segment.getAttribute("Variant_Classification");
41499
+ if (mutationTypes.has(mutationType)) {
41500
+ // Just count features overlapping region per sample
41501
+ scores[sampleKey] = (scores[sampleKey] || 0) + 1;
41502
+ }
41503
+ } else {
41504
+ // Just count features overlapping region per sample
41505
+ scores[sampleKey] = (scores[sampleKey] || 0) + 1;
41506
+ }
41507
+ } else {
41386
41508
 
41387
- const sortSeg = () => {
41388
- // Compute weighted average score for each sample
41389
- const bpLength = end - start + 1;
41390
- for (let segment of featureList) {
41391
- if (segment.end < start) continue
41392
- if (segment.start > end) break
41393
41509
  const min = Math.max(start, segment.start);
41394
41510
  const max = Math.min(end, segment.end);
41395
41511
  const f = (max - min) / bpLength;
41396
- const sampleKey = segment.sampleKey || segment.sample;
41397
- const s = scores[sampleKey] || 0;
41398
- scores[sampleKey] = s + f * segment.value;
41399
- }
41400
-
41401
- // Now sort sample names by score
41402
- this.sampleKeys.sort(function (a, b) {
41403
- let s1 = scores[a];
41404
- let s2 = scores[b];
41405
- if (!s1) s1 = d2 * Number.MAX_VALUE;
41406
- if (!s2) s2 = d2 * Number.MAX_VALUE;
41407
- if (s1 === s2) return 0
41408
- else if (s1 > s2) return d2
41409
- else return d2 * -1
41410
- });
41411
- };
41412
-
41413
- const sortMut = () => {
41414
- // Compute weighted average score for each sample
41415
- for (let segment of featureList) {
41416
- if (segment.end < start) continue
41417
- if (segment.start > end) break
41418
- const sampleKey = segment.sampleKey || segment.sample;
41419
- if (!scores.hasOwnProperty(sampleKey) || segment.value.localeCompare(scores[sampleKey]) > 0) {
41420
- scores[sampleKey] = segment.value;
41421
- }
41422
- }
41423
- // Now sort sample names by score
41424
- this.sampleKeys.sort(function (a, b) {
41425
- let sa = scores[a] || "";
41426
- let sb = scores[b] || "";
41427
- return d2 * (sa.localeCompare(sb))
41428
- });
41429
- };
41430
-
41431
- if ("mut" === this.type) {
41432
- sortMut();
41433
- } else {
41434
- sortSeg();
41512
+ scores[sampleKey] = (scores[sampleKey] || 0) + f * segment.value;
41513
+ }
41435
41514
  }
41436
41515
 
41437
- this.trackView.repaintViews();
41516
+ return scores
41438
41517
 
41439
41518
  }
41440
41519
 
41520
+
41441
41521
  sortByAttribute(attribute, sortDirection) {
41442
41522
 
41443
41523
  this.sampleKeys = this.browser.sampleInfo.getSortedSampleKeysByAttribute(this.sampleKeys, attribute, sortDirection);
@@ -41506,7 +41586,7 @@ class SegTrack extends TrackBase {
41506
41586
  const dirLabel = direction === "DESC" ? "descending" : "ascending";
41507
41587
  const sortLabel = this.type === 'seg' || this.type === 'shoebox' ?
41508
41588
  `Sort by value (${dirLabel})` :
41509
- `Sort by type (${dirLabel})`;
41589
+ `Sort by count (${dirLabel})`;
41510
41590
  return {
41511
41591
  label: sortLabel,
41512
41592
  click: () => {
@@ -41518,7 +41598,6 @@ class SegTrack extends TrackBase {
41518
41598
  end: Math.floor(genomicLocation + bpWidth)
41519
41599
  };
41520
41600
  sortHandler(sort);
41521
- this.config.sort = sort;
41522
41601
  }
41523
41602
  }
41524
41603
  })
@@ -41688,13 +41767,13 @@ class PairedAlignment {
41688
41767
  }
41689
41768
  }
41690
41769
 
41691
- popupData(genomicLocation) {
41770
+ popupData(genomicLocation, hiddenTags, showTags) {
41692
41771
 
41693
- let nameValues = this.firstAlignment.popupData(genomicLocation);
41772
+ let nameValues = this.firstAlignment.popupData(genomicLocation, hiddenTags, showTags);
41694
41773
 
41695
41774
  if (this.secondAlignment) {
41696
41775
  nameValues.push("-------------------------------");
41697
- nameValues = nameValues.concat(this.secondAlignment.popupData(genomicLocation));
41776
+ nameValues = nameValues.concat(this.secondAlignment.popupData(genomicLocation, hiddenTags, showTags));
41698
41777
  }
41699
41778
  return nameValues
41700
41779
  }
@@ -43408,7 +43487,7 @@ class BamAlignment {
43408
43487
  isNegativeStrand() {
43409
43488
  return (this.flags & READ_STRAND_FLAG$2) !== 0
43410
43489
  }
43411
-
43490
+
43412
43491
  isMateNegativeStrand() {
43413
43492
  return (this.flags & MATE_STRAND_FLAG$2) !== 0
43414
43493
  }
@@ -43456,7 +43535,7 @@ class BamAlignment {
43456
43535
  return (genomicLocation >= s && genomicLocation <= (s + l))
43457
43536
  }
43458
43537
 
43459
- popupData(genomicLocation) {
43538
+ popupData(genomicLocation, hiddenTags, showTags) {
43460
43539
 
43461
43540
  // if the user clicks on a base next to an insertion, show just the
43462
43541
  // inserted bases in a popup (like in desktop IGV).
@@ -43539,15 +43618,20 @@ class BamAlignment {
43539
43618
  }
43540
43619
  }
43541
43620
 
43542
- const hiddenTags = new Set(['SA', 'MD']);
43543
43621
  nameValues.push('<hr/>');
43544
43622
  for (let key in tagDict) {
43545
- if (!hiddenTags.has(key)) {
43623
+ if (showTags?.has(key)) {
43624
+ nameValues.push({name: key, value: tagDict[key]});
43625
+ } else if (showTags) {
43626
+ hiddenTags.add(key);
43627
+ } else if (!hiddenTags.has(key)) {
43546
43628
  nameValues.push({name: key, value: tagDict[key]});
43547
43629
  }
43548
43630
  }
43549
43631
 
43550
- nameValues.push({name: 'Hidden Tags', value: 'SA, MD'});
43632
+ if (hiddenTags && hiddenTags.size > 0) {
43633
+ nameValues.push({name: 'Hidden Tags', value: Array.from(hiddenTags).join(", ")});
43634
+ }
43551
43635
 
43552
43636
  nameValues.push('<hr/>');
43553
43637
  nameValues.push({name: 'Genomic Location: ', value: numberFormatter$1(1 + genomicLocation)});
@@ -43555,18 +43639,18 @@ class BamAlignment {
43555
43639
  nameValues.push({name: 'Base Quality:', value: this.readBaseQualityAt(genomicLocation)});
43556
43640
 
43557
43641
  const bmSets = this.getBaseModificationSets();
43558
- if(bmSets) {
43642
+ if (bmSets) {
43559
43643
  const i = this.positionToReadIndex(genomicLocation);
43560
- if(undefined !== i) {
43644
+ if (undefined !== i) {
43561
43645
  let found = false;
43562
43646
  for (let bmSet of bmSets) {
43563
43647
  if (bmSet.containsPosition(i)) {
43564
- if(!found) {
43648
+ if (!found) {
43565
43649
  nameValues.push('<hr/>');
43566
43650
  nameValues.push('<b>Base modifications:</b>');
43567
43651
  found = true;
43568
43652
  }
43569
- const lh = Math.round((100/255) * byteToUnsignedInt(bmSet.likelihoods.get(i)));
43653
+ const lh = Math.round((100 / 255) * byteToUnsignedInt(bmSet.likelihoods.get(i)));
43570
43654
  nameValues.push(`${bmSet.fullName()} @ likelihood = ${lh}%`);
43571
43655
  }
43572
43656
  }
@@ -43656,7 +43740,7 @@ class BamAlignment {
43656
43740
  const mm = this.tagDict["MM"] || this.tagDict["Mm"];
43657
43741
  const ml = this.tagDict["ML"] || this.tagDict["Ml"];
43658
43742
 
43659
- if (isString$2(mm) && (!ml || Array.isArray(ml))) { // minimal validation, 10X uses these reserved tags for something completely different
43743
+ if (isString$3(mm) && (!ml || Array.isArray(ml))) { // minimal validation, 10X uses these reserved tags for something completely different
43660
43744
  if (mm.length === 0) {
43661
43745
  this.baseModificationSets = EMPTY_SET;
43662
43746
  } else {
@@ -43669,7 +43753,7 @@ class BamAlignment {
43669
43753
  return this.baseModificationSets
43670
43754
  }
43671
43755
 
43672
- getGroupValue( groupBy, tag, expectedPairOrientation) {
43756
+ getGroupValue(groupBy, tag, expectedPairOrientation) {
43673
43757
 
43674
43758
  const al = this;
43675
43759
  switch (groupBy) {
@@ -43706,7 +43790,7 @@ class BamAlignment {
43706
43790
  }
43707
43791
  }
43708
43792
 
43709
- positionToReadIndex( position) {
43793
+ positionToReadIndex(position) {
43710
43794
  const block = blockAtGenomicLocation(this.blocks, position);
43711
43795
  if (block) {
43712
43796
  return (position - block.start) + block.seqOffset
@@ -45685,7 +45769,7 @@ function inferFileFormatFromName(fn) {
45685
45769
 
45686
45770
  function inferIndexPath(url, extension) {
45687
45771
 
45688
- if (isString$2(url)) {
45772
+ if (isString$3(url)) {
45689
45773
  if (url.includes("?")) {
45690
45774
  const idx = url.indexOf("?");
45691
45775
  return url.substring(0, idx) + "." + extension + url.substring(idx)
@@ -45844,7 +45928,7 @@ class BamSource {
45844
45928
  this.bamReader = new CramReader(config, genome, browser);
45845
45929
  } else {
45846
45930
  if (!this.config.indexURL && config.indexed !== false) {
45847
- if (isString$2(this.config.url)) {
45931
+ if (isString$3(this.config.url)) {
45848
45932
  const indexPath = inferIndexPath(this.config.url, "bai");
45849
45933
  if (indexPath) {
45850
45934
  console.warn(`Warning: no indexURL specified for ${this.config.url}. Guessing ${indexPath}`);
@@ -48156,7 +48240,7 @@ class AlignmentTrack extends TrackBase {
48156
48240
  highlightColor: undefined,
48157
48241
  minTLEN: undefined,
48158
48242
  maxTLEN: undefined,
48159
- tagColorPallete: "Set1"
48243
+ tagColorPallete: "Set1",
48160
48244
  }
48161
48245
 
48162
48246
  _colorTables = new Map()
@@ -48171,6 +48255,18 @@ class AlignmentTrack extends TrackBase {
48171
48255
  this.colorTable = new ColorTable(config.tagColorTable);
48172
48256
  }
48173
48257
 
48258
+ // Only one of showTags / hideTags should be specified. If both are specified showTags takes precedence.
48259
+ if (config.showTags && config.hideTags) {
48260
+ console.warn("Both showTags and hideTags specified. showTags will be used.");
48261
+ }
48262
+ if (config.showTags) {
48263
+ this.showTags = new Set(config.showTags);
48264
+ this.hiddenTags = new Set();
48265
+ } else {
48266
+ this.hiddenTags = new Set(config.hideTags || ["SA", "MD"]);
48267
+ }
48268
+
48269
+
48174
48270
  // Backward compatibility overrides
48175
48271
  if (config.largeFragmentLengthColor) this.largeTLENColor = config.largeFragmentLengthColor;
48176
48272
  if (config.pairOrienation) this.expectedPairOrientation = config.pairOrientation;
@@ -48731,7 +48827,7 @@ class AlignmentTrack extends TrackBase {
48731
48827
 
48732
48828
  popupData(clickState) {
48733
48829
  const clickedObject = this.getClickedObject(clickState);
48734
- return clickedObject ? clickedObject.popupData(clickState.genomicLocation) : undefined
48830
+ return clickedObject?.popupData(clickState.genomicLocation, this.hiddenTags, this.showTags)
48735
48831
  };
48736
48832
 
48737
48833
  /**
@@ -48762,7 +48858,10 @@ class AlignmentTrack extends TrackBase {
48762
48858
  colorByMenuItems.push({key: 'tlen', label: 'insert size (TLEN)'});
48763
48859
  colorByMenuItems.push({key: 'unexpectedPair', label: 'pair orientation & insert size (TLEN)'});
48764
48860
  }
48765
- colorByMenuItems.push({key: 'tag', label: 'tag'});
48861
+ if(this.colorBy && this.colorBy.startsWith("tag:")) {
48862
+ colorByMenuItems.push({key: this.colorBy, label: this.colorBy});
48863
+ }
48864
+ colorByMenuItems.push({key: 'tag', label: 'tag...'});
48766
48865
  for (let item of colorByMenuItems) {
48767
48866
  const selected = (this.colorBy === undefined && item.key === 'none') || this.colorBy === item.key;
48768
48867
  menuItems.push(this.colorByCB(item, selected));
@@ -49488,14 +49587,19 @@ class AlignmentTrack extends TrackBase {
49488
49587
  case "tag":
49489
49588
  const tagValue = alignment.tags()[tag];
49490
49589
  if (tagValue !== undefined) {
49491
- if (this.bamColorTag === tag) {
49590
+
49591
+ // If the tag value can be interpreted as a color, use it
49592
+ if(typeof tagValue.startsWith === 'function') {
49492
49593
  color = IGVColor.createColorStringSafe(tagValue);
49493
49594
  }
49494
- if (!this.colorTable) {
49495
- this.colorTable = new PaletteColorTable(this.tagColorPallete);
49496
- }
49497
- color = this.colorTable.getColor(tagValue);
49498
49595
 
49596
+ // Tag value is not a color, use a color table
49597
+ if (!color) {
49598
+ if (!this.colorTable) {
49599
+ this.colorTable = new PaletteColorTable(this.tagColorPallete);
49600
+ }
49601
+ color = this.colorTable.getColor(tagValue);
49602
+ }
49499
49603
  }
49500
49604
  break
49501
49605
  }
@@ -65712,7 +65816,7 @@ function autoscale(chr, featureArrays) {
65712
65816
  * THE SOFTWARE.
65713
65817
  */
65714
65818
 
65715
- const isString = isString$2;
65819
+ const isString$1 = isString$3;
65716
65820
 
65717
65821
  const DEFAULT_VISIBILITY_WINDOW = 1000000;
65718
65822
  const TOP_MARGIN = 10;
@@ -65835,7 +65939,7 @@ class VariantTrack extends TrackBase {
65835
65939
  }
65836
65940
  if (undefined === this.visibilityWindow && this.config.indexed !== false) {
65837
65941
  const fn = isFile(this.config.url) ? this.config.url.name : this.config.url;
65838
- if (isString(fn) && fn.toLowerCase().includes("gnomad")) {
65942
+ if (isString$1(fn) && fn.toLowerCase().includes("gnomad")) {
65839
65943
  this.visibilityWindow = 1000; // these are known to be very dense
65840
65944
  } else if (typeof this.featureSource.defaultVisibilityWindow === 'function') {
65841
65945
  this.visibilityWindow = await this.featureSource.defaultVisibilityWindow();
@@ -69719,6 +69823,9 @@ const trackFunctions =
69719
69823
  ['image', (config, browser) => new ImageTrack(config, browser)]
69720
69824
  ]);
69721
69825
 
69826
+ function knownTrackTypes () {
69827
+ return new Set(trackFunctions.keys())
69828
+ }
69722
69829
 
69723
69830
  /**
69724
69831
  * Return a track of the given type, passing configuration and a point to the IGV "Browser" object to its constructor function*
@@ -70211,7 +70318,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
70211
70318
  })
70212
70319
  }
70213
70320
 
70214
- const _version = "3.3.0";
70321
+ const _version = "3.4.0";
70215
70322
  function version() {
70216
70323
  return _version
70217
70324
  }
@@ -70262,7 +70369,7 @@ class ChromosomeSelectWidget {
70262
70369
  if (this.select.value.trim().toLowerCase() === "all" || this.select.value === "*") {
70263
70370
  if (browser.genome.wholeGenomeView) {
70264
70371
  const wgChr = browser.genome.getChromosome("all");
70265
- return {chr: "all", start: 0, end: wgChr.bpLength}
70372
+ browser.updateLoci([{chr: "all", start: 0, end: wgChr.bpLength}]);
70266
70373
  }
70267
70374
  } else {
70268
70375
  const chromosome = await browser.genome.loadChromosome(this.select.value);
@@ -72067,6 +72174,26 @@ class ROIMenu {
72067
72174
  }
72068
72175
 
72069
72176
 
72177
+ const found = this.browser.findTracks(track => typeof track.sortByValue === 'function');
72178
+ if (found.length > 0) {
72179
+ const { chr, start, end } = feature;
72180
+ items.push(
72181
+ '<hr/>',
72182
+ {
72183
+ label: 'Sort by value (ascending)',
72184
+ click: () => Promise.all(found.map(track => track.sortByValue({ option: 'VALUE', direction: 'ASC', chr, start, end })))
72185
+ });
72186
+
72187
+ items.push(
72188
+ '<hr/>',
72189
+ {
72190
+ label: 'Sort by value (descending)',
72191
+ click: () => Promise.all(found.map(track => track.sortByValue({ option: 'VALUE', direction: 'DESC', chr, start, end })))
72192
+ });
72193
+
72194
+ }
72195
+
72196
+
72070
72197
  if (roiSet.isUserDefined) {
72071
72198
  items.push(
72072
72199
  '<hr/>',
@@ -72075,7 +72202,7 @@ class ROIMenu {
72075
72202
  click: async () => {
72076
72203
  roiSet.removeFeature(feature);
72077
72204
  const userDefinedFeatures = await roiSet.getAllFeatures();
72078
-
72205
+
72079
72206
  // Delete user defined ROI Set if it is empty
72080
72207
  if (Object.keys(userDefinedFeatures).length === 0) {
72081
72208
  roiManager.deleteUserDefinedROISet();
@@ -73248,6 +73375,189 @@ class CytobandFile {
73248
73375
 
73249
73376
  }
73250
73377
 
73378
+ /**
73379
+ * Update deprecated fasta & index urls in the reference object.
73380
+ */
73381
+
73382
+ const isString = (x) => {
73383
+ return (x && typeof x === "string") || x instanceof String
73384
+ };
73385
+
73386
+ /**
73387
+ * Replaces deprecated s3 fasta URLs with twoBitURLs. The purpose here is to rescue references from saved sessions
73388
+ * that contain pointers to deprectated IGV s3 buckets. These buckets will eventually be deleted
73389
+ *
73390
+ * @param reference
73391
+ */
73392
+ function updateReference(reference) {
73393
+
73394
+ if (!(requiresUpdate(reference) && updates[reference.id])) {
73395
+ return
73396
+ }
73397
+
73398
+ const updatedReference = updates[reference.id];
73399
+ if (updatedReference) {
73400
+ delete reference.fastaURL;
73401
+ if (reference.indexURL) delete reference.indexURL;
73402
+ reference.twoBitURL = updatedReference.twoBitURL;
73403
+ if (updatedReference.twoBitBptURL) reference.twoBitBptURL = updatedReference.twoBitBptURL;
73404
+ if (updatedReference.chromSizesURL) reference.chromSizesURL = updatedReference.chromSizesURL;
73405
+ }
73406
+ }
73407
+
73408
+ function requiresUpdate(reference) {
73409
+ return isString(reference.fastaURL) &&
73410
+ (reference.fastaURL.startsWith("https://igv.org") ||
73411
+ ["igv.org.genomes", "igv.broadinstitute.org", "igv.genepattern.org", "igvdata.broadinstitute.org",
73412
+ "igv-genepattern-org"].some(bucket => reference.fastaURL.includes(bucket)))
73413
+ }
73414
+
73415
+ const updates = {
73416
+ "hs1": {
73417
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.2bit",
73418
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.2bit.bpt",
73419
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.chrom.sizes.txt"
73420
+ },
73421
+ "hg38": {
73422
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.2bit",
73423
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.chrom.sizes"
73424
+ },
73425
+ "hg19": {
73426
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.2bit",
73427
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.chrom.sizes"
73428
+ },
73429
+ "hg18": {
73430
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg18/bigZips/hg18.2bit",
73431
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg18/bigZips/hg18.chrom.sizes"
73432
+ },
73433
+ "mm39": {
73434
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm39/bigZips/mm39.2bit",
73435
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm39/bigZips/mm39.chrom.sizes"
73436
+ },
73437
+ "mm10": {
73438
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.2bit",
73439
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.chrom.sizes"
73440
+ },
73441
+ "mm9": {
73442
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm9/bigZips/mm9.2bit",
73443
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm9/bigZips/mm9.chrom.sizes"
73444
+ },
73445
+ "rn7": {
73446
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.2bit",
73447
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.2bit.bpt",
73448
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.chrom.sizes.txt"
73449
+ },
73450
+ "rn6": {
73451
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.2bit",
73452
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.2bit.bpt",
73453
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.chrom.sizes.txt"
73454
+ },
73455
+ "gorGor6": {
73456
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor6/bigZips/gorGor6.2bit",
73457
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor6/bigZips/gorGor6.chrom.sizes"
73458
+ },
73459
+ "gorGor4": {
73460
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor4/bigZips/gorGor4.2bit",
73461
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor4/bigZips/gorGor4.chrom.sizes"
73462
+ },
73463
+ "panTro6": {
73464
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro6/bigZips/panTro6.2bit",
73465
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro6/bigZips/panTro6.chrom.sizes"
73466
+ },
73467
+ "panTro5": {
73468
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro5/bigZips/panTro5.2bit",
73469
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro5/bigZips/panTro5.chrom.sizes"
73470
+ },
73471
+ "panTro4": {
73472
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro4/bigZips/panTro4.2bit",
73473
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro4/bigZips/panTro4.chrom.sizes"
73474
+ },
73475
+ "macFas5": {
73476
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/macFas5/bigZips/macFas5.2bit",
73477
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/macFas5/bigZips/macFas5.chrom.sizes"
73478
+ },
73479
+ "panPan2": {
73480
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panPan2/bigZips/panPan2.2bit",
73481
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panPan2/bigZips/panPan2.chrom.sizes"
73482
+ },
73483
+ "canFam6": {
73484
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam6/bigZips/canFam6.2bit",
73485
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam6/bigZips/canFam6.chrom.sizes"
73486
+ },
73487
+ "canFam5": {
73488
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam5/bigZips/canFam5.2bit",
73489
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam5/bigZips/canFam5.chrom.sizes"
73490
+ },
73491
+ "canFam4": {
73492
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam4/bigZips/canFam4.2bit",
73493
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam4/bigZips/canFam4.chrom.sizes"
73494
+ },
73495
+ "canFam3": {
73496
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam3/bigZips/canFam3.2bit",
73497
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam3/bigZips/canFam3.chrom.sizes"
73498
+ },
73499
+ "bosTau9": {
73500
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau9/bigZips/bosTau9.2bit",
73501
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau9/bigZips/bosTau9.chrom.sizes"
73502
+ },
73503
+ "bosTau8": {
73504
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau8/bigZips/bosTau8.2bit",
73505
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau8/bigZips/bosTau8.chrom.sizes"
73506
+ },
73507
+ "susScr11": {
73508
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/susScr11/bigZips/susScr11.2bit",
73509
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/susScr11/bigZips/susScr11.chrom.sizes"
73510
+ },
73511
+ "galGal6": {
73512
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/galGal6/bigZips/galGal6.2bit",
73513
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/galGal6/bigZips/galGal6.chrom.sizes"
73514
+ },
73515
+ "danRer11": {
73516
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer11/bigZips/danRer11.2bit",
73517
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer11/bigZips/danRer11.chrom.sizes"
73518
+ },
73519
+ "danRer10": {
73520
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer10/bigZips/danRer10.2bit",
73521
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer10/bigZips/danRer10.chrom.sizes"
73522
+ },
73523
+ "ce11": {
73524
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/ce11/bigZips/ce11.2bit",
73525
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/ce11/bigZips/ce11.chrom.sizes"
73526
+ },
73527
+ "dm6": {
73528
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm6/bigZips/dm6.2bit",
73529
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm6/bigZips/dm6.chrom.sizes"
73530
+ },
73531
+ "dm3": {
73532
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm3/bigZips/dm3.2bit",
73533
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm3/bigZips/dm3.chrom.sizes"
73534
+ },
73535
+ "sacCer3": {
73536
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.2bit",
73537
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.2bit.bpt",
73538
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.chrom.sizes.txt"
73539
+ },
73540
+ "GCF_000002945.1": {
73541
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.2bit",
73542
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.2bit.bpt",
73543
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.chrom.sizes.txt"
73544
+ },
73545
+ "GCF_009858895.2": {
73546
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.2bit",
73547
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.2bit.bpt",
73548
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.chrom.sizes.txt"
73549
+ },
73550
+ "tair10": {
73551
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/735/GCF_000001735.3/GCF_000001735.3.2bit",
73552
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/735/GCF_000001735.3/GCF_000001735.3.chrom.sizes.txt"
73553
+ },
73554
+ "GCA_000022165.1": {
73555
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.2bit",
73556
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.2bit.bpt",
73557
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.chrom.sizes.txt"
73558
+ }
73559
+ };
73560
+
73251
73561
  const ucsdIDMap = new Map([
73252
73562
  ["1kg_ref", "hg18"],
73253
73563
  ["1kg_v37", "hg19"],
@@ -73269,6 +73579,7 @@ class Genome {
73269
73579
 
73270
73580
  static async createGenome(options, browser) {
73271
73581
 
73582
+ updateReference(options);
73272
73583
  const genome = new Genome(options, browser);
73273
73584
  await genome.init();
73274
73585
  return genome
@@ -73292,7 +73603,6 @@ class Genome {
73292
73603
  // Load sequence
73293
73604
  this.sequence = await loadSequence(config, this.browser);
73294
73605
 
73295
-
73296
73606
  // Load cytobands. This is optional but required to support the ideogram. Only needed for whole genome view
73297
73607
  if(false !== config.showIdeogram && false !== config.wholeGenomeView) {
73298
73608
  if (config.cytobandURL) {
@@ -73336,10 +73646,14 @@ class Genome {
73336
73646
  } else {
73337
73647
  this.#wgChromosomeNames = config.chromosomeOrder.split(',').map(nm => nm.trim());
73338
73648
  }
73649
+ // Trim to remove non-existent chromosomes
73650
+ await this.chromAlias.preload(this.#wgChromosomeNames);
73651
+ this.#wgChromosomeNames =
73652
+ this.#wgChromosomeNames.map(c => this.getChromosomeName(c)).filter(c => this.chromosomes.has(c));
73339
73653
  } else {
73340
73654
  this.#wgChromosomeNames = trimSmallChromosomes(this.chromosomes);
73655
+ await this.chromAlias.preload(this.#wgChromosomeNames);
73341
73656
  }
73342
- await this.chromAlias.preload(this.#wgChromosomeNames);
73343
73657
  }
73344
73658
 
73345
73659
  // Optionally create the psuedo chromosome "all" to support whole genome view
@@ -73612,7 +73926,7 @@ function trimSmallChromosomes(chromosomes) {
73612
73926
  function generateGenomeID(config) {
73613
73927
  if (config.id !== undefined) {
73614
73928
  return config.id
73615
- } else if (config.fastaURL && isString$2(config.fastaURL) && !config.fastaURL.startsWith("data:")) {
73929
+ } else if (config.fastaURL && isString$3(config.fastaURL) && !config.fastaURL.startsWith("data:")) {
73616
73930
  return config.fastaURL
73617
73931
  } else if (config.fastaURL && config.fastaURL.name) {
73618
73932
  return config.fastaURL.name
@@ -74345,7 +74659,7 @@ class Browser {
74345
74659
  const urlOrFile = options.url || options.file;
74346
74660
 
74347
74661
  let config;
74348
- if (options.url && isString$2(options.url) && (options.url.startsWith("blob:") || options.url.startsWith("data:"))) {
74662
+ if (options.url && isString$3(options.url) && (options.url.startsWith("blob:") || options.url.startsWith("data:"))) {
74349
74663
  const json = Browser.uncompressSession(options.url);
74350
74664
  config = JSON.parse(json);
74351
74665
  } else {
@@ -74424,7 +74738,7 @@ class Browser {
74424
74738
  return
74425
74739
  }
74426
74740
 
74427
- const genomeConfig = isString$2(genomeOrReference) ?
74741
+ const genomeConfig = isString$3(genomeOrReference) ?
74428
74742
  await GenomeUtils.expandReference(this.alert, genomeOrReference) :
74429
74743
  genomeOrReference;
74430
74744
 
@@ -74546,7 +74860,7 @@ class Browser {
74546
74860
  }
74547
74861
 
74548
74862
  /**
74549
- * Load a reference genome object. This includes the fasta, and optional cytoband, but no tracks. This method
74863
+ * Load a reference genome object. This includes the sequence, and optional cytoband, but no tracks. This method
74550
74864
  * is used by loadGenome and loadSession.
74551
74865
  *
74552
74866
  * @param genomeConfig
@@ -74611,7 +74925,7 @@ class Browser {
74611
74925
 
74612
74926
  // Translate the generic "url" field, used by clients such as igv-webapp
74613
74927
  if (idOrConfig.url) {
74614
- if (isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {
74928
+ if (isString$3(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {
74615
74929
  idOrConfig.hubURL = idOrConfig.url;
74616
74930
  delete idOrConfig.url;
74617
74931
  } else if ("gbk" === getFileExtension(idOrConfig.url)) {
@@ -74621,11 +74935,11 @@ class Browser {
74621
74935
  }
74622
74936
 
74623
74937
  let genomeConfig;
74624
- const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
74938
+ const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$3(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
74625
74939
  if (isHubGenome) {
74626
74940
  const hub = await loadHub(idOrConfig.hubURL || idOrConfig.url);
74627
74941
  genomeConfig = hub.getGenomeConfig();
74628
- } else if (isString$2(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
74942
+ } else if (isString$3(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
74629
74943
  // Either an ID, a json string, or an object missing required properties.
74630
74944
  genomeConfig = await GenomeUtils.expandReference(this.alert, idOrConfig);
74631
74945
  } else {
@@ -74792,7 +75106,7 @@ class Browser {
74792
75106
  async #loadTrackHelper(config) {
74793
75107
 
74794
75108
  // config might be json
74795
- if (isString$2(config)) {
75109
+ if (isString$3(config)) {
74796
75110
  config = JSON.parse(config);
74797
75111
  }
74798
75112
 
@@ -74922,7 +75236,7 @@ class Browser {
74922
75236
 
74923
75237
  // Resolve function and promise urls
74924
75238
  let url = await resolveURL(config.url || config.fastaURL);
74925
- if (isString$2(url)) {
75239
+ if (isString$3(url)) {
74926
75240
  url = url.trim();
74927
75241
  }
74928
75242
 
@@ -74970,7 +75284,7 @@ class Browser {
74970
75284
  const featureSource = FeatureSource(config, this.genome);
74971
75285
  config._featureSource = featureSource; // This is a temp variable, bit of a hack
74972
75286
  const trackType = await featureSource.trackType();
74973
- if (trackType) {
75287
+ if (trackType && knownTrackTypes().has(trackType)) {
74974
75288
  type = trackType;
74975
75289
  } else {
74976
75290
  type = "annotation";