igv 3.3.0 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/igv.js CHANGED
@@ -379,7 +379,7 @@
379
379
  * @param x
380
380
  * @returns {boolean}
381
381
  */
382
- function isString$2(x) {
382
+ function isString$3(x) {
383
383
  return typeof x === "string" || x instanceof String
384
384
  }
385
385
 
@@ -479,7 +479,7 @@
479
479
 
480
480
  if (urlOrFile.name !== undefined) {
481
481
  return urlOrFile.name
482
- } else if (isString$2(urlOrFile)) {
482
+ } else if (isString$3(urlOrFile)) {
483
483
 
484
484
  let index = urlOrFile.lastIndexOf("/");
485
485
  let filename = index < 0 ? urlOrFile : urlOrFile.substr(index + 1);
@@ -10684,7 +10684,7 @@
10684
10684
  };
10685
10685
 
10686
10686
  async function getFilename$1(url) {
10687
- if (isString$2(url) && url.startsWith("https://drive.google.com")) {
10687
+ if (isString$3(url) && url.startsWith("https://drive.google.com")) {
10688
10688
  // This will fail if Google API key is not defined
10689
10689
  if (getApiKey() === undefined) {
10690
10690
  throw Error("Google drive is referenced, but API key is not defined. An API key is required for Google Drive access")
@@ -10726,7 +10726,7 @@
10726
10726
 
10727
10727
 
10728
10728
  function isDataURL(obj) {
10729
- return (isString$2(obj) && obj.startsWith("data:"))
10729
+ return (isString$3(obj) && obj.startsWith("data:"))
10730
10730
  }
10731
10731
 
10732
10732
  function createColumn(columnContainer, className) {
@@ -10952,7 +10952,7 @@
10952
10952
  return list;
10953
10953
  }
10954
10954
 
10955
- /*! @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 */
10955
+ /*! @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 */
10956
10956
 
10957
10957
  const {
10958
10958
  entries,
@@ -11156,7 +11156,7 @@
11156
11156
  const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
11157
11157
  const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
11158
11158
  const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
11159
- 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
11159
+ 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
11160
11160
  );
11161
11161
  const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
11162
11162
  const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
@@ -11253,7 +11253,7 @@
11253
11253
  function createDOMPurify() {
11254
11254
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
11255
11255
  const DOMPurify = root => createDOMPurify(root);
11256
- DOMPurify.version = '3.2.5';
11256
+ DOMPurify.version = '3.2.6';
11257
11257
  DOMPurify.removed = [];
11258
11258
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
11259
11259
  // Not running in a browser, provide a factory function
@@ -11492,8 +11492,8 @@
11492
11492
  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;
11493
11493
  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;
11494
11494
  FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
11495
- FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
11496
- FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
11495
+ FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
11496
+ FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
11497
11497
  USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
11498
11498
  ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
11499
11499
  ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
@@ -11858,7 +11858,7 @@
11858
11858
  allowedTags: ALLOWED_TAGS
11859
11859
  });
11860
11860
  /* Detect mXSS attempts abusing namespace confusion */
11861
- if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
11861
+ if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
11862
11862
  _forceRemove(currentNode);
11863
11863
  return true;
11864
11864
  }
@@ -12010,7 +12010,8 @@
12010
12010
  value: attrValue
12011
12011
  } = attr;
12012
12012
  const lcName = transformCaseFunc(name);
12013
- let value = name === 'value' ? attrValue : stringTrim(attrValue);
12013
+ const initValue = attrValue;
12014
+ let value = name === 'value' ? initValue : stringTrim(initValue);
12014
12015
  /* Execute a hook if present */
12015
12016
  hookEvent.attrName = lcName;
12016
12017
  hookEvent.attrValue = value;
@@ -12036,10 +12037,9 @@
12036
12037
  if (hookEvent.forceKeepAttr) {
12037
12038
  continue;
12038
12039
  }
12039
- /* Remove attribute */
12040
- _removeAttribute(name, currentNode);
12041
12040
  /* Did the hooks approve of the attribute? */
12042
12041
  if (!hookEvent.keepAttr) {
12042
+ _removeAttribute(name, currentNode);
12043
12043
  continue;
12044
12044
  }
12045
12045
  /* Work around a security issue in jQuery 3.0 */
@@ -12056,6 +12056,7 @@
12056
12056
  /* Is `value` valid for this attribute? */
12057
12057
  const lcTag = transformCaseFunc(currentNode.nodeName);
12058
12058
  if (!_isValidAttribute(lcTag, lcName, value)) {
12059
+ _removeAttribute(name, currentNode);
12059
12060
  continue;
12060
12061
  }
12061
12062
  /* Handle attributes that require Trusted Types */
@@ -12076,19 +12077,23 @@
12076
12077
  }
12077
12078
  }
12078
12079
  /* Handle invalid data-* attribute set by try-catching it */
12079
- try {
12080
- if (namespaceURI) {
12081
- currentNode.setAttributeNS(namespaceURI, name, value);
12082
- } else {
12083
- /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
12084
- currentNode.setAttribute(name, value);
12085
- }
12086
- if (_isClobbered(currentNode)) {
12087
- _forceRemove(currentNode);
12088
- } else {
12089
- arrayPop(DOMPurify.removed);
12080
+ if (value !== initValue) {
12081
+ try {
12082
+ if (namespaceURI) {
12083
+ currentNode.setAttributeNS(namespaceURI, name, value);
12084
+ } else {
12085
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
12086
+ currentNode.setAttribute(name, value);
12087
+ }
12088
+ if (_isClobbered(currentNode)) {
12089
+ _forceRemove(currentNode);
12090
+ } else {
12091
+ arrayPop(DOMPurify.removed);
12092
+ }
12093
+ } catch (_) {
12094
+ _removeAttribute(name, currentNode);
12090
12095
  }
12091
- } catch (_) {}
12096
+ }
12092
12097
  }
12093
12098
  /* Execute a hook if present */
12094
12099
  _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
@@ -16909,7 +16914,7 @@
16909
16914
  */
16910
16915
 
