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.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;
@@ -22337,7 +22342,7 @@ function overlaps(item, chrIdx1, startBase, chrIdx2, endBase) {
22337
22342
  }
22338
22343
 
22339
22344
  function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
22340
-
22345
+ //biggenepred
22341
22346
  if ("biginteract" === format || (autoSql && ('chromatinInteract' === autoSql.table || 'interact' === autoSql.table))) {
22342
22347
  return decodeInteract
22343
22348
  } else {
@@ -22384,9 +22389,20 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
22384
22389
  const extraStart = definedFieldCount;
22385
22390
  for (let i = extraStart; i < fieldCount; i++) {
22386
22391
  if (i < autoSql.fields.length) {
22392
+
22387
22393
  const name = autoSql.fields[i].name;
22388
- const value = tokens[i - 3];
22389
- feature[name] = value;
22394
+
22395
+ if (name === "exonFrames") {
22396
+ const frameOffsets = tokens[i - 3].replace(/,$/, '').split(',');
22397
+ for (let i = 0; i < feature.exons.length; i++) {
22398
+ const exon = feature.exons[i];
22399
+ const fo = parseInt(frameOffsets[i]);
22400
+ if (fo != -1) exon.readingFrame = fo;
22401
+ }
22402
+ } else {
22403
+ const value = tokens[i - 3];
22404
+ feature[name] = value;
22405
+ }
22390
22406
  }
22391
22407
  }
22392
22408
  }
@@ -22432,6 +22448,7 @@ function getDecoder(definedFieldCount, fieldCount, autoSql, format) {
22432
22448
 
22433
22449
  return feature
22434
22450
  }
22451
+
22435
22452
  }
22436
22453
 
