diff --git a/apps/heatmap/uicallbacks.js b/apps/heatmap/uicallbacks.js index 39de17cc1..6eddd0d79 100644 --- a/apps/heatmap/uicallbacks.js +++ b/apps/heatmap/uicallbacks.js @@ -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 diff --git a/components/heatmapcontrol/heatmapcontrol.css b/components/heatmapcontrol/heatmapcontrol.css index 48b08f72d..5d3721994 100644 --- a/components/heatmapcontrol/heatmapcontrol.css +++ b/components/heatmapcontrol/heatmapcontrol.css @@ -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; -} \ No newline at end of file +} diff --git a/components/heatmapcontrol/heatmapcontrol.js b/components/heatmapcontrol/heatmapcontrol.js index fa2c3045b..ac18ec60a 100644 --- a/components/heatmapcontrol/heatmapcontrol.js +++ b/components/heatmapcontrol/heatmapcontrol.js @@ -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'; /* @@ -50,25 +55,36 @@ HeatmapControl.prototype.__refresh = function(){ const template = `
- +
- +
- +
- +
- -
+ +
+
+
+
+
+
+ +
+
`; 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)); @@ -86,6 +102,34 @@ 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); } @@ -93,6 +137,8 @@ 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 @@ -100,6 +146,8 @@ HeatmapControl.prototype._modeChanged = function(flag = true){ 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=''; @@ -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; @@ -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()}); @@ -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) +} diff --git a/core/extension/osd-heatmap-overlay.js b/core/extension/osd-heatmap-overlay.js index bbf9b1850..d054c7894 100644 --- a/core/extension/osd-heatmap-overlay.js +++ b/core/extension/osd-heatmap-overlay.js @@ -1,4 +1,5 @@ //osd-heatmap-overlay.js + /** * @constructor * OpenSeadragon heatmap Plugin 0.0.1. @@ -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(); @@ -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: [] }); @@ -1128,7 +1139,7 @@ }; function createLegend(container, intervals) { container.innerHTML = ""; - container.innerHTML = intervals.reverse() + container.innerHTML = intervals .map( item => `