├── .gitignore ├── .npmignore ├── LICENSE ├── deep-sea-gradient.png ├── example └── main.js ├── fragment-shader-blit.glsl ├── framebuffer.js ├── heatmap-heights.js ├── heatmap-node.js ├── heatmap-shader.js ├── heatmap-texture.js ├── index.js ├── package.json ├── readme.md ├── skyline-gradient.png ├── vertex-shader-blit.glsl └── webgl-heatmap.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | deep-sea-gradient.png 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Victor Powell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | 24 | Copyright (c) 2012 Florian Boesch 25 | http://codeflow.com/ 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining 28 | a copy of this software and associated documentation files (the 29 | "Software"), to deal in the Software without restriction, including 30 | without limitation the rights to use, copy, modify, merge, publish, 31 | distribute, sublicense, and/or sell copies of the Software, and to 32 | permit persons to whom the Software is furnished to do so, subject to 33 | the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be 36 | included in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 39 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 41 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 42 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 43 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 44 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /deep-sea-gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicapow/webgl-heatmap/86274a3980dcfe6e7dac1bcefa1c80a107b65d6d/deep-sea-gradient.png -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var WebGLHeatmap = require('../index'); 4 | var fs = require('fs'); 5 | 6 | window.onload = function onload() { 7 | var canvas = document.createElement('canvas'); 8 | canvas.style.width = '100%'; 9 | canvas.style.height = '100%'; 10 | document.body.appendChild(canvas); 11 | var gradient = fs.readFileSync('./deep-sea-gradient.png', 'base64'); 12 | var gradient = fs.readFileSync('./skyline-gradient.png', 'base64'); 13 | var image = new Image(); 14 | image.src = 'data:image/png;base64,' + gradient; 15 | var heatmap = new WebGLHeatmap({ 16 | canvas: canvas, 17 | intensityToAlpha: true, 18 | alphaRange: [0, 1], 19 | // inverse transparency 20 | // alphaRange: [1, 0], 21 | // steep transparency 22 | // alphaRange: [0, 0.05], 23 | gradientTexture: image 24 | }); 25 | 26 | document.body.appendChild(heatmap.canvas); 27 | 28 | var paintAtCoord = function paintAtCoord(x, y) { 29 | var count = 0; 30 | while(count < 200){ 31 | var xoff = Math.random()*2-1; 32 | var yoff = Math.random()*2-1; 33 | var l = xoff*xoff + yoff*yoff; 34 | if(l > 1){ 35 | continue; 36 | } 37 | var ls = Math.sqrt(l); 38 | xoff/=ls; yoff/=ls; 39 | xoff*=1-l; yoff*=1-l; 40 | count += 1; 41 | heatmap.addPoint(x+xoff*50, y+yoff*50, 30, 2/300); 42 | } 43 | } 44 | 45 | // event handling 46 | var onTouchMove = function(evt){ 47 | evt.preventDefault(); 48 | var touches = evt.changedTouches; 49 | for(var i=0; i _ref; i = 0 <= _ref ? ++_i : --_i) { 42 | this.vertexBufferViews.push(new Float32Array(this.vertexBufferData.buffer, 0, i * this.vertexSize * 6)); 43 | } 44 | this.bufferIndex = 0; 45 | this.pointCount = 0; 46 | } 47 | 48 | HeatmapHeights.prototype.resize = function(width, height) { 49 | this.width = width; 50 | this.height = height; 51 | this.nodeBack.resize(this.width, this.height); 52 | return this.nodeFront.resize(this.width, this.height); 53 | }; 54 | 55 | HeatmapHeights.prototype.update = function() { 56 | var intensityLoc, positionLoc; 57 | if (this.pointCount > 0) { 58 | this.gl.enable(this.gl.BLEND); 59 | this.nodeFront.use(); 60 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); 61 | this.gl.bufferData(this.gl.ARRAY_BUFFER, this.vertexBufferViews[this.pointCount], this.gl.STREAM_DRAW); 62 | positionLoc = this.shader.attribLocation('position'); 63 | intensityLoc = this.shader.attribLocation('intensity'); 64 | this.gl.enableVertexAttribArray(1); 65 | this.gl.vertexAttribPointer(positionLoc, 4, this.gl.FLOAT, false, 8 * 4, 0 * 4); 66 | this.gl.vertexAttribPointer(intensityLoc, 4, this.gl.FLOAT, false, 8 * 4, 4 * 4); 67 | this.shader.use().vec2('viewport', this.width, this.height); 68 | this.gl.drawArrays(this.gl.TRIANGLES, 0, this.pointCount * 6); 69 | this.gl.disableVertexAttribArray(1); 70 | this.pointCount = 0; 71 | this.bufferIndex = 0; 72 | this.nodeFront.end(); 73 | return this.gl.disable(this.gl.BLEND); 74 | } 75 | }; 76 | 77 | HeatmapHeights.prototype.clear = function() { 78 | this.nodeFront.use(); 79 | this.gl.clearColor(0, 0, 0, 1); 80 | this.gl.clear(this.gl.COLOR_BUFFER_BIT); 81 | return this.nodeFront.end(); 82 | }; 83 | 84 | HeatmapHeights.prototype.clamp = function(min, max) { 85 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); 86 | this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); 87 | this.nodeFront.bind(0); 88 | this.nodeBack.use(); 89 | this.clampShader.use().int('source', 0).float('low', min).float('high', max); 90 | this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); 91 | this.nodeBack.end(); 92 | return this.swap(); 93 | }; 94 | 95 | HeatmapHeights.prototype.multiply = function(value) { 96 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); 97 | this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); 98 | this.nodeFront.bind(0); 99 | this.nodeBack.use(); 100 | this.multiplyShader.use().int('source', 0).float('value', value); 101 | this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); 102 | this.nodeBack.end(); 103 | return this.swap(); 104 | }; 105 | 106 | HeatmapHeights.prototype.blur = function() { 107 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); 108 | this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); 109 | this.nodeFront.bind(0); 110 | this.nodeBack.use(); 111 | this.blurShader.use().int('source', 0).vec2('viewport', this.width, this.height); 112 | this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); 113 | this.nodeBack.end(); 114 | return this.swap(); 115 | }; 116 | 117 | HeatmapHeights.prototype.swap = function() { 118 | var tmp; 119 | tmp = this.nodeFront; 120 | this.nodeFront = this.nodeBack; 121 | return this.nodeBack = tmp; 122 | }; 123 | 124 | HeatmapHeights.prototype.addVertex = function(x, y, xs, ys, intensity) { 125 | this.vertexBufferData[this.bufferIndex++] = x; 126 | this.vertexBufferData[this.bufferIndex++] = y; 127 | this.vertexBufferData[this.bufferIndex++] = xs; 128 | this.vertexBufferData[this.bufferIndex++] = ys; 129 | this.vertexBufferData[this.bufferIndex++] = intensity; 130 | this.vertexBufferData[this.bufferIndex++] = intensity; 131 | this.vertexBufferData[this.bufferIndex++] = intensity; 132 | return this.vertexBufferData[this.bufferIndex++] = intensity; 133 | }; 134 | 135 | HeatmapHeights.prototype.addPoint = function(x, y, size, intensity) { 136 | var s; 137 | if (size == null) { 138 | size = 50; 139 | } 140 | if (intensity == null) { 141 | intensity = 0.2; 142 | } 143 | if (this.pointCount >= this.maxPointCount - 1) { 144 | this.update(); 145 | } 146 | y = this.height - y; 147 | s = size / 2; 148 | this.addVertex(x, y, -s, -s, intensity); 149 | this.addVertex(x, y, +s, -s, intensity); 150 | this.addVertex(x, y, -s, +s, intensity); 151 | this.addVertex(x, y, -s, +s, intensity); 152 | this.addVertex(x, y, +s, -s, intensity); 153 | this.addVertex(x, y, +s, +s, intensity); 154 | return this.pointCount += 1; 155 | }; 156 | 157 | module.exports = HeatmapHeights; -------------------------------------------------------------------------------- /heatmap-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HeatmapTexture = require('./heatmap-texture'); 4 | var Framebuffer = require('./framebuffer'); 5 | 6 | function HeatmapNode(gl, width, height) { 7 | var floatExt; 8 | this.gl = gl; 9 | this.width = width; 10 | this.height = height; 11 | floatExt = this.gl.getFloatExtension({ 12 | require: ['renderable'] 13 | }); 14 | this.texture = new HeatmapTexture(this.gl, { 15 | type: floatExt.type 16 | }).bind(0).setSize(this.width, this.height).nearest().clampToEdge(); 17 | this.fbo = new Framebuffer(this.gl).bind().color(this.texture).unbind(); 18 | } 19 | 20 | HeatmapNode.prototype.use = function() { 21 | return this.fbo.bind(); 22 | }; 23 | 24 | HeatmapNode.prototype.bind = function(unit) { 25 | return this.texture.bind(unit); 26 | }; 27 | 28 | HeatmapNode.prototype.end = function() { 29 | return this.fbo.unbind(); 30 | }; 31 | 32 | HeatmapNode.prototype.resize = function(width, height) { 33 | this.width = width; 34 | this.height = height; 35 | return this.texture.bind(0).setSize(this.width, this.height); 36 | }; 37 | 38 | module.exports = HeatmapNode; 39 | -------------------------------------------------------------------------------- /heatmap-shader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function HeatmapShader(gl, _arg) { 4 | var fragment, vertex; 5 | this.gl = gl; 6 | vertex = _arg.vertex, fragment = _arg.fragment; 7 | this.program = this.gl.createProgram(); 8 | this.vs = this.gl.createShader(this.gl.VERTEX_SHADER); 9 | this.fs = this.gl.createShader(this.gl.FRAGMENT_SHADER); 10 | this.gl.attachShader(this.program, this.vs); 11 | this.gl.attachShader(this.program, this.fs); 12 | this.compileShader(this.vs, vertex); 13 | this.compileShader(this.fs, fragment); 14 | this.link(); 15 | this.value_cache = {}; 16 | this.uniform_cache = {}; 17 | this.attribCache = {}; 18 | } 19 | 20 | HeatmapShader.prototype.attribLocation = function(name) { 21 | var location; 22 | location = this.attribCache[name]; 23 | if (location === void 0) { 24 | location = this.attribCache[name] = this.gl.getAttribLocation(this.program, name); 25 | } 26 | return location; 27 | }; 28 | 29 | HeatmapShader.prototype.compileShader = function(shader, source) { 30 | this.gl.shaderSource(shader, source); 31 | this.gl.compileShader(shader); 32 | if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { 33 | throw "Shader Compile Error: " + (this.gl.getShaderInfoLog(shader)); 34 | } 35 | }; 36 | 37 | HeatmapShader.prototype.link = function() { 38 | this.gl.linkProgram(this.program); 39 | if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) { 40 | throw "Shader Link Error: " + (this.gl.getProgramInfoLog(this.program)); 41 | } 42 | }; 43 | 44 | HeatmapShader.prototype.use = function() { 45 | this.gl.useProgram(this.program); 46 | return this; 47 | }; 48 | 49 | HeatmapShader.prototype.uniformLoc = function(name) { 50 | var location; 51 | location = this.uniform_cache[name]; 52 | if (location === void 0) { 53 | location = this.uniform_cache[name] = this.gl.getUniformLocation(this.program, name); 54 | } 55 | return location; 56 | }; 57 | 58 | HeatmapShader.prototype.int = function(name, value) { 59 | var cached, loc; 60 | cached = this.value_cache[name]; 61 | if (cached !== value) { 62 | this.value_cache[name] = value; 63 | loc = this.uniformLoc(name); 64 | if (loc) { 65 | this.gl.uniform1i(loc, value); 66 | } 67 | } 68 | return this; 69 | }; 70 | 71 | HeatmapShader.prototype.vec2 = function(name, a, b) { 72 | var loc; 73 | loc = this.uniformLoc(name); 74 | if (loc) { 75 | this.gl.uniform2f(loc, a, b); 76 | } 77 | return this; 78 | }; 79 | 80 | HeatmapShader.prototype.float = function(name, value) { 81 | var cached, loc; 82 | cached = this.value_cache[name]; 83 | if (cached !== value) { 84 | this.value_cache[name] = value; 85 | loc = this.uniformLoc(name); 86 | if (loc) { 87 | this.gl.uniform1f(loc, value); 88 | } 89 | } 90 | return this; 91 | }; 92 | 93 | module.exports = HeatmapShader; 94 | -------------------------------------------------------------------------------- /heatmap-texture.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function HeatmapTexture(gl, params) { 4 | var _ref, _ref1; 5 | this.gl = gl; 6 | if (params == null) { 7 | params = {}; 8 | } 9 | this.channels = this.gl[((_ref = params.channels) != null ? _ref : 'rgba').toUpperCase()]; 10 | if (typeof params.type === 'number') { 11 | this.type = params.type; 12 | } else { 13 | this.type = this.gl[((_ref1 = params.type) != null ? _ref1 : 'unsigned_byte').toUpperCase()]; 14 | } 15 | switch (this.channels) { 16 | case this.gl.RGBA: 17 | this.chancount = 4; 18 | break; 19 | case this.gl.RGB: 20 | this.chancount = 3; 21 | break; 22 | case this.gl.LUMINANCE_ALPHA: 23 | this.chancount = 2; 24 | break; 25 | default: 26 | this.chancount = 1; 27 | } 28 | this.target = this.gl.TEXTURE_2D; 29 | this.handle = this.gl.createTexture(); 30 | } 31 | 32 | HeatmapTexture.prototype.destroy = function() { 33 | return this.gl.deleteTexture(this.handle); 34 | }; 35 | 36 | HeatmapTexture.prototype.bind = function(unit) { 37 | if (unit == null) { 38 | unit = 0; 39 | } 40 | if (unit > 15) { 41 | throw 'Texture unit too large: ' + unit; 42 | } 43 | this.gl.activeTexture(this.gl.TEXTURE0 + unit); 44 | this.gl.bindTexture(this.target, this.handle); 45 | return this; 46 | }; 47 | 48 | HeatmapTexture.prototype.setSize = function(width, height) { 49 | this.width = width; 50 | this.height = height; 51 | this.gl.texImage2D(this.target, 0, this.channels, this.width, this.height, 0, this.channels, this.type, null); 52 | return this; 53 | }; 54 | 55 | HeatmapTexture.prototype.upload = function(data) { 56 | this.width = data.width; 57 | this.height = data.height; 58 | this.gl.texImage2D(this.target, 0, this.channels, this.channels, this.type, data); 59 | return this; 60 | }; 61 | 62 | HeatmapTexture.prototype.linear = function() { 63 | this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); 64 | this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); 65 | return this; 66 | }; 67 | 68 | HeatmapTexture.prototype.nearest = function() { 69 | this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); 70 | this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); 71 | return this; 72 | }; 73 | 74 | HeatmapTexture.prototype.clampToEdge = function() { 75 | this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); 76 | this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); 77 | return this; 78 | }; 79 | 80 | HeatmapTexture.prototype.repeat = function() { 81 | this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT); 82 | this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT); 83 | return this; 84 | }; 85 | 86 | module.exports = HeatmapTexture; 87 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | 3 | var WebGLHeatmap = require('./webgl-heatmap'); 4 | var HeatmapTexture = require('./heatmap-texture'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | /* eslint-disable max-len */ 8 | var vertexShaderBlit = fs.readFileSync(path.join(__dirname, './vertex-shader-blit.glsl'), 'utf-8'); 9 | var fragmentShaderBlit = fs.readFileSync(path.join(__dirname, './fragment-shader-blit.glsl'), 'utf-8'); 10 | /* eslint-enable max-len */ 11 | 12 | var __indexOf = [].indexOf || function(item) { 13 | for (var i = 0, l = this.length; i < l; i++) { 14 | if (i in this && this[i] === item) { 15 | return i; 16 | } 17 | } 18 | return -1; 19 | }; 20 | 21 | function nukeVendorPrefix() { 22 | var getExtension, getSupportedExtensions, vendorRe, vendors; 23 | if (window.WebGLRenderingContext != null) { 24 | vendors = ['WEBKIT', 'MOZ', 'MS', 'O']; 25 | vendorRe = /^WEBKIT_(.*)|MOZ_(.*)|MS_(.*)|O_(.*)/; 26 | getExtension = WebGLRenderingContext.prototype.getExtension; 27 | WebGLRenderingContext.prototype.getExtension = function(name) { 28 | var extobj, match, vendor, _i, _len; 29 | match = name.match(vendorRe); 30 | if (match !== null) { 31 | name = match[1]; 32 | } 33 | extobj = getExtension.call(this, name); 34 | if (extobj === null) { 35 | for (_i = 0, _len = vendors.length; _i < _len; _i++) { 36 | vendor = vendors[_i]; 37 | extobj = getExtension.call(this, vendor + '_' + name); 38 | if (extobj !== null) { 39 | return extobj; 40 | } 41 | } 42 | return null; 43 | } else { 44 | return extobj; 45 | } 46 | }; 47 | getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions; 48 | return WebGLRenderingContext.prototype.getSupportedExtensions = function() { 49 | var extension, match, result, supported, _i, _len; 50 | supported = getSupportedExtensions.call(this); 51 | result = []; 52 | for (_i = 0, _len = supported.length; _i < _len; _i++) { 53 | extension = supported[_i]; 54 | match = extension.match(vendorRe); 55 | if (match !== null) { 56 | extension = match[1]; 57 | } 58 | if (__indexOf.call(result, extension) < 0) { 59 | result.push(extension); 60 | } 61 | } 62 | return result; 63 | }; 64 | } 65 | }; 66 | 67 | function textureFloatShims() { 68 | var checkColorBuffer, checkFloatLinear, checkSupport, checkTexture, createSourceCanvas, getExtension, getSupportedExtensions, name, shimExtensions, shimLookup, unshimExtensions, unshimLookup, _i, _len; 69 | createSourceCanvas = function() { 70 | var canvas, ctx, imageData; 71 | canvas = document.createElement('canvas'); 72 | canvas.width = 2; 73 | canvas.height = 2; 74 | ctx = canvas.getContext('2d'); 75 | imageData = ctx.getImageData(0, 0, 2, 2); 76 | imageData.data.set(new Uint8ClampedArray([0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255])); 77 | ctx.putImageData(imageData, 0, 0); 78 | return canvas; 79 | }; 80 | createSourceCanvas(); 81 | checkFloatLinear = function(gl, sourceType) { 82 | var buffer, cleanup, fragmentShader, framebuffer, positionLoc, program, readBuffer, result, source, sourceCanvas, sourceLoc, target, vertexShader, vertices; 83 | program = gl.createProgram(); 84 | vertexShader = gl.createShader(gl.VERTEX_SHADER); 85 | gl.attachShader(program, vertexShader); 86 | gl.shaderSource(vertexShader, 'attribute vec2 position;\nvoid main(){\n gl_Position = vec4(position, 0.0, 1.0);\n}'); 87 | gl.compileShader(vertexShader); 88 | if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { 89 | throw gl.getShaderInfoLog(vertexShader); 90 | } 91 | fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 92 | gl.attachShader(program, fragmentShader); 93 | gl.shaderSource(fragmentShader, 'uniform sampler2D source;\nvoid main(){\n gl_FragColor = texture2D(source, vec2(1.0, 1.0));\n}'); 94 | gl.compileShader(fragmentShader); 95 | if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { 96 | throw gl.getShaderInfoLog(fragmentShader); 97 | } 98 | gl.linkProgram(program); 99 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 100 | throw gl.getProgramInfoLog(program); 101 | } 102 | gl.useProgram(program); 103 | cleanup = function() { 104 | gl.deleteShader(fragmentShader); 105 | gl.deleteShader(vertexShader); 106 | gl.deleteProgram(program); 107 | gl.deleteBuffer(buffer); 108 | gl.deleteTexture(source); 109 | gl.deleteTexture(target); 110 | gl.deleteFramebuffer(framebuffer); 111 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 112 | gl.useProgram(null); 113 | gl.bindTexture(gl.TEXTURE_2D, null); 114 | return gl.bindFramebuffer(gl.FRAMEBUFFER, null); 115 | }; 116 | target = gl.createTexture(); 117 | gl.bindTexture(gl.TEXTURE_2D, target); 118 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 119 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 120 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 121 | framebuffer = gl.createFramebuffer(); 122 | gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); 123 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0); 124 | sourceCanvas = createSourceCanvas(); 125 | source = gl.createTexture(); 126 | gl.bindTexture(gl.TEXTURE_2D, source); 127 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, sourceType, sourceCanvas); 128 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 129 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 130 | vertices = new Float32Array([1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1]); 131 | buffer = gl.createBuffer(); 132 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 133 | gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); 134 | positionLoc = gl.getAttribLocation(program, 'position'); 135 | sourceLoc = gl.getUniformLocation(program, 'source'); 136 | gl.enableVertexAttribArray(positionLoc); 137 | gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0); 138 | gl.uniform1i(sourceLoc, 0); 139 | gl.drawArrays(gl.TRIANGLES, 0, 6); 140 | readBuffer = new Uint8Array(4 * 4); 141 | gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, readBuffer); 142 | result = Math.abs(readBuffer[0] - 127) < 10; 143 | cleanup(); 144 | return result; 145 | }; 146 | checkTexture = function(gl, targetType) { 147 | var target; 148 | target = gl.createTexture(); 149 | gl.bindTexture(gl.TEXTURE_2D, target); 150 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null); 151 | if (gl.getError() === 0) { 152 | gl.deleteTexture(target); 153 | return true; 154 | } else { 155 | gl.deleteTexture(target); 156 | return false; 157 | } 158 | }; 159 | checkColorBuffer = function(gl, targetType) { 160 | var check, framebuffer, target; 161 | target = gl.createTexture(); 162 | gl.bindTexture(gl.TEXTURE_2D, target); 163 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null); 164 | framebuffer = gl.createFramebuffer(); 165 | gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); 166 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0); 167 | check = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 168 | gl.deleteTexture(target); 169 | gl.deleteFramebuffer(framebuffer); 170 | gl.bindTexture(gl.TEXTURE_2D, null); 171 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 172 | if (check === gl.FRAMEBUFFER_COMPLETE) { 173 | return true; 174 | } else { 175 | return false; 176 | } 177 | }; 178 | shimExtensions = []; 179 | shimLookup = {}; 180 | unshimExtensions = []; 181 | checkSupport = function() { 182 | var canvas, extobj, gl, halfFloatExt, halfFloatTexturing, singleFloatExt, singleFloatTexturing; 183 | canvas = document.createElement('canvas'); 184 | gl = null; 185 | try { 186 | gl = canvas.getContext('experimental-webgl'); 187 | if (gl === null) { 188 | gl = canvas.getContext('webgl'); 189 | } 190 | } catch (_error) {} 191 | if (gl != null) { 192 | singleFloatExt = gl.getExtension('OES_texture_float'); 193 | if (singleFloatExt === null) { 194 | if (checkTexture(gl, gl.FLOAT)) { 195 | singleFloatTexturing = true; 196 | shimExtensions.push('OES_texture_float'); 197 | shimLookup.OES_texture_float = { 198 | shim: true 199 | }; 200 | } else { 201 | singleFloatTexturing = false; 202 | unshimExtensions.push('OES_texture_float'); 203 | } 204 | } else { 205 | if (checkTexture(gl, gl.FLOAT)) { 206 | singleFloatTexturing = true; 207 | shimExtensions.push('OES_texture_float'); 208 | } else { 209 | singleFloatTexturing = false; 210 | unshimExtensions.push('OES_texture_float'); 211 | } 212 | } 213 | if (singleFloatTexturing) { 214 | extobj = gl.getExtension('WEBGL_color_buffer_float'); 215 | if (extobj === null) { 216 | if (checkColorBuffer(gl, gl.FLOAT)) { 217 | shimExtensions.push('WEBGL_color_buffer_float'); 218 | shimLookup.WEBGL_color_buffer_float = { 219 | shim: true, 220 | RGBA32F_EXT: 0x8814, 221 | RGB32F_EXT: 0x8815, 222 | FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211, 223 | UNSIGNED_NORMALIZED_EXT: 0x8C17 224 | }; 225 | } else { 226 | unshimExtensions.push('WEBGL_color_buffer_float'); 227 | } 228 | } else { 229 | if (checkColorBuffer(gl, gl.FLOAT)) { 230 | shimExtensions.push('WEBGL_color_buffer_float'); 231 | } else { 232 | unshimExtensions.push('WEBGL_color_buffer_float'); 233 | } 234 | } 235 | extobj = gl.getExtension('OES_texture_float_linear'); 236 | if (extobj === null) { 237 | if (checkFloatLinear(gl, gl.FLOAT)) { 238 | shimExtensions.push('OES_texture_float_linear'); 239 | shimLookup.OES_texture_float_linear = { 240 | shim: true 241 | }; 242 | } else { 243 | unshimExtensions.push('OES_texture_float_linear'); 244 | } 245 | } else { 246 | if (checkFloatLinear(gl, gl.FLOAT)) { 247 | shimExtensions.push('OES_texture_float_linear'); 248 | } else { 249 | unshimExtensions.push('OES_texture_float_linear'); 250 | } 251 | } 252 | } 253 | halfFloatExt = gl.getExtension('OES_texture_half_float'); 254 | if (halfFloatExt === null) { 255 | if (checkTexture(gl, 0x8D61)) { 256 | halfFloatTexturing = true; 257 | shimExtensions.push('OES_texture_half_float'); 258 | halfFloatExt = shimLookup.OES_texture_half_float = { 259 | HALF_FLOAT_OES: 0x8D61, 260 | shim: true 261 | }; 262 | } else { 263 | halfFloatTexturing = false; 264 | unshimExtensions.push('OES_texture_half_float'); 265 | } 266 | } else { 267 | if (checkTexture(gl, halfFloatExt.HALF_FLOAT_OES)) { 268 | halfFloatTexturing = true; 269 | shimExtensions.push('OES_texture_half_float'); 270 | } else { 271 | halfFloatTexturing = false; 272 | unshimExtensions.push('OES_texture_half_float'); 273 | } 274 | } 275 | if (halfFloatTexturing) { 276 | extobj = gl.getExtension('EXT_color_buffer_half_float'); 277 | if (extobj === null) { 278 | if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) { 279 | shimExtensions.push('EXT_color_buffer_half_float'); 280 | shimLookup.EXT_color_buffer_half_float = { 281 | shim: true, 282 | RGBA16F_EXT: 0x881A, 283 | RGB16F_EXT: 0x881B, 284 | FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211, 285 | UNSIGNED_NORMALIZED_EXT: 0x8C17 286 | }; 287 | } else { 288 | unshimExtensions.push('EXT_color_buffer_half_float'); 289 | } 290 | } else { 291 | if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) { 292 | shimExtensions.push('EXT_color_buffer_half_float'); 293 | } else { 294 | unshimExtensions.push('EXT_color_buffer_half_float'); 295 | } 296 | } 297 | extobj = gl.getExtension('OES_texture_half_float_linear'); 298 | if (extobj === null) { 299 | if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) { 300 | shimExtensions.push('OES_texture_half_float_linear'); 301 | return shimLookup.OES_texture_half_float_linear = { 302 | shim: true 303 | }; 304 | } else { 305 | return unshimExtensions.push('OES_texture_half_float_linear'); 306 | } 307 | } else { 308 | if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) { 309 | return shimExtensions.push('OES_texture_half_float_linear'); 310 | } else { 311 | return unshimExtensions.push('OES_texture_half_float_linear'); 312 | } 313 | } 314 | } 315 | } 316 | }; 317 | if (window.WebGLRenderingContext != null) { 318 | checkSupport(); 319 | unshimLookup = {}; 320 | for (_i = 0, _len = unshimExtensions.length; _i < _len; _i++) { 321 | name = unshimExtensions[_i]; 322 | unshimLookup[name] = true; 323 | } 324 | getExtension = WebGLRenderingContext.prototype.getExtension; 325 | WebGLRenderingContext.prototype.getExtension = function(name) { 326 | var extobj; 327 | extobj = shimLookup[name]; 328 | if (extobj === void 0) { 329 | if (unshimLookup[name]) { 330 | return null; 331 | } else { 332 | return getExtension.call(this, name); 333 | } 334 | } else { 335 | return extobj; 336 | } 337 | }; 338 | getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions; 339 | WebGLRenderingContext.prototype.getSupportedExtensions = function() { 340 | var extension, result, supported, _j, _k, _len1, _len2; 341 | supported = getSupportedExtensions.call(this); 342 | result = []; 343 | for (_j = 0, _len1 = supported.length; _j < _len1; _j++) { 344 | extension = supported[_j]; 345 | if (unshimLookup[extension] === void 0) { 346 | result.push(extension); 347 | } 348 | } 349 | for (_k = 0, _len2 = shimExtensions.length; _k < _len2; _k++) { 350 | extension = shimExtensions[_k]; 351 | if (__indexOf.call(result, extension) < 0) { 352 | result.push(extension); 353 | } 354 | } 355 | return result; 356 | }; 357 | return WebGLRenderingContext.prototype.getFloatExtension = function(spec) { 358 | var candidate, candidates, half, halfFramebuffer, halfLinear, halfTexture, i, importance, preference, result, single, singleFramebuffer, singleLinear, singleTexture, use, _j, _k, _l, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2; 359 | if (spec.prefer == null) { 360 | spec.prefer = ['half']; 361 | } 362 | if (spec.require == null) { 363 | spec.require = []; 364 | } 365 | if (spec.throws == null) { 366 | spec.throws = true; 367 | } 368 | singleTexture = this.getExtension('OES_texture_float'); 369 | halfTexture = this.getExtension('OES_texture_half_float'); 370 | singleFramebuffer = this.getExtension('WEBGL_color_buffer_float'); 371 | halfFramebuffer = this.getExtension('EXT_color_buffer_half_float'); 372 | singleLinear = this.getExtension('OES_texture_float_linear'); 373 | halfLinear = this.getExtension('OES_texture_half_float_linear'); 374 | single = { 375 | texture: singleTexture !== null, 376 | filterable: singleLinear !== null, 377 | renderable: singleFramebuffer !== null, 378 | score: 0, 379 | precision: 'single', 380 | half: false, 381 | single: true, 382 | type: this.FLOAT 383 | }; 384 | half = { 385 | texture: halfTexture !== null, 386 | filterable: halfLinear !== null, 387 | renderable: halfFramebuffer !== null, 388 | score: 0, 389 | precision: 'half', 390 | half: true, 391 | single: false, 392 | type: (_ref = halfTexture != null ? halfTexture.HALF_FLOAT_OES : void 0) != null ? _ref : null 393 | }; 394 | candidates = []; 395 | if (single.texture) { 396 | candidates.push(single); 397 | } 398 | if (half.texture) { 399 | candidates.push(half); 400 | } 401 | result = []; 402 | for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) { 403 | candidate = candidates[_j]; 404 | use = true; 405 | _ref1 = spec.require; 406 | for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { 407 | name = _ref1[_k]; 408 | if (candidate[name] === false) { 409 | use = false; 410 | } 411 | } 412 | if (use) { 413 | result.push(candidate); 414 | } 415 | } 416 | for (_l = 0, _len3 = result.length; _l < _len3; _l++) { 417 | candidate = result[_l]; 418 | _ref2 = spec.prefer; 419 | for (i = _m = 0, _len4 = _ref2.length; _m < _len4; i = ++_m) { 420 | preference = _ref2[i]; 421 | importance = Math.pow(2, spec.prefer.length - i - 1); 422 | if (candidate[preference]) { 423 | candidate.score += importance; 424 | } 425 | } 426 | } 427 | result.sort(function sort(a, b) { 428 | if (a.score === b.score) { 429 | return 0; 430 | } else if (a.score < b.score) { 431 | return 1; 432 | } else if (a.score > b.score) { 433 | return -1; 434 | } 435 | }); 436 | if (result.length === 0) { 437 | if (spec.throws) { 438 | throw 'No floating point texture support that is ' + 439 | spec.require.join(', '); 440 | } else { 441 | return null; 442 | } 443 | } else { 444 | result = result[0]; 445 | return { 446 | filterable: result.filterable, 447 | renderable: result.renderable, 448 | type: result.type, 449 | precision: result.precision 450 | }; 451 | } 452 | }; 453 | } 454 | } 455 | 456 | nukeVendorPrefix(); 457 | textureFloatShims(); 458 | 459 | module.exports = WebGLHeatmap; 460 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl-heatmap", 3 | "version": "0.2.3", 4 | "description": "A commonJS compatible version of pyalot's webgl-heatmap", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "budo ./example/main.js --live | bistre" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "null" 13 | }, 14 | "keywords": [ 15 | "webgl", 16 | "heatmap", 17 | "webgl-heatmap" 18 | ], 19 | "author": "Victor Powell ", 20 | "license": "MIT", 21 | "dependencies": { 22 | "brfs": "^1.4.1", 23 | "global": "^4.3.0" 24 | }, 25 | "browserify": { 26 | "transform": [ 27 | "brfs" 28 | ] 29 | }, 30 | "peerDependencies": {}, 31 | "devDependencies": { 32 | "bistre": "^1.0.1", 33 | "budo": "^8.0.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | WebGL Heatmap 2 | ============= 3 | 4 | This is a fork of [Florian Boesch](https://github.com/pyalot)'s excellent WebGL 5 | Heatmap to support CommonJS and and to make it available from NPM. To install 6 | it run: 7 | 8 | npm install webgl-heatmap 9 | 10 | webgl-heatmap is a JavaScript library for high performance heatmap display. 11 | 12 | Demo 13 | ---- 14 | 15 | Live Demo at [codeflow.org](http://codeflow.org/entries/2013/feb/04/high-performance-js-heatmaps "") 16 | 17 | How to use it 18 | ------------- 19 | 20 | Instantiate a new heatmap, errors can be one of: 21 | 22 | * Webgl is not supported 23 | * No floating point texture support 24 | * Floating point render target not supported 25 | * Shader Compile Error: ... 26 | * Shader Link Error: ... 27 | 28 | ```javascript 29 | try{ 30 | var heatmap = new WebGLHeatmap({canvas: yourCanvas}); 31 | } 32 | catch(error){ 33 | // handle the error 34 | } 35 | ``` 36 | 37 | creation arguments 38 | 39 | * canvas: the canvas you wish to draw on 40 | * width: explicit width 41 | * height: explicit height 42 | * intensityToAlpha: defaults to true 43 | * gradientTexture: texture used instead of color calculation, can be path or an image 44 | 45 | Add a data point. 46 | 47 | * x and y relative to the canvas in pixels 48 | * size in pixels (radius) 49 | * intensity between 0 and 1 50 | 51 | ```javascript 52 | heatmap.addPoint(x, y, size, intensity); 53 | ``` 54 | 55 | Add a list of data points. 56 | 57 | * x and y relative to the canvas in pixels 58 | * size in pixels (radius) 59 | * intensity between 0 and 1 60 | 61 | ```javascript 62 | heatmap.addPoints([{x:x, y:y, size:size, intensity:intensity}]); 63 | ``` 64 | 65 | Draw queued data points: 66 | 67 | ```javascript 68 | heatmap.update() 69 | ``` 70 | 71 | Display the heatmap 72 | 73 | ```javascript 74 | heatmap.display() 75 | ``` 76 | 77 | Multiply all values in the heatmap by a number (useful for decay) 78 | 79 | ```javascript 80 | heatmap.multiply(0.995) 81 | ``` 82 | 83 | Clamp all values in the heatmap to between two values: 84 | 85 | ```javascript 86 | heatmap.clamp(0.0, 1.0) 87 | ``` 88 | 89 | Blur all values a little: 90 | 91 | ```javascript 92 | heatmap.blur() 93 | ``` -------------------------------------------------------------------------------- /skyline-gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicapow/webgl-heatmap/86274a3980dcfe6e7dac1bcefa1c80a107b65d6d/skyline-gradient.png -------------------------------------------------------------------------------- /vertex-shader-blit.glsl: -------------------------------------------------------------------------------- 1 | attribute vec4 position; 2 | varying vec2 texcoord; 3 | void main() { 4 | texcoord = position.xy * 0.5 + 0.5; 5 | gl_Position = position; 6 | } -------------------------------------------------------------------------------- /webgl-heatmap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var document = require('global/document'); 4 | var window = require('global/window'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | /* eslint-disable max-len */ 8 | var vertexShaderBlit = fs.readFileSync(path.join(__dirname, './vertex-shader-blit.glsl'), 'utf-8'); 9 | var fragmentShaderBlit = fs.readFileSync(path.join(__dirname, './fragment-shader-blit.glsl'), 'utf-8'); 10 | /* eslint-enable max-len */ 11 | var HeatmapTexture = require('./heatmap-texture'); 12 | var HeatmapHeights = require('./heatmap-heights'); 13 | var HeatmapShader = require('./heatmap-shader'); 14 | 15 | function WebGLHeatmap(_arg) { 16 | var alphaEnd, alphaRange, alphaStart, error, getColorFun, gradientTexture, image, intensityToAlpha, output, quad, textureGradient, _ref, _ref1; 17 | _ref = _arg != null ? _arg : {}, this.canvas = _ref.canvas, this.width = _ref.width, this.height = _ref.height, intensityToAlpha = _ref.intensityToAlpha, gradientTexture = _ref.gradientTexture, alphaRange = _ref.alphaRange; 18 | if (!this.canvas) { 19 | this.canvas = document.createElement('canvas'); 20 | } 21 | try { 22 | this.gl = this.canvas.getContext('experimental-webgl', { 23 | depth: false, 24 | antialias: false 25 | }); 26 | if (this.gl === null) { 27 | this.gl = this.canvas.getContext('webgl', { 28 | depth: false, 29 | antialias: false 30 | }); 31 | if (this.gl === null) { 32 | throw 'WebGL not supported'; 33 | } 34 | } 35 | } catch (_error) { 36 | error = _error; 37 | throw 'WebGL not supported'; 38 | } 39 | if (window.WebGLDebugUtils != null) { 40 | console.log('debugging mode'); 41 | this.gl = WebGLDebugUtils.makeDebugContext(this.gl, function(err, funcName, args) { 42 | throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to: " + funcName; 43 | }); 44 | } 45 | this.gl.enableVertexAttribArray(0); 46 | this.gl.blendFunc(this.gl.ONE, this.gl.ONE); 47 | if (gradientTexture) { 48 | textureGradient = this.gradientTexture = new HeatmapTexture(this.gl, { 49 | channels: 'rgba' 50 | }).bind(0).setSize(2, 2).nearest().clampToEdge(); 51 | if (typeof gradientTexture === 'string') { 52 | image = new window.Image(); 53 | image.onload = function() { 54 | return textureGradient.bind().upload(image); 55 | }; 56 | image.src = gradientTexture; 57 | } else { 58 | if (gradientTexture.width > 0 && gradientTexture.height > 0) { 59 | textureGradient.upload(gradientTexture); 60 | } else { 61 | gradientTexture.onload = function() { 62 | return textureGradient.upload(gradientTexture); 63 | }; 64 | } 65 | } 66 | getColorFun = 'uniform sampler2D gradientTexture;\nvec3 getColor(float intensity){\n return texture2D(gradientTexture, vec2(intensity, 0.0)).rgb;\n}'; 67 | } else { 68 | textureGradient = null; 69 | getColorFun = 'vec3 getColor(float intensity){\n vec3 blue = vec3(0.0, 0.0, 1.0);\n vec3 cyan = vec3(0.0, 1.0, 1.0);\n vec3 green = vec3(0.0, 1.0, 0.0);\n vec3 yellow = vec3(1.0, 1.0, 0.0);\n vec3 red = vec3(1.0, 0.0, 0.0);\n\n vec3 color = (\n fade(-0.25, 0.25, intensity)*blue +\n fade(0.0, 0.5, intensity)*cyan +\n fade(0.25, 0.75, intensity)*green +\n fade(0.5, 1.0, intensity)*yellow +\n smoothstep(0.75, 1.0, intensity)*red\n );\n return color;\n}'; 70 | } 71 | if (intensityToAlpha == null) { 72 | intensityToAlpha = true; 73 | } 74 | if (intensityToAlpha) { 75 | _ref1 = alphaRange != null ? alphaRange : [0, 1], alphaStart = _ref1[0], alphaEnd = _ref1[1]; 76 | output = "vec4 alphaFun(vec3 color, float intensity){\n float alpha = smoothstep(" + (alphaStart.toFixed(8)) + ", " + (alphaEnd.toFixed(8)) + ", intensity);\n return vec4(color*alpha, alpha);\n}"; 77 | } else { 78 | output = 'vec4 alphaFun(vec3 color, float intensity){\n return vec4(color, 1.0);\n}'; 79 | } 80 | this.shader = new HeatmapShader(this.gl, { 81 | vertex: vertexShaderBlit, 82 | fragment: fragmentShaderBlit + ("float linstep(float low, float high, float value){\n return clamp((value-low)/(high-low), 0.0, 1.0);\n}\n\nfloat fade(float low, float high, float value){\n float mid = (low+high)*0.5;\n float range = (high-low)*0.5;\n float x = 1.0 - clamp(abs(mid-value)/range, 0.0, 1.0);\n return smoothstep(0.0, 1.0, x);\n}\n\n" + getColorFun + "\n" + output + "\n\nvoid main(){\n float intensity = smoothstep(0.0, 1.0, texture2D(source, texcoord).r);\n vec3 color = getColor(intensity);\n gl_FragColor = alphaFun(color, intensity);\n}") 83 | }); 84 | if (this.width == null) { 85 | this.width = this.canvas.offsetWidth || 2; 86 | } 87 | if (this.height == null) { 88 | this.height = this.canvas.offsetHeight || 2; 89 | } 90 | this.canvas.width = this.width; 91 | this.canvas.height = this.height; 92 | this.gl.viewport(0, 0, this.width, this.height); 93 | this.quad = this.gl.createBuffer(); 94 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad); 95 | quad = new Float32Array([-1, -1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, 1, 0, 1, 1, -1, 0, 1, 1, 1, 0, 1]); 96 | this.gl.bufferData(this.gl.ARRAY_BUFFER, quad, this.gl.STATIC_DRAW); 97 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); 98 | this.heights = new HeatmapHeights(this, this.gl, this.width, this.height); 99 | } 100 | 101 | WebGLHeatmap.prototype.adjustSize = function() { 102 | var canvasHeight, canvasWidth; 103 | canvasWidth = this.canvas.offsetWidth || 2; 104 | canvasHeight = this.canvas.offsetHeight || 2; 105 | if (this.width !== canvasWidth || this.height !== canvasHeight) { 106 | this.gl.viewport(0, 0, canvasWidth, canvasHeight); 107 | this.canvas.width = canvasWidth; 108 | this.canvas.height = canvasHeight; 109 | this.width = canvasWidth; 110 | this.height = canvasHeight; 111 | return this.heights.resize(this.width, this.height); 112 | } 113 | }; 114 | 115 | WebGLHeatmap.prototype.display = function() { 116 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad); 117 | this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); 118 | this.heights.nodeFront.bind(0); 119 | if (this.gradientTexture) { 120 | this.gradientTexture.bind(1); 121 | } 122 | this.shader.use().int('source', 0).int('gradientTexture', 1); 123 | return this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); 124 | }; 125 | 126 | WebGLHeatmap.prototype.update = function() { 127 | return this.heights.update(); 128 | }; 129 | 130 | WebGLHeatmap.prototype.clear = function() { 131 | return this.heights.clear(); 132 | }; 133 | 134 | WebGLHeatmap.prototype.clamp = function(min, max) { 135 | if (min == null) { 136 | min = 0; 137 | } 138 | if (max == null) { 139 | max = 1; 140 | } 141 | return this.heights.clamp(min, max); 142 | }; 143 | 144 | WebGLHeatmap.prototype.multiply = function(value) { 145 | if (value == null) { 146 | value = 0.95; 147 | } 148 | return this.heights.multiply(value); 149 | }; 150 | 151 | WebGLHeatmap.prototype.blur = function() { 152 | return this.heights.blur(); 153 | }; 154 | 155 | WebGLHeatmap.prototype.addPoint = function(x, y, size, intensity) { 156 | return this.heights.addPoint(x, y, size, intensity); 157 | }; 158 | 159 | WebGLHeatmap.prototype.addPoints = function(items) { 160 | var item, _i, _len, _results; 161 | _results = []; 162 | for (_i = 0, _len = items.length; _i < _len; _i++) { 163 | item = items[_i]; 164 | _results.push(this.addPoint(item.x, item.y, item.size, item.intensity)); 165 | } 166 | return _results; 167 | }; 168 | 169 | module.exports = WebGLHeatmap; 170 | --------------------------------------------------------------------------------