22437
22454
  function findUTRs(exons, cdStart, cdEnd) {
@@ -23019,14 +23036,14 @@ class BWReader {
23019
23036
  // TODO -- align all feature attribute names with UCSC, an use specific column
23020
23037
  for (let key of Object.keys(f)) {
23021
23038
  const v = f[key];
23022
- if (isString$2(v) && v.toLowerCase() === term.toLowerCase()) {
23039
+ if (isString$3(v) && v.toLowerCase() === term.toLowerCase()) {
23023
23040
  return true
23024
23041
  }
23025
23042
  }
23026
23043
  return false
23027
23044
  });
23028
23045
  if (matching.length > 0) {
23029
- return matching.reduce((l, f) => (l.end - l.start) > (f.end - f.start) ? l : f, features[0])
23046
+ return matching.reduce((l, f) => (l.end - l.start) > (f.end - f.start) ? l : f, matching[0])
23030
23047
  } else {
23031
23048
  return undefined
23032
23049
  }
@@ -23317,7 +23334,7 @@ class BWReader {
23317
23334
  class ZoomLevelHeader {
23318
23335
  constructor(index, byteBuffer) {
23319
23336
  this.index = index;
23320
- this.reductionLevel = byteBuffer.getInt();
23337
+ this.reductionLevel = byteBuffer.getUInt();
23321
23338
  this.reserved = byteBuffer.getInt();
23322
23339
  this.dataOffset = byteBuffer.getLong();
23323
23340
  this.indexOffset = byteBuffer.getLong();
@@ -26920,7 +26937,7 @@ function renderAminoAcidSequence(ctx, strand, leftExon, exon, riteExon, bpStart,
26920
26937
  let aaLetter;
26921
26938
  if (undefined === aminoAcidLetter) {
26922
26939
 
26923
- if(sequenceInterval.hasSequence(start, end)) {
26940
+ if (sequenceInterval.hasSequence(start, end)) {
26924
26941
 
26925
26942
  const sequence = sequenceInterval.getSequence(start, end);
26926
26943
  if (sequence && 3 === sequence.length) {
@@ -27056,7 +27073,8 @@ function renderFeatureLabel(ctx, feature, featureX, featureX1, featureY, referen
27056
27073
  try {
27057
27074
  ctx.save();
27058
27075
 
27059
- let name = feature.name;
27076
+ const labelField = this.config.labelField ? this.config.labelField : 'name';
27077
+ let name = feature[labelField];
27060
27078
  if (name === undefined && feature.gene) name = feature.gene.name;
27061
27079
  if (name === undefined) name = feature.id || feature.ID;
27062
27080
  if (!name || name === '.') return
@@ -27089,20 +27107,13 @@ function renderFeatureLabel(ctx, feature, featureX, featureX1, featureY, referen
27089
27107
  if (options.labelAllFeatures || xleft > lastLabelX || selected) {
27090
27108
  options.rowLastLabelX[feature.row] = xright;
27091
27109
 
27092
- if ('y' === options.axis) {
27093
- // TODO -- is this ever used?
27094
- ctx.save();
27095
- IGVGraphics.labelTransformWithContext(ctx, centerX);
27096
- IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
27097
- ctx.restore();
27098
- } else {
27099
- ctx.clearRect(
27100
- centerX - textMetrics.width / 2 - 1,
27101
- labelY - textMetrics.actualBoundingBoxAscent - 1,
27102
- textMetrics.width + 2,
27103
- textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent + 2);
27104
- IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
27105
- }
27110
+ ctx.clearRect(
27111
+ centerX - textMetrics.width / 2 - 1,
27112
+ labelY - textMetrics.actualBoundingBoxAscent - 1,
27113
+ textMetrics.width + 2,
27114
+ textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent + 2);
27115
+ IGVGraphics.fillText(ctx, name, centerX, labelY, geneFontStyle, transform);
27116
+
27106
27117
  }
27107
27118
 
27108
27119
  } finally {
@@ -27516,7 +27527,11 @@ class FeatureTrack extends TrackBase {
27516
27527
  // If drawing amino acids fetch cached sequence interval. It is not needed if track does not support AA, but
27517
27528
  // costs nothing since only a reference to a cached object is fetched.
27518
27529
  if (bpPerPixel < aminoAcidSequenceRenderThreshold) {
27519
- options.sequenceInterval = this.browser.genome.getSequenceInterval(referenceFrame.chr, bpStart, bpEnd);
27530
+ // Restrict the range requested to the limits: 1-chromosome.bpLength
27531
+ const chromosome = this.browser.genome.getChromosome(referenceFrame.chr);
27532
+ const chromosomeEnd = chromosome.bpLength;
27533
+ options.sequenceInterval = this.browser.genome.getSequenceInterval(referenceFrame.chr,
27534
+ bpStart > 0 ? bpStart : 0, bpEnd > chromosomeEnd ? chromosomeEnd : bpEnd);
27520
27535
  }
27521
27536
 
27522
27537
 
@@ -27643,7 +27658,7 @@ class FeatureTrack extends TrackBase {
27643
27658
  fd.name &&
27644
27659
  fd.name.toLowerCase() === "name" &&
27645
27660
  fd.value &&
27646
- isString$2(fd.value) &&
27661
+ isString$3(fd.value) &&
27647
27662
  !fd.value.startsWith("<")) {
27648
27663
  const href = infoURL.replace("$$", feature.name);
27649
27664
  fd.value = `<a target=_blank href=${href}>${fd.value}</a>`;
@@ -28287,7 +28302,7 @@ class BlatTrack extends FeatureTrack {
28287
28302
  async postInit() {
28288
28303
  if(!this.featureSource) {
28289
28304
  // This will be the case when restoring from a session
28290
- const db = this.browser.genome.id; // TODO -- blat specific property
28305
+ const db = this.browser.genome.ucscID; // TODO -- blat specific property
28291
28306
  const url = this.browser.config["blatServerURL"];
28292
28307
  const features = await blat({url, userSeq: this.sequence, db});
28293
28308
  this._features = features;
@@ -28304,7 +28319,7 @@ class BlatTrack extends FeatureTrack {
28304
28319
  if (undefined === this.table) {
28305
28320
 
28306
28321
  const rows = this._features.map(f => [
28307
- f.chr,
28322
+ this.browser.genome.getChromosomeDisplayName(f.chr),
28308
28323
  (f.start + 1),
28309
28324
  f.end,
28310
28325
  f.strand,
@@ -28379,7 +28394,7 @@ async function createBlatTrack({sequence, browser, name, title}) {
28379
28394
 
28380
28395
  try {
28381
28396
 
28382
- const db = browser.genome.id; // TODO -- blat specific property
28397
+ const db = browser.genome.ucscID; // TODO -- blat specific property
28383
28398
  const url = browser.config["blatServerURL"] || defaultBlatServer;
28384
28399
  const features = await blat({url, userSeq: sequence, db});
28385
28400
 
@@ -28621,7 +28636,7 @@ class SequenceTrack {
28621
28636
  }
28622
28637
 
28623
28638
  items.push({
28624
- label: 'BLAT read sequence',
28639
+ label: 'BLAT visible sequence',
28625
28640
  click: async () => {
28626
28641
  let sequence = await this.browser.genome.getSequence(chr, start, end);
28627
28642
  if (sequence) {
@@ -30609,13 +30624,6 @@ class TrackDbHub {
30609
30624
  this.trackStanzas = trackStanzas;
30610
30625
  }
30611
30626
 
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
30627
 
30620
30628
  getSupportedTrackCount() {
30621
30629
  let count = 0;
@@ -30955,6 +30963,7 @@ function parseMetadata(metadata) {
30955
30963
 
30956
30964
  const idMappings = new Map([
30957
30965
  ["hg38", "GCF_000001405.40"],
30966
+ ["hg38_1kg", "GCF_000001405.40"],
30958
30967
  ["mm39", "GCF_000001635.27"],
30959
30968
  ["mm10", "GCF_000001635.26"],
30960
30969
  ["bosTau9", "GCF_002263795.1"],
@@ -30991,12 +31000,16 @@ class Hub {
30991
31000
  this.genomeStanzas = genomeStanzas;
30992
31001
  this.trackStanzas = trackStanzas;
30993
31002
  this.groupStanzas = groupStanzas;
31003
+ this.cytobandStanza = null;
30994
31004
  this.trackHubMap = new Map();
30995
31005
 
30996
31006
  // trackStanzas will not be null if this is a "onefile" hub
30997
31007
  if (trackStanzas) {
30998
31008
  const genomeId = genomeStanzas[0].getProperty("genome"); // Assumption here this is a single genome hub
30999
31009
  this.trackHubMap.set(genomeId, new TrackDbHub(trackStanzas, groupStanzas));
31010
+
31011
+ // Search for cytoband track. This supports a special but important case -- Genark assembly hubs
31012
+ this.cytobandStanza = this.trackStanzas.find(t => t.name === "cytoBandIdeo" && t.hasProperty("bigDataUrl")) || null;
31000
31013
  }
31001
31014
  }
31002
31015
 
@@ -31098,6 +31111,10 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
31098
31111
  config.twoBitBptURL = genomeStanza.getProperty("twoBitBptUrl");
31099
31112
  }
31100
31113
 
31114
+ if(this.cytobandStanza){
31115
+ config.cytobandBbURL = this.cytobandStanza.getProperty("bigDataUrl");
31116
+ }
31117
+
31101
31118
  if (this.hubStanza.hasProperty("longLabel")) {
31102
31119
  config.description = this.hubStanza.getProperty("longLabel").replace("/", "\n");
31103
31120
  } else {
@@ -31257,7 +31274,6 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
31257
31274
  if (t.hasProperty("searchTrix")) {
31258
31275
  config.trixURL = t.getProperty("searchTrix");
31259
31276
  }
31260
-
31261
31277
  if (t.hasProperty("group")) {
31262
31278
  config._group = t.getProperty("group");
31263
31279
  if (this.groupPriorityMap && this.groupPriorityMap.has(config._group)) {
@@ -31266,6 +31282,12 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1
31266
31282
  this.groupPriorityMap.set(config._group, nextPriority);
31267
31283
  }
31268
31284
  }
31285
+ const labelFields = t.hasProperty("defaultLabelFields") ?
31286
+ t.getProperty("defaultLabelFields") :
31287
+ t.getProperty("labelFields");
31288
+ if (labelFields) {
31289
+ config.labelField = labelFields.split(",")[0];
31290
+ }
31269
31291
 
31270
31292
  return config
31271
31293
  }
@@ -31406,9 +31428,9 @@ async function loadStanzas(url) {
31406
31428
  }
31407
31429
 
31408
31430
 
31409
- const i = line.indexOf(' ');
31410
- const key = line.substring(0, i).trim();
31411
- let value = line.substring(i + 1).trim();
31431
+ const index = line.indexOf(' ');
31432
+ const key = line.substring(0, index).trim();
31433
+ let value = line.substring(index + 1).trim();
31412
31434
 
31413
31435
  if (key === "type") {
31414
31436
  // The "type" property contains format and sometimes other information. For example, data range
@@ -31491,8 +31513,8 @@ function getHost(url) {
31491
31513
  return host
31492
31514
  }
31493
31515
 
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";
31516
+ const DEFAULT_GENOMES_URL = "https://igv.org/genomes/genomes3.json";
31517
+ const BACKUP_GENOMES_URL = "https://raw.githubusercontent.com/igvteam/igv-data/refs/heads/main/genomes/web/genomes.json";
31496
31518
 
31497
31519
  const GenomeUtils = {
31498
31520
 
@@ -31517,7 +31539,7 @@ const GenomeUtils = {
31517
31539
  } catch (error) {
31518
31540
  try {
31519
31541
  console.error("Error initializing default genomes:", error);
31520
- const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 2000});
31542
+ const jsonArray = await igvxhr.loadJson(BACKUP_GENOMES_URL, {timeout: 10000});
31521
31543
  processJson(jsonArray, table);
31522
31544
  } catch (e) {
31523
31545
  console.error("Error initializing backup genomes:", error);
@@ -31547,7 +31569,7 @@ const GenomeUtils = {
31547
31569
  expandReference: async function (alert, idOrConfig) {
31548
31570
 
31549
31571
  // idOrConfig might be a json string? I'm actually not sure how this arises.
31550
- if (isString$2(idOrConfig) && idOrConfig.startsWith("{")) {
31572
+ if (isString$3(idOrConfig) && idOrConfig.startsWith("{")) {
31551
31573
  try {
31552
31574
  idOrConfig = JSON.parse(idOrConfig);
31553
31575
  } catch (e) {
@@ -31556,7 +31578,7 @@ const GenomeUtils = {
31556
31578
  }
31557
31579
 
31558
31580
  let genomeID;
31559
- if (isString$2(idOrConfig)) {
31581
+ if (isString$3(idOrConfig)) {
31560
31582
  genomeID = idOrConfig;
31561
31583
  } else if (idOrConfig.genome) {
31562
31584
  genomeID = idOrConfig.genome;
@@ -32502,7 +32524,7 @@ class TrackViewport extends Viewport {
32502
32524
  let track = this.trackView.track;
32503
32525
  const dataList = track.popupData(clickState);
32504
32526
 
32505
- const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList]);
32527
+ const popupClickHandlerResult = this.browser.fireEvent('trackclick', [track, dataList, clickState.genomicLocation]);
32506
32528
 
32507
32529
  let content;
32508
32530
  if (undefined === popupClickHandlerResult || true === popupClickHandlerResult) {
@@ -40992,6 +41014,9 @@ function doSortByAttributes(sampleInfo, sampleKeys) {
40992
41014
  return true
40993
41015
  }
40994
41016
 
41017
+ /**
41018
+ * Track for segmented copy number, mut, maf and shoebox files.
41019
+ */
40995
41020
  class SegTrack extends TrackBase {
40996
41021
 
40997
41022
  #sortDirections = new Map()
@@ -41139,7 +41164,10 @@ class SegTrack extends TrackBase {
41139
41164
  }, e);
41140
41165
  }
41141
41166
 
41142
- menuItems.push({ element: createElementWithString('<div>Set color scale threshold</div>'), dialog: dialogPresentationHandler});
41167
+ menuItems.push({
41168
+ element: createElementWithString('<div>Set color scale threshold</div>'),
41169
+ dialog: dialogPresentationHandler
41170
+ });
41143
41171
  }
41144
41172
 
41145
41173
  menuItems.push('<hr/>');
@@ -41170,12 +41198,75 @@ class SegTrack extends TrackBase {
41170
41198
 
41171
41199
  getSamples() {
41172
41200
  return {
41173
- names: this.sampleKeys,
41201
+ names: this.filteredSampleKeys,
41174
41202
  height: this.sampleHeight,
41175
41203
  yOffset: 0
41176
41204
  }
41177
41205
  }
41178
41206
 
41207
+ /**
41208
+ * Set the sample filter object. This is used to filter samples from the set based on values over a specified
41209
+ * genomic region. The values compared depend on the track data type:
41210
+ * - "seg" and "shoebox" -- average value over the region
41211
+ * - "mut" and "maf" -- count of features overlapping the region
41212
+ *
41213
+ * The method is asynchronous because it may need to fetch data from the server to compute the scores.
41214
+ * Computed scores are stored and used to filter the sample keys on demand.
41215
+ *
41216
+ * @param filterObject
41217
+ * @returns {Promise<void>}
41218
+ */
41219
+ async setSampleFilter(filterObject) {
41220
+ if (!filterObject) {
41221
+ this.config.filterObject = undefined;
41222
+ this.filterObject = undefined;
41223
+ this.trackView.repaintViews();
41224
+ } else {
41225
+ const filterObjectCopy = Object.assign({}, filterObject);
41226
+ this.config.filterObject = filterObjectCopy;
41227
+
41228
+ filterObject.scores = await this.computeRegionScores(filterObject);
41229
+ this.filterObject = filterObject;
41230
+ this.trackView.checkContentHeight();
41231
+ this.trackView.repaintViews();
41232
+ }
41233
+ // TODO - store filter object in session
41234
+ }
41235
+
41236
+
41237
+ /**
41238
+ * Filter function for sample keys.
41239
+ *
41240
+ * @param sampleKey
41241
+ * @returns {boolean}
41242
+ */
41243
+ filter(sampleKey) {
41244
+ if (this.filterObject) {
41245
+ const filterObject = this.filterObject;
41246
+ const scores = filterObject.scores;
41247
+ const score = scores[sampleKey];
41248
+
41249
+ if (this.type === 'seg') {
41250
+ if (filterObject.op === '>') {
41251
+ return score > filterObject.value
41252
+ } else if (filterObject.op === '<') {
41253
+ return score < filterObject.value
41254
+ }
41255
+ } else if (this.type === 'mut' || this.type === 'maf') {
41256
+ return 'HAS' === filterObject.op ? score : !score
41257
+ }
41258
+ }
41259
+ // else if (this.config.sampleFilter) {
41260
+ // return this.config.sampleFilter(sampleKey)
41261
+ // }
41262
+ return true
41263
+ }
41264
+
41265
+ get filteredSampleKeys() {
41266
+ return this.sampleKeys.filter(sampleKey => this.filter(sampleKey))
41267
+ }
41268
+
41269
+
41179
41270
  async getFeatures(chr, start, end) {
41180
41271
  const features = await this.featureSource.getFeatures({chr, start, end});
41181
41272
  // New segments could conceivably add new samples
@@ -41183,8 +41274,10 @@ class SegTrack extends TrackBase {
41183
41274
 
41184
41275
  if (this.initialSort) {
41185
41276
  const sort = this.initialSort;
41277
+
41186
41278
  if (sort.option === undefined || sort.option.toUpperCase() === "VALUE") {
41187
- this.sortByValue(sort, features);
41279
+ const sortFeatures = (sort.chr === chr && sort.start >= start && sort.end <= end) ? features : undefined;
41280
+ this.sortByValue(sort, sortFeatures);
41188
41281
  } else if ("ATTRIBUTE" === sort.option.toUpperCase() && sort.attribute) {
41189
41282
  const sortDirection = "DESC" === sort.direction ? 1 : -1;
41190
41283
  this.sortByAttribute(sort.attribute, sortDirection);
@@ -41199,6 +41292,7 @@ class SegTrack extends TrackBase {
41199
41292
 
41200
41293
  IGVGraphics.fillRect(context, 0, pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"});
41201
41294
 
41295
+
41202
41296
  if (features && features.length > 0) {
41203
41297
 
41204
41298
  this.checkForLog(features);
@@ -41210,14 +41304,15 @@ class SegTrack extends TrackBase {
41210
41304
 
41211
41305
  // Create a map for fast id -> row lookup
41212
41306
  const samples = {};
41213
- this.sampleKeys.forEach(function (id, index) {
41307
+ const filteredKeys = this.filteredSampleKeys;
41308
+ filteredKeys.forEach(function (id, index) {
41214
41309
  samples[id] = index;
41215
41310
  });
41216
41311
 
41217
41312
  let border;
41218
41313
  switch (this.displayMode) {
41219
41314
  case "FILL":
41220
- this.sampleHeight = pixelHeight / this.sampleKeys.length;
41315
+ this.sampleHeight = pixelHeight / filteredKeys.length;
41221
41316
  border = 0;
41222
41317
  break
41223
41318
 
@@ -41355,7 +41450,7 @@ class SegTrack extends TrackBase {
41355
41450
  if (!features) return 0
41356
41451
  const sampleHeight = ("SQUISHED" === this.displayMode) ? this.squishedRowHeight : this.expandedRowHeight;
41357
41452
  this.updateSampleKeys(features);
41358
- return this.sampleKeys.length * sampleHeight
41453
+ return this.filteredSampleKeys.length * sampleHeight
41359
41454
  }
41360
41455
 
41361
41456
  /**
@@ -41364,16 +41459,38 @@ class SegTrack extends TrackBase {
41364
41459
  async sortByValue(sort, featureList) {
41365
41460
 
41366
41461
  const chr = sort.chr;
41462
+ const start = sort.position !== undefined ? sort.position - 1 : sort.start;
41463
+ const end = sort.end === undefined ? start + 1 : sort.end;
41464
+ const scores = await this.computeRegionScores({chr, start, end}, featureList);
41465
+ const d2 = (sort.direction === "ASC" ? 1 : -1);
41466
+
41467
+ this.sampleKeys.sort(function (a, b) {
41468
+ let s1 = scores[a];
41469
+ let s2 = scores[b];
41470
+ if (!s1) s1 = d2 * Number.MAX_VALUE;
41471
+ if (!s2) s2 = d2 * Number.MAX_VALUE;
41472
+ if (s1 === s2) return 0
41473
+ else if (s1 > s2) return d2
41474
+ else return d2 * -1
41475
+ });
41476
+
41477
+ this.config.sort = sort;
41478
+ this.trackView.repaintViews();
41479
+ }
41480
+
41481
+
41482
+ async computeRegionScores(filterObject, featureList) {
41483
+
41484
+ const chr = filterObject.chr;
41367
41485
  let start, end;
41368
- if (sort.position) {
41369
- start = sort.position - 1;
41486
+ if (filterObject.position) {
41487
+ start = filterObject.position - 1;
41370
41488
  end = start + 1;
41371
41489
  } else {
41372
- start = sort.start;
41373
- end = sort.end;
41490
+ start = filterObject.start;
41491
+ end = filterObject.end;
41374
41492
  }
41375
41493
 
41376
-
41377
41494
  if (!featureList) {
41378
41495
  featureList = await this.featureSource.getFeatures({chr, start, end});
41379
41496
  }
@@ -41382,62 +41499,40 @@ class SegTrack extends TrackBase {
41382
41499
  this.updateSampleKeys(featureList);
41383
41500
 
41384
41501
  const scores = {};
41385
- const d2 = (sort.direction === "ASC" ? 1 : -1);
41502
+ const bpLength = end - start + 1;
41503
+
41504
+ const mutationTypes = filterObject.value ? new Set(filterObject.value) : undefined;
41505
+
41506
+ for (let segment of featureList) {
41507
+ if (segment.end < start) continue
41508
+ if (segment.start > end) break
41509
+ const sampleKey = segment.sampleKey || segment.sample;
41510
+
41511
+ if ("mut" === this.type) {
41512
+ if (mutationTypes) {
41513
+ const mutationType = segment.getAttribute("Variant_Classification");
41514
+ if (mutationTypes.has(mutationType)) {
41515
+ // Just count features overlapping region per sample
41516
+ scores[sampleKey] = (scores[sampleKey] || 0) + 1;
41517
+ }
41518
+ } else {
41519
+ // Just count features overlapping region per sample
41520
+ scores[sampleKey] = (scores[sampleKey] || 0) + 1;
41521
+ }
41522
+ } else {
41386
41523
 
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
41524
  const min = Math.max(start, segment.start);
41394
41525
  const max = Math.min(end, segment.end);
41395
41526
  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();
41527
+ scores[sampleKey] = (scores[sampleKey] || 0) + f * segment.value;
41528
+ }
41435
41529
  }
41436
41530
 
41437
- this.trackView.repaintViews();
41531
+ return scores
41438
41532
 
41439
41533
  }
41440
41534
 
41535
+
41441
41536
  sortByAttribute(attribute, sortDirection) {
41442
41537
 
41443
41538
  this.sampleKeys = this.browser.sampleInfo.getSortedSampleKeysByAttribute(this.sampleKeys, attribute, sortDirection);
@@ -41506,7 +41601,7 @@ class SegTrack extends TrackBase {
41506
41601
  const dirLabel = direction === "DESC" ? "descending" : "ascending";
41507
41602
  const sortLabel = this.type === 'seg' || this.type === 'shoebox' ?
41508
41603
  `Sort by value (${dirLabel})` :
41509
- `Sort by type (${dirLabel})`;
41604
+ `Sort by count (${dirLabel})`;
41510
41605
  return {
41511
41606
  label: sortLabel,
41512
41607
  click: () => {
@@ -41518,7 +41613,6 @@ class SegTrack extends TrackBase {
41518
41613
  end: Math.floor(genomicLocation + bpWidth)
41519
41614
  };
41520
41615
  sortHandler(sort);
41521
- this.config.sort = sort;
41522
41616
  }
41523
41617
  }
41524
41618
  })
@@ -41688,13 +41782,13 @@ class PairedAlignment {
41688
41782
  }
41689
41783
  }
41690
41784
 
41691
- popupData(genomicLocation) {
41785
+ popupData(genomicLocation, hiddenTags, showTags) {
41692
41786
 
41693
- let nameValues = this.firstAlignment.popupData(genomicLocation);
41787
+ let nameValues = this.firstAlignment.popupData(genomicLocation, hiddenTags, showTags);
41694
41788
 
41695
41789
  if (this.secondAlignment) {
41696
41790
  nameValues.push("-------------------------------");
41697
- nameValues = nameValues.concat(this.secondAlignment.popupData(genomicLocation));
41791
+ nameValues = nameValues.concat(this.secondAlignment.popupData(genomicLocation, hiddenTags, showTags));
41698
41792
  }
41699
41793
  return nameValues
41700
41794
  }
@@ -43408,7 +43502,7 @@ class BamAlignment {
43408
43502
  isNegativeStrand() {
43409
43503
  return (this.flags & READ_STRAND_FLAG$2) !== 0
43410
43504
  }
43411
-
43505
+
43412
43506
  isMateNegativeStrand() {
43413
43507
  return (this.flags & MATE_STRAND_FLAG$2) !== 0
43414
43508
  }
@@ -43456,7 +43550,7 @@ class BamAlignment {
43456
43550
  return (genomicLocation >= s && genomicLocation <= (s + l))
43457
43551
  }
43458
43552
 
43459
- popupData(genomicLocation) {
43553
+ popupData(genomicLocation, hiddenTags, showTags) {
43460
43554
 
43461
43555
  // if the user clicks on a base next to an insertion, show just the
43462
43556
  // inserted bases in a popup (like in desktop IGV).
@@ -43539,15 +43633,20 @@ class BamAlignment {
43539
43633
  }
43540
43634
  }
43541
43635
 
43542
- const hiddenTags = new Set(['SA', 'MD']);
43543
43636
  nameValues.push('<hr/>');
43544
43637
  for (let key in tagDict) {
43545
- if (!hiddenTags.has(key)) {
43638
+ if (showTags?.has(key)) {
43639
+ nameValues.push({name: key, value: tagDict[key]});
43640
+ } else if (showTags) {
43641
+ hiddenTags.add(key);
43642
+ } else if (!hiddenTags.has(key)) {
43546
43643
  nameValues.push({name: key, value: tagDict[key]});
43547
43644
  }
43548
43645
  }
43549
43646
 
43550
- nameValues.push({name: 'Hidden Tags', value: 'SA, MD'});
43647
+ if (hiddenTags && hiddenTags.size > 0) {
43648
+ nameValues.push({name: 'Hidden Tags', value: Array.from(hiddenTags).join(", ")});
43649
+ }
43551
43650
 
43552
43651
  nameValues.push('<hr/>');
43553
43652
  nameValues.push({name: 'Genomic Location: ', value: numberFormatter$1(1 + genomicLocation)});
@@ -43555,18 +43654,18 @@ class BamAlignment {
43555
43654
  nameValues.push({name: 'Base Quality:', value: this.readBaseQualityAt(genomicLocation)});
43556
43655
 
43557
43656
  const bmSets = this.getBaseModificationSets();
43558
- if(bmSets) {
43657
+ if (bmSets) {
43559
43658
  const i = this.positionToReadIndex(genomicLocation);
43560
- if(undefined !== i) {
43659
+ if (undefined !== i) {
43561
43660
  let found = false;
43562
43661
  for (let bmSet of bmSets) {
43563
43662
  if (bmSet.containsPosition(i)) {
43564
- if(!found) {
43663
+ if (!found) {
43565
43664
  nameValues.push('<hr/>');
43566
43665
  nameValues.push('<b>Base modifications:</b>');
43567
43666
  found = true;
43568
43667
  }
43569
- const lh = Math.round((100/255) * byteToUnsignedInt(bmSet.likelihoods.get(i)));
43668
+ const lh = Math.round((100 / 255) * byteToUnsignedInt(bmSet.likelihoods.get(i)));
43570
43669
  nameValues.push(`${bmSet.fullName()} @ likelihood = ${lh}%`);
43571
43670
  }
43572
43671
  }
@@ -43656,7 +43755,7 @@ class BamAlignment {
43656
43755
  const mm = this.tagDict["MM"] || this.tagDict["Mm"];
43657
43756
  const ml = this.tagDict["ML"] || this.tagDict["Ml"];
43658
43757
 
43659
- if (isString$2(mm) && (!ml || Array.isArray(ml))) { // minimal validation, 10X uses these reserved tags for something completely different
43758
+ if (isString$3(mm) && (!ml || Array.isArray(ml))) { // minimal validation, 10X uses these reserved tags for something completely different
43660
43759
  if (mm.length === 0) {
43661
43760
  this.baseModificationSets = EMPTY_SET;
43662
43761
  } else {
@@ -43669,7 +43768,7 @@ class BamAlignment {
43669
43768
  return this.baseModificationSets
43670
43769
  }
43671
43770
 
43672
- getGroupValue( groupBy, tag, expectedPairOrientation) {
43771
+ getGroupValue(groupBy, tag, expectedPairOrientation) {
43673
43772
 
43674
43773
  const al = this;
43675
43774
  switch (groupBy) {
@@ -43706,7 +43805,7 @@ class BamAlignment {
43706
43805
  }
43707
43806
  }
43708
43807
 
43709
- positionToReadIndex( position) {
43808
+ positionToReadIndex(position) {
43710
43809
  const block = blockAtGenomicLocation(this.blocks, position);
43711
43810
  if (block) {
43712
43811
  return (position - block.start) + block.seqOffset
@@ -45685,7 +45784,7 @@ function inferFileFormatFromName(fn) {
45685
45784
 
45686
45785
  function inferIndexPath(url, extension) {
45687
45786
 
45688
- if (isString$2(url)) {
45787
+ if (isString$3(url)) {
45689
45788
  if (url.includes("?")) {
45690
45789
  const idx = url.indexOf("?");
45691
45790
  return url.substring(0, idx) + "." + extension + url.substring(idx)
@@ -45844,7 +45943,7 @@ class BamSource {
45844
45943
  this.bamReader = new CramReader(config, genome, browser);
45845
45944
  } else {
45846
45945
  if (!this.config.indexURL && config.indexed !== false) {
45847
- if (isString$2(this.config.url)) {
45946
+ if (isString$3(this.config.url)) {
45848
45947
  const indexPath = inferIndexPath(this.config.url, "bai");
45849
45948
  if (indexPath) {
45850
45949
  console.warn(`Warning: no indexURL specified for ${this.config.url}. Guessing ${indexPath}`);
@@ -48156,7 +48255,7 @@ class AlignmentTrack extends TrackBase {
48156
48255
  highlightColor: undefined,
48157
48256
  minTLEN: undefined,
48158
48257
  maxTLEN: undefined,
48159
- tagColorPallete: "Set1"
48258
+ tagColorPallete: "Set1",
48160
48259
  }
48161
48260
 
48162
48261
  _colorTables = new Map()
@@ -48171,6 +48270,18 @@ class AlignmentTrack extends TrackBase {
48171
48270
  this.colorTable = new ColorTable(config.tagColorTable);
48172
48271
  }
48173
48272
 
48273
+ // Only one of showTags / hideTags should be specified. If both are specified showTags takes precedence.
48274
+ if (config.showTags && config.hideTags) {
48275
+ console.warn("Both showTags and hideTags specified. showTags will be used.");
48276
+ }
48277
+ if (config.showTags) {
48278
+ this.showTags = new Set(config.showTags);
48279
+ this.hiddenTags = new Set();
48280
+ } else {
48281
+ this.hiddenTags = new Set(config.hideTags || ["SA", "MD"]);
48282
+ }
48283
+
48284
+
48174
48285
  // Backward compatibility overrides
48175
48286
  if (config.largeFragmentLengthColor) this.largeTLENColor = config.largeFragmentLengthColor;
48176
48287
  if (config.pairOrienation) this.expectedPairOrientation = config.pairOrientation;
@@ -48731,7 +48842,7 @@ class AlignmentTrack extends TrackBase {
48731
48842
 
48732
48843
  popupData(clickState) {
48733
48844
  const clickedObject = this.getClickedObject(clickState);
48734
- return clickedObject ? clickedObject.popupData(clickState.genomicLocation) : undefined
48845
+ return clickedObject?.popupData(clickState.genomicLocation, this.hiddenTags, this.showTags)
48735
48846
  };
48736
48847
 
48737
48848
  /**
@@ -48762,7 +48873,10 @@ class AlignmentTrack extends TrackBase {
48762
48873
  colorByMenuItems.push({key: 'tlen', label: 'insert size (TLEN)'});
48763
48874
  colorByMenuItems.push({key: 'unexpectedPair', label: 'pair orientation & insert size (TLEN)'});
48764
48875
  }
48765
- colorByMenuItems.push({key: 'tag', label: 'tag'});
48876
+ if(this.colorBy && this.colorBy.startsWith("tag:")) {
48877
+ colorByMenuItems.push({key: this.colorBy, label: this.colorBy});
48878
+ }
48879
+ colorByMenuItems.push({key: 'tag', label: 'tag...'});
48766
48880
  for (let item of colorByMenuItems) {
48767
48881
  const selected = (this.colorBy === undefined && item.key === 'none') || this.colorBy === item.key;
48768
48882
  menuItems.push(this.colorByCB(item, selected));
@@ -49278,7 +49392,7 @@ class AlignmentTrack extends TrackBase {
49278
49392
 
49279
49393
  if (seqstring.length < maxSequenceSize$1) {
49280
49394
  list.push({
49281
- label: 'BLAT read sequence',
49395
+ label: 'BLAT visible sequence',
49282
49396
  click: () => {
49283
49397
  const sequence = clickedAlignment.isNegativeStrand() ? reverseComplementSequence(seqstring) : seqstring;
49284
49398
  const name = `${clickedAlignment.readName} - blat`;
@@ -49488,14 +49602,19 @@ class AlignmentTrack extends TrackBase {
49488
49602
  case "tag":
49489
49603
  const tagValue = alignment.tags()[tag];
49490
49604
  if (tagValue !== undefined) {
49491
- if (this.bamColorTag === tag) {
49605
+
49606
+ // If the tag value can be interpreted as a color, use it
49607
+ if(typeof tagValue.startsWith === 'function') {
49492
49608
  color = IGVColor.createColorStringSafe(tagValue);
49493
49609
  }
49494
- if (!this.colorTable) {
49495
- this.colorTable = new PaletteColorTable(this.tagColorPallete);
49496
- }
49497
- color = this.colorTable.getColor(tagValue);
49498
49610
 
49611
+ // Tag value is not a color, use a color table
49612
+ if (!color) {
49613
+ if (!this.colorTable) {
49614
+ this.colorTable = new PaletteColorTable(this.tagColorPallete);
49615
+ }
49616
+ color = this.colorTable.getColor(tagValue);
49617
+ }
49499
49618
  }
49500
49619
  break
49501
49620
  }
@@ -65712,7 +65831,7 @@ function autoscale(chr, featureArrays) {
65712
65831
  * THE SOFTWARE.
65713
65832
  */
65714
65833
 
65715
- const isString = isString$2;
65834
+ const isString$1 = isString$3;
65716
65835
 
65717
65836
  const DEFAULT_VISIBILITY_WINDOW = 1000000;
65718
65837
  const TOP_MARGIN = 10;
@@ -65835,7 +65954,7 @@ class VariantTrack extends TrackBase {
65835
65954
  }
65836
65955
  if (undefined === this.visibilityWindow && this.config.indexed !== false) {
65837
65956
  const fn = isFile(this.config.url) ? this.config.url.name : this.config.url;
65838
- if (isString(fn) && fn.toLowerCase().includes("gnomad")) {
65957
+ if (isString$1(fn) && fn.toLowerCase().includes("gnomad")) {
65839
65958
  this.visibilityWindow = 1000; // these are known to be very dense
65840
65959
  } else if (typeof this.featureSource.defaultVisibilityWindow === 'function') {
65841
65960
  this.visibilityWindow = await this.featureSource.defaultVisibilityWindow();
@@ -69719,6 +69838,9 @@ const trackFunctions =
69719
69838
  ['image', (config, browser) => new ImageTrack(config, browser)]
69720
69839
  ]);
69721
69840
 
69841
+ function knownTrackTypes () {
69842
+ return new Set(trackFunctions.keys())
69843
+ }
69722
69844
 
69723
69845
  /**
69724
69846
  * Return a track of the given type, passing configuration and a point to the IGV "Browser" object to its constructor function*
@@ -70211,7 +70333,7 @@ function createReferenceFrameList(loci, genome, browserFlanking, minimumBases, v
70211
70333
  })
70212
70334
  }
70213
70335
 
70214
- const _version = "3.3.0";
70336
+ const _version = "3.4.1";
70215
70337
  function version() {
70216
70338
  return _version
70217
70339
  }
@@ -70262,7 +70384,7 @@ class ChromosomeSelectWidget {
70262
70384
  if (this.select.value.trim().toLowerCase() === "all" || this.select.value === "*") {
70263
70385
  if (browser.genome.wholeGenomeView) {
70264
70386
  const wgChr = browser.genome.getChromosome("all");
70265
- return {chr: "all", start: 0, end: wgChr.bpLength}
70387
+ browser.updateLoci([{chr: "all", start: 0, end: wgChr.bpLength}]);
70266
70388
  }
70267
70389
  } else {
70268
70390
  const chromosome = await browser.genome.loadChromosome(this.select.value);
@@ -72067,6 +72189,26 @@ class ROIMenu {
72067
72189
  }
72068
72190
 
72069
72191
 
72192
+ const found = this.browser.findTracks(track => typeof track.sortByValue === 'function');
72193
+ if (found.length > 0) {
72194
+ const { chr, start, end } = feature;
72195
+ items.push(
72196
+ '<hr/>',
72197
+ {
72198
+ label: 'Sort by value (ascending)',
72199
+ click: () => Promise.all(found.map(track => track.sortByValue({ option: 'VALUE', direction: 'ASC', chr, start, end })))
72200
+ });
72201
+
72202
+ items.push(
72203
+ '<hr/>',
72204
+ {
72205
+ label: 'Sort by value (descending)',
72206
+ click: () => Promise.all(found.map(track => track.sortByValue({ option: 'VALUE', direction: 'DESC', chr, start, end })))
72207
+ });
72208
+
72209
+ }
72210
+
72211
+
72070
72212
  if (roiSet.isUserDefined) {
72071
72213
  items.push(
72072
72214
  '<hr/>',
@@ -72075,7 +72217,7 @@ class ROIMenu {
72075
72217
  click: async () => {
72076
72218
  roiSet.removeFeature(feature);
72077
72219
  const userDefinedFeatures = await roiSet.getAllFeatures();
72078
-
72220
+
72079
72221
  // Delete user defined ROI Set if it is empty
72080
72222
  if (Object.keys(userDefinedFeatures).length === 0) {
72081
72223
  roiManager.deleteUserDefinedROISet();
@@ -73248,6 +73390,189 @@ class CytobandFile {
73248
73390
 
73249
73391
  }
73250
73392
 
73393
+ /**
73394
+ * Update deprecated fasta & index urls in the reference object.
73395
+ */
73396
+
73397
+ const isString = (x) => {
73398
+ return (x && typeof x === "string") || x instanceof String
73399
+ };
73400
+
73401
+ /**
73402
+ * Replaces deprecated s3 fasta URLs with twoBitURLs. The purpose here is to rescue references from saved sessions
73403
+ * that contain pointers to deprectated IGV s3 buckets. These buckets will eventually be deleted
73404
+ *
73405
+ * @param reference
73406
+ */
73407
+ function updateReference(reference) {
73408
+
73409
+ if (!(requiresUpdate(reference) && updates[reference.id])) {
73410
+ return
73411
+ }
73412
+
73413
+ const updatedReference = updates[reference.id];
73414
+ if (updatedReference) {
73415
+ delete reference.fastaURL;
73416
+ if (reference.indexURL) delete reference.indexURL;
73417
+ reference.twoBitURL = updatedReference.twoBitURL;
73418
+ if (updatedReference.twoBitBptURL) reference.twoBitBptURL = updatedReference.twoBitBptURL;
73419
+ if (updatedReference.chromSizesURL) reference.chromSizesURL = updatedReference.chromSizesURL;
73420
+ }
73421
+ }
73422
+
73423
+ function requiresUpdate(reference) {
73424
+ return isString(reference.fastaURL) &&
73425
+ (reference.fastaURL.startsWith("https://igv.org") ||
73426
+ ["igv.org.genomes", "igv.broadinstitute.org", "igv.genepattern.org", "igvdata.broadinstitute.org",
73427
+ "igv-genepattern-org"].some(bucket => reference.fastaURL.includes(bucket)))
73428
+ }
73429
+
73430
+ const updates = {
73431
+ "hs1": {
73432
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.2bit",
73433
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.2bit.bpt",
73434
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.chrom.sizes.txt"
73435
+ },
73436
+ "hg38": {
73437
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.2bit",
73438
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.chrom.sizes"
73439
+ },
73440
+ "hg19": {
73441
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.2bit",
73442
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg19/bigZips/hg19.chrom.sizes"
73443
+ },
73444
+ "hg18": {
73445
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg18/bigZips/hg18.2bit",
73446
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/hg18/bigZips/hg18.chrom.sizes"
73447
+ },
73448
+ "mm39": {
73449
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm39/bigZips/mm39.2bit",
73450
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm39/bigZips/mm39.chrom.sizes"
73451
+ },
73452
+ "mm10": {
73453
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.2bit",
73454
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm10/bigZips/mm10.chrom.sizes"
73455
+ },
73456
+ "mm9": {
73457
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm9/bigZips/mm9.2bit",
73458
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/mm9/bigZips/mm9.chrom.sizes"
73459
+ },
73460
+ "rn7": {
73461
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.2bit",
73462
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.2bit.bpt",
73463
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/015/227/675/GCF_015227675.2/GCF_015227675.2.chrom.sizes.txt"
73464
+ },
73465
+ "rn6": {
73466
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.2bit",
73467
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.2bit.bpt",
73468
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/895/GCF_000001895.5/GCF_000001895.5.chrom.sizes.txt"
73469
+ },
73470
+ "gorGor6": {
73471
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor6/bigZips/gorGor6.2bit",
73472
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor6/bigZips/gorGor6.chrom.sizes"
73473
+ },
73474
+ "gorGor4": {
73475
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor4/bigZips/gorGor4.2bit",
73476
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/gorGor4/bigZips/gorGor4.chrom.sizes"
73477
+ },
73478
+ "panTro6": {
73479
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro6/bigZips/panTro6.2bit",
73480
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro6/bigZips/panTro6.chrom.sizes"
73481
+ },
73482
+ "panTro5": {
73483
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro5/bigZips/panTro5.2bit",
73484
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro5/bigZips/panTro5.chrom.sizes"
73485
+ },
73486
+ "panTro4": {
73487
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro4/bigZips/panTro4.2bit",
73488
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panTro4/bigZips/panTro4.chrom.sizes"
73489
+ },
73490
+ "macFas5": {
73491
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/macFas5/bigZips/macFas5.2bit",
73492
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/macFas5/bigZips/macFas5.chrom.sizes"
73493
+ },
73494
+ "panPan2": {
73495
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panPan2/bigZips/panPan2.2bit",
73496
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/panPan2/bigZips/panPan2.chrom.sizes"
73497
+ },
73498
+ "canFam6": {
73499
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam6/bigZips/canFam6.2bit",
73500
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam6/bigZips/canFam6.chrom.sizes"
73501
+ },
73502
+ "canFam5": {
73503
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam5/bigZips/canFam5.2bit",
73504
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam5/bigZips/canFam5.chrom.sizes"
73505
+ },
73506
+ "canFam4": {
73507
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam4/bigZips/canFam4.2bit",
73508
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam4/bigZips/canFam4.chrom.sizes"
73509
+ },
73510
+ "canFam3": {
73511
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam3/bigZips/canFam3.2bit",
73512
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/canFam3/bigZips/canFam3.chrom.sizes"
73513
+ },
73514
+ "bosTau9": {
73515
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau9/bigZips/bosTau9.2bit",
73516
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau9/bigZips/bosTau9.chrom.sizes"
73517
+ },
73518
+ "bosTau8": {
73519
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau8/bigZips/bosTau8.2bit",
73520
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/bosTau8/bigZips/bosTau8.chrom.sizes"
73521
+ },
73522
+ "susScr11": {
73523
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/susScr11/bigZips/susScr11.2bit",
73524
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/susScr11/bigZips/susScr11.chrom.sizes"
73525
+ },
73526
+ "galGal6": {
73527
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/galGal6/bigZips/galGal6.2bit",
73528
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/galGal6/bigZips/galGal6.chrom.sizes"
73529
+ },
73530
+ "danRer11": {
73531
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer11/bigZips/danRer11.2bit",
73532
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer11/bigZips/danRer11.chrom.sizes"
73533
+ },
73534
+ "danRer10": {
73535
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer10/bigZips/danRer10.2bit",
73536
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/danRer10/bigZips/danRer10.chrom.sizes"
73537
+ },
73538
+ "ce11": {
73539
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/ce11/bigZips/ce11.2bit",
73540
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/ce11/bigZips/ce11.chrom.sizes"
73541
+ },
73542
+ "dm6": {
73543
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm6/bigZips/dm6.2bit",
73544
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm6/bigZips/dm6.chrom.sizes"
73545
+ },
73546
+ "dm3": {
73547
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm3/bigZips/dm3.2bit",
73548
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/goldenPath/dm3/bigZips/dm3.chrom.sizes"
73549
+ },
73550
+ "sacCer3": {
73551
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.2bit",
73552
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.2bit.bpt",
73553
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/146/045/GCF_000146045.2/GCF_000146045.2.chrom.sizes.txt"
73554
+ },
73555
+ "GCF_000002945.1": {
73556
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.2bit",
73557
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.2bit.bpt",
73558
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/945/GCF_000002945.1/GCF_000002945.1.chrom.sizes.txt"
73559
+ },
73560
+ "GCF_009858895.2": {
73561
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.2bit",
73562
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.2bit.bpt",
73563
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/009/858/895/GCF_009858895.2/GCF_009858895.2.chrom.sizes.txt"
73564
+ },
73565
+ "tair10": {
73566
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/735/GCF_000001735.3/GCF_000001735.3.2bit",
73567
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCF/000/001/735/GCF_000001735.3/GCF_000001735.3.chrom.sizes.txt"
73568
+ },
73569
+ "GCA_000022165.1": {
73570
+ "twoBitURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.2bit",
73571
+ "twoBitBptURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.2bit.bpt",
73572
+ "chromSizesURL": "https://hgdownload.soe.ucsc.edu/hubs/GCA/000/022/165/GCA_000022165.1/GCA_000022165.1.chrom.sizes.txt"
73573
+ }
73574
+ };
73575
+
73251
73576
  const ucsdIDMap = new Map([
73252
73577
  ["1kg_ref", "hg18"],
73253
73578
  ["1kg_v37", "hg19"],
@@ -73269,6 +73594,7 @@ class Genome {
73269
73594
 
73270
73595
  static async createGenome(options, browser) {
73271
73596
 
73597
+ updateReference(options);
73272
73598
  const genome = new Genome(options, browser);
73273
73599
  await genome.init();
73274
73600
  return genome
@@ -73292,7 +73618,6 @@ class Genome {
73292
73618
  // Load sequence
73293
73619
  this.sequence = await loadSequence(config, this.browser);
73294
73620
 
73295
-
73296
73621
  // Load cytobands. This is optional but required to support the ideogram. Only needed for whole genome view
73297
73622
  if(false !== config.showIdeogram && false !== config.wholeGenomeView) {
73298
73623
  if (config.cytobandURL) {
@@ -73336,10 +73661,14 @@ class Genome {
73336
73661
  } else {
73337
73662
  this.#wgChromosomeNames = config.chromosomeOrder.split(',').map(nm => nm.trim());
73338
73663
  }
73664
+ // Trim to remove non-existent chromosomes
73665
+ await this.chromAlias.preload(this.#wgChromosomeNames);
73666
+ this.#wgChromosomeNames =
73667
+ this.#wgChromosomeNames.map(c => this.getChromosomeName(c)).filter(c => this.chromosomes.has(c));
73339
73668
  } else {
73340
73669
  this.#wgChromosomeNames = trimSmallChromosomes(this.chromosomes);
73670
+ await this.chromAlias.preload(this.#wgChromosomeNames);
73341
73671
  }
73342
- await this.chromAlias.preload(this.#wgChromosomeNames);
73343
73672
  }
73344
73673
 
73345
73674
  // Optionally create the psuedo chromosome "all" to support whole genome view
@@ -73612,7 +73941,7 @@ function trimSmallChromosomes(chromosomes) {
73612
73941
  function generateGenomeID(config) {
73613
73942
  if (config.id !== undefined) {
73614
73943
  return config.id
73615
- } else if (config.fastaURL && isString$2(config.fastaURL) && !config.fastaURL.startsWith("data:")) {
73944
+ } else if (config.fastaURL && isString$3(config.fastaURL) && !config.fastaURL.startsWith("data:")) {
73616
73945
  return config.fastaURL
73617
73946
  } else if (config.fastaURL && config.fastaURL.name) {
73618
73947
  return config.fastaURL.name
@@ -74345,7 +74674,7 @@ class Browser {
74345
74674
  const urlOrFile = options.url || options.file;
74346
74675
 
74347
74676
  let config;
74348
- if (options.url && isString$2(options.url) && (options.url.startsWith("blob:") || options.url.startsWith("data:"))) {
74677
+ if (options.url && isString$3(options.url) && (options.url.startsWith("blob:") || options.url.startsWith("data:"))) {
74349
74678
  const json = Browser.uncompressSession(options.url);
74350
74679
  config = JSON.parse(json);
74351
74680
  } else {
@@ -74424,7 +74753,7 @@ class Browser {
74424
74753
  return
74425
74754
  }
74426
74755
 
74427
- const genomeConfig = isString$2(genomeOrReference) ?
74756
+ const genomeConfig = isString$3(genomeOrReference) ?
74428
74757
  await GenomeUtils.expandReference(this.alert, genomeOrReference) :
74429
74758
  genomeOrReference;
74430
74759
 
@@ -74546,7 +74875,7 @@ class Browser {
74546
74875
  }
74547
74876
 
74548
74877
  /**
74549
- * Load a reference genome object. This includes the fasta, and optional cytoband, but no tracks. This method
74878
+ * Load a reference genome object. This includes the sequence, and optional cytoband, but no tracks. This method
74550
74879
  * is used by loadGenome and loadSession.
74551
74880
  *
74552
74881
  * @param genomeConfig
@@ -74579,7 +74908,10 @@ class Browser {
74579
74908
 
74580
74909
  const locusFound = await this.search(locus, true);
74581
74910
  if (!locusFound) {
74582
- throw new Error(`Cannot set initial locus ${locus}`)
74911
+ console.error(`Cannot set initial locus ${locus}`);
74912
+ if(locus !== genome.initialLocus) {
74913
+ await this.search(genome.initialLocus);
74914
+ }
74583
74915
  }
74584
74916
 
74585
74917
  if (genomeChange) {
@@ -74611,7 +74943,7 @@ class Browser {
74611
74943
 
74612
74944
  // Translate the generic "url" field, used by clients such as igv-webapp
74613
74945
  if (idOrConfig.url) {
74614
- if (isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {
74946
+ if (isString$3(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) {
74615
74947
  idOrConfig.hubURL = idOrConfig.url;
74616
74948
  delete idOrConfig.url;
74617
74949
  } else if ("gbk" === getFileExtension(idOrConfig.url)) {
@@ -74621,11 +74953,11 @@ class Browser {
74621
74953
  }
74622
74954
 
74623
74955
  let genomeConfig;
74624
- const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$2(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
74956
+ const isHubGenome = idOrConfig.hubURL || (idOrConfig.url && isString$3(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt"));
74625
74957
  if (isHubGenome) {
74626
74958
  const hub = await loadHub(idOrConfig.hubURL || idOrConfig.url);
74627
74959
  genomeConfig = hub.getGenomeConfig();
74628
- } else if (isString$2(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
74960
+ } else if (isString$3(idOrConfig) || !(idOrConfig.url || idOrConfig.fastaURL || idOrConfig.twoBitURL || idOrConfig.gbkURL)) {
74629
74961
  // Either an ID, a json string, or an object missing required properties.
74630
74962
  genomeConfig = await GenomeUtils.expandReference(this.alert, idOrConfig);
74631
74963
  } else {
@@ -74792,7 +75124,7 @@ class Browser {
74792
75124
  async #loadTrackHelper(config) {
74793
75125
 
74794
75126
  // config might be json
74795
- if (isString$2(config)) {
75127
+ if (isString$3(config)) {
74796
75128
  config = JSON.parse(config);
74797
75129
  }
74798
75130
 
@@ -74922,7 +75254,7 @@ class Browser {
74922
75254
 
74923
75255
  // Resolve function and promise urls
74924
75256
  let url = await resolveURL(config.url || config.fastaURL);
74925
- if (isString$2(url)) {
75257
+ if (isString$3(url)) {
74926
75258
  url = url.trim();
74927
75259
  }
74928
75260
 
@@ -74970,7 +75302,7 @@ class Browser {
74970
75302
  const featureSource = FeatureSource(config, this.genome);
74971
75303
  config._featureSource = featureSource; // This is a temp variable, bit of a hack
74972
75304
  const trackType = await featureSource.trackType();
74973
- if (trackType) {
75305
+ if (trackType && knownTrackTypes().has(trackType)) {
74974
75306
  type = trackType;
74975
75307
  } else {
74976
75308
  type = "annotation";