/**
* Colormap shader
* data reference must contain one index to the data to render using colormap strategy
*
* expected parameters:
* index - unique number in the compiled shader
* supported parameters:
* color - can be a ColorMap, number of steps = x
* threshold - must be an AdvancedSlider, default values array (pipes) = x-1, mask array size = x, incorrect
* values are changed to reflect the color steps
* connect - a boolean switch to enable/disable advanced slider mapping to break values, enabled for type==="colormap" only
*
* colors shader will read underlying data (red component) and output
* to canvas defined color with opacity based on the data
* (0.0 => transparent, 1.0 => opaque)
* supports thresholding - outputs color on areas above certain value
* mapping html input slider 0-100 to .0-1.0
*/
WebGLModule.ColorMap = class extends WebGLModule.VisualizationLayer {
static type() {
return "colormap";
}
static name() {
return "ColorMap";
}
static description() {
return "data values encoded in color scale";
}
static sources() {
return [{
acceptsChannelCount: (x) => x===1,
description: "1D data mapped to color map"
}];
}
construct(options, dataReferences) {
super.construct(options, dataReferences);
//delete unused controls if applicable after initialization
if (this.color.getName() !== "colormap") {
this.removeControl("connect");
}
}
static defaultControls = {
color: {
default: {
type: "colormap",
steps: 3, //number of categories
default: "Viridis",
mode: "sequential",
title: "Colormap",
continuous: false,
},
accepts: (type, instance) => type === "vec3"
},
threshold: {
default: {
type: "advanced_slider",
default: [0.25, 0.75], //breaks/separators, e.g. one less than bin count
mask: [1, 0, 1], //same number of steps as color
title: "Breaks",
pips: {
mode: 'positions',
values: [0, 35, 50, 75, 90, 100],
density: 4
}},
accepts: (type, instance) => type === "float",
required: {type: "advanced_slider", inverted: false}
},
connect : {
default: {type: "bool", interactive: true, title: "Connect breaks: ", default: false},
accepts: (type, instance) => type === "bool"
}
};
getFragmentShaderExecution() {
return `
float chan = ${this.sampleChannel('tile_texture_coords')};
return vec4(${this.color.sample('chan', 'float')}, step(0.05, ${this.threshold.sample('chan', 'float')}));
`;
}
defaultColSteps(length) {
return [...Array(length).keys()].forEach(x => x+1);
}
init() {
const _this = this;
this.opacity.init();
if (this.connect) {
this.connect.on('default', function(raw, encoded, ctx) {
_this.color.setSteps(_this.connect.raw ? [0, ..._this.threshold.raw, 1] :
_this.defaultColSteps(_this.color.maxSteps)
);
_this.color.updateColormapUI();
}, true);
this.connect.init();
this.threshold.on('breaks', function(raw, encoded, ctx) {
if (_this.connect.raw) { //if YES
_this.color.setSteps([0, ...raw, 1]);
_this.color.updateColormapUI();
}
}, true);
}
this.threshold.init();
//todo fix this scenario
// if (this.threshold.raw.length != this.color.params.steps - 1) {
// }
if (this.connect) {
if (this.connect.raw) {
this.color.setSteps([0, ...this.threshold.raw, 1]);
} else {
//default breaks mapping for colormap if connect not enabled
this.color.setSteps(this.defaultColSteps(this.color.maxSteps));
}
}
this.color.init();
// let steps = this.color.steps.filter(x => x >= 0);
// steps.splice(steps.length-1, 1); //last element is 1 not a break
// this.storeProperty('threshold_values', steps);
}
};
WebGLModule.ShaderMediator.registerLayer(WebGLModule.ColorMap);