diff --git a/ssh/manager.go b/ssh/manager.go index 0f2393c..162cdcb 100644 --- a/ssh/manager.go +++ b/ssh/manager.go @@ -45,7 +45,7 @@ func (m *Manager) ConnectTo(addr net.TCPAddr) *ssh.Client { m.clientsMUX.Lock() defer m.clientsMUX.Unlock() if t, ok := m.clientsBlacklist[addr.IP.String()]; ok { - if time.Now().Add(-time.Hour * 24).After(t) { + if time.Now().Add(-time.Hour * 24).Before(t) { return nil } else { delete(m.clientsBlacklist, addr.IP.String()) diff --git a/webroot/css/images/layers-2x.png b/webroot/css/images/layers-2x.png new file mode 100644 index 0000000..200c333 Binary files /dev/null and b/webroot/css/images/layers-2x.png differ diff --git a/webroot/css/images/layers.png b/webroot/css/images/layers.png new file mode 100644 index 0000000..1a72e57 Binary files /dev/null and b/webroot/css/images/layers.png differ diff --git a/webroot/css/images/marker-icon-2x.png b/webroot/css/images/marker-icon-2x.png new file mode 100644 index 0000000..e4abba3 Binary files /dev/null and b/webroot/css/images/marker-icon-2x.png differ diff --git a/webroot/css/images/marker-icon.png b/webroot/css/images/marker-icon.png new file mode 100644 index 0000000..950edf2 Binary files /dev/null and b/webroot/css/images/marker-icon.png differ diff --git a/webroot/css/images/marker-shadow.png b/webroot/css/images/marker-shadow.png new file mode 100644 index 0000000..9fd2979 Binary files /dev/null and b/webroot/css/images/marker-shadow.png differ diff --git a/webroot/css/main.css b/webroot/css/main.css index 7f393bd..83de703 100644 --- a/webroot/css/main.css +++ b/webroot/css/main.css @@ -18,6 +18,12 @@ body { .status.offline { background: #dc0067; } +span.online { + color: #009ee0; +} +span.offline { + color: #dc0067; +} h1 { border-bottom: 4px solid #dc0067; } diff --git a/webroot/css/map.css b/webroot/css/map.css index b69e445..ea20aee 100644 --- a/webroot/css/map.css +++ b/webroot/css/map.css @@ -6,7 +6,7 @@ border-radius: 10px; } .leaflet-container .node.offline { - background-color rgba(255,0,0,0.5) + background-color: rgba(255,0,0,0.5); } .leaflet-container .node.client24 { border-left: 3px solid green; diff --git a/webroot/index.html b/webroot/index.html index 3a67c6f..925320e 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -10,6 +10,8 @@ + + diff --git a/webroot/js/config.js b/webroot/js/config.js index eaff6fa..e4371ea 100644 --- a/webroot/js/config.js +++ b/webroot/js/config.js @@ -5,9 +5,16 @@ var config = { view: {bound: [53.07093, 8.79464], zoom: 17}, maxZoom: 20, tileLayer: '//tiles.bremen.freifunk.net/{z}/{x}/{y}.png', - heatMax: { - wifi24: 15, - wifi5: 50 + /* heatmap settings + size: in meters (default: 30km) + opacity: in percent/100 (default: 1) + gradientTexture: url-to-texture-image (default: false) + alphaRange: change transparency in heatmap (default: 1) + autoresize: resize heatmap when map size changes (default: false) + */ + heatmap: { + wifi24: {size: 230, opacity: 0.5, alphaRange: 1}, + wifi5: {size: 230, opacity: 0.5, alphaRange: 1} }, icon:{ warn:{wifi24:20,wifi5:20}, diff --git a/webroot/js/gui.js b/webroot/js/gui.js index 0db1d96..22ada68 100644 --- a/webroot/js/gui.js +++ b/webroot/js/gui.js @@ -43,7 +43,7 @@ var router = new Navigo(null, true, '#'); '/n/:nodeID': { as: 'node', uses: function (params) { - guiNode.current_node_id = params['nodeID'].toLowerCase(); + guiNode.setNodeID(params['nodeID'].toLowerCase()); setView(guiNode); } }, @@ -68,5 +68,5 @@ var router = new Navigo(null, true, '#'); timeout = setTimeout(reset, 100); } - gui.render(); + window.onload = gui.render; })(); diff --git a/webroot/js/gui_map.js b/webroot/js/gui_map.js index dbe3bc6..3ec7be0 100644 --- a/webroot/js/gui_map.js +++ b/webroot/js/gui_map.js @@ -4,9 +4,15 @@ var guiMap = {}; var view = guiMap; var container, el; - var nodeLayer; + var nodeLayer, clientLayer24, clientLayer5;//, draggingNodeID; function addNode (node){ + /* + https://github.com/Leaflet/Leaflet/issues/4484 + if(node.node_id === draggingNodeID){ + return + } + */ if(node.location === undefined || node.location.latitude === undefined || node.location.longitude === undefined) { return; } @@ -50,8 +56,13 @@ var guiMap = {}; ''+ '' ); - + /* + nodemarker.on('dragstart',function(){ + draggingNodeID = node.node_id; + }) + */ nodemarker.on('dragend',function(){ + // draggingNodeID = undefined; var pos = nodemarker.getLatLng(); node.location = { 'latitude': pos.lat, @@ -69,6 +80,21 @@ var guiMap = {}; for(var i=0; i + * http://www.ursudio.com/ + * Please attribute Ursudio in any production associated with this JavaScript plugin. + */ +L.WebGLHeatMap=L.Renderer.extend({version:"0.2.2",options:{size:3e4,units:"m",opacity:1,gradientTexture:!1,alphaRange:1,padding:0},_initContainer:function(){var t=this._container=L.DomUtil.create("canvas","leaflet-zoom-animated"),i=this.options;t.id="webgl-leaflet-"+L.Util.stamp(this),t.style.opacity=i.opacity,t.style.position="absolute";try{this.gl=window.createWebGLHeatmap({canvas:t,gradientTexture:i.gradientTexture,alphaRange:[0,i.alphaRange]})}catch(t){console.error(t),this.gl={clear:function(){},update:function(){},multiply:function(){},addPoint:function(){},display:function(){},adjustSize:function(){}}}this._container=t},onAdd:function(){this.size=this.options.size,L.Renderer.prototype.onAdd.call(this),this.resize()},getEvents:function(){var t=L.Renderer.prototype.getEvents.call(this);return L.Util.extend(t,{resize:this.resize,move:L.Util.throttle(this._update,49,this)}),t},resize:function(){var t=this._container,i=this._map.getSize();t.width=i.x,t.height=i.y,this.gl.adjustSize(),this.draw()},reposition:function(){var t=this._map._getMapPanePos().multiplyBy(-1);L.DomUtil.setPosition(this._container,t)},_update:function(){L.Renderer.prototype._update.call(this),this.draw()},draw:function(){var t=this._map,i=this.gl,e=this.data,a=e.length,n=Math.floor,s=this["_scale"+this.options.units].bind(this),o=this._multiply;if(t){if(i.clear(),this.reposition(),a){for(var r=0;r b.score) { + return -1; + } + }); + if (result.length === 0) { + if (spec.throws) { + throw 'No floating point texture support that is ' + spec.require.join(', '); + } else { + return null; + } + } else { + result = result[0]; + return { + filterable: result.filterable, + renderable: result.renderable, + type: result.type, + precision: result.precision + }; + } + }; + } + }; + + nukeVendorPrefix(); + + textureFloatShims(); + + Shader = (function() { + function Shader(gl, _arg) { + var fragment, vertex; + this.gl = gl; + vertex = _arg.vertex, fragment = _arg.fragment; + this.program = this.gl.createProgram(); + this.vs = this.gl.createShader(this.gl.VERTEX_SHADER); + this.fs = this.gl.createShader(this.gl.FRAGMENT_SHADER); + this.gl.attachShader(this.program, this.vs); + this.gl.attachShader(this.program, this.fs); + this.compileShader(this.vs, vertex); + this.compileShader(this.fs, fragment); + this.link(); + this.value_cache = {}; + this.uniform_cache = {}; + this.attribCache = {}; + } + + Shader.prototype.attribLocation = function(name) { + var location; + location = this.attribCache[name]; + if (location === void 0) { + location = this.attribCache[name] = this.gl.getAttribLocation(this.program, name); + } + return location; + }; + + Shader.prototype.compileShader = function(shader, source) { + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { + throw "Shader Compile Error: " + (this.gl.getShaderInfoLog(shader)); + } + }; + + Shader.prototype.link = function() { + this.gl.linkProgram(this.program); + if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) { + throw "Shader Link Error: " + (this.gl.getProgramInfoLog(this.program)); + } + }; + + Shader.prototype.use = function() { + this.gl.useProgram(this.program); + return this; + }; + + Shader.prototype.uniformLoc = function(name) { + var location; + location = this.uniform_cache[name]; + if (location === void 0) { + location = this.uniform_cache[name] = this.gl.getUniformLocation(this.program, name); + } + return location; + }; + + Shader.prototype.int = function(name, value) { + var cached, loc; + cached = this.value_cache[name]; + if (cached !== value) { + this.value_cache[name] = value; + loc = this.uniformLoc(name); + if (loc) { + this.gl.uniform1i(loc, value); + } + } + return this; + }; + + Shader.prototype.vec2 = function(name, a, b) { + var loc; + loc = this.uniformLoc(name); + if (loc) { + this.gl.uniform2f(loc, a, b); + } + return this; + }; + + Shader.prototype.float = function(name, value) { + var cached, loc; + cached = this.value_cache[name]; + if (cached !== value) { + this.value_cache[name] = value; + loc = this.uniformLoc(name); + if (loc) { + this.gl.uniform1f(loc, value); + } + } + return this; + }; + + return Shader; + + })(); + + Framebuffer = (function() { + function Framebuffer(gl) { + this.gl = gl; + this.buffer = this.gl.createFramebuffer(); + } + + Framebuffer.prototype.destroy = function() { + return this.gl.deleteFRamebuffer(this.buffer); + }; + + Framebuffer.prototype.bind = function() { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffer); + return this; + }; + + Framebuffer.prototype.unbind = function() { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + return this; + }; + + Framebuffer.prototype.check = function() { + var result; + result = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER); + switch (result) { + case this.gl.FRAMEBUFFER_UNSUPPORTED: + throw 'Framebuffer is unsupported'; + break; + case this.gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + throw 'Framebuffer incomplete attachment'; + break; + case this.gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + throw 'Framebuffer incomplete dimensions'; + break; + case this.gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + throw 'Framebuffer incomplete missing attachment'; + } + return this; + }; + + Framebuffer.prototype.color = function(texture) { + this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, texture.target, texture.handle, 0); + this.check(); + return this; + }; + + Framebuffer.prototype.depth = function(buffer) { + this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, buffer.id); + this.check(); + return this; + }; + + Framebuffer.prototype.destroy = function() { + return this.gl.deleteFramebuffer(this.buffer); + }; + + return Framebuffer; + + })(); + + Texture = (function() { + function Texture(gl, params) { + var _ref, _ref1; + this.gl = gl; + if (params == null) { + params = {}; + } + this.channels = this.gl[((_ref = params.channels) != null ? _ref : 'rgba').toUpperCase()]; + if (typeof params.type === 'number') { + this.type = params.type; + } else { + this.type = this.gl[((_ref1 = params.type) != null ? _ref1 : 'unsigned_byte').toUpperCase()]; + } + switch (this.channels) { + case this.gl.RGBA: + this.chancount = 4; + break; + case this.gl.RGB: + this.chancount = 3; + break; + case this.gl.LUMINANCE_ALPHA: + this.chancount = 2; + break; + default: + this.chancount = 1; + } + this.target = this.gl.TEXTURE_2D; + this.handle = this.gl.createTexture(); + } + + Texture.prototype.destroy = function() { + return this.gl.deleteTexture(this.handle); + }; + + Texture.prototype.bind = function(unit) { + if (unit == null) { + unit = 0; + } + if (unit > 15) { + throw 'Texture unit too large: ' + unit; + } + this.gl.activeTexture(this.gl.TEXTURE0 + unit); + this.gl.bindTexture(this.target, this.handle); + return this; + }; + + Texture.prototype.setSize = function(width, height) { + this.width = width; + this.height = height; + this.gl.texImage2D(this.target, 0, this.channels, this.width, this.height, 0, this.channels, this.type, null); + return this; + }; + + Texture.prototype.upload = function(data) { + this.width = data.width; + this.height = data.height; + this.gl.texImage2D(this.target, 0, this.channels, this.channels, this.type, data); + return this; + }; + + Texture.prototype.linear = function() { + this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); + this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); + return this; + }; + + Texture.prototype.nearest = function() { + this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); + this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); + return this; + }; + + Texture.prototype.clampToEdge = function() { + this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + return this; + }; + + Texture.prototype.repeat = function() { + this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT); + this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT); + return this; + }; + + return Texture; + + })(); + + Node = (function() { + function Node(gl, width, height) { + var floatExt; + this.gl = gl; + this.width = width; + this.height = height; + floatExt = this.gl.getFloatExtension({ + require: ['renderable'] + }); + this.texture = new Texture(this.gl, { + type: floatExt.type + }).bind(0).setSize(this.width, this.height).nearest().clampToEdge(); + this.fbo = new Framebuffer(this.gl).bind().color(this.texture).unbind(); + } + + Node.prototype.use = function() { + return this.fbo.bind(); + }; + + Node.prototype.bind = function(unit) { + return this.texture.bind(unit); + }; + + Node.prototype.end = function() { + return this.fbo.unbind(); + }; + + Node.prototype.resize = function(width, height) { + this.width = width; + this.height = height; + return this.texture.bind(0).setSize(this.width, this.height); + }; + + return Node; + + })(); + + vertexShaderBlit = 'attribute vec4 position;\nvarying vec2 texcoord;\nvoid main(){\n texcoord = position.xy*0.5+0.5;\n gl_Position = position;\n}'; + + fragmentShaderBlit = '#ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp int;\n precision highp float;\n#else\n precision mediump int;\n precision mediump float;\n#endif\nuniform sampler2D source;\nvarying vec2 texcoord;'; + + Heights = (function() { + function Heights(heatmap, gl, width, height) { + var i, _i, _ref; + this.heatmap = heatmap; + this.gl = gl; + this.width = width; + this.height = height; + this.shader = new Shader(this.gl, { + vertex: 'attribute vec4 position, intensity;\nvarying vec2 off, dim;\nvarying float vIntensity;\nuniform vec2 viewport;\n\nvoid main(){\n dim = abs(position.zw);\n off = position.zw;\n vec2 pos = position.xy + position.zw;\n vIntensity = intensity.x;\n gl_Position = vec4((pos/viewport)*2.0-1.0, 0.0, 1.0);\n}', + fragment: '#ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp int;\n precision highp float;\n#else\n precision mediump int;\n precision mediump float;\n#endif\nvarying vec2 off, dim;\nvarying float vIntensity;\nvoid main(){\n float falloff = (1.0 - smoothstep(0.0, 1.0, length(off/dim)));\n float intensity = falloff*vIntensity;\n gl_FragColor = vec4(intensity);\n}' + }); + this.clampShader = new Shader(this.gl, { + vertex: vertexShaderBlit, + fragment: fragmentShaderBlit + 'uniform float low, high;\nvoid main(){\n gl_FragColor = vec4(clamp(texture2D(source, texcoord).rgb, low, high), 1.0);\n}' + }); + this.multiplyShader = new Shader(this.gl, { + vertex: vertexShaderBlit, + fragment: fragmentShaderBlit + 'uniform float value;\nvoid main(){\n gl_FragColor = vec4(texture2D(source, texcoord).rgb*value, 1.0);\n}' + }); + this.blurShader = new Shader(this.gl, { + vertex: vertexShaderBlit, + fragment: fragmentShaderBlit + 'uniform vec2 viewport;\nvoid main(){\n vec4 result = vec4(0.0);\n for(int x=-1; x<=1; x++){\n for(int y=-1; y<=1; y++){\n vec2 off = vec2(x,y)/viewport;\n //float factor = 1.0 - smoothstep(0.0, 1.5, length(off));\n float factor = 1.0;\n result += vec4(texture2D(source, texcoord+off).rgb*factor, factor);\n }\n }\n gl_FragColor = vec4(result.rgb/result.w, 1.0);\n}' + }); + this.nodeBack = new Node(this.gl, this.width, this.height); + this.nodeFront = new Node(this.gl, this.width, this.height); + this.vertexBuffer = this.gl.createBuffer(); + this.vertexSize = 8; + this.maxPointCount = 1024 * 10; + this.vertexBufferData = new Float32Array(this.maxPointCount * this.vertexSize * 6); + this.vertexBufferViews = []; + for (i = _i = 0, _ref = this.maxPointCount; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + this.vertexBufferViews.push(new Float32Array(this.vertexBufferData.buffer, 0, i * this.vertexSize * 6)); + } + this.bufferIndex = 0; + this.pointCount = 0; + } + + Heights.prototype.resize = function(width, height) { + this.width = width; + this.height = height; + this.nodeBack.resize(this.width, this.height); + return this.nodeFront.resize(this.width, this.height); + }; + + Heights.prototype.update = function() { + var intensityLoc, positionLoc; + if (this.pointCount > 0) { + this.gl.enable(this.gl.BLEND); + this.nodeFront.use(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, this.vertexBufferViews[this.pointCount], this.gl.STREAM_DRAW); + positionLoc = this.shader.attribLocation('position'); + intensityLoc = this.shader.attribLocation('intensity'); + this.gl.enableVertexAttribArray(1); + this.gl.vertexAttribPointer(positionLoc, 4, this.gl.FLOAT, false, 8 * 4, 0 * 4); + this.gl.vertexAttribPointer(intensityLoc, 4, this.gl.FLOAT, false, 8 * 4, 4 * 4); + this.shader.use().vec2('viewport', this.width, this.height); + this.gl.drawArrays(this.gl.TRIANGLES, 0, this.pointCount * 6); + this.gl.disableVertexAttribArray(1); + this.pointCount = 0; + this.bufferIndex = 0; + this.nodeFront.end(); + return this.gl.disable(this.gl.BLEND); + } + }; + + Heights.prototype.clear = function() { + this.nodeFront.use(); + this.gl.clearColor(0, 0, 0, 1); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + return this.nodeFront.end(); + }; + + Heights.prototype.clamp = function(min, max) { + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); + this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); + this.nodeFront.bind(0); + this.nodeBack.use(); + this.clampShader.use().int('source', 0).float('low', min).float('high', max); + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); + this.nodeBack.end(); + return this.swap(); + }; + + Heights.prototype.multiply = function(value) { + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); + this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); + this.nodeFront.bind(0); + this.nodeBack.use(); + this.multiplyShader.use().int('source', 0).float('value', value); + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); + this.nodeBack.end(); + return this.swap(); + }; + + Heights.prototype.blur = function() { + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); + this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); + this.nodeFront.bind(0); + this.nodeBack.use(); + this.blurShader.use().int('source', 0).vec2('viewport', this.width, this.height); + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); + this.nodeBack.end(); + return this.swap(); + }; + + Heights.prototype.swap = function() { + var tmp; + tmp = this.nodeFront; + this.nodeFront = this.nodeBack; + return this.nodeBack = tmp; + }; + + Heights.prototype.addVertex = function(x, y, xs, ys, intensity) { + this.vertexBufferData[this.bufferIndex++] = x; + this.vertexBufferData[this.bufferIndex++] = y; + this.vertexBufferData[this.bufferIndex++] = xs; + this.vertexBufferData[this.bufferIndex++] = ys; + this.vertexBufferData[this.bufferIndex++] = intensity; + this.vertexBufferData[this.bufferIndex++] = intensity; + this.vertexBufferData[this.bufferIndex++] = intensity; + return this.vertexBufferData[this.bufferIndex++] = intensity; + }; + + Heights.prototype.addPoint = function(x, y, size, intensity) { + var s; + if (size == null) { + size = 50; + } + if (intensity == null) { + intensity = 0.2; + } + if (this.pointCount >= this.maxPointCount - 1) { + this.update(); + } + y = this.height - y; + s = size / 2; + this.addVertex(x, y, -s, -s, intensity); + this.addVertex(x, y, +s, -s, intensity); + this.addVertex(x, y, -s, +s, intensity); + this.addVertex(x, y, -s, +s, intensity); + this.addVertex(x, y, +s, -s, intensity); + this.addVertex(x, y, +s, +s, intensity); + return this.pointCount += 1; + }; + + return Heights; + + })(); + + WebGLHeatmap = (function() { + function WebGLHeatmap(_arg) { + var alphaEnd, alphaRange, alphaStart, error, getColorFun, gradientTexture, image, intensityToAlpha, output, quad, textureGradient, _ref, _ref1; + _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; + if (!this.canvas) { + this.canvas = document.createElement('canvas'); + } + try { + this.gl = this.canvas.getContext('experimental-webgl', { + depth: false, + antialias: false + }); + if (this.gl === null) { + this.gl = this.canvas.getContext('webgl', { + depth: false, + antialias: false + }); + if (this.gl === null) { + throw 'WebGL not supported'; + } + } + } catch (_error) { + error = _error; + throw 'WebGL not supported'; + } + if (window.WebGLDebugUtils != null) { + console.log('debugging mode'); + this.gl = WebGLDebugUtils.makeDebugContext(this.gl, function(err, funcName, args) { + throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to: " + funcName; + }); + } + this.gl.enableVertexAttribArray(0); + this.gl.blendFunc(this.gl.ONE, this.gl.ONE); + if (gradientTexture) { + textureGradient = this.gradientTexture = new Texture(this.gl, { + channels: 'rgba' + }).bind(0).setSize(2, 2).nearest().clampToEdge(); + if (typeof gradientTexture === 'string') { + image = new Image(); + image.onload = function() { + return textureGradient.bind().upload(image); + }; + image.src = gradientTexture; + } else { + if (gradientTexture.width > 0 && gradientTexture.height > 0) { + textureGradient.upload(gradientTexture); + } else { + gradientTexture.onload = function() { + return textureGradient.upload(gradientTexture); + }; + } + } + getColorFun = 'uniform sampler2D gradientTexture;\nvec3 getColor(float intensity){\n return texture2D(gradientTexture, vec2(intensity, 0.0)).rgb;\n}'; + } else { + textureGradient = null; + 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}'; + } + if (intensityToAlpha == null) { + intensityToAlpha = true; + } + if (intensityToAlpha) { + _ref1 = alphaRange != null ? alphaRange : [0, 1], alphaStart = _ref1[0], alphaEnd = _ref1[1]; + 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}"; + } else { + output = 'vec4 alphaFun(vec3 color, float intensity){\n return vec4(color, 1.0);\n}'; + } + this.shader = new Shader(this.gl, { + vertex: vertexShaderBlit, + 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}") + }); + if (this.width == null) { + this.width = this.canvas.offsetWidth || 2; + } + if (this.height == null) { + this.height = this.canvas.offsetHeight || 2; + } + this.canvas.width = this.width; + this.canvas.height = this.height; + this.gl.viewport(0, 0, this.width, this.height); + this.quad = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad); + 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]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, quad, this.gl.STATIC_DRAW); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); + this.heights = new Heights(this, this.gl, this.width, this.height); + } + + WebGLHeatmap.prototype.adjustSize = function() { + var canvasHeight, canvasWidth; + canvasWidth = this.canvas.offsetWidth || 2; + canvasHeight = this.canvas.offsetHeight || 2; + if (this.width !== canvasWidth || this.height !== canvasHeight) { + this.gl.viewport(0, 0, canvasWidth, canvasHeight); + this.canvas.width = canvasWidth; + this.canvas.height = canvasHeight; + this.width = canvasWidth; + this.height = canvasHeight; + return this.heights.resize(this.width, this.height); + } + }; + + WebGLHeatmap.prototype.display = function() { + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad); + this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); + this.heights.nodeFront.bind(0); + if (this.gradientTexture) { + this.gradientTexture.bind(1); + } + this.shader.use().int('source', 0).int('gradientTexture', 1); + return this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); + }; + + WebGLHeatmap.prototype.update = function() { + return this.heights.update(); + }; + + WebGLHeatmap.prototype.clear = function() { + return this.heights.clear(); + }; + + WebGLHeatmap.prototype.clamp = function(min, max) { + if (min == null) { + min = 0; + } + if (max == null) { + max = 1; + } + return this.heights.clamp(min, max); + }; + + WebGLHeatmap.prototype.multiply = function(value) { + if (value == null) { + value = 0.95; + } + return this.heights.multiply(value); + }; + + WebGLHeatmap.prototype.blur = function() { + return this.heights.blur(); + }; + + WebGLHeatmap.prototype.addPoint = function(x, y, size, intensity) { + return this.heights.addPoint(x, y, size, intensity); + }; + + WebGLHeatmap.prototype.addPoints = function(items) { + var item, _i, _len, _results; + _results = []; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + _results.push(this.addPoint(item.x, item.y, item.size, item.intensity)); + } + return _results; + }; + + return WebGLHeatmap; + + })(); + + window.createWebGLHeatmap = function(params) { + return new WebGLHeatmap(params); + }; + +}).call(this);