diff --git a/README.md b/README.md index c5e87f0..cf9100f 100644 --- a/README.md +++ b/README.md @@ -3,41 +3,43 @@ Visual Heatmap, an open-source JavaScript module, emerges as a powerful tool des ### Examples:

- - - - + + + +

-## Installing +# Installing -If npm +npm ``` npm i visual-heatmap --save ``` -Download source code from below links +Or Download source code from below links * [visualHeatmap.min.js](https://raw.githubusercontent.com/nswamy14/visual-heatmap/master/dist/visualHeatmap.min.js) * [visualHeatmap.js](https://raw.githubusercontent.com/nswamy14/visual-heatmap/master/dist/visualHeatmap.js) * [visualHeatmap.esm.js](https://raw.githubusercontent.com/nswamy14/visual-heatmap/master/dist/visualHeatmap.esm.js) -Visual-Heatmap is written in ES6 Modules. To import use below syntax -Importing everything into namespace + +# Usage + +### Importing +Visual-Heatmap provides ES6 and UMD modules. Accordingly module can be embeded into applications. + ``` import Heatmap from 'visual-heatmap' ``` -## VisualHeatmapJs - API - -### visualHeatmap() -visualHeatmap provides a API to create context **WebGL**. API accepts container/containerId and config as an input. A layer will be created under the provided Div #containerId. +### Instance Creation API +visualHeatmap provides a API to create heatmap instance. API accepts container/containerId and config as an input. A context element will be created under the provided Div #containerId. ```Javascript let instance = Heatmap('#containerId', { - size: 30.0, + size: 30.0, //Radius of the data point, in pixels. Default: 20 max: 100, // if not set, will be derived from data min: 0, // if not set, will be derived from data - intensity: 1.0, + intensity: 1.0, background: { url: "urlPath", width: 100, // if not set, viewport width of the image will be considered @@ -63,32 +65,46 @@ let instance = Heatmap('#containerId', { }] }); ``` -**Container/ContainerId** The container div element or a string CSS Query selector which identifies the container. +**Container/ContainerId** : The container div element or a string CSS Query selector which identifies the container. -**Config** -Object with config properties. +**Config Object** : ``` { - size : Radius of the data point, in pixels. - max : Max data Value for relative gradient computation. - min : Min data Value for relative gradient computation. - intensity : intensity factor. - opacity : Opacity factor. - rotationAngle : Rotation angle. - translate : translate vector [x, y]. - zoom : Zoom Factor. + size : Radius of the data point, in pixels. Default: 20 + max : Max data Value for relative gradient computation. if not set, will be derived from data. + min : Min data Value for relative gradient computation. if not set, will be derived from data. + intensity : intensity factor. Default: 1.0 + opacity : Opacity factor. Default: 1.0 + rotationAngle : Rotation angle. Default: 0 + translate : translate vector [x, y]. Default: [0,0] + zoom : Zoom Factor. Default: 1.0 gradient : Color Gradient, array of objects with color value and offset. - background: To set background of the heatMap + background: To set background of the heatMap. Value : { url: , x: , y: , height: , width: } } ``` +## Adding Data API ### instance.renderData([]) Accepts an array of data points with 'x', 'y' and 'value'. [Demo](https://nswamy14.github.io/visual-heatmap/demo/heatmap1.html) +```Javascript +instance.renderData([{x: , y: , value: }]) +``` ### instance.addData([], transformationIntactflag); -Accepts an array of data points with 'x', 'y' and 'value' and a flag to specify to apply existing canvas transformations on the newly added data points. +Accepts an array of data points with 'x', 'y' and 'value' and a boolean flag to specify to apply existing heatmap transformations on the newly added data points. After adding data points, need to invoke `.render()` method to update the heatmap. Try [Example](https://nswamy14.github.io/visual-heatmap/demo/heatmap3.html) +```Javascript +instance.addData([{x: , y: , value: }],transformationIntactflag) +``` + +## Render API +Method to re-render the heatmap. This method needs to be invoked as and when configurations get changed. [Example](https://nswamy14.github.io/visual-heatmap/demo/heatmap1.html) +```Javascript +instance.render() +``` + +## Configuration Setting API ### instance.setMax(number) To set max data value, for relative gradient calculations. diff --git a/demo/heatmap1.html b/demo/heatmap1.html index c4ca57c..7182baa 100644 --- a/demo/heatmap1.html +++ b/demo/heatmap1.html @@ -61,25 +61,25 @@ instance.renderData(data); } ); gui.add( params, 'size', 15, 100 ).onChange( function () { - instance.setSize(params.size); + instance.setSize(params.size).render(); } ); gui.add( params, 'opacity', 0, 1 ).onChange( function () { - instance.setOpacity(params.opacity); + instance.setOpacity(params.opacity).render(); } ); gui.add( params, 'intensity', 0, 1 ).onChange( function () { - instance.setIntensity(params.intensity); + instance.setIntensity(params.intensity).render(); } ); gui.add( params, 'transalteX', -instance.width, instance.width ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); } ); gui.add( params, 'transalteY', -instance.height, instance.height ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); } ); gui.add( params, 'zoom', 0, 2 ).onChange( function () { - instance.setZoom(params.zoom); + instance.setZoom(params.zoom).render(); } ); gui.add( params, 'rotationAngle', 0, Math.PI * 2 ).onChange( function () { - instance.setRotationAngle(params.rotationAngle); + instance.setRotationAngle(params.rotationAngle).render(); } ); data = generateData(10000); @@ -108,6 +108,12 @@ return num; } + window.addEventListener("resize", function () { + if (instance && document.getElementById("canvas")) { + instance.resize(); + } + }); + diff --git a/demo/heatmap2.html b/demo/heatmap2.html index 5f0b60f..2c825a4 100644 --- a/demo/heatmap2.html +++ b/demo/heatmap2.html @@ -57,25 +57,25 @@ var params = new ParamsCon(); gui.add( params, 'size', 5, 100 ).onChange( function () { - instance.setSize(params.size); + instance.setSize(params.size).render(); } ); gui.add( params, 'opacity', 0, 1 ).onChange( function () { - instance.setOpacity(params.opacity); + instance.setOpacity(params.opacity).render(); } ); gui.add( params, 'intensity', 0, 1 ).onChange( function () { - instance.setIntensity(params.intensity); + instance.setIntensity(params.intensity).render(); } ); gui.add( params, 'transalteX', -instance.width, instance.width ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); } ); gui.add( params, 'transalteY', -instance.height, instance.height ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); } ); gui.add( params, 'zoom', 0, 2 ).onChange( function () { - instance.setZoom(params.zoom); + instance.setZoom(params.zoom).render(); } ); gui.add( params, 'rotationAngle', 0, Math.PI * 2 ).onChange( function () { - instance.setRotationAngle(params.rotationAngle); + instance.setRotationAngle(params.rotationAngle).render(); } ); diff --git a/demo/heatmap3.html b/demo/heatmap3.html index 315c529..3825a4b 100644 --- a/demo/heatmap3.html +++ b/demo/heatmap3.html @@ -61,30 +61,31 @@ var params = new ParamsCon(); gui.add( params, 'size', 1, 200 ).onChange( function () { - instance.setSize(params.size); + instance.setSize(params.size).render(); } ); gui.add( params, 'opacity', 0, 1 ).onChange( function () { - instance.setOpacity(params.opacity); + instance.setOpacity(params.opacity).render(); } ); gui.add( params, 'intensity', 0, 1 ).onChange( function () { - instance.setIntensity(params.intensity); + instance.setIntensity(params.intensity).render(); } ); gui.add( params, 'transalteX', -instance.width, instance.width ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); } ); gui.add( params, 'transalteY', -instance.height, instance.height ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); } ); gui.add( params, 'zoom', 0, 2 ).onChange( function () { - instance.setZoom(params.zoom); + instance.setZoom(params.zoom).render(); } ); gui.add( params, 'rotationAngle', 0, Math.PI * 2 ).onChange( function () { - instance.setRotationAngle(params.rotationAngle); + instance.setRotationAngle(params.rotationAngle).render(); } ); var dataPush = true; document.getElementById('canvas').addEventListener("mousemove", function(e){ - if (dataPush) { instance.addData([{ + if (dataPush) { + instance.addData([{ x: e.x, y: e.y, value: 10 +Math.random() * 50 diff --git a/demo/heatmapWithLabels.html b/demo/heatmapWithLabels.html index 585438a..25688b2 100644 --- a/demo/heatmapWithLabels.html +++ b/demo/heatmapWithLabels.html @@ -59,31 +59,31 @@ var params = new ParamsCon(); gui.add( params, 'size', 50, 100 ).onChange( function () { - instance.setSize(params.size); + instance.setSize(params.size).render(); params.updateLabels(); } ); gui.add( params, 'opacity', 0, 1 ).onChange( function () { - instance.setOpacity(params.opacity); + instance.setOpacity(params.opacity).render(); params.updateLabels(); } ); gui.add( params, 'intensity', 0, 1 ).onChange( function () { - instance.setIntensity(params.intensity); + instance.setIntensity(params.intensity).render(); params.updateLabels(); } ); gui.add( params, 'transalteX', -instance.width, instance.width ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); params.updateLabels(); } ); gui.add( params, 'transalteY', -instance.height, instance.height ).onChange( function () { - instance.setTranslate([params.transalteX, params.transalteY]); + instance.setTranslate([params.transalteX, params.transalteY]).render(); params.updateLabels(); } ); gui.add( params, 'zoom', 0, 2 ).onChange( function () { - instance.setZoom(params.zoom); + instance.setZoom(params.zoom).render(); params.updateLabels(); } ); gui.add( params, 'rotationAngle', 0, Math.PI * 2 ).onChange( function () { - instance.setRotationAngle(params.rotationAngle); + instance.setRotationAngle(params.rotationAngle).render(); params.updateLabels(); } ); diff --git a/dist/visualHeatmap.esm.js b/dist/visualHeatmap.esm.js index 92cdeda..dc477f0 100644 --- a/dist/visualHeatmap.esm.js +++ b/dist/visualHeatmap.esm.js @@ -1,5 +1,5 @@ /*! - * Heatmap v1.0.5 + * Heatmap v1.1.0 * (c) 2024 Narayana Swamy (narayanaswamy14@gmail.com) * @license BSD-3-Clause */ @@ -10,29 +10,63 @@ function Heatmap (context, config = {}) { let buffer2; let rVec = []; let pLen = 0; - let dataMinValue = null; - let dataMaxValue = null; let maxTextureSize = null; let imgWidth; let imgHeight; + let hearmapExData; + let imageConfig; + let configMin = 0; + let configMax = 0; + + + function isNullUndefined (val) { + return val === null || val === undefined; + } + + function isNotNumber (val) { + return typeof val !== 'number'; + } + + function isSortedAscending (arr) { + for (let i = 0; i < arr.length - 1; i++) { + if (arr[i + 1].offset - arr[i].offset < 0) { + return false; + } + } + return true; + } function gradientMapper (grad) { - const arr = []; + if (grad.constructor !== Array) { + throw new Error('Invalid gradient: Wrong Gradient type, expected Array'); + } + + if (grad.length < 2) { + throw new Error('Invalid gradient: 2 or more values expected'); + } + + if (!isSortedAscending(grad)) { + throw new Error('Invalid gradient: Gradient is not sorted'); + } + const gradLength = grad.length; - const offSetsArray = []; - - grad.forEach(function (d) { - arr.push(d.color[0] / 255); - arr.push(d.color[1] / 255); - arr.push(d.color[2] / 255); - arr.push(d.color[3] === undefined ? 1.0 : d.color[3]); - offSetsArray.push(d.offset); + const values = new Float32Array(gradLength * 4); + const offsets = new Array(gradLength); + + grad.forEach(function (d, i) { + const baseIndex = i * 4; + values[baseIndex] = d.color[0] / 255; + values[baseIndex + 1] = d.color[1] / 255; + values[baseIndex + 2] = d.color[2] / 255; + values[baseIndex + 3] = d.color[3] !== undefined ? d.color[3] : 1.0; + offsets[i] = d.offset; }); + return { - value: new Float32Array(arr), + value: values, length: gradLength, - offset: offSetsArray + offset: offsets }; } @@ -43,8 +77,8 @@ function Heatmap (context, config = {}) { var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS); if (!compiled) { var lastError = ctx.getShaderInfoLog(shader); - console.error("*** Error compiling shader '" + shader + "':" + lastError); ctx.deleteShader(shader); + throw new Error("*** Error compiling shader '" + shader + "':" + lastError); } return shader; } @@ -62,9 +96,8 @@ function Heatmap (context, config = {}) { var linked = ctx.getProgramParameter(program, ctx.LINK_STATUS); if (!linked) { var lastError = ctx.getProgramInfoLog(program); - console.error('Error in program linking:' + lastError); ctx.deleteProgram(program); - return null; + throw new Error('Error in program linking:' + lastError); } else { return program; } @@ -193,68 +226,111 @@ function Heatmap (context, config = {}) { } function Chart (context, config) { - let res; - if (typeof context === 'string') { - res = document.querySelector(context); - } else if (context instanceof Element) { - res = context; - } else { - throw new Error('Context must be either a string or an Element'); - } - const height = res.clientHeight; - const width = res.clientWidth; - const layer = document.createElement('canvas'); - const ctx = layer.getContext('webgl2', { - premultipliedAlpha: false, - depth: false, - antialias: true, - alpha: true, - preserveDrawingBuffer: false - }); - ratio = getPixlRatio(ctx); - ctx.clearColor(0, 0, 0, 0); - ctx.enable(ctx.BLEND); - ctx.blendEquation(ctx.FUNC_ADD); - ctx.blendFunc(ctx.ONE, ctx.ONE_MINUS_SRC_ALPHA); - ctx.depthMask(true); - layer.setAttribute('height', height * ratio); - layer.setAttribute('width', width * ratio); - layer.style.height = `${height}px`; - layer.style.width = `${width}px`; - layer.style.position = 'absolute'; - res.appendChild(layer); - - this.gradient = gradientMapper(config.gradient); - this.ctx = ctx; - this.width = width; - this.height = height; - this.layer = layer; - this.dom = res; - this.gradShadOP = createGradiantShader(this.ctx); - this.colorShadOP = createColorShader(this.ctx); - this.imageShaOP = createImageShader(this.ctx); - this.fbTexObj = ctx.createTexture(); - this.fbo = ctx.createFramebuffer(); - - this.size = config.size ? config.size : 20.0; - dataMaxValue = config.max ? config.max : null; - dataMinValue = config.min ? config.min : null; - this.intensity = config.intensity ? config.intensity : 1.0; - this.translate = (config.translate && config.translate.length === 2) ? config.translate : [0, 0]; - this.zoom = (config.zoom ? config.zoom : 1.0); - this.angle = (config.rotationAngle ? config.rotationAngle : 0.0); - this.opacity = config.opacity ? config.opacity : 1.0; - this.ratio = ratio; - - if (config.backgroundImage && config.backgroundImage.url) { - this.setBackgroundImage(config.backgroundImage); - } + try { + let res; + if (typeof context === 'string') { + res = document.querySelector(context); + } else if (context instanceof Element) { + res = context; + } else { + throw new Error('Context must be either a string or an Element'); + } + const height = res.clientHeight; + const width = res.clientWidth; + const layer = document.createElement('canvas'); + const ctx = layer.getContext('webgl2', { + premultipliedAlpha: false, + depth: false, + antialias: true, + alpha: true, + preserveDrawingBuffer: false + }); + ratio = getPixlRatio(ctx); + ctx.clearColor(0, 0, 0, 0); + ctx.enable(ctx.BLEND); + ctx.blendEquation(ctx.FUNC_ADD); + ctx.blendFunc(ctx.ONE, ctx.ONE_MINUS_SRC_ALPHA); + ctx.depthMask(true); + layer.setAttribute('height', height * ratio); + layer.setAttribute('width', width * ratio); + layer.style.height = `${height}px`; + layer.style.width = `${width}px`; + layer.style.position = 'absolute'; + res.appendChild(layer); + + this.ctx = ctx; + this.width = width; + this.height = height; + this.layer = layer; + this.dom = res; + this.gradShadOP = createGradiantShader(this.ctx); + this.colorShadOP = createColorShader(this.ctx); + this.imageShaOP = createImageShader(this.ctx); + this.fbTexObj = ctx.createTexture(); + this.fbo = ctx.createFramebuffer(); + + if (!isNullUndefined(config.size)) { + this.setSize(config.size); + } else { + this.size = 20.0; + } - this.rawData = []; + if (!isNullUndefined(config.max)) { + this.setMax(config.max); + } else { + configMax = null; + } + + if (!isNullUndefined(config.min)) { + this.setMin(config.min); + } else { + configMin = null; + } - ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); + if (!isNullUndefined(config.intensity)) { + this.setIntensity(config.intensity); + } else { + this.intensity = 1.0; + } + + if (!isNullUndefined(config.translate)) { + this.setTranslate(config.translate); + } else { + this.translate = [0, 0]; + } + + if (!isNullUndefined(config.zoom)) { + this.setZoom(config.zoom); + } else { + this.zoom = 1.0; + } - this.render(this.exData || {}); + if (!isNullUndefined(config.angle)) { + this.setRotationAngle(config.angle); + } else { + this.angle = 0.0; + } + + if (!isNullUndefined(config.opacity)) { + this.setOpacity(config.opacity); + } else { + this.opacity = 1.0; + } + + this.gradient = gradientMapper(config.gradient); + + this.ratio = ratio; + + if (config.backgroundImage && config.backgroundImage.url) { + this.setBackgroundImage(config.backgroundImage); + } + + this.heatmapData = []; + + this.ctx.viewport(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + } catch (error) { + console.error(error); + } } Chart.prototype.resize = function () { @@ -266,10 +342,9 @@ function Heatmap (context, config = {}) { this.layer.style.width = `${width}px`; this.width = width; this.height = height; - this.ctx.viewport(0, 0, this.width * ratio, this.height * ratio); - + this.ctx.viewport(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); /* Perform update */ - this.render(this.exData); + this.render(hearmapExData); }; Chart.prototype.clear = function () { @@ -277,43 +352,90 @@ function Heatmap (context, config = {}) { }; Chart.prototype.setMax = function (max) { - dataMaxValue = max; - this.render(this.exData); + if (isNullUndefined(max) || isNotNumber(max)) { + throw new Error('Invalid max: Expected Number'); + } + + configMax = max; + return this; }; Chart.prototype.setMin = function (min) { - dataMinValue = min; - this.render(this.exData); + if (isNullUndefined(min) || isNotNumber(min)) { + throw new Error('Invalid min: Expected Number'); + } + + configMin = min; + return this; + }; + + Chart.prototype.setGradient = function (gradient) { + this.gradient = gradientMapper(gradient); + return this; }; Chart.prototype.setTranslate = function (translate) { - this.translate = (translate.length === 2) ? translate : [0, 0]; - this.render(this.exData); + if (translate.constructor !== Array) { + throw new Error('Invalid Translate: Translate has to be of Array type'); + } + if (translate.length !== 2) { + throw new Error('Translate has to be of length 2'); + } + this.translate = translate; + return this; }; Chart.prototype.setZoom = function (zoom) { - this.zoom = zoom !== undefined ? zoom : 1.0; - this.render(this.exData); + if (isNullUndefined(zoom) || isNotNumber(zoom)) { + throw new Error('Invalid zoom: Expected Number'); + } + + this.zoom = zoom; + return this; }; Chart.prototype.setRotationAngle = function (angle) { - this.angle = angle !== undefined ? angle : 0.0; - this.render(this.exData); + if (isNullUndefined(angle) || isNotNumber(angle)) { + throw new Error('Invalid Angle: Expected Number'); + } + + this.angle = angle; + return this; }; Chart.prototype.setSize = function (size) { - this.size = size !== undefined ? size : 20.0; - this.render(this.exData); + if (isNullUndefined(size) || isNotNumber(size)) { + throw new Error('Invalid Size: Expected Number'); + } + + this.size = size; + return this; }; Chart.prototype.setIntensity = function (intensity) { - this.intensity = intensity !== undefined ? intensity : 1.0; - this.render(this.exData); + if (isNullUndefined(intensity) || isNotNumber(intensity)) { + this.intensity = 1.0; // applying default intensity + throw new Error('Invalid Intensity: Expected Number'); + } + + if (intensity > 1 || intensity < 0) { + this.intensity = intensity > 1 ? 1 : 0; // Setting bound value + throw new Error('Invalid Intensity value ' + intensity); + } + this.intensity = intensity; + return this; }; Chart.prototype.setOpacity = function (opacity) { - this.opacity = opacity !== undefined ? opacity : 1.0; - this.render(this.exData); + if (isNullUndefined(opacity) || isNotNumber(opacity)) { + throw new Error('Invalid Opacity: Expected Number'); + } + + if (opacity > 1 || opacity < 0) { + throw new Error('Invalid Opacity value ' + opacity); + } + this.opacity = opacity; + return this; }; Chart.prototype.setBackgroundImage = function (config) { @@ -325,7 +447,7 @@ function Heatmap (context, config = {}) { maxTextureSize = this.ctx.getParameter(this.ctx.MAX_TEXTURE_SIZE); this.imageTexture = this.ctx.createTexture(); this.type = 'TEXTURE_2D'; - this.imageConfig = null; + imageConfig = null; imgWidth = config.width || this.width; imgHeight = config.height || this.height; @@ -353,7 +475,7 @@ function Heatmap (context, config = {}) { this ); - self.imageConfig = { + imageConfig = { x: config.x || 0, y: config.y || 0, height: imgHeight, @@ -361,10 +483,17 @@ function Heatmap (context, config = {}) { image: this }; - self.render(self.exData || {}); + self.render(); }, function onErrorCallBack (error) { - console.error('Image Load Error', error); + throw new Error('Image Load Error', error); }); + return this; + }; + + Chart.prototype.clearData = function () { + this.heatmapData = []; + hearmapExData = {}; + this.render(); }; Chart.prototype.addData = function (data, transIntactFlag) { @@ -373,15 +502,24 @@ function Heatmap (context, config = {}) { if (transIntactFlag) { transCoOr.call(self, data[i]); } - this.rawData.push(data[i]); + this.heatmapData.push(data[i]); } - this.renderData(this.rawData); + this.renderData(this.heatmapData); + return this; }; Chart.prototype.renderData = function (data) { - const exData = extractData(data); - this.rawData = data; - this.render(exData); + if (data.constructor !== Array) { + throw new Error('Expected Array type'); + } + hearmapExData = extractData(data); + this.heatmapData = data; + this.render(); + return this; + }; + + Chart.prototype.render = function () { + renderExec.call(this); }; Chart.prototype.projection = function (data) { @@ -418,9 +556,8 @@ function Heatmap (context, config = {}) { return { x: posX, y: posY }; }; - Chart.prototype.render = function (exData) { + function renderExec () { const ctx = this.ctx; - this.exData = exData; ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT); @@ -433,19 +570,20 @@ function Heatmap (context, config = {}) { ctx.bindFramebuffer(ctx.FRAMEBUFFER, this.fbo); ctx.framebufferTexture2D(ctx.FRAMEBUFFER, ctx.COLOR_ATTACHMENT0, ctx.TEXTURE_2D, this.fbTexObj, 0); - renderHeatGrad.call(this, ctx, exData); + if (hearmapExData) { + renderHeatGrad.call(this, ctx, hearmapExData); + } ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); - if (this.imageConfig) { - renderImage.call(this, ctx); + if (imageConfig) { + renderImage.call(this, ctx, imageConfig); } renderColorGradiant.call(this, ctx); - }; - + } function renderHeatGrad (ctx, exData) { ctx.useProgram(this.gradShadOP.program); - this.min = dataMinValue !== null ? dataMinValue : exData?.minMax?.min ?? 0; - this.max = dataMaxValue !== null ? dataMaxValue : exData?.minMax?.max ?? 0; + this.min = configMin !== null ? configMin : exData?.minMax?.min ?? 0; + this.max = configMax !== null ? configMax : exData?.minMax?.max ?? 0; this.gradShadOP.attr[0].data = exData.posVec || []; this.gradShadOP.attr[1].data = exData.rVec || []; @@ -466,11 +604,11 @@ function Heatmap (context, config = {}) { ctx.vertexAttribPointer(d.attribute, d.size, d.valueType, true, 0, 0); }); - ctx.drawArrays(ctx.POINTS, 0, (this.exData.posVec || []).length / 2); + ctx.drawArrays(ctx.POINTS, 0, (exData.posVec || []).length / 2); } - function renderImage (ctx) { - const { x = 0, y = 0, width = 0, height = 0 } = this.imageConfig; + function renderImage (ctx, imageConfig) { + const { x = 0, y = 0, width = 0, height = 0 } = imageConfig; ctx.useProgram(this.imageShaOP.program); @@ -540,6 +678,9 @@ function Heatmap (context, config = {}) { posX = (posX * halfWidth) + halfWidth - this.translate[0]; posY = (posY * halfHeight) + halfHeight - this.translate[1]; + data.x = posX; + data.y = posY; + return { x: posX, y: posY }; } @@ -569,57 +710,51 @@ function getPixlRatio (ctx) { var GradShaders = { vertex: `#version 300 es - in vec2 a_position; - in float a_intensity; - uniform float u_size; - uniform vec2 u_resolution; - uniform vec2 u_translate; - uniform float u_zoom; - uniform float u_angle; - uniform float u_density; - out float v_i; - - vec2 rotation(vec2 v, float a, float aspect) { - float s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c); - mat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0); - mat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0); - return scaleMatInv * m * scaleMat * v; - } + in vec2 a_position; + in float a_intensity; + uniform float u_size; + uniform vec2 u_resolution; + uniform vec2 u_translate; + uniform float u_zoom; + uniform float u_angle; + uniform float u_density; + out float v_i; + + vec2 rotation(vec2 v, float a, float aspect) { + float s = sin(a); float c = cos(a); mat2 rotationMat = mat2(c, -s, s, c); + mat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0); + mat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0); + return scaleMatInv * rotationMat * scaleMat * v; + } - void main() { - vec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution); - vec2 zeroToTwo = zeroToOne * 2.0 - 1.0; - float zoomFactor = u_zoom; - if (zoomFactor == 0.0) { - zoomFactor = 0.1; - } - zeroToTwo = zeroToTwo / zoomFactor; - if (u_angle != 0.0) { - zeroToTwo = rotation(zeroToTwo, u_angle, u_resolution.x / u_resolution.y); - } - gl_Position = vec4(zeroToTwo , 0, 1); - gl_PointSize = u_size * u_density; - v_i = a_intensity; - }`, + void main() { + vec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution); + vec2 zeroToTwo = zeroToOne * 2.0 - 1.0; + float zoomFactor = max(u_zoom, 0.1); + zeroToTwo = zeroToTwo / zoomFactor; + if (u_angle != 0.0) { + zeroToTwo = rotation(zeroToTwo, u_angle, u_resolution.x / u_resolution.y); + } + gl_Position = vec4(zeroToTwo , 0, 1); + gl_PointSize = u_size * u_density; + v_i = a_intensity; + }`, fragment: `#version 300 es - precision mediump float; - uniform float u_max; - uniform float u_min; - uniform float u_intensity; - in float v_i; - out vec4 fragColor; - void main() { - float r = 0.0; - vec2 cxy = 2.0 * gl_PointCoord - 1.0; - r = dot(cxy, cxy); - float deno = u_max - u_min; - if (deno <= 0.0) { - deno = 1.0; - } - if(r <= 1.0) { - fragColor = vec4(0, 0, 0, ((v_i - u_min) / (deno)) * u_intensity * (1.0 - sqrt(r))); - } - }` + precision mediump float; + uniform float u_max; + uniform float u_min; + uniform float u_intensity; + in float v_i; + out vec4 fragColor; + void main() { + float r = 0.0; + vec2 cxy = 2.0 * gl_PointCoord - 1.0; + r = dot(cxy, cxy); + float deno = max(u_max - u_min, 1.0); + if(r <= 1.0) { + fragColor = vec4(0, 0, 0, ((v_i - u_min) / (deno)) * u_intensity * (1.0 - sqrt(r))); + } + }` }; var ColorShader = { @@ -655,7 +790,7 @@ var ColorShader = { if (alpha <= u_offset[0]) { color_ = u_colorArr[0]; } else { - for (int i = 1; i <= 10; ++i) { + for (int i = 1; i <= 20; ++i) { if (alpha <= u_offset[i]) { color_ = mix( u_colorArr[i - 1], u_colorArr[i], remap( u_offset[i - 1], u_offset[i], alpha ) ); color_ = color_ * mix( u_colorArr[i - 1][3], u_colorArr[i][3], remap( u_offset[i - 1], u_offset[i], alpha )); diff --git a/dist/visualHeatmap.esm.min.js b/dist/visualHeatmap.esm.min.js index 7771ce4..563a10d 100644 --- a/dist/visualHeatmap.esm.min.js +++ b/dist/visualHeatmap.esm.min.js @@ -1,6 +1,6 @@ /*! - * Heatmap v1.0.5 + * Heatmap v1.1.0 * (c) 2024 Narayana Swamy (narayanaswamy14@gmail.com) * @license BSD-3-Clause */ -function t(t,r={}){let n,a,s,u,f,h=[],c=[],l=0,m=null,_=null,d=null;function g(t,e,o){var i=t.createShader(t[e]);if(t.shaderSource(i,o),t.compileShader(i),!t.getShaderParameter(i,t.COMPILE_STATUS)){var r=t.getShaderInfoLog(i);console.error("*** Error compiling shader '"+i+"':"+r),t.deleteShader(i)}return i}function T(t,e){var o=g(t,"VERTEX_SHADER",e.vertex),i=g(t,"FRAGMENT_SHADER",e.fragment),r=t.createProgram();if(t.attachShader(r,o),t.attachShader(r,i),t.linkProgram(r),t.getProgramParameter(r,t.LINK_STATUS))return r;var n=t.getProgramInfoLog(r);return console.error("Error in program linking:"+n),t.deleteProgram(r),null}function x(t,r){let a;if("string"==typeof t)a=document.querySelector(t);else{if(!(t instanceof Element))throw new Error("Context must be either a string or an Element");a=t}const s=a.clientHeight,u=a.clientWidth,f=document.createElement("canvas"),h=f.getContext("webgl2",{premultipliedAlpha:!1,depth:!1,antialias:!0,alpha:!0,preserveDrawingBuffer:!1});n=function(t){const e=window.devicePixelRatio||1,o=t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1;return e/o}(h),h.clearColor(0,0,0,0),h.enable(h.BLEND),h.blendEquation(h.FUNC_ADD),h.blendFunc(h.ONE,h.ONE_MINUS_SRC_ALPHA),h.depthMask(!0),f.setAttribute("height",s*n),f.setAttribute("width",u*n),f.style.height=`${s}px`,f.style.width=`${u}px`,f.style.position="absolute",a.appendChild(f),this.gradient=function(t){const e=[],o=t.length,i=[];return t.forEach((function(t){e.push(t.color[0]/255),e.push(t.color[1]/255),e.push(t.color[2]/255),e.push(void 0===t.color[3]?1:t.color[3]),i.push(t.offset)})),{value:new Float32Array(e),length:o,offset:i}}(r.gradient),this.ctx=h,this.width=u,this.height=s,this.layer=f,this.dom=a,this.gradShadOP=function(t){var o=T(t,e);return{program:o,attr:[{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:2,attribute:t.getAttribLocation(o,"a_position"),data:new Float32Array([])},{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:1,attribute:t.getAttribLocation(o,"a_intensity"),data:new Float32Array([])}],uniform:{u_resolution:t.getUniformLocation(o,"u_resolution"),u_max:t.getUniformLocation(o,"u_max"),u_min:t.getUniformLocation(o,"u_min"),u_size:t.getUniformLocation(o,"u_size"),u_intensity:t.getUniformLocation(o,"u_intensity"),u_translate:t.getUniformLocation(o,"u_translate"),u_zoom:t.getUniformLocation(o,"u_zoom"),u_angle:t.getUniformLocation(o,"u_angle"),u_density:t.getUniformLocation(o,"u_density")}}}(this.ctx),this.colorShadOP=function(t){var e=T(t,o);return{program:e,attr:[{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:2,attribute:t.getAttribLocation(e,"a_texCoord"),data:new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1])}],uniform:{u_framebuffer:t.getUniformLocation(e,"u_framebuffer"),u_colorArr:t.getUniformLocation(e,"u_colorArr"),u_colorCount:t.getUniformLocation(e,"u_colorCount"),u_opacity:t.getUniformLocation(e,"u_opacity"),u_offset:t.getUniformLocation(e,"u_offset")}}}(this.ctx),this.imageShaOP=function(t){var e=T(t,i);return{program:e,attr:[{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:2,attribute:t.getAttribLocation(e,"a_position"),data:new Float32Array([])},{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:2,attribute:t.getAttribLocation(e,"a_texCoord"),data:new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1])}],uniform:{u_resolution:t.getUniformLocation(e,"u_resolution"),u_image:t.getUniformLocation(e,"u_image"),u_translate:t.getUniformLocation(e,"u_translate"),u_zoom:t.getUniformLocation(e,"u_zoom"),u_angle:t.getUniformLocation(e,"u_angle"),u_density:t.getUniformLocation(e,"u_density")}}}(this.ctx),this.fbTexObj=h.createTexture(),this.fbo=h.createFramebuffer(),this.size=r.size?r.size:20,_=r.max?r.max:null,m=r.min?r.min:null,this.intensity=r.intensity?r.intensity:1,this.translate=r.translate&&2===r.translate.length?r.translate:[0,0],this.zoom=r.zoom?r.zoom:1,this.angle=r.rotationAngle?r.rotationAngle:0,this.opacity=r.opacity?r.opacity:1,this.ratio=n,r.backgroundImage&&r.backgroundImage.url&&this.setBackgroundImage(r.backgroundImage),this.rawData=[],h.viewport(0,0,h.canvas.width,h.canvas.height),this.render(this.exData||{})}function p(t,e){t.useProgram(this.gradShadOP.program),this.min=null!==m?m:e?.minMax?.min??0,this.max=null!==_?_:e?.minMax?.max??0,this.gradShadOP.attr[0].data=e.posVec||[],this.gradShadOP.attr[1].data=e.rVec||[],t.uniform2fv(this.gradShadOP.uniform.u_resolution,new Float32Array([this.width*this.ratio,this.height*this.ratio])),t.uniform2fv(this.gradShadOP.uniform.u_translate,new Float32Array([this.translate[0],this.translate[1]])),t.uniform1f(this.gradShadOP.uniform.u_zoom,this.zoom?this.zoom:.01),t.uniform1f(this.gradShadOP.uniform.u_angle,this.angle),t.uniform1f(this.gradShadOP.uniform.u_density,this.ratio),t.uniform1f(this.gradShadOP.uniform.u_max,this.max),t.uniform1f(this.gradShadOP.uniform.u_min,this.min),t.uniform1f(this.gradShadOP.uniform.u_size,this.size),t.uniform1f(this.gradShadOP.uniform.u_intensity,this.intensity),this.gradShadOP.attr.forEach((function(e){t.bindBuffer(e.bufferType,e.buffer),t.bufferData(e.bufferType,e.data,e.drawType),t.enableVertexAttribArray(e.attribute),t.vertexAttribPointer(e.attribute,e.size,e.valueType,!0,0,0)})),t.drawArrays(t.POINTS,0,(this.exData.posVec||[]).length/2)}function y(t){const{x:e=0,y:o=0,width:i=0,height:r=0}=this.imageConfig;t.useProgram(this.imageShaOP.program),t.uniform2fv(this.imageShaOP.uniform.u_resolution,new Float32Array([this.width*this.ratio,this.height*this.ratio])),t.uniform2fv(this.imageShaOP.uniform.u_translate,new Float32Array([this.translate[0],this.translate[1]])),t.uniform1f(this.imageShaOP.uniform.u_zoom,this.zoom?this.zoom:.01),t.uniform1f(this.imageShaOP.uniform.u_angle,this.angle),t.uniform1f(this.imageShaOP.uniform.u_density,this.ratio),this.imageShaOP.attr[0].data=new Float32Array([e,o,e+i,o,e,o+r,e,o+r,e+i,o,e+i,o+r]),this.imageShaOP.attr.forEach((function(e){t.bindBuffer(e.bufferType,e.buffer),t.bufferData(e.bufferType,e.data,e.drawType),t.enableVertexAttribArray(e.attribute),t.vertexAttribPointer(e.attribute,e.size,e.valueType,!0,0,0)})),t.uniform1i(this.imageShaOP.uniform.u_image,0),t.activeTexture(this.ctx.TEXTURE0),t.bindTexture(this.ctx.TEXTURE_2D,this.imageTexture),t.drawArrays(t.TRIANGLES,0,6)}function v(t){t.useProgram(this.colorShadOP.program),t.uniform4fv(this.colorShadOP.uniform.u_colorArr,this.gradient.value),t.uniform1f(this.colorShadOP.uniform.u_colorCount,this.gradient.length),t.uniform1fv(this.colorShadOP.uniform.u_offset,new Float32Array(this.gradient.offset)),t.uniform1f(this.colorShadOP.uniform.u_opacity,this.opacity),this.colorShadOP.attr.forEach((function(e){t.bindBuffer(e.bufferType,e.buffer),t.bufferData(e.bufferType,e.data,e.drawType),t.enableVertexAttribArray(e.attribute),t.vertexAttribPointer(e.attribute,e.size,e.valueType,!0,0,0)})),t.uniform1i(this.colorShadOP.uniform.u_framebuffer,0),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.fbTexObj),t.drawArrays(t.TRIANGLES,0,6)}function E(t){const e=this.zoom||.1,o=this.width/2,i=this.height/2,r=this.angle;let n=(t.x-o)/o*e,a=(t.y-i)/i*e;if(0!==r){const t=Math.cos(r),e=Math.sin(r),o=t*n-e*a;a=e*n+t*a,n=o}return n=n*o+o-this.translate[0],a=a*i+i-this.translate[1],{x:n,y:a}}return x.prototype.resize=function(){const t=this.dom.clientHeight,e=this.dom.clientWidth;this.layer.setAttribute("height",t*n),this.layer.setAttribute("width",e*n),this.layer.style.height=`${t}px`,this.layer.style.width=`${e}px`,this.width=e,this.height=t,this.ctx.viewport(0,0,this.width*n,this.height*n),this.render(this.exData)},x.prototype.clear=function(){this.ctx.clear(this.ctx.COLOR_BUFFER_BIT|this.ctx.DEPTH_BUFFER_BIT)},x.prototype.setMax=function(t){_=t,this.render(this.exData)},x.prototype.setMin=function(t){m=t,this.render(this.exData)},x.prototype.setTranslate=function(t){this.translate=2===t.length?t:[0,0],this.render(this.exData)},x.prototype.setZoom=function(t){this.zoom=void 0!==t?t:1,this.render(this.exData)},x.prototype.setRotationAngle=function(t){this.angle=void 0!==t?t:0,this.render(this.exData)},x.prototype.setSize=function(t){this.size=void 0!==t?t:20,this.render(this.exData)},x.prototype.setIntensity=function(t){this.intensity=void 0!==t?t:1,this.render(this.exData)},x.prototype.setOpacity=function(t){this.opacity=void 0!==t?t:1,this.render(this.exData)},x.prototype.setBackgroundImage=function(t){const e=this;t.url&&(d=this.ctx.getParameter(this.ctx.MAX_TEXTURE_SIZE),this.imageTexture=this.ctx.createTexture(),this.type="TEXTURE_2D",this.imageConfig=null,u=t.width||this.width,f=t.height||this.height,u=u>d?d:u,f=f>d?d:f,function(t,e,o){const i=new Image;i.crossOrigin="anonymous",i.onload=e,i.onerror=o,i.src=t}(t.url,(function(){e.ctx.activeTexture(e.ctx.TEXTURE0),e.ctx.bindTexture(e.ctx.TEXTURE_2D,e.imageTexture),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_S,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_T,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MIN_FILTER,e.ctx.LINEAR),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MAG_FILTER,e.ctx.LINEAR),e.ctx.texImage2D(e.ctx.TEXTURE_2D,0,e.ctx.RGBA,this.naturalWidth,this.naturalHeight,0,e.ctx.RGBA,e.ctx.UNSIGNED_BYTE,this),e.imageConfig={x:t.x||0,y:t.y||0,height:f,width:u,image:this},e.render(e.exData||{})}),(function(t){console.error("Image Load Error",t)})))},x.prototype.addData=function(t,e){const o=this;for(let i=0;it[i].value&&(o.min=t[i].value),o.max 0.0 && alpha <= 1.0) {\n\t\t\t\t\t\t\tvec4 color_;\n\n\t\t\t\t\t\t\tif (alpha <= u_offset[0]) {\n\t\t\t\t\t\t\t\tcolor_ = u_colorArr[0];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor (int i = 1; i <= 10; ++i) {\n\t\t\t\t\t\t\t\t\tif (alpha <= u_offset[i]) {\n\t\t\t\t\t\t\t\t\t\tcolor_ = mix( u_colorArr[i - 1], u_colorArr[i], remap( u_offset[i - 1], u_offset[i], alpha ) );\n\t\t\t\t\t\t\t\t\t\tcolor_ = color_ * mix( u_colorArr[i - 1][3], u_colorArr[i][3], remap( u_offset[i - 1], u_offset[i], alpha ));\n\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcolor_ = color_ * u_opacity;\n\t\t\t\t\t\t\tif (color_.a < 0.0) {\n\t\t\t\t\t\t\t\tcolor_.a = 0.0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfragColor = color_;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfragColor = vec4(0.0, 0.0, 0.0, 0.0);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t"},i={vertex:"#version 300 es\n precision highp float;\n in vec2 a_position;\n in vec2 a_texCoord;\n uniform vec2 u_resolution;\n\t\t\t\t\tuniform vec2 u_translate; \n\t\t\t\t\tuniform float u_zoom; \n\t\t\t\t\tuniform float u_angle; \n\t\t\t\t\tuniform float u_density;\n out vec2 v_texCoord;\n\n vec2 rotation(vec2 v, float a, float aspect) {\n\t\t\t\t\t\tfloat s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c);\n\t\t\t\t\t\tmat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\tmat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\treturn scaleMatInv * m * scaleMat * v;\n\t\t\t\t\t}\n\n void main() {\n \tvec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution);\n \tzeroToOne.y = 1.0 - zeroToOne.y;\n\t\t\t\t\t\tvec2 zeroToTwo = zeroToOne * 2.0 - 1.0;\n\t\t\t\t\t\tfloat zoomFactor = u_zoom;\n\t\t\t\t\t\tif (zoomFactor == 0.0) {\n\t\t\t\t\t\t\tzoomFactor = 0.1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tzeroToTwo = zeroToTwo / zoomFactor;\n\t\t\t\t\t\tif (u_angle != 0.0) {\n\t\t\t\t\t\t\tzeroToTwo = rotation(zeroToTwo, u_angle * -1.0, u_resolution.x / u_resolution.y);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tgl_Position = vec4(zeroToTwo , 0, 1);\n\t\t\t\t\t\tv_texCoord = a_texCoord;\n }\n \t\t",fragment:"#version 300 es\n precision mediump float;\n uniform sampler2D u_image;\n in vec2 v_texCoord;\n out vec4 fragColor;\n void main() {\n fragColor = texture(u_image, v_texCoord);\n }\n "};export{t as default}; \ No newline at end of file +function t(t,i={}){let n,a,s,u,f,h,c,l=[],m=[],_=0,d=null,g=0,T=0;function p(t){return null==t}function x(t){return"number"!=typeof t}function y(t){if(t.constructor!==Array)throw new Error("Invalid gradient: Wrong Gradient type, expected Array");if(t.length<2)throw new Error("Invalid gradient: 2 or more values expected");if(!function(t){for(let e=0;e1||t<0)throw this.intensity=t>1?1:0,new Error("Invalid Intensity value "+t);return this.intensity=t,this},A.prototype.setOpacity=function(t){if(p(t)||x(t))throw new Error("Invalid Opacity: Expected Number");if(t>1||t<0)throw new Error("Invalid Opacity value "+t);return this.opacity=t,this},A.prototype.setBackgroundImage=function(t){const e=this;if(t.url)return d=this.ctx.getParameter(this.ctx.MAX_TEXTURE_SIZE),this.imageTexture=this.ctx.createTexture(),this.type="TEXTURE_2D",c=null,u=t.width||this.width,f=t.height||this.height,u=u>d?d:u,f=f>d?d:f,function(t,e,r){const o=new Image;o.crossOrigin="anonymous",o.onload=e,o.onerror=r,o.src=t}(t.url,(function(){e.ctx.activeTexture(e.ctx.TEXTURE0),e.ctx.bindTexture(e.ctx.TEXTURE_2D,e.imageTexture),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_S,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_T,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MIN_FILTER,e.ctx.LINEAR),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MAG_FILTER,e.ctx.LINEAR),e.ctx.texImage2D(e.ctx.TEXTURE_2D,0,e.ctx.RGBA,this.naturalWidth,this.naturalHeight,0,e.ctx.RGBA,e.ctx.UNSIGNED_BYTE,this),c={x:t.x||0,y:t.y||0,height:f,width:u,image:this},e.render()}),(function(t){throw new Error("Image Load Error",t)})),this},A.prototype.clearData=function(){this.heatmapData=[],h={},this.render()},A.prototype.addData=function(t,e){const r=this;for(let o=0;ot[o].value&&(r.min=t[o].value),r.max 0.0 && alpha <= 1.0) {\n\t\t\t\t\t\t\tvec4 color_;\n\n\t\t\t\t\t\t\tif (alpha <= u_offset[0]) {\n\t\t\t\t\t\t\t\tcolor_ = u_colorArr[0];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor (int i = 1; i <= 20; ++i) {\n\t\t\t\t\t\t\t\t\tif (alpha <= u_offset[i]) {\n\t\t\t\t\t\t\t\t\t\tcolor_ = mix( u_colorArr[i - 1], u_colorArr[i], remap( u_offset[i - 1], u_offset[i], alpha ) );\n\t\t\t\t\t\t\t\t\t\tcolor_ = color_ * mix( u_colorArr[i - 1][3], u_colorArr[i][3], remap( u_offset[i - 1], u_offset[i], alpha ));\n\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcolor_ = color_ * u_opacity;\n\t\t\t\t\t\t\tif (color_.a < 0.0) {\n\t\t\t\t\t\t\t\tcolor_.a = 0.0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfragColor = color_;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfragColor = vec4(0.0, 0.0, 0.0, 0.0);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t"},o={vertex:"#version 300 es\n precision highp float;\n in vec2 a_position;\n in vec2 a_texCoord;\n uniform vec2 u_resolution;\n\t\t\t\t\tuniform vec2 u_translate; \n\t\t\t\t\tuniform float u_zoom; \n\t\t\t\t\tuniform float u_angle; \n\t\t\t\t\tuniform float u_density;\n out vec2 v_texCoord;\n\n vec2 rotation(vec2 v, float a, float aspect) {\n\t\t\t\t\t\tfloat s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c);\n\t\t\t\t\t\tmat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\tmat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\treturn scaleMatInv * m * scaleMat * v;\n\t\t\t\t\t}\n\n void main() {\n \tvec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution);\n \tzeroToOne.y = 1.0 - zeroToOne.y;\n\t\t\t\t\t\tvec2 zeroToTwo = zeroToOne * 2.0 - 1.0;\n\t\t\t\t\t\tfloat zoomFactor = u_zoom;\n\t\t\t\t\t\tif (zoomFactor == 0.0) {\n\t\t\t\t\t\t\tzoomFactor = 0.1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tzeroToTwo = zeroToTwo / zoomFactor;\n\t\t\t\t\t\tif (u_angle != 0.0) {\n\t\t\t\t\t\t\tzeroToTwo = rotation(zeroToTwo, u_angle * -1.0, u_resolution.x / u_resolution.y);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tgl_Position = vec4(zeroToTwo , 0, 1);\n\t\t\t\t\t\tv_texCoord = a_texCoord;\n }\n \t\t",fragment:"#version 300 es\n precision mediump float;\n uniform sampler2D u_image;\n in vec2 v_texCoord;\n out vec4 fragColor;\n void main() {\n fragColor = texture(u_image, v_texCoord);\n }\n "};export{t as default}; \ No newline at end of file diff --git a/dist/visualHeatmap.js b/dist/visualHeatmap.js index a34c404..903e9bd 100644 --- a/dist/visualHeatmap.js +++ b/dist/visualHeatmap.js @@ -1,5 +1,5 @@ /*! - * Heatmap v1.0.5 + * Heatmap v1.1.0 * (c) 2024 Narayana Swamy (narayanaswamy14@gmail.com) * @license BSD-3-Clause */ @@ -16,29 +16,63 @@ let buffer2; let rVec = []; let pLen = 0; - let dataMinValue = null; - let dataMaxValue = null; let maxTextureSize = null; let imgWidth; let imgHeight; + let hearmapExData; + let imageConfig; + let configMin = 0; + let configMax = 0; + + + function isNullUndefined (val) { + return val === null || val === undefined; + } + + function isNotNumber (val) { + return typeof val !== 'number'; + } + + function isSortedAscending (arr) { + for (let i = 0; i < arr.length - 1; i++) { + if (arr[i + 1].offset - arr[i].offset < 0) { + return false; + } + } + return true; + } function gradientMapper (grad) { - const arr = []; + if (grad.constructor !== Array) { + throw new Error('Invalid gradient: Wrong Gradient type, expected Array'); + } + + if (grad.length < 2) { + throw new Error('Invalid gradient: 2 or more values expected'); + } + + if (!isSortedAscending(grad)) { + throw new Error('Invalid gradient: Gradient is not sorted'); + } + const gradLength = grad.length; - const offSetsArray = []; - - grad.forEach(function (d) { - arr.push(d.color[0] / 255); - arr.push(d.color[1] / 255); - arr.push(d.color[2] / 255); - arr.push(d.color[3] === undefined ? 1.0 : d.color[3]); - offSetsArray.push(d.offset); + const values = new Float32Array(gradLength * 4); + const offsets = new Array(gradLength); + + grad.forEach(function (d, i) { + const baseIndex = i * 4; + values[baseIndex] = d.color[0] / 255; + values[baseIndex + 1] = d.color[1] / 255; + values[baseIndex + 2] = d.color[2] / 255; + values[baseIndex + 3] = d.color[3] !== undefined ? d.color[3] : 1.0; + offsets[i] = d.offset; }); + return { - value: new Float32Array(arr), + value: values, length: gradLength, - offset: offSetsArray + offset: offsets }; } @@ -49,8 +83,8 @@ var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS); if (!compiled) { var lastError = ctx.getShaderInfoLog(shader); - console.error("*** Error compiling shader '" + shader + "':" + lastError); ctx.deleteShader(shader); + throw new Error("*** Error compiling shader '" + shader + "':" + lastError); } return shader; } @@ -68,9 +102,8 @@ var linked = ctx.getProgramParameter(program, ctx.LINK_STATUS); if (!linked) { var lastError = ctx.getProgramInfoLog(program); - console.error('Error in program linking:' + lastError); ctx.deleteProgram(program); - return null; + throw new Error('Error in program linking:' + lastError); } else { return program; } @@ -199,68 +232,111 @@ } function Chart (context, config) { - let res; - if (typeof context === 'string') { - res = document.querySelector(context); - } else if (context instanceof Element) { - res = context; - } else { - throw new Error('Context must be either a string or an Element'); - } - const height = res.clientHeight; - const width = res.clientWidth; - const layer = document.createElement('canvas'); - const ctx = layer.getContext('webgl2', { - premultipliedAlpha: false, - depth: false, - antialias: true, - alpha: true, - preserveDrawingBuffer: false - }); - ratio = getPixlRatio(ctx); - ctx.clearColor(0, 0, 0, 0); - ctx.enable(ctx.BLEND); - ctx.blendEquation(ctx.FUNC_ADD); - ctx.blendFunc(ctx.ONE, ctx.ONE_MINUS_SRC_ALPHA); - ctx.depthMask(true); - layer.setAttribute('height', height * ratio); - layer.setAttribute('width', width * ratio); - layer.style.height = `${height}px`; - layer.style.width = `${width}px`; - layer.style.position = 'absolute'; - res.appendChild(layer); - - this.gradient = gradientMapper(config.gradient); - this.ctx = ctx; - this.width = width; - this.height = height; - this.layer = layer; - this.dom = res; - this.gradShadOP = createGradiantShader(this.ctx); - this.colorShadOP = createColorShader(this.ctx); - this.imageShaOP = createImageShader(this.ctx); - this.fbTexObj = ctx.createTexture(); - this.fbo = ctx.createFramebuffer(); - - this.size = config.size ? config.size : 20.0; - dataMaxValue = config.max ? config.max : null; - dataMinValue = config.min ? config.min : null; - this.intensity = config.intensity ? config.intensity : 1.0; - this.translate = (config.translate && config.translate.length === 2) ? config.translate : [0, 0]; - this.zoom = (config.zoom ? config.zoom : 1.0); - this.angle = (config.rotationAngle ? config.rotationAngle : 0.0); - this.opacity = config.opacity ? config.opacity : 1.0; - this.ratio = ratio; - - if (config.backgroundImage && config.backgroundImage.url) { - this.setBackgroundImage(config.backgroundImage); - } + try { + let res; + if (typeof context === 'string') { + res = document.querySelector(context); + } else if (context instanceof Element) { + res = context; + } else { + throw new Error('Context must be either a string or an Element'); + } + const height = res.clientHeight; + const width = res.clientWidth; + const layer = document.createElement('canvas'); + const ctx = layer.getContext('webgl2', { + premultipliedAlpha: false, + depth: false, + antialias: true, + alpha: true, + preserveDrawingBuffer: false + }); + ratio = getPixlRatio(ctx); + ctx.clearColor(0, 0, 0, 0); + ctx.enable(ctx.BLEND); + ctx.blendEquation(ctx.FUNC_ADD); + ctx.blendFunc(ctx.ONE, ctx.ONE_MINUS_SRC_ALPHA); + ctx.depthMask(true); + layer.setAttribute('height', height * ratio); + layer.setAttribute('width', width * ratio); + layer.style.height = `${height}px`; + layer.style.width = `${width}px`; + layer.style.position = 'absolute'; + res.appendChild(layer); + + this.ctx = ctx; + this.width = width; + this.height = height; + this.layer = layer; + this.dom = res; + this.gradShadOP = createGradiantShader(this.ctx); + this.colorShadOP = createColorShader(this.ctx); + this.imageShaOP = createImageShader(this.ctx); + this.fbTexObj = ctx.createTexture(); + this.fbo = ctx.createFramebuffer(); + + if (!isNullUndefined(config.size)) { + this.setSize(config.size); + } else { + this.size = 20.0; + } - this.rawData = []; + if (!isNullUndefined(config.max)) { + this.setMax(config.max); + } else { + configMax = null; + } + + if (!isNullUndefined(config.min)) { + this.setMin(config.min); + } else { + configMin = null; + } - ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); + if (!isNullUndefined(config.intensity)) { + this.setIntensity(config.intensity); + } else { + this.intensity = 1.0; + } + + if (!isNullUndefined(config.translate)) { + this.setTranslate(config.translate); + } else { + this.translate = [0, 0]; + } + + if (!isNullUndefined(config.zoom)) { + this.setZoom(config.zoom); + } else { + this.zoom = 1.0; + } - this.render(this.exData || {}); + if (!isNullUndefined(config.angle)) { + this.setRotationAngle(config.angle); + } else { + this.angle = 0.0; + } + + if (!isNullUndefined(config.opacity)) { + this.setOpacity(config.opacity); + } else { + this.opacity = 1.0; + } + + this.gradient = gradientMapper(config.gradient); + + this.ratio = ratio; + + if (config.backgroundImage && config.backgroundImage.url) { + this.setBackgroundImage(config.backgroundImage); + } + + this.heatmapData = []; + + this.ctx.viewport(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + } catch (error) { + console.error(error); + } } Chart.prototype.resize = function () { @@ -272,10 +348,9 @@ this.layer.style.width = `${width}px`; this.width = width; this.height = height; - this.ctx.viewport(0, 0, this.width * ratio, this.height * ratio); - + this.ctx.viewport(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); /* Perform update */ - this.render(this.exData); + this.render(hearmapExData); }; Chart.prototype.clear = function () { @@ -283,43 +358,90 @@ }; Chart.prototype.setMax = function (max) { - dataMaxValue = max; - this.render(this.exData); + if (isNullUndefined(max) || isNotNumber(max)) { + throw new Error('Invalid max: Expected Number'); + } + + configMax = max; + return this; }; Chart.prototype.setMin = function (min) { - dataMinValue = min; - this.render(this.exData); + if (isNullUndefined(min) || isNotNumber(min)) { + throw new Error('Invalid min: Expected Number'); + } + + configMin = min; + return this; + }; + + Chart.prototype.setGradient = function (gradient) { + this.gradient = gradientMapper(gradient); + return this; }; Chart.prototype.setTranslate = function (translate) { - this.translate = (translate.length === 2) ? translate : [0, 0]; - this.render(this.exData); + if (translate.constructor !== Array) { + throw new Error('Invalid Translate: Translate has to be of Array type'); + } + if (translate.length !== 2) { + throw new Error('Translate has to be of length 2'); + } + this.translate = translate; + return this; }; Chart.prototype.setZoom = function (zoom) { - this.zoom = zoom !== undefined ? zoom : 1.0; - this.render(this.exData); + if (isNullUndefined(zoom) || isNotNumber(zoom)) { + throw new Error('Invalid zoom: Expected Number'); + } + + this.zoom = zoom; + return this; }; Chart.prototype.setRotationAngle = function (angle) { - this.angle = angle !== undefined ? angle : 0.0; - this.render(this.exData); + if (isNullUndefined(angle) || isNotNumber(angle)) { + throw new Error('Invalid Angle: Expected Number'); + } + + this.angle = angle; + return this; }; Chart.prototype.setSize = function (size) { - this.size = size !== undefined ? size : 20.0; - this.render(this.exData); + if (isNullUndefined(size) || isNotNumber(size)) { + throw new Error('Invalid Size: Expected Number'); + } + + this.size = size; + return this; }; Chart.prototype.setIntensity = function (intensity) { - this.intensity = intensity !== undefined ? intensity : 1.0; - this.render(this.exData); + if (isNullUndefined(intensity) || isNotNumber(intensity)) { + this.intensity = 1.0; // applying default intensity + throw new Error('Invalid Intensity: Expected Number'); + } + + if (intensity > 1 || intensity < 0) { + this.intensity = intensity > 1 ? 1 : 0; // Setting bound value + throw new Error('Invalid Intensity value ' + intensity); + } + this.intensity = intensity; + return this; }; Chart.prototype.setOpacity = function (opacity) { - this.opacity = opacity !== undefined ? opacity : 1.0; - this.render(this.exData); + if (isNullUndefined(opacity) || isNotNumber(opacity)) { + throw new Error('Invalid Opacity: Expected Number'); + } + + if (opacity > 1 || opacity < 0) { + throw new Error('Invalid Opacity value ' + opacity); + } + this.opacity = opacity; + return this; }; Chart.prototype.setBackgroundImage = function (config) { @@ -331,7 +453,7 @@ maxTextureSize = this.ctx.getParameter(this.ctx.MAX_TEXTURE_SIZE); this.imageTexture = this.ctx.createTexture(); this.type = 'TEXTURE_2D'; - this.imageConfig = null; + imageConfig = null; imgWidth = config.width || this.width; imgHeight = config.height || this.height; @@ -359,7 +481,7 @@ this ); - self.imageConfig = { + imageConfig = { x: config.x || 0, y: config.y || 0, height: imgHeight, @@ -367,10 +489,17 @@ image: this }; - self.render(self.exData || {}); + self.render(); }, function onErrorCallBack (error) { - console.error('Image Load Error', error); + throw new Error('Image Load Error', error); }); + return this; + }; + + Chart.prototype.clearData = function () { + this.heatmapData = []; + hearmapExData = {}; + this.render(); }; Chart.prototype.addData = function (data, transIntactFlag) { @@ -379,15 +508,24 @@ if (transIntactFlag) { transCoOr.call(self, data[i]); } - this.rawData.push(data[i]); + this.heatmapData.push(data[i]); } - this.renderData(this.rawData); + this.renderData(this.heatmapData); + return this; }; Chart.prototype.renderData = function (data) { - const exData = extractData(data); - this.rawData = data; - this.render(exData); + if (data.constructor !== Array) { + throw new Error('Expected Array type'); + } + hearmapExData = extractData(data); + this.heatmapData = data; + this.render(); + return this; + }; + + Chart.prototype.render = function () { + renderExec.call(this); }; Chart.prototype.projection = function (data) { @@ -424,9 +562,8 @@ return { x: posX, y: posY }; }; - Chart.prototype.render = function (exData) { + function renderExec () { const ctx = this.ctx; - this.exData = exData; ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT); @@ -439,19 +576,20 @@ ctx.bindFramebuffer(ctx.FRAMEBUFFER, this.fbo); ctx.framebufferTexture2D(ctx.FRAMEBUFFER, ctx.COLOR_ATTACHMENT0, ctx.TEXTURE_2D, this.fbTexObj, 0); - renderHeatGrad.call(this, ctx, exData); + if (hearmapExData) { + renderHeatGrad.call(this, ctx, hearmapExData); + } ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); - if (this.imageConfig) { - renderImage.call(this, ctx); + if (imageConfig) { + renderImage.call(this, ctx, imageConfig); } renderColorGradiant.call(this, ctx); - }; - + } function renderHeatGrad (ctx, exData) { ctx.useProgram(this.gradShadOP.program); - this.min = dataMinValue !== null ? dataMinValue : exData?.minMax?.min ?? 0; - this.max = dataMaxValue !== null ? dataMaxValue : exData?.minMax?.max ?? 0; + this.min = configMin !== null ? configMin : exData?.minMax?.min ?? 0; + this.max = configMax !== null ? configMax : exData?.minMax?.max ?? 0; this.gradShadOP.attr[0].data = exData.posVec || []; this.gradShadOP.attr[1].data = exData.rVec || []; @@ -472,11 +610,11 @@ ctx.vertexAttribPointer(d.attribute, d.size, d.valueType, true, 0, 0); }); - ctx.drawArrays(ctx.POINTS, 0, (this.exData.posVec || []).length / 2); + ctx.drawArrays(ctx.POINTS, 0, (exData.posVec || []).length / 2); } - function renderImage (ctx) { - const { x = 0, y = 0, width = 0, height = 0 } = this.imageConfig; + function renderImage (ctx, imageConfig) { + const { x = 0, y = 0, width = 0, height = 0 } = imageConfig; ctx.useProgram(this.imageShaOP.program); @@ -546,6 +684,9 @@ posX = (posX * halfWidth) + halfWidth - this.translate[0]; posY = (posY * halfHeight) + halfHeight - this.translate[1]; + data.x = posX; + data.y = posY; + return { x: posX, y: posY }; } @@ -575,57 +716,51 @@ var GradShaders = { vertex: `#version 300 es - in vec2 a_position; - in float a_intensity; - uniform float u_size; - uniform vec2 u_resolution; - uniform vec2 u_translate; - uniform float u_zoom; - uniform float u_angle; - uniform float u_density; - out float v_i; - - vec2 rotation(vec2 v, float a, float aspect) { - float s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c); - mat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0); - mat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0); - return scaleMatInv * m * scaleMat * v; - } + in vec2 a_position; + in float a_intensity; + uniform float u_size; + uniform vec2 u_resolution; + uniform vec2 u_translate; + uniform float u_zoom; + uniform float u_angle; + uniform float u_density; + out float v_i; + + vec2 rotation(vec2 v, float a, float aspect) { + float s = sin(a); float c = cos(a); mat2 rotationMat = mat2(c, -s, s, c); + mat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0); + mat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0); + return scaleMatInv * rotationMat * scaleMat * v; + } - void main() { - vec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution); - vec2 zeroToTwo = zeroToOne * 2.0 - 1.0; - float zoomFactor = u_zoom; - if (zoomFactor == 0.0) { - zoomFactor = 0.1; - } - zeroToTwo = zeroToTwo / zoomFactor; - if (u_angle != 0.0) { - zeroToTwo = rotation(zeroToTwo, u_angle, u_resolution.x / u_resolution.y); - } - gl_Position = vec4(zeroToTwo , 0, 1); - gl_PointSize = u_size * u_density; - v_i = a_intensity; - }`, + void main() { + vec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution); + vec2 zeroToTwo = zeroToOne * 2.0 - 1.0; + float zoomFactor = max(u_zoom, 0.1); + zeroToTwo = zeroToTwo / zoomFactor; + if (u_angle != 0.0) { + zeroToTwo = rotation(zeroToTwo, u_angle, u_resolution.x / u_resolution.y); + } + gl_Position = vec4(zeroToTwo , 0, 1); + gl_PointSize = u_size * u_density; + v_i = a_intensity; + }`, fragment: `#version 300 es - precision mediump float; - uniform float u_max; - uniform float u_min; - uniform float u_intensity; - in float v_i; - out vec4 fragColor; - void main() { - float r = 0.0; - vec2 cxy = 2.0 * gl_PointCoord - 1.0; - r = dot(cxy, cxy); - float deno = u_max - u_min; - if (deno <= 0.0) { - deno = 1.0; - } - if(r <= 1.0) { - fragColor = vec4(0, 0, 0, ((v_i - u_min) / (deno)) * u_intensity * (1.0 - sqrt(r))); - } - }` + precision mediump float; + uniform float u_max; + uniform float u_min; + uniform float u_intensity; + in float v_i; + out vec4 fragColor; + void main() { + float r = 0.0; + vec2 cxy = 2.0 * gl_PointCoord - 1.0; + r = dot(cxy, cxy); + float deno = max(u_max - u_min, 1.0); + if(r <= 1.0) { + fragColor = vec4(0, 0, 0, ((v_i - u_min) / (deno)) * u_intensity * (1.0 - sqrt(r))); + } + }` }; var ColorShader = { @@ -661,7 +796,7 @@ if (alpha <= u_offset[0]) { color_ = u_colorArr[0]; } else { - for (int i = 1; i <= 10; ++i) { + for (int i = 1; i <= 20; ++i) { if (alpha <= u_offset[i]) { color_ = mix( u_colorArr[i - 1], u_colorArr[i], remap( u_offset[i - 1], u_offset[i], alpha ) ); color_ = color_ * mix( u_colorArr[i - 1][3], u_colorArr[i][3], remap( u_offset[i - 1], u_offset[i], alpha )); diff --git a/dist/visualHeatmap.min.js b/dist/visualHeatmap.min.js index 1ab2b47..5e15a0d 100644 --- a/dist/visualHeatmap.min.js +++ b/dist/visualHeatmap.min.js @@ -1,6 +1,6 @@ /*! - * Heatmap v1.0.5 + * Heatmap v1.1.0 * (c) 2024 Narayana Swamy (narayanaswamy14@gmail.com) * @license BSD-3-Clause */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).visualHeatmap=e()}(this,(function(){"use strict";var t={vertex:"#version 300 es\n\tin vec2 a_position;\n\tin float a_intensity;\n\tuniform float u_size;\n\tuniform vec2 u_resolution;\n\tuniform vec2 u_translate; \n\tuniform float u_zoom; \n\tuniform float u_angle; \n\tuniform float u_density;\n\tout float v_i;\n\n\tvec2 rotation(vec2 v, float a, float aspect) {\n\t\tfloat s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c); \n\t\tmat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0);\n\t\tmat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0);\n\t\treturn scaleMatInv * m * scaleMat * v;\n\t}\n\n\tvoid main() {\n\t\tvec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution);\n\t\tvec2 zeroToTwo = zeroToOne * 2.0 - 1.0;\n\t\tfloat zoomFactor = u_zoom;\n\t\tif (zoomFactor == 0.0) {\n\t\t\tzoomFactor = 0.1;\n\t\t}\n\t\tzeroToTwo = zeroToTwo / zoomFactor;\n\t\tif (u_angle != 0.0) {\n\t\t\tzeroToTwo = rotation(zeroToTwo, u_angle, u_resolution.x / u_resolution.y);\n\t\t}\n\t\tgl_Position = vec4(zeroToTwo , 0, 1);\n\t\tgl_PointSize = u_size * u_density;\n\t\tv_i = a_intensity;\n\t}",fragment:"#version 300 es\n\tprecision mediump float;\n\tuniform float u_max;\n\tuniform float u_min;\n\tuniform float u_intensity;\n\tin float v_i;\n\tout vec4 fragColor;\n\tvoid main() {\n\t\tfloat r = 0.0; \n\t\tvec2 cxy = 2.0 * gl_PointCoord - 1.0;\n\t\tr = dot(cxy, cxy);\n\t\tfloat deno = u_max - u_min;\n\t\tif (deno <= 0.0) {\n\t\t\tdeno = 1.0;\n\t\t}\n\t\tif(r <= 1.0) {\n\t\t\tfragColor = vec4(0, 0, 0, ((v_i - u_min) / (deno)) * u_intensity * (1.0 - sqrt(r)));\n\t\t}\n\t}"},e={vertex:"#version 300 es\n\t\t\t\tprecision highp float;\n\t\t\t\tin vec2 a_texCoord;\n\t\t\t\tout vec2 v_texCoord;\n\t\t\t\tvoid main() {\n\t\t\t\t\tvec2 clipSpace = a_texCoord * 2.0 - 1.0;\n\t\t\t\t\tgl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);\n\t\t\t\t\tv_texCoord = a_texCoord;\n\t\t\t\t}\n\t",fragment:"#version 300 es\n\t\t\t\t\tprecision mediump float;\n\t\t\t\t\tin vec2 v_texCoord;\n\t\t\t\t\tout vec4 fragColor;\n\t\t\t\t\tuniform sampler2D u_framebuffer;\n\t\t\t\t\tuniform vec4 u_colorArr[20];\n\t\t\t\t\tuniform float u_colorCount;\n\t\t\t\t\tuniform float u_opacity;\n\t\t\t\t\tuniform float u_offset[20];\n\n\t\t\t\t\tfloat remap ( float minval, float maxval, float curval ) {\n\t\t\t\t\t\treturn ( curval - minval ) / ( maxval - minval );\n\t\t\t\t\t}\n\n\t\t\t\t\tvoid main() {\n\t\t\t\t\t\tfloat alpha = texture(u_framebuffer, v_texCoord.xy).a;\n\t\t\t\t\t\tif (alpha > 0.0 && alpha <= 1.0) {\n\t\t\t\t\t\t\tvec4 color_;\n\n\t\t\t\t\t\t\tif (alpha <= u_offset[0]) {\n\t\t\t\t\t\t\t\tcolor_ = u_colorArr[0];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor (int i = 1; i <= 10; ++i) {\n\t\t\t\t\t\t\t\t\tif (alpha <= u_offset[i]) {\n\t\t\t\t\t\t\t\t\t\tcolor_ = mix( u_colorArr[i - 1], u_colorArr[i], remap( u_offset[i - 1], u_offset[i], alpha ) );\n\t\t\t\t\t\t\t\t\t\tcolor_ = color_ * mix( u_colorArr[i - 1][3], u_colorArr[i][3], remap( u_offset[i - 1], u_offset[i], alpha ));\n\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcolor_ = color_ * u_opacity;\n\t\t\t\t\t\t\tif (color_.a < 0.0) {\n\t\t\t\t\t\t\t\tcolor_.a = 0.0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfragColor = color_;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfragColor = vec4(0.0, 0.0, 0.0, 0.0);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t"},o={vertex:"#version 300 es\n precision highp float;\n in vec2 a_position;\n in vec2 a_texCoord;\n uniform vec2 u_resolution;\n\t\t\t\t\tuniform vec2 u_translate; \n\t\t\t\t\tuniform float u_zoom; \n\t\t\t\t\tuniform float u_angle; \n\t\t\t\t\tuniform float u_density;\n out vec2 v_texCoord;\n\n vec2 rotation(vec2 v, float a, float aspect) {\n\t\t\t\t\t\tfloat s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c);\n\t\t\t\t\t\tmat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\tmat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\treturn scaleMatInv * m * scaleMat * v;\n\t\t\t\t\t}\n\n void main() {\n \tvec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution);\n \tzeroToOne.y = 1.0 - zeroToOne.y;\n\t\t\t\t\t\tvec2 zeroToTwo = zeroToOne * 2.0 - 1.0;\n\t\t\t\t\t\tfloat zoomFactor = u_zoom;\n\t\t\t\t\t\tif (zoomFactor == 0.0) {\n\t\t\t\t\t\t\tzoomFactor = 0.1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tzeroToTwo = zeroToTwo / zoomFactor;\n\t\t\t\t\t\tif (u_angle != 0.0) {\n\t\t\t\t\t\t\tzeroToTwo = rotation(zeroToTwo, u_angle * -1.0, u_resolution.x / u_resolution.y);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tgl_Position = vec4(zeroToTwo , 0, 1);\n\t\t\t\t\t\tv_texCoord = a_texCoord;\n }\n \t\t",fragment:"#version 300 es\n precision mediump float;\n uniform sampler2D u_image;\n in vec2 v_texCoord;\n out vec4 fragColor;\n void main() {\n fragColor = texture(u_image, v_texCoord);\n }\n "};return function(i,r={}){let n,a,s,u,f,h=[],l=[],c=0,m=null,_=null,d=null;function g(t,e,o){var i=t.createShader(t[e]);if(t.shaderSource(i,o),t.compileShader(i),!t.getShaderParameter(i,t.COMPILE_STATUS)){var r=t.getShaderInfoLog(i);console.error("*** Error compiling shader '"+i+"':"+r),t.deleteShader(i)}return i}function T(t,e){var o=g(t,"VERTEX_SHADER",e.vertex),i=g(t,"FRAGMENT_SHADER",e.fragment),r=t.createProgram();if(t.attachShader(r,o),t.attachShader(r,i),t.linkProgram(r),t.getProgramParameter(r,t.LINK_STATUS))return r;var n=t.getProgramInfoLog(r);return console.error("Error in program linking:"+n),t.deleteProgram(r),null}function x(i,r){let a;if("string"==typeof i)a=document.querySelector(i);else{if(!(i instanceof Element))throw new Error("Context must be either a string or an Element");a=i}const s=a.clientHeight,u=a.clientWidth,f=document.createElement("canvas"),h=f.getContext("webgl2",{premultipliedAlpha:!1,depth:!1,antialias:!0,alpha:!0,preserveDrawingBuffer:!1});n=function(t){const e=window.devicePixelRatio||1,o=t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1;return e/o}(h),h.clearColor(0,0,0,0),h.enable(h.BLEND),h.blendEquation(h.FUNC_ADD),h.blendFunc(h.ONE,h.ONE_MINUS_SRC_ALPHA),h.depthMask(!0),f.setAttribute("height",s*n),f.setAttribute("width",u*n),f.style.height=`${s}px`,f.style.width=`${u}px`,f.style.position="absolute",a.appendChild(f),this.gradient=function(t){const e=[],o=t.length,i=[];return t.forEach((function(t){e.push(t.color[0]/255),e.push(t.color[1]/255),e.push(t.color[2]/255),e.push(void 0===t.color[3]?1:t.color[3]),i.push(t.offset)})),{value:new Float32Array(e),length:o,offset:i}}(r.gradient),this.ctx=h,this.width=u,this.height=s,this.layer=f,this.dom=a,this.gradShadOP=function(e){var o=T(e,t);return{program:o,attr:[{bufferType:e.ARRAY_BUFFER,buffer:e.createBuffer(),drawType:e.STATIC_DRAW,valueType:e.FLOAT,size:2,attribute:e.getAttribLocation(o,"a_position"),data:new Float32Array([])},{bufferType:e.ARRAY_BUFFER,buffer:e.createBuffer(),drawType:e.STATIC_DRAW,valueType:e.FLOAT,size:1,attribute:e.getAttribLocation(o,"a_intensity"),data:new Float32Array([])}],uniform:{u_resolution:e.getUniformLocation(o,"u_resolution"),u_max:e.getUniformLocation(o,"u_max"),u_min:e.getUniformLocation(o,"u_min"),u_size:e.getUniformLocation(o,"u_size"),u_intensity:e.getUniformLocation(o,"u_intensity"),u_translate:e.getUniformLocation(o,"u_translate"),u_zoom:e.getUniformLocation(o,"u_zoom"),u_angle:e.getUniformLocation(o,"u_angle"),u_density:e.getUniformLocation(o,"u_density")}}}(this.ctx),this.colorShadOP=function(t){var o=T(t,e);return{program:o,attr:[{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:2,attribute:t.getAttribLocation(o,"a_texCoord"),data:new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1])}],uniform:{u_framebuffer:t.getUniformLocation(o,"u_framebuffer"),u_colorArr:t.getUniformLocation(o,"u_colorArr"),u_colorCount:t.getUniformLocation(o,"u_colorCount"),u_opacity:t.getUniformLocation(o,"u_opacity"),u_offset:t.getUniformLocation(o,"u_offset")}}}(this.ctx),this.imageShaOP=function(t){var e=T(t,o);return{program:e,attr:[{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:2,attribute:t.getAttribLocation(e,"a_position"),data:new Float32Array([])},{bufferType:t.ARRAY_BUFFER,buffer:t.createBuffer(),drawType:t.STATIC_DRAW,valueType:t.FLOAT,size:2,attribute:t.getAttribLocation(e,"a_texCoord"),data:new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1])}],uniform:{u_resolution:t.getUniformLocation(e,"u_resolution"),u_image:t.getUniformLocation(e,"u_image"),u_translate:t.getUniformLocation(e,"u_translate"),u_zoom:t.getUniformLocation(e,"u_zoom"),u_angle:t.getUniformLocation(e,"u_angle"),u_density:t.getUniformLocation(e,"u_density")}}}(this.ctx),this.fbTexObj=h.createTexture(),this.fbo=h.createFramebuffer(),this.size=r.size?r.size:20,_=r.max?r.max:null,m=r.min?r.min:null,this.intensity=r.intensity?r.intensity:1,this.translate=r.translate&&2===r.translate.length?r.translate:[0,0],this.zoom=r.zoom?r.zoom:1,this.angle=r.rotationAngle?r.rotationAngle:0,this.opacity=r.opacity?r.opacity:1,this.ratio=n,r.backgroundImage&&r.backgroundImage.url&&this.setBackgroundImage(r.backgroundImage),this.rawData=[],h.viewport(0,0,h.canvas.width,h.canvas.height),this.render(this.exData||{})}function p(t,e){t.useProgram(this.gradShadOP.program),this.min=null!==m?m:e?.minMax?.min??0,this.max=null!==_?_:e?.minMax?.max??0,this.gradShadOP.attr[0].data=e.posVec||[],this.gradShadOP.attr[1].data=e.rVec||[],t.uniform2fv(this.gradShadOP.uniform.u_resolution,new Float32Array([this.width*this.ratio,this.height*this.ratio])),t.uniform2fv(this.gradShadOP.uniform.u_translate,new Float32Array([this.translate[0],this.translate[1]])),t.uniform1f(this.gradShadOP.uniform.u_zoom,this.zoom?this.zoom:.01),t.uniform1f(this.gradShadOP.uniform.u_angle,this.angle),t.uniform1f(this.gradShadOP.uniform.u_density,this.ratio),t.uniform1f(this.gradShadOP.uniform.u_max,this.max),t.uniform1f(this.gradShadOP.uniform.u_min,this.min),t.uniform1f(this.gradShadOP.uniform.u_size,this.size),t.uniform1f(this.gradShadOP.uniform.u_intensity,this.intensity),this.gradShadOP.attr.forEach((function(e){t.bindBuffer(e.bufferType,e.buffer),t.bufferData(e.bufferType,e.data,e.drawType),t.enableVertexAttribArray(e.attribute),t.vertexAttribPointer(e.attribute,e.size,e.valueType,!0,0,0)})),t.drawArrays(t.POINTS,0,(this.exData.posVec||[]).length/2)}function y(t){const{x:e=0,y:o=0,width:i=0,height:r=0}=this.imageConfig;t.useProgram(this.imageShaOP.program),t.uniform2fv(this.imageShaOP.uniform.u_resolution,new Float32Array([this.width*this.ratio,this.height*this.ratio])),t.uniform2fv(this.imageShaOP.uniform.u_translate,new Float32Array([this.translate[0],this.translate[1]])),t.uniform1f(this.imageShaOP.uniform.u_zoom,this.zoom?this.zoom:.01),t.uniform1f(this.imageShaOP.uniform.u_angle,this.angle),t.uniform1f(this.imageShaOP.uniform.u_density,this.ratio),this.imageShaOP.attr[0].data=new Float32Array([e,o,e+i,o,e,o+r,e,o+r,e+i,o,e+i,o+r]),this.imageShaOP.attr.forEach((function(e){t.bindBuffer(e.bufferType,e.buffer),t.bufferData(e.bufferType,e.data,e.drawType),t.enableVertexAttribArray(e.attribute),t.vertexAttribPointer(e.attribute,e.size,e.valueType,!0,0,0)})),t.uniform1i(this.imageShaOP.uniform.u_image,0),t.activeTexture(this.ctx.TEXTURE0),t.bindTexture(this.ctx.TEXTURE_2D,this.imageTexture),t.drawArrays(t.TRIANGLES,0,6)}function v(t){t.useProgram(this.colorShadOP.program),t.uniform4fv(this.colorShadOP.uniform.u_colorArr,this.gradient.value),t.uniform1f(this.colorShadOP.uniform.u_colorCount,this.gradient.length),t.uniform1fv(this.colorShadOP.uniform.u_offset,new Float32Array(this.gradient.offset)),t.uniform1f(this.colorShadOP.uniform.u_opacity,this.opacity),this.colorShadOP.attr.forEach((function(e){t.bindBuffer(e.bufferType,e.buffer),t.bufferData(e.bufferType,e.data,e.drawType),t.enableVertexAttribArray(e.attribute),t.vertexAttribPointer(e.attribute,e.size,e.valueType,!0,0,0)})),t.uniform1i(this.colorShadOP.uniform.u_framebuffer,0),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.fbTexObj),t.drawArrays(t.TRIANGLES,0,6)}function E(t){const e=this.zoom||.1,o=this.width/2,i=this.height/2,r=this.angle;let n=(t.x-o)/o*e,a=(t.y-i)/i*e;if(0!==r){const t=Math.cos(r),e=Math.sin(r),o=t*n-e*a;a=e*n+t*a,n=o}return n=n*o+o-this.translate[0],a=a*i+i-this.translate[1],{x:n,y:a}}return x.prototype.resize=function(){const t=this.dom.clientHeight,e=this.dom.clientWidth;this.layer.setAttribute("height",t*n),this.layer.setAttribute("width",e*n),this.layer.style.height=`${t}px`,this.layer.style.width=`${e}px`,this.width=e,this.height=t,this.ctx.viewport(0,0,this.width*n,this.height*n),this.render(this.exData)},x.prototype.clear=function(){this.ctx.clear(this.ctx.COLOR_BUFFER_BIT|this.ctx.DEPTH_BUFFER_BIT)},x.prototype.setMax=function(t){_=t,this.render(this.exData)},x.prototype.setMin=function(t){m=t,this.render(this.exData)},x.prototype.setTranslate=function(t){this.translate=2===t.length?t:[0,0],this.render(this.exData)},x.prototype.setZoom=function(t){this.zoom=void 0!==t?t:1,this.render(this.exData)},x.prototype.setRotationAngle=function(t){this.angle=void 0!==t?t:0,this.render(this.exData)},x.prototype.setSize=function(t){this.size=void 0!==t?t:20,this.render(this.exData)},x.prototype.setIntensity=function(t){this.intensity=void 0!==t?t:1,this.render(this.exData)},x.prototype.setOpacity=function(t){this.opacity=void 0!==t?t:1,this.render(this.exData)},x.prototype.setBackgroundImage=function(t){const e=this;t.url&&(d=this.ctx.getParameter(this.ctx.MAX_TEXTURE_SIZE),this.imageTexture=this.ctx.createTexture(),this.type="TEXTURE_2D",this.imageConfig=null,u=t.width||this.width,f=t.height||this.height,u=u>d?d:u,f=f>d?d:f,function(t,e,o){const i=new Image;i.crossOrigin="anonymous",i.onload=e,i.onerror=o,i.src=t}(t.url,(function(){e.ctx.activeTexture(e.ctx.TEXTURE0),e.ctx.bindTexture(e.ctx.TEXTURE_2D,e.imageTexture),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_S,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_T,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MIN_FILTER,e.ctx.LINEAR),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MAG_FILTER,e.ctx.LINEAR),e.ctx.texImage2D(e.ctx.TEXTURE_2D,0,e.ctx.RGBA,this.naturalWidth,this.naturalHeight,0,e.ctx.RGBA,e.ctx.UNSIGNED_BYTE,this),e.imageConfig={x:t.x||0,y:t.y||0,height:f,width:u,image:this},e.render(e.exData||{})}),(function(t){console.error("Image Load Error",t)})))},x.prototype.addData=function(t,e){const o=this;for(let i=0;it[i].value&&(o.min=t[i].value),o.max 0.0 && alpha <= 1.0) {\n\t\t\t\t\t\t\tvec4 color_;\n\n\t\t\t\t\t\t\tif (alpha <= u_offset[0]) {\n\t\t\t\t\t\t\t\tcolor_ = u_colorArr[0];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfor (int i = 1; i <= 20; ++i) {\n\t\t\t\t\t\t\t\t\tif (alpha <= u_offset[i]) {\n\t\t\t\t\t\t\t\t\t\tcolor_ = mix( u_colorArr[i - 1], u_colorArr[i], remap( u_offset[i - 1], u_offset[i], alpha ) );\n\t\t\t\t\t\t\t\t\t\tcolor_ = color_ * mix( u_colorArr[i - 1][3], u_colorArr[i][3], remap( u_offset[i - 1], u_offset[i], alpha ));\n\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcolor_ = color_ * u_opacity;\n\t\t\t\t\t\t\tif (color_.a < 0.0) {\n\t\t\t\t\t\t\t\tcolor_.a = 0.0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfragColor = color_;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfragColor = vec4(0.0, 0.0, 0.0, 0.0);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t"},r={vertex:"#version 300 es\n precision highp float;\n in vec2 a_position;\n in vec2 a_texCoord;\n uniform vec2 u_resolution;\n\t\t\t\t\tuniform vec2 u_translate; \n\t\t\t\t\tuniform float u_zoom; \n\t\t\t\t\tuniform float u_angle; \n\t\t\t\t\tuniform float u_density;\n out vec2 v_texCoord;\n\n vec2 rotation(vec2 v, float a, float aspect) {\n\t\t\t\t\t\tfloat s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c);\n\t\t\t\t\t\tmat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\tmat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0);\n\t\t\t\t\t\treturn scaleMatInv * m * scaleMat * v;\n\t\t\t\t\t}\n\n void main() {\n \tvec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution);\n \tzeroToOne.y = 1.0 - zeroToOne.y;\n\t\t\t\t\t\tvec2 zeroToTwo = zeroToOne * 2.0 - 1.0;\n\t\t\t\t\t\tfloat zoomFactor = u_zoom;\n\t\t\t\t\t\tif (zoomFactor == 0.0) {\n\t\t\t\t\t\t\tzoomFactor = 0.1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tzeroToTwo = zeroToTwo / zoomFactor;\n\t\t\t\t\t\tif (u_angle != 0.0) {\n\t\t\t\t\t\t\tzeroToTwo = rotation(zeroToTwo, u_angle * -1.0, u_resolution.x / u_resolution.y);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tgl_Position = vec4(zeroToTwo , 0, 1);\n\t\t\t\t\t\tv_texCoord = a_texCoord;\n }\n \t\t",fragment:"#version 300 es\n precision mediump float;\n uniform sampler2D u_image;\n in vec2 v_texCoord;\n out vec4 fragColor;\n void main() {\n fragColor = texture(u_image, v_texCoord);\n }\n "};return function(o,i={}){let n,a,s,u,f,h,c,l=[],m=[],_=0,d=null,g=0,T=0;function p(t){return null==t}function x(t){return"number"!=typeof t}function y(t){if(t.constructor!==Array)throw new Error("Invalid gradient: Wrong Gradient type, expected Array");if(t.length<2)throw new Error("Invalid gradient: 2 or more values expected");if(!function(t){for(let e=0;e1||t<0)throw this.intensity=t>1?1:0,new Error("Invalid Intensity value "+t);return this.intensity=t,this},A.prototype.setOpacity=function(t){if(p(t)||x(t))throw new Error("Invalid Opacity: Expected Number");if(t>1||t<0)throw new Error("Invalid Opacity value "+t);return this.opacity=t,this},A.prototype.setBackgroundImage=function(t){const e=this;if(t.url)return d=this.ctx.getParameter(this.ctx.MAX_TEXTURE_SIZE),this.imageTexture=this.ctx.createTexture(),this.type="TEXTURE_2D",c=null,u=t.width||this.width,f=t.height||this.height,u=u>d?d:u,f=f>d?d:f,function(t,e,r){const o=new Image;o.crossOrigin="anonymous",o.onload=e,o.onerror=r,o.src=t}(t.url,(function(){e.ctx.activeTexture(e.ctx.TEXTURE0),e.ctx.bindTexture(e.ctx.TEXTURE_2D,e.imageTexture),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_S,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_WRAP_T,e.ctx.CLAMP_TO_EDGE),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MIN_FILTER,e.ctx.LINEAR),e.ctx.texParameteri(e.ctx.TEXTURE_2D,e.ctx.TEXTURE_MAG_FILTER,e.ctx.LINEAR),e.ctx.texImage2D(e.ctx.TEXTURE_2D,0,e.ctx.RGBA,this.naturalWidth,this.naturalHeight,0,e.ctx.RGBA,e.ctx.UNSIGNED_BYTE,this),c={x:t.x||0,y:t.y||0,height:f,width:u,image:this},e.render()}),(function(t){throw new Error("Image Load Error",t)})),this},A.prototype.clearData=function(){this.heatmapData=[],h={},this.render()},A.prototype.addData=function(t,e){const r=this;for(let o=0;ot[o].value&&(r.min=t[o].value),r.max 1 || intensity < 0) { + this.intensity = intensity > 1 ? 1 : 0; // Setting bound value + throw new Error('Invalid Intensity value ' + intensity); + } + this.intensity = intensity; + return this; }; Chart.prototype.setOpacity = function (opacity) { - this.opacity = opacity !== undefined ? opacity : 1.0; - this.render(this.exData); + if (isNullUndefined(opacity) || isNotNumber(opacity)) { + throw new Error('Invalid Opacity: Expected Number'); + } + + if (opacity > 1 || opacity < 0) { + throw new Error('Invalid Opacity value ' + opacity); + } + this.opacity = opacity; + return this; }; Chart.prototype.setBackgroundImage = function (config) { @@ -320,7 +442,7 @@ export default function Heatmap (context, config = {}) { maxTextureSize = this.ctx.getParameter(this.ctx.MAX_TEXTURE_SIZE); this.imageTexture = this.ctx.createTexture(); this.type = 'TEXTURE_2D'; - this.imageConfig = null; + imageConfig = null; imgWidth = config.width || this.width; imgHeight = config.height || this.height; @@ -348,7 +470,7 @@ export default function Heatmap (context, config = {}) { this ); - self.imageConfig = { + imageConfig = { x: config.x || 0, y: config.y || 0, height: imgHeight, @@ -356,10 +478,17 @@ export default function Heatmap (context, config = {}) { image: this }; - self.render(self.exData || {}); + self.render(); }, function onErrorCallBack (error) { - console.error('Image Load Error', error); + throw new Error('Image Load Error', error); }); + return this; + }; + + Chart.prototype.clearData = function () { + this.heatmapData = []; + hearmapExData = {}; + this.render(); }; Chart.prototype.addData = function (data, transIntactFlag) { @@ -368,15 +497,24 @@ export default function Heatmap (context, config = {}) { if (transIntactFlag) { transCoOr.call(self, data[i]); } - this.rawData.push(data[i]); + this.heatmapData.push(data[i]); } - this.renderData(this.rawData); + this.renderData(this.heatmapData); + return this; }; Chart.prototype.renderData = function (data) { - const exData = extractData(data); - this.rawData = data; - this.render(exData); + if (data.constructor !== Array) { + throw new Error('Expected Array type'); + } + hearmapExData = extractData(data); + this.heatmapData = data; + this.render(); + return this; + }; + + Chart.prototype.render = function () { + renderExec.call(this); }; Chart.prototype.projection = function (data) { @@ -413,9 +551,8 @@ export default function Heatmap (context, config = {}) { return { x: posX, y: posY }; }; - Chart.prototype.render = function (exData) { + function renderExec () { const ctx = this.ctx; - this.exData = exData; ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT); @@ -428,10 +565,12 @@ export default function Heatmap (context, config = {}) { ctx.bindFramebuffer(ctx.FRAMEBUFFER, this.fbo); ctx.framebufferTexture2D(ctx.FRAMEBUFFER, ctx.COLOR_ATTACHMENT0, ctx.TEXTURE_2D, this.fbTexObj, 0); - renderHeatGrad.call(this, ctx, exData); + if (hearmapExData) { + renderHeatGrad.call(this, ctx, hearmapExData); + } ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); - if (this.imageConfig) { - renderImage.call(this, ctx); + if (imageConfig) { + renderImage.call(this, ctx, imageConfig); } renderColorGradiant.call(this, ctx); }; @@ -439,8 +578,8 @@ export default function Heatmap (context, config = {}) { function renderHeatGrad (ctx, exData) { ctx.useProgram(this.gradShadOP.program); - this.min = dataMinValue !== null ? dataMinValue : exData?.minMax?.min ?? 0; - this.max = dataMaxValue !== null ? dataMaxValue : exData?.minMax?.max ?? 0; + this.min = configMin !== null ? configMin : exData?.minMax?.min ?? 0; + this.max = configMax !== null ? configMax : exData?.minMax?.max ?? 0; this.gradShadOP.attr[0].data = exData.posVec || []; this.gradShadOP.attr[1].data = exData.rVec || []; @@ -461,11 +600,11 @@ export default function Heatmap (context, config = {}) { ctx.vertexAttribPointer(d.attribute, d.size, d.valueType, true, 0, 0); }); - ctx.drawArrays(ctx.POINTS, 0, (this.exData.posVec || []).length / 2); + ctx.drawArrays(ctx.POINTS, 0, (exData.posVec || []).length / 2); } - function renderImage (ctx) { - const { x = 0, y = 0, width = 0, height = 0 } = this.imageConfig; + function renderImage (ctx, imageConfig) { + const { x = 0, y = 0, width = 0, height = 0 } = imageConfig; ctx.useProgram(this.imageShaOP.program); @@ -535,6 +674,9 @@ export default function Heatmap (context, config = {}) { posX = (posX * halfWidth) + halfWidth - this.translate[0]; posY = (posY * halfHeight) + halfHeight - this.translate[1]; + data.x = posX; + data.y = posY; + return { x: posX, y: posY }; } @@ -564,57 +706,51 @@ function getPixlRatio (ctx) { var GradShaders = { vertex: `#version 300 es - in vec2 a_position; - in float a_intensity; - uniform float u_size; - uniform vec2 u_resolution; - uniform vec2 u_translate; - uniform float u_zoom; - uniform float u_angle; - uniform float u_density; - out float v_i; - - vec2 rotation(vec2 v, float a, float aspect) { - float s = sin(a); float c = cos(a); mat2 m = mat2(c, -s, s, c); - mat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0); - mat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0); - return scaleMatInv * m * scaleMat * v; - } + in vec2 a_position; + in float a_intensity; + uniform float u_size; + uniform vec2 u_resolution; + uniform vec2 u_translate; + uniform float u_zoom; + uniform float u_angle; + uniform float u_density; + out float v_i; + + vec2 rotation(vec2 v, float a, float aspect) { + float s = sin(a); float c = cos(a); mat2 rotationMat = mat2(c, -s, s, c); + mat2 scaleMat = mat2(aspect, 0.0, 0.0, 1.0); + mat2 scaleMatInv = mat2(1.0/aspect, 0.0, 0.0, 1.0); + return scaleMatInv * rotationMat * scaleMat * v; + } - void main() { - vec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution); - vec2 zeroToTwo = zeroToOne * 2.0 - 1.0; - float zoomFactor = u_zoom; - if (zoomFactor == 0.0) { - zoomFactor = 0.1; - } - zeroToTwo = zeroToTwo / zoomFactor; - if (u_angle != 0.0) { - zeroToTwo = rotation(zeroToTwo, u_angle, u_resolution.x / u_resolution.y); - } - gl_Position = vec4(zeroToTwo , 0, 1); - gl_PointSize = u_size * u_density; - v_i = a_intensity; - }`, + void main() { + vec2 zeroToOne = (a_position * u_density + u_translate * u_density) / (u_resolution); + vec2 zeroToTwo = zeroToOne * 2.0 - 1.0; + float zoomFactor = max(u_zoom, 0.1); + zeroToTwo = zeroToTwo / zoomFactor; + if (u_angle != 0.0) { + zeroToTwo = rotation(zeroToTwo, u_angle, u_resolution.x / u_resolution.y); + } + gl_Position = vec4(zeroToTwo , 0, 1); + gl_PointSize = u_size * u_density; + v_i = a_intensity; + }`, fragment: `#version 300 es - precision mediump float; - uniform float u_max; - uniform float u_min; - uniform float u_intensity; - in float v_i; - out vec4 fragColor; - void main() { - float r = 0.0; - vec2 cxy = 2.0 * gl_PointCoord - 1.0; - r = dot(cxy, cxy); - float deno = u_max - u_min; - if (deno <= 0.0) { - deno = 1.0; - } - if(r <= 1.0) { - fragColor = vec4(0, 0, 0, ((v_i - u_min) / (deno)) * u_intensity * (1.0 - sqrt(r))); - } - }` + precision mediump float; + uniform float u_max; + uniform float u_min; + uniform float u_intensity; + in float v_i; + out vec4 fragColor; + void main() { + float r = 0.0; + vec2 cxy = 2.0 * gl_PointCoord - 1.0; + r = dot(cxy, cxy); + float deno = max(u_max - u_min, 1.0); + if(r <= 1.0) { + fragColor = vec4(0, 0, 0, ((v_i - u_min) / (deno)) * u_intensity * (1.0 - sqrt(r))); + } + }` }; var ColorShader = { @@ -650,7 +786,7 @@ var ColorShader = { if (alpha <= u_offset[0]) { color_ = u_colorArr[0]; } else { - for (int i = 1; i <= 10; ++i) { + for (int i = 1; i <= 20; ++i) { if (alpha <= u_offset[i]) { color_ = mix( u_colorArr[i - 1], u_colorArr[i], remap( u_offset[i - 1], u_offset[i], alpha ) ); color_ = color_ * mix( u_colorArr[i - 1][3], u_colorArr[i][3], remap( u_offset[i - 1], u_offset[i], alpha ));