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')