diff --git a/dev/ui/example.html b/dev/ui/example.html
new file mode 100644
index 000000000..88c824557
--- /dev/null
+++ b/dev/ui/example.html
@@ -0,0 +1,162 @@
+
+
+
+
+ Title
+
+
+
+
+UI classes demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/ui/table.html b/dev/ui/table.html
new file mode 100644
index 000000000..99cb5fa06
--- /dev/null
+++ b/dev/ui/table.html
@@ -0,0 +1,120 @@
+
+
+
+
+ Table example
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dev/wig/bedgraph.html b/dev/wig/bedgraph.html
index 9ef31a96a..edaf9e98a 100644
--- a/dev/wig/bedgraph.html
+++ b/dev/wig/bedgraph.html
@@ -19,6 +19,7 @@ bedgraph file
var options =
{
genome: "hg38",
+ locus: "muc1",
tracks: [
{
url: "https://s3.amazonaws.com/igv.org.test/data/bedgraph/ENCFF439GUL.bedgraph.gz",
diff --git a/js/feature/wigTrack.js b/js/feature/wigTrack.js
index 1b39cb91d..ed72a920a 100755
--- a/js/feature/wigTrack.js
+++ b/js/feature/wigTrack.js
@@ -8,6 +8,7 @@ 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"
+import ColorScaleEditor from "../ui/components/colorScaleEditor.js"
const DEFAULT_COLOR = 'rgb(150, 150, 150)'
@@ -63,28 +64,34 @@ class WigTrack extends TrackBase {
}
}
- if(config.colorScale) {
+ if (config.colorScale) {
this._colorScale = ColorScaleFactory.fromJson(config.colorScale)
}
// Override default height for heatmaps
- if("heatmap" === config.graphType && !config.height) {
+ if ("heatmap" === config.graphType && !config.height) {
this.height = 20
}
}
- get paintAxis() {
- return "heatmap" === this.graphType ? undefined : this._paintAxis
- }
-
async postInit() {
const header = await this.getHeader()
if (this.disposed) return // This track was removed during async load
if (header) this.setTrackProperties(header)
}
- supportsWholeGenome() {
- return this.visibilityWindow === undefined || this.visibilityWindow < 0
+ get supportsWholeGenome() {
+ return !this.config.indexURL && this.config.supportsWholeGenome !== false
+ }
+
+ get paintAxis() {
+ // Supply do-nothing implementation for heatmaps
+ return "heatmap" === this.graphType ? () => {
+ } : this._paintAxis
+ }
+
+ get colorScale() {
+ return this._colorScale
}
async getFeatures(chr, start, end, bpPerPixel) {
@@ -124,18 +131,30 @@ class WigTrack extends TrackBase {
menuItemList() {
const items = []
- if (this.flipAxis !== undefined) {
+ if ('heatmap' === this.graphType) {
items.push('
')
-
- function click() {
- this.flipAxis = !this.flipAxis
- this.trackView.repaintViews()
- }
-
- items.push({label: 'Flip y-axis', click})
+ items.push({
+ label: 'Set color scale', click: function () {
+ ColorScaleEditor.open(this.colorScale, this.browser.columnContainer, (colorScale) => {
+ this._colorScale = colorScale
+ this.trackView.repaintViews()
+ })
+ }
+ })
+ } else if (this.flipAxis !== undefined) {
+ items.push('
')
+ items.push({
+ label: 'Flip y-axis',
+ click: function () {
+ this.flipAxis = !this.flipAxis
+ this.trackView.repaintViews()
+ }
+ })
}
- if(this.featureSource.windowFunctions) {
+ items.push(...this.graphTypeItems())
+
+ if (this.featureSource.windowFunctions) {
items.push(...this.wigSummarizationItems())
}
@@ -165,6 +184,27 @@ class WigTrack extends TrackBase {
return menuItems
}
+ graphTypeItems() {
+
+ const graphType = ['bar', 'line', 'points', 'heatmap']
+
+ const menuItems = []
+ menuItems.push('
')
+ menuItems.push("Graph type
")
+
+ for (const gt of graphType) {
+ const object = $(createCheckbox(gt, this.graphType === gt))
+
+ function clickHandler() {
+ this.graphType = gt
+ this.trackView.updateViews()
+ }
+
+ menuItems.push({object, click: clickHandler})
+ }
+
+ return menuItems
+ }
async getHeader() {
@@ -188,11 +228,11 @@ class WigTrack extends TrackBase {
computeYPixelValueInLogScale(yValue, yScaleFactor) {
let maxValue = this.dataRange.max
- let minValue = this.dataRange.min
+ let minValue = this.dataRange.min
minValue = (minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1)
maxValue = (maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1)
-
- yValue = (yValue < 0) ? -Math.log10(Math.abs(yValue) +1) : Math.log10(yValue + 1)
+
+ yValue = (yValue < 0) ? -Math.log10(Math.abs(yValue) + 1) : Math.log10(yValue + 1)
return ((this.flipAxis ? (yValue - minValue) : (maxValue - yValue)) * yScaleFactor)
}
@@ -205,7 +245,7 @@ class WigTrack extends TrackBase {
const pixelWidth = options.pixelWidth
const pixelHeight = options.pixelHeight - 1
const bpEnd = bpStart + pixelWidth * bpPerPixel + 1
-
+
const scaleFactor = this.getScaleFactor(this.dataRange.min, this.dataRange.max, pixelHeight, this.logScale)
const yScale = (yValue) => this.logScale
? this.computeYPixelValueInLogScale(yValue, scaleFactor)
@@ -229,7 +269,7 @@ class WigTrack extends TrackBase {
if (f.start > bpEnd) break
const x = (f.start - bpStart) / bpPerPixel
- if (isNaN(x)) continue
+ if (Number.isNaN(x)) continue
let y = yScale(f.value)
@@ -259,14 +299,15 @@ class WigTrack extends TrackBase {
} else if (this.graphType === "heatmap") {
- if(!this._colorScale) {
- throw Error("Graphtype heatmap requires a color scale")
+ if (!this._colorScale) {
+ // Create a default color scale.
+ this._colorScale = this.dataRange.min < 0 && this.dataRange.max > 0 ?
+ ColorScaleFactory.defaultDivergingScale(this.dataRange.min, 0, this.dataRange.max) :
+ ColorScaleFactory.defaultGradientScale(this.dataRange.min, this.dataRange.max)
}
const color = this._colorScale.getColor(f.value)
IGVGraphics.fillRect(ctx, x, 0, width, pixelHeight, {fillStyle: color})
- }
-
- else {
+ } else {
// Default graph type (bar)
const height = Math.min(pixelHeight, y - y0)
IGVGraphics.fillRect(ctx, x, y0, width, height, {fillStyle: color})
@@ -284,7 +325,7 @@ class WigTrack extends TrackBase {
// If the track includes negative values draw a baseline
if (this.dataRange.min < 0) {
let maxValue = this.dataRange.max
- let minValue = this.dataRange.min
+ let minValue = this.dataRange.min
minValue = (this.logScale === true) ? ((minValue < 0) ? -Math.log10(Math.abs(minValue) + 1) : Math.log10(Math.abs(minValue) + 1)) : minValue
maxValue = (this.logScale === true) ? ((maxValue < 0) ? -Math.log10(Math.abs(maxValue) + 1) : Math.log10(Math.abs(maxValue) + 1)) : maxValue
const ratio = maxValue / (maxValue - minValue)
@@ -360,10 +401,6 @@ class WigTrack extends TrackBase {
}
}
- get supportsWholeGenome() {
- return !this.config.indexURL && this.config.supportsWholeGenome !== false
- }
-
/**
* Return color for feature.
* @param feature
@@ -375,6 +412,15 @@ class WigTrack extends TrackBase {
return (typeof c === "function") ? c(f.value) : c
}
+
+ getState() {
+ const state = super.getState()
+ if (this._colorScale) {
+ state.colorScale = this._colorScale.toJson()
+ }
+ return state
+ }
+
/**
* Called when the track is removed. Do any needed cleanup here
*/
@@ -440,7 +486,7 @@ function summarizeData(features, startBP, bpPerPixel, windowFunction = "mean") {
if (!currentBinData || endBin > currentBinData.bin) {
- if(currentBinData) {
+ if (currentBinData) {
finishBin(currentBinData)
}
@@ -454,7 +500,7 @@ function summarizeData(features, startBP, bpPerPixel, windowFunction = "mean") {
}
}
- if(currentBinData) {
+ if (currentBinData) {
finishBin(currentBinData)
}
diff --git a/js/shoebox/shoeboxTrack.js b/js/shoebox/shoeboxTrack.js
index b8554b988..21ba837e6 100755
--- a/js/shoebox/shoeboxTrack.js
+++ b/js/shoebox/shoeboxTrack.js
@@ -22,7 +22,8 @@ class ShoeboxTrack extends TrackBase {
scale: 1.0,
visibilityWindow: 10000,
supportHiDPI: false,
- rowStepSize: 1 // Stepsize for each row in bp for footprint radius
+ startSize: 4, // Footprint size for the first column of data (first row in track)
+ stepSize: 2 // Stepsize for each row in bp for footprint radius
}
constructor(config, browser) {
@@ -235,12 +236,12 @@ class ShoeboxTrack extends TrackBase {
'strokeStyle': "black"
}
- const max = 2 * (this.rowCount + 1) //2x for size in diameter, rather than radius
- const min = 0
+ const max = this.startSize + (this.rowCount * this.stepSize)
+ const min = this.startSize
const yScale = (max - min) / pixelHeight
- const n = 50
- for (let p = n; p <= max; p += n) {
+ const tickStep = 50
+ for (let p = tickStep; p <= max; p += tickStep) {
const yp = Math.max(10, pixelHeight - Math.round((p - min) / yScale))
IGVGraphics.strokeLine(ctx, 35, yp , 40, yp , font)
if(p > min ) {
diff --git a/js/trackBase.js b/js/trackBase.js
index a9d8b1241..deb4e0855 100644
--- a/js/trackBase.js
+++ b/js/trackBase.js
@@ -28,6 +28,7 @@ import {FeatureUtils, FileUtils, StringUtils} from "../node_modules/igv-utils/sr
import $ from "./vendor/jquery-3.3.1.slim.js"
import {createCheckbox} from "./igv-icons.js"
import {findFeatureAfterCenter} from "./feature/featureUtils.js"
+import ColorScaleEditor from "./ui/components/colorScaleEditor.js"
const DEFAULT_COLOR = 'rgb(150,150,150)'
@@ -522,50 +523,47 @@ class TrackBase {
menuItems.push('
')
- // Data range
- let object = $('')
- object.text('Set data range')
+ // Data range or color scale
- function dialogPresentationHandler() {
+ if ("heatmap" !== this.graphType && this.colorScale) {
- if (this.trackView.track.selected) {
- this.browser.dataRangeDialog.configure(this.trackView.browser.getSelectedTrackViews())
- } else {
- this.browser.dataRangeDialog.configure(this.trackView)
+ function dialogPresentationHandler() {
+
+ if (this.trackView.track.selected) {
+ this.browser.dataRangeDialog.configure(this.trackView.browser.getSelectedTrackViews())
+ } else {
+ this.browser.dataRangeDialog.configure(this.trackView)
+ }
+ this.browser.dataRangeDialog.present($(this.browser.columnContainer))
}
- this.browser.dataRangeDialog.present($(this.browser.columnContainer))
- }
- menuItems.push({object, dialog: dialogPresentationHandler})
+ menuItems.push({label: 'Set data range', dialog: dialogPresentationHandler})
- if (this.logScale !== undefined) {
+ if (this.logScale !== undefined) {
- object = $(createCheckbox("Log scale", this.logScale))
+ function logScaleHandler() {
+ this.logScale = !this.logScale
+ this.trackView.repaintViews()
+ }
- function logScaleHandler() {
- this.logScale = !this.logScale
- this.trackView.repaintViews()
+ menuItems.push({object: $(createCheckbox("Log scale", this.logScale)), click: logScaleHandler})
}
- menuItems.push({object, click: logScaleHandler})
- }
-
- object = $(createCheckbox("Autoscale", this.autoscale))
+ function autoScaleHandler() {
+ this.autoscaleGroup = undefined
+ this.autoscale = !this.autoscale
+ this.browser.updateViews()
+ }
- function autoScaleHandler() {
- this.autoscaleGroup = undefined
- this.autoscale = !this.autoscale
- this.browser.updateViews()
+ menuItems.push({object: $(createCheckbox("Autoscale", this.autoscale)), click: autoScaleHandler})
}
- menuItems.push({object, click: autoScaleHandler})
-
return menuItems
}
- setDataRange({ min, max }) {
+ setDataRange({min, max}) {
- this.dataRange = { min, max }
+ this.dataRange = {min, max}
this.autoscale = false
this.autoscaleGroup = undefined
this.trackView.repaintViews()
diff --git a/js/ui/components/colorScaleEditor.js b/js/ui/components/colorScaleEditor.js
new file mode 100644
index 000000000..e06386254
--- /dev/null
+++ b/js/ui/components/colorScaleEditor.js
@@ -0,0 +1,250 @@
+import * as DOMUtils from "../utils/dom-utils.js"
+import Picker from "../../../node_modules/vanilla-picker/dist/vanilla-picker.csp.mjs"
+import Dialog from "./dialog.js"
+import DOMPurify from "../../../node_modules/dompurify/dist/purify.es.mjs"
+import Checkbox from "./checkbox.js"
+import {DivergingGradientScale, GradientColorScale} from "../../util/colorScale.js"
+
+
+function paintLegend(legend, newColorScale) {
+
+ const ctx = legend.getContext("2d")
+ const w = legend.width
+ const step = (newColorScale.high - newColorScale.low) / w
+ for (let i = 0; i < w; i++) {
+ const v = newColorScale.low + i * step
+ const color = newColorScale.getColor(v)
+ ctx.fillStyle = color
+ ctx.fillRect(i, 0, 1, legend.height)
+ }
+}
+
+/**
+ * Editor for color scales. Supported types:
+ *
+ * 'gradient': {low, high, lowColor, highColor}
+ *
+ * 'diverging': {mid, midColor, lowGradientScale, highGradientScale}
+ *
+ *
+ */
+class ColorScaleEditor {
+
+ static open(colorScale, parent, callback) {
+
+ let newColorScale = colorScale.clone()
+
+ const table = document.createElement('table')
+
+ const legend = document.createElement('canvas')
+ legend.style.height = "20px"
+ legend.style.width = "100%"
+ legend.style.marginTop = "10px"
+ legend.style.border = "1px solid black"
+
+ const minTextbox = new TextBoxRow({
+ label: "Min value",
+ value: newColorScale.low.toString(),
+ onchange: (v) => {
+ newColorScale.low = Number.parseFloat(v)
+ paintLegend(legend, newColorScale)
+ }
+ })
+ table.append(minTextbox.row)
+
+ const midTextbox = new TextBoxRow({
+ label: "Mid value",
+ value: (newColorScale.mid || newColorScale.low).toString(),
+ onchange: (v) => {
+ newColorScale.mid = Number.parseFloat(v)
+ paintLegend(legend, newColorScale)
+ }
+ })
+ table.append(midTextbox.row)
+
+ const maxTextbox = new TextBoxRow({
+ label: "Max value",
+ value: newColorScale.high.toString(),
+ onchange: (v) => {
+ newColorScale.high = Number.parseFloat(v)
+ paintLegend(legend, newColorScale)
+ }
+ })
+ table.append(maxTextbox.row)
+
+
+ const colorElem = new ColorPickerRow({
+ label: "Min color",
+ value: newColorScale.lowColor,
+ onchange: (v) => {
+ newColorScale.lowColor = v
+ paintLegend(legend, newColorScale)
+ }
+ })
+ table.append(colorElem.row)
+
+ const midColorElem = new ColorPickerRow({
+ label: "Mid color",
+ value: newColorScale.midColor || newColorScale.lowColor,
+ onchange: (v) => {
+ newColorScale.midColor = v
+ paintLegend(legend, newColorScale)
+ }
+ })
+ table.append(midColorElem.row)
+
+ const highColorElem = new ColorPickerRow({
+ label: "Max color",
+ value: newColorScale.highColor,
+ onchange: (v) => {
+ newColorScale.maxColor = v
+ paintLegend(legend, newColorScale)
+ }
+ })
+ table.append(highColorElem.row)
+
+ const divergingCheckbox = new Checkbox({
+ selected: "diverging" === colorScale.type,
+ label: "Diverging Scale",
+ onchange: (diverging) => {
+ if (diverging) {
+ // Converting from gradient to diverting
+ newColorScale.mid = newColorScale.low < 0 && newColorScale.high > 0 ? 0 : (newColorScale.low + newColorScale.high) / 2
+ newColorScale.midColor = "rgb(255,255,255)"
+ newColorScale = new DivergingGradientScale(newColorScale)
+
+ midTextbox.value = newColorScale.mid
+ midTextbox.show()
+
+ midColorElem.value = newColorScale.midColor
+ midColorElem.show()
+
+ paintLegend(legend, newColorScale)
+ } else {
+
+ // Converting from diverging to gradient
+ newColorScale = new GradientColorScale(newColorScale)
+ midTextbox.hide()
+ midColorElem.hide()
+ paintLegend(legend, newColorScale)
+ }
+ }
+ })
+ divergingCheckbox.elem.style.marginBottom = "20px"
+
+ if('diverging' !== colorScale.type) {
+ midTextbox.hide()
+ midColorElem.hide()
+ }
+
+ const panel = document.createElement('div')
+ panel.appendChild(divergingCheckbox.elem)
+ panel.appendChild(table)
+ panel.appendChild(legend)
+
+ const okHandler = () => {
+ if (callback) {
+ callback(newColorScale)
+ }
+
+ }
+
+ const config = {
+ parent, // label: 'Multi-select',
+ content: {elem: panel}, okHandler
+ }
+ const dialog = new Dialog(config)
+ parent.append(dialog.elem)
+ DOMUtils.show(dialog.elem)
+
+ paintLegend(legend, newColorScale)
+
+ }
+
+}
+
+class LabeledButtonRow {
+ constructor({label, value, onchange}) {
+
+ this.row = document.createElement('tr')
+ const cell = document.createElement('td')
+ this.row.appendChild(cell)
+
+ const div = document.createElement('div')
+ div.innerHTML = label
+ cell.appendChild(div)
+ }
+
+ hide() {
+ this.row.style.display = 'none'
+ }
+
+ show() {
+ this.row.style.display = 'table-row'
+ }
+}
+
+class TextBoxRow extends LabeledButtonRow {
+
+ constructor({label, value, onchange}) {
+ super({label, value, onchange})
+
+ const cell2 = document.createElement('td')
+ this.row.appendChild(cell2)
+ this.input = document.createElement('input')
+
+ value = value || "0"
+ this.input.value = DOMPurify.sanitize(value)
+
+ cell2.appendChild(this.input)
+
+ if (onchange) {
+ this.input.addEventListener('change', (e) => onchange(this.input.value))
+ }
+ }
+
+ get value() {
+ return this.input.value
+ }
+
+ set value(v) {
+ this.input.value = v
+ }
+}
+
+class ColorPickerRow extends LabeledButtonRow {
+
+ constructor({label, value, onchange}) {
+ super({label, value, onchange})
+
+ const cell2 = document.createElement('td')
+ this.row.appendChild(cell2)
+ const colorButton = document.createElement('div')
+ cell2.appendChild(colorButton)
+ colorButton.style.width = "20px"
+ colorButton.style.height = "20px"
+ colorButton.style.border = "1px solid black"
+ this.colorButton = colorButton
+
+ value = value || "white"
+ colorButton.style.background = value
+
+ const picker = new Picker(colorButton)
+ picker.setOptions({
+ alpha: false, color: value
+ })
+
+ picker.onDone = (color) => {
+ colorButton.style.background = color.rgbString
+ if (onchange) {
+ onchange(color.rgbString)
+ }
+ }
+ }
+
+ set value(c) {
+ this.colorButton.style.background = c
+ }
+}
+
+export default ColorScaleEditor
diff --git a/js/ui/components/textbox.js b/js/ui/components/textbox.js
index 9de3f2f50..a5dbc1469 100644
--- a/js/ui/components/textbox.js
+++ b/js/ui/components/textbox.js
@@ -1,5 +1,5 @@
import * as DOMUtils from "../utils/dom-utils.js"
-import DOMPurify from "../../node_modules/dompurify/dist/purify.es.mjs"
+import DOMPurify from "../../../node_modules/dompurify/dist/purify.es.mjs"
class Textbox {
diff --git a/js/ui/menuUtils.js b/js/ui/menuUtils.js
index c7db4c4d6..93dabd666 100644
--- a/js/ui/menuUtils.js
+++ b/js/ui/menuUtils.js
@@ -346,7 +346,7 @@ function getTrackLabelText(track) {
}
function canShowColorPicker(track) {
- return undefined === track.type || colorPickerTrackTypeSet.has(track.type)
+ return undefined === track.type || (colorPickerTrackTypeSet.has(track.type) && 'heatmap' !== track.graphType)
}
function didSelectSingleTrackType(types) {
diff --git a/js/ui/utils/dom-utils.js b/js/ui/utils/dom-utils.js
index 1c687aeef..36f18ffce 100644
--- a/js/ui/utils/dom-utils.js
+++ b/js/ui/utils/dom-utils.js
@@ -28,11 +28,11 @@ function hide(elem) {
}
function show(elem) {
- const currentDisplay = getComputedStyle(elem).display;
- if (currentDisplay === "none") {
+ //const currentDisplay = getComputedStyle(elem).display;
+ //if (currentDisplay === "none") {
const d = elem._initialDisplay || "block";
elem.style.display = d;
- }
+ // }
}
function empty(elem) {
diff --git a/js/util/colorScale.js b/js/util/colorScale.js
index 51fe5a9a6..a8be10120 100644
--- a/js/util/colorScale.js
+++ b/js/util/colorScale.js
@@ -4,15 +4,38 @@ import {IGVColor} from "../../node_modules/igv-utils/src/index.js"
const ColorScaleFactory = {
fromJson: (obj) => {
-
- switch(obj.type) {
+ switch (obj.type) {
case 'gradient':
return new GradientColorScale(obj)
case 'doubleGradient':
- return new DoubleGradientScale(obj)
+ case 'diverging':
+ return new DivergingGradientScale(obj)
default:
throw Error("Unknown color scale type: " + obj)
}
+ },
+
+ defaultGradientScale: function (low, high) {
+
+ return new GradientColorScale({
+ "type": "doubleGradient",
+ "low": low,
+ "high": high,
+ "lowColor": "rgb(46,56,183)",
+ "highColor": "rgb(164,0,30)"
+ })
+ },
+
+ defaultDivergingScale: function (low, mid, high) {
+ return new DivergingGradientScale({
+ "type": "doubleGradient",
+ "low": 0,
+ "mid": 0.25,
+ "high": 0.5,
+ "lowColor": "rgb(46,56,183)",
+ "midColor": "white",
+ "highColor": "rgb(164,0,30)"
+ })
}
}
@@ -41,30 +64,39 @@ class BinnedColorScale {
}
}
-/**
- *
- * @param scale - object with the following properties
- * low
- * lowR
- * lowG
- * lowB
- * high
- * highR
- * highG
- * highB
- *
- * @constructor
- */
+
class GradientColorScale {
constructor({low, high, lowColor, highColor}) {
+ this.type = 'gradient'
+ this.setProperties({low, high, lowColor, highColor})
+ }
+ setProperties({low, high, lowColor, highColor}) {
+ this.type = 'gradient'
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)
+ this._lowColor = lowColor
+ this._highColor = highColor
+ this.lowComponents = IGVColor.rgbComponents(lowColor)
+ this.highComponents = IGVColor.rgbComponents(highColor)
+ }
+
+ get lowColor() {
+ return this._lowColor
+ }
+
+ set lowColor(c) {
+ this._lowColor = c
+ this.lowComponents = IGVColor.rgbComponents(c)
+ }
+
+ get highColor() {
+ return this._highColor
+ }
+
+ set highColor(c) {
+ this._highColor = c
+ this.highComponents = IGVColor.rgbComponents(c)
}
getColor(value) {
@@ -72,7 +104,7 @@ class GradientColorScale {
if (value <= this.low) return this.lowColor
else if (value >= this.high) return this.highColor
- const frac = (value - this.low) / this.diff
+ const frac = (value - this.low) / (this.high - this.low)
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]))
@@ -86,6 +118,7 @@ class GradientColorScale {
*/
toJson() {
return {
+ type: this.type,
low: this.low,
high: this.high,
lowColor: this.lowColor,
@@ -93,42 +126,113 @@ class GradientColorScale {
}
}
+ clone() {
+ return new GradientColorScale(this.toJson())
+ }
+
}
-class DoubleGradientScale {
+class DivergingGradientScale {
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})
+ this.type = 'diverging'
+ this.setProperties({lowColor, midColor, highColor, low, mid, high})
+ }
+
+ setProperties({lowColor, midColor, highColor, low, mid, high}) {
+
+ this.lowGradientScale = new GradientColorScale({
+ lowColor: lowColor,
+ highColor: midColor,
+ low: low,
+ high: mid
+ })
+ this.highGradientScale = new GradientColorScale({
+ lowColor: midColor,
+ highColor: highColor,
+ low: mid,
+ high: high
+ })
}
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
+ return this.highGradientScale.getColor(value)
}
}
+ get low() {
+ return this.lowGradientScale.low
+ }
+
+ set low(v) {
+ this.lowGradientScale.low = v
+ }
+
+ get high() {
+ return this.highGradientScale.high
+ }
+
+ set high(v) {
+ this.highGradientScale.high = v
+ }
+
+ get mid() {
+ return this.lowGradientScale.high
+ }
+
+ set mid(v) {
+ this.lowGradientScale.high = v
+ this.highGradientScale.low = v
+ }
+
+ get lowColor() {
+ return this.lowGradientScale.lowColor
+ }
+
+ set lowColor(c) {
+ this.lowGradientScale.lowColor = c
+ }
+
+ get highColor() {
+ return this.highGradientScale.highColor
+ }
+
+ set highColor(c) {
+ this.highGradientScale.highColor = c
+ }
+
+ get midColor() {
+ return this.lowGradientScale.highColor
+ }
+
+ set midColor(c) {
+ this.lowGradientScale.highColor = c
+ this.highGradientScale.lowColor = c
+ }
+
/**
* Return a simple json-like object, not a literaly json string
- * @returns {{high, low, highColor, lowColor}}
+ * @returns {{high, mid, low, highColor, midColor, lowColor}}
*/
toJson() {
return {
- low: this.low,
+ type: this.type,
+ low: this.lowGradientScale.low,
mid: this.mid,
- high: this.high,
- lowColor: this.lowColor,
+ high: this.highGradientScale.high,
+ lowColor: this.lowGradientScale.lowColor,
midColor: this.midColor,
- highColor: this.highColor
+ highColor: this.highGradientScale.highColor
}
}
+
+ clone() {
+ return new DivergingGradientScale(this.toJson())
+ }
}
class ConstantColorScale {
@@ -142,4 +246,4 @@ class ConstantColorScale {
}
-export {BinnedColorScale, GradientColorScale, ConstantColorScale, DoubleGradientScale, ColorScaleFactory}
+export {BinnedColorScale, GradientColorScale, ConstantColorScale, DivergingGradientScale, ColorScaleFactory}