From 86209f5cc94b7727228457b01506ad741a1211bc Mon Sep 17 00:00:00 2001 From: jrobinso <933148+jrobinso@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:26:30 -0700 Subject: [PATCH] support whole genome view and chr dropdown under some conditions --- dev/ucsc/genome.html | 3 +- js/bigwig/bwReader.js | 62 ++++++++------------------------- js/browser.js | 2 +- js/genome/chromAliasBB.js | 7 ++++ js/genome/chromAliasDefaults.js | 3 ++ js/genome/chromAliasFile.js | 4 +++ js/genome/genome.js | 24 +++++++------ js/ucsc/ucscHub.js | 39 +++++++++++++++++++-- js/ui/chromosomeSelectWidget.js | 11 ++++-- 9 files changed, 90 insertions(+), 65 deletions(-) diff --git a/dev/ucsc/genome.html b/dev/ucsc/genome.html index 9441cf697..edaff11a3 100644 --- a/dev/ucsc/genome.html +++ b/dev/ucsc/genome.html @@ -12,8 +12,9 @@ import igv from "../../js/index.js" + //https://hgdownload.soe.ucsc.edu/hubs/GCF/000/002/035/GCF_000002035.6/hub.txt const igvConfig = { - genome: "GCA_000002305.1" + genome: "GCF_000002035.6" } diff --git a/js/bigwig/bwReader.js b/js/bigwig/bwReader.js index 63e4b0f1f..9f545d58a 100644 --- a/js/bigwig/bwReader.js +++ b/js/bigwig/bwReader.js @@ -54,7 +54,8 @@ class BWReader { this.config = config this.bufferSize = BUFFER_SIZE this.loader = isDataURL(this.path) ? - new DataBuffer(this.path) : igvxhr + new DataBuffer(BGZip.decodeDataURI(this.path).buffer) : + igvxhr if (config.searchTrix) { this._trix = new Trix(`${config.searchTrix}x`, config.searchTrix) @@ -62,6 +63,15 @@ class BWReader { } + /** + * Preload all the data for this bb file + * @returns {Promise} + */ + async preload() { + const data = await igvxhr.loadArrayBuffer(this.path) + this.loader = new DataBuffer(data) + } + async readWGFeatures(bpPerPixel, windowFunction) { await this.loadHeader() const chrIdx1 = 0 @@ -393,7 +403,7 @@ class BWReader { if (header.extensionOffset > 0) { await this.loadExtendedHeader(header.extensionOffset) } - return this.header + return this.header } } @@ -680,8 +690,8 @@ function decodeZoomData(data, chrIdx1, bpStart, chrIdx2, bpEnd, featureArray, ch class DataBuffer { - constructor(dataURI) { - this.data = BGZip.decodeDataURI(dataURI).buffer + constructor(data) { + this.data = data } /** @@ -710,49 +720,5 @@ class DataBuffer { } } -class WholeFileBuffer { - - data - - constructor(path) { - this.path = path - } - - async loadFile() { - this.data = await igvxhr.loadArrayBuffer(this.path) - } - - /** - * igvxhr interface - * @param ignore - * @param options - * @returns {any} - */ - async loadArrayBuffer(ignore, options) { - if (!this.data) { - await this.loadFile() - } - const range = options.range - return range ? this.data.slice(range.start, range.start + range.size) : this.data - } - - /** - * BufferedReader interface - * - * @param requestedRange - byte rangeas {start, size} - * @param fulfill - function to receive result - * @param asUint8 - optional flag to return result as an UInt8Array - */ - async dataViewForRange(requestedRange, asUint8) { - if (!this.data) { - await this.loadFile() - } - const len = Math.min(this.data.byteLength - requestedRange.start, requestedRange.size) - return asUint8 ? - new Uint8Array(this.data, requestedRange.start, len) : - new DataView(this.data, requestedRange.start, len) - } - -} export default BWReader diff --git a/js/browser.js b/js/browser.js index e8d4c9935..9b0551d42 100755 --- a/js/browser.js +++ b/js/browser.js @@ -1579,7 +1579,7 @@ class Browser { } if (this.chromosomeSelectWidget) { - this.chromosomeSelectWidget.select.value = referenceFrameList.length === 1 ? this.referenceFrameList[0].chr : '' + this.chromosomeSelectWidget.setValue(referenceFrameList.length === 1 ? this.referenceFrameList[0].chr : '') } const loc = this.referenceFrameList.map(rf => rf.getLocusString()).join(' ') diff --git a/js/genome/chromAliasBB.js b/js/genome/chromAliasBB.js index 8d38a3e46..2b1d16b64 100644 --- a/js/genome/chromAliasBB.js +++ b/js/genome/chromAliasBB.js @@ -19,6 +19,13 @@ class ChromAliasBB { this.reader = new BWReader(config, genome) } + async preload(chrNames) { + await this.reader.preload(); + for(let nm of chrNames) { + await this.search(nm) + } + } + /** * Return the cached canonical chromosome name for the alias. If none found return the alias. * diff --git a/js/genome/chromAliasDefaults.js b/js/genome/chromAliasDefaults.js index 4fd66e0a5..4f3a57494 100644 --- a/js/genome/chromAliasDefaults.js +++ b/js/genome/chromAliasDefaults.js @@ -14,6 +14,9 @@ class ChromAliasDefaults { this.update(id, chromosomeNames) } + async preload() { + // no-op + } /** * Return the canonical chromosome name for the alias. If none found return the alias diff --git a/js/genome/chromAliasFile.js b/js/genome/chromAliasFile.js index 4a80ada14..16ffadb8d 100644 --- a/js/genome/chromAliasFile.js +++ b/js/genome/chromAliasFile.js @@ -22,6 +22,10 @@ class ChromAliasFile { this.genome = genome } + async preload() { + return this.loadAliases(); + } + /** * Return the canonical chromosome name for the alias. If none found return the alias * diff --git a/js/genome/genome.js b/js/genome/genome.js index 376570903..fa72c94ab 100644 --- a/js/genome/genome.js +++ b/js/genome/genome.js @@ -57,28 +57,28 @@ class Genome { if (config.chromAliasBbURL) { this.chromAlias = new ChromAliasBB(config.chromAliasBbURL, Object.assign({}, config), this) - if(!this.chromosomeNames) { + if (!this.chromosomeNames) { this.chromosomeNames = await this.chromAlias.getChromosomeNames() } } else if (config.aliasURL) { this.chromAlias = new ChromAliasFile(config.aliasURL, Object.assign({}, config), this) } else if (this.chromosomeNames) { - this.chromAlias = new ChromAliasDefaults(this.id, this.chromosomeNames); + this.chromAlias = new ChromAliasDefaults(this.id, this.chromosomeNames) } if (config.cytobandBbURL) { this.cytobandSource = new CytobandFileBB(config.cytobandBbURL, Object.assign({}, config), this) - if(!this.chromosomeNames) { + if (!this.chromosomeNames) { this.chromosomeNames = await this.cytobandSource.getChromosomeNames() } } else if (config.cytobandURL) { this.cytobandSource = new CytobandFile(config.cytobandURL, Object.assign({}, config)) - if(!this.chromosomeNames) { + if (!this.chromosomeNames) { this.chromosomeNames = await this.cytobandSource.getChromosomeNames() } - if(this.chromosomes.size === 0) { + if (this.chromosomes.size === 0) { const c = await this.cytobandSource.getChromosomes() - for(let chromosome of c) { + for (let chromosome of c) { this.chromosomes.set(c.name, c) } } @@ -96,6 +96,7 @@ class Genome { } else { this.#wgChromosomeNames = trimSmallChromosomes(this.chromosomes) } + await this.chromAlias.preload(this.#wgChromosomeNames) } // Optionally create the psuedo chromosome "all" to support whole genome view @@ -162,15 +163,15 @@ class Genome { async loadChromosome(chr) { if (this.chromAlias) { - const chromAliasRecord = await this.chromAlias.search(chr) - if(chromAliasRecord) { + const chromAliasRecord = await this.chromAlias.search(chr) + if (chromAliasRecord) { chr = chromAliasRecord.chr } } if (!this.chromosomes.has(chr)) { let chromosome - const sequenceRecord = await this.sequence.getSequenceRecord(chr) + const sequenceRecord = await this.sequence.getSequenceRecord(chr) if (sequenceRecord) { chromosome = new Chromosome(chr, 0, sequenceRecord.bpLength) } @@ -180,6 +181,7 @@ class Genome { return this.chromosomes.get(chr) } + async getAliasRecord(chr) { if (this.chromAlias) { return this.chromAlias.search(chr) @@ -199,7 +201,7 @@ class Genome { } get wgChromosomeNames() { - return this.#wgChromosomeNames ? this.#wgChromosomeNames.slice() : undefined + return this.#wgChromosomeNames ? this.#wgChromosomeNames.slice() : undefined } get showChromosomeWidget() { @@ -303,7 +305,7 @@ class Genome { * @param end */ getSequenceInterval(chr, start, end) { - if(typeof this.sequence.getSequenceInterval === 'function') { + if (typeof this.sequence.getSequenceInterval === 'function') { return this.sequence.getSequenceInterval(chr, start, end) } else { return undefined diff --git a/js/ucsc/ucscHub.js b/js/ucsc/ucscHub.js index c015f0fcd..c191faf99 100644 --- a/js/ucsc/ucscHub.js +++ b/js/ucsc/ucscHub.js @@ -24,6 +24,16 @@ class Hub { const groupsTxtURL = baseURL + genome.getProperty("groups") groups = await loadStanzas(groupsTxtURL) } + + // If the genome has a chromSizes file, and it is not too large, set the chromSizesURL property. This will + // enable whole genome view and the chromosome pulldown + if (genome.hasProperty("chromSizes")) { + const chromSizesURL = baseURL + genome.getProperty("chromSizes") + const l = await getContentLength(chromSizesURL) + if (l !== null && Number.parseInt(l) < 1000000) { + genome.setProperty("chromSizesURL", chromSizesURL) + } + } } // TODO -- categorize extra "user" supplied and other tracks in some distinctive way before including them @@ -126,8 +136,13 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1 name: name, twoBitURL: this.baseURL + this.genomeStanza.getProperty("twoBitPath"), nameSet: "ucsc", - wholeGenomeView: false, - showChromosomeWidget: false + } + + if (this.genomeStanza.hasProperty("chromSizesURL")) { + config.chromSizesURL = this.genomeStanza.getProperty("chromSizesURL") + } else { + config.wholeGenomeView = false + config.showChromosomeWidget = false } if (this.genomeStanza.hasProperty("defaultPos")) { @@ -419,6 +434,26 @@ class Stanza { } } + +/** + * Return the content length of the resource. If the content length cannot be determined return null; + * @param url + * @returns {Promise} + */ +async function getContentLength(url) { + try { + const response = await fetch(url, {method: 'HEAD'}) + const headers = response.headers + if (headers.has("content-length")) { + return headers.get("content-length") + } else { + return null + } + } catch (e) { + return null + } +} + /** * Parse a UCSC file * @param url diff --git a/js/ui/chromosomeSelectWidget.js b/js/ui/chromosomeSelectWidget.js index f3f900734..f8d98bb39 100644 --- a/js/ui/chromosomeSelectWidget.js +++ b/js/ui/chromosomeSelectWidget.js @@ -47,7 +47,7 @@ class ChromosomeSelectWidget { }) this.showAllChromosomes = browser.config.showAllChromosomes !== false // i.e. default to true - + this.genome = browser.genome } show() { @@ -58,10 +58,16 @@ class ChromosomeSelectWidget { this.container.style.display = 'none' } + setValue(chrName) { + this.select.value = this.genome.getChromosomeDisplayName(chrName) + } + update(genome) { + this.genome = genome + // Start with explicit chromosome name list - const list = genome.wgChromosomeNames || [] + const list = genome.wgChromosomeNames.map(nm => genome.getChromosomeDisplayName(nm)) || [] if (this.showAllChromosomes && genome.chromosomeNames.length > 1) { const exclude = new Set(list) @@ -72,6 +78,7 @@ class ChromosomeSelectWidget { break } if (!exclude.has(nm)) { + nm = genome.getChromosomeDisplayName(nm) list.push(nm) } }