16911
16916
  const fixColor = (colorString) => {
16912
- if (isString$2(colorString)) {
16917
+ if (isString$3(colorString)) {
16913
16918
  return (colorString.indexOf(",") > 0 && !(colorString.startsWith("rgb(") || colorString.startsWith("rgba("))) ?
16914
16919
  `rgb(${colorString})` : colorString
16915
16920
  } else {
@@ -16981,7 +16986,7 @@
16981
16986
  this.name = config.name || config.label;
16982
16987
  } else if (isFile(config.url)) {
16983
16988
  this.name = config.url.name;
16984
- } else if (isString$2(config.url) && !config.url.startsWith("data:")) {
16989
+ } else if (isString$3(config.url) && !config.url.startsWith("data:")) {
16985
16990
  this.name = getFilename$2(config.url);
16986
16991
  }
16987
16992
 
@@ -20903,7 +20908,7 @@
20903
20908
  * THE SOFTWARE.
20904
20909
  */
20905
20910
 
20906
- const isString$1 = isString$2;
20911
+ const isString$2 = isString$3;
20907
20912
 
20908
20913
 
20909
20914
  class CustomServiceReader {
@@ -20942,7 +20947,7 @@
20942
20947
  if (data) {
20943
20948
  if (typeof this.config.parser === "function") {
20944
20949
  features = this.config.parser(data);
20945
- } else if (isString$1(data)) {
20950
+ } else if (isString$2(data)) {
20946
20951
  features = JSON.parse(data);
20947
20952
  } else {
20948
20953
  features = data;
@@ -22343,7 +22348,7 @@
22343
22348
  }
22344
22349
 
22345
22350
  function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
22346
-
22351
+ //biggenepred
22347
22352
  if ("biginteract" === format || (autoSql && ('chromatinInteract' === autoSql.table || 'interact' === autoSql.table))) {
22348
22353
  return decodeInteract
22349
22354
  } else {
@@ -22390,9 +22395,20 @@
22390
22395
  const extraStart = definedFieldCount;
22391
22396
  for (let i = extraStart; i < fieldCount; i++) {
22392
22397
  if (i < autoSql.fields.length) {
22398
+
22393
22399
  const name = autoSql.fields[i].name;
22394
- const value = tokens[i - 3];
22395
- feature[name] = value;
22400
+
22401
+ if (name === "exonFrames") {
22402
+ const frameOffsets = tokens[i - 3].replace(/,$/, '').split(',');
22403
+ for (let i = 0; i < feature.exons.length; i++) {
22404
+ const exon = feature.exons[i];
22405
+ const fo = parseInt(frameOffsets[i]);
22406
+ if (fo != -1) exon.readingFrame = fo;
22407
+ }
22408
+ } else {
22409
+ const value = tokens[i - 3];
22410
+ feature[name] = value;
22411
+ }
22396
22412
  }
22397
22413
  }
22398
22414
  }
@@ -22438,6 +22454,7 @@
22438
22454
 
22439
22455
  return feature
22440
22456
  }
22457
+
22441
22458
  }
22442
22459
 
22443
22460
  function findUTRs(exons, cdStart, cdEnd) {
@@ -23025,14 +23042,14 @@
23025
23042
  // TODO -- align all feature attribute names with UCSC, an use specific column
23026
23043
  for (let key of Object.keys(f)) {
23027
23044
  const v = f[key];
23028
- if (isString$2(v) && v.toLowerCase() === term.toLowerCase()) {
23045
+ if (isString$3(v) && v.toLowerCase() === term.toLowerCase()) {
23029
23046
  return true
23030
23047
  }
23031
23048
  }
23032
23049
  return false
23033
23050
  });
23034
23051
  if (matching.length > 0) {
23035
- return matching.reduce((l, f) => (l.end - l.start) > (f.end - f.start) ? l : f, features[0])
23052
+ return matching.reduce((l, f) => (l.end - l.start) > (f.end - f.start) ? l : f, matching[0])
23036
23053
  } else {
23037
23054
  return undefined
23038
23055
  }
@@ -23323,7 +23340,7 @@
23323
23340
  class ZoomLevelHeader {
23324
23341
  constructor(index, byteBuffer) {
23325
23342
  this.index = index;
23326
- this.reductionLevel = byteBuffer.getInt();
23343
+ this.reductionLevel = byteBuffer.getUInt();
23327
23344
  this.reserved = byteBuffer.getInt();
23328
23345
  this.dataOffset = byteBuffer.getLong();
23329
23346
  this.indexOffset = byteBuffer.getLong();
@@ -26926,7 +26943,7 @@
26926
26943
  let aaLetter;
26927
26944
  if (undefined === aminoAcidLetter) {
26928
26945
 
26929
- if(sequenceInterval.hasSequence(start, end)) {
26946
+ if (sequenceInterval.hasSequence(start, end)) {
26930
26947
 
26931
26948
  const sequence = sequenceInterval.getSequence(start, end);
26932
26949
  if (sequence && 3 === sequence.length) {
@@ -27062,7 +27079,8 @@
27062
27079
  try {
27063
27080
  ctx.save();
27064
27081
 
27065
- let name = feature.name;
27082
+ const labelField = this.config.labelField ? this.config.labelField : 'name';
27083
+ let name = feature[labelField];
27066
27084
  if (name === undefined && feature.gene) name = feature.gene.name;
27067
27085
  if (name === undefined) name = feature.id || feature.ID;
27068
27086
  if (!name || name === '.') return
@@ -27095,20 +27113,13 @@
27095
27113
  if (options.labelAllFeatures || xleft > lastLabelX || selected) {
27096
27114
  options.rowLastLabelX[feature.row] = xright;
27097
27115
 
27098
- if ('y' === options.axis) {
27099
- // TODO -- is this ever used?
27100
- ctx.save();
27101
- IGVGraphics.labelTransformWithContext(ctx, centerX);
27102
- IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
27103
- ctx.restore();
27104
- } else {
27105
- ctx.clearRect(
27106
- centerX - textMetrics.width / 2 - 1,
27107
- labelY - textMetrics.actualBoundingBoxAscent - 1,
27108
- textMetrics.width + 2,
27109
- textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent + 2);
27110
- IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
27111
- }
27116
+ ctx.clearRect(
27117
+ centerX - textMetrics.width / 2 - 1,
27118
+ labelY - textMetrics.actualBoundingBoxAscent - 1,
27119
+ textMetrics.width + 2,
27120
+ textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent + 2);
27121
+ IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
27122
+
27112
27123
  }
27113
27124
 
27114
27125
  } finally {
@@ -27522,7 +27533,11 @@
27522
27533
  // If drawing amino acids fetch cached sequence interval. It is not needed if track does not support AA, but
27523
27534
  // costs nothing since only a reference to a cached object is fetched.
27524
27535
  if (bpPerPixel < aminoAcidSequenceRenderThreshold) {
27525
- options.sequenceInterval = this.browser.genome.getSequenceInterval(referenceFrame.chr, bpStart, bpEnd);
27536
+ // Restrict the range requested to the limits: 1-chromosome.bpLength
27537
+ const chromosome = this.browser.genome.getChromosome(referenceFrame.chr);
27538
+ const chromosomeEnd = chromosome.bpLength;
27539
+ options.sequenceInterval = this.browser.genome.getSequenceInterval(referenceFrame.chr,
27540
+ bpStart > 0 ? bpStart : 0, bpEnd > chromosomeEnd ? chromosomeEnd : bpEnd);
27526
27541
  }
27527
27542
 
27528
27543
 
@@ -27649,7 +27664,7 @@
27649
27664
  fd.name &&
27650
27665
  fd.name.toLowerCase() === "name" &&
27651
27666
  fd.value &&
27652
- isString$2(fd.value) &&
27667
+ isString$3(fd.value) &&
27653
27668
  !fd.value.startsWith("<")) {
27654
27669
  const href = infoURL.replace("$$", feature.name);
27655
27670
  fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
@@ -28293,7 +28308,7 @@
28293
28308
  async postInit() {
28294
28309
  if(!this.featureSource) {
28295
28310
  // This will be the case when restoring from a session
28296
- const db = this.browser.genome.id; // TODO -- blat specific property
28311
+ const db = this.browser.genome.ucscID; // TODO -- blat specific property
28297
28312
  const url = this.browser.config["blatServerURL"];
28298
28313
  const features = await blat({url, userSeq: this.sequence, db});
28299
28314
  this._features = features;
@@ -28310,7 +28325,7 @@
28310
28325
  if (undefined === this.table) {
28311
28326
 
28312
28327
  const rows = this._features.map(f => [
28313
- f.chr,
28328
+ this.browser.genome.getChromosomeDisplayName(f.chr),
28314
28329
  (f.start + 1),
28315
28330
  f.end,
28316
28331
  f.strand,
@@ -28385,7 +28400,7 @@
28385
28400
 
28386
28401
  try {
28387
28402
 
28388
- const db = browser.genome.id; // TODO -- blat specific property
28403
+ const db = browser.genome.ucscID; // TODO -- blat specific property
28389
28404
  const url = browser.config["blatServerURL"] || defaultBlatServer;
28390
28405
  const features = await blat({url, userSeq: sequence, db});
28391
28406
 
@@ -28627,7 +28642,7 @@
28627
28642
  }
28628
28643
 
28629
28644
  items.push({
28630
- label: 'BLAT read sequence',
28645
+ label: 'BLAT visible sequence',
28631
28646
  click: async () => {
28632
28647
  let sequence = await this.browser.genome.getSequence(chr, start, end);
28633
28648
  if (sequence) {
@@ -30615,13 +30630,6 @@
30615
30630
  this.trackStanzas = trackStanzas;
30616
30631
  }
30617
30632
 
30618
- findCytobandURL() {
30619
- for (const t of this.trackStanzas) {
30620
- if (t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) {
30621
- return t.getProperty("bigDataUrl")
30622
- }
30623
- }
30624
- }
30625
30633
 
30626
30634
  getSupportedTrackCount() {
30627
30635
  let count = 0;
@@ -30961,6 +30969,7 @@
30961
30969
 
30962
30970
  const idMappings = new Map([
30963
30971
  ["hg38", "GCF_000001405.40"],
30972
+ ["hg38_1kg", "GCF_000001405.40"],
30964
30973
  ["mm39", "GCF_000001635.27"],
30965
30974
  ["mm10", "GCF_000001635.26"],
30966
30975
  ["bosTau9", "GCF_002263795.1"],
@@ -30997,12 +31006,16 @@
30997
31006
  this.genomeStanzas = genomeStanzas;
30998
31007
  this.trackStanzas = trackStanzas;
30999
31008
  this.groupStanzas = groupStanzas;
31009
+ this.cytobandStanza = null;
31000
31010
  this.trackHubMap = new Map();
31001
31011
 
31002
31012
  // trackStanzas will not be null if this is a "onefile" hub
31003
31013
  if (trackStanzas) {
31004
31014
  const genomeId = genomeStanzas[0].getProperty("genome"); // Assumption here this is a single genome hub
31005
31015
  this.trackHubMap.set(genomeId, new TrackDbHub(trackStanzas, groupStanzas));
31016
+
31017
+ // Search for cytoband track. This supports a special but important case -- Genark assembly hubs
31018
+ this.cytobandStanza = this.trackStanzas.find(t => t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) || null;
31006
31019
  }
31007
31020
  }
31008
31021
 
@@ -31104,6 +31117,10 @@
31104
31117
  config.twoBitBptURL = genomeStanza.getProperty("twoBitBptUrl");
31105
31118
  }
31106
31119
 
31120
+ if(this.cytobandStanza){
31121
+ config.cytobandBbURL = this.cytobandStanza.getProperty("bigDataUrl");
31122
+ }
31123
+
31107
31124
  if (this.hubStanza.hasProperty("longLabel")) {
31108
31125
  config.description = this.hubStanza.getProperty("longLabel").replace("/", "\n");
31109
31126
  } else {
@@ -31263,7 +31280,6 @@
31263
31280
  if (t.hasProperty("searchTrix")) {
31264
31281
  config.trixURL = t.getProperty("searchTrix");
31265
31282
  }
31266
-
31267
31283
  if (t.hasProperty("group")) {
31268
31284
  config._group = t.getProperty("group");
31269
31285
  if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
@@ -31272,6 +31288,12 @@
31272
31288
  this.groupPriorityMap.set(config._group, nextPriority);
31273
31289
  }
31274
31290
  }
31291
+ const labelFields = t.hasProperty("defaultLabelFields") ?
31292
+ t.getProperty("defaultLabelFields") :
31293
+ t.getProperty("labelFields");
31294
+ if (labelFields) {
31295
+ config.labelField = labelFields.split(",")[0];
31296
+ }
31275
31297
 
31276
31298
  return config
31277
31299
  }
@@ -31412,9 +31434,9 @@
31412
31434
  }
31413
31435
 
31414
31436
 
31415
- const i = line.indexOf(' ');
31416
- const key = line.substring(0, i).trim();
31417
- let value = line.substring(i + 1).trim();
31437
+ const index = line.indexOf(' ');
31438
+ const key = line.substring(0, index).trim();
31439
+ let value = line.substring(index + 1).trim();
31418
31440
 
31419
31441
  if (key === "type") {
31420
31442
  // The "type" property contains format and sometimes other information. For example, data range
@@ -31497,8 +31519,8 @@
31497
31519
  return host
31498
31520
  }
31499
31521
 
31500
- const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes.json";
31501
- const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-genomes/refs/heads/main/dist/genomes.json";
31522
+ const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes3.json";
31523
+ const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-data/refs/heads/main/genomes/web/genomes.json";
31502
31524
 
31503
31525
  const GenomeUtils = {
31504
31526
 
@@ -31523,7 +31545,7 @@
31523
31545
  } catch (error) {
31524
31546
  try {
31525
31547
  console.error("Error initializing default genomes:", error);
31526
- const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 2000});
31548
+ const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 10000});
31527
31549
  processJson(jsonArray, table);
31528
31550
  } catch (e) {
31529
31551
  console.error("Error initializing backup genomes:", error);
@@ -31553,7 +31575,7 @@
31553
31575
  expandReference: async function (alert, idOrConfig) {
31554
31576
 
31555
31577
  // idOrConfig might be a json string? I'm actually not sure how this arises.
31556
- if (isString$2(idOrConfig) && idOrConfig.startsWith("{")) {
31578
+ if (isString$3(idOrConfig) && idOrConfig.startsWith("{")) {
31557
31579
  try {
31558
31580
  idOrConfig = JSON.parse(idOrConfig);
31559
31581
  } catch (e) {
@@ -31562,7 +31584,7 @@
31562
31584
  }
31563
31585
 
31564
31586
  let genomeID;
31565
- if (isString$2(idOrConfig)) {
31587
+ if (isString$3(idOrConfig)) {
31566
31588
  genomeID = idOrConfig;
31567
31589
  } else if (idOrConfig.genome) {
31568
31590
  genomeID = idOrConfig.genome;
@@ -32508,7 +32530,7 @@
32508
32530
  let track = this.trackView.track;
32509
32531
  const dataList = track.popupData(clickState);
32510
32532
 
32511
- const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList]);
32533
+ const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList, clickState.genomicLocation]);
32512
32534
 
32513
32535
  let content;
32514
32536
  if (undefined === popupClickHandlerResult || true === popupClickHandlerResult) {
@@ -40998,6 +41020,9 @@
40998
41020
  return true
40999
41021
  }
41000
41022
 
41023
+ /**
41024
+ * Track for segmented copy number, mut, maf and shoebox files.
41025
+ */
41001
41026
  class SegTrack extends TrackBase {
41002
41027
 
41003
41028
  #sortDirections = new Map()
@@ -41145,7 +41170,10 @@
41145
41170
  }, e);
41146
41171
  }
41147
41172
 
41148
- menuItems.push({ element: createElementWithString('<div>Set color scale threshold</div>'), dialog: dialogPresentationHandler});
41173
+ menuItems.push({
41174
+ element: createElementWithString('<div>Set color scale threshold</div>'),
41175
+ dialog: dialogPresentationHandler
41176
+ });
41149
41177
  }
41150
41178
 
41151
41179
  menuItems.push('<hr/>');
@@ -41176,12 +41204,75 @@
41176
41204
 
41177
41205
  getSamples() {
41178
41206
  return {
41179
- names: this.sampleKeys,
41207
+ names: this.filteredSampleKeys,
41180
41208
  height: this.sampleHeight,
41181
41209
  yOffset: 0
41182
41210
  }
41183
41211
  }
41184
41212
 
41213
+ /**
41214
+ * Set the sample filter object. This is used to filter samples from the set based on values over a specified
41215
+ * genomic region. The values compared depend on the track data type:
41216
+ * - "seg" and "shoebox" -- average value over the region
41217
+ * - "mut" and "maf" -- count of features overlapping the region
41218
+ *
41219
+ * The method is asynchronous because it may need to fetch data from the server to compute the scores.
41220
+ * Computed scores are stored and used to filter the sample keys on demand.
41221
+ *
41222
+ * @param filterObject
41223
+ * @returns {Promise<void>}
41224
+ */
41225
+ async setSampleFilter(filterObject) {
41226
+ if (!filterObject) {
41227
+ this.config.filterObject = undefined;
41228
+ this.filterObject = undefined;
41229
+ this.trackView.repaintViews();
41230
+ } else {
41231
+ const filterObjectCopy = Object.assign({}, filterObject);
41232
+ this.config.filterObject = filterObjectCopy;
41233
+
41234
+ filterObject.scores = await this.computeRegionScores(filterObject);
41235
+ this.filterObject = filterObject;
41236
+ this.trackView.checkContentHeight();
41237
+ this.trackView.repaintViews();
41238
+ }
41239
+ // TODO - store filter object in session
41240
+ }
41241
+
41242
+
41243
+ /**
41244
+ * Filter function for sample keys.
41245
+ *
41246
+ * @param sampleKey
41247
+ * @returns {boolean}
41248
+ */
41249
+ filter(sampleKey) {
41250
+ if (this.filterObject) {
41251
+ const filterObject = this.filterObject;
41252
+ const scores = filterObject.scores;
41253
+ const score = scores[sampleKey];
41254
+
41255
+ if (this.type === 'seg') {
41256
+ if (filterObject.op === '>') {
41257
+ return score > filterObject.value
41258
+ } else if (filterObject.op === '<') {
41259
+ return score < filterObject.value
41260
+ }
41261
+ } else if (this.type === 'mut' || this.type === 'maf') {
41262
+ return 'HAS' === filterObject.op ? score : !score
41263
+ }
41264
+ }
41265
+ // else if (this.config.sampleFilter) {
41266
+ // return this.config.sampleFilter(sampleKey)
41267
+ // }
41268
+ return true
41269
+ }
41270
+
41271
+ get filteredSampleKeys() {
41272
+ return this.sampleKeys.filter(sampleKey => this.filter(sampleKey))
41273
+ }
41274
+
41275
+
41185
41276
  async getFeatures(chr, start, end) {
41186
41277
  const features = await this.featureSource.getFeatures({chr, start, end});
41187
41278
  // New segments could conceivably add new samples
@@ -41189,8 +41280,10 @@
41189
41280
 
41190
41281
  if (this.initialSort) {
41191
41282
  const sort = this.initialSort;
41283
+
41192
41284
  if (sort.option === undefined || sort.option.toUpperCase() === "VALUE") {
41193
- this.sortByValue(sort, features);
41285
+ const sortFeatures = (sort.chr === chr && sort.start >= start && sort.end <= end) ? features : undefined;
41286
+ this.sortByValue(sort, sortFeatures);
41194
41287
  } else if ("ATTRIBUTE" === sort.option.toUpperCase() && sort.attribute) {
41195
41288
  const sortDirection = "DESC" === sort.direction ? 1 : -1;
41196
41289
  this.sortByAttribute(sort.attribute, sortDirection);
@@ -41205,6 +41298,7 @@
41205
41298
 
41206
41299
  IGVGraphics.fillRect(context, 0, pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
41207
41300
 
41301
+
41208
41302
  if (features && features.length > 0) {
41209
41303
 
41210
41304
  this.checkForLog(features);
@@ -41216,14 +41310,15 @@
41216
41310
 
41217
41311
  // Create a map for fast id -> row lookup
41218
41312
  const samples = {};
41219
- this.sampleKeys.forEach(function (id, index) {
41313
+ const filteredKeys = this.filteredSampleKeys;
41314
+ filteredKeys.forEach(function (id, index) {
41220
41315
  samples[id] = index;
41221
41316
  });
41222
41317
 
41223
41318
  let border;
41224
41319
  switch (this.displayMode) {
41225
41320
  case "FILL":
41226
- this.sampleHeight = pixelHeight / this.sampleKeys.length;
41321
+ this.sampleHeight = pixelHeight / filteredKeys.length;
41227
41322
  border = 0;
41228
41323
  break
41229
41324
 
@@ -41361,7 +41456,7 @@
41361
41456
  if (!features) return 0
41362
41457
  const sampleHeight = ("SQUISHED" === this.displayMode) ? this.squishedRowHeight : this.expandedRowHeight;
41363
41458
  this.updateSampleKeys(features);
41364
- return this.sampleKeys.length * sampleHeight
41459
+ return this.filteredSampleKeys.length * sampleHeight
41365
41460
  }
41366
41461
 
41367
41462
  /**
@@ -41370,16 +41465,38 @@
41370
41465
  async sortByValue(sort, featureList) {
41371
41466
 
41372
41467
  const chr = sort.chr;
41468
+ const start = sort.position !== undefined ? sort.position - 1 : sort.start;
41469
+ const end = sort.end === undefined ? start + 1 : sort.end;
41470
+ const scores = await this.computeRegionScores({chr, start, end}, featureList);
41471
+ const d2 = (sort.direction === "ASC" ? 1 : -1);
41472
+
41473
+ this.sampleKeys.sort(function (a, b) {
41474
+ let s1 = scores[a];
41475
+ let s2 = scores[b];
41476
+ if (!s1) s1 = d2 * Number.MAX_VALUE;
41477
+ if (!s2) s2 = d2 * Number.MAX_VALUE;
41478
+ if (s1 === s2) return 0
41479
+ else if (s1 > s2) return d2
41480
+ else return d2 * -1
41481
+ });
41482
+
41483
+ this.config.sort = sort;
41484
+ this.trackView.repaintViews();
41485
+ }
41486
+
41487
+
41488
+ async computeRegionScores(filterObject, featureList) {
41489
+
41490
+ const chr = filterObject.chr;
41373
41491
  let start, end;
41374
- if (sort.position) {
41375
- start = sort.position - 1;
41492
+ if (filterObject.position) {
41493
+ start = filterObject.position - 1;
41376
41494
  end = start + 1;
41377
41495
  } else {
41378
- start = sort.start;
41379
- end = sort.end;
41496
+ start = filterObject.start;
41497
+ end = filterObject.end;
41380
41498
  }
41381
41499
 
41382
-
41383
41500
  if (!featureList) {
41384
41501
  featureList = await this.featureSource.getFeatures({chr, start, end});
41385
41502
  }
@@ -41388,62 +41505,40 @@
41388
41505
  this.updateSampleKeys(featureList);
41389
41506
 
41390
41507
  const scores = {};
41391
- const d2 = (sort.direction === "ASC" ? 1 : -1);
41508
+ const bpLength = end - start + 1;
41509
+
41510
+ const mutationTypes = filterObject.value ? new Set(filterObject.value) : undefined;
41511
+
41512
+ for (let segment of featureList) {
41513
+ if (segment.end < start) continue
41514
+ if (segment.start > end) break
41515
+ const sampleKey = segment.sampleKey || segment.sample;
41516
+
41517
+ if ("mut" === this.type) {
41518
+ if (mutationTypes) {
41519
+ const mutationType = segment.getAttribute("Variant_Classification");
41520
+ if (mutationTypes.has(mutationType)) {
41521
+ // Just count features overlapping region per sample
41522
+ scores[sampleKey] = (scores[sampleKey] || 0) + 1;
41523
+ }
41524
+ } else {
41525
+ // Just count features overlapping region per sample
41526
+ scores[sampleKey] = (scores[sampleKey] || 0) + 1;
41527
+ }
41528
+ } else {
41392
41529
 
41393
- const sortSeg = () => {
41394
- // Compute weighted average score for each sample
41395
- const bpLength = end - start + 1;
41396
- for (let segment of featureList) {
41397
- if (segment.end < start) continue
41398
- if (segment.start > end) break
41399
41530
  const min = Math.max(start, segment.start);
41400
41531
  const max = Math.min(end, segment.end);
41401
41532
  const f = (max - min) / bpLength;
41402
- const sampleKey = segment.sampleKey || segment.sample;
41403
- const s = scores[sampleKey] || 0;
41404
- scores[sampleKey] = s + f * segment.value;
41405
- }
41406
-
41407
- // Now sort sample names by score
41408
- this.sampleKeys.sort(function (a, b) {
41409
- let s1 = scores[a];
41410
- let s2 = scores[b];
41411
- if (!s1) s1 = d2 * Number.MAX_VALUE;
41412
- if (!s2) s2 = d2 * Number.MAX_VALUE;
41413
- if (s1 === s2) return 0
41414
- else if (s1 > s2) return d2
41415
- else return d2 * -1
41416
- });
41417
- };
41418
-
41419
- const sortMut = () => {
41420
- // Compute weighted average score for each sample
41421
- for (let segment of featureList) {
41422
- if (segment.end < start) continue
41423
- if (segment.start > end) break
41424
- const sampleKey = segment.sampleKey || segment.sample;
41425
- if (!scores.hasOwnProperty(sampleKey) || segment.value.localeCompare(scores[sampleKey]) > 0) {
41426
- scores[sampleKey] = segment.value;
41427
- }
41428
- }
41429
- // Now sort sample names by score
41430
- this.sampleKeys.sort(function (a, b) {
41431
- let sa = scores[a] || "";
41432
- let sb = scores[b] || "";
41433
- return d2 * (sa.localeCompare(sb))
41434
- });
41435
- };
41436
-
41437
- if ("mut" === this.type) {
41438
- sortMut();
41439
- } else {
41440
- sortSeg();
41533
+ scores[sampleKey] = (scores[sampleKey] || 0) + f * segment.value;
41534
+ }
41441
41535
  }
41442
41536
 
41443
- this.trackView.repaintViews();
41537
+ return scores
41444
41538
 
41445
41539
  }
41446
41540
 
41541
+
41447
41542
  sortByAttribute(attribute, sortDirection) {
41448
41543
 
41449
41544
  this.sampleKeys = this.browser.sampleInfo.getSortedSampleKeysByAttribute(this.sampleKeys, attribute, sortDirection);
@@ -41512,7 +41607,7 @@
41512
41607
  const dirLabel = direction === "DESC" ? "descending" : "ascending";
41513
41608
  const sortLabel = this.type === 'seg' || this.type === 'shoebox' ?
41514
41609
  `Sort by value (${dirLabel})` :
41515
- `Sort by type (${dirLabel})`;
41610
+ `Sort by count (${dirLabel})`;
41516
41611
  return {
41517
41612
  label: sortLabel,
41518
41613
  click: () => {
@@ -41524,7 +41619,6 @@
41524
41619
  end: Math.floor(genomicLocation + bpWidth)
41525
41620
  };
41526
41621
  sortHandler(sort);
41527
- this.config.sort = sort;
41528
41622
  }
41529
41623
  }
41530
41624
  })
@@ -41694,13 +41788,13 @@
41694
41788
  }
41695
41789
  }
41696
41790
 
41697
- popupData(genomicLocation) {
41791
+ popupData(genomicLocation, hiddenTags, showTags) {
41698
41792
 
41699
- let nameValues = this.firstAlignment.popupData(genomicLocation);
41793
+ let nameValues = this.firstAlignment.popupData(genomicLocation, hiddenTags, showTags);
41700
41794
 
41701
41795
  if (this.secondAlignment) {
41702
41796
  nameValues.push("-------------------------------");
41703
- nameValues = nameValues.concat(this.secondAlignment.popupData(genomicLocation));
41797
+ nameValues = nameValues.concat(this.secondAlignment.popupData(genomicLocation, hiddenTags, showTags));
41704
41798
  }
41705
41799
  return nameValues
41706
41800
  }
@@ -43414,7 +43508,7 @@
43414
43508
  isNegativeStrand() {
43415
43509
  return (this.flags & READ_STRAND_FLAG$2) !== 0
43416
43510
  }
43417
-
43511
+
43418
43512
  isMateNegativeStrand() {
43419
43513
  return (this.flags & MATE_STRAND_FLAG$2) !== 0
43420
43514
  }
@@ -43462,7 +43556,7 @@
43462
43556
  return (genomicLocation >= s && genomicLocation <= (s + l))
43463
43557
  }
43464
43558
 
43465
- popupData(genomicLocation) {
43559
+ popupData(genomicLocation, hiddenTags, showTags) {
43466
43560
 
43467
43561
  // if the user clicks on a base next to an insertion, show just the
43468
43562
  // inserted bases in a popup (like in desktop IGV).
@@ -43545,15 +43639,20 @@
43545
43639
  }
43546
43640
  }
43547
43641
 
43548
- const hiddenTags = new Set(['SA', 'MD']);
43549
43642
  nameValues.push('<hr/>');
43550
43643
  for (let key in tagDict) {
43551
- if (!hiddenTags.has(key)) {
43644
+ if (showTags?.has(key)) {
43645
+ nameValues.push({name: key, value: tagDict[key]});
43646
+ } else if (showTags) {
43647
+ hiddenTags.add(key);
43648
+ } else if (!hiddenTags.has(key)) {
43552
43649
  nameValues.push({name: key, value: tagDict[key]});
43553
43650
  }
43554
43651
  }
43555
43652
 
43556
- nameValues.push({name: 'Hidden Tags', value: 'SA, MD'});
43653
+ if (hiddenTags && hiddenTags.size > 0) {
43654
+ nameValues.push({name: 'Hidden Tags', value: Array.from(hiddenTags).join(", ")});
43655
+ }
43557
43656
 
43558
43657
  nameValues.push('<hr/>');
43559
43658
  nameValues.push({name: 'Genomic Location: ', value: numberFormatter$1(1 + genomicLocation)});
@@ -43561,18 +43660,18 @@
43561
43660
  nameValues.push({name: 'Base Quality:', value: this.readBaseQualityAt(genomicLocation)});
43562
43661
 
43563
43662
  const bmSets = this.getBaseModificationSets();
43564
- if(bmSets) {
43663
+ if (bmSets) {
43565
43664
  const i = this.positionToReadIndex(genomicLocation);
43566
- if(undefined !== i) {
43665
+ if (undefined !== i) {
43567
43666
  let found = false;
43568
43667
  for (let bmSet of bmSets) {
43569
43668
  if (bmSet.containsPosition(i)) {
43570
- if(!found) {
43669
+ if (!found) {
43571
43670
  nameValues.push('<hr/>');
43572
43671
  nameValues.push('<b>Base modifications:</b>');
43573
43672
  found = true;
43574
43673
  }
43575
- const lh = Math.round((100/255) * byteToUnsignedInt(bmSet.likelihoods.get(i)));
43674
+ const lh = Math.round((100 / 255) * byteToUnsignedInt(bmSet.likelihoods.get(i)));
43576
43675
  nameValues.push(`${bmSet.fullName()} @ likelihood = ${lh}%`);
43577
43676
  }
43578
43677
  }
@@ -43662,7 +43761,7 @@
43662
43761
  const mm = this.tagDict["MM"] || this.tagDict["Mm"];
43663
43762
  const ml = this.tagDict["ML"] || this.tagDict["Ml"];
43664
43763
 
43665
- if (isString$2(mm) && (!ml || Array.isArray(ml))) { // minimal validation, 10X uses these reserved tags for something completely different
43764
+ if (isString$3(mm) && (!ml || Array.isArray(ml))) { // minimal validation, 10X uses these reserved tags for something completely different
43666
43765
  if (mm.length === 0) {
43667
43766
  this.baseModificationSets = EMPTY_SET;
43668
43767
  } else {
@@ -43675,7 +43774,7 @@
43675
43774
  return this.baseModificationSets
43676
43775
  }
43677
43776
 
43678
- getGroupValue( groupBy, tag, expectedPairOrientation) {
43777
+ getGroupValue(groupBy, tag, expectedPairOrientation) {
43679
43778
 
43680
43779
  const al = this;
43681
43780
  switch (groupBy) {
@@ -43712,7 +43811,7 @@
43712
43811
  }
43713
43812
  }
43714
43813
 
43715
- positionToReadIndex( position) {
43814
+ positionToReadIndex(position) {
43716
43815
  const block = blockAtGenomicLocation(this.blocks, position);
43717
43816
  if (block) {
43718
43817
  return (position - block.start) + block.seqOffset
@@ -45691,7 +45790,7 @@
45691
45790
 
45692
45791
  function inferIndexPath(url, extension) {
45693
45792
 
45694
- if (isString$2(url)) {
45793
+ if (isString$3(url)) {
45695
45794
  if (url.includes("?")) {
45696
45795
  const idx = url.indexOf("?");
45697
45796
  return url.substring(0, idx) + "." + extension + url.substring(idx)
@@ -45850,7 +45949,7 @@
45850
45949
  this.bamReader = new CramReader(config, genome, browser);
45851
45950
  } else {
45852
45951
  if (!this.config.indexURL && config.indexed !== false) {
45853
- if (isString$2(this.config.url)) {
45952
+ if (isString$3(this.config.url)) {
45854
45953
  const indexPath = inferIndexPath(this.config.url, "bai");
45855
45954
  if (indexPath) {
45856
45955
  console.warn(`Warning: no indexURL specified for ${this.config.url}. Guessing ${indexPath}`);
@@ -48162,7 +48261,7 @@
48162
48261
  highlightColor: undefined,
48163
48262
  minTLEN: undefined,
48164
48263
  maxTLEN: undefined,
48165
- tagColorPallete: "Set1"
48264
+ tagColorPallete: "Set1",
48166
48265
  }
48167
48266
 
48168
48267
  _colorTables = new Map()
@@ -48177,6 +48276,18 @@
48177
48276
  this.colorTable = new ColorTable(config.tagColorTable);
48178
48277
  }
48179
48278
 
48279
+ // Only one of showTags / hideTags should be specified. If both are specified showTags takes precedence.
48280
+ if (config.showTags && config.hideTags) {
48281
+ console.warn("Both showTags and hideTags specified. showTags will be used.");
48282
+ }
48283
+ if (config.showTags) {
48284
+ this.showTags = new Set(config.showTags);
48285
+ this.hiddenTags = new Set();
48286
+ } else {
48287
+ this.hiddenTags = new Set(config.hideTags || ["SA", "MD"]);
48288
+ }
48289
+
48290
+
48180
48291
  // Backward compatibility overrides
48181
48292
  if (config.largeFragmentLengthColor) this.largeTLENColor = config.largeFragmentLengthColor;
48182
48293
  if (config.pairOrienation) this.expectedPairOrientation = config.pairOrientation;
@@ -48737,7 +48848,7 @@
48737
48848
 
48738
48849
  popupData(clickState) {
48739
48850
  const clickedObject = this.getClickedObject(clickState);
48740
- return clickedObject ? clickedObject.popupData(clickState.genomicLocation) : undefined
48851
+ return clickedObject?.popupData(clickState.genomicLocation, this.hiddenTags, this.showTags)
48741
48852
  };
48742
48853
 
48743
48854
  /**
@@ -48768,7 +48879,10 @@
48768
48879
  colorByMenuItems.push({key: 'tlen', label: 'insert size (TLEN)'});
48769
48880
  colorByMenuItems.push({key: 'unexpectedPair', label: 'pair orientation & insert size (TLEN)'});
48770
48881
  }
48771
- colorByMenuItems.push({key: 'tag', label: 'tag'});
48882
+ if(this.colorBy && this.colorBy.startsWith("tag:")) {
48883
+ colorByMenuItems.push({key: this.colorBy, label: this.colorBy});
48884
+ }
48885
+ colorByMenuItems.push({key: 'tag', label: 'tag...'});
48772
48886
  for (let item of colorByMenuItems) {
48773
48887
  const selected = (this.colorBy === undefined && item.key === 'none') || this.colorBy === item.key;
48774
48888
  menuItems.push(this.colorByCB(item, selected));
@@ -49284,7 +49398,7 @@
49284
49398
 
49285
49399
  if (seqstring.length < maxSequenceSize$1) {
49286
49400
  list.push({
49287
- label: 'BLAT read sequence',
49401
+ label: 'BLAT visible sequence',
49288
49402
  click: () => {
49289
49403
  const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(seqstring) : seqstring;
49290
49404
  const name = `${clickedAlignment.readName} - blat`;
@@ -49494,14 +49608,19 @@
49494
49608
  case "tag":
49495
49609
  const tagValue = alignment.tags()[tag];
49496
49610
  if (tagValue !== undefined) {
49497
- if (this.bamColorTag === tag) {
49611
+
49612
+ // If the tag value can be interpreted as a color, use it
49613
+ if(typeof tagValue.startsWith === 'function') {
49498
49614
  color = IGVColor.createColorStringSafe(tagValue);
49499
49615
  }
49500
- if (!this.colorTable) {
49501
- this.colorTable = new PaletteColorTable(this.tagColorPallete);
49502
- }
49503
- color = this.colorTable.getColor(tagValue);
49504
49616
 
49617
+ // Tag value is not a color, use a color table
49618
+ if (!color) {
49619
+ if (!this.colorTable) {
49620
+ this.colorTable = new PaletteColorTable(this.tagColorPallete);
49621
+ }
49622
+ color = this.colorTable.getColor(tagValue);
49623
+ }
49505
49624
  }
49506
49625
  break
49507
49626
  }
@@ -65718,7 +65837,7 @@ ${indent}columns: ${matrix.columns}
65718
65837
  * THE SOFTWARE.
65719
65838
  */
65720
65839
 
65721
- const isString = isString$2;
65840
+ const isString$1 = isString$3;
65722
65841
 
65723
65842
  const DEFAULT_VISIBILITY_WINDOW = 1000000;
65724
65843
  const TOP_MARGIN = 10;
@@ -65841,7 +65960,7 @@ ${indent}columns: ${matrix.columns}
65841
65960
  }
65842
65961
  if (undefined === this.visibilityWindow && this.config.indexed !== false) {
65843
65962
  const fn = isFile(this.config.url) ? this.config.url.name : this.config.url;
65844
- if (isString(fn) && fn.toLowerCase().includes("gnomad")) {
65963
+ if (isString$1(fn) && fn.toLowerCase().includes("gnomad")) {
65845
65964
  this.visibilityWindow = 1000; // these are known to be very dense
65846
65965
  } else if (typeof this.featureSource.defaultVisibilityWindow === 'function') {
65847
65966
  this.visibilityWindow = await this.featureSource.defaultVisibilityWindow();
@@ -69725,6 +69844,9 @@ ${indent}columns: ${matrix.columns}
69725
69844
  ['image', (config, browser) => new ImageTrack(config, browser)]
69726
69845
  ]);
69727
69846
 
69847
+ function knownTrackTypes () {
69848
+ return new Set(trackFunctions.keys())
69849
+ }
69728
69850
 
69729
69851
  /**
69730
69852
  * Return a track of the given type, passing configuration and a point to the IGV "Browser" object to its constructor function*
@@ -70217,7 +70339,7 @@ ${indent}columns: ${matrix.columns}
70217
70339
  })
70218
70340
  }
70219
70341
 
70220
- const _version = "3.3.0";
70342
+ const _version = "3.4.1";
70221
70343
  function version() {
70222
70344
  return _version
70223
70345
  }
@@ -70268,7 +70390,7 @@ ${indent}columns: ${matrix.columns}
70268
70390
  if (this.select.value.trim().toLowerCase() === "all" || this.select.value === "*") {
70269
70391
  if (browser.genome.wholeGenomeView) {
70270
70392
  const wgChr = browser.genome.getChromosome("all");
70271
- return {chr: "all", start: 0, end: wgChr.bpLength}
70393
+ browser.updateLoci([{chr: "all", start: 0, end: wgChr.bpLength}]);
70272
70394
  }
70273
70395
  } else {
70274
70396
  const chromosome = await browser.genome.loadChromosome(this.select.value);
@@ -72073,6 +72195,26 @@ ${indent}columns: ${matrix.columns}
72073
72195
  }
72074
72196
 
72075
72197
 
72198
+ const found = this.browser.findTracks(track => typeof track.sortByValue === 'function');
72199
+ if (found.length > 0) {
72200
+ const { chr, start, end } = feature;
72201
+ items.push(
72202
+ '<hr/>',
72203
+ {
72204
+ label: 'Sort by value (ascending)',
72205
+ click: () => Promise.all(found.map(track => track.sortByValue({ option: 'VALUE', direction: 'ASC', chr, start, end })))
72206
+ });
72207
+
72208
+ items.push(
72209
+ '<hr/>',
72210
+ {
72211
+ label: 'Sort by value (descending)',
72212
+ click: () => Promise.all(found.map(track => track.sortByValue({ option: 'VALUE', direction: 'DESC', chr, start, end })))
72213
+ });
72214
+
72215
+ }
72216
+
72217
+
72076
72218
  if (roiSet.isUserDefined) {
72077
72219
  items.push(
72078
72220
  '<hr/>',
@@ -72081,7 +72223,7 @@ ${indent}columns: ${matrix.columns}
72081
72223
  click: async () => {
72082
72224
  roiSet.removeFeature(feature);
72083
72225
  const userDefinedFeatures = await roiSet.getAllFeatures();
72084
-
72226
+
72085
72227
  // Delete user defined ROI Set if it is empty
72086
72228
  if (Object.keys(userDefinedFeatures).length === 0) {
72087
72229
  roiManager.deleteUserDefinedROISet();
@@ -73254,6 +73396,189 @@ ${indent}columns: ${matrix.columns}
73254
73396
 
73255
73397
  }
73256
73398
 
73399
+ /**
73400
+ * Update deprecated fasta & index urls in the reference object.
73401
+ */
73402
+
73403
+ const isString = (x) => {
73404
+ return (x && typeof x === "string") || x instanceof String
73405
+ };
73406
+
73407
+ /**
73408
+ * Replaces deprecated s3 fasta URLs with twoBitURLs. The purpose here is to rescue references from saved sessions
73409
+ * that contain pointers to deprectated IGV s3 buckets. These buckets will eventually be deleted
73410
+ *
73411
+ * @param reference
73412
+ */
73413
+ function updateReference(reference) {
73414
+
73415
+ if (!(requiresUpdate(reference) && updates[reference.id])) {
73416
+ return
73417
+ }
73418
+
73419
+ const updatedReference = updates[reference.id];
73420
+ if (updatedReference) {
73421
+ delete reference.fastaURL;
73422
+ if (reference.indexURL) delete reference.indexURL;
73423
+ reference.twoBitURL = updatedReference.twoBitURL;
73424
+ if (updatedReference.twoBitBptURL) reference.twoBitBptURL = updatedReference.twoBitBptURL;
73425
+ if (updatedReference.chromSizesURL) reference.chromSizesURL = updatedReference.chromSizesURL;
73426
+ }
73427
+ }
73428
+
73429
+ function requiresUpdate(reference) {
73430
+ return isString(reference.fastaURL) &&
73431
+ (reference.fastaURL.startsWith("https://igv.org") ||
73432
+ ["igv.org.genomes", "igv.broadinstitute.org", "igv.genepattern.org", "igvdata.broadinstitute.org",
73433
+ "igv-genepattern-org"].some(bucket => reference.fastaURL.includes(bucket)))
73434
+ }
73435
+
73436
+ const updates = {
73437
+ "hs1": {
73438
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.2bit",
73439
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.2bit.bpt",
73440
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.chrom.sizes.txt"
73441
+ },
73442
+ "hg38": {
73443
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.2bit",
73444
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.chrom.sizes"
73445
+ },
73446
+ "hg19": {
73447
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.2bit",
73448
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.chrom.sizes"
73449
+ },
73450
+ "hg18": {
73451
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg18/bigZips/hg18.2bit",
73452
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg18/bigZips/hg18.chrom.sizes"
73453
+ },
73454
+ "mm39": {
73455
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm39/bigZips/mm39.2bit",
73456
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm39/bigZips/mm39.chrom.sizes"
73457
+ },
73458
+ "mm10": {
73459
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.2bit",
73460
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.chrom.sizes"
73461
+ },
73462
+ "mm9": {
73463
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm9/bigZips/mm9.2bit",
73464
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm9/bigZips/mm9.chrom.sizes"
73465
+ },
73466
+ "rn7": {
73467
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.2bit",
73468
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.2bit.bpt",
73469
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.chrom.sizes.txt"
73470
+ },
73471
+ "rn6": {
73472
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.2bit",
73473
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.2bit.bpt",
73474
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.chrom.sizes.txt"
73475
+ },
73476
+ "gorGor6": {
73477
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor6/bigZips/gorGor6.2bit",
73478
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor6/bigZips/gorGor6.chrom.sizes"
73479
+ },
73480
+ "gorGor4": {
73481
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor4/bigZips/gorGor4.2bit",
73482
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor4/bigZips/gorGor4.chrom.sizes"
73483
+ },
73484
+ "panTro6": {
73485
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro6/bigZips/panTro6.2bit",
73486
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro6/bigZips/panTro6.chrom.sizes"
73487
+ },
73488
+ "panTro5": {
73489
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro5/bigZips/panTro5.2bit",
73490
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro5/bigZips/panTro5.chrom.sizes"
73491
+ },
73492
+ "panTro4": {
73493
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro4/bigZips/panTro4.2bit",
73494
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro4/bigZips/panTro4.chrom.sizes"
73495
+ },
73496
+ "macFas5": {
73497
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/macFas5/bigZips/macFas5.2bit",
73498
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/macFas5/bigZips/macFas5.chrom.sizes"
73499
+ },
73500
+ "panPan2": {
73501
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panPan2/bigZips/panPan2.2bit",
73502
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panPan2/bigZips/panPan2.chrom.sizes"
73503
+ },
73504
+ "canFam6": {
73505
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam6/bigZips/canFam6.2bit",
73506
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam6/bigZips/canFam6.chrom.sizes"
73507
+ },
73508
+ "canFam5": {
73509
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam5/bigZips/canFam5.2bit",
73510
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam5/bigZips/canFam5.chrom.sizes"
73511
+ },
73512
+ "canFam4": {
73513
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam4/bigZips/canFam4.2bit",
73514
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam4/bigZips/canFam4.chrom.sizes"
73515
+ },
73516
+ "canFam3": {
73517
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam3/bigZips/canFam3.2bit",
73518
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam3/bigZips/canFam3.chrom.sizes"
73519
+ },
73520
+ "bosTau9": {
73521
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau9/bigZips/bosTau9.2bit",
73522
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau9/bigZips/bosTau9.chrom.sizes"
73523
+ },
73524
+ "bosTau8": {
73525
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau8/bigZips/bosTau8.2bit",
73526
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau8/bigZips/bosTau8.chrom.sizes"
73527
+ },
73528
+ "susScr11": {
73529
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/susScr11/bigZips/susScr11.2bit",
73530
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/susScr11/bigZips/susScr11.chrom.sizes"
73531
+ },
73532
+ "galGal6": {
73533
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/galGal6/bigZips/galGal6.2bit",
73534
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/galGal6/bigZips/galGal6.chrom.sizes"
73535
+ },
73536
+ "danRer11": {
73537
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer11/bigZips/danRer11.2bit",
73538
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer11/bigZips/danRer11.chrom.sizes"
73539
+ },
73540
+ "danRer10": {
73541
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer10/bigZips/danRer10.2bit",
73542
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer10/bigZips/danRer10.chrom.sizes"
73543
+ },
73544
+ "ce11": {
73545
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/ce11/bigZips/ce11.2bit",
73546
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/ce11/bigZips/ce11.chrom.sizes"
73547
+ },
73548
+ "dm6": {
73549
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm6/bigZips/dm6.2bit",
73550
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm6/bigZips/dm6.chrom.sizes"
73551
+ },
73552
+ "dm3": {
73553
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm3/bigZips/dm3.2bit",
73554
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm3/bigZips/dm3.chrom.sizes"
73555
+ },
73556
+ "sacCer3": {
73557
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.2bit",
73558
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.2bit.bpt",
73559
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.chrom.sizes.txt"
73560
+ },
73561
+ "GCF_000002945.1": {
73562
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.2bit",
73563
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.2bit.bpt",
73564
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.chrom.sizes.txt"
73565
+ },
73566
+ "GCF_009858895.2": {
73567
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.2bit",
73568
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.2bit.bpt",
73569
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.chrom.sizes.txt"
73570
+ },
73571
+ "tair10": {
73572
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/735/GCF_000001735.3/GCF_000001735.3.2bit",
73573
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/735/GCF_000001735.3/GCF_000001735.3.chrom.sizes.txt"
73574
+ },
73575
+ "GCA_000022165.1": {
73576
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.2bit",
73577
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.2bit.bpt",
73578
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.chrom.sizes.txt"
73579
+ }
73580
+ };
73581
+
73257
73582
  const ucsdIDMap = new Map([
73258
73583
  ["1kg_ref", "hg18"],
73259
73584
  ["1kg_v37", "hg19"],
@@ -73275,6 +73600,7 @@ ${indent}columns: ${matrix.columns}
73275
73600
 
73276
73601
  static async createGenome(options, browser) {
73277
73602
 
73603
+ updateReference(options);
73278
73604
  const genome = new Genome(options, browser);
73279
73605
  await genome.init();
73280
73606
  return genome
@@ -73298,7 +73624,6 @@ ${indent}columns: ${matrix.columns}
73298
73624
  // Load sequence
73299
73625
  this.sequence = await loadSequence(config, this.browser);
73300
73626
 
73301
-
73302
73627
  // Load cytobands. This is optional but required to support the ideogram. Only needed for whole genome view
73303
73628
  if(false !== config.showIdeogram && false !== config.wholeGenomeView) {
73304
73629
  if (config.cytobandURL) {
@@ -73342,10 +73667,14 @@ ${indent}columns: ${matrix.columns}
73342
73667
  } else {
73343
73668
  this.#wgChromosomeNames = config.chromosomeOrder.split(',').map(nm => nm.trim());
73344
73669
  }
73670
+ // Trim to remove non-existent chromosomes
73671
+ await this.chromAlias.preload(this.#wgChromosomeNames);
73672
+ this.#wgChromosomeNames =
73673
+ this.#wgChromosomeNames.map(c => this.getChromosomeName(c)).filter(c => this.chromosomes.has(c));
73345
73674
  } else {
73346
73675
  this.#wgChromosomeNames = trimSmallChromosomes(this.chromosomes);
73676
+ await this.chromAlias.preload(this.#wgChromosomeNames);
73347
73677
  }
73348
- await this.chromAlias.preload(this.#wgChromosomeNames);
73349
73678
  }
73350
73679
 
73351
73680
  // Optionally create the psuedo chromosome "all" to support whole genome view
@@ -73618,7 +73947,7 @@ ${indent}columns: ${matrix.columns}
73618
73947
  function generateGenomeID(config) {
73619
73948
  if (config.id !== undefined) {
73620
73949
  return config.id
73621
- } else if (config.fastaURL && isString$2(config.fastaURL) && !config.fastaURL.startsWith("data:")) {
73950
+ } else if (config.fastaURL && isString$3(config.fastaURL) && !config.fastaURL.startsWith("data:")) {
73622
73951
  return config.fastaURL
73623
73952
  } else if (config.fastaURL && config.fastaURL.name) {
73624
73953
  return config.fastaURL.name
@@ -74351,7 +74680,7 @@ ${indent}columns: ${matrix.columns}
74351
74680
  const urlOrFile = options.url || options.file;
74352
74681
 
74353
74682
  let config;
74354
- if (options.url && isString$2(options.url) && (options.url.startsWith("blob:") || options.url.startsWith("data:"))) {
74683
+ if (options.url && isString$3(options.url) && (options.url.startsWith("blob:") || options.url.startsWith("data:"))) {
74355
74684
  const json = Browser.uncompressSession(options.url);
74356
74685
  config = JSON.parse(json);
74357
74686
  } else {
@@ -74430,7 +74759,7 @@ ${indent}columns: ${matrix.columns}
74430
74759
  return
74431
74760
  }
74432
74761
 
74433
- const genomeConfig = isString$2(genomeOrReference) ?
74762
+ const genomeConfig = isString$3(genomeOrReference) ?
74434
74763
  await GenomeUtils.expandReference(this.alert, genomeOrReference) :
74435
74764
  genomeOrReference;
74436
74765
 
@@ -74552,7 +74881,7 @@ ${indent}columns: ${matrix.columns}
74552
74881
  }
74553
74882
 
74554
74883
  /**
74555
- * Load a reference genome object. This includes the fasta, and optional cytoband, but no tracks. This method
74884
+ * Load a reference genome object. This includes the sequence, and optional cytoband, but no tracks. This method
74556
74885
  * is used by loadGenome and loadSession.
74557
74886
  *
74558
74887
  * @param genomeConfig
@@ -74585,7 +74914,10 @@ ${indent}columns: ${matrix.columns}
74585
74914
 
74586
74915
  const locusFound = await this.search(locus, true);
74587
74916
  if (!locusFound) {
74588
- throw new Error(`Cannot set initial locus ${locus}`)
74917
+ console.error(`Cannot set initial locus ${locus}`);
74918
+ if(locus !== genome.initialLocus) {
74919
+ await this.search(genome.initialLocus);
74920
+ }
74589
74921
  }
74590
74922
 
74591
74923
  if (genomeChange) {
@@ -74617,7 +74949,7 @@ ${indent}columns: ${matrix.columns}
74617
74949
 
74618
74950
  // Translate the generic "url" field, used by clients such as igv-webapp
74619
74951
  if (idOrConfig.url) {
74620
- if (isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {
74952
+ if (isString$3(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {
74621
74953
  idOrConfig.hubURL = idOrConfig.url;
74622
74954
  delete idOrConfig.url;
74623
74955
  } else if ("gbk" === getFileExtension(idOrConfig.url)) {
@@ -74627,11 +74959,11 @@ ${indent}columns: ${matrix.columns}
74627
74959
  }
74628
74960
 
74629
74961
  let genomeConfig;
74630
- const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
74962
+ const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$3(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
74631
74963
  if (isHubGenome) {
74632
74964
  const hub = await loadHub(idOrConfig.hubURL || idOrConfig.url);
74633
74965
  genomeConfig = hub.getGenomeConfig();
74634
- } else if (isString$2(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
74966
+ } else if (isString$3(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
74635
74967
  // Either an ID, a json string, or an object missing required properties.
74636
74968
  genomeConfig = await GenomeUtils.expandReference(this.alert, idOrConfig);
74637
74969
  } else {
@@ -74798,7 +75130,7 @@ ${indent}columns: ${matrix.columns}
74798
75130
  async #loadTrackHelper(config) {
74799
75131
 
74800
75132
  // config might be json
74801
- if (isString$2(config)) {
75133
+ if (isString$3(config)) {
74802
75134
  config = JSON.parse(config);
74803
75135
  }
74804
75136
 
@@ -74928,7 +75260,7 @@ ${indent}columns: ${matrix.columns}
74928
75260
 
74929
75261
  // Resolve function and promise urls
74930
75262
  let url = await resolveURL(config.url || config.fastaURL);
74931
- if (isString$2(url)) {
75263
+ if (isString$3(url)) {
74932
75264
  url = url.trim();
74933
75265
  }
74934
75266
 
@@ -74976,7 +75308,7 @@ ${indent}columns: ${matrix.columns}
74976
75308
  const featureSource = FeatureSource(config, this.genome);
74977
75309
  config._featureSource = featureSource; // This is a temp variable, bit of a hack
74978
75310
  const trackType = await featureSource.trackType();
74979
- if (trackType) {
75311
+ if (trackType && knownTrackTypes().has(trackType)) {
74980
75312
  type = trackType;
74981
75313
  } else {
74982
75314
  type = "annotation";