From 15db6584abdbeba4c8d8cd2def001831ff13a301 Mon Sep 17 00:00:00 2001 From: jrobinso <933148+jrobinso@users.noreply.github.com> Date: Fri, 25 Oct 2024 22:27:06 -0700 Subject: [PATCH] Shoebox updates --- dev/shoebox/shoebox.html | 82 +++++++++++++++++---------------- js/feature/decode/ucsc.js | 24 +--------- js/feature/featureParser.js | 2 +- js/shoebox/decodeShoebox.js | 23 +++++++++ js/shoebox/shoeboxColorScale.js | 14 ++++-- js/shoebox/shoeboxTrack.js | 55 +++++++++++++++------- js/trackBase.js | 2 +- js/ui/navbarButton.js | 2 +- 8 files changed, 118 insertions(+), 86 deletions(-) create mode 100644 js/shoebox/decodeShoebox.js diff --git a/dev/shoebox/shoebox.html b/dev/shoebox/shoebox.html index 7640860ff..ab2575ec8 100644 --- a/dev/shoebox/shoebox.html +++ b/dev/shoebox/shoebox.html @@ -23,52 +23,54 @@

Shoebox

{ sampleNameViewportWidth: 6, genome: "hg38", - locus: "chr1:1,001,575-1,002,408", + "locus": "chr11:47,376,930-47,380,077", + "roi": [ + { + "name": "ROI regions", + "url": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/BMMC_20celltype_multiscale_roi_regions.bed", + "isVisible": false + } + ], tracks: [ { - "url": "https://www.dropbox.com/scl/fi/ayu63q7raqi47yduvvte5/pseudobulk_0.bed.gz?rlkey=ci5oqo928iljje4igk4wwjoor&dl=0", - "indexURL": "https://www.dropbox.com/scl/fi/bxz53av4v0ri9snidxj8g/pseudobulk_0.bed.gz.tbi?rlkey=qvc7zk8si0ays89cqjp05fdw9&dl=0", - // visibilityWindow: 10000 - // "name": "pseudobulk 14", - // "type": "shoebox", - // "format": "shoebox" + "url": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/NK.bed.gz", + "indexURL": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/NK.bed.gz.tbi", + "name": "NK", + "format": "bed", + "type": "shoebox", + "color": "rgb(73, 26, 136)" + }, + { + "url": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/CLP.bed.gz", + "indexURL": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/CLP.bed.gz.tbi", + "name": "CLP", + "format": "bed", + "type": "shoebox", + "color": "rgb(106, 207, 255)" + }, + { + "url": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/late-Ery.bed.gz", + "indexURL": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/late-Ery.bed.gz.tbi", + "name": "late-Ery", + "format": "bed", + "type": "shoebox", + "color": "rgb(137, 17, 0)" }, { - "name": "Homo sapiens HepG2 H3K4me3 ", - "url": "https://www.encodeproject.org/files/ENCFF752OCZ/@@download/ENCFF752OCZ.bigWig", - "color": "rgb(0,150,0)", - "metadata": { - "Biosample": "Homo sapiens HepG2", - "AssayType": "ChIP-seq", - "Target": "H3K4me3 ", - "BioRep": "1", - "TechRep": "1_1", - "OutputType": "signal p-value", - "Format": "bigWig", - "Lab": "John Stamatoyannopoulos, UW", - "Accession": "ENCFF752OCZ", - "Experiment": "ENCSR000DUF" - }, - "format": "bigwig", - "type": "wig" + "url": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/CD16mono.bed.gz", + "indexURL": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/CD16mono.bed.gz.tbi", + "name": "CD16mono", + "format": "bed", + "type": "shoebox", + "color": "rgb(255, 136, 2)", }, { - "name": "Homo sapiens HepG2 CTCF ", - "url": "https://www.encodeproject.org/files/ENCFF160QOX/@@download/ENCFF160QOX.bigWig", - "metadata": { - "Biosample": "Homo sapiens HepG2", - "AssayType": "ChIP-seq", - "Target": "CTCF ", - "BioRep": "1", - "TechRep": "1_1", - "OutputType": "fold change over control", - "Format": "bigWig", - "Lab": "Richard Myers, HAIB", - "Accession": "ENCFF160QOX", - "Experiment": "ENCSR000BIE" - }, - "format": "bigwig", - "type": "wig" + "url": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/NaiveB.bed.gz", + "indexURL": "https://storage.googleapis.com/broad-buenrostro-pipeline-genome-annotations/neva/BMMC_roi/NaiveB.bed.gz.tbi", + "name": "NaiveB", + "format": "bed", + "type": "shoebox", + "color": "rgb(210, 120, 255)" } ] } diff --git a/js/feature/decode/ucsc.js b/js/feature/decode/ucsc.js index f9aea40a9..5b9c38b68 100644 --- a/js/feature/decode/ucsc.js +++ b/js/feature/decode/ucsc.js @@ -538,28 +538,6 @@ function decodeSNP(tokens, header) { } -function decodeShoebox(tokens, header, maxColumnCount = Number.MAX_SAFE_INTEGER) { - - if (tokens.length < 4) return undefined - - const chr = tokens[0] - const start = parseInt(tokens[1]) - const end = tokens.length > 2 ? parseInt(tokens[2]) : start + 1 - if (isNaN(start) || isNaN(end)) { - return new DecodeError(`Unparsable bed record.`) - } - const feature = new UCSCBedFeature({chr: chr, start: start, end: end, score: 1000}) - - const values = [] - for(let i = 3; i< tokens.length; i++) { - values.push(Number.parseInt(tokens[i])) - } - feature.values = values; - - - return feature -} - class UCSCBedFeature { @@ -673,6 +651,6 @@ class PSLFeature { export { decodeBed, decodeBedGraph, decodeGenePred, decodeGenePredExt, decodePeak, decodeReflat, decodeRepeatMasker, - decodeSNP, decodeWig, decodePSL, decodeBedmethyl, decodeGappedPeak, decodeNarrowPeak, decodeShoebox + decodeSNP, decodeWig, decodePSL, decodeBedmethyl, decodeGappedPeak, decodeNarrowPeak } diff --git a/js/feature/featureParser.js b/js/feature/featureParser.js index 5da3b655c..eff989416 100644 --- a/js/feature/featureParser.js +++ b/js/feature/featureParser.js @@ -36,7 +36,6 @@ import { decodePeak, decodeReflat, decodeRepeatMasker, - decodeShoebox, decodeSNP, decodeWig } from "./decode/ucsc.js" @@ -45,6 +44,7 @@ import {decodeFusionJuncSpan} from "./decode/fusionJuncSpan.js" import {decodeGtexGWAS} from "./decode/gtexGWAS.js" import {decodeCustom} from "./decode/custom.js" import {decodeGcnv} from "../gcnv/gcnvDecoder.js" +import decodeShoebox from "../shoebox/decodeShoebox.js" import DecodeError from "./decode/decodeError.js" import GFFHelper from "./gff/gffHelper.js" diff --git a/js/shoebox/decodeShoebox.js b/js/shoebox/decodeShoebox.js new file mode 100644 index 000000000..092471057 --- /dev/null +++ b/js/shoebox/decodeShoebox.js @@ -0,0 +1,23 @@ +import DecodeError from "../feature/decode/decodeError.js" + +export default function decodeShoebox(tokens, header, maxColumnCount = Number.MAX_SAFE_INTEGER) { + + if (tokens.length < 4) return undefined + + const chr = tokens[0] + const start = parseInt(tokens[1]) + const end = tokens.length > 2 ? parseInt(tokens[2]) : start + 1 + if (isNaN(start) || isNaN(end)) { + return new DecodeError(`Unparsable bed record.`) + } + const feature = {chr, start, end} + + const values = [] + for(let i = 3; i< tokens.length; i++) { + values.push(Number.parseFloat(tokens[i])) + } + feature.values = values; + + + return feature +} diff --git a/js/shoebox/shoeboxColorScale.js b/js/shoebox/shoeboxColorScale.js index 0c6b831a1..bd2fa779b 100644 --- a/js/shoebox/shoeboxColorScale.js +++ b/js/shoebox/shoeboxColorScale.js @@ -36,6 +36,9 @@ class ShoeboxColorScale { this.nbins = 1000 this.binsize = (this.max - this.min) / this.nbins this.updateColor(scale.color || "rgb(0,0,255)") + this.br = 255 + this.bg = 255 + this.bb = 255 } @@ -57,13 +60,16 @@ class ShoeboxColorScale { } getColor(value) { - const low = 0 - if (value < this.min) return "white" + if (value <= this.min) return "white" + else if (value >= this.max) return `rgb(${this.r},${this.g},${this.b})` const bin = Math.floor((Math.min(this.max, value) - this.min) / this.binsize) + if (undefined === this.cache[bin]) { - const alpha = (IGVMath.clamp(value, low, this.max) - low) / (this.max - low) - this.cache[bin] = `rgba(${this.r},${this.g},${this.b}, ${alpha})` + const alpha = (value - this.min) / (this.max - this.min) + const beta = 1 - alpha + this.cache[bin] = //`rgba(${this.r},${this.g},${this.b}, ${alpha})` + `rgb(${ Math.floor(alpha*this.r + beta*this.br)},${Math.floor(alpha*this.g + beta*this.bg)},${Math.floor(alpha*this.b + beta*this.bb)})` } return this.cache[bin] } diff --git a/js/shoebox/shoeboxTrack.js b/js/shoebox/shoeboxTrack.js index 89d972e6b..8f71faf57 100755 --- a/js/shoebox/shoeboxTrack.js +++ b/js/shoebox/shoeboxTrack.js @@ -4,14 +4,23 @@ import TrackBase from "../trackBase.js" import IGVGraphics from "../igv-canvas.js" import ShoeboxColorScale from "./shoeboxColorScale.js" +/** + * Configurable properties + * min, max (viewlimits) - min / max values of the color scale. Alpha is linearly varied from 100% (min) to 0% (max) + * color - base color before alpha is applied + * rowHeight - height of each row + */ class ShoeboxTrack extends TrackBase { static defaults = { height: 300, rowHeight: 3, - max: 3000, - visibilityWindow: 10000 + min: 0.5, + max: 3, + scale: 1.0, + visibilityWindow: 10000, + supportHiDPI: false } constructor(config, browser) { @@ -19,21 +28,23 @@ class ShoeboxTrack extends TrackBase { } init(config) { + super.init(config) this.type = "shoebox" - this.height = config.height || 300 - this.rowHeight = config.rowHeight || 3 - this.max = config.max || 3000 - this.colorScale = new ShoeboxColorScale({min: 0, max: 3000, color: this.color}) - - // Hardcoded -- todo get from track line + // Hardcoded -- todo, perhaps, get from track line this.sampleKeys = [] for (let i = 1; i <= 100; i++) { this.sampleKeys.push(i) } + if(config.max) { + this.dataRange = { + min: config.min || 0, + max: config.max + } + } // Create featureSource const configCopy = Object.assign({}, this.config) configCopy.format = 'shoebox' // bit of a hack @@ -47,9 +58,22 @@ class ShoeboxTrack extends TrackBase { } // Set properties from track line if (this.header) { + if(this.header.scale) { + this.header.scale = Number.parseFloat(this.header.scale) + } this.setTrackProperties(this.header) } + // Must do the following after setting track properties as they can be overriden via a track line + + // Color settings + const min = this.dataRange.min + const max = this.dataRange.max + this.colorScale = new ShoeboxColorScale({min, max, color: this.color}) + + // This shouldn't be neccessary + if(!this.scale) this.scale = 1.0 + } get color() { @@ -92,6 +116,7 @@ class ShoeboxTrack extends TrackBase { setDataRange({min, max}) { this.dataRange.min = min this.dataRange.max = max + this.colorScale.setMinMax(min, max) this.trackView.repaintViews() } @@ -115,7 +140,6 @@ class ShoeboxTrack extends TrackBase { draw({context, pixelTop, pixelWidth, pixelHeight, features, bpPerPixel, bpStart}) { - IGVGraphics.fillRect(context, 0, pixelTop, pixelWidth, pixelHeight, {'fillStyle': "rgb(255, 255, 255)"}) if (features && features.length > 0) { @@ -132,15 +156,14 @@ class ShoeboxTrack extends TrackBase { if (f.end < bpStart || f.start > bpEnd) continue // Pixel x values - const xLeft = Math.round((f.start - bpStart) / bpPerPixel) - const xRight = Math.round((f.end - bpStart) / bpPerPixel) - const w = xRight - xLeft + const xLeft = Math.floor((f.start - bpStart) / bpPerPixel) + const xRight = Math.floor((f.end - bpStart) / bpPerPixel) + const w = Math.max(1, xRight - xLeft) // Loop through value array - for (let i = f.values.length - 1; i >= 0; i--) { - const v = f.values[i] + const v = f.values[i] // / this.scale if(v >= this.dataRange.min) { @@ -153,11 +176,11 @@ class ShoeboxTrack extends TrackBase { } const color = this.colorScale.getColor(v) - context.fillStyle = color - context.fillRect(xLeft, y, w, h) + } + } } } else { diff --git a/js/trackBase.js b/js/trackBase.js index 2e649ed16..a9d8b1241 100644 --- a/js/trackBase.js +++ b/js/trackBase.js @@ -290,7 +290,7 @@ class TrackBase { } break case "viewlimits": - if (!this.config.autoscale) { // autoscale in the config has precedence + if (!this.config.autoscale && !this.config.max) { //config has precedence tokens = properties[key].split(":") let min = 0 let max diff --git a/js/ui/navbarButton.js b/js/ui/navbarButton.js index 29218b883..1cede8d12 100644 --- a/js/ui/navbarButton.js +++ b/js/ui/navbarButton.js @@ -85,7 +85,7 @@ class NavbarButton { } configureTextButton(textContent) { - console.log(`text ${this.title}`) + this.button.classList.add('igv-navbar-text-button') const tempDiv = document.createElement('div')