From e2774c3880c4253f28b265b6258c52740f214156 Mon Sep 17 00:00:00 2001 From: Martin Geno Date: Tue, 16 May 2017 00:22:15 +0200 Subject: [PATCH] [TASK] add position editing of node --- ssh/manager.go | 2 +- webroot/css/images/layers-2x.png | Bin 0 -> 1259 bytes webroot/css/images/layers.png | Bin 0 -> 696 bytes webroot/css/images/marker-icon-2x.png | Bin 0 -> 2586 bytes webroot/css/images/marker-icon.png | Bin 0 -> 1466 bytes webroot/css/images/marker-shadow.png | Bin 0 -> 618 bytes webroot/css/main.css | 6 + webroot/css/map.css | 2 +- webroot/index.html | 2 + webroot/js/config.js | 13 +- webroot/js/gui.js | 4 +- webroot/js/gui_map.js | 35 +- webroot/js/gui_node.js | 108 ++- webroot/js/leaflet-webgl-heatmap.min.js | 6 + webroot/js/webgl-heatmap.js | 1040 +++++++++++++++++++++++ 15 files changed, 1203 insertions(+), 15 deletions(-) create mode 100644 webroot/css/images/layers-2x.png create mode 100644 webroot/css/images/layers.png create mode 100644 webroot/css/images/marker-icon-2x.png create mode 100644 webroot/css/images/marker-icon.png create mode 100644 webroot/css/images/marker-shadow.png create mode 100644 webroot/js/leaflet-webgl-heatmap.min.js create mode 100644 webroot/js/webgl-heatmap.js 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 0000000000000000000000000000000000000000..200c333dca9652ac4cba004d609e5af4eee168c1 GIT binary patch literal 1259 zcmVFhCYNy;#0irRPomHqW|G1C*;4?@4#E?jH>?v@U%cy?3dQAc-DchXVErpOh~ z-jbon+tNbnl6hoEb;)TVk+%hTDDi_G%i3*RZ&15!$Fjr^f;Ke&A@|?=`2&+{zr+3a z{D*=t(`AXyS%X7N z%a#RZw6vD^t_rnM`L4E>m=U&R!A-&}nZIi$BOPvkhrCuUe@BN~-lRD)f44;J%TwgE zcze8u!PQ_NR7?o(NylLXVTfDO zxs5=@|GsYEsNo4M#nT%N!UE(?dnS)t2+{ELYAFp*3=iF=|EQnTp`#vlSXuGVraYo? z+RCzXo6h3qA8{KG?S4nE(lM+;Eb4nT3XV;7gcAxUi5m)`k5tv}cPy()8ZR3TLW3I- zAS^}cq-IJvL7a4RgR!yk@~RT%$lA7{L5ES*hyx)M4(yxI$Ub(4f)K|^v1>zvwQY!_ zIrWw8q9GS^!Dp~}+?mbnB6jDF8mVlbQ!jFKDY;w=7;XO{9bq7>LXGK24WA`;rL)_Z z)&j}pbV(;6gY;VMhbxgvn`X;6x}VUEE-7 z%)7j-%t8S=ZL3yc)HbXDAqJZvBTPoiW_A-+a8m3_Z?v{DN7Tnr#O_VUMT0UBt$;p` zDh6JbGHN8JJ*JN%y2%msb97@_S>9!%Egwk;?PEkU9ntz&3uR}%Fj5d$JHQbQb3}a{ zSzFT^#n=VInPpcAS}CNxj?_ zVscANk5Cfz(51EI1pz};AWWb|kgbYNb4wCEGUn3+eMUMV?1-{=I4TlmLJMot@rd07 zZuo2hk1ccu{YmGkcYdWAVdk{Z4Nm?^cTD&}jGm+Q1SYIXMwmG*oO*83&#>l%nbR`G zhh=lZ%xIb7kU3#;TBbfECrnC9P=-XpL|TG2BoZdj61*XiFbW8?1Z_wp%#;>${SUIy V$8qr;L*)Pf002ovPDHLkV1hYLS~36t literal 0 HcmV?d00001 diff --git a/webroot/css/images/layers.png b/webroot/css/images/layers.png new file mode 100644 index 0000000000000000000000000000000000000000..1a72e5784b2b456eac5d7670738db80697af3377 GIT binary patch literal 696 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 UQO-*oc+)|Q_08}VdXD6O0C*xx%>V!Z literal 0 HcmV?d00001 diff --git a/webroot/css/images/marker-icon.png b/webroot/css/images/marker-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..950edf24677ded147df13b26f91baa2b0fa70513 GIT binary patch literal 1466 zcmV;r1x5OaP)P001cn1^@s6z>|W`000GnNklGNuHDcIX17Zdjl&3`L?0sTjIws<{((Dh&g-s0<@jYQyl?D*X^?%13;ml^gy> ziMrY_^1WI=(g@LMizu=zCoA>C`6|QEq1eV92k*7m>G65*&@&6)aC&e}G zI)pf-Za|N`DT&Cn1J|o`19mumxW~hiKiKyc-P`S@q)rdTo84@QI@;0yXrG%9uhI>A zG5QHb6s4=<6xy{1 z@NMxEkryp{LS44%z$3lP^cX!9+2-;CTt3wM4(k*#C{aiIiLuB>jJj;KPhPzIC00bL zU3a#;aJld94lCW=`4&aAy8M7PY=HQ>O%$YEP4c4UY#CRxfgbE~(|uiI=YS8q;O9y6 zmIkXzR`}p7ti|PrM3a}WMnR=3NVnWdAAR>b9X@)DKL6=YsvmH%?I24wdq?Gh54_;# z$?_LvgjEdspdQlft#4CQ z`2Zyvy?*)N1Ftw|{_hakhG9WjS?Az@I@+IZ8JbWewR!XUK4&6346+d#~gsE0SY(LX8&JfY>Aj)RxGy96nwhs2rv zzW6pTnMpFkDSkT*a*6Dx|u@ds6ISVn0@^RmIsKZ5Y;bazbc;tTSq(kg(=481ODrPyNB6n z-$+U}(w$m6U6H$w17Bw+wDaFIe~GvNMYvnw31MpY0eQKT9l>SU``8k7w4)z!GZKMI z#_cEKq7k~i%nlK@6c-K?+R;B#5$?T#YpKD`t_4bAs^#E+@5QW$@OX3*`;(#{U^d-vY)&xEE>n5lYl&T?Amke9$Lam@{1K@O ze*LXqlKQHiv=gx+V^Cbb2?z@ISBQ*3amF;9UJ3SBg(N|710TLamQmYZ&Qjn2LuO<* zCZlB4n%@pc&7NNnY1}x+NWpHlq`OJEo|`aYN9<`RBUB+79g;>dgb6YlfN#kGL?lO_ z!6~M^7sOnbsUkKk<@Ysie&`G>ruxH&Mgy&8;i=A zB9OO!xR{AyODw>DS-q5YM{0ExFEAzt zm>RdS+ssW(-8|?xr0(?$vBVB*%(xDLtq3Hf0I5yFm<_g=W2`QWAax{1rWVH=I!VrP zs(rTFX@W#t$hXNvbgX`gK&^w_YD;CQ!B@e0QbLIWaKAXQe2-kkloo;{iF#6}z!4=W zi$giRj1{ zt;2w`VSCF#WE&*ev7jpsC=6175@(~nTE2;7M-L((0bH@yG}-TB$R~WXd?tA$s3|%y zA`9$sA(>F%J3ioz<-LJl*^o1|w84l>HBR`>3l9c8$5Xr@xCiIQ7{x$fMCzOk_-M=% z+{a_Q#;42`#KfUte@$NT77uaTz?b-fBe)1s5XE$yA79fm?KqM^VgLXD07*qoM6N<$ Ef<_J(9smFU literal 0 HcmV?d00001 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);