Skip to content

Commit

Permalink
Introduce "heatmap" as a graph type for wig tracks.
Browse files Browse the repository at this point in the history
  • Loading branch information
jrobinso committed Oct 29, 2024
1 parent d416a2d commit 9ce2735
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 35 deletions.
13 changes: 7 additions & 6 deletions js/feature/segTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ class SegTrack extends TrackBase {
case "mut":
this.colorTable = new ColorTable(MUT_COLORS)
break
case "shoebox":
if (config.colorScale) this.sbColorScale = HicColorScale.parse(config.colorScale)
break
// case "shoebox":
// if (config.colorScale) this.sbColorScale = HicColorScale.parse(config.colorScale)
// break
default:
// Color scales for "seg" (copy number) tracks.
this.posColorScale = new GradientColorScale(config.posColorScale || POS_COLOR_SCALE)
Expand Down Expand Up @@ -564,9 +564,10 @@ class SegTrack extends TrackBase {
}

// Default copy number scales
const POS_COLOR_SCALE = {low: 0.1, lowR: 255, lowG: 255, lowB: 255, high: 1.5, highR: 255, highG: 0, highB: 0}
const NEG_COLOR_SCALE = {low: -1.5, lowR: 0, lowG: 0, lowB: 255, high: -0.1, highR: 255, highG: 255, highB: 255}

const POS_COLOR_SCALE = {low: 0.1, high: 1.5, lowColor: 'rgb(255,255,255)', highColor: 'rgb(255,0,0)'}
const NEG_COLOR_SCALE = {low: -1.5, high: -0.1, lowColor: 'rgb(0,0,255)', highColor: 'rgb(255,255,255)'}
//const POS_COLOR_SCALE = {low: 0.1, lowR: 255, lowG: 255, lowB: 255, high: 1.5, highR: 255, highG: 0, highB: 0}
//const NEG_COLOR_SCALE = {low: -1.5, lowR: 0, lowG: 0, lowB: 255, high: -0.1, highR: 255, highG: 255, highB: 255}

// Mut and MAF file default color table
const MUT_COLORS = {
Expand Down
27 changes: 24 additions & 3 deletions js/feature/wigTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import paintAxis from "../util/paintAxis.js"
import {IGVColor, StringUtils} from "../../node_modules/igv-utils/src/index.js"
import $ from "../vendor/jquery-3.3.1.slim.js"
import {createCheckbox} from "../igv-icons.js"
import {ColorScaleFactory} from "../util/colorScale.js"

const DEFAULT_COLOR = 'rgb(150, 150, 150)'

Expand Down Expand Up @@ -38,7 +39,6 @@ class WigTrack extends TrackBase {
this.type = "wig"
this.featureType = 'numeric'
this.resolutionAware = true
this.paintAxis = paintAxis

const format = config.format ? config.format.toLowerCase() : config.format
if (config.featureSource) {
Expand All @@ -52,7 +52,6 @@ class WigTrack extends TrackBase {
this.featureSource = FeatureSource(config, this.browser.genome)
}


// Override autoscale default
if (config.max === undefined || config.autoscale === true) {
this.autoscale = true
Expand All @@ -62,6 +61,19 @@ class WigTrack extends TrackBase {
max: config.max
}
}

if(config.colorScale) {
this._colorScale = ColorScaleFactory.fromJson(config.colorScale)
}

// Override default height for heatmaps
if("heatmap" === config.graphType && !config.height) {
this.height = 25
}
}

get paintAxis() {
return "heatmap" === this.graphType ? undefined : paintAxis
}

async postInit() {
Expand Down Expand Up @@ -244,7 +256,16 @@ class WigTrack extends TrackBase {
IGVGraphics.fillCircle(ctx, px, pixelHeight - pointSize / 2, pointSize / 2, 3, {fillStyle: this.overflowColor})
}

} else {

} else if (this.graphType === "heatmap") {
if(!this._colorScale) {
throw Error("Graphtype heatmap requires a color scale")
}
const color = this._colorScale.getColor(f.value)
IGVGraphics.fillRect(ctx, x, 0, width, pixelHeight, {fillStyle: color})
}

else {
// Default graph type (bar)
const height = Math.min(pixelHeight, y - y0)
IGVGraphics.fillRect(ctx, x, y0, width, height, {fillStyle: color})
Expand Down
123 changes: 97 additions & 26 deletions js/util/colorScale.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
import {IGVColor} from "../../node_modules/igv-utils/src/index.js"


const ColorScaleFactory = {

fromJson: (obj) => {

switch(obj.type) {
case 'gradient':
return new GradientColorScale(obj)
case 'doubleGradient':
return new DoubleGradientScale(obj)
default:
throw Error("Unknown color scale type: " + obj)
}
}
}

/**
*
* @param cs - object containing
* 1) array of threshold values defining bin boundaries in ascending order
* 2) array of colors for bins (length == thresholds.length + 1)
* @constructor
*/
function BinnedColorScale(cs) {
this.thresholds = cs.thresholds
this.colors = cs.colors
}
class BinnedColorScale {
constructor(cs) {
this.thresholds = cs.thresholds
this.colors = cs.colors
}

BinnedColorScale.prototype.getColor = function (value) {
getColor(value) {

for (let threshold of this.thresholds) {
if (value < threshold) {
return this.colors[this.thresholds.indexOf(threshold)]
for (let threshold of this.thresholds) {
if (value < threshold) {
return this.colors[this.thresholds.indexOf(threshold)]
}
}
}

return this.colors[this.colors.length - 1]

return this.colors[this.colors.length - 1]
}
}

/**
Expand All @@ -36,28 +55,80 @@ BinnedColorScale.prototype.getColor = function (value) {
*
* @constructor
*/
function GradientColorScale(scale) {
class GradientColorScale {
constructor({low, high, lowColor, highColor}) {

this.low = low
this.high = high
this.diff = high - low
this.lowColor = lowColor
this.highColor = highColor
this.lowComponents = IGVColor.rgbComponents(this.lowColor)
this.highComponents = IGVColor.rgbComponents(this.highColor)
}

getColor(value) {

this.scale = scale
this.lowColor = "rgb(" + scale.lowR + "," + scale.lowG + "," + scale.lowB + ")"
this.highColor = "rgb(" + scale.highR + "," + scale.highG + "," + scale.highB + ")"
this.diff = scale.high - scale.low
if (value <= this.low) return this.lowColor
else if (value >= this.high) return this.highColor

const frac = (value - this.low) / this.diff
const r = Math.floor(this.lowComponents[0] + frac * (this.highComponents[0] - this.lowComponents[0]))
const g = Math.floor(this.lowComponents[1] + frac * (this.highComponents[1] - this.lowComponents[1]))
const b = Math.floor(this.lowComponents[2] + frac * (this.highComponents[2] - this.lowComponents[2]))

return "rgb(" + r + "," + g + "," + b + ")"
}

/**
* Return a simple json-like object, not a literaly json string
* @returns {{high, low, highColor, lowColor}}
*/
toJson() {
return {
low: this.low,
high: this.high,
lowColor: this.lowColor,
highColor: this.highColor
}
}

}

GradientColorScale.prototype.getColor = function (value) {
class DoubleGradientScale {

var scale = this.scale, r, g, b, frac
constructor({lowColor, midColor, highColor, low, mid, high}) {
this.mid = mid
this.midColor = midColor
this.lowGradientScale = new GradientColorScale({lowColor, highColor: midColor, low, high: mid})
this.highGradientScale = new GradientColorScale({lowColor: midColor, highColor, low: mid, high})
}

if (value <= scale.low) return this.lowColor
else if (value >= scale.high) return this.highColor
getColor(value) {
if (value < this.mid) {
return this.lowGradientScale.getColor(value)
} else if(value > this.mid) {
return this.highGradientScale.getColor(value)
} else {
return this.midColor
}
}

frac = (value - scale.low) / this.diff
r = Math.floor(scale.lowR + frac * (scale.highR - scale.lowR))
g = Math.floor(scale.lowG + frac * (scale.highG - scale.lowG))
b = Math.floor(scale.lowB + frac * (scale.highB - scale.lowB))

return "rgb(" + r + "," + g + "," + b + ")"
/**
* Return a simple json-like object, not a literaly json string
* @returns {{high, low, highColor, lowColor}}
*/
toJson() {
return {
low: this.low,
mid: this.mid,
high: this.high,
lowColor: this.lowColor,
midColor: this.midColor,
highColor: this.highColor
}
}
}

class ConstantColorScale {
Expand All @@ -71,4 +142,4 @@ class ConstantColorScale {
}


export {BinnedColorScale, GradientColorScale, ConstantColorScale}
export {BinnedColorScale, GradientColorScale, ConstantColorScale, DoubleGradientScale, ColorScaleFactory}

0 comments on commit 9ce2735

Please sign in to comment.