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);