Skip to content

Commit

Permalink
Custom colors for heatmap (#253)
Browse files Browse the repository at this point in the history
* Added code for Color Selction in Heat Maps

* Update osd-heatmap-overlay.js

Remove Global declaration

* Adding source code documentation

* Change Legends order and code refactoring

* Update heatmapcontrol.js

Corrected validation for legend colors.

* Added logic for gradient colors & increase interval size to 10

* Update heatmapcontrol.css

* Update components/heatmapcontrol/heatmapcontrol.js

Co-Authored-By: Ryan Birmingham <birm@rbirm.us>

* Update heatmapcontrol.js

Co-authored-by: Ryan Birmingham <birm@rbirm.us>
Co-authored-by: Nan Li <linanldj@gmail.com>
Co-authored-by: rahuldeep-attri <RahulDeep.Attri@sap.com>
  • Loading branch information
4 people authored Mar 27, 2020
1 parent e904cde commit e9c4c53
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 20 deletions.
10 changes: 6 additions & 4 deletions apps/heatmap/uicallbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,19 @@ function closeSecondaryViewer(){
function goHome(data){
redirect($D.pages.home,`GO Home Page`, 0);
}

// function to update and repaint the heatmap
function heatmapSettingChanged(data){
switch (data.mode) {
case 'binal':
data.fields.forEach( f=> {
$CAMIC.viewer.heatmap.setThresholdsByName(f.name,f.range[0],f.range[1],false);
});
data.fields.forEach( f=> {
$CAMIC.viewer.heatmap.setThresholdsByName(f.name,f.range[0],f.range[1],false);
});
if(data.color) $CAMIC.viewer.heatmap.setColor(data.color)
break;
case 'gradient':
$CAMIC.viewer.heatmap.setThresholdsByName(data.field.name,data.field.range[0],data.field.range[1],false);
$CAMIC.viewer.heatmap.setCurrentField(data.field.name,false);
if(data.colors) $CAMIC.viewer.heatmap.setColors(data.colors)
break;
default:
// statements_def
Expand Down
10 changes: 7 additions & 3 deletions components/heatmapcontrol/heatmapcontrol.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@
color: #365f9c;
}

.hmc-container > label,.hmc-container .mode-panel > label{
.hmc-container > label,.hmc-container .mode-panel > label, .color-panel > label, .colors-legend-panel > label{
font-size: 14px;
}
.mode-panel {
.mode-panel, .color-panel, .colors-legend-panel, .colors-legend-panel > .legends {
margin-top:.5rem;
margin-bottom:.5rem;
}

.color-input-container {
margin-top:.5rem;
margin-bottom:.5rem;
}
.fields-panel, .opacity-panel {
padding:10px;
}
.sel-field-panel > label, .fields-panel label, .opacity-panel label {
font-size:13px;
}
}
124 changes: 117 additions & 7 deletions components/heatmapcontrol/heatmapcontrol.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// heatmapcontrol.js
//

//Default Color List for gradient view
const defaultColorList = ["#EAF2F8", "#D4E6F1", "#A9CCE3", "#7FB3D5", "#5499C7", "#2980B9", "#2471A3", "#1F618D", "#1A5276", "#154360"];
//Regular Expression for testing valid color values
const cssHexRegExp = new RegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$');
function HeatmapControl(options){
this.name = 'HeatmapControl';
/*
Expand Down Expand Up @@ -50,25 +55,36 @@ HeatmapControl.prototype.__refresh = function(){
const template = `
<div class='mode-panel'>
<label> Gradient <input type='checkbox' value='gradient' ${this.setting.mode == 'gradient'? 'checked':''} /></label>
<label> Gradient <input type='checkbox' value='gradient' ${this.setting.mode == 'gradient'? 'checked':''} /></label>
</div>
<div class='sel-field-panel'>
<select></select>
<select></select>
</div>
<label>Properties:</label>
<label>Properties:</label>
<div class='fields-panel'>
</div>
<div style='display:none;'>
<label>Opacity:</label>
<div class='opacity-panel'>
<label>Opacity:</label>
<div class='opacity-panel'>
</div>
</div>
<div class='color-panel'>
<label> Color <input id='heatMapColor' type='color' value='#1034A6' /></label>
</div>
<div class='colors-legend-panel'>
<label># of Intervals <input id='legendIntervals' type='number' class='range-enforced' value='5' min='2' max='10'/></label> <div class="warning" style="display: none;"></div>
<div class='legends'>
</div>
</div>
`;
this.elt.innerHTML = template;
const checkbox = this.elt.querySelector('.mode-panel input[type=checkbox]');
checkbox.addEventListener('change', this._modeChanged.bind(this));
//
const color = this.elt.querySelector('.color-panel input[type=color]');
color.addEventListener('input', this._colorChanged.bind(this));

this.rangeSliders = {};
createSelect(this.elt.querySelector('.sel-field-panel select') ,this.setting.fields,this.setting.currentField);
this.elt.querySelector('.sel-field-panel select').addEventListener('change', this._selChanged.bind(this));
Expand All @@ -86,20 +102,52 @@ HeatmapControl.prototype.__refresh = function(){
this.opacitySliders[f.name] = createOpacities(opacitiesPanel,f,this.__opacityChange.bind(this));
},this);

const colorsLegendPanel = this.elt.querySelector('.colors-legend-panel');
//min max logic for valid number of intervals
$(colorsLegendPanel).find('#legendIntervals').on('change', function(e){
var min=parseFloat($(this).attr('min'));
var max=parseFloat($(this).attr('max'));
var curr=parseFloat($(this).val());
if (curr > max) { $(this).val(max); var changed=true; }
if (curr < min) { $(this).val(min); var changed=true; }
if (changed) {
$warning = $(colorsLegendPanel).find('.warning')
$warning.text('Only values in ' + min + ' through ' + max + ' allowed.');
$warning.show()
$warning.fadeOut(4500);
}
});

const legendIntervalsInput = colorsLegendPanel.querySelector("#legendIntervals");
//Setting default value of intervals
legendIntervalsInput.value = 5;
const noOfIntervals = legendIntervalsInput.value;

const legendsContainer = colorsLegendPanel.querySelector('.legends');
createIntervalInputs(legendsContainer, noOfIntervals, this._legendColorsChanged.bind(this) );
legendIntervalsInput.addEventListener('change', ()=>{
createIntervalInputs(legendsContainer, legendIntervalsInput.value, this._legendColorsChanged.bind(this))
this._legendColorsChanged();
});

this._modeChanged(false);
}

HeatmapControl.prototype._modeChanged = function(flag = true){
const mode = this.elt.querySelector(`.mode-panel input[type=checkbox]`).checked;

if(!mode){// binal
this.elt.querySelector('.color-panel').style.display='';
this.elt.querySelector('.colors-legend-panel').style.display='none';
this.elt.querySelector('.sel-field-panel').style.display='none';
this.setting.fields.forEach( f=> {
// statements
this.rangeSliders[f.name].slider.parentNode.style.display='';
this.rangeSliders[f.name].disabled(false);
},this);
}else{ // gradient
this.elt.querySelector('.color-panel').style.display='none';
this.elt.querySelector('.colors-legend-panel').style.display='';
this.elt.querySelector('.sel-field-panel').style.display='';
const selectedField = this.elt.querySelector('.sel-field-panel select').value;
this.rangeSliders[selectedField].slider.parentNode.style.display='';
Expand All @@ -115,6 +163,27 @@ HeatmapControl.prototype._modeChanged = function(flag = true){
}
if(flag)this.__change.call(this);
}
//To validate and update heatmap color in binal mode
HeatmapControl.prototype._colorChanged = function(){
const color = this.elt.querySelector("#heatMapColor").value;
if(cssHexRegExp.test(color)){
this.__change.call(this);
}
}
//To validate and update heatmap colors in gradient mode
HeatmapControl.prototype._legendColorsChanged = function(){
let valid = true;
const colorLegendPanel = this.elt.querySelector('.colors-legend-panel');
$(colorLegendPanel.querySelector('.legends'))
.children()
.each(function (index,colorDiv) {
if(cssHexRegExp.test(colorDiv.querySelector('input').value)===false){
valid = false;
return;
}
});
if(valid) this.__change.call(this);
}

HeatmapControl.prototype._selChanged = function(e){
const selectedField = this.elt.querySelector('.sel-field-panel select').value;
Expand Down Expand Up @@ -142,12 +211,19 @@ HeatmapControl.prototype.resize = function(){
}
HeatmapControl.prototype.__change = function(){
if(this.setting.onChange && typeof this.setting.onChange === 'function'){

const mode = this.elt.querySelector(`.mode-panel input[type=checkbox]`).checked;
const color = this.elt.querySelector("#heatMapColor").value
const colorLegendPanel = this.elt.querySelector('.colors-legend-panel');
const colors = getColors(colorLegendPanel.querySelector('.legends'));
const fields = [];
const field = {};
const data = {
mode:mode?'gradient':'binal'
mode:mode?'gradient':'binal',
color:color,
colors:colors
}

if(!mode){
this.setting.fields.forEach( f=> {
fields.push({name:f.name,range:this.rangeSliders[f.name].getValue()});
Expand Down Expand Up @@ -240,3 +316,37 @@ function createOpacities(container, field, changeFunc){
container.appendChild(div);
return rs;
}
//Create HTML Color inputs for given noOfIntervals
function createIntervalInputs(container, noOfIntervals, changeFunc){
//Empty the container
while ( container.firstChild ) container.removeChild( container.firstChild );
for (let i = 1; i <= noOfIntervals; i++) {

const div = document.createElement('div');
div.className = 'color-input-container';
const label = document.createElement('label');
label.textContent = `Interval ${i} `;
label.className = 'color-input'
const color = document.createElement('input');
color.type = 'color';
color.value = defaultColorList[getGradientColorIndex(i, noOfIntervals)];
color.oninput = changeFunc
//Input for color legends.
div.appendChild(label);
div.appendChild(color);

container.appendChild(div);
}
}
// returns selected colors for intervals
function getColors(container){
const rs = [];
$(container).children().each(function (index,colorDiv) {
rs.push(colorDiv.querySelector('input').value)
});
return rs;
}
// returns index of color in defaultColorList for given position and no of intervals
function getGradientColorIndex(position, noOfIntervals){
return parseInt((position * (10 / noOfIntervals)) - 1)
}
23 changes: 17 additions & 6 deletions core/extension/osd-heatmap-overlay.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//osd-heatmap-overlay.js

/**
* @constructor
* OpenSeadragon heatmap Plugin 0.0.1.
Expand Down Expand Up @@ -482,6 +483,7 @@
setColors: function(colors) {
if (!Array.isArray(colors) || colors.length < 2) return;
this._colors = colors;
this._steps = colors.length + 1;
// refresh view/heatmap/ui if the heatmap is in 'gradient' mode
if (this.mode == "gradient") {
this.drawOnCanvas();
Expand Down Expand Up @@ -805,25 +807,34 @@
//
// const colorList = interpolateColors(hexToRgb(colors[0]),hexToRgb(colors[1]),steps);

const colorList = ["#2b83ba", "#abdda4", "#ffffbf", "#fdae61", "#d7191c"];
// Default preset of colors
const defaultColorList = ["#2b83ba", "#abdda4", "#ffffbf", "#fdae61", "#d7191c"];

if(colors.length + 1 != steps){
console.log(`Number of colors ${colors.length + 1 } required for steps ${steps} are not right. Switch to default colors`)
colors = defaultColorList;
steps = defaultColorList.length + 1;
}


//const colorList = ['#f2f0f7','#cbc9e2','#9e9ac8','#756bb1','#54278f'];
//const colorList = ['#eff3ff','#bdd7e7','#6baed6','#3182bd','#08519c'];
//const colorList = ['#fee5d9','#fcae91','#fb6a4a','#de2d26','#a50f15'];
// const colorList = ['#feedde','#fdbe85','#fd8d3c','#e6550d','#a63603'];
//const colorList = ['#edf8e9','#bae4b3','#74c476','#31a354','#006d2c'];
steps = 5;
// get a boundary list of intervals
const threstholds = field.value;
const boundaries = interpolateNums(
threstholds[0],
threstholds[1],
steps + 1
steps
);
const rs = [];
for (let i = 0; i < colorList.length; i++) {
for (let i = 0; i < colors.length; i++) {
// create a new interval
rs.push({
color: colorList[i],
color: colors[i],
range: [boundaries[i], boundaries[i + 1]],
data: []
});
Expand Down Expand Up @@ -1128,7 +1139,7 @@
};
function createLegend(container, intervals) {
container.innerHTML = "";
container.innerHTML = intervals.reverse()
container.innerHTML = intervals
.map(
item =>
`<i style="background:${
Expand Down

0 comments on commit e9c4c53

Please sign in to comment.